Author: Gerard Chen

  • Collabora 佈署 & Nextcloud 整合

    You have not configured the allow-list for WOPI requests. Without this setting users may download restricted files via WOPI requests to the Nextcloud server. Click here for more info

  • 如何避免ai GPT胡說八道

    告別「正確的廢話」:掌握 Gemini 3 的三大進階核心技巧

    許多人使用 Gemini 3 的方式仍停留在兩年前的搜尋框模式,僅輸入零散指令,導致 AI 只能回覆泛泛而談的內容。為了真正發揮這款最新一代推理模型的潛力,我們需要從系統指令、交互禁忌與幻覺規避三個維度進行深度優化。

    一、 編寫系統指令:打造「量身定制」的專屬 AI

    **系統指令(System Instruction)**或 Gemini 官網的 Gems 功能,能定義大模型的基礎行為準則,並在全局對話中生效。

    用戶畫像與偏好: 讓 AI 了解您的硬體環境(如 Windows 11 或特定顯卡)、身份背景(如無法註冊公司、所在地點)甚至是健康狀況。例如,若設定了高血脂背景,AI 在推薦美食時會自動避開不合適的選項。

    行為與人設: 您可以要求 AI 禁止諂媚,必須一針見血地指出錯誤,並在遇到不確定的資訊時直接承認「查不到」,而非胡編亂造。

    輸出規格: 指定特定的輸出格式(如 Markdown)或語言習慣(如術語需中英雙語並行),確保回覆內容能直接用於筆記軟體或商務文案。

    二、 交互禁忌:避免降低模型效率的錯誤行為

    根據 Google 官方文檔,Gemini 3 作為原生推理模型,有些傳統的提示詞技巧反而會適得其反:

    不要調整溫度(Temperature)與 Top P: Gemini 3 內建的思維鏈推理機制依賴高熵值進行路徑探索,調低溫度會破壞推理鏈

    不再需要「請一步步思考」: 這類指令會讓原生推理模型感到困惑,正確做法是給予具體的約束條件,引導其檢查特定邏輯。

    拒絕情緒勒索與贅字: 現代模型已能識別「越獄」技巧或低質量提示詞,過多廢話會稀釋關鍵詞權重。建議使用 XML 或 Markdown 格式的結構化指令,且不要混合使用不同格式以免解析混亂。

    三、 幻覺規避:確保答案的準確性

    AI 為了得分往往傾向於「猜測」而非承認無知,Gemini 3 Pro 的幻覺率甚至高於部分輕量模型,因為它更敢於推測複雜問題。

    引入 RAG(檢索增強生成): 利用最新更新的 NotebookLM 功能,將參考資料上傳,讓 AI 結合您提供的文檔與網絡搜尋來回答,能大幅降低幻覺。

    交叉驗證法: 讓一個 AI 生成內容後,交給另一個 AI 進行教驗。

    質疑前提: 在系統指令中要求 AI 優先審視用戶假設。若用戶給出的前提錯誤,AI 應主動反駁而非順著錯誤邏輯發展。

    Reference

  • 密碼產生器

    # pwgen

    pwgen -s 20

    產生20字符的密碼

  • Nextcloud + Onlyoffice deploy by using yml

    最近一段時間密集使用 Docker 佈署各種應用服務,一開始其實沒有特別留意系統資源的使用狀況。直到某一天,突然發現主機前方的系統運作指示燈幾乎沒有熄滅過,心裡只覺得怪怪的。打開系統監控工具一看,才發現事情大條了 —— RAM 幾乎被吃光。

    檢查後發現,原來「資源消耗怪獸」是:

    Nextcloud + OnlyOffice(Community Server + Document Server + Elasticsearch)+ MySQL

    光是完成佈署、系統閒置、甚至還沒做任何壓力測試,記憶體使用量就已經輕鬆突破 10GB

    「好吧,那就買 RAM 來改善把~~~」

    一查價格,我的老天鵝啊~~~~~ 今年 2 月才剛組了一台新電腦,同樣品牌、同樣型號。 32GB RAM,當時大約 NT$2,800。現在再查看一次價格 —— 接近3倍旳 NT$9,900。

    回想幾個星期前,曾在某個平台(一時想不起來是哪裡)聽到有人提到:

    由於 AI 需求動能強勁記憶體(RAM)價格正在快速上漲,且漲勢仍在持續。

    當時心裡還想:「能漲到什麼程度?」
    現在才發現自己真的是天真了。

    目前先將佈署流程與設定完整記錄下來,才不會以後忘記佈署的細節。接下來打算利用現有的 2 台電腦,練習如何將不同的 service 佈署在不同的 server 上協同運作,分散 RAM 與 I/O 的資源消耗。

    Bash Script × 2, dirCREATOR / initSQL
    Docker Compose(yml)× 1
    .env x1

    dirCREATOR
    負責根據 docker-compose.yml 中定義的 volume 結構,自動建立所有必要的資料夾與目錄階層。

    initSQL
    剛開始佈署,在這卡了很久。 Docker Compose 初始啟動時,SQL 初始化存在 bug,如果沒有先將SQL初始化搞定,會一直卡關。

    佈署流程

    dirCREATOR

    initSQL

    docker compose up -d


    dirCREATOR

    #!/usr/bin/env bash
    
    # Base data directory relative to the script location
    BASE_DIR="./data"
    
    echo "Creating directory structure for Nextcloud + OnlyOffice ( community + document + elacticsearch ) + MySQL"
    
    # Format: "path:owner_uid:group_gid:permissions"
    DIRS=(
        "$BASE_DIR/nextcloud_data:33:33:750"
        "$BASE_DIR/mysql/conf.d:0:0:755"
        "$BASE_DIR/mysql/docker-entrypoint-initdb.d:0:0:755"
        "$BASE_DIR/mysql_data:999:999:750"
        "$BASE_DIR/community_data:0:0:755"
        "$BASE_DIR/community_log:0:0:755"
        "$BASE_DIR/community_letsencrypt:0:0:755"
        "$BASE_DIR/document_data:0:0:755"
        "$BASE_DIR/document_log:0:0:755"
        "$BASE_DIR/document_forgotten:0:0:755"
        "$BASE_DIR/document_fonts:0:0:755"
        "$BASE_DIR/certs:0:0:755"
        "$BASE_DIR/es_data:1000:1000:775"
        "$BASE_DIR/mail_data:0:0:755"
        "$BASE_DIR/mail_certs:0:0:755"
        "$BASE_DIR/mail_log:0:0:755"
        "$BASE_DIR/controlpanel_data:0:0:755"
        "$BASE_DIR/controlpanel_log:0:0:755"
    )
    
    if [ "$EUID" -ne 0 ]; then
        echo "Please run as root (use sudo)"
        exit 1
    fi
    
    for entry in "${DIRS[@]}"; do
        IFS=':' read -r path uid gid perm <<< "$entry"
        echo "Processing: $path"
        mkdir -p "$path"
        chown "$uid:$gid" "$path"
        chmod "$perm" "$path"
    done
    
    echo "Directory setup complete."

    initSQL

    #!/usr/bin/env bash
    # --- ONLYOFFICE & NEXTCLOUD MySQL Configuration Creator ---
    set -e
    
    # --- 1. Define Paths and Credentials ---
    BASE_DIR="./data/mysql"
    CONF_DIR="${BASE_DIR}/conf.d"
    INIT_DB_DIR="${BASE_DIR}/docker-entrypoint-initdb.d"
    CONF_FILE="${CONF_DIR}/onlyoffice.cnf"
    INIT_SQL_FILE="${INIT_DB_DIR}/init.sql"
    
    # Credentials
    OO_USER="onlyoffice"
    OO_PASS="adjust to your pass"
    MAIL_USER="onlyoffice_mail"
    MAIL_PASS="adjust to your pass"
    
    # Nextcloud Credentials
    NC_DB="nextcloud"
    NC_USER="onlyoffice"
    NC_PASS="adjust to your pass"
    
    # --- 2. Ensure Directories Exist ---
    echo "Ensuring configuration directories exist..."
    mkdir -p "$CONF_DIR"
    mkdir -p "$INIT_DB_DIR"
    
    # --- 3. Write MySQL Configuration ---
    echo "Writing MySQL configuration to ${CONF_FILE}..."
    cat << 'EOF' > "$CONF_FILE"
    [mysqld]
    sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
    max_allowed_packet=256M
    innodb_buffer_pool_size=1G
    innodb_log_file_size=256M
    innodb_flush_log_at_trx_commit=2
    character-set-server=utf8mb4
    collation-server=utf8mb4_unicode_ci
    binlog_format=ROW
    EOF
    
    # --- 4. Write Database Initialization Script ---
    echo "Writing database user initialization script to ${INIT_SQL_FILE}..."
    cat << EOF > "$INIT_SQL_FILE"
    -- 1. Create Databases
    CREATE DATABASE IF NOT EXISTS onlyoffice CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    CREATE DATABASE IF NOT EXISTS onlyoffice_mailserver CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    CREATE DATABASE IF NOT EXISTS ${NC_DB} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    
    -- 2. Create Users
    CREATE USER IF NOT EXISTS '${OO_USER}'@'%' IDENTIFIED WITH mysql_native_password BY '${OO_PASS}';
    CREATE USER IF NOT EXISTS '${MAIL_USER}'@'%' IDENTIFIED WITH mysql_native_password BY '${MAIL_PASS}';
    
    -- 3. Grant Privileges
    GRANT ALL PRIVILEGES ON onlyoffice.* TO '${OO_USER}'@'%';
    GRANT ALL PRIVILEGES ON onlyoffice_mailserver.* TO '${MAIL_USER}'@'%';
    GRANT ALL PRIVILEGES ON ${NC_DB}.* TO '${OO_USER}'@'%';
    
    -- 4. Finalize
    FLUSH PRIVILEGES;
    -- Set to Taipei time (+08:00)
    SET GLOBAL time_zone = '+08:00';
    EOF
    
    echo "Initial scripts created successfully."

    .env

    # -----------------------------
    # SYSTEM
    # -----------------------------
    TZ=Asia/Taipei
    MYSQL_ROOT_PASSWORD="adjust to your pass"
    
    # -----------------------------
    # DATABASE
    # -----------------------------
    MYSQL_SERVER_HOST=onlyoffice-mysql-server
    MYSQL_SERVER_USER=onlyoffice
    MYSQL_SERVER_PASS="adjust to your pass"
    MYSQL_SERVER_DB_NAME=onlyoffice
    
    # -----------------------------
    # MAIL
    # -----------------------------
    MAIL_SERVER_HOSTNAME="adjust to your hostname"
    MAIL_DB_NAME=onlyoffice_mailserver
    MAIL_DB_USER=onlyoffice_mail
    MAIL_DB_PASS="adjust to your pass"
    
    # -----------------------------
    # SMTP RELAY
    # -----------------------------
    MAIL_RELAY_HOST="adjust to your hostname"
    MAIL_RELAY_PORT=587
    MAIL_RELAY_USER="adjust to your email"
    MAIL_RELAY_PASSWD="adjust to your pass"
    
    
    # -----------------------------
    # SECURITY (UNIQUE FOR GERARDCHEN INSTANCE)
    # -----------------------------
    JWT_SECRET="adjust to your secret"
    
    DOCUMENT_SERVER_JWT_HEADER=AuthorizationJwt
    ONLYOFFICE_CORE_MACHINEKEY="adjust to your key"
    
    # -----------------------------
    # NEXTCLOUD
    # -----------------------------
    NEXTCLOUD_DB_PASSWORD="adjust to your pass"
    
    # -----------------------------
    # DOMAINS
    # -----------------------------
    CLOUD_DOMAIN="adjust to your url"
    OFFICE_DOMAIN="adjust to your url"
    DOCUMENT_DOMAIN="adjust to your url"

    docker-compose.yml

    services:
    # --- REDIS ---
    redis:
    image: redis:alpine
    container_name: onlyoffice-redis
    restart: always
    networks:
    - onlyoffice

    # --- MYSQL SERVER ---
    mysql-server:
    container_name: onlyoffice-mysql-server
    image: mysql:8.0.29
    environment:
    - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
    - TZ=${TZ}
    networks:
    - onlyoffice
    restart: always
    volumes:
    - ./data/mysql/conf.d:/etc/mysql/conf.d
    - ./data/mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
    - ./data/mysql_data:/var/lib/mysql
    healthcheck:
    test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
    interval: 10s
    timeout: 5s
    retries: 10
    start_period: 30s

    # --- NEXTCLOUD ---
    nextcloud:
    image: nextcloud:latest
    container_name: nextcloud
    restart: always
    depends_on:
    mysql-server:
    condition: service_healthy
    redis:
    condition: service_started
    ports:
    - "port:80"
    environment:
    - MYSQL_HOST=onlyoffice-mysql-server
    - MYSQL_DATABASE=nextcloud
    - MYSQL_USER=${MYSQL_SERVER_USER}
    - MYSQL_PASSWORD=${MYSQL_SERVER_PASS}
    - NEXTCLOUD_TRUSTED_DOMAINS=${CLOUD_DOMAIN} domain.local 127.0.0.1
    - REDIS_HOST=redis
    - OVERWRITEPROTOCOL=https
    - OVERWRITEHOST=${CLOUD_DOMAIN}
    - TZ=${TZ}
    volumes:
    - ./data/nextcloud_data:/var/www/html
    - /mnt/path to your physical harddisk:/path to your physical harddisk
    - /mnt/path to your physical harddisk:/mnt/path to your physical harddisk
    - /mnt/path to your physical harddisk:/mnt/path to your physical harddisk
    - /mnt/path to your physical harddisk:/mnt/path to your physical harddisk:
    - onlyoffice

    # --- DOCUMENT SERVER ---
    onlyoffice-document-server:
    container_name: onlyoffice-document-server
    image: onlyoffice/documentserver:8.1
    restart: always
    environment:
    - JWT_ENABLED=true
    - JWT_SECRET=${JWT_SECRET}
    - JWT_HEADER=${DOCUMENT_SERVER_JWT_HEADER}
    - TZ=${TZ}
    - REDIS_SERVER_HOST=redis
    networks:
    - onlyoffice
    ports:
    - 'port:80'
    volumes:
    - ./data/document_data:/var/www/onlyoffice/Data
    - ./data/document_log:/var/log/office
    - ./data/document_fonts:/usr/share/fonts/truetype/custom
    - ./data/document_forgotten:/var/lib/onlyoffice/documentserver/App_Data/cache/files/forgotten

    # --- ONLYOFFICE COMMUNITY SERVER ---
    onlyoffice-community-server:
    container_name: onlyoffice-community-server
    image: onlyoffice/communityserver:12.6.0.1900
    depends_on:
    mysql-server:
    condition: service_healthy
    onlyoffice-document-server:
    condition: service_started
    redis:
    condition: service_started
    environment:
    - ONLYOFFICE_CORE_MACHINEKEY=${ONLYOFFICE_CORE_MACHINEKEY}
    - TZ=${TZ}
    - REDIS_SERVER_HOST=redis
    - DOCUMENT_SERVER_PORT_80_TCP_ADDR=onlyoffice-document-server
    - DOCUMENT_SERVER_PORT_80_TCP_PROTO=http
    - DOCUMENT_SERVER_URL_PUBLIC=https://${DOCUMENT_DOMAIN}/
    - DOCUMENT_SERVER_URL_INTERNAL=http://onlyoffice-document-server/
    - DOCUMENT_SERVER_JWT_ENABLED=true
    - DOCUMENT_SERVER_JWT_SECRET=${JWT_SECRET}
    - DOCUMENT_SERVER_JWT_HEADER=${DOCUMENT_SERVER_JWT_HEADER}
    - MYSQL_SERVER_HOST=onlyoffice-mysql-server
    - MYSQL_SERVER_DB_NAME=${MYSQL_SERVER_DB_NAME}
    - MYSQL_SERVER_USER=${MYSQL_SERVER_USER}
    - MYSQL_SERVER_PASS=${MYSQL_SERVER_PASS}
    networks:
    - onlyoffice
    ports:
    - 'port:80'
    privileged: true
    cgroup: host
    restart: always
    volumes:
    - ./data/community_data:/var/www/onlyoffice/Data
    - ./data/community_log:/var/log/onlyoffice
    - ./data/community_ds_data:/var/www/onlyoffice/DocumentServerData
    - /sys/fs/cgroup:/sys/fs/cgroup:rw
    - ./data/certs:/var/www/onlyoffice/Data/certs

    # --- ELASTICSEARCH ---
    onlyoffice-elasticsearch:
    image: onlyoffice/elasticsearch:7.16.3
    container_name: onlyoffice-elasticsearch
    restart: always
    environment:
    - discovery.type=single-node
    - bootstrap.memory_lock=true
    - "ES_JAVA_OPTS=-Xms1g -Xmx1g"
    networks:
    - onlyoffice
    ulimits:
    memlock:
    soft: -1
    hard: -1
    volumes:
    - ./data/es_data:/usr/share/elasticsearch/data

    # --- MAIL SERVER ---
    onlyoffice-mail-server:
    container_name: onlyoffice-mail-server
    image: onlyoffice/mailserver:1.6.75
    depends_on:
    mysql-server:
    condition: service_healthy
    hostname: ${MAIL_SERVER_HOSTNAME}
    environment:
    - MYSQL_SERVER=onlyoffice-mysql-server
    - MYSQL_SERVER_PORT=3306
    - MYSQL_ROOT_USER=${MAIL_DB_USER}
    - MYSQL_ROOT_PASSWD=${MAIL_DB_PASS}
    - MYSQL_SERVER_DB_NAME=${MAIL_DB_NAME}
    - TZ=${TZ}
    - MAIL_RELAY_HOST=${MAIL_RELAY_HOST}
    - MAIL_RELAY_PORT=${MAIL_RELAY_PORT}
    - MAIL_RELAY_USER=${MAIL_RELAY_USER}
    - MAIL_RELAY_PASSWD=${MAIL_RELAY_PASSWD}
    networks:
    - onlyoffice
    restart: always
    privileged: true
    volumes:
    - ./data/mail_data:/var/vmail
    - ./data/mail_certs:/etc/pki/tls/mailserver
    - ./data/mail_log:/var/log

    # --- CONTROL PANEL ---
    onlyoffice-control-panel:
    container_name: onlyoffice-control-panel
    image: onlyoffice/controlpanel:3.5.2.530
    depends_on:
    - onlyoffice-document-server
    - onlyoffice-mail-server
    - onlyoffice-community-server
    environment:
    - ONLYOFFICE_CORE_MACHINEKEY=${ONLYOFFICE_CORE_MACHINEKEY}
    - TZ=${TZ}
    restart: always
    volumes:
    - /var/run/docker.sock:/var/run/docker.sock
    - ./data/controlpanel_data:/var/www/onlyoffice/Data
    - ./data/controlpanel_log:/var/log/onlyoffice
    networks:
    - onlyoffice

    networks:
    onlyoffice:
    driver: 'bridge'
  • ERPNEXT deployment, 25-003, multitenancy, DNS based

    # 啟動 DNS based multitenancy

    bench config dns_multitenant on

    # 建立新的ERPNEXT位置

    bench new-site siteNAME

    可以利用下列bash script建立新的ERPNEXT site

    #/usr/bin/env bash
    
    bench new-site siteNAME \
     --db-host 127.0.0.1 \
     --db-port 3306 \
     --mariadb-root-username mariadbROOT \
     --mariadb-root-password mariadbROOT-password \
     --admin-password adminPASSWORD \
     --mariadb-user-host-login-scope='%' \
     --verbose

    # 建立新的tenancy

    bench --site siteNAME add-to-hosts
  • ERPNEXT deployment, 25-001, basic system setup

    OS, UBUNTU 24.04
    ERPNEXT, version 15

    sudo apt update && sudo apt upgrade -y
    sudo apt install -y python3 python3-pip python3-venv python3-dev
    sudo apt install -y redis-server git
    sudo apt install -y supervisor

    # install Node.js, official NodeSource

    # Remove older versions first
    sudo apt remove nodejs npm -y
    
    # Add NodeSource for Node.js 18.x
    curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
    
    # Install Node.js and npm
    sudo apt install -y nodejs
    

    # versions check

    node -v
    npm -v
    yarn -v
    redis-server -v

    step 03, crete user

    # 設定frappe系統使用者
    sudo useradd -m -d /dir/for/frappe -s /bin/bash frappe
    
    # 設定frappe密碼
    sudo passwd frappe
    
    # 增加frappe群組
    sudo groupadd groupname
    
    # 將使用者frappe, 加入群組frappe
    sudo usermod -aG frappe frappe
    
    # 將使用者frappe, 加入群組frappe
    sudo usermod -aG sudo frappe

    step 04, virtual env setup, bench install & initiate

    # move to frappe dir
    cd /dir/to/frappe
    
    # Switch to user
    su frappe
    
    # deploy python virtualenv & activate
    python3 -m venv virtualENV
    source /path/to/virtualenv
    
    # Install bench & check bench version
    pip3 install frappe-bench
    bench --version
    bench init --frappe-branch version-15 frappeBENCH

    # setup bench Instance for

    # move to frappe bench directory
    mv frappeBENCH
    
    
    # setup supervisor
    確認在/etc/supervisor/conf.d內是否有supervisor.conf. 如果沒有, 執行下列指令. 
    bench setup supervisor
    
    ln -s /path/to/frappeBench/config/supervisor.conf /etc/supervisor/conf.d
    
    sudo supervisorctl reload
    
    
    # 下載app
    bench get-app --branch version-15 erpnext
    bench get-app --branch version-15 payments
    bench get-app --branch version-15 hrms
    
    
    # 利用下列bash script 建立個別erpnext個別網站
    
    #!/usr/bin/env bash
    
    siteNAME=erpnext.gerardchen.com
    
      bench new-site $siteNAME \
      --db-host 127.0.0.1 \
      --db-port 4306 \
      --mariadb-root-username root \
      --mariadb-root-password password.for.root \
      --admin-password password for admin \
      --mariadb-user-host-login-scope='%' \
      --verbose
    
    
    # 啟動bench
    bench start
    
    # Install ERPNext app
    bench --site siteNAME install-app erpnext
    bench --site siteNAME install-app hrms
    bench --site siteNAME install-app payments

    # setup production server

    # enable scheduler service
    bench --site siteNAME enable-scheduler
    
    # disable maintenance mode
    bench --site siteNAME set-maintenance-mode off
    
    # setup production config
    sudo bench setup production [frappe-user]
    因為以virtualenv建立python3環境, 且沒有設定PATH
    退出使用者frappe, 以superuser身分設定
    sudo /path/to/virtualENV/bin/bench setup production [frappe-user]
    
    bench setup socketio

    # setup port

    bench set-nginx-port siteName port
    

    bench init frappe --frappe-branch version-15
    bench init site01-bench --frappe-branch version-14

    Reference

  • erpNEXT deploy, conventional way

    System Update & Prerequisites

    sudo apt update && sudo apt upgrade -y
    
    sudo apt install -y git python3-dev python3-pip python3-setuptools python3-venv python3-full build-essential libmysqlclient-dev libssl-dev libffi-dev redis-server curl software-properties-common pkg-config

    Install MariaDBate & Prerequisites

    因為 host Ubuntu 巳有安裝mairadb,改用docker 佈署mariadb

    # 建立mariadb directory
    mkdir /path/to/mariadb/folder
    cd  /path/to/mariadb/folder
    
    # copy 建立setup.sh
    chmod +x setup.sh
    ./setup.sh
    
    # 啟動dokcer mairadb
    docker compose up -d
    
    # 確認mariadb 是否運行正常
    docker compose ps
    mysql -h 127.0.0.1 -P 3406 -u root -p
    # setup.sh
    
    #!/usr/bin/env bash
    
    # Get the directory where the script is located
    BASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    
    echo "Setting up Frappe MariaDB environment in $BASE_DIR..."
    
    # 1. Create Data Directory in the current path
    mkdir -p "$BASE_DIR/data"
    
    # 2. Create .env File
    cat <<EOF > "$BASE_DIR/.env"
    # Database Credentials
    MYSQL_ROOT_PASSWORD=frappe_admin_123
    
    # Host Port Configuration
    DB_HOST_PORT=3306
    # 如果 host 巳有佈署mariadb,必需變更port以避免衝宊
    
    EOF
    
    # 3. Create docker-compose.yml
    cat <<EOF > "$BASE_DIR/docker-compose.yml"
    services:
      mariadb:
        image: mariadb:10.6
        container_name: frappe-mariadb
        command:
          - --character-set-server=utf8mb4
          - --collation-server=utf8mb4_unicode_ci
          - --skip-character-set-client-handshake
          - --innodb-file-format=barracuda
          - --innodb-file-per-table=1
          - --innodb-large-prefix=1
          - --innodb-read-only-compressed=OFF
        environment:
          - MYSQL_ROOT_PASSWORD=\${MYSQL_ROOT_PASSWORD}
        volumes:
          - ./data:/var/lib/mysql
        ports:
          - "\${DB_HOST_PORT}:3306"
        restart: always
    EOF
    
    # 4. Set Permissions
    chmod -R 775 "$BASE_DIR/data"
    
    echo "-------------------------------------------------------"
    echo "SUCCESS: Configuration files and data folder created in:"
    echo "$BASE_DIR"
    echo ""
    echo "ATTENTION: Please revise the PORT and ADMIN PASSWORD in .env before running 'docker compose up -d'"
    echo "-------------------------------------------------------"

    Install Node.js and Yarn

    curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
    source ~/.bashrc
    nvm install 20
    npm install -g yarn

    Install Frappe Bench

    sudo apt update
    sudo apt install -y pipx
    pipx ensurepath
    pipx install uv
    
    # you may need to restart your terminal or run source ~/.bashrc

    Install Frappe Bench via pipx

    pipx install frappe-bench
    bench --version

    Initialize Bench

    bench init frappeBENCH --frappe-branch version-15
    cd my-frappe-bench
    
    
    # Generate the missing config file
    bench setup supervisor
    
    # Link it (if not already linked)
    sudo ln -sf $(pwd)/config/supervisor.conf /etc/supervisor/conf.d/frappe-bench.conf
    
    # Load it into Supervisor
    sudo supervisorctl reread
    sudo supervisorctl update