Author: kenng

  • 小心 Server 被人盜用來挖礦或者成為肉雞!

    小心 Server 被人盜用來挖礦或者成為肉雞!

    電腦業行內稱肉雞是什麼? 正確用語應該是 “殭屍電腦“,殭屍電腦又是什麼呢? 總括來說就是被入侵了,電腦的控制權已經被人盜用了,連機主自己都不知道自己的電腦已授人控制。

    好多時 D 大規模電腦攻擊事件,就是黑客遙控 “肉雞”作出的攻擊行為。

    舊的 windows 系統,例如:95,98, XP 都是很容易成為 “肉雞”, 1.系統漏洞多很容易受到攻擊 2. 當時的防毒軟件功能尚未完善

    不要以為只有 windows 系統才容易被人入侵,事實上因為 windows 系統用戶量最多,成功入侵 “肉雞”數量可觀,而 linux OSX 等系統用戶量偏少,設計安全性較周詳,但都有漏洞給黑客入侵。

    題外話,CPU 設計缺陷都會有漏洞成為黑客入侵的後門 ,而有些地區政府要求晶片製造商要在其生產的晶片要有後門程式。

    以下連結是維基百科的詮釋:

    以下是我自己的經歷 , 前陣子我的 VPS server 死了, 可以睇前兩章 VPS 之死 .

    server 要重新安裝 , 安裝好了, 又把資料重新滙入新機裏 , 運行了一會兒, 在某一天我突然發覺 CPU time 成日到達 80-100% 使用率. 所以我嘗試尋找那些程序使用那麼多 CPU 資源.

    最後發現在 /tmp 目錄運行著兩個程序 , 一個是 kinsing & kdevtmpfsi , 我在網上找, 很多網站說是挖礦病毒 >_< .

    以下網址是可在網上找到的 :

    How to Fix the kdevtmpfsi and kinsing Mining Virus Infection on an Ubuntu Server

    How to Remove Kinsing (kdevtmpfsi) Malware

    记一次云服务器 CPU 爆满原因—被 kdevtmpfsi 挖矿病毒入侵 – Cheyaoyao – 博客园

    Linux服务器kdevtmpfsi挖矿病毒解决方法:治标+治本-CSDN博客

    我的 server 被盜用情況 , CPU 差不多滿載運行

    我用 VI 看看 kinsing 內容, 需然是 執行檔 , 但可以見到有個 create shell script 的描述

    我找了很久究竟是那一個軟件出現問題 , 還是在網上的 source file 已經被植入那病毒,真的不太清楚。

    我嘗試了會不會是 apache 的 cgi-bin 出現漏洞,deny all cgi-bin folder,又試過一些軟件程序內有 cron job progam disable。

    最後用了很多方法未能從軟件方面解決是否植入病毒,或軟件內的 api 介面入手。

    所以只好從作業系統方面入手了。

    1. 可以建立一個 tmp folder, mount option(nodev,noexec,nosuid)可以參考這個網址教學 https://md3v.com/mount-the-tmp-partition-with-noexec-and-nosuid-options , 雖然個病毒執行檔仍然在 /tmp folder,但已經不能執行了,你可以用 cron job 定期清除這些病毒檔。
    2. 你可以把 php.ini upload_tmp_dir 改成另一個 folder,當然這個 folder 要 noexec,nosuid,nodev
    3. 最極端方法就是把系統 /tmp folder 設定成 read only,但如果設定是 read only 可能影響系統 system update 之類的更新 , 所以最好用方法 1. +方法2. 組合
    4. 還有另一選擇,就是把 apache php mysql 等服務用 docker 呈現,對外的設定為 read only , 另一組對內的可 read write 用來更新內容用的 !

  • 用開源軟件製作 QR CODE 生成網頁

    用開源軟件製作 QR CODE 生成網頁

    大家應該很多時都會用到 QR CODE 有些網頁要付費才能用,有些免費但有些廣告彈出,今次我就介紹如何用開源軟件製作自 QR 產生器。

    我們使用 chillerlan/php-qrcode 庫, Apache, php 軟件

    For the QR Code reader, either ext-gd or ext-imagick is required!

    chillerlan/php-qrcode 用 composer 安裝

    開工

    先安裝好 linux 系統我用 ubutu 作演示 , 如果你沒有獨立的 linux server , 可以在 windows 裝 vritualbox , 作測試之用 又或者買 raspberry pi, orange pi 等等做一做 mini server .

    # 先安裝 apache web server
    apt install apache2
    
    # 再安裝 composer 
    apt install composer 

    php 要求8.4+ , ubuntu 官方 php package 是 8.3 , 所以用另一方法安裝其他版本的 php

    # 先 update package source
    sudo apt update
    
    # 再 Add PHP Repository 
    sudo apt install software-properties-common gnupg2  apt-transport-https lsb-release ca-certificates
    
    sudo add-apt-repository ppa:ondrej/php
    
    # 安裝 php8.4 
    sudo apt install php8.4 php8.4-cli php8.4-gd php8.4-imagick php8.4-zip php8.4-xml php8.4-common php8.4-mbstring
    
    # apache2 啟用 php8.4
    a2enmod php8.4

    安裝 chillerlan/php-qrcode

    # 在你的 home 建立新 folder 把 php-qrcode 下載到該 folder 
    
    cd ~
    mkdir temp 
    cd temp
    composer require chillerlan/php-qrcode

    這時 temp folder 內應該有 “vendor” folder 把這個 folder copy 到 “/var/www/html” 裏面 , 再把 index.php 製作出來, 就用 AI 生成吧 !

    我用 deepseek 生成 index.php , 這個 file 包含 logo 拼合

    <?php
    require_once 'vendor/autoload.php';
    
    use chillerlan\QRCode\QRCode;
    use chillerlan\QRCode\QROptions;
    use chillerlan\QRCode\Common\EccLevel;
    use chillerlan\QRCode\Output\QRGdImagePNG;
    
    $qrCodeBase64 = '';
    $error = '';
    $outputPath = '';
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['text'])) {
        $text = trim($_POST['text']);
        
        if (empty($text)) {
            $error = '请输入要编码的文本或 URL。';
        } else {
            try {
                // 配置二维码选项 - 使用 QRGdImagePNG 输出类
                $options = new QROptions([
                    'outputInterface'  => QRGdImagePNG::class,  // PNG 输出
                    'eccLevel'         => EccLevel::H,          // 高纠错级别(加 Logo 必需)
                    'version'          => 7,
                    'scale'            => 10,
                    'outputBase64'     => false,
                    'imageTransparent' => true,
                ]);
                
                // 生成二维码
                $qrcode = new QRCode($options);
                $qrPngData = $qrcode->render($text);
                
                // 从二进制数据创建 GD 图像资源
                $qrImage = imagecreatefromstring($qrPngData);
                
                if (!$qrImage) {
                    throw new Exception('无法创建二维码图像');
                }
                
                // 处理 Logo 上传
                if (isset($_FILES['logo']) && $_FILES['logo']['error'] === UPLOAD_ERR_OK) {
                    $logoFile = $_FILES['logo']['tmp_name'];
                    $logoInfo = getimagesize($logoFile);
                    
                    if ($logoInfo && in_array($logoInfo[2], [IMAGETYPE_PNG, IMAGETYPE_JPEG, IMAGETYPE_WEBP])) {
                        switch ($logoInfo[2]) {
                            case IMAGETYPE_PNG:
                                $logoImage = imagecreatefrompng($logoFile);
                                break;
                            case IMAGETYPE_JPEG:
                                $logoImage = imagecreatefromjpeg($logoFile);
                                break;
                            case IMAGETYPE_WEBP:
                                $logoImage = imagecreatefromwebp($logoFile);
                                break;
                            default:
                                $logoImage = null;
                        }
                        
                        if ($logoImage) {
                            $qrWidth = imagesx($qrImage);
                            $qrHeight = imagesy($qrImage);
                            $logoWidth = imagesx($logoImage);
                            $logoHeight = imagesy($logoImage);
                            
                            // Logo 占二维码的 20%
                            $logoMaxWidth = (int)($qrWidth / 5);
                            $logoMaxHeight = (int)($qrHeight / 5);
                            
                            $scale = min($logoMaxWidth / $logoWidth, $logoMaxHeight / $logoHeight, 1);
                            $newLogoWidth = max(1, (int)($logoWidth * $scale));
                            $newLogoHeight = max(1, (int)($logoHeight * $scale));
                            
                            $dstX = (int)(($qrWidth - $newLogoWidth) / 2);
                            $dstY = (int)(($qrHeight - $newLogoHeight) / 2);
                            
                            $resizedLogo = imagecreatetruecolor($newLogoWidth, $newLogoHeight);
                            imagealphablending($resizedLogo, false);
                            imagesavealpha($resizedLogo, true);
                            $transparent = imagecolorallocatealpha($resizedLogo, 0, 0, 0, 127);
                            imagefilledrectangle($resizedLogo, 0, 0, $newLogoWidth, $newLogoHeight, $transparent);
                            
                            imagecopyresampled($resizedLogo, $logoImage, 0, 0, 0, 0, 
                                              $newLogoWidth, $newLogoHeight, $logoWidth, $logoHeight);
                            
                            imagealphablending($qrImage, true);
                            imagecopy($qrImage, $resizedLogo, $dstX, $dstY, 0, 0, 
                                     $newLogoWidth, $newLogoHeight);
                            
                            imagedestroy($logoImage);
                            imagedestroy($resizedLogo);
                        }
                    }
                }
                
                // 转为 Base64 显示
                ob_start();
                imagepng($qrImage);
                $qrPngData = ob_get_clean();
                $qrCodeBase64 = 'data:image/png;base64,' . base64_encode($qrPngData);
                
                // 保存文件
                if (!is_dir('qrcodes')) {
                    mkdir('qrcodes', 0777, true);
                }
                $outputPath = 'qrcodes/qr_' . time() . '.png';
                imagepng($qrImage, $outputPath);
                
                imagedestroy($qrImage);
                
            } catch (Exception $e) {
                $error = '生成二维码失败:' . $e->getMessage();
            }
        }
    }
    ?>
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <title>带 Logo 的二维码生成器</title>
        <style>
            * { margin: 0; padding: 0; box-sizing: border-box; }
            body {
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                min-height: 100vh;
                padding: 20px;
            }
            .container {
                max-width: 700px;
                margin: 0 auto;
                background: white;
                border-radius: 20px;
                padding: 40px;
            }
            h1 { text-align: center; margin-bottom: 30px; color: #333; }
            .form-group { margin-bottom: 20px; }
            label { display: block; margin-bottom: 8px; font-weight: 500; color: #555; }
            textarea, input[type="file"] {
                width: 100%;
                padding: 12px;
                border: 2px solid #e0e0e0;
                border-radius: 10px;
                font-size: 14px;
            }
            textarea:focus, input:focus { outline: none; border-color: #667eea; }
            button {
                width: 100%;
                padding: 12px;
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                color: white;
                border: none;
                border-radius: 10px;
                font-size: 16px;
                font-weight: 600;
                cursor: pointer;
            }
            button:hover { transform: translateY(-2px); }
            .result { text-align: center; margin-top: 30px; }
            .qr-container { margin: 20px 0; }
            .qr-container img { max-width: 100%; border-radius: 10px; }
            .download-link {
                display: inline-block;
                padding: 10px 20px;
                background: #4CAF50;
                color: white;
                text-decoration: none;
                border-radius: 8px;
            }
            .error { background: #fee; color: #c33; padding: 10px; border-radius: 8px; margin-top: 20px; }
            .tip { background: #e8f0fe; padding: 10px; border-radius: 8px; margin-top: 15px; font-size: 13px; }
        </style>
    </head>
    <body>
        <div class="container">
            <h1>📱 带 Logo 的二维码生成器</h1>
            
            <form method="POST" enctype="multipart/form-data">
                <div class="form-group">
                    <label>文本或 URL:</label>
                    <textarea name="text" rows="3" placeholder="https://github.com"></textarea>
                </div>
                <div class="form-group">
                    <label>Logo 图片(可选):</label>
                    <input type="file" name="logo" accept="image/png,image/jpeg,image/webp">
                </div>
                <button type="submit">✨ 生成二维码</button>
            </form>
            
            <?php if ($error): ?>
                <div class="error">⚠️ <?php echo htmlspecialchars($error); ?></div>
            <?php endif; ?>
            
            <?php if ($qrCodeBase64): ?>
                <div class="result">
                    <div class="qr-container">
                        <img src="<?php echo $qrCodeBase64; ?>" alt="QR Code">
                    </div>
                    <?php if ($outputPath): ?>
                        <a href="<?php echo $outputPath; ?>" download class="download-link">💾 下载 PNG</a>
                    <?php endif; ?>
                </div>
            <?php endif; ?>
            
            <div class="tip">
                💡 提示:需要 PHP 8.2+ 和 GD 扩展。Logo 会自动居中缩放,建议使用透明背景 PNG。
            </div>
        </div>
    </body>
    </html>

    如果順利的話 , 你開瀏覽器輸入你的 server ip

    這是最基本應用 , 你還可以叫 deepseek 加上不同的內容 , 例如 : QR CODE 底色等等 …….

    Demo 測試演示

    login name & password = demo

  • 重新審視 XSS 攻擊的可能

    重新審視 XSS 攻擊的可能

    事源近日同同事閒談時,她說女兒學校比黑客入侵,全校系統停擺對外對內都是。

    所以我又重新審視 kenng.hk 現有系統的安全性,雖然我的網站內的資料沒有什麼價值,但都希望學習加強保安的知識。

    跨網站指令碼(英語:Cross-site scripting,通常簡稱為:XSS)是一種網站應用程式的安全漏洞攻擊,是代碼注入的一種。它允許惡意使用者將程式碼注入到網頁上,其他使用者在觀看網頁時就會受到影響。這類攻擊通常包含了HTML以及使用者端手稿語言

    XSS攻擊通常指的是通過利用網頁開發時留下的漏洞,通過巧妙的方法注入惡意指令代碼到網頁,使使用者載入並執行攻擊者惡意製造的網頁程式。這些惡意網頁程式通常是JavaScript,但實際上也可以包括JavaVBScriptActiveXFlash或者甚至是普通的HTML。攻擊成功後,攻擊者可能得到更高的權限(如執行一些操作)、私密網頁內容、對談cookie等各種內容。

    更多詳情可以參考以下網站:

    https://www.cloudflare.com/zh-tw/learning/security/threats/cross-site-scripting

    https://baike.baidu.com/item/XSS%E6%94%BB%E5%87%BB/954065

    我自己就使用 CSP 來做一些保障措施 , 那麼什麼是 CSP 呢?

    内容安全策略(CSP)

    内容安全策略CSP)是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本(XSS)和数据注入攻击等。无论是数据盗取、网站内容污染还是恶意软件分发,这些攻击都是主要的手段。

    CSP 被设计成完全向后兼容(除 CSP2 在向后兼容有明确提及的不一致; 更多细节查看这里 章节 1.1)。不支持 CSP 的浏览器也能与实现了 CSP 的服务器正常工作,反之亦然:不支持 CSP 的浏览器只会忽略它,如常运行,默认为网页内容使用标准的同源策略。如果网站不提供 CSP 标头,浏览器也使用标准的同源策略

    为使 CSP 可用,你需要配置你的网络服务器返回 Content-Security-Policy HTTP 标头(有时你会看到 X-Content-Security-Policy 标头,但那是旧版本,并且你无须再如此指定它)。

    除此之外,<meta> 元素也可以被用来配置该策略,例如

    htmlCopy

    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; img-src https://*; child-src 'none';" />
    

    备注:某些功能(例如发送 CSP 违规报告)仅在使用 HTTP 标头时可用。

    威胁

    缓解跨站脚本攻击

    CSP 的主要目标是减少和报告 XSS 攻击。XSS 攻击利用了浏览器对于从服务器所获取的内容的信任。恶意脚本在受害者的浏览器中得以运行,因为浏览器信任其内容来源,即使有的时候这些脚本并非来自于它本该来的地方。

    CSP 通过指定有效域——即浏览器认可的可执行脚本的有效来源——使服务器管理者有能力减少或消除 XSS 攻击所依赖的载体。一个 CSP 兼容的浏览器将会仅执行从白名单域获取到的脚本文件,忽略所有的其他脚本(包括内联脚本和 HTML 的事件处理属性)。

    作为一种终极防护形式,始终不允许执行脚本的站点可以选择全面禁止脚本执行。

    缓解数据包嗅探攻击

    除限制可以加载内容的域,服务器还可指明哪种协议允许使用;比如(从理想化的安全角度来说),服务器可指定所有内容必须通过 HTTPS 加载。一个完整的数据安全传输策略不仅强制使用 HTTPS 进行数据传输,也为所有的 cookie 标记 secure 标识,并且提供自动的重定向使得 HTTP 页面导向 HTTPS 版本。网站也可以使用 Strict-Transport-Security HTTP 标头确保连接它的浏览器只使用加密通道。

    使用 CSP

    配置内容安全策略涉及到添加 Content-Security-Policy HTTP 标头到一个页面,并配置相应的值,以控制用户代理(浏览器等)可以为该页面获取哪些资源。比如一个可以上传文件和显示图片页面,应该允许图片来自任何地方,但限制表单的 action 属性只可以赋值为指定的端点。一个经过恰当设计的内容安全策略应该可以有效的保护页面免受跨站脚本攻击。本文阐述如何恰当的构造这样的标头,并提供了一些例子。

    https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Guides/CSP

    https://www.cnblogs.com/wuguanglin/p/XSS.html

    https://aszx87410.github.io/beyond-xss/ch2/xss-defense-csp

    https://csper.io/evaluator

    https://csp-evaluator.withgoogle.com

  • VPS 安裝 Arch Linux 教學

    VPS 安裝 Arch Linux 教學

    之前已完成的文件 , 又要重新寫一次 , 事因 VPS 供應商的問題 , 導致所有資料盡失

    開始了! 首先你要確定你的 VPS 最少有 2GB RAM 才能有足夠的 RAM DISK 作為安裝 Arch Linux 的 temp root file system。

    你的 VPS 安裝好 Debain 系統先 , 因為接下來用 Grml 軟件才能作為 arch linux 安裝的引導 , 詳情可瀏覽

    https://grml.org

    在 debian console shell 安裝 grml-rescueboot

    apt -y update
    apt -y install grml-rescueboot

    Download grml iso 鏡像檔

    # 可以使用如下命令自動下載最新的 grml iso, 但只能 download full 版本的 iso
    update-grml-rescueboot
    
    # 以我的經驗如果RAM 只有 2GB Ram, 最好用 small 版本的 iso 成功率高很多 , 可以瀏覽 https://download.grml.org
    cd /boot/grml
    wget https://mirror-hk.koddos.net/grml/grml-small-2025.12-amd64.iso

    請先自己記下當前 debain 的網絡訊息 , 用以下 command

    ip address   # 查看現時的 IP 及 netmask 
    ip route     # 查看現時的 default route ip address

    Edit 設定檔 , 這個設定檔是 grml 引導的設定包括暫時 ssh password 等信息.

    vi /etc/default/grml-rescueboot
    
    CUSTOM_BOOTOPTIONS="ssh=password lang=us keyboard=us tz=Asia/Hong_Kong ip=192.168.1.50::192.168.1.1:255.255.255.0:grmlhost:ens3:none dns=8.8.8.8,8.8.4.4 toram"
    
    #其中:
    #192.168.1.50 → 客戶端 IP
    #:: → 省略 NFS server IP(非必填)
    #192.168.1.1 → Gateway
    #255.255.255.0 → Netmask
    #grmlhost → 主機名稱
    #ens3 → 網卡名稱
    #none → 不使用自動設定(如 DHCP)
    
    #如果你用 DHCP 的設定
    CUSTOM_BOOTOPTIONS="ssh=password lang=us keyboard=us tz=Asia/Hong_Kong toram"

    如果你有其他設定需求可以參考以下網頁 grml-rescueboot

    更新 GRUB BOOT 配置

    #先新增 /usr/sbin/ 目錄入 PATH 裏面
    export PATH=$PATH:/usr/sbin/
    
    #之後執行
    update-grub

    更新 grub 記錄, 使下一次啟動時引導到 arch linux 安裝步驟

    # 使用 full iso 的使用如下命令
    grub-reboot "Grml Rescue System (grml-full_20xx.xx.iso)"
    # 使用 small iso 的使用如下命令
    grub-reboot "Grml Rescue System (grml-small_20xx.xx.iso)"

    reboot 系統進入 arch linux 安裝程序

    sync
    reboot

    用 VPS 提供的 VNC CONSOLE 可以看到 BOOT 機情況 , 如果上唔到網可以在這個畫面按下 “e” 就可以手動輸入之前你寫下的網絡訊息 !

    設定完成可以試一試 ping 看看有沒有回應

    Arch Linux 安裝

    以上步驟是在 grml live 引導內進步 , ram disk 有限, 下載完 arch linux base root system 後把 zip 檔 delete

    cd /tmp
    wget https://geo.mirror.pkgbuild.com/iso/latest/archlinux-bootstrap-x86_64.tar.zst
    
    # 解壓 ZST
    tar --use-compress-program=unzstd -xvf archlinux-bootstrap-x86_64.tar.zst
    
    # delete ZST file 增加 ram disk 空間
    rm archlinux-bootstrap-x86_64.tar.zst

    選擇你地區的 source list , 例如 : Hong Kong 當然你可以選擇其他地區, 但可能 update 時會比自己地區 server 慢

    vi /tmp/root.x86_64/etc/pacman.d/mirrorlist
    
    ## Hong Kong
    #Server = https://hk.mirrors.cicku.me/archlinux/$repo/os/$arch
    #Server = https://mirror-hk.koddos.net/archlinux/$repo/os/$arch
    #Server = https://hkg.mirror.rackspace.com/archlinux/$repo/os/$arch
    #Server = https://arch-mirror.wtako.net/$repo/os/$arch
    Server = https://mirror.xtom.com.hk/archlinux/$repo/os/$arch

    避免 chroot 後提示硬碟空間不足

    mount --bind root.x86_64 root.x86_64

    chroot 修改 arch linux root file system

    /tmp/root.x86_64/bin/arch-chroot /tmp/root.x86_64/

    以下操作是在 chroot arch linux root system 內進行

    #初始化 pacman 軟件包管理器 key
    
    pacman-key --init
    pacman-key --populate archlinux

    更新系统並安裝分區需要用到的工具

    pacman -Syyu                 # 更新軟件包管理器的軟件
    pacman -S dosfstools parted  # 安裝劃分分區軟件

    磁碟分區

    # 首先將硬盤轉換為 gpt 類型.
    lsblk                       # 顯示分區情况 找到你想安装的硬盤名
    parted /dev/vda             # run "parted", 進入交互式命令行,進行硬盤類型轉換 ( vda 是我的HD , 如果你的是 sda 或 hda )
    (parted)mktable             # 輸入 mktable
    New disk label type? gpt    # 輸入 gpt 將類型轉換為 gpt 如硬盤有數據警告, 輸入 yes 即可
    quit                        # 最後 quit 退出 parted 命令行交互
    
    cfdisk /dev/vda # 執行分區操作, 分配各個分區大小及類型
    # bios 引導需要分區 BIOS boot, Linux Swap, Linux filesystem
    # uefi 引導需要分區 EFI System, Linux Swap, Linux filesystem
    fdisk -l # 分區結束後, 檢查硬碟分區情况

    分成 1MB BIOS Boot , 3GB linux swap (大約是你的 RAM 1.5倍左右, 如果空間有限可以set細過 RAM 都可以 , 其餘全部做 root ( 當然你可以分開獨立 home folder 另一個分區 )

    如果用 BIOS boot , 用以下 command format ( 多數 VPS 都是用這個 )

    mkfs.ext4  /dev/vda3            # format root 
    mkswap /dev/vda2		# 初始化 swap 分區
    mount /dev/vda3 /mnt	        # mount root 分區到臨時系統
    swapon /dev/vda2		# enable swap 

    如果用 uefi boot , 用以下 command format

    mkfs.ext4  /dev/vda3            # format root 
    mkfs.vfat  /dev/vda1            # 格式化 uefi 分區, 僅 uefi 引導需要
    mkswap /dev/vda2		# 初始化 swap 分區
    mount /dev/vda3 /mnt	        # mount root 分區到臨時系統
    swapon /dev/vda2		# enable swap 
    mount --mkdir /dev/vda1 /mnt/efi   # mount uefi 分區

    安裝基本軟件包

    pacstrap /mnt base base-devel linux linux-headers linux-firmware dhcpcd iwd vim bash-completion grub openssh
    

    生成 fstab 配置文件

    genfstab -U /mnt >> /mnt/etc/fstab

    run exit 退出 chroot 環境

    重新 chroot 到剛剛做好的基本 root file system

    /tmp/root.x86_64/bin/arch-chroot /tmp/root.x86_64/mnt

    出現這訊息不用理會

    設定 time zone

    ln -sf /usr/share/zoneinfo/Asia/Hong_Kong /etc/localtime
    hwclock --systohc
    systemctl enable systemd-timesyncd.service

    配置語言環境, 選擇 en_HK.UTF-8 UTF-8 及 zh_HK.UTF-8 UTF-8 ( 如果你有需要可以選擇你喜歡的語言環境)

    vim /etc/locale.gen

    生成 locale

    locale-gen

    向 /etc/locale.conf 導入内容

    echo 'LANG=en_HK.UTF-8'  > /etc/locale.conf

    設定 host name

    # 加入你想為 server 命名, 例如: ArchLinux
    vim /etc/hostname
    
    # 同時在 /etc/hosts 設定與其匹配
    vim /etc/hosts
    
    # 加入如下内容
    127.0.0.1   localhost
    ::1         localhost
    127.0.1.1   ArchLinux

    設定網絡

    vim /etc/systemd/network/20-wired.network

    內容如下:

    [Match]
    Name=eth0
    
    [Network]
    Address=xx.xx.xx.xx/24 # ipv4 地址
    
    #如果沒有 ipv6 不用加以
    Address=xxx:xxx:xxx:xxx:xxx:xxx:xxx:xxx/80 # ipv6 地址 
    IPv6AcceptRA=no
    
    [Route]
    Gateway=x.xx.xx.1
    GatewayOnLink=yes
    
    # 如果沒有 ipv6 route 不用加以下內容
    [Route]   
    Gateway=xxx:xxx:xxx::1/80
    GatewayOnLink=ye

    如果網絡支持dhcp, systemd-networkd的 dhcp 配置可如下配置

    [Match]
    Name=eth0
    
    [Network]
    DHCP=yes

    設置 systemd-networkd 及 sshd 開機自動執行

    systemctl enable systemd-networkd
    systemctl enable sshd

    配置 dns

    vim /etc/resolv.conf
    
    nameserver 8.8.8.8
    nameserver 8.8.4.4

    setup root password

    passwd root

    安装 grub

    pacman -S grub
    # 對於 bios 引導的機器請使用下面的命令, 注意是安裝 grub 到硬碟而不是某一個分區
    
    # bios boot
    grub-install --target=i386-pc /dev/vda
    
    # uefi only #
    # 如果使用 uefi 引導, 則還需要安裝 efibootmgr
    pacman -S efibootmgr
    # 然後使用下面的命令安裝 grub
    grub-install --target=x86_64-efi --efi-directory=/efi --bootloader-id=GRUB
    # end uefi only #
    
    # 編輯配置文件, 去掉 GRUB_CMDLINE_LINUX_DEFAULT 參數的 quit
    # 添加 net.ifnames=0(避免機器重啟後, 網卡接口名無法與 systemd-networkd 內配置的接口名相匹配, 從而導致機器失聯)
    # 添加 nowatchdog(提高開關機速度), 把 log level 的數值從 3 改成 5(出現系統錯誤方便排錯)
    vim /etc/default/grub
    # 生成 GRUB 所需的配置文件
    grub-mkconfig -o /boot/grub/grub.cfg

    退出 chroot 環境及reboot

    exit
    reboot
  • 2026 年 CloudCone 重大事故停機

    我就是用這間 VPS 供應商,把我的網站放在那裡,過了一個星期還未修復,我的網站 DATA 凍過水! 好彩我還有一份舊備份,花了很多時間重建好!

    今次把 Server 的軟件同時升級 ! 現在我的 Apache 可以支援 HTTP2

    CLOUDCONE 最新情況 , 需要重新 REBUILD 自己的 VPS , 所以之前的 VPS DATA 是沒有了 , 救唔番 ! >_< , 下次續約時會送多兩個月 , 還有一年份半價備份方案優惠可供選購 !

    有關今次的事態可以看看以下網址:

    2026 年 CloudCone 重大事故停机超一周了,还能用吗 – 知乎

    CloudCone 出现 Hypervisor 事故,还能用吗? – VPS之家

    突发!VPS服务商CloudCone遭遇“灭顶之灾”,全网数据遭删除?深扒Virtualizor背后的安全黑洞 – 知乎

  • 用PYTHON每日更新追蹤香港COVID-19確診或到訪大廈名單

    過了農曆新年, 全港疫情大爆發, 好多香港人都會每日上網看看”衛生署”的確診名單 , 尤其對做物管及做公司ADMIN朋友 , 某程度上有少許幫助 , 但間中最好抽查https://www.chp.gov.hk/files/pdf/building_list_chi.pdf

    所以我寫了一個小程式來追蹤指定大廈是否在確診名單內

    本身我寫的程式在 LINUX 運行 , 每天會自動執行一次再電郵給我 , 但好多人公司或家用電腦都是行 windows 所以我修改程式適合在 windows 行及輸出檔案是 EXCEL FILE

    設計該程式概念 : 先下載衛生署 PDF 名單 , 轉換成文字檔方便程式讀取 , 之後程式核對 自定名單同衛生署名單有沒有匹配 , 記錄你要找的數據 , 把文字檔轉換成 EXCEL FILE

    步驟一:

    先安裝 windows 版 OPENJDK 下載網址:https://docs.microsoft.com/zh-tw/java/openjdk/download

    64bit windows 下載64bit版, 32bit windows 下載32bit版

    下載完直接安裝

    步驟二:

    安裝 Python 程式語言 , 下載網址: https://www.python.org/downloads/release/python-3102/

    下載完直接安裝

    完成後要裝程序需要的python模組

    按 “win key” + R , 執行 “CMD”

    升級 python pip 下載器 , 在 “命令提示字元” 內 輸入 :

    pip install –upgrade pip

    注意 : –upgrade 前是兩個 – 減號

    安裝python模組, 在 “命令提示字元” 內 輸入 :

    pip3 install requests tabula-py Workbook datetime openpyxl

    步驟三:

    如果你的電腦已經有軟件可以開啟 UTF8 FILE可以 忽略這個

    安裝 notepad++ 軟件 , 免費好用 ! 下載網址: https://notepad-plus-plus.org/downloads/

    步驟四:

    我編寫的程式 : Download

    解壓後內有3個FILE , “run.cmd” “main.py” “udata.csv”

    值得注意的是 , 有些朋友會 “不經意” 把繁簡字混亂使用 , 例子: “厦” 及 “廈” 前者簡體 後者繁體 ! 對系統來說這是不同的字 , 如果你輸入的 “某某大厦” 是會找不到的, 應該”某某大廈”才能找到 , 所以我建議不要用全名, 用 “某某”

    用 notepadd++ 修改 udata.csv , 編碼 utf-8

    run.cmd 內容如下所示

    double click “run.cmd” 會執行程式, 如下圖

    同時會有5個FILE 產生 , “building_list_chi.pdf” 是從衛生署下載的名單 , “data.csv” 是從PDF轉換成文字檔, “email.csv” 是輸出文字備用EMAIL, “record.csv” 是記錄你找到的記錄, “checklist.xlsx” 是可以用 EXCEL 開啟的記錄

    備註: 所有 CSV 檔案都是 UTF8 編碼, 用 EXCEL 開會亂碼 , 如果真的要開用 NOTEPAD++ 或者用 libreoffice 都可以正常顯示中文字

    如有疑問可電郵至 info@kenng.hk

  • Orangepi + “MOS場效應管” 控制風扇開關

    Orangepi + “MOS場效應管” 控制風扇開關

    我平時試程式或其他Linux service , 都會用 orange pi zore 安裝 armbian 作為 server , 但這板子有點燙 , 所以我就想用板上的 GPIO 上的 5V , 推動5V風扇用作散熱

    把風扇直接接上 pin4 正極 及 pin6 接地 , 風扇就會轉動 , 但在家中安靜時風扇的噪音有點大

    所以我就想用温度控制風扇的開關 , 減少噪音的時間, 但兩腳的風扇開關只能用通電及斷電來控制 .

    我可以選擇 “三極管” 或 “MOS管” 來作開關 , 我手頭上有些 A2SHB N-MOS , 所以我今次用 MOS管 做示範 .

    以我所理解 MOS管用電壓來控制開關 , 三極管用電流控制開關 . 如果想詳細了解可以上網找相關內容, 我提供一個給大家參考

    URL = 用理论告诉你 三极管和MOS管的区别在哪-KIA MOS管 (kiaic.com)

    今次我用的 MOS 是 SI2302 印有 A2SHB 字樣 (好細粒)

    想了解一吓 MOS 管原理, 所以看看以下 youtube 視頻

    我的構想圖如下:

    不是每款MOS管的 vgs(th) 都是 1.2V , 我手上的 SI2302 的 vgs(th) 是1.2V 就能打開MOS管, 其他型號你要上網找找 vgs(th) 參數

    好了我們開始實作吧 , 先把細小的 A2SHB 焊在電路板固定 , MOS管D極焊同風扇負極接上 , MOS管S極同 GPIO (GND) 焊上 , MOS管G極同GPIO控制端口接上(我用了pin8 作為控制端口)

    記住 orangepi zero pin 4 正極 , pin 6 負極 , 我用 pin 7 控制風扇

    把風扇控制板與 orangepi zero 連接後, 以下就來寫控制風扇的程序了 !

    我的 orangepi 是行 armbian linux , 所以我的程序會用 python 3.x 所寫 , 先用 python 的安裝程式 pip , 裝上 OPi.GPIO library

    https://pypi.org/project/OPi.GPIO/

    下載 onoff_fan.zip 內有 onoff_fan.py 程序

    程序內容如下:

    #!/usr/bin/python3
    # -*- coding: UTF-8 -*-
    import OPi.GPIO as GPIO   # 載入 orangepi api
    import time
    FAN_PIN=7    # 風扇控制端
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(FAN_PIN, GPIO.OUT) #把風扇控端設置為輸出模式
    start_temp_value=45   #風扇ON溫度
    stop_temp_value=40   #風扇OFF溫度
    
    def get_temp():   # 讀取 CPU 溫度 function
        with open('/sys/class/thermal/thermal_zone0/temp') as fp:
            return int(fp.read()) // 1000
    
    try:
        while True:  # loop
            cputemp = get_temp()    # call 溫度 function
            if cputemp < stop_temp_value:
                GPIO.output(FAN_PIN, GPIO.LOW) # 如果CPU溫度降低, 控制端輸出低電壓 MOS 關閉
            elif cputemp > start_temp_value:
                GPIO.output(FAN_PIN, GPIO.HIGH) # 如果CPU溫度升高, 控制端輸出高電壓 MOS 開啓
            time.sleep(10)  # 暫停 10 秒
    except Exception as e:
        print(e)
    
    GPIO.cleanup(FAN_PIN)
    

    稍微講解程序內一些段落

    def get_temp():
             with open(‘/sys/class/thermal/thermal_zone0/temp’) as fp: 
                       return int(fp.read()) // 1000

    這一段定義 get_temp()  function  , 內容是讀取系統內CPU溫度的數據 , 之後 result 時把數據   / 1000  , 就會得出我們平時多少  XX 度了

    #循環開始
            while True: 

    #CALL function 讀取溫度數據 
                cputemp = get_temp() 

    #判斷溫度是否低於我們設定風扇停轉的數值, 如果 “TRUE” 就把 pin7 輸出設定為低電壓, MOS 管截止 , 風扇迴路成為開路, 沒有電流流過而停轉
                if cputemp < stop_temp_value:
                     GPIO.output(FAN_PIN, GPIO.LOW) 

    #判斷溫度是否高於我們設定風扇開啟的數值, 如果 “TRUE” 就把 pin7 輸出設定為高電壓, MOS 管導通 , 風扇迴路成為閉路, 有電流流過而轉動
                elif cputemp > start_temp_value:
                    GPIO.output(FAN_PIN, GPIO.HIGH)

    #程序暫停10秒才進入下一個循環
                 time.sleep(10) 

     

  • 用Arduino 控制全彩LED(紅,綠,藍) 三原色調配顏色

    用Arduino 控制全彩LED(紅,綠,藍) 三原色調配顏色

    我們先準備一些材料 , arduino nano , 紅綠藍LED各一, 一粒全彩LED, 3個按鈕,3粒10KΩ電阻(按鈕用) , 6粒150Ω(保護LED用) . 注意我的圖片是用了470Ω , 因為我找不到150Ω

    先看看以下是我的材料圖片

    我用了模擬器製作了一個接線圖給大家參考

    首先我們先了解一下, 脈波寬度調變(英語:Pulse-width modulation,縮寫:PWM)參考網址 : https://zh.wikipedia.org/wiki/脈衝寬度調變 , https://a091234765.pixnet.net/blog/post/399625162-%5B筆記%5Darduino實驗五%3Apwm調控

    我嚐試去表達一下自己的理解 , 如果不正確請不要駡我, 只是我個人的理解能力有限 !

    首先我們假設10秒是一個周期 , 我們有個水桶 , 有一個水龍頭 . 例子1: 打開水龍頭, 水桶開始注水 , 10 秒周期水龍頭都是開著的,水桶就滿了 . 例子2 : 打開水龍頭, 10 秒周期裏水龍頭只開了9秒, 那水桶就只有90%滿 , 例子3 : 打開水龍頭, 10 秒周期裏水龍頭只開了8秒, 那水桶就只有80%滿 以此類推 .

    PWM 用上述例子解釋 , 占空比= 水龍頭開 , 水龍頭開=5V , 水龍頭關=0V , 如果周期內占空比滿戴 , 結果就是 5V , 如果占空比只有75% , 那麼結果是3.75V , 如果占空比只20%, 那麼結果是1V 了 ! 說白了就是控制占空比 , 控制輸出的電壓來控制LED亮度 , 這就是我們今次的目的 , 得出三原色不同亮度的組合來合成各種顏色

    講完構思, 現在實作吧 ! 開始 …… LED 燈加上限流電阻來保護

    按鈕部分 , 一端接 GND , 一端接5V+10K電阻作為 Arduino 輸入訊號端. 平時不按下去 Arduino 輸入訊號端 會是高電壓 , 按下去的時候接地道通, Arduino 輸入訊號端 會是低壓 ! 高低電壓的輸入就會控制 Arduino 程序的流向

    以下是 Arduino 的程序代碼 , 如有興趣的朋友可以下載來玩玩 下載

    以下是本人寫的代碼 , 不是寫得好好 ! 請見諒 :

    int redled = 15; // 紅色LED接腳
    int greenled = 16; // 綠色LED接腳
    int blueled = 17; // 藍色LED接腳
    int rled = 3; // 全彩LED紅色接腳 PWM
    int gled = 5; // 全彩LED綠色接腳 PWM
    int bled = 6; // 全彩LED藍色接腳 PWM
    int ranum =0; // 全彩LED紅色亮度參數 0 – 255
    int ganum =0; // 全彩LED綠色亮度參數 0 – 255
    int banum=0; // 全彩LED藍色亮度參數 0 – 255
    int rgbbutt=14; // 轉換”紅綠藍”燈按鈕接腳
    int upbutt=7; // 增加亮度按鈕接腳
    int downbutt=8; // 減少亮度按鈕接腳
    int val=0; // 儲存紅綠藍按鈕狀態
    int valup=0; // 儲存增加亮度按鈕狀態
    int valdown=0; // 儲存減少亮度按鈕狀態
    int ledstat=0; // 儲存 “紅綠藍” 當前參數,全滅=0, 紅色=1 , 綠色=2, 藍色=3

    void setup() {
    // put your setup code here, to run once:
    Serial.begin(9600);

    pinMode (redled, OUTPUT); // 設定為輸出狀態
    pinMode (greenled, OUTPUT); // 設定為輸出狀態
    pinMode (blueled, OUTPUT); // 設定為輸出狀態
    pinMode (rled, OUTPUT); // 設定為輸出狀態
    pinMode (gled, OUTPUT); // 設定為輸出狀態
    pinMode (bled, OUTPUT); // 設定為輸出狀態
    pinMode (rgbbutt, INPUT); // 設定為輸入狀態
    pinMode (upbutt, INPUT); // 設定為輸入狀態
    pinMode (downbutt, INPUT); // 設定為輸入狀態
    }

    void loop() {
    // put your main code here, to run repeatedly:
    val = digitalRead(rgbbutt);
    delay(50);
    // Serial.println (val);

    if (val == LOW) {
    val = digitalRead(rgbbutt);
    if (val == HIGH){
    ledstat=ledstat+1;

            if (ledstat == 1) {
              digitalWrite(redled,1);
              digitalWrite(greenled,0);
              digitalWrite(blueled,0);
            }
            else if (ledstat == 2) {
              digitalWrite(redled,0);
              digitalWrite(greenled,1);
              digitalWrite(blueled,0);  
            }
            else if (ledstat == 3) {
              digitalWrite(redled,0);
              digitalWrite(greenled,0);
              digitalWrite(blueled,1);  
            }
            else {
              digitalWrite(redled,0);
              digitalWrite(greenled,0);
              digitalWrite(blueled,0);
              ledstat=0;
            }
    }

    }

    Serial.println (ledstat);
    if (ledstat == 1 ) {
    valup = digitalRead(upbutt);
    valdown = digitalRead(downbutt);
    delay(20);
    if (valup == LOW) {
    if (ranum < 255 ){ ranum = ranum+1; Serial.println (“紅色”+String(ranum)); } } if (valdown == LOW) { if (ranum > 0 ){
    ranum = ranum-1;
    }
    }
    analogWrite(rled,ranum);
    }

    else if (ledstat == 2) {
    valup = digitalRead(upbutt);
    valdown = digitalRead(downbutt);
    delay(20);
    if (valup == LOW) {
    if (ganum < 255 ){ ganum = ganum+1; } } if (valdown == LOW) { if (ganum > 0 ){
    ganum = ganum-1;
    }
    }
    analogWrite(gled,ganum);
    }
    else if (ledstat == 3) {
    valup = digitalRead(upbutt);
    valdown = digitalRead(downbutt);
    delay(20);
    if (valup == LOW) {
    if (banum < 255 ){ banum = banum+1; } } if (valdown == LOW) { if (banum > 0 ){
    banum = banum-1;
    }
    }
    analogWrite(bled,banum);
    }

    Serial.println (String(ranum)+”:”+String(ganum)+”:”+String(banum));
    }

    以下是我自己完成後做的 DEMO 視頻

    理論上可以實現全彩 , 但因為 LED 各色的參數是不一樣 红2.1v,绿2.8v,蓝2.8v,白3.1v ! 我的 DEMO 不能完全的全彩

  • ARDUINO 模擬器

    ARDUINO 模擬器

    近期健伍試用一款模擬器 , 等我介紹一下比大家看看, 我都是初學不是太熟識

    我用的是 AUTODESK TINKERCARD , 網圵 : https://www.tinkercad.com

    我們來CREATE 一個新電路玩玩

    新增了一個試驗板 , 加了 6 顆不同顏色 LED , 在每顆發光二極管加上一個150Ω 電阻(我自己會加大一點點, 我會用470Ω 電阻) , 為什麼要加上電阻? 因為可以防止LED承受過大電壓而燒壞

    如何計算電阻阻值

    LED限流電阻計算公式:限流電阻Ω = (電源電壓V – LED正向穩壓電壓V) / 限流電流A

    設紅色LED正向穩壓電壓2V,電流20mA,電源電壓5V,代入公式就是(5V-2V) / 0.02A = 150Ω

    你們可以參考以下網址 : https://www.gushiciku.cn/dc_news/digital_ZgNU

    用圖塊的方式製作, 就好像積木一樣 , 一顆一顆的接著發亮又關燈

  • ARDUINO NANO 不能寫入 , 有什麼方法重設成出廠狀態呢?

    ARDUINO NANO 不能寫入 , 有什麼方法重設成出廠狀態呢?

    應該有不少朋友會遇到玩死了 自己的 NANO , 原因例如有 : 寫 program 入NANO 時 arduino java error 跳出了 , 當機了都有可能使 arduino nano 出現錯誤不能再寫入 !

    本來諗住要放進垃圾桶內, 但心想上網找找有沒有人同我一樣玩死了 NANO , 找了一會原來可以試吓重新燒錄 bootloader !

    紅色麵包板 NANO 是用來當作燒錄器, 綠色麵包板是不能寫入程序的 NANO

    我們今次會用 ICSP 來重新寫入 bootloader , ICSP 是什麼 ?

    “ICSP 代表電路串行程式設計,這是可用於程式設計 Arduino 板的幾種方法之一。通常,Arduino 引導載入程式用於程式設計Arduino板,但如果引導載入器丟失或損壞,則可以使用ICSP。ICSP 可用於恢復遺失或損壞的引導載入器。”

    用杜邦線連上 被燒錄NANO 的 ICSP , 被燒錄的 ICSP RST 腳連接燒錄器上的 D10 腳 , 如下圖 :

    如果你用作燒錄器的板子不是 NANO , 那你找出 SPI 的引腳連接就是了 .

    另一種接法就是用板子 SPI , 像以下圖片

    完成了接線後, 我們先把作為燒錄器的 NANO 連接上 PC

    打開 Arduino — > File —> Examples —> 11.ArduinoISP

    然後到 Tools 選擇你自己用作 “燒錄器” 的板子 , 我的是 Board: “Arduino Nano” —> Processor: “ATmega328P (Old Bootloader)” —> 我的電腦連接是 Port: “COM12” ( 你的電腦可能是別的 COM PORT )

    如果你用作燒錄器板子是別的型號 , 就要選上正確型號: 例如UNO , 就選UNO

    按 “箭頭” 把 Arduino ISP 程序寫入燒錄器板子

    成功寫入如下圖 :

    凖備工作好了 , 現在開始重寫 bootloader 了 , 我們再次選擇要被燒 bootloader 的板子了 , “Arduino Nano” —> “ATmega328p (Old Bootlooder” , 如果要燒的是 UNO 就選 UNO 板子.

    再選擇 Programmer : “Arduino as ISP”

    之後就可以 Burn Bootloader

    如果成功了, 就會出現 “Done burning bootloader. “

    完成後可以找一些範例 , 寫一次入去重新燒錄bootloader 的板子了 !