Hero Image
[授權] OAuth2.0

OAuth 是一個開發標準(Open Standard),用來處理有關「授權」(Authorization)相關的問題 允許授權當下的APP取得使用者在平台的相關資訊 OAuth2 有很多變化 腳色: Resource Owner,也就是使用者。 Client,要向使用者取得權限的應用程式,有自己的 Client ID、 Client Secret。 Authorization Server,負責驗證使用者身分、發 Access Token 給應用程式 Resource Server,存放資源的伺服器,認 Token 給使用者存取資源 用詞 Authorization Grant 同意應用程式取得資源 Redirect URI 驗證伺服器驗證、授權完畢後,返回應用程式的路徑 Scope 授權範圍 OAuth2.0 四種授權類型流程(Grant Types): Authorization Code 最常見,步驟: 應用程式(Client) 將使用者導向 Authorization Server,提供 Redirect URL, scope, 應用程式的 client id… Authorization Server 驗證使用者身分,通過之後發給 Authorization Grant,將網址列帶上 Authorization Grant 後將使用者導回 Redirect URI 回到應用程式(Client)。 應用程式(Client)拿 Authorization Grant 和 Authorization Server 換取 Access Token,Authorization Server 會透過應用程式(Client)專屬的 Client ID、 Client Secret 驗證應用程式身分。 應用程式(Client)帶著 Access Token 向 Resource Server 存取資源 Implicit 適合在 Client-side 運行的應用程式適合使用,例如 SPA(Single Page Application) 跳過交換 Access Token 的過程,由 Authorization Server 直接給予 Access Token 比較不安全

Hero Image
[HA] 使用 Docker Compose 安裝 Home Assistant

這裡是假設手邊已經有一台安裝 docker、docker-compose 的 Linux 系統。 採用的映象檔是 ghcr.io/home-assistant/home-assistant:stable ,因為我需要使用網址來區分服務(同一個 443 port 的情況下),所以採取反向代理的方式,一方面讓之後要部屬其他應用、加上憑證、等等操作都交給 nginx 比較方便,因此不會將 8123 port 直接對外。 建立 mynetwork docker create network mynetwork 建立 ha ha/docker-compose.yml version: '3' services: ha: container_name: homeassistant #image: "homeassistant/home-assistant:stable" image: ${HA_IMAGE} volumes: - ./volume/ha/config:/config - /etc/localtime:/etc/localtime:ro - /run/dbus:/run/dbus:ro restart: unless-stopped privileged: true networks: - mynetwork networks: mynetwork: external: true ha/.env HA_IMAGE="homeassistant/home-assistant:stable" ha/.gitignore volume/ 建立 nginx nginx/docker-compose.yml version: '3' services: web: image: nginx volumes: # - ./templates:/etc/nginx/templates - /usr/share/nginx/html:/usr/share/nginx/html - ./nginx.conf:/etc/nginx/nginx.conf - /etc/letsencrypt:/etc/letsencrypt networks: - mynetwork ports: - "80:80" - "443:443" environment: - NGINX_ENVSUBST_TEMPLATE_SUFFIX=.conf - NGINX_PORT=80 networks: mynetwork: external: true nginx/nginx.conf events { } http { upstream ha { server ha:8123; } error_log /etc/nginx/error_log.log warn; client_max_body_size 20m; # proxy_cache_path /etc/nginx/cache keys_zone=one:500m max_size=1000m; proxy_cache off; server { server_name localhost; location / { root /usr/share/nginx/html; index index.html index.htm; try_files $uri $uri/ /index.html; } } server { listen 80; #listen 443 ssl; #server_name home.example.com; # SSL certificate and key configuration #ssl_certificate /etc/letsencrypt/live/home.example.com/fullchain.pem; #ssl_certificate_key /etc/letsencrypt/live/home.example.com/privkey.pem; # Additional SSL configurations (e.g., enable secure ciphers, etc.) #ssl_protocols TLSv1.2 TLSv1.3; #ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384'; location /.well-known/acme-challenge { root /usr/share/nginx/html; } location / { proxy_pass http://ha; proxy_set_header Host $host; proxy_http_version 1.1; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; } } } 啟動服務 docker-compose -f ha/docker-compose.yml up -d docker-compose -f nginx/docker-compose.yml up -d 創建帳號 例如我的 IP 是 192.168.56.100 現在可以進入 http://192.168.56.100/ 開始創建帳號。

Hero Image
[DIY] 設計一個可回傳 Promise 的 Dialog 元件方法

有用過 sweetalert2 的話,應該會喜歡可以同步等待對話框回傳值的方式, 這裡做一個 Vue2 元件,呼叫該元件的方法會彈出對話框等待使用者輸入,並且回傳 Promise, 如此一來就能夠在同一個函式當中處理使用者輸入值。 Dialog 元件設計原理: 元件方法 GetConfirm() 顯示 Dialog 元件並回傳一個 Promise,。 設置watcher讓元件取得使用者輸入後 resolve promise 得利於上述元件的設計,實際上的效益是將複雜度封裝到子元件裡面(watcher移動到元件內), 如此不需在上層元件撰寫使用者輸入取值的監視邏輯, 讓我們得以在上層元件直接 await GetConfirm 同步取得值進行操作。 這個概念的用途非常廣,例如 Vue router 的 component route guard,在離開表單頁面前跳出使用者確認的 Dialog。 Vue3 實作 <template> <v-dialog v-model="dialog" v-bind="$attrs"> <slot v-bind="{ Resolve }"></slot> </v-dialog> </template> <script setup> import { ref } from "vue"; const dialog = ref(false); let resolve = null; const Resolve = (v) => { resolve(v); dialog.value = false; }; const GetResult = async () => { dialog.value = true; return new Promise((res) => (resolve = res)); }; defineExpose({ GetResult, Resolve }); </script> [舊]Vuejs 實作 <button id="xBtn">執行測試</button> <div id="xApp" class="modal" :style="{display: dialog?'block':'none'}"> <div class="modal-content"> <span class="close">Test Modal</span> <p>The value selected will resolve by promise.</p> <button @click="choose(1)">1</button> <button @click="choose(2)">2</button> </div> </div> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script> <script> let data = { result: null, dialog: false } let dialog = new Vue({ el: '#xApp', data:() => data, methods: { getConfirm() { // 先清空 result (避免兩次選中一樣的值無法觸發 watcher) this.result = null // open dialog this.dialog = true return new Promise((resolve, reject) => { try { const watcher = this.$watch( // 設置監視的對象為 result () => this.result , // 一旦 result 的值有改變,就 resolve promise,並啟動下一輪 watcher (newVal) => resolve(newVal) && watcher() ) } catch (error) { // 如果出錯就 reject promise reject(error) } }) }, choose(value) { // 為 result 設置值觸發 watcher 解開 promise this.result = value // 關閉 dialog this.dialog = false } } }) document.getElementById('xBtn') .addEventListener( 'click', async e => alert( await dialog.getConfirm() ) ); </script> /* The Modal (background) */ .modal { display: none; /* Hidden by default */ position: fixed; /* Stay in place */ z-index: 1; /* Sit on top */ padding-top: 100px; /* Location of the box */ left: 0; top: 0; width: 100%; /* Full width */ height: 100%; /* Full height */ overflow: auto; /* Enable scroll if needed */ background-color: rgb(0,0,0); /* Fallback color */ background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ } /* Modal Content */ .modal-content { background-color: #fefefe; margin: auto; padding: 20px; border: 1px solid #888; width: 80%; } [舊]Vue-next 實作 這裡使用 vue-next/setup/quasar/typescript

Hero Image
[DIY] 利用 orphan branch 和 worktree 在同一 Git 儲存庫控管原始碼與靜態資源分支

目前使用 docker 疊前端的編譯環境比較複雜,不比本機端方便,本篇的把 SSG 發布流程移植到前端專案。 一鍵產生前端靜態資源到特定分支,可設定該分支進入 CI/CD 流程。 在同一 Git Repo 管理部屬的靜態資源與原始碼。 P.S.這裡是以 quasar CLI 為例,專案放在 gitlab,腳本可在 git bash 環境執行,build 指令是 quasar build,輸出的 路徑是 dist/spa,不同專案架構需要作相對應調整。 Git 設定 建立一個 spa orphan branch P.S. 不需在 gitlab 上事先新增相對應的 branch git checkout --orphan spa git reset --hard git commit --allow-empty -m "Initializing gh-pages branch" git push origin spa git checkout master 新增部屬腳本 deploy.sh 新增到專案跟目錄 #!/bin/bash # 如果要檢查是否有 commit才進行部屬,就取消註解 # if [ "`git status -s`" ] # then # echo "The working directory is dirty. Please commit any pending changes." # exit 1; # fi echo "Deleting old publication" rm -rf dist mkdir dist # 清空 worktree git worktree prune rm -rf .git/worktrees/dist/ # 新增 worktree echo "Checking out spa branch into /dist" git worktree add -B spa dist origin/spa echo "Generating site" quasar build && cp -r deploy/. dist/ echo "Updating spa branch" cd dist && git add --all && git commit -m "Publishing to spa " #echo "Pushing to github" git push --all 修改 package.json 新增一項 script:deploy "deploy": "bash deploy.sh", 把 branch 設置為 Protected 先執行一次 npm run deploy,讓 git 上產生 spa 這個 orphan branch 再到這裡把 spa 設定為 Protected Branche:Settings->Repository->Protected Branches

Hero Image
[.NET] 開發階段管理應用程式的敏感資料

基於資訊安全的理由,密碼等敏感性資訊不應該出現在程式碼裡面, 應該把敏感性資料儲存在專案以外的地方,防止對 Git Server 提交專案程式碼的時候把密碼推送到伺服器上, 因此程式開發、部屬階段都應該用適當的策略存放敏感性資料讓程式讀取使用, .NET 儲存敏感性資料大致上來說可以用這兩種方式: 環境變數 Secret Manager 這裡紀錄要如何在 .NET 開發環境以 Secret 儲存敏感性資料,以及程式讀取的方式。 Secret Manager Secret Manager 就是在本地端特定路徑存放 secret.json 檔案: %APPDATA%\Microsoft\UserSecrets\<user_secrets_id>\secrets.json ~/.microsoft/usersecrets/<user_secrets_id>/secrets.json 需要先針對個別專案啟用專案的 Secret Storage 支援,切換到專案目錄執行: dotnet user-secrets init 在專案檔裡的 UserSecretsId 區段會得到一段 GUID,這個要作為 user_secrets_id 資料夾名稱。 <PropertyGroup> <TargetFramework>netcoreapp3.1</TargetFramework> <UserSecretsId>79a3edd0-2092-40a2-a04d-dcb46d5ca9ed</UserSecretsId> </PropertyGroup> 以指令設置一組 secret,例如連線字串: dotnet user-secrets set "ConnectionStrings:POSTGRES" "User ID=root;Password=myPassword;Host=localhost;Port=5432;Database=myDataBase;Pooling=true;Min Pool Size=0;Max Pool Size=100;Connection Lifetime=0;" --project "D:\workspace\MySolution\MyProject" 以檔案直接設置 secret windows type .\input.json | dotnet user-secrets set Linux/MacOS

Hero Image
[K8S] 自架 Kubernetes 使用 VM 模擬多台 Server

準備/安裝兩台 Server ( 使用 VM clone 然後修改 hostname ) 一台為 Master (主控),另外一台為 Node ( 節點 ) 安裝 kubelet/kubeadm ( Master 與 node 皆要執行此步驟 ) # 設定 k8s server上網路 cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 EOF sudo sysctl --system # 安裝 kubeadm / kubelet sudo apt-get update && sudo apt-get install -y apt-transport-https curl curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list deb https://apt.kubernetes.io/ kubernetes-xenial main EOF sudo apt-get update sudo apt-get install -y kubelet kubeadm kubectl sudo apt-mark hold kubelet kubeadm kubectl # 安裝 Docker sudo apt-get install apt-transport-https ca-certificates curl software-properties-common curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" sudo apt update sudo apt-get install docker-ce -y docker --version sudo systemctl start docker sudo chmod 666 /var/run/docker.sock sudo systemctl enable docker cat <<EOF | sudo tee /etc/docker/daemon.json { "exec-opts": ["native.cgroupdriver=systemd"] } EOF sudo systemctl restart docker # 關閉 swap sudo swapoff -a sudo sed -i '/\/swap/s/^/#/' /etc/fstab # 設定服務自動重啟 systemctl enable kubelet # Master Node 啟動 sudo kubeadm init --pod-network-cidr=192.168.0.0/16 kubectl taint nodes --all node-role.kubernetes.io/master- # kubeconfig mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config # Deploying a pod network kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml