2022年12月6日 星期二

pCloud - 使用 Cryptomator 加密資料庫與 .davfs.tmp 檔案們 @ macOS

圖:macOS - pCloud app -> Settings

最近在自己的硬碟空間越來越少,感到納悶。我之前把 Cryptomator 內的資料庫,用 pCloud Sync 的方式出去,由於 Cryptomator 跟 pCloud PC app 都有一些跟 WebDav / FUSE 相關的技術,讓人很難確定這些 .davfs.tmp 到底是哪個程式在使用,最後透過使用空間來推估,應當是 Cryptomator 機會高一點。

接著著手來刪除 .davfs.tmp 檔案,清掉快百 GB 後,再次呼叫 pCloud Sync 時,可以看到 pCloud 使用空間有慢慢降低,最終可以驗證是 Cryptomator 採用的暫存檔案。

對 Cryptomator 來說,他提供 WebDav 跟 FUSE 的溝通機制,我恰好使用 FUSE 機制,忘了 FUSE 底層也是呼叫 API 做事,若 API service 剛好又是 WebDAV 時,就滿合理了。

最後,在 pCloud PC app 中的 Settings -> Backup/Sync Exclusions -> The following files will be excluded 區,多定義個 .davfs.tmp* 未來就不用太擔心了 pCloud 空間被吃掉,但依舊要記得,哪天覺得硬碟空間不足時,可以用 find 指令找一下

註:有人會靠 `% rclone -v --dry-run --include ".davfs.tmp*" delete /path/Cryptomator` 去刪檔案

2022年11月15日 星期二

pCloud - 使用 Cryptomator 加密資料與 PC app, NAS, Linux Server 管理筆記 @ macOS 13, Ubuntu 18.04, Synology DS216play

圖:Synology - Cloud Sync

大概幾年前起,一直以來都有在跑小程式撈資料,並使用 Dropbox 來共享資料並且備份。像 Dropbox 免費版有的 2GB,更早之前甚至加一位好友有 500MB 時期,甚至還有 edu 校園比賽等病毒行銷,可以輕鬆衝到 10GB, 20GB, 甚至30GB等,這個空間也滿夠用。

現況,則是來試試 pCloud 500GB 終身版,掏錢付費 XD 被推坑後,就開始想想這個空間該怎樣規劃。首先,先來試試 Cryptomator - cryptomator.org 開源的端加密工具!pCloud 本身也有這等檔案加密的付費服務。原理上是上傳前先加密,讓雲端空間服務商無法知道資料內容。而使用 Cryptomator 時,他是透過一個檔案庫概念(可以想像跟 git 底層儲存拆成數個檔案,屬於常見的資料儲存架構),透過 WebDav 或 FUSE 建立一個虛擬空間或目錄,把資料搬進去時,順便完成加密儲存。對終端用戶來說,大概會稍微感覺讀寫資料較慢,其餘感受還好。

整體上的體驗較慢時,通常是直接掛載 pCloud 網路硬碟才會感受到,若是純本地存取 Cryptomator 加密檔案庫時,應該都還能接受的。除非是在搞 build code 等大量零碎檔案、不停更動等,才會感到不堪使用。


圖:Cryptomator PC app

使用原理上,就是先建立個 Cryptomator 加密檔案庫,接著把此資料上傳到 pCloud 即可搞定。剩下的就是存取資料的問題規劃,屬於 pCloud 在各類系統的存取方式:
  • 在 macOS 上,直接安裝 pCloud 官方提供的 PC app 最方便
  • 在 Ubuntu 18.04 server 機器上,透過 github.com/pcloudcom/console-client ,可用指令完成掛載 pCloud 網路空間進來
  • 在 Synology NAS 上,透過 pCloud WebDav API 溝通方式,可靠 Cloud Sync 完成資料同步
如此可以達到 pCloud (雲端) 有一份資料、NAS (Synology) 內也有一份資料、Macbook Pro (masOS) 也有一份資料等,三處會透過 pCloud 雲服務機制同步中。此外,若跟 NAS 在同一個區網內,倒也可以考慮純靠 NAS 同步,再靠 Samba 把 NAS 的資料掛進來使用,如此 PC 不需要額外安裝 pCloud app 處理同步。

當然,若主力有 macOS + iCloud ,也可以替代掉 pCloud 角色,沒錯,只是 iCloud 2TB 而已,通常拿來備份無止盡的照片影片了。

存取 pCloud 用法:
  1. 使用 WebDav Protocol: https://webdav.pcloud.com ,使用時無法開啟兩段式登入(2FA)或簡訊驗證,否則會無法登入
    • 在 Synology NAS 上,可以用 Cloud Sync 搭配 WebDav Protocol 來同步資料下來
  2. 使用 github.com/pcloudcom/console-client 上,需要花點心力依照專案上簡介編譯出工具來使用,同理,預設不支援兩段式登入驗證(2FA),但有人有改 code 讓他支援: github.com/dberlin/console-client
  3. 安裝 pCloud PC app ,這時可以純靠 Sync 功能,把想要的目錄進行同步即可,不一定要整包 pCloud 500GB 都下載回來使用
    • 例如負責創 Cryptomator 加密資料庫且經常更新的那台電腦可以安裝,其他台電腦不一定要安裝 pCloud PC app
在 Ubuntu server 透過指令存取 Cryptomator 加密資料庫:
  1. 首先,透過 JavaRutime/JAR 環境運行 github.com/cryptomator/cli
    • $ java -jar cryptomator-cli-0.5.1.jar --vault myVault=/pCloud/CStore/myVault/ --password myVault=MyPassword --bind 127.0.0.1 --port 12345
  2. 接著再靠 mount 指令,掛載 davfs 使用
    • $ mkdir -p /media/myVault
    • $ echo | sudo mount -t davfs -o username=,user,gid=1000,uid=1000 http://localhost:12345/myVault /media/myVault
  3. 如此,就能透過 /media/myVault 存取加密資料
目前美中不足的就是 NAS 透過 WebDAV 存取 pCloud 這步無法做2FA,令人感到不安心,但在雲台機器上,可以把 pCloud 掛在進來當網路硬碟真的很佛,像是原本一台 20GB 空間的機器,瞬間就擴增到 500GB 等級,並且把資料整理後就能塞進 pCloud 備份,很舒服的。對應使用 Dropbox 缺點就是 Dropbox 空間上,若有 5GB ,那 server 上就得佔系統空間 5GB 給他同步資料,而 server 本身並不需要多存一份 5GB 來用。

使用 Cryptomator 的最大缺點,就是任何要存取資料的電腦環境都需要安裝 Cryptomator 對應軟體,也使得 pCloud 網頁版變成沒用了,必須把 Cryptomator 完整的加密資料庫都下載到本地端才可以瀏覽。這時就得花點心思規畫要如何透過 Cryptomator 切資料儲存機制了

其他資訊:

2022年11月14日 星期一

macOS 開發筆記 - crontab 與 script 的 Operation not permitted 問題 @ macOS 13



很少會在 macOS 內埋 crontabe 做事,最近想嘗試定期做一些 log 檔案,才發現一直踩到 Operation not permitted 問題。

追了一下,主因是權限問題,只需在 設定 -> 隱私權與安全性 -> 完全取用磁碟,只需在這邊把 crontab: /usr/sbin/cron 添加進去即可。添加方式:
  1. 用 terminal 開啟 /usr/sbin 目錄:% open /usr/sbin
  2. 把 cron 拖拉進去 "設定 -> 隱私權與安全性 -> 完全取用磁碟" ,並改成 On 即可(拖進去會自動轉On)
只是,這也是另一個資安問題 XD 猜想 macOS 做更嚴謹的權限管控,也是想避開 macOS 不幸被惡意程式植入時,埋了一堆定期跑的程式。

2022年9月19日 星期一

Electron 開發筆記 - 使用 Vue.js 3 與 Vite 和最新 Electron Stable Release (20.x.x)

週末繼續把玩一下 Electron framework 也複習 Vue.js ,一直在想要怎樣兜再一起比較方便,類似的整合一直都有:
等等,後來自己想了一下,乾脆練習配置吧!自己先規劃想要的架構:
  1. 盡量把高變動的 code 都擺在 src 目錄中
  2. 盡量讓 vue.js code 可以統一在某個目錄中
  3. 直接使用最新的 Vue.js 及其最新工具
  4. 使用最新的 Electron Stable Release
  5. 盡量簡單,甚至不用 TypeScript ,讓其他新手想跨入也方便
就這樣,採用了 Electron 20.0.0 (2022.08) 和 Vue.js + Vite 環境

於是乎,先摸索了一個出來:github.com/changyy/node-electron-vue-vite

% tree src 
src
├── html
│   └── mainRenderer
│       ├── index.html
│       ├── src
│       │   ├── App.vue
│       │   ├── components
│       │   │   └── HelloWorld.vue
│       │   └── main.js
│       └── vite.config.js
├── main
│   └── index.js
├── preload
│   └── mainRenderer.js
└── renderer
    ├── index.js
    └── mainRenederer.js

7 directories, 9 files

之後再來想想看有什麼題目可以把玩吧!

2022年9月13日 星期二

Javascript 開發筆記 - 使用 Vue.js 和 Vite

規劃網頁開發時,引入一個框架是不錯的選項,讓團隊有共同溝通的語言,目前打算把以前接觸 Vue.js 部分複習一下,直接來用 Vue 3 以及 Vite ,其中 Vite 是一個很像 Webpack 的工具,號稱是下一代前端開發工具且運作效率高,看了一下是可以達成打包的程式碼任務,也能提供 dev web server + hot reload (hot module replacement/HMR) 服務。

文件資料:
這次主要接觸了 Vite ,才跑去看一下 Vue.JS 作者是誰 XD 我用了好一陣子都沒去認識一下。跑去看了一下 Vue.JS 作者的 twitter 跟 WIKI,發現人在新加坡且學經歷也滿有趣的,偶爾會分享跟小孩相處的經驗,並且也分享網路受訪又被辱罵等文,在 2022.08 剛和團隊產出了 Vue.JS 3 中文文檔,年紀相仿,滿有趣的生活。

快速上手法:

% nvm use v16
Now using node v16.13.0 (npm v8.1.0)
% npm -v
8.1.0
% npm create vite@latest my-vue-app --template vue
Need to install the following packages:
  create-vite@latest
Ok to proceed? (y) y
✔ Select a framework: › Vue
✔ Select a variant: › JavaScript

Scaffolding project in /private/tmp/my-vue-app...

Done. Now run:

  cd my-vue-app
  npm install
  npm run dev
% cd my-vue-app 
% cat package.json 
{
  "name": "my-vue-app",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "vue": "^3.2.37"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^3.1.0",
    "vite": "^3.1.0"
  }
}
% tree     
.
├── README.md
├── index.html
├── package.json
├── public
│   └── vite.svg
├── src
│   ├── App.vue
│   ├── assets
│   │   └── vue.svg
│   ├── components
│   │   └── HelloWorld.vue
│   ├── main.js
│   └── style.css
└── vite.config.js

4 directories, 10 files
% npm install
% npm run dev

  VITE v3.1.0  ready in 435 ms

  ➜  Local:   http://127.0.0.1:5173/
  ➜  Network: use --host to expose

接著去編輯 src/components/HelloWorld.vue 可以看到網頁內容立即改變。如此大概就可以窺見 Vite 的基本用法,接著是編譯:

% npm run build

> my-vue-app@0.0.0 build
> vite build

vite v3.1.0 building for production...
✓ 16 modules transformed.
dist/assets/vue.5532db34.svg     0.48 KiB
dist/index.html                  0.44 KiB
dist/assets/index.43cf8108.css   1.26 KiB / gzip: 0.65 KiB
dist/assets/index.795d9409.js    52.87 KiB / gzip: 21.33 KiB

% tree dist 
dist
├── assets
│   ├── index.43cf8108.css
│   ├── index.795d9409.js
│   └── vue.5532db34.svg
├── index.html
└── vite.svg

1 directory, 5 files

% cat dist/index.html 
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + Vue</title>
    <script type="module" crossorigin src="/assets/index.795d9409.js"></script>
    <link rel="stylesheet" href="/assets/index.43cf8108.css">
  </head>
  <body>
    <div id="app"></div>
    
  </body>
</html>

以上就簡單過一下常用的東西。

Javascript 開發筆記 - 使用 npm/npx degit 管理開發模板

當你把你習慣的部分都設置在 github 後,可以靠以下資料快速複製出來使用:

% npx degit changyy/study-node-electron-simple my-project 
> cloned changyy/study-node-electron-simple#HEAD to my-project
% cd my-project 
% source env_nvm.sh 
Now using node v16.13.0 (npm v8.1.0)
% npm install
% npm run start

> my-electron-app@1.0.0 start
> electron-forge start

✔ Checking your system
✔ Locating Application
✔ Preparing native dependencies
✔ Launching Application

其中 degit 套件還滿方便的,可以快速複製樣板出來並去除 git 相關結構。而使用 npx 的指令,他本身 man page 介紹是 Run a command from a local or remote npm package,可以用完就拋掉也不用安裝在本地。

相關資料:

2022年9月11日 星期日

[Linux] 免費註冊 pCloud 獲得 500GB 空間 / 掛載 pCloud 空間進來使用 @ Docker - Ubuntu 22.04 Image

圖:pcloud.com 活動截圖 

pCloud 是瑞士的雲端空間服務,有在推 500GB/2TB/10TB 等終身付費服務,通常會隨著節慶推出 80% OFF 的方案(兩折),有興趣可以去逛逛粉絲團,像是註冊完一個免費帳號後 ( https://u.pcloud.com/#page=register&invite=cFRj7ZYwNRty 使用此連結註冊成功有機會送我1GB空間? ),接著再瀏覽 https://www.pcloud.com/zh/500GB_free 連結,可以啟用 3個月 500GB 免費方案 (這應該是限時活動,不知何時結束)

接著再來講講在 Linux/Ubuntu server 把 pCloud 網路空間用網芳的機制掛在進來,實作上是用 Filesystem in Userspace (FUSE) 機制。我在 Ubuntu 18.04 上,原本要安裝特定環境,但踩到 libudev-dev 安裝上的套件限制,想著想就改用 docker 來跑:

$ sudo docker run -i -t --privileged ubuntu:22.04

root@109661692d2c:/# 

# uname -a
Linux 109661692d2c 5.4.0-1082-oracle #90~18.04.1-Ubuntu SMP Mon Aug 15 16:42:41 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

# apt update

# apt-get install cmake zlib1g-dev libboost-system-dev libboost-program-options-dev libpthread-stubs0-dev libfuse-dev libudev-dev fuse build-essential git 


# cd ./console-client/pCloudCC/lib/pclsync/
# make clean && make fs
# cd ../mbedtls/ && cmake . && make clean && make 
# cd ../.. && cmake . && make && make install
# ldconfig
# which pcloudcc
/usr/local/bin/pcloudcc
# pcloudcc 
pCloud console client v.2.0.1
Username option is required!!!

以上就是編譯出 pCloud - pcloudcc 指令的過程,接著使用時,在 docker 裡可以靠 tmux or screen 來切換,在此用 screen (因為我已經先用 tmux 包一層 XD)

# apt install screen
# screen

screen 1# pcloudcc -u YourpCloudAccount           
pCloud console client v.2.0.1
Down: Everything Downloaded| Up: Everything Uploaded, status is LOGIN_REQUIRED
Please, enter password
logging in
Down: Everything Downloaded| Up: Everything Uploaded, status is CONNECTING
Down: Everything Downloaded| Up: Everything Uploaded, status is SCANNING
Down: Everything Downloaded| Up: Everything Uploaded, status is READY

如此,接著可以靠 screen 切另一個視窗

screen 2# df -h
Filesystem      Size  Used Avail Use% Mounted on
overlay          45G   38G  7.7G  84% /
tmpfs            64M     0   64M   0% /dev
tmpfs           487M     0  487M   0% /sys/fs/cgroup
shm              64M     0   64M   0% /dev/shm
pCloud.fs       500G  7.3G  493G   2% /pcloud

screen 2# tree /pcloud/
/pcloud/
|-- Getting\ started\ with\ pCloud.pdf
|-- My\ Music
|   |-- Demo\ Audio\ 2.mp3
|   |-- GotJoy.mp3
|   |-- Lovely\ Day.wav
|   `-- Momentum.mp3
|-- My\ Pictures
|   |-- friends.jpg
|   |-- happy-family.jpg
|   |-- in-the-sky.jpg
|   |-- lovers.jpg
|   |-- romance.jpg
|   `-- sweet.jpg
`-- My\ Videos
    `-- pCloud.mp4

3 directories, 12 files

最後,如果要打包目前 docker 的環境,要回到 host 上進行 commit :

$ sudo docker ps
CONTAINER ID   IMAGE          COMMAND   CREATED          STATUS          PORTS     NAMES
123456789012   ubuntu:22.04   "bash"    25 minutes ago   Up 25 minutes             eager_bardeen

$ sudo docker commit 123456789012 pcloudcc:1.0
sha256:daf74928ed5ef2ee7c8d158ecf066d2b0707a9c145df4829488e01bf3bda751b

$ sudo docker images
REPOSITORY           TAG       IMAGE ID       CREATED              SIZE
pcloudcc             1.0       daf74928ed5e   43 seconds ago       769MB

下次要使用時,再進入,可以省去重裝一堆東西:

$ sudo docker run -i -t --privileged pcloudcc:1.0
root@06bbad280e77:/# 

另外,若想跟 host 環境做綁定,可以用 sudo docker run -i -t --privileged -v ~/my_host_dir:/vm_dir cloudcc:1.0,把 host 端的目錄掛載進去 docker vm 中,可以在靠 rsync 等指令把檔案傳到 pCloud 空間上!在 Oracle VM - JP Region 上,上傳到 pCloud US region 的速度可以來到均速 20MB/s 上喔,算是非常不錯的品質

此外,pCloud 採用類似網芳方式 mount 進來使用,對於這些虛擬機器只有 20GB, 40GB 來說,可以瞬間擴大到 500GB 等空間,很舒服 XD 且不像使用 Dropbox 機制,佔用到本機空間的。

最後,補充一下關於 docker 使用,由於 pcloudcc 會使用 fuse 環境,因此要求更高權限:特權容器 (privileged container) ,這件事是有風險的,當 docker 裡頭運行的程式被黑了,有可能傷及到 host 環境,但無論如何,在 host 安裝環境本來也會有風險沒錯 XD 就留意一下即可

以上體驗後,再自行評估要不要拿來當虛擬機的備份用法囉!

[Linux] apt: error while loading shared libraries: libudev.so.1: cannot open shared object file: No such file or directory @ Ubuntu 18.04

安裝套件時,有碰到 libudev-dev 相關套件太新:

$ sudo apt install libudev-dev
Reading package lists... Done
Building dependency tree       
Reading state information... Done
Some packages could not be installed. This may mean that you have
requested an impossible situation or if you are using the unstable
distribution that some required packages have not yet been created
or been moved out of Incoming.
The following information may help to resolve the situation:

The following packages have unmet dependencies:
 libudev-dev : Depends: libudev1 (= 237-3ubuntu10.53) but 237-3ubuntu10.54 is to be installed
E: Unable to correct problems, you have held broken packages.

$ dpkg -l | grep libudev
ii  libudev1:amd64                            237-3ubuntu10.54                            amd64        libudev shared library

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 18.04.6 LTS
Release: 18.04
Codename: bionic

然後,就耍蠢把他移掉 XD

$ sudo dpkg --purge --force-all libudev1
dpkg: libudev1:amd64: dependency problems, but removing anyway as you requested:
 util-linux depends on libudev1 (>= 183).
 libpci3:amd64 depends on libudev1 (>= 196).
 initramfs-tools-bin depends on libudev1 (>= 183).
 liblvm2app2.2:amd64 depends on libudev1 (>= 183).
 libplymouth4:amd64 depends on libudev1 (>= 183).
 udev depends on libudev1 (= 237-3ubuntu10.54).
 libusb-1.0-0:amd64 depends on libudev1 (>= 183).
 snapd depends on libudev1 (>= 183).
 liblvm2cmd2.02:amd64 depends on libudev1 (>= 183).
 libdevmapper1.02.1:amd64 depends on libudev1 (>= 183).
 open-vm-tools depends on libudev1 (>= 183).
 lvm2 depends on libudev1 (>= 183).
 dosfstools depends on libudev1 (>= 183).
 libapt-pkg5.0:amd64 depends on libudev1 (>= 183).

(Reading database ... 157028 files and directories currently installed.)
Removing libudev1:amd64 (237-3ubuntu10.54) ...
Processing triggers for libc-bin (2.27-3ubuntu1.6) ...

一堆指令都高度依賴不能跑了 XD

$ sudo apt update
apt: error while loading shared libraries: libudev.so.1: cannot open shared object file: No such file or directory

解法也不難,在用 dpkg 裝回來,在 http://archive.ubuntu.com/ubuntu/pool/main/s/systemd/ 尋找

$ wget http://archive.ubuntu.com/ubuntu/pool/main/s/systemd/libudev1_237-3ubuntu10.54_amd64.deb
$ sudo dpkg -i libudev1_237-3ubuntu10.54_amd64.deb
Selecting previously unselected package libudev1:amd64.
(Reading database ... 157024 files and directories currently installed.)
Preparing to unpack libudev1_237-3ubuntu10.54_amd64.deb ...
Unpacking libudev1:amd64 (237-3ubuntu10.54) ...
Setting up libudev1:amd64 (237-3ubuntu10.54) ...
Processing triggers for libc-bin (2.27-3ubuntu1.6) ... 
$ sudo apt update
Get:1 http://security.ubuntu.com/ubuntu bionic-security InRelease [88.7 kB]
Hit:2 http://ap-tokyo-1-ad-1.clouds.archive.ubuntu.com/ubuntu bionic InRelease
Get:3 http://ap-tokyo-1-ad-1.clouds.archive.ubuntu.com/ubuntu bionic-updates InRelease [88.7 kB]
Get:4 http://ap-tokyo-1-ad-1.clouds.archive.ubuntu.com/ubuntu bionic-backports InRelease [74.6 kB]
Fetched 252 kB in 2s (128 kB/s)   
Reading package lists... Done
Building dependency tree       
Reading state information... Done
All packages are up to date.

記錄一下,而真實的任務還沒解決,推論應該是裝上舊把對應的 so 路徑修一下即可,目前還是走安全一點的路徑,用虛擬環境/docker來執行

ref:

2022年9月1日 星期四

Electron 開發筆記 - 使用 @electron-forge/plugin-webpack

評估 electron 到底要跟哪套網頁開發的流程合在一起用,先研究一下 @electron-forge/plugin-webpack,粗略整合還堪用,但還有一些路徑或程式碼架構要處理。

先規劃了 src 目錄:

  % tree src 
  src
  ├── html
  │   └── mainRenderer
  │       ├── index.html
  │       └── index.js
  ├── main
  │   └── index.js
  ├── preload
  │   └── mainRenderer.js
  └── renderer
      ├── index.js
      └── mainRenederer.js
  
  5 directories, 6 files

其中 html 就是預期每個 renderer 的網頁所在地;而 main/index.js 就是 Electron main process 處理的項目;而 renderer/mainRenederer.js 是預期在 Electron main process 中處理 createWindow 的項目抽取出來重複使用;而 preload/mainRenderer.js 就是 Electron Renderer 展現前的 preload script

如此,想讓純前端網頁的程式碼都可以賴在 html 目錄下,屆時要靠常見工具封裝成 SPA 也方便。

而使用 electron-forge/plugin-webpack 上,只需要留意二件事:

1. 在 package.json 描述要被 webpack 封裝的項目

% cat package.json | jq '.config'
{
  "forge": {
    "plugins": [
      [
        "@electron-forge/plugin-webpack",
        {
          "mainConfig": "./webpack.main.config.js",
          "renderer": {
            "config": "./webpack.renderer.config.js",
            "entryPoints": [
              {
                "name": "main_window",
                "html": "./src/html/mainRenderer/index.html",
                "js": "./src/html/mainRenderer/index.js",
                "preload": {
                  "js": "./src/preload/mainRenderer.js"
                }
              }
            ]
          }
        }
      ]
    ]
  }
}

2. 在 Electron main process 中,使用 createWindow 後要讀取的 index.html 跟 preload.js 要改用變數

MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY, MAIN_WINDOW_WEBPACK_ENTRY

目前還會卡到 webpack dev server 運行時,有些 js code 執行異常,正考慮不要靠 webpack dev server,可能就要正式離開 @electron-forge/plugin-webpack" 用法了

2022年8月30日 星期二

Electron 開發筆記 - 初探 ElectronJS 框架開發 PC app

約莫一年前團隊同事接了個用 ElectronJS 開發的程式,並且依照其框架成果再做出另一個 PC app ,最近播點時間來研究一下 ElectronJS 。幾個月前接觸 Golang 時,也曾想過用 Golang + Electron 練習一下 PC app ,只是整體上運作起來還是太卡了點。

整體上,要認識 ElectronJS 建議都先看一次官方文件 www.electronjs.org/docs/latest/ ,基本上寫的精簡不錯,看完一輪 Processes in Electron 就會學會:
  • Electron 的流程架構 (Process Model),可以分成 main process (node.js / main.js) 和 renderer process (BrowserWindow / renderer.js ),並且透過 preload.js 串起 main process 跟 renderer process 溝通橋樑
  • preload.js 是在 renderer process 執行前運行的,等於進入到 renderer process 前,可以打造一些溝通環境
  • 資安領域上,如何讓 renderer process 的權限不要太大 (Context Isolation)
  • 關於 main process 和 renderer process 溝通時,該怎樣進行 (ipcMain, ipcRenderer),可以分成 Renderer to Main 單向溝通、Renderer to Main 雙向溝通、Main to Renderer 溝通等
  • 看一眼 Process Sandboxing 情況,再多了解一下 MessagePorts 用法 (像 window.postMessage 之 iframe 溝通)
如此,就差不多可以來用 Electron 開發 PC app,然而有更多眉眉角角躲在 package.json 中以及一堆神秘的指令,如 electron-forge 等等。

我想,開發 Electron 的複雜度,源自於整個專案進行會跨非常多領域(指令)吧,每個混再一起就會覺得爆炸大,每個分離看待就會舒適許多。

像我自己也弄了:
  • env_nvm.sh:靠 nvm 切換 node.js 版本
  • env_vim.sh:靠 .vimrc 和 .vimrc_coc.nvim 完成簡易 coding style 和 auto-complete 設置
  • 把上述包裝在 init_env.sh 運行,每次開發就先跑一下 source init_env.sh 就行了
最後,對於 Electron PC app 開發,後續也都還會再做一些程式碼封裝方式(webpack等)和程式碼混淆等,當然,若還有開發框架 vue.js, react 等,也都還有對應的任務,所以連續動作還是很多的,之後繼續筆記一下。

2022年8月27日 星期六

Go 開發筆記 - 台灣身分證檢查器/大量產生器

晚上跟數學系的朋友聊幾句,他用個公式算出合法的帳號數字(滿合理的遠大於台灣人口數),於似乎,來用鳥鳥暴力解試試,並且練一下 golang 的 goroutine 用法。當然最後的資料個數是一樣的,共有 5.2億組。總數公式 = 26 (字母) * 2 (男女) * 10^7 (最後一碼是校正碼,只有七種變化)

只是我用暴力解,偷懶用多層 loop 發一下波動拳 XD 理論上還可以優化最後一碼用算的,而不是用暴力算,在一台老電腦上(3.1 GHz 雙核心Intel Core i7),大概花 1小時產出(包括每百萬筆寫到檔案)

最後的成果:

% go run main.go --verify A123456789
A123456789 : PASS, time cost: 23.449µs

% go run main.go --verify A123456780
A123456780 : FAIL, time cost: 25.528µs

% time go run main.go --cpu 4
...
taskID: 0, taskCount:    1000000, totalCount:  510000000, timeCost: 57m56.195527594s, mem: 336 MB
taskID: 2, taskCount:    1000000, totalCount:  511000000, timeCost: 58m12.348436403s, mem: 286 MB
taskID: 1, taskCount:    1000000, totalCount:  512000000, timeCost: 58m17.794134191s, mem: 360 MB
taskID: 0, taskCount:    1000000, totalCount:  513000000, timeCost: 58m18.106069808s, mem: 380 MB
taskID: 2, taskCount:    1000000, totalCount:  514000000, timeCost: 58m33.170090521s, mem: 247 MB
taskID: 1, taskCount:    1000000, totalCount:  515000000, timeCost: 58m39.34318826s, mem: 308 MB
taskID: 0, taskCount:    1000000, totalCount:  516000000, timeCost: 58m39.902875471s, mem: 370 MB
taskID: 2, taskCount:    1000000, totalCount:  517000000, timeCost: 58m50.616989581s, mem: 133 MB
taskID: 1, taskCount:    1000000, totalCount:  518000000, timeCost: 58m55.655774417s, mem: 67 MB
taskID: 2, taskCount:    1000000, totalCount:  519000000, timeCost: 59m3.85291194s, mem: 111 MB
taskID: 2, taskCount:    1000000, totalCount:  520000000, timeCost: 59m14.91097006s, mem: 111 MB
Total verified IDs count: 520000000 , time cost: 59m18.802491746s
go run main.go --cpu 4  9320.09s user 3332.58s system 355% cpu 59:19.56 total

% ls -lah /tmp/ids*
-rw-------  1 user  wheel   1.4G  8 27 08:41 /tmp/ids-0.txt
-rw-------  1 user  wheel   1.4G  8 27 08:41 /tmp/ids-1.txt
-rw-------  1 user  wheel   1.4G  8 27 08:42 /tmp/ids-2.txt
-rw-------  1 user  wheel   1.0G  8 27 08:28 /tmp/ids-3.txt

% wc -l /tmp/ids-* 
 140000000 /tmp/ids-0.txt
 140000000 /tmp/ids-1.txt
 140000000 /tmp/ids-2.txt
 100000000 /tmp/ids-3.txt
 520000000 total

2022年8月25日 星期四

C 開發筆記 - 使用 libwebsockets 製作簡易的 WebSocket Client

已經有用過 Golang 開發小工具,以及其他現成工具如 websocat 等,做 WebSocket Server 的測試。這次回過頭來再仔細看看 libwebsockets 這套,寫一點 C 語言。

程式架構不會太難,比較難的反而是一些小東西,像是跟 ws.ptt.cc 連線時,PTT 會做 Request Header(Origin欄位) 檢查,當作簡單的管控,然而 libwebsockets 則是要設定  LWS_SERVER_OPTION_JUST_USE_RAW_ORIGIN 才能不加料 XD

#include <include/libwebsockets/lws-context-vhost.h>

For backwards-compatibility reasons, by default lws prepends "http://" to the origin you give in the client connection info struct. If you give this flag when you create the context, only the string you give in the client connect info for .origin (if any) will be used directly.

大概在這種地方耗掉半小時以上。另外,還有做 wss 連線時,也須設定 LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT 等等,以及如果想要略過 wss 的 SSL 憑證檢查,有滿多設定項目可以試試:LCCSCF_USE_SSL | LCCSCF_ALLOW_SELFSIGNED | LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK | LCCSCF_ALLOW_INSECURE;

其他則是依照 libwebsockets 的架構,要設計連線時的 callback 處理機制,算是體驗了一下。未來就多一個 C 語言工具可以做 WebSocket Server 連線測試。


常見的錯誤訊息:
  • E: SSL_new failed: error:00000063:lib(0)::reason(99)
  • W: [wsicli|0|WS/h1/default/ws.ptt.cc]: lws_client_ws_upgrade: got bad HTTP response '403'
  • E: lws_ssl_client_bio_create: Unable to get hostname
執行 logs:

% make
gcc -g -Wall main.c `pkg-config libwebsockets --libs --cflags`

% ./a.out   
Usage> ./a.out url
./a.out ws://localhost:8000/
./a.out wss://ws.ptt.cc/bbs app://cmd
./a.out wss://ws.ptt.cc/bbs https://term.ptt.cc

% ./a.out wss://ws.ptt.cc/bbs https://term.ptt.cc
[2022/08/25 20:45:29:8530] N: lws_create_context: LWS: 4.3.2-no_hash, NET CLI SRV H1 H2 WS ConMon IPV6-on
[2022/08/25 20:45:29:8539] N: __lws_lc_tag:  ++ [wsi|0|pipe] (1)
[2022/08/25 20:45:29:8964] N: __lws_lc_tag:  ++ [vh|0|default||-1] (1)
wsCallback - LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS
[INFO] libwebsockets version: 4.3.2
[INFO] lws_parse_uri - Server: ws.ptt.cc:443
[INFO] lws_parse_uri - URLScheme: wss
[INFO] lws_parse_uri - URLPath: bbs
[INFO] clientConnectInfo.addres: ws.ptt.cc
[INFO] clientConnectInfo.port: 443
[INFO] clientConnectInfo.path: /bbs
[INFO] clientConnectInfo.host: ws.ptt.cc
[INFO] clientConnectInfo.origin: https://term.ptt.cc
[INFO] clientConnectInfo.ssl_connection: 109
[2022/08/25 20:45:29:9789] N: __lws_lc_tag:  ++ [wsicli|0|WS/h1/default/ws.ptt.cc] (1)
call lws_client_connect_via_info done, webSocket is NULL(NO)
noop
[2022/08/25 20:45:30:0229] N: lws_gate_accepts: on = 0
[2022/08/25 20:45:30:0575] N: lws_gate_accepts: on = 0
wsCallback - LWS_CALLBACK_CLIENT_ESTABLISHED
wsCallback - LWS_CALLBACK_CLIENT_WRITEABLE
wsCallback - LWS_CALLBACK_CLIENT_RECEIVE
RECEIVER: HTTP/1.1 200 OK

     ??      PTT                ?P   ??        ?P  ??      ???i?i?i?i?i?i??
             140.112.172.11 ?P     ???i???i??            ???i?i?i?i?i
  ?z?w?{     ?????~?{        ???d?i?i???i??   ?P   ???i?i?i?i?i??  ?P
  ?x?V?|?{   ptt.cc            ???i?i?i?i?i???i??    ???i?i?i?i?i
  ?x?V  ?x                   ???i?i?i???i?i?i????  ???i?i?i?i?i??  ??  ?P
?w?}    ?x?z?w?w?{  ?P       ????      ?i?i?i?i?????i?i?i?i    ?P
        ?|?t  ?V?x      ?P           ???i?i?i?i???i?i?i??            ?P
                ?x?z?w?w?w?{         ?i?i?i?i?i?h?h?g?g?f?f?e?e?d?c?b
            ?z?w?r?}?V?V  ?| ???i?i?i?i?i?h?h?g?g?f?f?e?e?d?d?c?c?b
            ?x?V?V           ???i?i
wsCallback - LWS_CALLBACK_CLIENT_RECEIVE
RECEIVER:       ?i?h?h?g?g?f?f?e?e?d?c?b

wsCallback - LWS_CALLBACK_CLIENT_RECEIVE
RECEIVER:            ?f?d?d?e?g?f?e         ?e?d?g??     ???âg?d?e       ????            
               ?g?h  ?h         ?e?d         ?b      ???c?g?d                   
?п?J?N???A?ΥH guest ???[?A?ΥH new ???U:               
^C   ?b?c?e?f?e?g?h?g?f?d    ?n  ??   ??  ???n?l?? ??  ???? ?l?k  ?h            

使用 curl 指定 Server IP 檢驗 SSL 憑證 (SKIP DNS LOOKUP)

原先是想要做 curl -I https://IP 檢驗時,在想怎樣處理憑證檢驗,像是多塞 "Host: test.com" 是不是有用,最後查了一下,要反過來想,可以多增加 --resolve 參數去指定,如此就可以測試指定的機器了

% man curl 
...
       --resolve <[+]host:port:addr[,addr]...>
              Provide a custom address for a specific host and port pair. Using this, you can make the curl requests(s) use a specified
              address and prevent the otherwise normally resolved address to be used. Consider it a sort of /etc/hosts alternative
              provided on the command line. The port number should be the number used for the specific protocol the host will be used for.
              It means you need several entries if you want to provide address for the same host but different ports.

              By specifying '*' as host you can tell curl to resolve any host and specific port pair to the specified address. Wildcard is
              resolved last so any --resolve with a specific host and port will be used first.

              The provided address set by this option will be used even if --ipv4 or --ipv6 is set to make curl use another IP version.

              By prefixing the host with a '+' you can make the entry time out after curl's default timeout (1 minute). Note that this
              will only make sense for long running parallel transfers with a lot of files. In such cases, if this option is used curl
              will try to resolve the host as it normally would once the timeout has expired.

              Support for providing the IP address within [brackets] was added in 7.57.0.

              Support for providing multiple IP addresses per entry was added in 7.59.0.

              Support for resolving with wildcard was added in 7.64.0.

              Support for the '+' prefix was was added in 7.75.0.

              This option can be used many times to add many host names to resolve.

              Example:
               curl --resolve example.com:443:127.0.0.1 https://example.com

              See also --connect-to and --alt-svc.

這用法可以用在 load balancer 或 GeoDNS 後面的機器群測試,也能拿來測試某機器的 SSL 憑證是否設定正常。

2022年8月23日 星期二

Cloudflare 1.1.1.1 FOR FAMILIES 提供阻擋惡意網站與成人網站的 DNS 查詢服務


最近跟朋友聊天,才發現 Cloudflare 除了提供 1.1.1.1 反應速度極快的 DNS 查詢服務外,還有提供阻擋惡意網站、成人網站的 DNS 查詢服務!當發現是惡意網站時,直接給予 0.0.0.0 (查不到) 的回饋,如此使用者在瀏覽網站時,不小心點擊到的惡意連結也不會瀏覽到,對於家裡有長輩、小孩的環境來說,真的很不錯。

大概五六年前在幫公司做 使用者生成內容 (User-generated content) 時,再加上規劃權限機制,打算從大家公開的資料中,整理出熱門的網站/影片連結出來,這時就會踩到有人擺了色情網站等問題,那時則是做了一套 URL Checker/Filter (SPAM) 機制,透過網域黑名單做的。現在,單純用 Cloudflare 提供的 DNS 來查詢也能直接達到類似的效果。

Cloudflare 1.1.1.1 FOR FAMILIES: https://one.one.one.one/family/
  • 1.1.1.1
    • 與 Google 8.8.8.8, 8.8.4.4 同樣效果但標榜更快、更注重隱私
    • 2018.04.01 開始的服務
  • 1.1.1.2, 1.0.0.2
    • 可阻擋惡意網站: Malware Blocking Only
  • 1.1.1.3, 1.0.0.3
    • 可阻擋惡意網站+成人網站: Malware and Adult Content Blocking Together
如此,在疫情後常態在家上課時,小孩拿手機平板連到家裡的 WIFI AP ,這時在 WIFI AP 的 DHCP 設置 DNS = 1.1.1.3 , 1.0.0.3 後,就可以簡單防護到所有連到此 WIFI AP 的設備,可防止小孩誤點、誤連到惡意網站和成人網站內容,畢竟有不少惡意網站/成人網站仍會買流量的,立即能避開一堆奇怪廣告。當然,對長輩來說也有一樣的效果。

圖:TP-Link WIFI AP 之 DHCP 設定 主要 DNS / 次要 DNS

其他:

2022年8月20日 星期六

Go 開發筆記 - 簡易 WebSocket Client 練習,WSS 與 Skip SSL Check

公司的 WebSocket Server 使用 node.js Primus 建構服務,最近正在調整架構,弄隻 WebSocket Client 交叉比對,這時就想到 Golang 可以練一下 XD 預設先以 PTT 的 WebSocket 為例 wss://ws.ptt.cc/bbs 練習,而 PTT WebSocket 連線時,有檢查 Request Header 的 Origin 欄位的,完整的用法請直接使用 https://term.ptt.cc 。

實作上,直接用 Gorilla WebSocket 即可,用他建制 WebSocket server 跟 client。

此外,對於 WebSocket over SSL (wss) 連線瑣事上,若要處理 SKIP SSL Check 這件事,可以這樣處理:

// https://pkg.go.dev/crypto/tls
type zeroSource struct{}
func (zeroSource) Read(b []byte) (n int, err error) {
    for i := range b { 
        b[i] = 0 
    }   
    return len(b), nil 
}

func main() {

// ...

    var dialer ws.Dialer
    if *skipSSLCheck {
        fmt.Println("[IFNO] Skip SSL check")
        // https://github.com/gorilla/websocket/blob/master/client.go
        // https://github.com/gorilla/websocket/blob/master/tls_handshake.go
        dialer = ws.Dialer{
            Subprotocols: []string{},
            ReadBufferSize: 2048,
            WriteBufferSize: 2048,
            TLSClientConfig: &tls.Config{
                Rand: zeroSource{},
                InsecureSkipVerify: true,
            },
        }
    } else {
        dialer = ws.Dialer{
            Subprotocols: []string{},
            ReadBufferSize: 2048,
            WriteBufferSize: 2048,
        }
    }
    fmt.Println("[IFNO] try to connect to: ", connectToURL)

// ...

}

至於跟 node.js Primus WebSocket server 溝通時,會有 PING/PONG 的 heartbeat 檢驗,目前偷懶等收到 server 詢問時才回,此外,實際上可以靠 messageType (Control Messages) 來判斷:

    go func() {
        defer func() {
            wg.Done()
        }()
        for {
            _, p, err := conn.ReadMessage()
            if err == nil {
                cmd := string(p)
                fmt.Println("SERVER> ", cmd)

                    if strings.Index(cmd, "\"primus::ping::") >= 0 {
                        text := fmt.Sprintf("\"primus::ping::%d\"", time.Now().UnixMilli())
                        fmt.Println("CLIENT:AUTO> [",text,"]")
                        conn.WriteMessage(ws.TextMessage, []byte(text))
                    }

            } else {
                fmt.Println("SERVER:ERROR> ", err)
                break
            }
        }
        fmt.Println("[IFNO] server error")
    }()

更多完整的資訊,就直接參考 github.com/changyy/go-ws-client

2022年8月8日 星期一

PHP 開發筆記 - 在 Windows 10 上配置 cmd line 開發環境


這個故事說來話長,我本身不會在 Windows 開發,但為了同事只好配置環境出來,交叉比對運作的問題。就筆記一下,這次要做的事就弄到純 cmd line 開發環境:

安裝:

如此,在 cmd 就可以靠純指令工作,像是 composer install ,而在 cmd line 中有 vim 編輯器,可以快速編輯小東西。

2022年8月3日 星期三

PHP 開發筆記 - 關於 CodeIgniter4 starter app / PHP Coding Standards Fixer v3.9.5 / PHP 7.4 @ macOS 與 Windows

之前初探 PHP Coding Standards Fixer (php-cs-fixer) 時,略知預設跑下去已經有一定水準的 Coding Style 制約機制,然而,工作上引導同事在使用時,碰到了一些雷區。

首先是用 CodeIgniter 4 官方文件建立出的資料後,要跑 PHP Coding Standards Fixer 時會出現要修正語法的問題

```
// https://www.codeigniter.com/user_guide/installation/installing_composer.html
% composer create-project codeigniter4/appstarter project-root
% ./vendor/bin/php-cs-fixer fix --dry-run -v .
PHP CS Fixer 3.9.5 Grand Awaiting by Fabien Potencier and Dariusz Ruminski.
PHP runtime: 7.4.30
Loaded config CodeIgniter4 Coding Standards from "/path/./.php-cs-fixer.dist.php".
Using cache file ".php-cs-fixer.cache".
Paths from configuration file have been overridden by paths provided as command arguments.
.....F......................................F.............                                                                            58 / 58 (100%)
Legend: ?-unknown, I-invalid file syntax (file ignored), S-skipped (cached or empty file), .-no changes, F-fixed, E-error
   1) app/Config/Logger.php (ordered_imports)
   2) app/Views/errors/html/error_exception.php (braces, unary_operator_spaces, not_operator_with_successor_space)

Checked all files in 1.104 seconds, 16.000 MB memory used
```

然後故意降版本到 v3.8.0 又可以正常

```
% mv composer.json composer.json-bak
% composer require friendsofphp/php-cs-fixer:3.8.0 --dev
% ./vendor/bin/php-cs-fixer fix --dry-run -v .
PHP CS Fixer 3.8.0 BerSzcz against war! by Fabien Potencier and Dariusz Ruminski.
PHP runtime: 7.4.30
Loaded config default.
Using cache file ".php-cs-fixer.cache".
..........................................................                                                                            58 / 58 (100%)
Legend: ?-unknown, I-invalid file syntax (file ignored), S-skipped (cached or empty file), .-no changes, F-fixed, E-error

Checked all files in 0.303 seconds, 14.000 MB memory used
```

為了避免未來同事不同平台開發的困局,就來安分地寫一下 .php-cs-fixer.dist.php 檔案吧!也順道研究到 CodeIgniter 也有個 CodeIgniter/coding-standard 規範,接著參考 github.com/CodeIgniter/coding-standard#setup 一步一步測試

```
% cat .php-cs-fixer.dist.php
<?php

use CodeIgniter\CodingStandard\CodeIgniter4;
use Nexus\CsConfig\Factory;

return Factory::create(new CodeIgniter4())->forProjects();

% ./vendor/bin/php-cs-fixer fix --dry-run -v .
PHP CS Fixer 3.9.5 Grand Awaiting by Fabien Potencier and Dariusz Ruminski.
PHP runtime: 7.4.30
Loaded config CodeIgniter4 Coding Standards from "/path/./.php-cs-fixer.dist.php".
Using cache file ".php-cs-fixer.cache".
Paths from configuration file have been overridden by paths provided as command arguments.
.....F......................................F.............                                                                            58 / 58 (100%)
Legend: ?-unknown, I-invalid file syntax (file ignored), S-skipped (cached or empty file), .-no changes, F-fixed, E-error
   1) app/Config/Logger.php (ordered_imports)
   2) app/Views/errors/html/error_exception.php (braces, unary_operator_spaces, not_operator_with_successor_space)

Checked all files in 1.104 seconds, 16.000 MB memory used
```

開始先找一下怎樣略過那兩個檔案的用法:

```
% cat .php-cs-fixer.dist.php
<?php
use CodeIgniter\CodingStandard\CodeIgniter4;
use Nexus\CsConfig\Factory;
use PhpCsFixer\Finder;

// https://github.com/codeigniter4/CodeIgniter4/blob/develop/.php-cs-fixer.dist.php
$finder = Finder::create()
->files()
->notPath([
'app/Config/Logger.php',
'app/Views/errors/html/error_exception.php',
]);

// https://github.com/NexusPHP/cs-config/blob/develop/src/Factory.php
//return Factory::create(new CodeIgniter4())->forProjects();
return Factory::create(new CodeIgniter4(), [], [
'finder' => $finder,
])->forProjects();

% ./vendor/bin/php-cs-fixer fix --dry-run .
Loaded config CodeIgniter4 Coding Standards from "/path/./.php-cs-fixer.dist.php".
Using cache file ".php-cs-fixer.cache".

Checked all files in 0.032 seconds, 14.000 MB memory used
```

接著去 Windows 跑:

```
C:\path>.\vendor\bin\php-cs-fixer fix --dry-run .
PHP CS Fixer 3.9.1 Grand Awaiting by Fabien Potencier and Dariusz Ruminski.
PHP runtime: 7.4.30
Loaded config CodeIgniter4 Coding Standards from "C:\path\.\.php-cs-fixer.dist.php".
Using cache file ".php-cs-fixer.cache".
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF          56 / 56 (100%)
Legend: ?-unknown, I-invalid file syntax (file ignored), S-skipped (cached or empty file), .-no changes, F-fixed, E-error

   1) app\Common.php (line_ending)
  ...
  55) tests\_support\Libraries\ConfigReader.php (line_ending)
  56) tests\_support\Models\ExampleModel.php (line_ending)

Checked all files in 1.187 seconds, 16.000 MB memory used
```

最後再加個 line_ending 來處理一下:

```
% cat .php-cs-fixer.dist.php
<?php
use CodeIgniter\CodingStandard\CodeIgniter4;
use Nexus\CsConfig\Factory;
use PhpCsFixer\Finder;

// https://github.com/codeigniter4/CodeIgniter4/blob/develop/.php-cs-fixer.dist.php
$finder = Finder::create()
->files()
->notPath([
'app/Config/Logger.php',
'app/Views/errors/html/error_exception.php',
]);

// https://github.com/NexusPHP/cs-config/blob/develop/src/Factory.php
//return Factory::create(new CodeIgniter4())->forProjects();
return Factory::create(new CodeIgniter4(), [], [
'finder' => $finder,
// https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues/3955
'lineEnding' => PHP_EOL,
])->forProjects();
```

收工!

2022年7月30日 星期六

Go 開發筆記 - 透過 Electron 實現 PC app GUI 之 go-astilectron / go-astilectron-bundler / go-astilectron-bootstrap


前陣子練習 Golang 後,除了建制後端 API 及網頁服務外,在想該怎樣做個 Windows / macOS 的 PC App 時,逛了一下熱門的 GUI 套件,有常見的 QT, GTK3/GTK4 等等,最後想起 Electron 這套,找了一下果真也有人串好 Go 與 Electron 整合方式。就來試試看 go-astilectron 這套吧!

處理的項目:
專案初始化:

% cat main.go 
package main

import (
    "github.com/asticode/go-astilectron"
)

func main() {

}

% go mod init github.com/changyy/study-go-electron
% go mod tidy

接著切一個目錄 web 做網頁開發管理:

% tree -L 1 web
web
├── README.md
├── dist
├── env_nvm.sh
├── env_vim.sh
├── node_modules
├── package-lock.json
├── package.json
├── src
├── webpack.common.js
├── webpack.development.js
└── webpack.production.js

3 directories, 8 files

其中 package.json 內使用 webpack 安置開發模式和發版封裝、使用 eslint 做 coding style 規範:

% cat web/package.json | jq '.scripts'
{
  "eslint": "eslint --ext .js src/",
  "eslint-fix": "eslint --fix --ext .js src/",
  "watch": "webpack --config webpack.development.js --watch",
  "start": "webpack serve --config webpack.development.js --open",
  "build": "webpack --config webpack.production.js"
}

而 web 程式碼入口點:

% tree web/src     
web/src
├── index.html
└── js
    ├── helper-old-style.js
    ├── helper.js
    └── index.js

1 directory, 4 files

試試看在 web/src/index.html 內,在 body.onload 內呼叫 hehe(),而 web/src/js/index.js 是一個統整區,而 webpack 在 production mode 會精簡程式碼,因此要匯出的功能都必須做點處理,像是故意埋在 windows.xx 等等,或是要調整 webpack 包裝的機制,如 output 區要多描述 libraryTarget 和 library 資訊,會自動綁定在 window.xx 環境。

回過頭來,關於 go astilectron 的使用,在不需要封裝成各平台的環境時,可以很簡單地用以下程式碼,並在根目錄用 `% go run .` 就可以運行了:

    a, _ := astilectron.New(log.New(os.Stderr, "", 0), astilectron.Options{
        AppName: "MyGoAstilectronProject",
    })
    defer a.Close()

    // Start astilectron
    a.Start()

    w, _ := a.NewWindow("./resources/app/index.html", &astilectron.WindowOptions{
        Center: astikit.BoolPtr(true),
        Height: astikit.IntPtr(600),
        Width:  astikit.IntPtr(600),
    })
    w.Create()

    // Blocking pattern
    a.Wait()

如此,在開發網頁版型時,單純在 web 目錄做事,如 npm run start 就可以喚起瀏覽器來瀏覽,等做完事後,在靠 npm run build 封裝到 web/dist 目錄以及複製一份到 resource/app 目錄中,後續就著靠 ~/go/bin/astilectron-bundler 封裝成 PC App,在封裝成 PC app 時,會面臨到 resources 目錄內的文件也得跟著封裝進去,也就是上述程式碼指定的 "./resources/app/index.html" 網頁資源,或者網頁位址要改成絕對路徑也是一招,但等同也要求使用此 PC app 者,下需要自行配置好網頁資料。

這時,就要使用 go-astilectron-bundler 跟 go-astilectron-bootstrap ,前者是編譯出 PC app ,後者是封裝好資源,使得執行 PC app 時,可以順道把對應的資源("./resources/app/")擺定位,這時程式碼就會變得稍微複雜一點,並且執行執行 `go run .` 不見得能常運行,將使得開發週期拉的很長(每次編譯要花幾分鐘的時間)

% ~/go/bin/astilectron-bundler cc
% time ~/go/bin/astilectron-bundler
...
~/go/bin/astilectron-bundler  63.79s user 10.56s system 108% cpu 1:08.21 total

最後,就把他配置成可以傳一個 -dev 參數,使用該參數時,不經過 ~/go/bin/astilectron-bundler 運行(運行的速度也只是小減 20% 時間而已 XD)

% go run . -dev

目前覺得用 Go 與 Electron framework 一起使用的好處還不夠強烈,以及整合開發的優勢頂多是用 Go 寫工具(api)等等,以及使 Electron 時,本來就是重度倚賴 HTML/JS/CSS 在 UI 呈現,因此直接 node.js 的整合優勢仍是最佳的。

更多資訊就在這邊:github.com/changyy/study-go-electron ,或是直接觀看 github.com/asticode/go-astilectron 內有提到 demo 範例,可以直接看 demo 內的實作。

2022年7月26日 星期二

[NAS] Private IP / Custom Domain / Wildcard SSL / HTTPS 憑證定期更新與設定 @ DS216play / DSM 7.1



之前在 2018 年已經寫過一篇:[NAS] Private IP Server 與 Let's Encrypt Wildcard SSL 憑證 / HTTPS 服務,那時是採用 Let's Encrypt Wildcard SSL 憑證,主因是只有 Private IP 的 NAS Server ,無法讓憑證服務連進來驗證 IP 與 Domain 的關係,因此主要都是靠 DNS Record (TXT Record) 的驗證方式。

然而,忘了是何時開始,原本設定好的 acme.sh 跟連續動作 script 失效好一陣子,後來才發現在 2021.08.01 起,預設已經改成 ZeroSSL 方案,所以要處理創帳號的部分:

$ ~/.acme.sh/acme.sh --upgrade
$ ~/.acme.sh/acme.sh --register-account -m YOUR_EMAIL
[Sun May 22 13:26:39 CST 2022] No EAB credentials found for ZeroSSL, let's get one
[Sun May 22 13:26:41 CST 2022] Registering account: https://acme.zerossl.com/v2/DV90
[Sun May 22 13:26:55 CST 2022] Registered
[Sun May 22 13:26:55 CST 2022] ACCOUNT_THUMBPRINT='XXXXXXXXXXXXXXXXXXXXXXX'

運行:

$ CF_Key=YOUR_KEY CF_Email=YOUR_EMAIL ~/.acme.sh/acme.sh --issue --dns dns_cf -d *.YOUR_DOMAIN

如此,在 ~/.acme.sh\*.YOUR_DOMAIN/ 中,會有:
  • ~/.acme.sh\*.YOUR_DOMAIN/\*.YOUR_DOMAIN.cer
  • ~/.acme.sh\*.YOUR_DOMAIN/\*.YOUR_DOMAIN.key
  • ~/.acme.sh\*.YOUR_DOMAIN/ca.cer
  • ~/.acme.sh\*.YOUR_DOMAIN/fullchain.cer
接著把這些 copy 出來,直接到 NAS DSM7.1 網頁介面匯入

控制台 -> 安全性 -> 憑證 -> 新增 -> 匯入憑證 ->

私鑰: Your cert key - \*.YOUR_DOMAIN.key
憑證:  Your cert - \*.YOUR_DOMAIN.cer
中繼憑證: ca.cer

控制台 -> 安全性 -> 憑證 -> 設定 -> 系統預設 -> 更換成剛剛上傳的憑證(如 *.YOUR_DOMAIN)

 

如此,再去系統 /usr/syno/etc/www/certificate/system_default/cert.conf 查一下資訊,可以看到寫死的憑證位置,未來就可以靠手段去覆蓋掉。

取代掉 ssl_certificate 欄位,如 /usr/syno/etc/www/certificate/system_default/XXX-SC-XXX.pem;

$ cat ~/.acme.sh/\*.YOUR_DOMAIN/\*.YOUR_DOMAIN.cer ~/.acme.sh/\*.YOUR_DOMAIN/ca.cer > ~/.acme.sh/\*.YOUR_DOMAIN/\*.YOUR_DOMAIN.fullchain.pem
$ sudo cp ~/.acme.sh/\*.YOUR_DOMAIN/\*.YOUR_DOMAIN.fullchain.pem /usr/syno/etc/www/certificate/system_default/XXX-SC-XXX.pem;

取代掉 ssl_certificate_key,如 /usr/syno/etc/www/certificate/system_default/XXX-SCK-XXX.pem;

$ openssl pkcs8 -topk8 -inform pem -in ~/.acme.sh/\*.YOUR_DOMAIN/\*.YOUR_DOMAIN.key -outform pem -nocrypt -out ~/.acme.sh/\*.YOUR_DOMAIN/\*.YOUR_DOMAIN.key.pem
$ sudo cp ~/.acme.sh/\*.YOUR_DOMAIN/\*.YOUR_DOMAIN.key.pem /usr/syno/etc/www/certificate/system_default/XXX-SCK-XXX.pem;

測試與重啟 Nginx

$ sudo nginx -t && sudo synopkg restart --service nginx

上述的任務都可以擠在一個 script 中,再靠 NAS Web UI 設定定期跑,而 NAS 只需在 Web UI 上設定好一次使用指定的憑證資訊後,後續就可以靠系統底層不斷覆蓋重啟 nginx 方式來處理,像是有需要的話,也可以靠 gawk 去動態取出 ssl_certificate_key 和 ssl_certificate 的位置

ssl_certificate_path=`sudo grep "ssl_certificate " /usr/syno/etc/www/certificate/system_default/cert.conf | gawk -F';' '{print $1}' | gawk -F' ' '{print $2}'`

ssl_certificate_key_path=`sudo grep "ssl_certificate_key " /usr/syno/etc/www/certificate/system_default/cert.conf | gawk -F';' '{print $1}' | gawk -F' ' '{print $2}'` 

echo "ssl_certificate_path: $ssl_certificate_path"

echo "ssl_certificate_key_path: $ssl_certificate_key_path"


ref: 

2022年7月20日 星期三

PHP 開發筆記 - 使用 Google Analytic (GA4) Measurement Protocol

使用 GA Measurement Protocol 大概也有超過五年了 XD 最近一直收到通報,說舊版 GA Measurement Protocol 即將在 2023.07.01 不在接收資料處理,簡稱看不到報表。

建立一個 GA4 專案後,發現要在使用 Measurement Protocol 的難度有稍微增加。包括測試也有點卡卡 XD 最近就記錄一下筆記:
首先,想要模擬個 page_view 時,才發現官方文件沒有多提,後來大概是組出這樣:

{
    "client_id":"00000000",
    "non_personalized_ads":true,
    "events":[
        {
            "name":"page_view",
            "params":{
                "page_title":"hello",
                "page_location":"blog.changyy.org"
            }
        }
    ]
}

而 PHP Code:

function _GA4_($api_secret, $measurement_id, $deviceId, $deviceIP, $events = array()) {
    $query_string = array(
        'measurement_id' => $measurement_id,
        'api_secret' => $api_secret,
    );  
    if (!empty($deviceIP)) {
        // https://support.google.com/analytics/answer/9143382
        // https://github.com/dataunlocker/save-analytics-from-content-blockers/issues/25
        $query_string['uip'] = $deviceIP;
        $query_string['_uip'] = $deviceIP;
    }   
    $url = 'https://www.google-analytics.com/mp/collect?'.http_build_query($query_string);
    //$url = 'https://www.google-analytics.com/debug/mp/collect?'.http_build_query($query_string);

    //  post without libcurl
    // https://ga-dev-tools.web.app/ga4/event-builder/
    $payload = array(
        'client_id' => $deviceId,
        'non_personalized_ads' => true,
        //'timestamp_micros' => "".floor(microtime(true) * 1000)."000",
        'events' => $events,
    );

    //if (isset($_SERVER) && isset($_SERVER['COUNTRY_CODE']) && !empty($_SERVER['COUNTRY_CODE']) ) {
    //        if (!isset($payload['user_properties']))
    //                $payload['user_properties'] = array();
    //        $payload['user_properties']['country_code'] = array(
    //                'value' => $_SERVER['COUNTRY_CODE'],
    //        );
    //}

    $POST_DATA = json_encode($payload);
    $options = array(
        'http' => array(
            'header' => implode("\r\n", array(
                "Content-Type: application/json",
                //"Content-Length: ".strlen($POST_DATA),
            )), 
            'method' => 'POST',
            'content' => $POST_DATA,
        )   
    );  
    $context  = @stream_context_create($options);
    $result = @file_get_contents($url, false, $context);
    //echo "result:[$result]\n$POST_DATA\n".print_r($options)."\n";exit;
}

如此,就可以用以下完成事件回報了:

_GA4_($api_secret, $measurement_id, $deviceId, $deviceIP, array(
    array(
        'name' => 'page_view',
        'params' => array(
            'page_title' => 'Hello',
            'page_location' => 'https://blog.changyy.org/',
        ),
    )
);

而使用 https://www.google-analytics.com/debug/mp/collect 驗證時,有錯誤可以在 http response body 看到資料,例如:

{
  "validationMessages": [ {
    "description": "Unable to parse Measurement Protocol JSON payload. : invalid value Cannot bind a list to map for field 'user_properties'. for type Map",
    "validationCode": "VALUE_INVALID"
  } ]
}

2022年7月7日 星期四

Node.js 開發筆記 - 模擬 Web Browser 環境,執行前端 JS code / library

最近協助同事處理一個棘手的事,把一段需要算力的 JS Code 從本地端改成雲端運行,首要碰到的就是 node.js 預設環境並沒有很適合,如果有 JS library 十分依賴 Web Browser 環境時,那段 js code 透過 eval 運行時,會出現初始化錯誤。

原本土炮零碎地拼湊出 window, document, location, hostname 等把環境弄的堪用,後來還是先找一下,找到 browser-env 這套件,非常不錯!套上去後,所有土炮破壞架構美感的東西都可以去掉了。

如此,如果要將算力很高的任務拋去雲端計算時,且不幸依賴很多常見的 Web browser 套件邏輯時,就可以如此封裝成一隻 api ,供人把算力高的任務拋去高速運算的機器處理即可。

註:此例用 eval 是個危險的用法,只適合運行信任的程式碼,亂給使用者填寫會有高資安風險的。


範例:
  • 故意在 node.js 植入前端 jQuery library - https://code.jquery.com/jquery-3.6.0.slim.min.js (實務上真的有需求時,是要找 node.js 套件)
  • 在 node.js 透過 eval 啟用整段程式碼時,缺少 web browser 環境時,會噴錯誤訊息:TypeError: Cannot read properties of undefined (reading 'createElement')

2022年6月24日 星期五

Go開發筆記 - 使用 net/http 處理 URL Redirect 和 Cookie 之 Service Unit Test 需求

最近用 Golang 撰寫 Service Unit Test 時,練了一下 net/http 要如何處理 HTTP Response Header Location 的處理。主要是在使用 net/http 的 http.Client 時,可以加設定 CheckRedirect 的機制來阻擋 URL Redirect 的行為,藉以檢驗 URL Redirect 是否正確。

//httpClient := http.DefaultClient
httpClient := &http.Client{}

httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}

if jar, err := cookiejar.New(nil); err == nil {
httpClient.Jar = jar
}

如此,做 Service Unit Test 時,在發送 HTTP GET request or HTTP HEAD request 時,可以停下來,看看要跳轉到哪裡,以此判斷跳轉機制是否正常。

接著,碰到了另一個問題: 在 follow URL Redirect 後,似乎跳轉過程中產生的 Cookie 都沒有被記錄下來?例如最後的 response.Cookies() 輸出,的確只列出最後的 Response Cookies 資料。這時想要驗證中間的 Cookie 資訊,則需要瀏覽 httpClient.Jar 資料:

for _, c := range(httpClient.Jar.Cookies(res.Request.URL)) {
fmt.Println(c)
}

如此就可以搞定 URL Redirect 驗證,以及全部過程的產生的 Cookie 檢查!當然,若要更細膩地去追蹤每次 URL Redirect 時,產生的 Response Cookies 資料,可以透過 httpClient.CheckRedirect 那處理:

httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
for _, c := range (req.Response.Cookies()) {
fmt.Println(c)
}
}

參考資料:
  • https://pkg.go.dev/net/http#Client
  • https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/net/http/client.go
  • https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/net/http/client.go;l=493
  • https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/net/http/client.go;l=811
  • https://pkg.go.dev/net/http/cookiejar#Jar.Cookies
  • https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/net/http/cookiejar/jar.go

2022年6月21日 星期二

VIM 設定檔(vimrc)切換方式 @ macOS 12.4

由於 coding style 的規劃,需要調整 vim editor 的行為:

set tabstop=4
set shiftwidth=4
set expandtab

因此,設計一個 vim_env.sh 來做切換啟用:

% vim --version
...
   system vimrc file: "$VIM/vimrc"
     user vimrc file: "$HOME/.vimrc"
 2nd user vimrc file: "~/.vim/vimrc"
      user exrc file: "$HOME/.exrc"
       defaults file: "$VIMRUNTIME/defaults.vim"
  fall-back for $VIM: "/usr/share/vim"

% cat project_vimrc 
try
    source $VIM/vimrc 
catch
    echo "\$VIM/vimrc not found"
endtry

set tabstop=4
set shiftwidth=4
set expandtab

% cat vim_env.sh
#!/bin/bash

SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]:-$0}"; )" &> /dev/null && pwd 2> /dev/null; )";
alias vim='vim -u $SCRIPT_DIR/project_vimrc'
echo "Update vim environment (using 'type vim' to show raw command) "

% type vim
vim is an alias for vim -u $SCRIPT_DIR/project_vimrc

接著,就靠 alias 來啟用專案 vimrc

2022年6月19日 星期日

隔離險理賠 - 富邦疫苗隔離險理賠郵寄申請 時程約30天才開始審核


四月就被隔離了,恰好 110 年打疫苗前,保了個疫苗險,原本只是單純保個疫苗不良事件,殊不知翻出來看有隔離補償。找了個時間打電話問問看申辦流程,客服要求留完資料後,會有專人聯繫,可惜過了兩個月也沒接到過電話,現在疫情放緩,來把資料郵寄過去試試。

首先,先到 富邦產險 - 防疫險專區 網站,查一下理賠申請方式,內容都是建議,直接攜帶好相關證件直接到現場辦理。先來試試看郵件這招。

致電富邦產險客服(免付費電話,手機可打 0800-009-888 ),確認一下郵寄收件的切確地址跟收件人,於是得知台北地區就直接掛號寄到總公司即可。

地址:台北市中山區遼寧街179號7樓
電話:(02)66367890
富邦壽險 個人理賠部 收

至於文件方面,就下載一份理賠申請單填寫一下,再將身分證影本, 銀行帳戶影本和隔離簡訊列上,有順便把當初衛福部的隔離單印一下(六月下旬,點擊原本的簡訊,已經看不到帶有個人資訊的網頁內容),再看看是不是這樣就搞定。

後續大概寄出 20天後,突然想到查了一下,果真有一些進度,不錯,至少可以信件溝通了:



約30天後,收到簡訊通知,顯示開始審核:


最後,送件滿 3個月後,收到匯款通知。

參考資料:

2022年6月16日 星期四

C開發筆記 - 使用 JS Engine : Duktape 接收與傳遞 Javascript Object

對嵌入式設備來說,如果資源夠大的話,直接用 Node.js 絕對是個不錯方案,受限於硬體資源限制,採用了 Duktape JS Engine。

寫 C Code 操作 Duktape JS Engine 時,呼叫的方式還滿像以前學生時代寫組語的感覺,要先一步步把參數先配置好,接著在呼叫函示去運算。這次就筆記一下,如何從 JS Code 傳遞 Object 到 C Code,以及 C Code 要怎樣傳遞 Object 回 JS Code。

從 JS Code 傳遞 Object 到 C Code:

//
// _js_helper_fetch_url_more 是從 C Code 植入 function,並且接送一個 JS Object,有 url 跟 request_header 兩個屬性
//
if (typeof _js_helper_fetch_url_more !== "undefined") {
    var ret = _js_helper_fetch_url_more({
        url: 'https://ipinfo.io/country',
        request_header: [
            'Content-Type: HelloWorld',
            'X-MAN: HelloWorld',
        ],  
    }); 
    console.log(JSON.stringify(ret));
}

在 C Code 要取得傳進來的 JS Object,並存取 url 跟 request_header 兩個屬性:

if (duk_is_object(ctx, -1) && duk_has_prop_string(ctx, -1, "url")) {
    duk_get_prop_string(ctx, -1, "url");
    const char *url = duk_to_string(ctx, -1);
    duk_pop(ctx);
}

if (duk_has_prop_string(ctx, -1, "request_header")) {
    duk_get_prop_string(ctx, -1, "request_header");
    if (duk_is_object(ctx, -1) && duk_has_prop_string(ctx, -1, "length")) {
        duk_get_prop_string(ctx, -1, "length");
        int header_length = duk_get_int(ctx, -1);
        printf("#JS_Helper_Fetch_Url_More> access input_object.request_header.length: %d\n", header_length);
        duk_pop(ctx);

        for (int i=0 ; i<header_length ; ++i) {
            duk_get_prop_index(ctx, -1, i);
            const char *header_value = duk_get_string(ctx, -1);
            printf("#JS_Helper_Fetch_Url_More> access input_object.request_header[%d] = [%s]\n", i, header_value);
            duk_pop(ctx);
        }
    }
    duk_pop(ctx);
}

最後,想要從 C Code 傳遞一個 JS Object 回去的方式:

//
// 傳回 {"status": true, "errorCode": 0}
//

duk_idx_t obj_idx;
obj_idx = duk_push_object(ctx);

int errorCode = 0;
duk_push_int(ctx, errorCode);
duk_put_prop_string(ctx, obj_idx, "error_code");

if (errorCode == 0) {
    duk_push_true(ctx);
} else {
    duk_push_false(ctx);
}
duk_put_prop_string(ctx, obj_idx, "status");

更多完整資訊,請參考:github.com/changyy/study-duktape