• 改用rclone 來同步靜態文件到S3 和R2

    之前從CloudFlare 改用CloudFront 主要是因為中國訪問的速度不佳,因為CloudFlare 會直接將中國人的訪問送去美國,但是CloudFront 可以選擇送去香港。

    CloudFlare 其實也可以,只不過要付更多錢。

    因此我想同時將wordpress 的靜態文件同步到S3 和R2 ,就當多一個備份。

    修改前的狀態:

    同步工具使用 aws s3 sync,腳本是 /usr/local/bin/aws-cli-sync-s3.sh。

    #!/bin/bash
    /usr/bin/inotifywait -m -r -e modify,create,delete /home/www/bbken.org/wp-conten
    t/uploads/ |
    while read path action file; do
        echo "Change detected: $action $file"
        /usr/bin/aws s3 sync /home/www/bbken.org/wp-content/uploads/ s3://bbken.org/
    wp-content/uploads/ --delete
    done

    由 systemd 服務 s3-sync.service 以 www 使用者執行。腳本用 inotifywait watch /home/www/bbken.org/wp-content/uploads/ 的 modify、create、delete 事件,每偵測到一次事件就對 s3://bbken.org/wp-content/uploads/ 完整同步一次,帶 –delete。但是,這不太通用,移植到其他雲端的時候會有一點問題

    市面上同步工具其實蠻多,但是rclone這東西歷史悠久,就他了。

    未來要擴充到其他雲端的話,添加更多的profile 就可以。aws cli 本質為 S3 設計,連 R2 要靠 –endpoint-url;rclone 原生支援數十種後端,全部共用同一套 rclone sync 指令。

    接下來我們建立並驗證 rclone 的 S3 remote:建一個名為 s3 的 remote,type 選 s3、provider 選 AWS、env_auth 設為 true,直接沿用 EC2 的 IAM role,不需填AK/SK。驗證方式是 rclone lsd s3:,成功列出帳號內所有 bucket 含 bbken.org。

    初次用預設參數空跑耗時 23.9 秒,原因是 rclone 預設比對檔案原始修改時間,該值存在物件的 X-Amz-Meta-Mtime 自訂中繼資料裡,LIST 結果不含這項,必須對每個物件各發一次 HEAD 請求,11358 個檔案就是 1.1 萬次額外請求,需要調整 rclone 參數解決效能問題。

    改用 –fast-list –update –use-server-modtime 後,改以 LIST 就能取得的 LastModified 比對,不再發 HEAD。實測結果是 aws s3 sync 空跑 24.8 秒,rclone 加上這組參數後 2.1 秒,快約 12 倍。

    這個調整對免費仔使用R2 的意義比速度更大:HEAD 在 R2 算 Class B 操作,免費額度每月 1000 萬次,若不調參數,每次同步就消耗約 1.1 萬次,雖然也不是不可以,但平均每天只有3次同步機會似乎顯得略少。

    S3 的同步很簡單,因為不需要AK/SK,接下來在 Cloudflare 建立 bucket 與憑證:建立 bucket bbken-org,產生 R2 API Token 取得 Access Key ID、Secret Access Key 與 endpoint。

    接下來建立並驗證 rclone 的 R2 remote:用 rclone config 新增名為 r2 的 remote,type 選 s3、provider 選 Cloudflare,填入金鑰、region 設 auto、endpoint 設 R2 提供的url,並加上 no_check_bucket = true。設定檔權限設為 600。

    首次完整同步 R2 並驗證,需要從EFS上同步的文件總量為 11358 個檔案、1.048 GiB,約占 R2 免費儲存額度 10 GB 的一成。

    禁用舊腳本,新增同步腳本 /usr/local/bin/rclone-sync-uploads.sh。相較舊版做了五項改動。

    #!/bin/bash
    # 監看 WordPress uploads 目錄,變動時用 rclone 同步到多個目的地
    
    SRC=/home/www/bbken.org/wp-content/uploads/
    RCLONE=/usr/bin/rclone
    DEBOUNCE=5   # 最後一次事件後等待幾秒才同步
    
    # 要擴充目的地,在這裡加一行即可
    DESTS=(
        "s3:bbken.org/wp-content/uploads"
        "r2:bbken-org/wp-content/uploads"
    )
    
    sync_all() {
        for dest in "${DESTS[@]}"; do
            if "$RCLONE" sync "$SRC" "$dest" --fast-list --update --use-server-modtime; then
                echo "Synced to $dest"
            else
                echo "ERROR: sync to $dest failed" >&2
            fi
        done
    }
    
    # 啟動時先完整同步一次,補上服務停止期間漏掉的變更
    sync_all
    
    /usr/bin/inotifywait -m -r -q \
        -e close_write,create,delete,moved_to,moved_from \
        --format '%e %w%f' "$SRC" |
    while read -r event; do
        echo "Change detected: $event"
        # 去抖動:持續讀取後續事件,直到連續 DEBOUNCE 秒沒有新事件
        while read -r -t "$DEBOUNCE" event; do
            echo "Change detected: $event"
        done
        sync_all
    done

    一是目的地改成陣列 DESTS,同時包含 s3:bbken.org/wp-content/uploads 與 r2:bbken-org/wp-content/uploads,用loop逐一同步,未來擴充只需加一行,比如要同步去GCP。

    二是加入 5 秒去抖動,用 read -t 持續吸收後續事件,直到連續 5 秒沒有新事件才同步一次,避免 WordPress 一次上傳產生多個縮圖就觸發多次完整同步。

    三是監看事件由 modify 改為 close_write,避免寫入過程重複觸發,並補上 moved_to 與 moved_from,涵蓋以改名或搬移方式進出目錄的檔案。

    四是服務啟動時先完整同步一次,補上服務停止期間漏掉的變更。

    五是各目的地分開判斷結果,單一目的地失敗會寫入日誌但不影響其他目的地。

    接下來修改 systemd 服務,變更為新的腳本,用 systemctl edit –full s3-sync.service,將 ExecStart 指向新腳本,Description 改為 Uploads Sync Service (rclone to S3 and R2),並把 After 從 network.target 改為 network-online.target 並加上對應的 Wants。接著 daemon-reload 與 restart。

    [Unit]
    Description=Uploads Sync Service (rclone to S3 and R2)
    After=network-online.target
    Wants=network-online.target
    
    [Service]
    Type=simple
    User=www
    ExecStart=/usr/local/bin/rclone-sync-uploads.sh
    Restart=always
    RestartSec=5
    
    [Install]
    WantedBy=multi-user.target

    驗證:目測沒問題,我應該沒瞎。

  • 男人總是有很多工具

    使用多年的Nanch 螺絲刀很早以前就有點問題,extension rod 使用一段時間後,變得很難拉伸出來,使勁拉出來後又會收不回去,然後在拉出來的狀態下,會出現旋轉滑絲的情形,只有收起來的狀態下可以正常使用,在大多數的場景下使用沒有問題,目前遇到CPU 風扇螺絲會有一點點長度不足。

    今天去Costco 看到一組Bosch 的螺絲刀,看起來做工還不錯,就拿了一組,主要是看到他有個很大的握柄,Nanch 的細小手柄在應對腳踏車螺絲的時候會有點力不從心,上次安裝車燈差點擰到我手臂關節痛,當然,這也是因為Fenix 只是個做電筒的,他的支架做工沒有他的電筒做工精良。

    回來立刻把腳踏車頭燈重新擰緊了一下,省力多了。

  • PLEX 定時開關機的問題

    為了響應能源節約的口號,為放置大量電影文件的WD NAS 設置了自動開關機,每天22點會自行關機,每天8點會自行開機。

    這就產生了一個問題,運行Plex media server 的debian OS 檢測不到從NAS export 出來的nfs mount 之後他有一定的幾率會死機。

    所以要對這台運行在ESXi上的VM 進行定時開關機的操作。

    Vmware 官方提供了api,並且提供了一個官方的cli govc,首先當然是安裝他。

    Usage: govc <COMMAND> [COMMON OPTIONS] [PATH]...
    
    govmomi is a Go library for interacting with VMware vSphere APIs (ESXi and/or
    vCenter Server).
    It is licensed under the Apache License, Version 2.0
    
    govc is the CLI for govmomi.
    
    The available commands are listed below. A detailed description of each
    command can be displayed with "govc <COMMAND> -h". The description of all
    commands can be also found at https://github.com/vmware/govmomi/blob/main/govc/USAGE.md.

    使用起來呢,倒是很簡單,主要的問題在於我不能直接使用root 帳戶去操作poweron/shutdown 這類的動作,萬一密碼洩漏了呢。但是,Vmware 也沒有在ESXi v6 的GUI 中提供為新增的用戶賦權的功能。所以,第一次先要使用root 帳戶來為新增的用戶賦權。

    設置幾個env,這裡需要注意的是一定要用單引號,因為特殊字符密碼可能會被吃掉:

    export GOVC_URL=https://10.1.1.2
    export GOVC_USERNAME='root'
    export GOVC_PASSWORD='IAM-root-password'
    export GOVC_INSECURE=1

    看一下about

    govc about
    FullName:     VMware ESXi 6.7.0 build-15160138
    Name:         VMware ESXi
    Vendor:       VMware, Inc.
    Version:      6.7.0
    Build:        15160138
    OS type:      vmnix-x86
    API type:     HostAgent
    API version:  6.7.3
    Product ID:   embeddedEsx
    
    govc host.info 
    Name:              esxi-s01.bbken.net
      Path:            /ha-datacenter/host/esxi-s01.bbken.net
      Manufacturer:    HP
      Logical CPUs:    6 CPUs @ 2496MHz
      Processor type:  12th Gen Intel(R) Core(TM) i5-12400
      CPU usage:       4609 MHz (30.8%)
      Memory:          40644MB
      Memory usage:    35268 MB (86.8%)
      Boot time:       2026-09-13 09:15:48.082707 +0000 UTC
      State:           connected

    列舉一下當前有的這些VM,看起來還蠻多的呢,不過都沒開機:

    govc ls /ha-datacenter/vm 
    /ha-datacenter/vm/pfSense
    /ha-datacenter/vm/PLEX
    /ha-datacenter/vm/Webserver
    /ha-datacenter/vm/Windows10
    /ha-datacenter/vm/Alteon
    /ha-datacenter/vm/ROS
    /ha-datacenter/vm/OpenWRT
    /ha-datacenter/vm/Windwos10CHN
    /ha-datacenter/vm/ROS7
    /ha-datacenter/vm/Debian
    /ha-datacenter/vm/RHEL8
    /ha-datacenter/vm/FaceFusion

    看一下PLEX 這台VM 的詳細:

    govc vm.info "PLEX"
    Name:           PLEX
      Path:         /ha-datacenter/vm/PLEX
      Guest name:   Debian GNU/Linux 10 (64-bit)
      Memory:       8192MB
      CPU:          8 vCPU(s)
      Power state:  poweredOn
      Boot time:    2026-09-30 03:10:46.763837 +0000 UTC
      IP address:   10.1.1.5
      Host:         s01.plex.tv

    graceful shutdown 的命令是這樣:

    govc vm.power -s "PLEX"

    加電的命令是這樣:

    govc vm.power -on "PLEX"

    測試了一下開關機,沒問題。

    啊,要先創建一個reboot user,我們就叫reboot-user好了,從ESXi的GUI 創建這個用戶,然後使用root用戶操作govc 賦權:

    govc role.create vmpower VirtualMachine.Interact.PowerOn VirtualMachine.Interact.PowerOff VirtualMachine.Interact.Reset
    
    govc permissions.set -principal reboot-user -role vmpower -propagate=true

    完成後看一下:

    govc permissions.ls /
    Role     Entity  Principal    Propagate
    vmpower  /       reboot-user  Yes
    Admin    /       vpxuser      Yes
    Admin    /       dcui         Yes
    Admin    /       root         Yes

    看起來沒問題,接下來重新設置env,使用剛才創建的reboot-user和他的密碼:

    export GOVC_URL=https://10.1.1.2
    export GOVC_USERNAME='reboot-user'
    export GOVC_PASSWORD='reboot-user-password'
    export GOVC_INSECURE=1
    
    govc vm.info "PLEX" | grep "Power state"
    Power state:  poweredOn

    寫兩個腳本來加入到FreeBSD 的計劃任務:

    vm-stop.sh:
    
    #!/bin/sh
    export GOVC_URL=https://10.1.1.2
    export GOVC_USERNAME='reboot-user'
    export GOVC_PASSWORD='reboot-user-password'
    export GOVC_INSECURE=1
    /usr/local/bin/govc vm.power -s "PLEX"
    vm-start.sh:
    
    #!/bin/sh
    export GOVC_URL=https://10.1.1.2
    export GOVC_USERNAME='reboot-user'
    export GOVC_PASSWORD='reboot-user-password'
    export GOVC_INSECURE=1
    /usr/local/bin/govc vm.power -on "PLEX"

    設定權限(腳本含有密碼,請務必設置為 700):
    chmod 700 vm-start.sh
    chmod 700 vm-stop.sh

    crontab -e 加入这两行(21:30 关机、08:30 开机):

    30 21 * * * /vm-stop.sh >> /tmp/vm.log 2>&1
    30 8 * * * /vm-start.sh >> /tmp/vm.log 2>&1