From 7660b16a2e357f348dd4d94be897b1bb4f32e141 Mon Sep 17 00:00:00 2001 From: "allen.yan" Date: Tue, 17 Jun 2025 12:47:14 +0800 Subject: [PATCH] KTVSinglePublisher V.0.0.0 20250617 --- .env.example | 11 ++ .gitattributes | 15 +++ .gitignore | 20 ++++ Dockerfile | 34 ++++++ README.md | 179 +++++++++++++++++++++++++++++ docker-compose.yml | 51 ++++++++ docker/entrypoint.git.sh | 24 ++++ docker/entrypoint.sh | 50 ++++++++ docker/logrotate/laravel | 23 ++++ docker/mariadb/my.cnf | 23 ++++ docker/nginx/default.conf.template | 27 +++++ docker/php/opcache.ini | 8 ++ docker/php/php.ini | 5 + docker/supervisord.conf | 35 ++++++ restart.sh | 30 +++++ start.sh | 28 +++++ status.sh | 19 +++ stop.sh | 33 ++++++ 開發紀錄.txt | 30 +++++ 19 files changed, 645 insertions(+) create mode 100644 .env.example create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 docker/entrypoint.git.sh create mode 100644 docker/entrypoint.sh create mode 100644 docker/logrotate/laravel create mode 100644 docker/mariadb/my.cnf create mode 100644 docker/nginx/default.conf.template create mode 100644 docker/php/opcache.ini create mode 100644 docker/php/php.ini create mode 100644 docker/supervisord.conf create mode 100755 restart.sh create mode 100755 start.sh create mode 100644 status.sh create mode 100755 stop.sh create mode 100644 開發紀錄.txt diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..450156b --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +# Project +APP_NAME=ktvsingle +APP_DOMAIN=hzd.superstar.dnsnet.cc +APP_URL=http://hzd.superstar.dnsnet.cc + +REPO_URL=http://47.251.18.130:3000/Leecheng/KTVSingle.git + +# Database +DB_DATABASE=KTVSingle +DB_USERNAME=KTVSingle +DB_PASSWORD=ESM7yTPMnavFmbBH diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7f300e2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,15 @@ +# 所有 Shell 腳本用 LF +*.sh text eol=lf + +# Docker 設定與腳本 +Dockerfile text eol=lf +*.yaml text eol=lf +*.yml text eol=lf + +# Laravel & PHP 設定檔 +*.conf text eol=lf +*.ini text eol=lf +.env* text eol=lf + +# 其他你專案內的自定義目錄內腳本 +docker/** text eol=lf \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7c77d24 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# 忽略開發用 override 設定 +docker-compose.override.yml + +# 忽略建置產出的 volume、掛載資料 +data/ +html/ +log/ +logs/ +*.sqlite + +# 忽略環境變數備份 +.env +.env.backup +.env.*.backup + +# 忽略暫存與 log +*.log +*.pid + +.DS_Store \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ede2417 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +FROM php:8.3-fpm + +# ---- system & PHP extensions ------------------------------------------------- +RUN apt-get update && apt-get install -y --no-install-recommends \ + procps nginx gettext logrotate \ + git unzip zip curl ca-certificates supervisor cron nano \ + libpng-dev libjpeg62-turbo-dev libfreetype6-dev \ + libonig-dev libxml2-dev libzip-dev libpq-dev libicu-dev libxslt-dev \ + libsqlite3-dev sqlite3 default-mysql-client \ + && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ + && apt-get install -y --no-install-recommends nodejs \ + && npm install -g npm \ + && docker-php-ext-configure gd --with-freetype --with-jpeg \ + && docker-php-ext-install -j$(nproc) gd pdo_mysql zip bcmath intl xsl pcntl sockets opcache\ + && pecl install redis && docker-php-ext-enable redis \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +# ---- composer --------------------------------------------------------------- +COPY --from=composer:latest /usr/bin/composer /usr/bin/composer + +# ---- config files ----------------------------------------------------------- +COPY docker/php/php.ini /usr/local/etc/php/conf.d/custom-php.ini +COPY docker/php/opcache.ini /usr/local/etc/php/conf.d/opcache.ini +COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf +COPY docker/entrypoint.sh /entrypoint.sh +COPY docker/entrypoint.git.sh /entrypoint.git.sh +COPY docker/nginx/default.conf.template /etc/nginx/templates/default.conf.template +COPY docker/logrotate/laravel /etc/logrotate.d/app/laravel.conf +RUN chmod 644 /etc/logrotate.d/app/laravel.conf + +WORKDIR /var/www + +RUN chmod +x /entrypoint.sh /entrypoint.git.sh +ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..52b619c --- /dev/null +++ b/README.md @@ -0,0 +1,179 @@ +# Laravel + Docker 開發環境 + +本專案提供一個以 Docker 為基礎的 Laravel 開發環境,整合以下功能: + +- Laravel App with PHP 8.3 +- Nginx 作為 Web Server +- MariaDB 作為資料庫 +- Supervisor 管理 Laravel 的 queue 與 scheduler +- Git Worker 自動同步程式碼 (透過 git pull) +- 多容器架構支援 `.env` 配置 + +--- + +## 📁 專案結構 + +``` +. +├── docker/ # 各服務用到的設定檔 +│ ├── nginx/ # Nginx templates +│ ├── mariadb/ # MariaDB my.cnf +│ ├── php/ # PHP 設定 +│ ├── entrypoint.git.sh # Git worker 初始化腳本 +│ ├── supervisord.conf # Laravel App 用 Supervisor 設定 +│ └── supervisord.git.conf # Git Worker 用 Supervisor 設定 +├── data/ # 資料儲存與服務掛載目錄 +│ ├── html/ # Laravel 專案程式碼 (git clone 下來) +│ ├── logs/ # Log 檔目錄 +│ ├── mariadb/ # DB 資料儲存 +│ └── nginx/ # Nginx 設定 +├── Dockerfile # Laravel App Dockerfile +├── Dockerfile.git # Git Worker Dockerfile +├── docker-compose.yml +└── README.md +``` + +--- + +## 🚀 快速開始 + +### 1️⃣ 建立 `.env` 檔案 + +```bash +cp .env.example .env +``` + +填寫必要參數: + +```env +APP_NAME=ktvcentral +APP_URL=http://localhost +APP_DOMAIN=localhost +APP_PORT=80 + +REPO_URL=https://github.com/your/laravel-repo.git +REPO_BRANCH=main + +DB_HOST=mariadb +DB_PORT=3306 +DB_DATABASE=laravel +DB_USERNAME=laravel +DB_PASSWORD=secret +``` + +--- + +### 2️⃣ 使用容器方法 + +```bash +./start.sh --wipe +./stop.sh --wipe +./restart.sh +``` + +完成後會啟動: + +- Laravel app +- Git worker (定期同步程式碼) +- Queue worker / Scheduler(由 Supervisor 管理) +- MariaDB +- Nginx + +--- + +## 🛠️ 服務說明 + +| 容器名稱 | 說明 | +|--------------------|---------------------------------------------| +| `ktvcentral_app` | Laravel App (PHP 8.3 + Supervisor) | +| `ktvcentral_db` | MariaDB 10.6 | +| `ktvcentral_nginx` | 靜態資源與反向代理(連接 Laravel App) | + +--- + +## 📚 Laravel 常見操作 + +```bash +# 進入 app 容器 +docker compose exec app bash + +# Laravel 操作範例 +php artisan migrate --seed +php artisan queue:restart +npm install && npm run build +``` + +--- + +## 🧼 目錄權限與 Log + +請確保以下目錄存在且權限正確: + +```bash +mkdir -p data/html +mkdir -p data/logs/php +mkdir -p data/logs/nginx +mkdir -p data/logs/mariadb +``` + +Laravel 儲存相關: + +```bash +chown -R www-data:www-data storage bootstrap/cache +chmod -R 775 storage bootstrap/cache +``` + +--- + +## 🐳 Supervisor 管理進程 + +| 進程名稱 | 功能 | +|----------------|----------------------------------| +| `queue-worker` | 執行 Laravel 的 queue 任務 | +| `scheduler` | 每分鐘執行 Laravel schedule:run | +| `git-worker` | 每分鐘從 Git 拉取最新程式碼 | + +查看 log: + +```bash +docker compose exec app tail -f /var/www/logs/supervisord.log +``` + +--- + +## 🧪 Git 版本控制(手動) + +```bash +# 回到上一個版本 +git checkout HEAD^ + +# 建立回退分支 +git switch -c fix-rollback-version + +# 回 commit 並推送 +git push origin fix-rollback-version +``` + +--- + +## 📦 附加服務(可擴充) + +你可以額外整合以下服務: + +- Redis +- phpMyAdmin +- Laravel Telescope / Horizon +- Mailpit / Mailhog + +--- + +## ✅ 建議調整 + +- 若為生產環境,請考慮設定更嚴格的權限與環境變數 +- 使用 `.env.production` 搭配 `.env` 切換設定檔 + +--- + +## 📜 License + +MIT © 2025 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..4873cba --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,51 @@ +services: + mariadb: + image: mariadb:10.6 + container_name: ${APP_NAME}_db + restart: always + environment: + MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} + MYSQL_DATABASE: ${DB_DATABASE} + MYSQL_USER: ${DB_USERNAME} + MYSQL_PASSWORD: ${DB_PASSWORD} + volumes: + - ./data/mariadb/lib:/var/lib/mysql + - ./data/mariadb/conf.d:/etc/mysql/conf.d + - ./data/logs/mariadb:/var/log/mysql + - ./docker/mariadb/my.cnf:/etc/mysql/conf.d/custom.cnf + ports: + - "3306:3306" + networks: + - app_network + + app: + build: + context: . + dockerfile: Dockerfile + container_name: ${APP_NAME}_app + volumes: + - ./data/html:/var/www/html + - ./data/logs/php:/var/www/logs + - ./data/logs/nginx/:/var/log/nginx + ports: + - "${APP_PORT:-80}:${APP_PORT:-80}" + depends_on: + - mariadb + environment: + REPO_URL: ${REPO_URL} + BRANCH: ${REPO_BRANCH:-main} + APP_NAME: ${APP_NAME} + APP_URL: ${APP_URL} + DB_HOST: mariadb + DB_PORT: 3306 + DB_DATABASE: ${DB_DATABASE} + DB_USERNAME: ${DB_USERNAME} + DB_PASSWORD: ${DB_PASSWORD} + NGINX_HOST: ${APP_DOMAIN} + NGINX_PORT: ${APP_PORT:-80} + networks: + - app_network + +networks: + app_network: + driver: bridge \ No newline at end of file diff --git a/docker/entrypoint.git.sh b/docker/entrypoint.git.sh new file mode 100644 index 0000000..aa745f8 --- /dev/null +++ b/docker/entrypoint.git.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -e + +BRANCH=${BRANCH:-main} +TARGET_DIR=/var/www/html + +echo "[git-worker] Loop started for branch $BRANCH" +while true; do + cd "$TARGET_DIR" + git remote update + LOCAL=$(git rev-parse @) + REMOTE=$(git rev-parse "origin/$BRANCH") + + if [ "$LOCAL" != "$REMOTE" ]; then + echo "[git-worker] Detected new commits, pulling..." + git pull origin "$BRANCH" + + echo "[git-worker] Restarting queue-worker..." + supervisorctl restart queue-worker + else + echo "[git-worker] No changes." + fi + sleep 300 +done \ No newline at end of file diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 0000000..3072289 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +set -e + +REPO_URL=${REPO_URL} +BRANCH=${BRANCH:-main} +TARGET_DIR=/var/www/html + +# 1) 初次 clone 或拉取 +if [ -z "$(ls -A "$TARGET_DIR")" ]; then + echo "[entrypoint] Cloning $BRANCH from $REPO_URL ..." + git clone --branch "$BRANCH" "$REPO_URL" "$TARGET_DIR" +else + echo "[entrypoint] Repository already present, skipping clone." +fi + +cd "$TARGET_DIR" + +# 2) Laravel 基礎安裝 +composer install --no-interaction --prefer-dist +[ -f .env ] || cp .env.example .env + +update_env() { local k=$1 v=$2; grep -q "^$k=" .env && sed -i "s|^$k=.*|$k=$v|" .env || echo "$k=$v" >> .env; } +update_env APP_NAME "$APP_NAME" +update_env APP_URL "${APP_URL%/}" +update_env DB_HOST "$DB_HOST" +update_env DB_PORT "$DB_PORT" +update_env DB_DATABASE "$DB_DATABASE" +update_env DB_USERNAME "$DB_USERNAME" +update_env DB_PASSWORD "$DB_PASSWORD" + +php artisan key:generate --force +php artisan migrate --force + +[ -d node_modules ] || npm install +npm run build + +chown -R www-data:www-data storage bootstrap/cache +chmod -R 775 storage bootstrap/cache + +mkdir -p /var/www/logs +supervisord -c /etc/supervisor/conf.d/supervisord.conf & +php-fpm -D +envsubst '${NGINX_HOST} ${NGINX_PORT}' < /etc/nginx/templates/default.conf.template > /etc/nginx/conf.d/default.conf +exec nginx -g "daemon off;" + +echo "0 0 * * * root /usr/sbin/logrotate /etc/logrotate.d/app" > /etc/cron.d/logrotate-Laravel +chmod 0644 /etc/cron.d/logrotate-Laravel + +echo "[entrypoint] Testing logrotate config..." +logrotate --debug /etc/logrotate.d/app/laravel.conf \ No newline at end of file diff --git a/docker/logrotate/laravel b/docker/logrotate/laravel new file mode 100644 index 0000000..206264a --- /dev/null +++ b/docker/logrotate/laravel @@ -0,0 +1,23 @@ +/var/log/nginx/*.log { + daily + missingok + rotate 7 + compress + delaycompress + notifempty + create 0640 www-data www-data + sharedscripts + postrotate + pkill -HUP nginx + endscript +} + +/var/www/logs/*.log { + daily + missingok + rotate 7 + compress + delaycompress + notifempty + create 0640 www-data www-data +} \ No newline at end of file diff --git a/docker/mariadb/my.cnf b/docker/mariadb/my.cnf new file mode 100644 index 0000000..3ca321a --- /dev/null +++ b/docker/mariadb/my.cnf @@ -0,0 +1,23 @@ +[mysqld] +log_error = /var/log/mysql/error.log +slow_query_log = 1 +slow_query_log_file = /var/log/mysql/slow.log +long_query_time = 2 + +# 使用 UTF-8 編碼 +character-set-server=utf8mb4 +collation-server=utf8mb4_unicode_ci + +# 提升最大連線數 +max_connections=200 + +# InnoDB 設定 +default-storage-engine=InnoDB +innodb_file_per_table=1 +innodb_buffer_pool_size=256M + +# 安全 SQL 模式 +sql_mode=STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION + +# 時區 +default_time_zone='+08:00' \ No newline at end of file diff --git a/docker/nginx/default.conf.template b/docker/nginx/default.conf.template new file mode 100644 index 0000000..3e3225e --- /dev/null +++ b/docker/nginx/default.conf.template @@ -0,0 +1,27 @@ +server { + listen ${NGINX_PORT}; + server_name ${NGINX_HOST}; + + root /var/www/html/public; + index index.php index.html; + + client_max_body_size 100M; + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location ~ \.php$ { + include fastcgi_params; + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + } + + location ~ /\.ht { + deny all; + } +} \ No newline at end of file diff --git a/docker/php/opcache.ini b/docker/php/opcache.ini new file mode 100644 index 0000000..c7dce53 --- /dev/null +++ b/docker/php/opcache.ini @@ -0,0 +1,8 @@ +opcache.enable=1 +opcache.enable_cli=1 +opcache.memory_consumption=128 +opcache.interned_strings_buffer=8 +opcache.max_accelerated_files=10000 +opcache.validate_timestamps=0 +opcache.revalidate_freq=0 +opcache.fast_shutdown=1 \ No newline at end of file diff --git a/docker/php/php.ini b/docker/php/php.ini new file mode 100644 index 0000000..f5698b0 --- /dev/null +++ b/docker/php/php.ini @@ -0,0 +1,5 @@ +memory_limit = 512M +upload_max_filesize = 50M +post_max_size = 50M +max_execution_time = 300 +date.timezone = Asia/Taipei \ No newline at end of file diff --git a/docker/supervisord.conf b/docker/supervisord.conf new file mode 100644 index 0000000..445da03 --- /dev/null +++ b/docker/supervisord.conf @@ -0,0 +1,35 @@ +[supervisord] +user=root +nodaemon=true +logfile=/var/www/logs/supervisord.log ; 設定 log 儲存位置 +pidfile=/var/www/logs/supervisord.pid ; 設定 pid 儲存位置 + +[program:queue-worker] +directory=/var/www/html +command=php artisan queue:work --daemon --timeout=3600 --tries=1 --queue=default +autostart=true +autorestart=true +stdout_logfile=/var/www/logs/queue.log +stderr_logfile=/var/www/logs/queue_error.log +stopsignal=INT + +[program:scheduler] +directory=/var/www/html +command=/bin/sh -c "while true; do php artisan schedule:run; sleep 60; done" +autostart=true +autorestart=true +stdout_logfile=/var/www/logs/schedule.log +stderr_logfile=/var/www/logs/schedule_error.log + +[program:git-worker] +command=/bin/sh /entrypoint.git.sh +autostart=true +autorestart=true +stdout_logfile=/var/www/logs/git-worker.log +stderr_logfile=/var/www/logs/git-worker_error.log +startsecs=0 + +[program:cron] +command=/usr/sbin/cron -f +autostart=true +autorestart=true \ No newline at end of file diff --git a/restart.sh b/restart.sh new file mode 100755 index 0000000..d37a10a --- /dev/null +++ b/restart.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +WIPE=false +if [ "$1" == "--wipe" ]; then + WIPE=true +fi + +# 載入 .env 中的變數 +if [ -f .env ]; then + export $(grep -v '^#' .env | xargs) + echo "📦 .env 載入完成" +else + echo "❌ 沒有找到 .env,無法載入環境變數" + exit 1 +fi + +# 使用 .env 中的 APP_NAME 作為 project name +PROJECT_NAME="${APP_NAME}" + +echo "♻️ 正在重新啟動 $PROJECT_NAME 所有服務..." + +# 先停服務 +echo "🔻 執行 stop.sh..." +./stop.sh $([ "$WIPE" == "true" ] && echo "--wipe") + +# 再啟動服務 +echo "🔺 執行 start.sh..." +./start.sh + +echo "✅ $PROJECT_NAME 已完成重啟!" \ No newline at end of file diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..9690a34 --- /dev/null +++ b/start.sh @@ -0,0 +1,28 @@ +#!/bin/bash +WIPE=false +if [ "$1" == "--wipe" ]; then + WIPE=true +fi + +# 載入 .env 中的變數 +if [ -f .env ]; then + export $(grep -v '^#' .env | xargs) + echo "📦 .env 載入完成" +else + echo "❌ 沒有找到 .env,無法載入環境變數" + exit 1 +fi + +# 使用 .env 中的 APP_NAME 作為 project name +PROJECT_NAME="${APP_NAME}" + +# 組合額外參數(如果有需要清除 volume 與 image) +EXTRA_FLAGS="up -d" +if [ "$WIPE" == "true" ]; then + EXTRA_FLAGS="up -d --build" +fi + +echo "🚀 Starting APP services..." +docker compose -p $PROJECT_NAME -f docker-compose.yml $EXTRA_FLAGS + +echo "✅ All services for $PROJECT_NAME are up and running!" \ No newline at end of file diff --git a/status.sh b/status.sh new file mode 100644 index 0000000..b171df7 --- /dev/null +++ b/status.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# 載入 .env 中的變數 +if [ -f .env ]; then + export $(grep -v '^#' .env | xargs) + echo "📦 .env 載入完成" +else + echo "❌ 沒有找到 .env,無法載入環境變數" + exit 1 +fi + +# 使用 .env 中的 APP_NAME 作為 project name +PROJECT_NAME="${APP_NAME}" + +echo "📊 目前 $PROJECT_NAME 相關容器狀態:" +docker ps --filter "name=${PROJECT_NAME}_" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" + +echo "🔍 目前網路狀態(含 app_network):" +docker network inspect app_network --format '{{json .Containers}}' | jq \ No newline at end of file diff --git a/stop.sh b/stop.sh new file mode 100755 index 0000000..3f18c77 --- /dev/null +++ b/stop.sh @@ -0,0 +1,33 @@ +#!/bin/bash +WIPE=false +if [ "$1" == "--wipe" ]; then + WIPE=true +fi + +# 載入 .env 中的變數 +if [ -f .env ]; then + export $(grep -v '^#' .env | xargs) + echo "📦 .env 載入完成" +else + echo "❌ 沒有找到 .env,無法載入環境變數" + exit 1 +fi + +# 使用 .env 中的 APP_NAME 作為 project name +PROJECT_NAME="${APP_NAME}" + + +# 組合額外參數(如果有需要清除 volume 與 image) +EXTRA_FLAGS="" +if [ "$WIPE" == "true" ]; then + EXTRA_FLAGS="-v --rmi all" +fi + +echo "🛑 Stopping APP services..." +docker compose -p "$PROJECT_NAME" -f docker-compose.yml down $EXTRA_FLAGS + +if [ "$WIPE" == "true" ]; then + echo "🧹 所有資料(volumes, image)已清除!" +else + echo "✅ 所有服務 $PROJECT_NAME 已成功關閉。" +fi diff --git a/開發紀錄.txt b/開發紀錄.txt new file mode 100644 index 0000000..9b81857 --- /dev/null +++ b/開發紀錄.txt @@ -0,0 +1,30 @@ + • 啟動所有服務:./start.sh --wipe + • 停止服務保留資料:./stop.sh + • 停止並清除資料:./stop.sh --wipe +# 正常重啟,不刪除資料 +./restart.sh + +# 重啟並清除 volumes 和 images +./restart.sh --wipe + +docker ps + + docker logs ktvcentral_app + +docker exec -it ktvcentral_app bash -c "cd /var/www/html && php artisan migrate:fresh --seed" + +docker exec -it ktvcentral_app php artisan migrate + + +artisan: + docker exec -it ktvcentral_app php artisan $(cmd) + docker exec -it ktvcentral_app bash + + +docker exec -it ktvcentral_app php artisan $(cmd) + + +docker exec -it ktvcentral_app php artisan transfer:sqlite sqlite/tempUser.sqlite --sync + + +