diff --git a/Dockerfile.prod b/Dockerfile.prod new file mode 100644 index 00000000..254d6f58 --- /dev/null +++ b/Dockerfile.prod @@ -0,0 +1,48 @@ +# Use the official PHP 5.6 Apache image for classic mod_php +FROM php:5.6-apache + +# Install required system libraries and PHP extensions for CakePHP +RUN sed -i 's|http://deb.debian.org/debian|http://archive.debian.org/debian|g' /etc/apt/sources.list && \ + sed -i 's|http://security.debian.org/debian-security|http://archive.debian.org/debian-security|g' /etc/apt/sources.list && \ + sed -i '/stretch-updates/d' /etc/apt/sources.list && \ + echo 'Acquire::AllowInsecureRepositories "true";' > /etc/apt/apt.conf.d/99allow-insecure && \ + echo 'Acquire::AllowDowngradeToInsecureRepositories "true";' >> /etc/apt/apt.conf.d/99allow-insecure && \ + apt-get update && \ + apt-get install --allow-unauthenticated -y libc-client2007e-dev libkrb5-dev libpng-dev libjpeg-dev libfreetype6-dev libcurl4-openssl-dev libxml2-dev libssl-dev libmcrypt-dev libicu-dev && \ + docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ && \ + docker-php-ext-configure imap --with-kerberos --with-imap-ssl && \ + docker-php-ext-install mysql mbstring gd curl imap + +# Set environment variables. +ENV HOME /root + +# Define working directory. +WORKDIR /root + +ARG COMMIT +ENV COMMIT_SHA=${COMMIT} + +EXPOSE 80 + +# Copy vhost config to Apache's sites-available +ADD conf/apache-vhost.conf /etc/apache2/sites-available/cmc-sales.conf +ADD conf/ripmime /bin/ripmime + +RUN chmod +x /bin/ripmime \ + && a2ensite cmc-sales \ + && a2dissite 000-default \ + && a2enmod rewrite \ + && a2enmod headers + +# Copy site into place. +ADD . /var/www/cmc-sales +ADD app/config/database.php /var/www/cmc-sales/app/config/database.php +RUN mkdir /var/www/cmc-sales/app/tmp +RUN mkdir /var/www/cmc-sales/app/tmp/logs +RUN chmod -R 755 /var/www/cmc-sales/app/tmp +RUN chmod +x /var/www/cmc-sales/run_vault.sh + +# Ensure CakePHP tmp directory is writable by web server +RUN chmod -R 777 /var/www/cmc-sales/app/tmp +# By default, simply start apache. +CMD /usr/sbin/apache2ctl -D FOREGROUND diff --git a/Dockerfile.prod.db b/Dockerfile.prod.db new file mode 100644 index 00000000..f18d6a48 --- /dev/null +++ b/Dockerfile.prod.db @@ -0,0 +1,4 @@ +FROM mariadb:latest + +COPY deploy/scripts/restore_db_from_backup.sh /docker-entrypoint-initdb.d/restore_db_from_backup.sh +RUN chmod +x /docker-entrypoint-initdb.d/restore_db_from_backup.sh diff --git a/Dockerfile.prod.go b/Dockerfile.prod.go new file mode 100644 index 00000000..fbfa0623 --- /dev/null +++ b/Dockerfile.prod.go @@ -0,0 +1,20 @@ +FROM golang:1.24-alpine AS builder + +RUN apk add --no-cache git +WORKDIR /app +COPY go-app/go.mod go-app/go.sum ./ +RUN go mod download +COPY go-app/ . +RUN go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest +RUN sqlc generate +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o server cmd/server/main.go + +FROM alpine:latest +RUN apk --no-cache add ca-certificates +WORKDIR /root/ +COPY --from=builder /app/server . +COPY go-app/templates ./templates +COPY go-app/static ./static +COPY go-app/.env.example .env +EXPOSE 8082 +CMD ["./server"] diff --git a/app/config/core.php b/app/config/core.php index 6baf049a..618ffba2 100644 --- a/app/config/core.php +++ b/app/config/core.php @@ -62,11 +62,21 @@ $host = $_SERVER['HTTP_HOST']; // 'timeout' => '30', // 'host' => '172.17.0.1')); -// SMTP settings for staging -Configure::write('smtp_settings', array( - 'port' => '1025', - 'timeout' => '30', - 'host' => 'mailpit')); + +// SMTP settings for production +if (in_array($host, $production_hosts)) { + Configure::write('smtp_settings', array( + 'port' => '25', + 'timeout' => '30', + 'host' => '172.17.0.1' + )); +} else { + // SMTP settings for staging + Configure::write('smtp_settings', array( + 'port' => '1025', + 'timeout' => '30', + 'host' => 'mailpit')); +} // Mailhog SMTP settings for local development diff --git a/conf/nginx-site.conf b/conf/nginx-site.conf index 0940883b..1765f6f2 100644 --- a/conf/nginx-site.conf +++ b/conf/nginx-site.conf @@ -3,7 +3,7 @@ server { auth_basic_user_file /etc/nginx/userpasswd; auth_basic "Restricted"; location /go/ { - proxy_pass http://cmc-go:8080; + proxy_pass http://cmc-prod-go:8082; proxy_read_timeout 300s; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -11,7 +11,7 @@ server { proxy_set_header X-Forwarded-Proto $scheme; } location / { - proxy_pass http://cmc-php:80; + proxy_pass http://cmc-prod-php:80; proxy_read_timeout 300s; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; diff --git a/conf/nginx-site.prod.conf b/conf/nginx-site.prod.conf new file mode 100644 index 00000000..8c1a129e --- /dev/null +++ b/conf/nginx-site.prod.conf @@ -0,0 +1,26 @@ +server { + server_name cmclocal; + auth_basic_user_file /etc/nginx/userpasswd; + auth_basic "Restricted"; + location /go/ { + proxy_pass http://cmc-prod-go:8082; + proxy_read_timeout 300s; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + location / { + proxy_pass http://cmc-prod-php:80; + proxy_read_timeout 300s; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + listen 0.0.0.0:80; +# include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot +# ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot + +} diff --git a/deploy/deploy-prod.sh b/deploy/deploy-prod.sh new file mode 100755 index 00000000..de4e8089 --- /dev/null +++ b/deploy/deploy-prod.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# Deploy production environment for cmc-sales + +# Usage: ./deploy-prod.sh [--no-cache] + +USE_CACHE=true +for arg in "$@"; do + if [[ "$arg" == "--no-cache" ]]; then + USE_CACHE=false + echo "No cache flag detected: will rebuild images without cache." + fi +done +if [[ "$USE_CACHE" == "true" ]]; then + echo "Using cached layers for docker build." +fi + +echo "Starting production deployment for cmc-sales..." +echo "Setting variables..." +SERVER="cmc-sales" +REPO="git@code.springupsoftware.com:cmc/cmc-sales.git" +BRANCH="prod" +PROD_DIR="cmc-sales-prod" + +echo "Connecting to server $SERVER via SSH..." +# Pass variables into SSH session +ssh $SERVER \ + "SERVER=$SERVER REPO='$REPO' BRANCH='$BRANCH' PROD_DIR='$PROD_DIR' USE_CACHE='$USE_CACHE' bash -s" << 'ENDSSH' + set -e + echo "Connected to $SERVER." + cd /home/cmc + # Clone or update production branch + if [ -d "$PROD_DIR" ]; then + echo "Updating existing production directory $PROD_DIR..." + cd "$PROD_DIR" + git fetch origin + git checkout $BRANCH + git reset --hard origin/$BRANCH + else + echo "Cloning repository $REPO to $PROD_DIR..." + git clone -b $BRANCH $REPO $PROD_DIR + cd "$PROD_DIR" + fi + + # Create .env file for go-app if it doesn't exist + ENV_PATH="/home/cmc/$PROD_DIR/go-app/.env" + echo "(Re)creating .env file for go-app..." + cat > "$ENV_PATH" <<'ENVEOF' +# Database configuration +DB_HOST=db +DB_PORT=3306 +DB_USER=cmc +DB_PASSWORD=xVRQI&cA?7AU=hqJ!%au +DB_NAME=cmc + +# Root database password (for dbshell-root) +DB_ROOT_PASSWORD=secureRootPassword + +# Environment variables for Go app mail configuration +SMTP_HOST="172.17.0.1" +SMTP_PORT=25 +SMTP_USER="" +SMTP_PASS="" +SMTP_FROM="CMC Sales " +ENVEOF + + if [[ "$USE_CACHE" == "false" ]]; then + echo "Building and starting docker compose for production (no cache)..." + docker compose -f docker-compose.prod.yml build --no-cache + docker compose -f docker-compose.prod.yml up -d --remove-orphans + else + echo "Building and starting docker compose for production (using cache)..." + docker compose -f docker-compose.prod.yml build + docker compose -f docker-compose.prod.yml up -d --remove-orphans + fi + + echo "Checking running containers..." + echo "Production deployment complete." +ENDSSH diff --git a/deploy/scripts/restore_db_from_backup.sh b/deploy/scripts/restore_db_from_backup.sh index b9c8bafb..67fa941e 100644 --- a/deploy/scripts/restore_db_from_backup.sh +++ b/deploy/scripts/restore_db_from_backup.sh @@ -1,12 +1,37 @@ #!/bin/bash set -e +# Default to staging +TARGET="stg" +for arg in "$@"; do + if [[ "$arg" == "-target" ]]; then + NEXT_IS_TARGET=1 + continue + fi + if [[ $NEXT_IS_TARGET == 1 ]]; then + TARGET="$arg" + NEXT_IS_TARGET=0 + fi +done + +if [[ "$TARGET" == "prod" ]]; then + DB_CONTAINER="cmc-prod-db" + DB_USER="cmc" + DB_PASS="xVRQI&cA?7AU=hqJ!%au" + DB_NAME="cmc" +else + DB_CONTAINER="cmc-db" + DB_USER="cmc" + DB_PASS="xVRQI&cA?7AU=hqJ!%au" + DB_NAME="cmc" +fi + # Sync latest backup from production rsync -avz -e "ssh -i ~/.ssh/cmc-old" --progress cmc@sales.cmctechnologies.com.au:~/backups /home/cmc/ LATEST_BACKUP=$(ls -t /home/cmc/backups/backup_*.sql.gz | head -n1) -echo "Restoring database from latest backup: $LATEST_BACKUP" +echo "Restoring database from latest backup: $LATEST_BACKUP to $TARGET ($DB_CONTAINER)" if [ -f "$LATEST_BACKUP" ]; then - docker cp "$LATEST_BACKUP" cmc-db:/tmp/backup.sql.gz - docker exec cmc-db sh -c "gunzip < /tmp/backup.sql.gz | mariadb -u cmc -p'xVRQI&cA?7AU=hqJ!%au' cmc" + docker cp "$LATEST_BACKUP" "$DB_CONTAINER":/tmp/backup.sql.gz + docker exec "$DB_CONTAINER" sh -c "gunzip < /tmp/backup.sql.gz | mariadb -u $DB_USER -p'$DB_PASS' $DB_NAME" echo "Database restore complete." else echo "No backup file found in /home/cmc/backups. Skipping database restore." diff --git a/deploy/scripts/run_all_migrations.sh b/deploy/scripts/run_all_migrations.sh index 7650581a..b00f79a3 100644 --- a/deploy/scripts/run_all_migrations.sh +++ b/deploy/scripts/run_all_migrations.sh @@ -1,10 +1,31 @@ #!/bin/bash set -e -SQL_DIR="/home/cmc/cmc-sales-staging/go-app/sql/schema" -DB_USER="cmc" -DB_PASS="xVRQI&cA?7AU=hqJ!%au" -DB_NAME="cmc" -DB_HOST="127.0.0.1" +# Default to staging +TARGET="stg" +for arg in "$@"; do + if [[ "$arg" == "-target" ]]; then + NEXT_IS_TARGET=1 + continue + fi + if [[ $NEXT_IS_TARGET == 1 ]]; then + TARGET="$arg" + NEXT_IS_TARGET=0 + fi +done + +if [[ "$TARGET" == "prod" ]]; then + DB_CONTAINER="cmc-prod-db" + DB_USER="cmc" + DB_PASS="xVRQI&cA?7AU=hqJ!%au" + DB_NAME="cmc" + SQL_DIR="/home/cmc/cmc-sales-prod/go-app/sql/schema" +else + DB_CONTAINER="cmc-db" + DB_USER="cmc" + DB_PASS="xVRQI&cA?7AU=hqJ!%au" + DB_NAME="cmc" + SQL_DIR="/home/cmc/cmc-sales-staging/go-app/sql/schema" +fi for sqlfile in "$SQL_DIR"/*.sql; do # Skip files starting with ignore_ @@ -12,8 +33,8 @@ for sqlfile in "$SQL_DIR"/*.sql; do echo "Skipping ignored migration: $sqlfile" continue fi - echo "Running migration: $sqlfile" - docker cp "$sqlfile" cmc-db:/tmp/migration.sql - docker exec cmc-db sh -c "mariadb -u $DB_USER -p'$DB_PASS' $DB_NAME < /tmp/migration.sql" + echo "Running migration: $sqlfile on $TARGET ($DB_CONTAINER)" + docker cp "$sqlfile" "$DB_CONTAINER":/tmp/migration.sql + docker exec "$DB_CONTAINER" sh -c "mariadb -u $DB_USER -p'$DB_PASS' $DB_NAME < /tmp/migration.sql" done echo "All migrations applied." diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 00000000..1dcc270b --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,86 @@ +services: + nginx: + image: nginx:latest + container_name: cmc-prod-nginx + hostname: nginx-prod + ports: + - "8080:80" # Production nginx on port 8080 to avoid conflict + volumes: + - ./conf/nginx-site.prod.conf:/etc/nginx/conf.d/cmc.conf + - ./userpasswd:/etc/nginx/userpasswd:ro + depends_on: + - cmc-prod-php + restart: unless-stopped + networks: + - cmc-prod-network + + cmc-prod-php: + build: + context: . + dockerfile: Dockerfile.prod + container_name: cmc-prod-php + environment: + MAIL_HOST: 172.17.0.1 + MAIL_PORT: 25 + DB_HOST: db + DB_PORT: 3306 + DB_USER: cmc + DB_PASSWORD: xVRQI&cA?7AU=hqJ!%au + DB_NAME: cmc + volumes: + - ./userpasswd:/etc/nginx/userpasswd:ro + networks: + - cmc-prod-network + restart: unless-stopped + depends_on: + - db + + cmc-prod-go: + build: + context: . + dockerfile: Dockerfile.prod.go + container_name: cmc-prod-go + environment: + DB_HOST: db + DB_PORT: 3306 + DB_USER: cmc + DB_PASSWORD: xVRQI&cA?7AU=hqJ!%au + DB_NAME: cmc + PORT: 8082 + SMTP_HOST: 172.17.0.1 + SMTP_PORT: 25 + SMTP_USER: "" + SMTP_PASS: "" + SMTP_FROM: "sales@cmctechnologies.com.au" + ports: + - "8083:8082" + volumes: + - /var/www/cmc-sales/app/webroot/pdf:/root/webroot/pdf:ro + networks: + - cmc-prod-network + restart: unless-stopped + depends_on: + - db + + db: + build: + context: . + dockerfile: Dockerfile.prod.db + container_name: cmc-prod-db + environment: + MYSQL_ROOT_PASSWORD: secureRootPassword + MYSQL_DATABASE: cmc + MYSQL_USER: cmc + MYSQL_PASSWORD: xVRQI&cA?7AU=hqJ!%au + volumes: + - db_data:/var/lib/mysql + ports: + - "3307:3306" + networks: + - cmc-prod-network + +networks: + cmc-prod-network: + +volumes: + db_data: