顯示具有 macos 標籤的文章。 顯示所有文章
顯示具有 macos 標籤的文章。 顯示所有文章

2026年3月10日 星期二

[macOS] 使用 macstrap 批次快速設定 Mac Mini M4 與架設 OpenClaw 環境 @ macOS

最近拿到一台 Mac mini M4 32GB/1TB ,之前很習慣用 Notes 貼一下快速安裝的指令,想著想著自己十多年前都在用 ansible 管理百台 Ubuntu servers ,這個年代一直貼指令微遜 XD 就 vibe coding 一下,請 AI 小弟幫忙實現工具,於是乎就長出個 macstrap 工具,其特色就是讓不熟悉 ansible 的人也能快速上手,而安裝途徑是透過 ssh remote command ,這樣搞定一台後,未來有多台 mac mini 要初始化,也可以快速搞定。

首先,先把 Mac mini 開箱後,設定好登入的帳號密碼,接著在 System Settings -> General -> Sharing -> Remote login 打開,這樣就完成很基本的 ssh 可登入機制,然而 macstrap 是基於 python 的工具,所以 Mac mini 本地需要故意打一下 python 指令觸發系統去下載安裝 Python3

接下來就是 macstrap 領域了:

```
% cd /tmp
% python3 -m venv venv
% source /tmp/venv/bin/activate
% pip install macstrap
...
% macstrap --version 
macstrap, version 1.1.0
```

初始化一些範例來用:

```
% macstrap init --exmaples
% tree -L 1 examples
examples
├── ai-cli
├── openclaw
├── php8.3-dev
└── utilities-dev

5 directories, 0 files
```

設置連上 Mac mini 的帳密,假設 Mac mini IP 是 192.168.1.100 (或 mac-mini.local) 登入帳號是 macuser 且擁有 sudo 權限:

```
% macstrap ssh-auth mac-mini.local --user macuser
```

替 192.168.1.100 (或 mac-mini.local) 安裝 AI CLI:

```
% macstrap run --config examples/ai-cli mac-mini.local
...

TASK [nvm : Install global npm packages] *****
ok: [mac-mini.local] => (item=@github/copilot)
ok: [mac-mini.local] => (item=@anthropic-ai/claude-code)
ok: [mac-mini.local] => (item=@openai/codex)
ok: [mac-mini.local] => (item=@google/gemini-cli)

...

```

安裝 OpenClaw:

```
% macstrap run --config examples/openclaw mac-mini.local
...
TASK [nvm : Install global npm packages] *****
ok: [user-macmini.local] => (item=openclaw)
...
```

接著登入 mac-mini.local 後,就可以用 openclaw onboard 啟動他來做設定:

```
% ssh macuser@mac-mini.local
Now using node v22.22.1 (npm v10.9.4)
[env] node: v22.22.1 | npm: 10.9.4 | Python 3.9.6

% openclaw onboard

🦞 OpenClaw 2026.3.8 (3caab92)
   Runs on a Raspberry Pi. Dreams of a rack in Iceland.

▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
██░▄▄▄░██░▄▄░██░▄▄▄██░▀██░██░▄▄▀██░████░▄▄▀██░███░██
██░███░██░▀▀░██░▄▄▄██░█░█░██░█████░████░▀▀░██░█░█░██
██░▀▀▀░██░█████░▀▀▀██░██▄░██░▀▀▄██░▀▀░█░██░██▄▀▄▀▄██
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
                  🦞 OPENCLAW 🦞                    
 
┌  OpenClaw onboarding
◇  Security ─────────────────────────────────────────────────────────────╮
│                                                                        │
│  Security warning — please read.                                       │
│                                                                        │
│  OpenClaw is a hobby project and still in beta. Expect sharp edges.    │
│  By default, OpenClaw is a personal agent: one trusted operator        │
│  boundary.                                                             │
│  This bot can read files and run actions if tools are enabled.         │
│  A bad prompt can trick it into doing unsafe things.                   │
│                                                                        │
│  OpenClaw is not a hostile multi-tenant boundary by default.           │
│  If multiple users can message one tool-enabled agent, they share      │
│  that delegated tool authority.                                        │
│                                                                        │
│  If you’re not comfortable with security hardening and access          │
│  control, don’t run OpenClaw.                                          │
│  Ask someone experienced to help before enabling tools or exposing it  │
│  to the internet.                                                      │
│                                                                        │
│  Recommended baseline:                                                 │
│  - Pairing/allowlists + mention gating.                                │
│  - Multi-user/shared inbox: split trust boundaries (separate           │
│    gateway/credentials, ideally separate OS users/hosts).              │
│  - Sandbox + least-privilege tools.                                    │
│  - Shared inboxes: isolate DM sessions (`session.dmScope:              │
│    per-channel-peer`) and keep tool access minimal.                    │
│  - Keep secrets out of the agent’s reachable filesystem.               │
│  - Use the strongest available model for any bot with tools or         │
│    untrusted inboxes.                                                  │
│                                                                        │
│  Run regularly:                                                        │
│  openclaw security audit --deep                                        │
│  openclaw security audit --fix                                         │
│                                                                        │
│  Must read: https://docs.openclaw.ai/gateway/security                  │
│                                                                        │
├────────────────────────────────────────────────────────────────────────╯
◆  I understand this is personal-by-default and shared/multi-user use requires
 lock-down. Continue?
│  ○ Yes / ● No

```

2025年6月26日 星期四

兒童相機記憶卡損毀救援筆記 - 在 macOS 修復摔壞的 microSD 卡資料



兩三年前買了兒童相機給小孩亂拍,坊間大部分的兒童相機都大同小異,有的著重相機畫素,有的著重拍攝的方便性,像是可自拍、觸控螢幕等。後來,我選了觸控螢幕款,畢竟方便第一,至於畫素、閃光燈等等的就放掉,並買了一張 32GB microSD ,讓小孩大小可隨便錄影。

最近出遊時,兒童相機在開機的狀態下,不小心摔到地上,接著無法拍照(儲存),也看不到所有已拍攝的資料。

當下在想,這種 microSD 跟 SSD 的,不像以前的 HDD 有轉盤在那邊轉,應該不至於壞軌,再細想一下,有可能像 OS 優化連續錄影/開多檔快速寫檔案,但這時撞到地上導致 OS 狀態異常,沒有把資料收尾而損毀,可能導致 micorSD 檔案系統 Header 就被破壞掉,而不是 microSD 受到什麼物理性的破壞。

接著,就來煩 AI 了 XD 我自己是先把損毀的 microSD 透過 macOS 內建的 Disk Utility 先備份一份 Image 檔案(備份過程也不是很正常的結束,但至少產生了一個 img 檔案),接著拿另一張同樣容量的 32GB microSD 來處理,避免一開始就對原始 microSD 卡操作。

最後,透過跟 AI 聊天的過程,他認為我的想法/直覺有機會解掉,於是乎就操作看看,出發點是想重建 microSD header,流程: 
  • 先把損毀的 microSD卡(A),透過 macOS 內建 Disk Utility 先備份一份 Image 檔案,產出 a_card.img
  • 製作乾淨的 microSD卡(B),把額外一張 microSD 卡放入兒童相機格式化,確保資料檔案系統跟損毀的 microSD 卡一樣,在產出 clean_b_card.img
  • 後續則是在 microSD卡(B) 操作,用 a_card.img 資料覆蓋 microSD卡(B),這時 microSD卡(B) 插入兒童相機一樣會顯示檔案損毀的狀態
  • 最後則是拿 clean_b_card.img 部分資料去覆蓋 microSD卡(B) ,直到可以 OS 正常讀出資料
執行過程中,AI 其實會去猜檔案系統為何,然後摸索 Header 大小,像是建議一開始用 8MB 來猜,然後測試完看不到資料,又說可以試試 4MB (一樣看不到資料),又說要不要試試 2MB,這時我反而去問他應該要從 header size 小到大來測試,也讓 AI 想起可以用 macos 內建的 diskutil 指令查詢資料,此例是 microSD 插入後被系統安置在 /dev/disk10 ,相關指令如:

%  diskutil info /dev/disk10 | grep -E "(Block|Cluster|Sector)" 
   Device Block Size:         512 Bytes
   Allocation Block Size:     512 Bytes

不然根據 AI 的建議,他的操作每一次都要重新製作一次 microSD卡(B) ,滿費時的,最終我就只測試三次,分別是 8MB, 4MB ,接著到 512KB 就搞定了(過程重建 3次資料),而我請 AI 由小到大的 header size 測試,他給予的指令流程:

# 1. 先一次性寫入 A 卡資料(只做這一次)
diskutil unmountDisk /dev/disk10
sudo dd if=a_card.img of=/dev/rdisk10 bs=1m

# 2. 從最小範圍開始測試 - 只覆蓋 Boot Sector (512 bytes)
sudo dd if=clean_b_card.img of=/dev/rdisk10 bs=512 count=1 conv=notrunc
diskutil mountDisk /dev/disk10
# 檢查是否有資料,如果沒有繼續下一步

# 3. 如果 512 bytes 不夠,擴大到 40KB(到 FAT 表開始位置)
diskutil unmountDisk /dev/disk10
sudo dd if=clean_b_card.img of=/dev/rdisk10 bs=1k count=40 conv=notrunc
diskutil mountDisk /dev/disk10
# 檢查結果

# 4. 如果還不夠,擴大到 100KB
diskutil unmountDisk /dev/disk10
sudo dd if=clean_b_card.img of=/dev/rdisk10 bs=1k count=100 conv=notrunc
diskutil mountDisk /dev/disk10
# 檢查結果

# 5. 繼續擴大到 500KB
diskutil unmountDisk /dev/disk10
sudo dd if=clean_b_card.img of=/dev/rdisk10 bs=1k count=500 conv=notrunc
diskutil mountDisk /dev/disk10
# 檢查結果

# 6. 最後到 1MB
diskutil unmountDisk /dev/disk10
sudo dd if=clean_b_card.img of=/dev/rdisk10 bs=1k count=1024 conv=notrunc
diskutil mountDisk /dev/disk10

我後來只做這些,就看得到資料:

% diskutil unmountDisk /dev/disk10
% sudo dd if=a_card.img of=/dev/rdisk10 bs=1m
9297+1 records in
9297+1 records out
9748611584 bytes transferred in 642.300766 secs (15177643 bytes/sec)

% sudo dd if=clean_b_card.img of=/dev/rdisk10 bs=512 count=1 conv=notrunc
Password:
1+0 records in
1+0 records out
512 bytes transferred in 0.009456 secs (54146 bytes/sec)

% diskutil mountDisk /dev/disk10
Volume(s) mounted successfully

如此能看到資料後,也確定有些檔案相機的確沒正確寫入,顯示損毀,猜測應該是晚一點寫檔的機制(cache),導致有些檔案並未正確收尾。

最後在靠這些:

% diskutil unmountDisk /dev/disk10
Unmount of all volumes on disk10 was successful

% sudo fsck_msdos -f -y /dev/disk10
Password:
** /dev/rdisk10
** Phase 1 - Preparing FAT
** Phase 2 - Checking Directories
** Phase 3 - Checking for Orphan Clusters
Warning: 175 files, 31120736 KiB free (972523 clusters)

% brew install testdisk

% sudo photorec /dev/disk10

過程:

PhotoRec 7.2, Data Recovery Utility, February 2024
Christophe GRENIER <grenier@cgsecurity.org>
https://www.cgsecurity.org

Disk /dev/disk10 - 31 GB / 29 GiB (RO)
     Partition                  Start        End    Size in sectors
   P FAT32                          0   62333951   62333952 [NO NAME]

Destination /path/recup_dir

Pass 1 - Reading sector    6458368/62333952, 217 files found
Elapsed time 0h02m02s - Estimated time to completion 0h17m35
jpg: 101 recovered
txt: 94 recovered
mov: 17 recovered
gz: 5 recovered

希望可以救多一點 

2025年4月11日 星期五

[macOS] 調整 MacBook screenshot 的畫質,從 png 調整成 jpg 縮小檔案大小 @ MacBook Air 15吋 / Apple M2

關於 MacBook Print Screen 的畫質太高這件事,其實困擾我很久了,以往都是 screenshot 後,自己縮一下圖再傳出去,一週做個不到五次。但如果和同事工作交流更加頻繁時,會懶得縮圖,使得 Mail/Line/Stack 傳的截圖傳遞檔案很大,觀看者也要花更多的時間處理

隨口問問 AI ,可以靠指令調整,像是把預設儲存方式改成 JPG 並調整品質,這樣就搞定了!以 MacBook Air M2 15吋為例,預設整個畫面 screenshot 是落在快 5MB 大小(跟畫面內容複雜度有關),調整後約 1.5MB

我自己就弄成兩個 script 方便開關調整:

% cat reduce-macos-screenshot-quality.sh
#!/bin/bash

defaults read com.apple.screencapture

defaults write com.apple.screencapture type jpg
killall SystemUIServer
defaults write com.apple.screencapture jpg-quality 60

defaults read com.apple.screencapture

% cat reset-macos-screenshot-quality.sh 
#!/bin/bash

defaults read com.apple.screencapture
#defaults write com.apple.screencapture type png

defaults delete com.apple.screencapture
killall SystemUIServer

defaults read com.apple.screencapture

預設的畫質:

% file raw.png 
~/User/Desktop/raw.png: PNG image data, 3420 x 2214, 8-bit/color RGBA, non-interlaced

調整後: 

% file reduce.jpg 
~/User/Desktop/reduce.jpg: JPEG image data, JFIF standard 1.01, aspect ratio, density 144x144, segment length 16, Exif Standard: [TIFF image data, big-endian, direntries=4, xresolution=62, yresolution=70, resolutionunit=2], baseline, precision 8, 3420x2214, components 3

% defaults read com.apple.screencapture
{
    "jpg-quality" = 60;
    type = jpg;
}

收工

其他資訊:

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:

2024年9月23日 星期一

PHP 開發筆記 - 在 macOS 15 安裝 PHP 7.4 開發環境 @ Apple M2 / arm64

原本是習慣用 MacPorts 管理套件,然後升上 macOS 15 後,跑了 MacPorts migration 後,發現 php74 不見了,細追才發現沒裝起來 XD 努力研究了一下:

--->  Configuring php74
Warning: Configuration logfiles contain indications of -Wimplicit-function-declaration; check that features were not accidentally disabled:
  gethostbyname_r: found in php-7.4.33/config.log
  _controlfp: found in php-7.4.33/config.log
  _controlfp_s: found in php-7.4.33/config.log
Warning: Configuration logfiles contain indications of -Wimplicit-int; check that features were not accidentally disabled:
  found in php-7.4.33/config.log
--->  Building php74
Error: Failed to build php74: command execution failed   
Error: See /opt/local/var/macports/logs/_opt_local_var_macports_sources_rsync.macports.org_macports_release_tarballs_ports_lang_php/php74/main.log for details.
Error: Follow https://guide.macports.org/#project.tickets if you believe there is a bug.
Error: Processing of port php74 failed
--->  Some of the ports you installed have notes:
  db48 has the following notes:
    The Java and Tcl bindings are now provided by the db48-java and
    db48-tcl subports.

===

:info:build ld: warning: ignoring duplicate libraries: '-largon2', '-lxml2', '-lz'
:info:build Undefined symbols for architecture arm64:
:info:build   "_res_9_dn_expand", referenced from:
:info:build       _php_parserr in dns.o
:info:build       _php_parserr in dns.o
:info:build       _php_parserr in dns.o
:info:build       _php_parserr in dns.o
:info:build       _php_parserr in dns.o
:info:build       _php_parserr in dns.o
:info:build       _php_parserr in dns.o
:info:build       ...  
:info:build   "_res_9_dn_skipname", referenced from:
:info:build       _zif_dns_get_record in dns.o
:info:build       _zif_dns_get_mx in dns.o
:info:build       _zif_dns_get_mx in dns.o
:info:build   "_res_9_init", referenced from:
:info:build       _zif_dns_check_record in dns.o
:info:build       _zif_dns_get_record in dns.o
:info:build       _zif_dns_get_mx in dns.o
:info:build   "_res_9_search", referenced from:
:info:build       _zif_dns_check_record in dns.o
:info:build       _zif_dns_get_record in dns.o
:info:build       _zif_dns_get_mx in dns.o
:info:build ld: symbol(s) not found for architecture arm64
:info:build clang: error: linker command failed with exit code 1 (use -v to see invocation)
:info:build make: *** [sapi/cli/php] Error 1
:info:build make: *** Waiting for unfinished jobs....
:info:build ld: warning: ignoring duplicate libraries: '-largon2', '-lxml2', '-lz'
:info:build Undefined symbols for architecture arm64:
:info:build   "_res_9_dn_expand", referenced from:
:info:build       _php_parserr in dns.o
:info:build       _php_parserr in dns.o
:info:build       _php_parserr in dns.o
:info:build       _php_parserr in dns.o
:info:build       _php_parserr in dns.o
:info:build       _php_parserr in dns.o
:info:build       _php_parserr in dns.o
:info:build       ...  
:info:build   "_res_9_dn_skipname", referenced from:
:info:build       _zif_dns_get_record in dns.o
:info:build       _zif_dns_get_mx in dns.o
:info:build       _zif_dns_get_mx in dns.o
:info:build   "_res_9_init", referenced from:
:info:build       _zif_dns_check_record in dns.o
:info:build       _zif_dns_get_record in dns.o
:info:build       _zif_dns_get_mx in dns.o
:info:build   "_res_9_search", referenced from:
:info:build       _zif_dns_check_record in dns.o
:info:build       _zif_dns_get_record in dns.o
:info:build       _zif_dns_get_mx in dns.o
:info:build ld: symbol(s) not found for architecture arm64
:info:build clang: error: linker command failed with exit code 1 (use -v to see invocation)
:info:build make: *** [sapi/phpdbg/phpdbg] Error 1
:info:build make: Leaving directory `/opt/local/var/macports/build/_opt_local_var_macports_sources_rsync.macports.org_macports_release_tarballs_ports_lang_php/php74/work/php-7.4.33'
:info:build Command failed:  cd "/opt/local/var/macports/build/_opt_local_var_macports_sources_rsync.macports.org_macports_release_tarballs_ports_lang_php/php74/work/php-7.4.33" && /usr/bin/make -j8 -w all
:info:build Exit code: 2
:error:build Failed to build php74: command execution failed
:debug:build Error code: CHILDSTATUS 94983 2
:debug:build Backtrace: command execution failed
:debug:build     while executing
:debug:build "system {*}$notty {*}$callback {*}$nice $fullcmdstring"
:debug:build     invoked from within
:debug:build "command_exec -callback portprogress::target_progress_callback build"
:debug:build     (procedure "portbuild::build_main" line 10)
:debug:build     invoked from within
:debug:build "$procedure $targetname"
:error:build See /opt/local/var/macports/logs/_opt_local_var_macports_sources_rsync.macports.org_macports_release_tarballs_ports_lang_php/php74/main.log for details.

因此,主要是 _res_9_dn_* 等等的問題,問 AI 說,解方若還要繼續用 MacPorts 管理,只能裝個肥滋滋的 bind9

% sudo port install bind9
% sudo port clean php74
% sudo port install php74 configure.cflags="-I/opt/local/include" configure.ldflags="-L/opt/local/lib -lbind9 -ldns -lisc"

但這樣跑也失敗了

接著又試了 

% brew install php@7.4   
Warning: No available formula with the name "php@7.4". Did you mean php@8.2, php@8.1 or php@8.0?
==> Searching for similarly named formulae and casks...
==> Formulae
php@8.2                                               php@8.1                                               php@8.0

To install php@8.2, run:
  brew install php@8.2

雖然繼續深入問 AI 可以得到要做 php-7.4 patch 檔案,但這樣搞下去維護太累了,等於自己編譯 php74 跟改 code 了

最終,取個平衡,未來需要 php74 的環境,就靠 Docker 吧 XD 就單純這樣用即可:

% docker run -it -v ~/projects/:/projects -p 8080:8080 -p 8000:8000 -p 8443:8443 --rm php:7.4-cli bash

如此在 Docker 內部用 `php -S 0.0.0.0:8000` 就可以在 Host 端存取查看運行結果了

若還是想直接裝在系統,可以參考 github.com/shivammathur/homebrew-php

2024年8月6日 星期二

PHP 開發筆記 - 嘗試使用 Laravel + Twill CMS Toolkit 開發 CMS 後台管理服務 @ macOS M1



近期公司內部服務都朝向 Laravel framework 來維護,其中常見需求是製作具有 CMS 管理的機制,提供不同部門的同事編輯資料及發佈出去。就來試試看 Twill 這個 CMS Toolkit 套件。從他的文件得知,引入他可以快速擁有後台登入機制,以及省去自己規劃資料庫的資料表,而相較 wordpress 則是有更大的彈性做事。

目前在 Macbook M1 和 MacPorts 環境下,操作一下,先弄個 Laravel project 出來:

```
% sudo port install php83 php83-iconv php83-intl php83-mbstring php83-openssl php83-curl php83-sqlite php83-zip php83-gd php83-exif
% alias php=php83
% wget https://getcomposer.org/download/latest-stable/composer.phar -O /tmp/composer.phar
% php /tmp/composer.phar self-update
% alias composer="php /tmp/composer.phar"
% composer create-project --prefer-dist laravel/laravel /tmp/laravel-workspace
Creating a "laravel/laravel" project at "/tmp/laravel-workspace"
Installing laravel/laravel (v11.1.4)
  - Installing laravel/laravel (v11.1.4): Extracting archive
Created project in /tmp/laravel-workspace
> @php -r "file_exists('.env') || copy('.env.example', '.env');"
Loading composer repositories with package information
Updating dependencies
...
```
這邊偷懶用 alias composer="php /tmp/composer.phar" ,後面會碰到類似的的錯誤訊息時,其實就是找不到 composer 指令:

```
   Symfony\Component\Process\Exception\ProcessStartFailedException 

  The command "'composer' 'dump-autoload'" failed.

Working directory: /private/tmp/laravel-workspace

Error: proc_open(): posix_spawn() failed: No such file or directory
```

因此可以把 /tmp/composer.phar 擺到 PATH 內會尋找指令的地方,或是人工再補個 composer dump-autoload 等等

```
% cp /tmp/composer.phar ~/.bin/composer
% chmod 755 ~/.bin/composer
```

接下來安裝 Twill Toolkit,整個流程其實參考 Twill 官網教學文即可:Building a simple page builder with Laravel Blade,在此僅記錄一下操作流程:

```
% cd /tmp/laravel-workspace
laravel-workspace % composer require area17/twill:"^3.0"
laravel-workspace % php artisan twill:install
...
Let's create a superadmin account!

 Enter an email:
 > user@example.com

 Enter a password:
 > 

 Confirm the password:
 > 

Your account has been created
All good!
```

接著安裝後台模組 Pages:

```
laravel-workspace % php artisan twill:make:module pages

 Do you need to use the block editor on this module? [yes]:
  [0] no
  [1] yes
 > 

 Do you need to translate content on this module? [yes]:
  [0] no
  [1] yes
 > 

 Do you need to generate slugs on this module? [yes]:
  [0] no
  [1] yes
 > 

 Do you need to attach images on this module? [yes]:
  [0] no
  [1] yes
 > 

 Do you need to attach files on this module? [yes]:
  [0] no
  [1] yes
 > 

 Do you need to manage the position of records on this module? [yes]:
  [0] no
  [1] yes
 > 

 Do you need to enable revisions on this module? [yes]:
  [0] no
  [1] yes
 > 

 Do you need to enable nesting on this module? [no]:
  [0] no
  [1] yes
 > 

 Do you also want to generate a model factory? [yes]:
  [0] no
  [1] yes
 > 

 Do you also want to generate a model seeder? [yes]:
  [0] no
  [1] yes
 > 

Migration created successfully! Add some fields!

   INFO  Factory [database/factories/PageFactory.php] created successfully.  

Models created successfully! Fill your fillables!
Repository created successfully! Control all the things!
Controller created successfully! Define your index/browser/form endpoints options!
Form request created successfully! Add some validation rules!

 Do you also want to generate the preview file? [yes]:
  [0] no
  [1] yes
 > 

   INFO  Seeder [database/seeders/PageSeeder.php] created successfully.  

The following snippet has been added to routes/twill.php:
-----
TwillRoutes::module('pages');
-----
To add a navigation entry add the following to your AppServiceProvider BOOT method.
-----
use A17\Twill\Facades\TwillNavigation;
use A17\Twill\View\Components\Navigation\NavigationLink;

public function boot()
{
    ...
    
    TwillNavigation::addLink(
        NavigationLink::make()->forModule('pages')
    );
}
-----
Do not forget to migrate your database after modifying the migrations.

Enjoy.
```

上述的意思是 routes/twill.php 已經增加好後台管理介面的 routing 規則:

```
% cat routes/twill.php 
<?php

use A17\Twill\Facades\TwillRoutes;

// Register Twill routes here eg.
// TwillRoutes::module('posts');

TwillRoutes::module('pages');
```

但是 app/Providers/AppServiceProvider.php 必須自己處理,調整成下方:

```
laravel-workspace % cat app/Providers/AppServiceProvider.php 
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use A17\Twill\Facades\TwillNavigation;
use A17\Twill\View\Components\Navigation\NavigationLink;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        //
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        TwillNavigation::addLink(
            NavigationLink::make()->forModule('pages')
        );
    }
}
```

這些更動是讓後台 /admin 時,上方導覽可以多一項 Pages 的功能,後續透過下方來使用:

laravel-workspace % php artisan migrate
laravel-workspace % php artisan serve --host 0.0.0.0 --port 8000

如此,就可以用 http://localhost:8000/admin 登入後台,點擊 Pages 切換到 http://localhost:8000/admin/pages 可以新增 Pages ,可點擊 Add new 按鈕一則,這時可以看到前台網址規則是 localhost/en/pages/hello-world ,但實際上在 PHP Laravel routing 規則中,前台網址規則都還沒實作處理,所以是看不到資料的。


上述僅做了簡易的後台搭建,後續要處理的有:
  1. 讓後來編輯界面更加豐富,例如有更多的元件(image, text)等,主要是擴增編輯使用的表單元素
  2. 處理後台編輯時,preview 缺少的 css 資源 (運行 npm install && npm run build)
  3. 建立新元素的樣板資料
  4. 建立前台網頁的網址規則跟處理的 Controller (PageDisplayController)
如此,在後台把頁面發布出去後,就可以在前台被瀏覽到。但光上述四點的操作項目是不少的。

接著來進行,也就是 Twill 官網導覽流程,並且把其他碰到的問題也解一解:Configuring the page module

由於預設的前台網頁網址規則是跟語言相關的,例如建立個 Hello World Page 後,可以看到他的前台網址是 localhost/en/pages/hello-world ,若要去掉語言,就是調整 app/Http/Controllers/Twill/PageController.php :

```
 18     protected function setUpController(): void
 19     {
 20         $this->setPermalinkBase('');          // 去掉 /pages/ 那層網址
 21         $this->withoutLanguageInPermalink();  // 去掉 /en/ 那層網址 
 22     }
```

接著來調整撰文時的表單功能,例如目前每一個 Page 都可以填寫 title 跟 description 可增加 SEO 的效果,而增加文章分享後的美觀,則是要增加圖片,這時直接修改 app/Http/Controllers/Twill/PageController.php ,添加發文時可以上傳圖片功能:

```
  6 use A17\Twill\Services\Forms\Fields\Medias;
...
 29     public function getForm(TwillModelContract $model): Form
 30     {   
 31         $form = parent::getForm($model);
 32         
 33         $form->add(
 34             Input::make()->name('description')->label('Description')->translatable()
 35         );
 36         
 37         $form->add(
 38             Medias::make()->name('cover')->label('Cover image')
 39         );
 40         
 41         return $form;
 42     }
...
```

主要是新增引入 `use A17\Twill\Services\Forms\Fields\Medias;` 跟 `$form->add( Medias::make()->name('cover')->label('Cover image') );`


這時我們的 laravel 是透過 artisan 跑在 8000 port,拖拉上傳圖片時,其實會顯示不出來


因為他的圖片網址規則並沒有帶 port ,且只要把網址複製出來加上 port number 就可以正常顯示,代表只需處理 Laravel framework 的 .env 中 APP_URL 規則:

```
% cat .env | grep APP_URL
APP_URL=http://localhost
```

這時建議測試時可以有兩套環境設置,運行時指定設定檔案:

```
% cp .env .env.local
% cat .env.local | grep APP_URL 
APP_URL=http://localhost:8000
% php artisan serve --host 0.0.0.0 --port 8000 --env local

   INFO  Server running on [http://0.0.0.0:8000].  

  Press Ctrl+C to stop the server
```

如此也解掉後台圖片顯示失敗的問題。


下一刻則是擴充文章編輯環境,引入 Block Editor 架構,也就是在編輯文章時,可以拖拉區塊到文章內,還可以自行開發,把常用的項目元件化。從 Twill 官網的範例資訊,預設有兩個 Block 了,一個是 image block ,另一個是 wysiwyg block,官網範例會試著新增一個小的區塊,例如名為 Text 的區塊。

首先,先啟用 Block Editor,啟用方式是在 app/Http/Controllers/Twill/PageController.php 裡引入 `use A17\Twill\Services\Forms\Fields\BlockEditor;` 和增加 `$form->add( BlockEditor::make() );`

接著則是試著建立一個 text block:

```
laravel-workspace % php artisan twill:make:block text

 Should we also generate a view file for rendering the block? (yes/no) [no]:
 > yes

Creating block...
File: /private/tmp/laravel-workspace/resources/views/twill/blocks/text.blade.php
Block text was created.
Block text blank render view was created.
Block is ready to use with the name 'text'

laravel-workspace % cat resources/views/twill/blocks/text.blade.php
@twillBlockTitle('Text')
@twillBlockIcon('text')
@twillBlockGroup('app')

<x-twill::input
    name="title"
    label="Title"
    :translated="true"
/>

<x-twill::wysiwyg
    name="text"
    label="Text"
    placeholder="Text"
    :toolbar-options="[
        'bold',
        'italic',
        ['list' => 'bullet'],
        ['list' => 'ordered'],
        [ 'script' => 'super' ],
        [ 'script' => 'sub' ],
        'link',
        'clean'
    ]"
    :translated="true"
/>
```

可以看到其樣板也長出來了


但是在後台 preview 時,仍會有待處理的訊息:

This is a basic preview. You can use dd($block) to view the data you have access to. <br />This preview file is located at: /private/tmp/laravel-workspace/resources/views/site/blocks/text.blade.php

將他修改一下:

```
% cat resources/views/site/blocks/text.blade.php
<div class="prose">
    <h2>{{$block->translatedInput('title')}}</h2>
    {!! $block->translatedInput('text') !!}
</div>
```    


接著再產生另一個 image block 並更新他的 block view 跟 preview:

```
laravel-workspace % php artisan twill:make:block image

 Should we also generate a view file for rendering the block? (yes/no) [no]:
 > yes

Creating block...
File: /private/tmp/laravel-workspace/resources/views/twill/blocks/image.blade.php
Block image was created.
Block image blank render view was created.
Block is ready to use with the name 'image'

laravel-workspace % cat resources/views/twill/blocks/image.blade.php
@twillBlockTitle('Image')
@twillBlockIcon('text')
@twillBlockGroup('app')
 
<x-twill::medias
    name="highlight"
    label="Highlight"
/>

laravel-workspace % cat resources/views/site/blocks/image.blade.php 
<div class="py-8 mx-auto max-w-2xl flex items-center">
    <img src="{{$block->image('highlight', 'desktop')}}"/>
</div>
```

此外,image block 要生效還需要調整 config/twill.php

```
% cat config/twill.php 
<?php

return [
    'block_editor' => [
        'crops' => [ 
            'highlight' => [
                'desktop' => [
                    [
                        'name' => 'desktop',
                        'ratio' => 16 / 9,
                    ],
                ],
                'mobile' => [
                    [
                        'name' => 'mobile',
                        'ratio' => 1,
                    ],
                ],
            ],
        ],
    ],
];
```

接著,還要幫前後台產的文章添加 CSS 效果 `@vite('resources/css/app.css')` ,添加方式是修改 `resources/views/site/layouts/block.blade.php` 跟 `resources/views/site/page.blade.php`

```
laravel-workspace % cat resources/views/site/layouts/block.blade.php 
<!doctype html>
<html lang="en">
<head>
    <title>#madewithtwill website</title>
    @vite('resources/css/app.css') 
</head>
<body>
<div>
    @yield('content')
</div>
</body>
</html>

laravel-workspace % cat resources/views/site/page.blade.php
<!doctype html>
<html lang="en">
<head>
    <title>{{ $item->title }}</title>
    @vite('resources/css/app.css') 
</head>
<body>
<div class="mx-auto max-w-2xl">
    {!! $item->renderBlocks() !!}
</div>
</body>
</html>
```

此外,還要用 npm 工具編譯出 resources/css/app.css:

```
% nvm use v20
Now using node v20.9.0 (npm v10.3.0)

% npm install        

added 23 packages, and audited 24 packages in 577ms

5 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

laravel-workspace % tree resources/css/       
resources/css/
└── app.css

1 directory, 1 file
```

如此,在 Twill 後台編輯文章時,就可以在 Block editor 操作下增加文字區塊、圖片等等的功能。

不過,直到現在前台機制還沒打通,尚未提供前台 routing rule 等顯示前台網頁,先建立個 PageDisplayController 來處理:

```
% php artisan make:controller PageDisplayController

   INFO  Controller [app/Http/Controllers/PageDisplayController.php] created successfully.  
```

把 PageDisplayController 更新為可以接一個參數,並且立刻把它印出 debug 訊息:

```
% cat app/Http/Controllers/PageDisplayController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Contracts\View\View;

class PageDisplayController extends Controller
{
    public function show(string $slug): View
    {
        dd($slug);
    }
}
```

接著,再把 routing 設置好:

```
% cat routes/web.php 
<?php

use Illuminate\Support\Facades\Route;

//Route::get('/', function () {
//    return view('welcome');
//});

Route::get('{slug}', [\App\Http\Controllers\PageDisplayController::class, 'show'])->name('frontend.page'); 
```

如此在前台瀏覽網頁就會看到:

最後,再把 PageDisplayController 調整成顯示正確的資料:

```
laravel-workspace % cat app/Http/Controllers/PageDisplayController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Contracts\View\View;
use App\Repositories\PageRepository;

class PageDisplayController extends Controller
{
    //public function show(string $slug): View
    //{
    //    dd($slug);
    //}

    public function show(string $slug, PageRepository $pageRepository): View
    {
        $page = $pageRepository->forSlug($slug);
 
        if (!$page) {
            abort(404);
        }
 
        return view('site.page', ['item' => $page]);
    }
}
```

如此瀏覽前台時,例如 http://localhost:8000/hello-world 就可以顯示網頁內容了。

最後,如果碰到前台圖片沒有顯示出來的部分,則是留意拖拉建立圖片時,需要依照不同裝置版型做設定,例如 PC 瀏覽時看不到圖片,那應當是少設定的 desktop 的設置:

```
laravel-workspace % cat resources/views/site/blocks/image.blade.php
<div class="py-8 mx-auto max-w-2xl flex items-center">
    <img src="{{$block->image('highlight', 'desktop')}}"/>
</div>
```

需留意在 block editor 時,其 Image 拖拉進去時,有沒有 `desktop crop` 的描述

2024年7月25日 星期四

AI 開發筆記 - 透過 llama.cpp 使用 Meta Llama 3.1 8B,過程包括資料格式轉換 @ MacBook Pro M1 32GB RAM



首先,下載 Meta llama3.1 8B 方式都寫在網站上,先到 llama.meta.com/llama-downloads 填單,填完單就會顯示下載網址,例如這種格式:

https://llama3-1.llamameta.net/*?Policy=XXX&Key-Pair-Id=XXX&Download-Request-ID=XXX

然後下載方式不是直接瀏覽他,要依照指定方式,透過 download.sh 下載,下載資訊:


連續動作:

```
% git clone https://github.com/meta-llama/llama-models
% bash llama-models/models/llama3_1/download.sh
Enter the URL from email:  https://llama3-1.llamameta.net/*?Policy=XXX&Key-Pair-Id=XXX&Download-Request-ID=XXX

 **** Model list ***
 -  meta-llama-3.1-405b
 -  meta-llama-3.1-70b
 -  meta-llama-3.1-8b
 -  meta-llama-guard-3-8b
 -  prompt-guard
Choose the model to download: meta-llama-3.1-8b

**** Available models to download: *** 
 -  meta-llama-3.1-8b-instruct
 -  meta-llama-3.1-8b

Enter the list of models to download without spaces or press Enter for all: meta-llama-3.1-8b
Downloading LICENSE and Acceptable Usage Policy
...
```

資料量大小:

```
% du -hd1 Meta-Llama-3.1-8B 
 15G    Meta-Llama-3.1-8B

% tree Meta-Llama-3.1-8B 
Meta-Llama-3.1-8B
├── consolidated.00.pth
├── params.json
└── tokenizer.model

1 directory, 3 files
```

接著就可以享受 Meta 釋出給全世界的 AI 模型了,萬分感謝 Orz 省去自己掏錢買機器訓練,據說這版 llama3.1 號稱可以跟 ChatGPT-4o 或 Claude 3.5 Sonnet 抗衡。但基於家裡的算力不足,純試試 8B 吧!

接下來使用 llama.cpp 體驗,除了程式碼編譯外,還要做資料格式的轉換:

```
% wget https://raw.githubusercontent.com/huggingface/transformers/main/src/transformers/models/llama/convert_llama_weights_to_hf.py
% python3 -m venv venv
% source venv/bin/activate
(venv) % pip install transformers torch huggingface_hub tiktoken blobfile accelerate
(venv) % python3 convert_llama_weights_to_hf.py --input_dir Meta-Llama-3.1-8B --model_size 8B --output_dir llama3_1_hf --llama_version 3.1

(venv) % du -hd1 llama3_1_hf
 15G    llama3_1_hf
(venv) % tree llama3_1_hf
llama3_1_hf
├── config.json
├── generation_config.json
├── model-00001-of-00004.safetensors
├── model-00002-of-00004.safetensors
├── model-00003-of-00004.safetensors
├── model-00004-of-00004.safetensors
├── model.safetensors.index.json
├── special_tokens_map.json
├── tokenizer.json
└── tokenizer_config.json

1 directory, 10 files
```

編譯及使用 llama.cpp:

```
(venv) % git clone https://github.com/ggerganov/llama.cpp
(venv) % cd llama.cpp
(venv) llama.cpp % LLAMA_METAL=1 make  
(venv) llama.cpp % pip install -r requirements.txt
(venv) llama.cpp % time python3 convert_hf_to_gguf.py ../llama3_1_hf/ --outfile llama3_1-8B.gguf
(venv) llama.cpp % 
(venv) llama.cpp % du -hd1 llama3_1-8B.gguf
 15G    llama3_1-8B.gguf
(venv) llama.cpp % ./llama-server -m ./llama3_1-8B.gguf
...
error: Insufficient Memory (00000008:kIOGPUCommandBufferCallbackErrorOutOfMemory)
...
llama_new_context_with_model: n_ctx      = 131072
llama_new_context_with_model: n_batch    = 2048
llama_new_context_with_model: n_ubatch   = 512
llama_new_context_with_model: flash_attn = 0
llama_new_context_with_model: freq_base  = 500000.0
llama_new_context_with_model: freq_scale = 1
ggml_metal_init: allocating
ggml_metal_init: found device: Apple M1 Pro
ggml_metal_init: picking default device: Apple M1 Pro
ggml_metal_init: using embedded metal library
ggml_metal_init: GPU name:   Apple M1 Pro
ggml_metal_init: GPU family: MTLGPUFamilyApple7  (1007)
ggml_metal_init: GPU family: MTLGPUFamilyCommon3 (3003)
ggml_metal_init: GPU family: MTLGPUFamilyMetal3  (5001)
ggml_metal_init: simdgroup reduction support   = true
ggml_metal_init: simdgroup matrix mul. support = true
ggml_metal_init: hasUnifiedMemory              = true
ggml_metal_init: recommendedMaxWorkingSetSize  = 22906.50 MB
llama_kv_cache_init:      Metal KV buffer size = 16384.00 MiB
llama_new_context_with_model: KV self size  = 16384.00 MiB, K (f16): 8192.00 MiB, V (f16): 8192.00 MiB
llama_new_context_with_model:        CPU  output buffer size =     0.98 MiB
llama_new_context_with_model:      Metal compute buffer size =  8480.00 MiB
llama_new_context_with_model:        CPU compute buffer size =   264.01 MiB
llama_new_context_with_model: graph nodes  = 1030
llama_new_context_with_model: graph splits = 2
...
^Cggml_metal_free: deallocating
```

預設環境會碰到 Aplpe MacBook Pro 筆電資源的限制,多使用了 -c 31072 來跑,整個 llama-server 在 macOS 活動監視器上,可以看到使用記憶體不到 4GB,看起來有穩定下來:

```
(venv) llama.cpp % ./llama-server -m ./llama3_1-8B.gguf -c 31072
...
.........................................................................................
llama_new_context_with_model: n_ctx      = 31072
llama_new_context_with_model: n_batch    = 2048
llama_new_context_with_model: n_ubatch   = 512
llama_new_context_with_model: flash_attn = 0
llama_new_context_with_model: freq_base  = 500000.0
llama_new_context_with_model: freq_scale = 1
ggml_metal_init: allocating
ggml_metal_init: found device: Apple M1 Pro
ggml_metal_init: picking default device: Apple M1 Pro
ggml_metal_init: using embedded metal library
ggml_metal_init: GPU name:   Apple M1 Pro
ggml_metal_init: GPU family: MTLGPUFamilyApple7  (1007)
ggml_metal_init: GPU family: MTLGPUFamilyCommon3 (3003)
ggml_metal_init: GPU family: MTLGPUFamilyMetal3  (5001)
ggml_metal_init: simdgroup reduction support   = true
ggml_metal_init: simdgroup matrix mul. support = true
ggml_metal_init: hasUnifiedMemory              = true
ggml_metal_init: recommendedMaxWorkingSetSize  = 22906.50 MB
llama_kv_cache_init:      Metal KV buffer size =  3884.00 MiB
llama_new_context_with_model: KV self size  = 3884.00 MiB, K (f16): 1942.00 MiB, V (f16): 1942.00 MiB
llama_new_context_with_model:        CPU  output buffer size =     0.98 MiB
llama_new_context_with_model:      Metal compute buffer size =  2034.69 MiB
llama_new_context_with_model:        CPU compute buffer size =    68.69 MiB
llama_new_context_with_model: graph nodes  = 1030
llama_new_context_with_model: graph splits = 2

INFO [                    init] initializing slots | tid="0x1f3b34c00" timestamp=1721895109 n_slots=1
INFO [                    init] new slot | tid="0x1f3b34c00" timestamp=1721895109 id_slot=0 n_ctx_slot=31072
INFO [                    main] model loaded | tid="0x1f3b34c00" timestamp=1721895109
INFO [                    main] chat template | tid="0x1f3b34c00" timestamp=1721895109 chat_example="<|im_start|>system\nYou are a helpful assistant<|im_end|>\n<|im_start|>user\nHello<|im_end|>\n<|im_start|>assistant\nHi there<|im_end|>\n<|im_start|>user\nHow are you?<|im_end|>\n<|im_start|>assistant\n" built_in=true
INFO [                    main] HTTP server listening | tid="0x1f3b34c00" timestamp=1721895109 port="8080" n_threads_http="9" hostname="127.0.0.1"
INFO [            update_slots] all slots are idle | tid="0x1f3b34c00" timestamp=1721895109
...
```

如此就可以用 http://localhost:8080 來體驗 llama3.1 8B 的資料了

2023年9月26日 星期二

[Linux] OpenSSL AES-128 加解密 @ macOS 13.2.1, OpenSSL 3.1.2 1 Aug 2023

總覺得我是不是以前也寫過這類筆記? 查了下快十年前 XD 用的是 openssl des3 

由於工作中太常接觸 AES-128 加解密了,想要筆記一下方便未來查詢,而透過 openssl command 也方便交叉驗證程式是否正確,這次就仿 openssl des3 筆記:

明文:

% cat /tmp/input.txt
Hello World
% md5 /tmp/input.txt
MD5 (/tmp/input.txt) = e59ff97941044f85df5297e1c302d260
% hexdump /tmp/input.txt 
0000000 6548 6c6c 206f 6f57 6c72 0a64          
000000c

產生加解密的 Key 值:

% dd if=/dev/urandom of=/tmp/password bs=1 count=32
32+0 records in
32+0 records out
32 bytes transferred in 0.000303 secs (105611 bytes/sec)
% md5 /tmp/password 
MD5 (/tmp/password) = 92eeb8e1bec70865650e1f96e5cd1819
% hexdump /tmp/password 
0000000 cf23 5006 70d0 bf1e 0e9e a70c 10f0 ecd6
0000010 dc01 e156 d818 bff2 2e3e f859 28c9 a91d
0000020
% hexdump -v -e '/1 "%02x"' -n 16 /tmp/password 
23cf0650d0701ebf9e0e0ca7f010d6ec

產生加解密的 IV 值(其實同 Key 值產生即可,目前改用另一招):

% date | md5
5d6476c85eca3ec56fda4913f5578b83

使用 OpenSSL AES-128 加密:

% openssl enc -e -aes-128-cbc -in /tmp/input.txt -out /tmp/encrypt.txt -K 23cf0650d0701ebf9e0e0ca7f010d6ec -iv 5d6476c85eca3ec56fda4913f5578b83
% hexdump /tmp/encrypt.txt
0000000 c3d4 cb0a d845 182c 319e afdf b29c c484
0000010

使用 OpenSSL AES-128 解密:

% openssl enc -d -aes-128-cbc -in /tmp/encrypt.txt -out /tmp/output.txt -K 23cf0650d0701ebf9e0e0ca7f010d6ec -iv 5d6476c85eca3ec56fda4913f5578b83
% md5 /tmp/output.txt 
MD5 (/tmp/output.txt) = e59ff97941044f85df5297e1c302d260
% cat /tmp/output.txt 
Hello World

工作上很容易 Key 值是一個 32 bytes 的 binary 檔案,且不加入輸出至檔案的方式,可以立即看解密的內容,連續動作如下:

% openssl enc -d -aes-128-cbc -in /tmp/encrypt.txt -K $(hexdump -v -e '/1 "%02x"' -n 16 /tmp/password) -iv 5d6476c85eca3ec56fda4913f5578b83
Hello World

收工

2023年9月3日 星期日

Kubernetes/k8s 開發筆記 - 在 macOS 僅安裝 kubectl 、在 Windows 安裝 minikube v1.31.2 和 VirtualBox v7.0.10 @ macOS 13 , Windows 11

雖然 k8s 已夯很久,但工作事務上一直還沒用他,主因是現有的 Jenkins + Ansible 已經可以輕鬆管理數百台機器群,說穿了也滿像實體機房管理機制,有人(Linode/AWS/GCP/Azure)負責把實體機器上架插上電源網路線,接著設法自動化(Ansible)找到他們完成初始化,接著靠 Jenkions 完成 CI/CD。

年初就想要提升同事技能,刻意備好書推動,現在趁個颱風天自己也找點時間走一輪吧!我認為 k8s 的線上資源已經夠多了,已夯了五年,到處都有精美的簡中電子書和鐵人文,如:
整體上,我覺得可以 Kubernetes Handbook (Kubernetes指南)那為基準,先把基礎入門文都看一下,知道 k8s 源自於 Google Borg 的設計理念,往後只要看著 k8s 架構圖即可:


例如操作 k8s 可以透過 kubectl 指令,也可以透過 api 呼叫,更可以透過 Web UI 來操作。如果硬要對比的話,早年 AWS 2009 前後,一開始也是先提供 API 操控方式,接著進展到 Firefox plugin,後續提供 Web UI 並且越來越豐富,對應的還有 Azure / GCP 一推出就有 Web UI 可操作,接著為了自動化操控,我們會使用 API level 的方式,透過基本的權限掌控後,得知機器的 IP 跟默認的登入方式,也可以簡化成單純在 AWS/GCP Web UI 開好機器及設定好標籤後,後續 ansible-playbook 就能全盤接手處理。

至於要認識 k8s ,目前看到 minukube 是最佳的下手,他支援跨平台。實務上只需做一兩件事:
  1. 下載 minukube ,可以架設出本地 k8s cluster (預設是單一節點)
  2. 下載 kubectl 工具(雖然 minukube kubectl 也能呼喚出)
這邊做一點有趣的情境:在 macos 上僅安裝 kubectl 而已,在 windows 11 安裝 minukube v1.31.2 + VirtualBox v7.0.10,讓 Windows 資源拿來運作 k8s 環境,未來 macos 就縮減成 thin client 用來遠端操作。

實作方式 - macOS (也可靠 MacPorts 或 Homebrew 安裝):

% curl -L https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/darwin/amd64/kubectl > /tmp/kubectl
% chmod 700 /tmp/kubectl
% /tmp/kubectl version
Client Version: v1.28.1
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
The connection to the server localhost:8080 was refused - did you specify the right host or port?

這邊就可以看到 kubectl 他本身預設會去跟 localhost:8080 溝通,那代表其可以設定在遠方。

實作方式 - Windows 11: 

PS C:\Users\user\Downloads> .\minikube-windows-amd64.exe start
😄  minikube v1.31.2 on Microsoft Windows 11 Pro 10.0.22621.2215 Build 22621.2215
✨  Using the virtualbox driver based on existing profile
👍  Starting control plane node minikube in cluster minikube
🔄  Restarting existing virtualbox VM for "minikube" ...
❗  This VM is having trouble accessing https://registry.k8s.io
💡  To pull new external images, you may need to configure a proxy: https://minikube.sigs.k8s.io/docs/reference/networking/proxy/
🐳  Preparing Kubernetes v1.27.4 on Docker 24.0.4 ...
🔗  Configuring bridge CNI (Container Networking Interface) ...
    ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
    ▪ Using image docker.io/kubernetesui/dashboard:v2.7.0
    ▪ Using image docker.io/kubernetesui/metrics-scraper:v1.0.8
🔎  Verifying Kubernetes components...
💡  Some dashboard features require the metrics-server addon. To enable all features please run:

        minikube addons enable metrics-server

🌟  Enabled addons: default-storageclass, storage-provisioner, dashboard
💡  kubectl not found. If you need it, try: 'minikube kubectl -- get pods -A'
🏄  Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default

PS C:\Users\user\Downloads> .\minikube-windows-amd64.exe stop
✋  Stopping node "minikube"  ...
🛑  1 node stopped.

PS C:\Users\user\Downloads> .\minikube-windows-amd64.exe delete
🔥  Deleting "minikube" in virtualbox ...
💀  Removed all traces of the "minikube" cluster.

以上就是很粗淺的....我起來了,我停掉,我砍掉了。其中比較麻煩之處應該是 Virutalbox 7 的使用,剛安裝完後要重新啟動電腦,接著運行 minikube-windows-amd64.exe start 時,看能不能順利跑起來,常見卡住的地方是 Host-Only 網路卡等等。

此外,一直切換筆電很煩,就來把 Windows 11 Desktop 安裝 OpenSSH ,讓 macOS 遠端登入:[macOS] 從 Macbook 遠端登入 Windows 筆電並使用 PowerShell 工作環境

接著就回到 macOS 遠端登入 windows minikube 的用法:

% ssh user@windows-minikube-ip

Microsoft Windows [版本 10.0.22621.2215]
(c) Microsoft Corporation. 著作權所有,並保留一切權利。

user@WINDOWS-DESKTOP C:\Users\user>cd Downloads

user@WINDOWS-DESKTOP C:\Users\user\Downloads>minikube-windows-amd64.exe start
😄  minikube v1.31.2 on Microsoft Windows 11 Pro 10.0.22621.2215 Build 22621.2215
✨  Automatically selected the virtualbox driver
👍  Starting control plane node minikube in cluster minikube
🔥  Creating virtualbox VM (CPUs=2, Memory=4000MB, Disk=20000MB) ...
❗  This VM is having trouble accessing https://registry.k8s.io
💡  To pull new external images, you may need to configure a proxy: https://minikube.sigs.k8s.io/docs/reference/networking/proxy/
🐳  Preparing Kubernetes v1.27.4 on Docker 24.0.4 ...
    ▪ Generating certificates and keys ...
    ▪ Booting up control plane ...
    ▪ Configuring RBAC rules ...
🔗  Configuring bridge CNI (Container Networking Interface) ...
    ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🔎  Verifying Kubernetes components...
🌟  Enabled addons: default-storageclass, storage-provisioner
💡  kubectl not found. If you need it, try: 'minikube kubectl -- get pods -A'
🏄  Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default

user@WINDOWS-DESKTOP C:\Users\user\Downloads>

user@WINDOWS-DESKTOP C:\Users\user\Downloads>minikube-windows-amd64.exe status
minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

user@WINDOWS-DESKTOP C:\Users\user\Downloads>minikube-windows-amd64.exe kubectl -- config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority: C:\Users\user\.minikube\ca.crt
    extensions:
    - extension:
        provider: minikube.sigs.k8s.io
        version: v1.31.2
      name: cluster_info
    server: https://192.168.59.101:8443
  name: minikube
contexts:
- context:
    cluster: minikube
    extensions:
    - extension:
        provider: minikube.sigs.k8s.io
        version: v1.31.2
      name: context_info
    namespace: default
    user: minikube
  name: minikube
current-context: minikube
kind: Config
preferences: {}
users:
- name: minikube
  user:
    client-certificate: C:\Users\user\.minikube\profiles\minikube\client.crt
    client-key: C:\Users\user\.minikube\profiles\minikube\client.key

user@WINDOWS-DESKTOP C:\Users\user\Downloads>minikube-windows-amd64.exe kubectl -- version --output=json     
{
  "clientVersion": {
    "major": "1",
    "minor": "27",
    "gitVersion": "v1.27.4",
    "gitCommit": "fa3d7990104d7c1f16943a67f11b154b71f6a132",
    "gitTreeState": "clean",
    "buildDate": "2023-07-19T12:20:54Z",
    "goVersion": "go1.20.6",
    "compiler": "gc",
    "platform": "windows/amd64"
  },
  "kustomizeVersion": "v5.0.1",
  "serverVersion": {
    "major": "1",
    "minor": "27",
    "gitVersion": "v1.27.4",
    "gitCommit": "fa3d7990104d7c1f16943a67f11b154b71f6a132",
    "gitTreeState": "clean",
    "buildDate": "2023-07-19T12:14:49Z",
    "goVersion": "go1.20.6",
    "compiler": "gc",
    "platform": "linux/amd64"
  }
}


user@WINDOWS-DESKTOP C:\Users\user\Downloads>ipconfig

Windows IP 設定


乙太網路卡 乙太網路:

   媒體狀態 . . . . . . . . . . . . .: 媒體已中斷連線
   連線特定 DNS 尾碼 . . . . . . . . :

乙太網路卡 乙太網路 2:

   連線特定 DNS 尾碼 . . . . . . . . :
   IPv4 位址 . . . . . . . . . . . . : 192.168.56.1
   子網路遮罩 . . . . . . . . . . . .: 255.255.255.0
   預設閘道 . . . . . . . . . . . . .:

乙太網路卡 乙太網路 3:

   連線特定 DNS 尾碼 . . . . . . . . :
   IPv4 位址 . . . . . . . . . . . . : 192.168.59.1
   子網路遮罩 . . . . . . . . . . . .: 255.255.255.0
   預設閘道 . . . . . . . . . . . . .:

...

如此,若要用 macOS kubectl 去控制 Windows 11 上的 minikube ,先挑個土法煉鋼模式:

1. 把 minikube-windows-amd64.exe kubectl -- config view 存起來(可以透 ssh remote command)

% ssh user@windows-minikube-ip '%HOME%\Downloads\minikube-windows-amd64.exe kubectl -- config view' > /tmp/kubectl.config.yaml

2. 將上面列到的 certificate-authority, client-certificate 和 client-key 也都靠 scp 下載回來

% scp user@windows-minikube-ip:"/C:/Users/user/.minikube/ca.crt" /tmp/kubectl.ca.crt
% scp user@windows-minikube-ip:"/C:/Users/user/.minikube/profiles/minikube/client.crt" /tmp/kubectl.client.crt
% scp user@windows-minikube-ip:"/C:/Users/user/.minikube/profiles/minikube/client.key" /tmp/kubectl.client.key

3. 修改 /tmp/kubectl.config.yaml 上 certificate-authority, client-certificate 和 client-key 對應位置

% /tmp/kubectl --kubeconfig ./kubectl.config.yaml config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority: kubectl.ca.crt
    extensions:
    - extension:
        provider: minikube.sigs.k8s.io
        version: v1.31.2
      name: cluster_info
    server: https://127.0.0.1:8443
  name: minikube
contexts:
- context:
    cluster: minikube
    extensions:
    - extension:
        provider: minikube.sigs.k8s.io
        version: v1.31.2
      name: context_info
    namespace: default
    user: minikube
  name: minikube
current-context: minikube
kind: Config
preferences: {}
users:
- name: minikube
  user:
    client-certificate: kubectl.client.crt
    client-key: kubectl.client.key

其中上述的 clusters.cluster.server 數值我已換成 https://127.0.0.1:8443 ,這是為了彈性自行靠 ssh tunnel 

4. 最後,我再透過 SSH tunnel 自建一條連到 192.168.56.101:8443 (Windows 11 上的 minikube api 入口點)

% ssh -N -L 8443:192.168.59.101:8443 user@windows-minikube-ip

5. 如此,未來想從 macOS 的 kubectl 遠端連去控制 Windows 11 上的 minikube 時,就是先建立一條 SSH tunnel ,接著運行 kubectl 要指定 config 位置,就能黑皮遠端操控

% ./kubectl --kubeconfig ./kubectl.config.yaml version
Client Version: v1.28.1
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
Server Version: v1.27.4

最後一提,其實 Windows 11 Desktop 安裝完 OpenSSH server 後,已經可以自行遠端進去用 minikube-windows-amd64.exe kubectl 做事,只剩熟不熟悉 PowerShell 指令環境,所以,上述有點脫褲子放屁 XD 好處是練一輪後,未來有很多 k8s cluster 可以用 --kubeconfig 切換吧!

其他資訊:

2023年3月15日 星期三

Flutter 開發筆記 - 排除 Could not open settings generic class cache for settings file @ macOS 13.2.1, openjdk 19.0.2, Android Studio Electric Eel | 2022.1.1 Patch 2


最近工作上關係,來回顧一下 Flutter app 的開發,預計寫一款非常簡單的 App 試試水溫。結果在 macOS 13.2.1, openjdk 19.0.2, Android Studio Electric Eel | 2022.1.1 Patch 2 環境,預設弄個專案出來卻不能編譯:

Launching lib/main.dart on sdk gphone64 arm64 in debug mode...
Running Gradle task 'assembleDebug'...

FAILURE: Build failed with an exception.

* What went wrong:
Could not open settings generic class cache for settings file '/Users/UserID/AndroidStudio/usb_displayport_helper/android/settings.gradle' (/Users/UserID/.gradle/caches/7.5/scripts/XXXXXX).
> BUG! exception in phase 'semantic analysis' in source unit '_BuildScript_' Unsupported class file major version 63

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 24s
Exception: Gradle task assembleDebug failed with exit code 1

研究了一下,主因是 JDK 與 Gradle 版本問題,需要多做一些事就能排除了。記錄一下。

此時環境:

% java --version
openjdk 19.0.2 2023-01-17
OpenJDK Runtime Environment (build 19.0.2+7-44)
OpenJDK 64-Bit Server VM (build 19.0.2+7-44, mixed mode, sharing)

這時,在既有專案的左邊上方的 Project 小視窗,挑選 android 目錄,點選右鍵 -> Flutter -> Open Android module in Android Studio -> New Window ,在新的 Android Studio 還沒按 Build 就看到訊息:

Unsupported Java. 
Your build is currently configured to use Java 19.0.2 and Gradle 7.5.

Possible solution:
 - Open Gradle wrapper settings, change `distributionUrl` property to use compatible Gradle version and reload the project

這時就來更換預設的環境: File -> Project Structure -> Project

- Android Gradle Plugin Version: 7.4.2
- Gradle Version: 7.6.1

按下 OK 就會開始抓資料,最後也順利編譯後在模擬器上跑了。

2023年1月23日 星期一

安裝 PC 版 app 舊版 Line v6.7.3.2506 @ macOS 10.13.6

之前在 Macbook Pro 13-inch, Late 2011 在 2020年重灌後,一直靠 Chrome Browser extension 用 Line 服務 (Ver 2.5.8 @ 2022/12/22 更新),今年回鄉使用時,發現很多 app icon 縮圖也怪了,開始想裝舊版 PC app 了 XD 可惜 macOS App Store 的最新架上版本已經不能裝了,要求 macOS 版本不合。

找了一下,有些論壇都在提供舊版 dmg (或 App 壓縮檔案),瞬間才想起 macOS App Store 可以去找以前下載過的版本,瞬間可以解決需求 XD 且又不用擔心網路來源不明的程式。安裝到 Line v6.7.3.2506 版本,收工!

缺點就是佔了個 100MB 系統空間。