10 分鐘設定好 open web-ui 跟 ollama
open web-ui 是一個很方便的界面讓你可以像用 chat-GPT 那樣去跟 ollama 運行的模型對話。由於我最近收到一個 Zoraxy 的 bug report 指 open web-ui 經過 Zoraxy 進行 reverse proxy 之後出現問題,所以我就只好來裝裝看看並且嘗試 reproduce 出來了。 安裝 ollama 我這裡用的是 Debian,首先第一件事要做的當然就是安裝 ollama。教學在他們的網上有我這裡就直接寫 code 出來了。 https://ollama.com/download/linux curl -fsSL https://ollama.com/install.sh | sh 在執行這個 bash script 之後它會自動建立一個 systemd 的服務。預設 ollama 的 web server 只能透過 localhost loopback interface 存取,如果要透過其他網絡才能存取到 ollama 的 API 的話,我們就要讓它同時 listen to 其他的 network interface。最簡單直接的方式是把預設的 systemd service 檔案改成這樣: sudo systemctl stop ollama.service sudo systemctl edit ollama.service 然後在 service 檔案裡加入下面那行(見備注) ### Editing /etc/systemd/system/ollama.service.d/override.conf ### Anything between here and the comment below will become the new contents of the file # 加入下面這兩行 [Service] Environment="OLLAMA_HOST=0.0.0.0:11434" #下面的不要碰 ### Lines below this comment will be discarded ### /etc/systemd/system/ollama.service # [Unit] # Description=Ollama Service # After=network-online.target # # [Service] # ExecStart=/usr/local/bin/ollama serve # User=ollama # Group=ollama # Restart=always # RestartSec=3 # Environment="PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games" # # [Install] # WantedBy=default.target 儲存並退出之後重啟 ollama systemd 服務 sudo systemctl start ollama.service 抓模型 因為我比較喜歡用 CLI,所以我就直接透過 ssh 來順便把模型也載下來。這裡我在試玩的是 qwen:https://ollama.com/library/qwen 一般模型也會有不同大小,而我選這個是因為我要省空間(對,SSD 快要炸了),所以我就選了比較小的 1.8b 版本。你可以用這個指令來讓 ollama 準備這個模型: ollama run qwen:1.8b 如果要其他大小的模型的話,只要把後面的 1.8b 換成 4b / 7b 之類的就好了。另外比較有名的包含 llama3 之類也是可以透過這樣的方式下載。 值得一提 如果你的 root disk (Linux 的 / 或是 Windows 的 C: 硬碟)不夠空間跑你想測試的模型,你可以在啟動 ollama…
SEO 快速上手筆記
作為經營好幾個中小型的 Open Source Project website 的開發者,間中也會收到一些來自不同國家的 SEO 公司的垃圾郵件,說甚麼可以幫你把網站最佳化到上 Google 首頁結果之類的。首先,有幾點我要先說明 沒人知道 Google 的 Search Engine 排名演算法,而且很可能他們也是用 AI 來做的,他們自己的員工也不知道SEO 本身只是在 HTML file 的 head section 裡加一些 metadata 等級的簡單作業而已。就算要弄的話如果本身你會一點 HTML 跟知道要加甚麼 meta tag 的話其實也就一兩個小時的工作量就能做到外面公司的 80 - 90% 有效度。想在 Google 出第一個結果的最佳方法是幫你的產品改一個 Google Search 沒結果的名字(真的,不相信的話你去 Google 找 "Zoraxy" 看看,第一項應該就會出現我的 Zoraxy project 了,而那個 github page 根本除了 OG 以外沒做 SEO) 那正文要來了囉 HTML Head 結構 HTML Header 是一個簡單的 HTML tag。一般的話大概長這樣 <!DOCTYPE html> <html> <head> <!-- 這裡就是 Head 的部分--> </head> <body> </body> </html> 那一個完整,有做 SEO 的 head 會長怎樣呢?我們來看這個範例 <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="format-detection" content="telephone=no,email=no,address=no"> <title>托比的神奇蹦蹦主頁</title> <!-- HTML Meta Tags --> <title>imuslab</title> <meta name="description" content="創客產品與開源軟體項目,還有方便好用的各種網上小工具 | Archive of tobychui's maker products and open source projects, with online tools and service for myself but also for anyone who visited my site"> <!-- Facebook Meta Tags --> <meta property="og:url" content="https://imuslab.com"> <meta property="og:type" content="website"> <meta property="og:title" content="imuslab"> <meta property="og:description" content="創客產品與開源軟體項目,還有方便好用的各種網上小工具 | Archive of tobychui's maker products and open source projects, with online tools and service for myself but also for anyone who visited my site"> <meta property="og:image" content="https://imuslab.com/index/og.png"> <!-- Twitter Meta Tags --> <meta name="twitter:card"…
Concurrent & Racing Conditions (並發與競態條件)
最近因為我在開發 Zoraxy 時需要設計一個可以做到 high speed concurrent READ 跟間中 write 的 map,跟另一位高雄的工程師大佬討論了一個晚上,後來我發現原來滿多人對 concurrent / racing conditions 的處理有一定程度的誤解,所以我就來簡單的解釋一下最常見的誤解 - Check for locking。 問題 假設你有兩個 process,一邊要讀取一個無法同時讀寫的 map 資料結構(例如 Go 的 map[string]struct{}),另一個要寫入新的 key 到同一個 map。由於 map 並沒辦法處理 concurrent R/W,所以你要怎樣做呢? 解決方法 1:改用專用的 concurrent map (如 Go 的 sync.Map) 這個解決方法簡單直接,我就不再詳細解釋了。然而由於我在開發 Zoraxy 的 high concurrent access 特質,使用 sync.Map 的話會做成很高的 over-head,因此我並不是很想用它(但是也不影響 sync.Map 作為沒法解決時用的最後方案的決定) 解決方法 2:mutex lock 這算是比較正規的方案之一。在讀取和寫入之前先對 map (或是 slice) 進行鎖定,然後在寫入或讀取之後解鎖。這樣就能確保同時間只有一個 process 對這個 map 有 R/W access。但是這同時也會產生另一個問題,就是在 read lock 了之後,假設有另一個 process 也想同時進行讀取的話就必須要等前面的讀完,因此這個方案對於高並發的 request handling 來說並不是太有效率。 那聽到這裡的時候很多人會直覺的覺得:那只有 write 的時候 lock 就好啦,read 的時候檢查 map 是不是被 lock,沒 lock 的話就讀取。由於檢查的時候不會 lock,因此其他需要 read 的 process 也不會被擋不是嗎? 聽上去好像沒錯喔?讓我簡單寫一個 pseudocode 例子: function write(){ mutex.lock() map.write("foo", "bar") mutex.unlock() } function read(){ if (!mutex.isLocked()){ //Map is not locked var value = map.read("foo") print(value) }else{ //Map is locked, retry after 100ms delay(100) read() } } mutex 的部分可以是 programming language 自帶的 mutex 功能或是簡單的 boolean,效果都是一樣的。但是如果你有注意到,你不會找到 mutex.isLocked() 這個功能。 如果你需要用到這個,很大機會你寫錯了 這是為甚麼呢?那是因為 OS 層基本運作原理所引起的。這裡對於本科沒修 Computer Architecture (計算機架構)的工程師可能比較難理解。簡單來說就是,CPU 上就只有 n 個核心,但是你的作業系統上面有很多東西在執行,所以 OS 會對上面執行的工作(process)進行快速切換。每次切換的時候因為速度很快,所以會讓你有一種它們在並行處理的感覺。而實質上,每個 process switching 都會有一個 overhead,而這個 overhead 就是為甚麼 mutex lock 沒辦法被用於檢查的原因。 其中一種常用的 scheduling 演算法:Round Robin Process Switching 關於 CPU scheduling 跟 scheduling 演算法的詳情可以看這邊 所以為甚麼 Mutex 不能檢查是不是被其他 process 鎖上? 當你去檢查一個 mutex 是否被鎖的時候,檢查當下可能沒被鎖,但是檢查完到執行下一個邏輯的時候可能已經被另一個 process 鎖上了…
在 Linux 上建立一個有容量限制的資料夾
最近我在研究 mdadm 的時候無意中發現了一個可以依靠 losetup 和 partition 映像檔的神奇方法來建立一個具有容量限制的資料夾。所以就寫這篇來簡單記錄一下。 好,可是這有甚麼用? 之前我在開發 ArozOS 的時候想著給使用者建立一個 storage quota。本來想著用 linux 的 File System 來 implement 這個功能,可是找了很久之後發現方法都很麻煩或是過於複雜(例如依賴一些特別的 file system format / container)。所以如果剛好你想設置一個 hard limit 給一些軟體來做 buffer / upload 之類的,都可以考慮這個方法。 首先,建立一個映像檔 我們第一件事情要做的就是建立一個固定大小的映像檔。以我這裡為例,我建立了一個 64MB 的映像檔來模擬一個硬碟 partition。 bs 是 block size,就是系統會 buffer 多少 data 才會真正寫進去這個(block )device,這裡用的是 4MB。 count 是這個虛擬硬碟裡有多少個 block,16 個 4MB 的 block 加起來就是 64MB 啦 sudo dd if/dev/zero of=sdX.img bs=4M count=16 至於 /dev/zero 是甚麼,它是一個只會不斷吐出 0 的 byte stream。你可以把它當成一個無限大的檔案,裡面只存在無限個 0。透過 dd 指令,我們就可以把需要的 0 bit 從這個檔案裡 clone 出來來建立我們需要的映像檔大小。 之後:建立檔案系統 在建立了一個空白的映像檔之後,透過 ls 指令我們可以看到新鮮的 .img 檔出現了。 aroz@orangepizero2:~/raidtest$ ls sdX.img 然後我們就是需要對它進行格式化,就古人所說 Everything is a file Linus Torvalds did not say this 所以我們可以直接對它用 mkfs 跟 mount 指令。假設我們有一個叫 sdX/ 的資料夾,那我們就可以把映像檔格式化之後掛到那個資料夾。 // 建立掛載點 aroz@orangepizero2:~/raidtest$ mkdir sdX //用 ext4 把映像檔格式化 aroz@orangepizero2:~/raidtest$ sudo mkfs.ext4 sdX.img mke2fs 1.47.0 (5-Feb-2023) Discarding device blocks: done Creating filesystem with 65536 1k blocks and 16384 inodes Filesystem UUID: bf600d34-c93a-48bc-ad27-27255fbd1333 Superblock backups stored on blocks: 8193, 24577, 40961, 57345 Allocating group tables: done Writing inode tables: done Creating journal (4096 blocks): done Writing superblocks and filesystem accounting information: done //檢查看看是不是需要的資料夾和檔案都存在 aroz@orangepizero2:~/raidtest$ ls sdX sdX.img //把映像檔掛上去 aroz@orangepizero2:~/raidtest$ sudo mount sdX.img ./sdX 之後我們就可以在 df 裡看到掛載的虛擬硬碟 /…
Golang 程序連續執行一個月之後一定會出錯之神奇原因及除錯
話說半年前我開發了一個叫 imusutm 的 Simple Up Time Monitor。它是一個很簡單的程式:每隔 5 分鐘去 ping 一次 json 設定檔案裡面的網址,然後把它 buffer 到 RAM。同時提供一個 RESTFUL API 給 php script 來 call 並回傳最近一天的狀態。 可是不知道為甚麼,這程式很固定每隔一個月就會出現全部斷線的狀態,連帶用這系統的 Telegram bot 也一律全部報錯 在無論如何都沒辦法在本地端 reproduce 之後,在上一個月我把所有 error 裡都加入了 error message print-out 來協助除錯。結果被我發現問題了: 2023/02/24 20:20:34 Get "{某個要 ping 的 IP 地址}": dial tcp {某個要 ping 的 IP 地址}: bind: An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full. 一看到這個我就明白了,原本是 socket 用光了。可是一台主機的 socket 有這麼多怎可能能全部佔用? 原來是忘記了 resp.Body.Close() 於是我去看整段 code 唯一有用到 socket 的部分:http.Get(url),發現忘記了寫 resp.Body.Close() 了(222 行是新加入的),所以難怪會出現這個問題。 可是,又為甚麼是大約一個月會出現一次? Windows 的 Dynamic Port 與 ArozOS Windows 的 dynamic port range 是由 49152 到 65535。雖然有一些間中會被佔用,但是大部分都是用完之後就會被釋放出來。這兩個數字相減之後便能得出 16384 個能用的 dynamic port。 至於為甚麼是一個月?因為 每小時有 12 個 5 分鐘 x 一天 24小時 x 30天 = 8640 個 connection,然後由於 ArozOS 使用的 Go net/http 會自動斷開,而這數字又差不多是可用 port 數的一半,再加上列表上又剛好只有兩個 connection 是能維持的,我好像猜到點甚麼的樣子(? 總括而言 相信加入了 resp.Body.Close() 之後就能解決 utm 不穩定的問題了,如果下一個月再出現類似的問題我還是整個砍掉重寫好了(畢竟這只是一個我在等上飛機時的小作品,出現這些奇怪的 bug 也不怎麼意外就是了)
ESP8266 讀取 SD 卡太慢?要試試全速 SPI 嗎?
我看到日本技術宅的 Blog ,覺得奇怪這裡為甚麼他可以在 SD.begin 後面設定一個指定的速度(? https://www.mgo-tec.com/blog-entry-esp8266-wroom-spi-speed-up.html 於是我跑去翻 ESP8266 Arduino Core 的源碼,原來 ESP8266 比起原生的 Arduino core 在 SD.begin function call 多了一個可選擇指定的 uint32_t 參數,預設是 SPI 一半速度(4 Mhz),但是如果你的 SD 卡夠快(例如說現在大部分 A1 Class 10 的卡)都可以上到 8Mhz (SPI Full Speed) 或以上(這裡日本部落格用的是 40Mhz,為了資料安全好孩子不要隨便超頻你的 SD 卡) https://github.com/esp8266/Arduino/blob/313b3c07ecccbe6fee24aa9fa447c4aed16ca499/libraries/SD/src/SD.h#L35 嘛不過 ESP8266 的 WiFi 速度極限也就 4Mbps,如果要用來做網頁伺服器的話 SPI 速度設定到全速(8Mhz)已經足夠盡用它的網絡速度了。 備注:如果要設定速度的話可以用 ESP8266 SD library 內預設的常數 uint32_t const SPI_FULL_SPEED = 8000000; uint32_t const SPI_HALF_SPEED = 4000000; uint32_t const SPI_QUARTER_SPEED = 2000000;
在 Raspberry Pi Zero 2W 上設定 WiFi AP 作為 WiFi 中繼器
因為不知道為甚麼網上沒有 Raspberry Pi Zero 2W 以外接 WiFi USB 作為 WiFi 中繼器的教學,所以我就來自己研究出一個方法囉 材料 Raspberry Pi Zero 2WRaspberry Pi OS 用作 wlan1 的 USB WiFi 模組 安裝所需 Package sudo apt-get update sudo apt-get upgrade sudo apt-get install hostapd sudo apt-get install dnsmasq sudo systemctl stop hostapd sudo systemctl stop dnsmasq 設定網絡界面卡 sudo mv /etc/dnsmasq.conf /etc/dnsmasq.conf.orig sudo nano /etc/dnsmasq.conf 寫入以下內容 interface=wlan1 dhcp-range=192.168.0.11,192.168.0.30,255.255.255.0,24h 固定 wlan1 的 IP 地址 sudo nano /etc/network/interfaces 把 source /etc/network/interfaces.d/* 加上 comment: #source /etc/network/interfaces.d/* 寫入以下內容 auto lo iface lo inet loopback auto wlan0 iface wlan0 inet dhcp allow-hotplug wlan1 iface wlan1 inet static address 192.168.0.10 netmask 255.255.255.0 broadcast 192.168.0.255 gateway 192.168.0.254 設定 Hostapd sudo nano /etc/hostapd/hostapd.conf 寫入以下內容(記得更新 WiFi 名稱與密碼) interface=wlan1 hw_mode=g channel=7 wmm_enabled=0 macaddr_acl=0 auth_algs=1 ignore_broadcast_ssid=0 wpa=2 wpa_key_mgmt=WPA-PSK wpa_pairwise=TKIP rsn_pairwise=CCMP ssid=[WiFi SSID 名稱] wpa_passphrase=[WIFI 密碼] 之後編輯預設設定 sudo nano /etc/default/hostapd 把原本的 #DAEMON_CONF="" 改成 DAEMON_CONF="/etc/hostapd/hostapd.conf" 設定 wlan1 -> wlan0 的 forwarding sudo nano /etc/sysctl.conf 把 #net.ipv4.ip_forward=1 改成 net.ipv4.ip_forward=1 新增 iptables 規則 如果你是用 Lite 版沒有 iptables 可以先透過以下指令安裝 sudo apt-get install iptables 然後輸入 sudo iptables -t nat -A POSTROUTING -o wlan1 -j MASQUERADE sudo sh -c "iptables-save > /etc/iptables.ipv4.nat" 編輯 rc.local 在啟動時自動應用規則 sudo nano…
USB 與各種接口的轉換晶片
話說我一直都在研究一片叫 ArOZ Portable 的 Raspberry Pi Zero W (或 2W)用的主板,用作架設口袋雲端而使用的。它基本上就是一個 IoT Hub,引出三個 USB A port 用作連接其他裝置(如 USB 鏡頭、外接硬碟或隨身碟等等)。 而很多 ARM 開發板上因為處理器限制就只有 USB 2.0 / 3.0 接口,因此要把不同的裝置接上去 ARM 開發板的話就會需要不同的轉接器了。然而在 production 環境中,你不可以直接把轉接器包含在產品裡面(這樣看上去有夠不專業的),因此你就很會需要以下的一些晶片了。 所以以下列表總結出幾種我近幾年發現的晶片。我都把人手沒辦法焊接的包裝(如 QFN 等)都跳過了,剩下的都是人手可焊接的晶片包裝。 把萬用的 USB 接口轉換成其他的接口吧! USB Hub 說到 USB Hub 晶片第一個跳出來的應該就是 FE1.1S,畢竟他是現時市場上最便宜(和發熱量最高)的選擇,然而它是 SSOP 針腳,對創客並不友好,所以這裡我提供另外兩款 USB Hub 晶片選擇給大家參考: SL2.1A (SOP16) 只要外接 2 顆 10uF 加一塊 12Mhz 晶振就能動的 USB hub HS8836A (SOP16) 如果你連晶振都不想用的話,可以考慮用這片 HS8836A。它只需要兩顆 4.7uF 電容就好了(0.1UF),看樣子 4.7R 跟 0.1uF 的部分可以省略,但是如果你不是為了省空間的話還是加上去比較穩。 SD 轉 USB - HS8826 (SOP16) 這個我還沒用過,但看起來是可以支援 MS 跟 SD 卡的讀取的 SOP16 IC 單晶片 USB 編程微控制器 - CH552 看起來是一片內置 USB HID 的 MCU,還在研究中(以為下網絡找到的 CH552 鍵盤電路圖) 其他還在研究中 如果你發現了甚麼神奇蹦蹦的 SOP 針腳的 USB 轉換 IC 記得留言給我加上去喔!
神奇蹦蹦的 IC 與他們的用法
最近我在世界上其中一個最大的電子零件零售商的網站隨便看的時候,發現了一批不錯的 IC,於是順便把這些新發現記錄下來,之後需要用的時候就方便很多了! 基本中的基本 首先,最基本中的基本就是一些大家都在用,到處可見的 IC。由於這些 IC 的線路圖和模組設計教學整個網絡上都是,這裡就直接省略跳過詳細解釋它們的用途。通常在創客的 DIY 中最常見的就是: TP4056 - 單節 3.7V 鋰電池 1A 充電晶片XL6009 - 直流到直流升壓晶片LM2596(s) - 直流到直流降壓晶片 一些比較少見但是還是整個網絡上都是的晶片: MP1584(EN) - DC 可調降壓晶片 (小型 DIY 降壓用)D0505S-1W (D0505S-2W) - 5V DC 到 5V DC 直流隔離器(用於藍牙接收器與喇叭驅動電源隔離用)AMS1117-x (x 可以是 3.0, 3.3, 5.0 等) - 12V DC 到 x 的 LDO8205A + DW01 - 鋰電池保護板 很多時候 Maker 為了做帶電池的裝置就是用 TP4056 + XL6009 / MP1584 這樣配合著用,可是這樣做很浪費空間,所以我就開始找不少關於電源管理的方案,以下的應該就已經踏進沒甚麼人知道的領域了。 5V 0.8A 充放控制器 - HT4928(s) HT4928 是一片用起來很方便的鋰電池充電和升壓輸出的晶片。通常你很容易在那些便宜的單節 18650 行動充電器 (充電寶)裡面找到它。雖然用它來充手機是超級的慢,但是作為一些低功率裝置的供電(例如 Pi zero w)來說是不錯的選項。 5V 1A 充放控制器 - TP4333 如果 0.8A 差了一點點才夠,你可以考慮使用 TP4333。它也是一個用於行充的電源方案,但是用的外接零件會多幾個(R1,S1 跟 D5 如不需要手電筒功能可省略)。 5V 2A 充放控制器 - IP5306 / IP5307 這是一片大功率的鋰電充電和升壓晶片。能夠在 SOP8 package 裡做到 10W 的同時充放電,還帶 4 顆 LED 作電量顯示。(注:這東西發熱滿厲害的,記得底部的銅要鋪好鋪滿) 順帶一提,如果 IP5306 太貴,你可以試試看用替代用 IC FM5324 鎘鎳氫電池充電器 - CJC5122 / ASC0304B 這是一片 NiMH 充電器 IC,支援 1 到 4 顆的 NiMH 電池充電,預設是以 300mA 的充電速度來充。以下為 3.6V NiMH (3 顆串流)時的電路圖(R1 R2 及 R3 在不同配置下需要變更其電阻值) 單節鋰電池保護晶片 - XB8887A 通常的保護板需要使用兩塊晶片來做保護功能,可是這一片就能做到單片保證的功能。對於需要用到 18650 同時對空間要求很高的 project 很有用。 Charger 的部分可以配合 TP4056 晶片使用,這樣兩塊 SOP8 的晶片就能做到原本要一整片指甲大小的 PCB 的功能。 更小的單節鋰電池保護晶片 - XB5306A 如果 SOP8 還是太大,你可以考慮用這一片 XB5306A 。 使用 SOT23-6 包裝,真的打個噴嚏就不見了。電路圖跟上面的相近,Over current cutoff 電流量則降至 3A 使用 SOP8 Package 的升降壓晶片 - XL6007 對於一些對厚度很有要求的 project,要放進 XL6009 可能會有一點難度。這個時候就可以考慮使用 XL6007 了。這一片 SOP8 的晶片與 XL6009…
JavaScript 顯示時間的例子
因為間中要用到每次都要去 Stack Overflow 抓有點麻煩所以就直接記下來了: new Date().toLocaleDateString() > 2022/1/30 new Date().toLocaleString(undefined, {year: 'numeric', month: '2-digit', day: '2-digit', weekday:"long", hour: '2-digit', hour12: false, minute:'2-digit', second:'2-digit'}) > 2022年01月30日 星期日 01:05:48 new Date().toLocaleDateString('en-US', {year: 'numeric', month: '2-digit', day: '2-digit'}) > 01/30/2022 new Date().toLocaleDateString('en-ZA') > 2022/01/30 new Date().toLocaleDateString('en-CA') > 2022-01-30 new Date().toLocaleString("en-US", {timeZone: "America/New_York"}) > 1/29/2022, 12:05:48 PM new Date().toLocaleString("en-US", {hour: '2-digit', hour12: false, timeZone: "America/New_York"}) > 12
目前第 1 頁,共有 4 頁