2025年3月7日 星期五

Docker 開發筆記 - 在 Synology NAS 運行 immich 照片服務與 Symbolic link 管理方式 @ Synology DS723+


周邊有強者好友真不錯,時常分享把一堆服務都搞自建方案,目的不是省錢,而是追求資料握在自己手中的架構。挑選 immich 大概算是公認做得不錯的相簿管理服務,其介面跟 Google Photos 很像,且支援人臉辨識也有依照地圖(OpenStreetMap)顯示照片拍攝位置


而 immich 官方文件有非常方便架設 Docker 的筆記:
在 Synology NAS 上,若有支援 Docker 的,可以在 Container Manager 中新增專案,這時要上傳 docker-composer.yml 檔案,則是在剛剛的教學網站有顯示:
在此先決定了 NAS 上的檔案位置,把 Docker 運行所需的環境擺在 /docker/immich-app 目錄(這是 File Station 看到的路徑),他實際的路徑位置會是 /volume1/docker/immich-app 。


這時 docker-compose.yml 可透過 Container Manager 上傳好,而 example.env 則是透過 File Station 拖拉進去改名成 .env

這時直接在 Container Manager 運行時,應當會踩到問題,因為 library 或 postgres 目錄不存在,這時也繼續靠 File Station 建立,後續 Container Manager 運行就會正常了,正常到最後可以用 NAS_IP:2283 瀏覽起來,可以看到註冊畫面等。

這邊有幾個議題記錄一下:
  1. 使用 NAS 反向代理伺服器,提供 https 連線 immich 服務
  2. 在 immich 使用外部圖庫 (External Library) ,將原本的 Synology Photos 匯入
  3. 在 immich External Library 如何使用 Symbolic link 來管理
首先,NAS 反向代理伺服器的設定還滿簡單的,找一個 port 來服務 https 連線,例如 2284 ,直接把它導向到 2283 ,這樣就搞定收工,未來就有 https://NAS_IP:2284 可用了!


接著,使用外部圖庫部分,這邊跟 Docker 設定有關,必須把額外的資料像掛載進來,例如

    volumes:
      # Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in the .env file
      - ${UPLOAD_LOCATION}:/usr/src/app/upload
      - /etc/localtime:/etc/localtime:ro
      - /var/services/homes/UserID/Photos:/synology-photo:ro

透過把 Synology 在使用者家目錄的位置掛進來,這樣對 immich docker 環境中,就多了 /synology-photo 路徑可以查看資料,這時在 immich 網頁設定上,直接設定外部位置在 /synology-photo ,就可以讓 immich 掃描到照片資料來分析了

然而,對於資料管理上會想要慢慢實驗,例如少量的把目錄資料加入到 immich 外部圖庫,且不需要一直改 YAML volumes?通常對系統熟悉的,就會想試試 symbolic link 架構,還能避免 NAS 上有重複資料佔著空間,然而,symbolic link 在 Docker 環境上有使用限制,有一些討論串:
我這邊的解法,其實是設法先排除 Docker 限制,只要繞過限制後,Symbolic link 還是可以使用的。目前的設計就先在 ~/Photos/ 建立一個目錄,如 immich ,接著在裡面建立 Symbolic link 到上一層 ~/Photos 想要的資料就好,而掛載到 Docker 裡仍維持在 ~/Photos 位置,而 immich 網頁上就設定 /synology-photo/immich 即可。

總結一下 Symbolic link 使用資訊:
  • Docker volumes YAML 設定不變,概念上要把 Symbolic link 來源也包覆到
    • /var/services/homes/UserID/Photos:/synology-photo:ro
  • 建立 /var/services/homes/UserID/Photos/immich 目錄
  • 在 immich 網頁上,將外部圖庫設定在 /synology-photo/immich
  • 未來想動態將資料交給 immich 服務時,就只須在 /var/services/homes/UserID/Photos/immich 內建立向上一層的目錄,確保那些享用 symbolic link 的資料都有在 Docker volumes 內即可

UserID@NAS:~/Photos/immich$ ls -l
lrwxrwxrwx+ 1 UserID users 27 Mar  6 23:25 '2020-01-01' -> '../2020-01-01/'
lrwxrwxrwx  1 UserID users 24 Mar  6 23:28 '2021-02-02' -> '../2021-02-02/'
lrwxrwxrwx  1 UserID users 27 Mar  6 23:28 '2022-03-03' -> '../2022-03-03/'
  
收工

2025年2月26日 星期三

[macOS] 使用 Windows APP (原 Microsoft RDP app) 遠端登入 Ubuntu 24.04 Desktop 遠端桌面 及 Error code: 0x207 問題排除 @ macOS 15.3.1


把自己的 Pi 5 安裝 Ubuntu 24.04 Desktop 後,原本想要裝 VNC 來提供遠端桌面的,搞了半天才發現已經有內建的 RDP 服務,接著當然就是在 macOS 上找尋很多年前已裝過的 Microsoft Windows Remote Desktop app ,找了半天也沒找到,最後才發現 macOS App Store 已更名為 Windows App

第一次在 macOS 上 Windows APP 登入到 Ubuntu 24.04 Desktop 是正常的,但很詭異的是之後一直會收到錯誤訊息:

We couldn't connect to the remote PC. This might be due to an expired password. If this keeps happening, contact your network administrator for assistance. Error code: 0x207

我也是趁這次才知道,原來 Ubuntu 24.04 Desktop 內建的 RDP 服務,必須使用者先登入過桌面才行,並且鎖住螢幕也不行。事後發現有一連串的方式解套。


首先是使用者必須先登入過桌面,那就開啟自動登入就好,剛好在 Pi 5 實驗裝置不會踩到資安議題。接著是鎖住螢幕的問題,有一派是調整成不自動鎖住,此時發現還可以透過額外安裝 GNOME Extension Manager,並且在裡頭下載 Allow Locked Remote Desktop 就好,設定好重啟。

$ sudo apt install gnome-shell-extension-manager

最後,關於 macOS 上第二次起登入 Ubuntu Desktop 總是碰到的 Error code: 0x207 問題,神人解法是先把設定檔匯出,接著修改關鍵設定,把 "use redirection server name:i:0" 更新成 "use redirection server name:i:1" ,再匯入回去即可搞定。有些討論串認為是 macOS - Windows app 的 bug


搞定

ref:

2025年2月24日 星期一

[Linux] 樹莓派5開發筆記 - Raspberry Pi 5 + Pi AI kit + Pi Mini Tower kit @ Ubuntu 24.04 Desktop



二月初手癢,敗了個 Raspberry Pi AI kit Hailo-8 ,接著發現以前買的 Pi 5 小機殼不能用了,裸機運行了一下,就手癢找找有沒有比較大的機殼,就看到了 樹莓派 5 塔式散熱器外殼組 | Raspberry Pi 5 Mini Tower Kit with ICE Tower ,接著再去蝦皮逛一眼,有看到遠方發貨的,就試了一下。


實際上排線是非常緊繃地連著:


有一邊的腳架不鎖(無法鎖):



最終就這樣被我硬拼了出來,上方的 Pi AI kit 的架子有兩隻腳是懸空的,懸空那面剛好有排線針腳固定著也不太會晃,就這樣胡亂用了起來。

這邊就簡單的完成 Mini Tower Kit 的面板(OLED)初始化,順便小改 code 來達成顯示 IP 和收到關機時可正常退出使螢幕變黑。


目前就包成一個 script 來運行:

user@user-desktop:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 24.04.2 LTS
Release: 24.04
Codename: noble
user@user-desktop:~$ curl https://raw.githubusercontent.com/changyy/study-pi5/refs/heads/main/script/pi5-mini-tower-kit-with-oled-only.sh > /tmp/pi5-mini-tower-kit-with-oled-only.sh
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  6184  100  6184    0     0  11814      0 --:--:-- --:--:-- --:--:-- 11801
user@user-desktop:~$ bash /tmp/pi5-mini-tower-kit-with-oled-only.sh 
...

收工

2025年2月21日 星期五

Python 開發筆記 - promptpack-for-code 一個將專案的目錄結構和指定檔案輸出單一文字檔,讓 AI 點評跟建議的小工具


近半年已經極高的比例都會用 claude.ai 做事,最近用量太大後,再加上 grok.com 免費中,就想到如果碰到付費額度用原被凍時,該怎樣快速換另一家 AI 服務使用,也可以避免與 AI 輔助努力了老半天,重啟對話又要打得落落長的內容。


因此就開發了一個小工具:promptpack-for-code

他的用法就是把指定專案的目錄結構、指定的程式碼輸出成 output.txt ,接著就可以上傳 output.txt 跟 AI 互動,成果包括可以請他點評哪邊可以優化、撰寫系統架構、教學文件等等,十分方便

```
% python3 -m venv venv
% source venv/bin/activate
(venv) /tmp % pip install promptpack-for-code
% git clone https://github.com/changyy/py-promptpack-for-code
% promptpack-for-code py-promptpack-for-code -r py-promptpack-for-code 
Successfully created output.txt
```

問問此時此刻(2025-02-21)最強的 Grok 3 AI:

```
### 專案概述

`py-promptpack-for-code` 是一個 Python 命令列工具,旨在將多個程式碼檔案或目錄中的檔案打包成一個結構化的單一文字檔,特別針對與 AI 語言模型進行程式碼審查或分析的需求進行優化。這個工具的主要功能包括:

1. **檔案結構生成**:根據指定的根目錄生成樹狀目錄結構,方便理解專案的組織。
2. **內容整合**:將指定目錄中的程式碼檔案內容合併到輸出檔案,並保留相對路徑。
3. **過濾與忽略**:支援自訂忽略模式(例如 `.git`、`.pyc` 等),避免不必要的檔案內容。
4. **進度顯示**:提供進度條選項,適合處理大型專案時使用。
5. **易於與 AI 整合**:輸出格式規範且結構化,便於直接輸入到 AI 聊天平台進行分析。

### 專案評論

#### 優點
1. **實用性強**:
   - 該工具解決了一個具體且常見的痛點:如何快速準備程式碼供 AI 分析。它不僅合併檔案,還提供了目錄結構,這對理解上下文非常有幫助。
   - 支援多目錄處理、進度條、強制覆蓋等功能,滿足不同使用場景的需求。

2. **結構清晰**:
   - 專案採用標準 Python 包結構(`src/`、`tests/` 等),易於維護和擴展。
   - 文件(如 `README.md` 和 `LICENSE`)完整,提供了詳細的使用說明和授權資訊。

3. **測試覆蓋良好**:
   - `tests/test_core.py` 包含多個單元測試,涵蓋核心功能(樹狀結構生成、檔案處理、忽略模式等),提高了程式碼的可靠性。

4. **部署與分享便利**:
   - 透過 `setup.py` 和 GitHub Actions(`python-publish.yml`)實現 PyPI 發佈,使用者只需 `pip install` 即可使用。
   - MIT 許可證允許廣泛的開源使用和修改。

5. **效能考慮**:
   - 使用 `ThreadPoolExecutor` 實現並行處理檔案,對於大型專案具有一定的效能優勢。

#### 不足之處
1. **功能單一性**:
   - 目前工具主要專注於生成文字輸出,缺乏更高級的分析或格式化選項(例如 JSON 輸出或程式碼摘要生成)。
   - 對於非文字檔案的處理較為簡單(僅跳過),可能無法滿足需要處理混合內容的需求。

2. **錯誤處理有限**:
   - 雖然有基本的日誌記錄(`logging`),但對於檔案編碼錯誤(`UnicodeDecodeError`)或權限問題的處理僅限於跳過或警告,缺乏更靈活的應對策略。

3. **文件不夠詳盡**:
   - `README.md` 提供了基本用法,但對於進階功能(例如自訂忽略模式的具體範例)或輸出檔案的詳細格式說明不夠深入。
   - 缺少 API 文件,對於希望程式化調用的開發者不夠友好。

4. **忽略模式的靈活性**:
   - 雖然支援 `fnmatch` 通配符,但忽略模式僅限於檔案名稱,無法根據檔案內容或大小進行過濾。

### 優化建議

1. **增加輸出格式選項**:
   - 除了目前的純文字輸出,新增 JSON 或 YAML 格式選項,讓使用者可以更方便地將結果用於其他工具或程式化處理。
   - 例如:
     ```json
     {
       "tree": {
         "project-name": {
           "src": {
             "main.py": "def main():\n    print('Hello')\n",
             "utils": {
               "helper.py": "def helper():\n    return True\n"
             }
           }
         }
       }
     }
     ```

2. **增強錯誤處理與回饋**:
   - 提供選項讓使用者決定如何處理非 UTF-8 檔案(例如轉換編碼或以二進位模式顯示部分內容)。
   - 對於權限錯誤,記錄到輸出檔案中,而不是僅在日誌中警告,這樣使用者能更容易追蹤問題。

3. **擴展忽略模式功能**:
   - 支援基於檔案大小、修改時間或內容關鍵字的過濾。例如:
     ```bash
     promptpack-for-code /path/to/src --ignore-size ">10MB" --ignore-modified "<2023-01-01"
     ```
   - 允許從外部檔案(如 `.gitignore`)載入忽略模式,增強與現有工作流的兼容性。

4. **改善文件與範例**:
   - 在 `README.md` 中新增更多實際範例,例如處理大型專案或與 AI 工具(如 ChatGPT 或 GitHub Copilot)配合的具體流程。
   - 提供一份獨立的 `CONTRIBUTING.md`,詳細說明如何提交問題或貢獻程式碼。

5. **新增摘要功能**:
   - 提供選項生成程式碼摘要(例如每個檔案的前幾行或函數簽名),減少輸出檔案的大小,方便快速預覽。
     ```bash
     promptpack-for-code /path/to/src --summary
     ```

6. **支援增量更新**:
   - 增加一個模式,僅處理自上次運行以來有變更的檔案(例如根據檔案修改時間戳記),提高處理效率。
     ```bash
     promptpack-for-code /path/to/src --incremental
     ```

7. **UI 或互動模式**:
   - 開發一個簡單的 TUI(文字使用者介面)或互動模式,讓使用者在命令列中動態選擇要處理的目錄或檔案。

### 總結

`py-promptpack-for-code` 是一個設計良好且實用的工具,特別適合需要與 AI 進行程式碼分析的開發者。其核心功能已經相當成熟,但在靈活性、錯誤處理和文件方面仍有提升空間。透過上述建議,這個專案可以進一步滿足更廣泛的使用場景,並提升使用者的體驗。

如果你有具體的使用需求或想討論某個優化方向,我可以進一步提供更詳細的建議或程式碼範例!
```

2025年2月17日 星期一

Docker 開發筆記 - 使用 aws cli 和 Docker Exec 進入 AWS ECS Container @ macOS

這個議題比我想像中麻煩了點,讓我回憶起 2009 年寫的 AWS 筆記,那時同事一開始還只在用 Firefox extension 管理 AWS EC2 呢 XD 我覺得這理論上要能都透過網頁搞定才對,先把目前研究的過程筆記一下。

總之,要能像 ssh 遠端(docker exec -it ContainerID bash)進去 AWS ECS container 的關鍵之處:
  • 使用 awscli 做事
  • AWS ECS 的 Task 定義,基礎設施需求 -> 任務角色,需要指定一下角色,例如 ecsTaskExecutionRole
  • AWS IAM -> ecsTaskExecutionRole -> 添加 AmazonSSMManagedInstanceCore 權限
  • AWS ECS -> Cluster -> Service ,需要用 awscli 啟動 enable-execute-command ,並且重新更新服務,使之生效
  • 使用 aws 指令登入
首先,先下載 awscli 並且版本要夠新:
安裝後檢查版本,版本太低會無法完成任務:

% aws --version
aws-cli/2.24.5 Python/3.12.6 Darwin/24.3.0 exe/x86_64

接著還要安裝 Session Manager plugin,此例紀錄 Mac with Apple silicon 版:
% curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/mac_arm64/sessionmanager-bundle.zip" -o "sessionmanager-bundle.zip"
% unzip sessionmanager-bundle.zip
% sudo ./sessionmanager-bundle/install -i /usr/local/sessionmanagerplugin -b /usr/local/bin/session-manager-plugin

基本的環境已準備好了,下一刻是查看自己的 AWS ECS 的任務定義是否有把 任務角色 設定好,這部就維持用網頁吧:



若你的 AWS ECS 上定義的 Task 只有一個,也可以偷懶靠 awscli 操作(在此就不贅述 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION 部分),會用到的指令:

aws ecs list-task-definition-families --status ACTIVE
aws ecs list-task-definitions --family-prefix webapp
aws ecs describe-task-definition --task-definition webapp:3

連續技,快速檢查 taskRoleArn 跟 executionRoleArn:

% NamespaceID=$(aws ecs list-task-definition-families --status ACTIVE | jq -r '.families[0]') ; echo "Namespace: $NamespaceID" ; TaskID=$(aws ecs list-task-definitions --family-prefix "$NamespaceID" | jq -r '.taskDefinitionArns[0]') ; echo "Task: $TaskID" ; aws ecs describe-task-definition --task-definition "$TaskID" | jq ".taskDefinition | { taskRoleArn: .taskRoleArn, executionRoleArn: .executionRoleArn}"
Namespace: myapp-task
Task: arn:aws:ecs:ap-northeast-1:####:task-definition/myapp-task:2
{
  "taskRoleArn": "arn:aws:iam::####:role/ecsTaskExecutionRole",
  "executionRoleArn": "arn:aws:iam::####:role/ecsTaskExecutionRole"
}

接下來,若 AWS ECS Cluster 還沒有建立任何 Service,用指令查:

% aws ecs list-tasks --cluster myapp-cluster  
{
    "taskArns": []
}

接著我們在 AWS ECS 網頁端起了一個 Service 名為 myapp-service ,在創建過程中沒看到啟用 enable-execute-command,這時在網頁上查看也是顯示 "ECS 執行: 關閉"



接下來用 aws cli 查詢:

% aws ecs list-tasks --cluster myapp-cluster   
{
    "taskArns": [
        "arn:aws:ecs:ap-northeast-1:####:task/myapp-cluster/#TASKID#"
    ]
}

% TaskID=$(aws ecs list-tasks --cluster myapp-cluster | jq -r '.taskArns[0]') ; echo "TaskID: $TaskID" ; aws ecs describe-tasks --cluster myapp-cluster --tasks $TaskID | jq '.tasks[0] | { "clusterArn": .clusterArn, "taskArn": .taskArn, "taskDefinitionArn": .taskDefinitionArn, "group": .group, "healthStatus": .healthStatus, "desiredStatus": .desiredStatus, "enableExecuteCommand": .enableExecuteCommand, "containers-name": [.containers |.[] | { "name":.name, "runtimeId": .runtimeId} ] }'
TaskID: arn:aws:ecs:ap-northeast-1:####:task/myapp-cluster/#TASKID#
{
  "clusterArn": "arn:aws:ecs:ap-northeast-1:####:cluster/myapp-cluster",
  "taskArn": "arn:aws:ecs:ap-northeast-1:####:task/myapp-cluster/#TASKID#",
  "taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:####:task-definition/myapp-task:2",
  "group": "service:myapp-service",
  "healthStatus": "HEALTHY",
  "desiredStatus": "RUNNING",
  "enableExecuteCommand": false,
  "containers-name": [
    {
      "name": "php-fpm-docker",
      "runtimeId": "#TASKID#-#ContainerID#"
    },
    {
      "name": "web-docker",
      "runtimeId": "#TASKID#-#ContainerID#"
    }
  ]
}

可以看到 enableExecuteCommand 為 false

這時候,如果透過 aws cli 來設法登入到 Container:

% aws ecs execute-command --cluster myapp-cluster --task #TASKID# --container #containers#name# --command "/bin/bash" --interactive


The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.


An error occurred (InvalidParameterException) when calling the ExecuteCommand operation: Unable to start session because the container doesn’t exist. Specify a valid container and try again.

接著,使用 aws cli 來啟動 enableExecuteCommand 吧:

% aws ecs update-service --cluster myapp-cluster --service arn:aws:ecs:ap-northeast-1:####:service/myapp-cluster/myapp-service --enable-execute-command 
{
    "service": {
        ... 
        "enableExecuteCommand": true,
        ...
    }
}

可以看到 enableExecuteCommand 被標記成 true 了,這時還需要重新發布服務,可以重網頁去觸發,或是靠指令觸發:

% aws ecs update-service --cluster myapp-cluster --service arn:aws:ecs:ap-northeast-1:####:service/myapp-cluster/myapp-service --force-new-deployment

當服務發布完畢後,在網頁上就可以看到改變,或是用指令在查一次:

% TaskID=$(aws ecs list-tasks --cluster myapp-cluster | jq -r '.taskArns[0]') ; echo "TaskID: $TaskID" ; aws ecs describe-tasks --cluster myapp-cluster --tasks $TaskID | jq '.tasks[0] | { "clusterArn": .clusterArn, "taskArn": .taskArn, "taskDefinitionArn": .taskDefinitionArn, "group": .group, "healthStatus": .healthStatus, "desiredStatus": .desiredStatus, "enableExecuteCommand": .enableExecuteCommand, "containers-name": [.containers |.[] | { "name":.name, "runtimeId": .runtimeId} ] }'
TaskID: arn:aws:ecs:ap-northeast-1:####:task/myapp-cluster/#TASKID#
{
  "clusterArn": "arn:aws:ecs:ap-northeast-1:####:cluster/myapp-cluster",
  "taskArn": "arn:aws:ecs:ap-northeast-1:####:task/myapp-cluster/#TASKID#",
  "taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:####:task-definition/myapp-task:2",
  "group": "service:myapp-service",
  "healthStatus": "HEALTHY",
  "desiredStatus": "RUNNING",
  "enableExecuteCommand": true,
  "containers-name": [
    {
      "name": "php-fpm-docker",
      "runtimeId": "#TASKID#-#CONTAINERID#"
    },
    {
      "name": "web-docker",
      "runtimeId": "#TASKID#-#CONTAINERID#"
    }
  ]
}

如此,就可以正式遠端進去一下:

% aws ecs execute-command --cluster myapp-cluster --task #TASKID# --container web-docker --command "/bin/bash" --interactive

The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.


Starting session with SessionId: ecs-execute-command-################
ip-123-45-6-123:/var/www/html# 

收工

參考資料: