Month: December 2025

  • 密碼產生器

    # 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'