diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..bcc84a0 --- /dev/null +++ b/.env.example @@ -0,0 +1,17 @@ +# Copy to .env and fill in real values before running: docker compose up -d --build + +# MariaDB root password (also used by the app's db_pass) +DB_ROOT_PASSWORD= + +# Public IP or domain the browser uses to reach this server (Socket.IO client URL) +PUBLIC_HOST= + +# Shared secret between PHP and Node (must be a long random string) +EMIT_SECRET= + +# SMTP (Gmail) used for outgoing mail +SMTP_USERNAME= +SMTP_PASSWORD= + +# Port to expose the web app on (default 80) +HTTP_PORT=80 diff --git a/.gitignore b/.gitignore index de8581b..7e24262 100755 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ app/uploads node_modules/ nodejs/.env +# Docker deploy secrets +/.env + # zxcvbn dev dependencies (not needed at runtime) lib/zxcvbn-php-master/vendor/sebastian/ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6292605 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,55 @@ +services: + db: + image: mariadb:11 + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} + MYSQL_DATABASE: wms + volumes: + - db_data:/var/lib/mysql + - ./docker/mariadb/init-wms2.sql:/docker-entrypoint-initdb.d/init-wms2.sql:ro + networks: [wms] + + php: + build: + context: . + dockerfile: docker/php/Dockerfile + restart: unless-stopped + depends_on: [db] + environment: + DB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} + PUBLIC_HOST: ${PUBLIC_HOST} + EMIT_SECRET: ${EMIT_SECRET} + SMTP_USERNAME: ${SMTP_USERNAME} + SMTP_PASSWORD: ${SMTP_PASSWORD} + volumes: + - .:/var/www/html/wms-app + ports: + - "${HTTP_PORT:-80}:80" + networks: [wms] + + node: + build: + context: . + dockerfile: docker/node/Dockerfile + restart: unless-stopped + depends_on: [php] + environment: + PORT: 3000 + EMIT_SECRET: ${EMIT_SECRET} + PHP_BASE_URL: http://php + PHP_WEBROOT: /wms-app/app + ALLOWED_ORIGIN: http://${PUBLIC_HOST} + volumes: + - ./nodejs:/app + - node_modules:/app/node_modules + ports: + - "3000:3000" + networks: [wms] + +volumes: + db_data: + node_modules: + +networks: + wms: diff --git a/docker/mariadb/init-wms2.sql b/docker/mariadb/init-wms2.sql new file mode 100644 index 0000000..9f7066a --- /dev/null +++ b/docker/mariadb/init-wms2.sql @@ -0,0 +1 @@ +CREATE DATABASE IF NOT EXISTS wms2; diff --git a/docker/node/Dockerfile b/docker/node/Dockerfile new file mode 100644 index 0000000..2eb79fb --- /dev/null +++ b/docker/node/Dockerfile @@ -0,0 +1,12 @@ +FROM node:20-alpine + +RUN npm install -g pm2 + +WORKDIR /app + +COPY nodejs/package.json nodejs/package-lock.json ./ +RUN npm ci --omit=dev + +COPY nodejs/ ./ + +CMD ["pm2-runtime", "ecosystem.config.js"] diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile new file mode 100644 index 0000000..a7beb6d --- /dev/null +++ b/docker/php/Dockerfile @@ -0,0 +1,17 @@ +FROM php:8.3-apache + +RUN apt-get update && apt-get install -y --no-install-recommends \ + libzip-dev libicu-dev libonig-dev default-mysql-client gettext-base \ + && docker-php-ext-install pdo_mysql mysqli mbstring gd zip intl sockets exif opcache \ + && a2enmod rewrite \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +COPY docker/php/opcache.ini /usr/local/etc/php/conf.d/opcache-recommended.ini +COPY docker/php/entrypoint.sh /usr/local/bin/docker-entrypoint-wms.sh +COPY docker/php/config.php.template /usr/local/etc/wms/config.php.template +RUN chmod +x /usr/local/bin/docker-entrypoint-wms.sh + +WORKDIR /var/www/html/wms-app + +ENTRYPOINT ["docker-entrypoint-wms.sh"] +CMD ["apache2-foreground"] diff --git a/docker/php/config.php.template b/docker/php/config.php.template new file mode 100644 index 0000000..735e0ab --- /dev/null +++ b/docker/php/config.php.template @@ -0,0 +1,55 @@ +<><><><><><><> MAIN CONFIG (docker-generated) <><><><><><><><>// +if(true){ + $isTest = "master"; + $base_url = "/wms-app/"; + $server_url = $base_url."app/"; + $include_url = $_SERVER['DOCUMENT_ROOT'].$server_url; + + $db_server = "db"; + $db_user = "root"; + $db_pass = "${DB_ROOT_PASSWORD}"; + $db_type = "mysql"; + $db_database = "wms"; + $db_database2 = "wms2"; + + $db_server2 = (!empty($db_server2))?$db_server2:$db_server; + $db_user2 = (!empty($db_user2))?$db_user2:$db_user; + $db_pass2 = (!empty($db_pass2))?$db_pass2:$db_pass; + $db_type2 = (!empty($db_type2))?$db_type2:$db_type; + + $time_zone = "Asia/Bangkok"; +} + +// ── Real-time Node.js server ───────────────────────────────────────────────── +if (!defined('NODE_PUBLIC_URL')) { + define('NODE_PUBLIC_URL', 'http://${PUBLIC_HOST}:3000'); +} +if (!defined('NODE_EMIT_URL')) { + define('NODE_EMIT_URL', 'http://node:3000/emit'); +} +if (!defined('NODE_EMIT_SECRET')) { + define('NODE_EMIT_SECRET', '${EMIT_SECRET}'); +} + +// ── Usage packages ─────────────────────────────────────────────────────────── +$packages = [ + 'starter' => [ + 'daily_limit' => 30, + 'weekly_limit' => 100, + 'lock_on_limit' => ['dashboard'], + ], +]; + +// unique key — used for SMTP password encryption, keep consistent across deploys +$pinkey = "wms"; + +$SMTP = []; +$SMTP['server'] = "smtp.gmail.com"; +$SMTP['username'] = "${SMTP_USERNAME}"; +$SMTP['port'] = "587"; +$SMTP['password'] = "${SMTP_PASSWORD}"; // Gmail app password, not your login password +$method = "AES-256-CBC"; +$iv = "1234567890123456"; // Must be exactly 16 bytes +$SMTP['password'] = openssl_encrypt($SMTP['password'], $method, $pinkey, 0, $iv); diff --git a/docker/php/entrypoint.sh b/docker/php/entrypoint.sh new file mode 100644 index 0000000..15ec422 --- /dev/null +++ b/docker/php/entrypoint.sh @@ -0,0 +1,27 @@ +#!/bin/sh +set -e + +APP_DIR=/var/www/html/wms-app +CONFIG=$APP_DIR/app/config.php + +# Generate app/config.php from template on first run only. +# Restrict envsubst to known placeholders so it never touches the app's own +# $variable syntax (envsubst blanks out any $NAME it doesn't recognize). +if [ ! -f "$CONFIG" ]; then + echo "[entrypoint] generating app/config.php" + envsubst '${DB_ROOT_PASSWORD} ${PUBLIC_HOST} ${EMIT_SECRET} ${SMTP_USERNAME} ${SMTP_PASSWORD}' \ + < /usr/local/etc/wms/config.php.template > "$CONFIG" +fi + +mkdir -p "$APP_DIR/app/uploads" +chown -R www-data:www-data "$APP_DIR/app/uploads" + +echo "[entrypoint] waiting for database at db:3306" +until mysqladmin ping -h db -u root -p"$DB_ROOT_PASSWORD" --silent 2>/dev/null; do + sleep 2 +done + +echo "[entrypoint] running setup.php (idempotent schema sync)" +php "$APP_DIR/setup.php" || true + +exec "$@" diff --git a/docker/php/opcache.ini b/docker/php/opcache.ini new file mode 100644 index 0000000..406e4f5 --- /dev/null +++ b/docker/php/opcache.ini @@ -0,0 +1,5 @@ +opcache.enable=1 +opcache.memory_consumption=64 +opcache.max_accelerated_files=10000 +opcache.validate_timestamps=1 +opcache.revalidate_freq=2