dev #1

Merged
webmaster merged 19 commits from dev into main 2025-12-22 15:28:43 +00:00
7 changed files with 421 additions and 61 deletions
Showing only changes of commit fa2956e8b6 - Show all commits

View File

@@ -51,7 +51,7 @@ jobs:
run: | run: |
scp -i ~/.ssh/id_ed25519 \ scp -i ~/.ssh/id_ed25519 \
/workspace/repo/merchbay_dev.tar.gz \ /workspace/repo/merchbay_dev.tar.gz \
/workspace/repo/docker-compose.yml \ /workspace/repo/docker-compose.dev.yml \
${DEPLOY_USER}@${DEPLOY_HOST}:/tmp/ ${DEPLOY_USER}@${DEPLOY_HOST}:/tmp/
# 5⃣ Deploy on server # 5⃣ Deploy on server
@@ -75,7 +75,7 @@ jobs:
docker images | grep merchbay | grep -v "$(docker images merchbay:dev -q)" | awk '{print $3}' | xargs -r docker rmi -f || true docker images | grep merchbay | grep -v "$(docker images merchbay:dev -q)" | awk '{print $3}' | xargs -r docker rmi -f || true
echo "📄 Updating compose file" echo "📄 Updating compose file"
cp /tmp/docker-compose.yml "$DEPLOY_DIR/" cp /tmp/docker-compose.dev.yml "$DEPLOY_DIR/docker-compose.yml"
cd "$DEPLOY_DIR" cd "$DEPLOY_DIR"
@@ -115,7 +115,7 @@ jobs:
fi fi
echo "🧹 Cleanup" echo "🧹 Cleanup"
rm -f /tmp/merchbay_dev.tar.gz /tmp/docker-compose.yml rm -f /tmp/merchbay_dev.tar.gz /tmp/docker-compose.dev.yml
echo "🧹 Aggressive Docker cleanup to reclaim space" echo "🧹 Aggressive Docker cleanup to reclaim space"
docker image prune -af --filter "until=24h" || true docker image prune -af --filter "until=24h" || true

View File

@@ -1,4 +1,4 @@
name: Deploy Production name: Deploy Production (merchbay.com)
on: on:
push: push:
@@ -12,84 +12,170 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: catthehacker/ubuntu:act-latest image: catthehacker/ubuntu:act-latest
steps: steps:
# 1⃣ Checkout code
- name: Checkout code - name: Checkout code
shell: sh shell: sh
run: | run: |
git clone $GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git /workspace/repo || true git clone $GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git /workspace/repo
cd /workspace/repo cd /workspace/repo
git fetch origin $GITHUB_REF_NAME
git checkout $GITHUB_REF_NAME git checkout $GITHUB_REF_NAME
git pull origin $GITHUB_REF_NAME
- name: Build Docker Image # 2⃣ Build image
- name: Build Docker image
shell: sh shell: sh
run: | run: |
cd /workspace/repo cd /workspace/repo
docker build -t merchbay:latest . docker build -t merchbay:latest .
docker save merchbay:latest | gzip > merchbay.tar.gz docker save merchbay:latest | gzip > merchbay.tar.gz
- name: Setup SSH and Deploy # 3⃣ Setup SSH
- name: Setup SSH
shell: sh shell: sh
env:
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
run: | run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
chmod 700 ~/.ssh chmod 700 ~/.ssh
echo "$PROD_DEPLOY_SSH_KEY" > ~/.ssh/deploy_key echo "$DEPLOY_SSH_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/deploy_key chmod 600 ~/.ssh/id_ed25519
ssh-keygen -y -f ~/.ssh/deploy_key > /dev/null 2>&1 || { echo "Error: Invalid SSH key format"; exit 1; } ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
cd /workspace/repo # 4⃣ Upload artifacts
scp -o StrictHostKeyChecking=no -i ~/.ssh/deploy_key merchbay.tar.gz docker-compose.yml "$PROD_DEPLOY_USER@$PROD_DEPLOY_HOST:/tmp/" - name: Upload image and compose
shell: sh
ssh -o StrictHostKeyChecking=no -i ~/.ssh/deploy_key "$PROD_DEPLOY_USER@$PROD_DEPLOY_HOST" " env:
DEPLOY_DIR='/var/www/merchbay' DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
mkdir -p \$DEPLOY_DIR DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
cd /tmp run: |
docker load < merchbay.tar.gz scp -i ~/.ssh/id_ed25519 \
/workspace/repo/merchbay.tar.gz \
echo 'Removing old merchbay images' /workspace/repo/docker-compose.prod.yml \
docker images | grep merchbay | grep -v "\$(docker images merchbay:latest -q)" | awk '{print \$3}' | xargs -r docker rmi -f || true ${DEPLOY_USER}@${DEPLOY_HOST}:/tmp/
cp docker-compose.yml \$DEPLOY_DIR/ # 5⃣ Deploy on server
cd \$DEPLOY_DIR - name: Deploy on server
shell: sh
# .env file should already exist on server with all required variables env:
# Required: DB_*, PROD_PRIVATE, IMAGES_URL, UPLOAD_URL DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
# Required: MAIL_*, CAPTCHA_*, ANALYTICS_* DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
# If it doesn't exist, deployment will fail (this is intentional for security)
run: |
docker compose down || true ssh -i ~/.ssh/id_ed25519 $DEPLOY_USER@$DEPLOY_HOST << 'EOF'
docker image prune -f set -e
docker network inspect traefik-public >/dev/null 2>&1 || docker network create traefik-public
export DOMAIN=merchbay.app DEPLOY_DIR="/var/www/merchbay"
export APP_URL=https://merchbay.app sudo mkdir -p "$DEPLOY_DIR"
sudo chown $USER:$USER "$DEPLOY_DIR"
echo "<22> Stopping dev environment"
DEV_DIR="/var/www/apps/merchbay_dev"
if [ -d "$DEV_DIR" ]; then
cd "$DEV_DIR"
docker compose down || true
cd -
echo "✅ Dev environment stopped"
echo "📋 Copying .env from dev to production"
if [ -f "$DEV_DIR/.env" ]; then
cp "$DEV_DIR/.env" "$DEPLOY_DIR/.env"
echo "✅ .env file copied from dev to production"
else
echo "⚠️ No .env file found in dev directory"
fi else
echo " No dev environment found"
fi
echo "📦 Loading image"
docker load < /tmp/merchbay.tar.gz
echo "🧹 Removing dev images and old merchbay images"
docker images | grep "merchbay:dev" | awk '{print $3}' | xargs -r docker rmi -f || true
docker images | grep merchbay | grep -v "$(docker images merchbay:latest -q)" | awk '{print $3}' | xargs -r docker rmi -f || true
echo "📄 Updating compose file"
cp /tmp/docker-compose.prod.yml "$DEPLOY_DIR/docker-compose.yml"
cd "$DEPLOY_DIR"
echo "🔍 Checking .env file"
if [ ! -f .env ]; then
echo "❌ .env file not found at $DEPLOY_DIR/.env"
echo "Please create it first with required variables:"
echo " - DB_*, PROD_PRIVATE, IMAGES_URL, UPLOAD_URL"
echo " - MAIL_*, CAPTCHA_*, ANALYTICS_*"
exit 1
fi
echo "🔧 Fixing .env permissions"
sudo chown $USER:$USER .env
sudo chmod 600 .env
echo "🌐 Ensure networks"
docker network inspect traefik-public >/dev/null 2>&1 || \
docker network create traefik-public
docker network inspect crew-app-net >/dev/null 2>&1 || \
docker network create crew-app-net
echo "🚀 Starting containers (env vars from .env file)"
docker compose up -d docker compose up -d
sleep 10
docker compose exec -T app php artisan migrate --force echo "⏳ Waiting for app container"
docker compose exec -T app php artisan config:cache sleep 15
docker compose exec -T app php artisan route:cache
docker compose exec -T app php artisan view:cache if docker ps --format '{{.Names}}' | grep -q merchbay_app; then
rm -f /tmp/merchbay.tar.gz /tmp/docker-compose.yml echo "🧹 Clearing and rebuilding config cache"
docker compose exec -T app php artisan config:clear
docker compose exec -T app php artisan config:cache
else
echo "❌ App container not running"
docker compose logs
exit 1
fi
echo "🧹 Cleanup"
rm -f /tmp/merchbay.tar.gz /tmp/docker-compose.prod.yml
echo 'Aggressive Docker cleanup to reclaim space' echo "🧹 Aggressive Docker cleanup to reclaim space"
docker image prune -af --filter "until=24h" || true docker image prune -af --filter "until=24h" || true
docker container prune -f || true docker container prune -f || true
docker volume prune -f || true docker volume prune -f || true
docker builder prune -af --filter "until=48h" || true docker builder prune -af --filter "until=48h" || true
echo 'Docker space usage:' echo "📊 Docker space usage:"
docker system df docker system df
echo 'Production deployment completed successfully!'
echo 'Application available at: https://merchbay.app'
"
env:
PROD_DEPLOY_SSH_KEY: ${{ secrets.PROD_DEPLOY_SSH_KEY }}
PROD_DEPLOY_USER: ${{ secrets.PROD_DEPLOY_USER }}
PROD_DEPLOY_HOST: ${{ secrets.PROD_DEPLOY_HOST }}
- name: Health Check echo "✅ Production deployment completed!"
echo "🌐 Application available at: https://merchbay.com"
EOF
# 6⃣ Health check
- name: Health check
shell: sh shell: sh
run: | run: |
sleep 10 echo "⏳ Waiting for app to be ready..."
curl -f https://merchbay.app || exit 1 sleep 20
echo "🔍 Testing health check..."
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 30 https://merchbay.com || echo "000")
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "302" ] || [ "$HTTP_CODE" = "301" ]; then
echo "✅ Health check passed! (HTTP $HTTP_CODE)"
echo "🎉 Production deployment successful!"
else
echo "❌ Health check failed! (HTTP $HTTP_CODE)"
echo ""
echo "💡 Troubleshooting:"
echo " 1. Check if container is running:"
echo " docker ps | grep merchbay_app"
echo ""
echo " 2. Check app logs:"
echo " docker logs merchbay_app"
echo ""
echo " 3. Check Traefik logs:"
echo " docker logs traefik"
echo ""
echo " 4. Test manually:"
echo " curl -Ik https://merchbay.com"
exit 1
fi

125
DIGICERT_SSL_SETUP.md Normal file
View File

@@ -0,0 +1,125 @@
# DigiCert SSL Certificate Setup for Production
## Certificate Files Required
From DigiCert, you'll receive these files:
- `merchbay_com.crt` - Your domain certificate
- `merchbay_com.key` - Private key (generated during CSR creation)
- `DigiCertCA.crt` - Intermediate certificate
- `TrustedRoot.crt` - Root certificate (optional)
## Step 1: Combine Certificate Chain (on your local machine)
```bash
# Create full chain certificate
cat merchbay_com.crt DigiCertCA.crt > merchbay.com.crt
# Copy private key
cp merchbay_com.key merchbay.com.key
```
## Step 2: Upload to Production Server
```bash
# SSH to production server
ssh PROD_DEPLOY_USER@PROD_DEPLOY_HOST
# Create certificates directory
sudo mkdir -p /srv/certs
sudo chmod 700 /srv/certs
# Exit SSH, then upload from local machine
scp merchbay.com.crt PROD_DEPLOY_USER@PROD_DEPLOY_HOST:/tmp/
scp merchbay.com.key PROD_DEPLOY_USER@PROD_DEPLOY_HOST:/tmp/
# SSH back to server and move files
ssh PROD_DEPLOY_USER@PROD_DEPLOY_HOST
sudo mv /tmp/merchbay.com.crt /srv/certs/
sudo mv /tmp/merchbay.com.key /srv/certs/
sudo chmod 600 /srv/certs/*
sudo chown root:root /srv/certs/*
```
## Step 3: Upload Traefik Configuration
```bash
# From local machine
scp traefik-certs.yml PROD_DEPLOY_USER@PROD_DEPLOY_HOST:/tmp/
# SSH to server
ssh PROD_DEPLOY_USER@PROD_DEPLOY_HOST
sudo mkdir -p /srv/traefik
sudo mv /tmp/traefik-certs.yml /srv/traefik/dynamic-certs.yml
sudo chmod 644 /srv/traefik/dynamic-certs.yml
```
## Step 4: Update Traefik Container
Ensure your Traefik docker-compose.yml includes:
```yaml
services:
traefik:
image: traefik:v2.10
command:
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --providers.file.filename=/dynamic-certs.yml
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /srv/certs:/srv/certs:ro
- /srv/traefik/dynamic-certs.yml:/dynamic-certs.yml:ro
networks:
- traefik-public
restart: unless-stopped
```
## Step 5: Restart Traefik
```bash
cd /opt/traefik # or wherever your traefik docker-compose.yml is
docker compose restart traefik
# Verify certificate is loaded
docker compose logs traefik | grep -i certificate
```
## Step 6: Deploy merchbay Application
Once Traefik is configured, deploy merchbay:
```bash
cd /var/www/merchbay
docker compose up -d
```
## Verification
```bash
# Check certificate
openssl s_client -connect merchbay.com:443 -servername merchbay.com < /dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates
# Should show:
# subject=CN = merchbay.com
# issuer=O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1
# notBefore=...
# notAfter=...
```
## Certificate Renewal
DigiCert certificates typically last 1-2 years. Set a reminder to renew 30 days before expiration and repeat Steps 1-3 and 5.
## Security Notes
- Never commit `.key` files to git
- Keep private keys secure (600 permissions)
- Use strong encryption for private key storage
- Consider using a certificate management system for automatic renewal

58
docker-compose.dev.yml Normal file
View File

@@ -0,0 +1,58 @@
services:
app:
image: merchbay:dev
container_name: merchbay_app_dev
restart: unless-stopped
environment:
- APP_ENV=${APP_ENV:-development}
- APP_DEBUG=${APP_DEBUG:-true}
- APP_URL=${APP_URL:-https://dev.merchbay.app}
- DB_CONNECTION=mysql
- DB_HOST=${DB_HOST}
- DB_PORT=${DB_PORT:-3306}
- DB_DATABASE=${DB_DATABASE}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
- PROD_PRIVATE=${PROD_PRIVATE}
- IMAGES_URL=${IMAGES_URL}
- UPLOAD_URL=${UPLOAD_URL}
- FORCE_HTTPS=true
- MAIL_DRIVER=${MAIL_DRIVER}
- MAIL_HOST=${MAIL_HOST}
- MAIL_PORT=${MAIL_PORT}
- MAIL_USERNAME=${MAIL_USERNAME}
- MAIL_PASSWORD=${MAIL_PASSWORD}
- MAIL_ENCRYPTION=${MAIL_ENCRYPTION}
- CAPTCHA_SITE_KEY=${CAPTCHA_SITE_KEY}
- CAPTCHA_SECRET_KEY=${CAPTCHA_SECRET_KEY}
- ANALYTICS_SITE_ID=${ANALYTICS_SITE_ID}
- ANALYTICS_CLIENT_ID=${ANALYTICS_CLIENT_ID}
- ANALYTICS_SERVICE_EMAIL=${ANALYTICS_SERVICE_EMAIL}
volumes:
- ./storage:/var/www/html/storage
- ./public/uploads:/var/www/html/public/uploads
labels:
- "traefik.enable=true"
# Development environment (dev.merchbay.app)
- "traefik.http.routers.merchbay-dev.rule=Host(`dev.merchbay.app`)"
- "traefik.http.routers.merchbay-dev.entrypoints=websecure"
- "traefik.http.routers.merchbay-dev.tls=true"
- "traefik.http.routers.merchbay-dev.tls.certresolver=le"
- "traefik.http.services.merchbay-dev.loadbalancer.server.port=80"
# HTTP to HTTPS redirect
- "traefik.http.routers.merchbay-dev-http.rule=Host(`dev.merchbay.app`)"
- "traefik.http.routers.merchbay-dev-http.entrypoints=web"
- "traefik.http.routers.merchbay-dev-http.middlewares=https-redirect"
- "traefik.http.middlewares.https-redirect.redirectscheme.scheme=https"
networks:
- traefik-public
- crew-app-net
- default
networks:
traefik-public:
external: true
crew-app-net:
external: true
default:
driver: bridge

57
docker-compose.prod.yml Normal file
View File

@@ -0,0 +1,57 @@
services:
app:
image: merchbay:latest
container_name: merchbay_app_prod
restart: unless-stopped
environment:
- APP_ENV=${APP_ENV:-production}
- APP_DEBUG=${APP_DEBUG:-false}
- APP_URL=${APP_URL:-https://merchbay.com}
- DB_CONNECTION=mysql
- DB_HOST=${DB_HOST}
- DB_PORT=${DB_PORT:-3306}
- DB_DATABASE=${DB_DATABASE}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
- PROD_PRIVATE=${PROD_PRIVATE}
- IMAGES_URL=${IMAGES_URL}
- UPLOAD_URL=${UPLOAD_URL}
- FORCE_HTTPS=true
- MAIL_DRIVER=${MAIL_DRIVER}
- MAIL_HOST=${MAIL_HOST}
- MAIL_PORT=${MAIL_PORT}
- MAIL_USERNAME=${MAIL_USERNAME}
- MAIL_PASSWORD=${MAIL_PASSWORD}
- MAIL_ENCRYPTION=${MAIL_ENCRYPTION}
- CAPTCHA_SITE_KEY=${CAPTCHA_SITE_KEY}
- CAPTCHA_SECRET_KEY=${CAPTCHA_SECRET_KEY}
- ANALYTICS_SITE_ID=${ANALYTICS_SITE_ID}
- ANALYTICS_CLIENT_ID=${ANALYTICS_CLIENT_ID}
- ANALYTICS_SERVICE_EMAIL=${ANALYTICS_SERVICE_EMAIL}
volumes:
- ./storage:/var/www/html/storage
- ./public/uploads:/var/www/html/public/uploads
labels:
- "traefik.enable=true"
# Production environment (merchbay.com) - Uses DigiCert SSL
- "traefik.http.routers.merchbay-prod.rule=Host(`merchbay.com`) || Host(`www.merchbay.com`)"
- "traefik.http.routers.merchbay-prod.entrypoints=websecure"
- "traefik.http.routers.merchbay-prod.tls=true"
- "traefik.http.services.merchbay-prod.loadbalancer.server.port=80"
# HTTP to HTTPS redirect
- "traefik.http.routers.merchbay-prod-http.rule=Host(`merchbay.com`) || Host(`www.merchbay.com`)"
- "traefik.http.routers.merchbay-prod-http.entrypoints=web"
- "traefik.http.routers.merchbay-prod-http.middlewares=https-redirect"
- "traefik.http.middlewares.https-redirect.redirectscheme.scheme=https"
networks:
- traefik-public
- crew-app-net
- default
networks:
traefik-public:
external: true
crew-app-net:
external: true
default:
driver: bridge

View File

@@ -1,6 +1,13 @@
# ⚠️ DEPRECATED: Use docker-compose.dev.yml or docker-compose.prod.yml instead
# This file is kept for reference only
#
# For development: docker-compose.dev.yml (dev.merchbay.app)
# For production: docker-compose.prod.yml (merchbay.com)
# For local dev: docker-compose.local.yml (localhost:8080)
services: services:
app: app:
image: merchbay:dev image: merchbay:latest
container_name: merchbay_app container_name: merchbay_app
restart: unless-stopped restart: unless-stopped
environment: environment:
@@ -33,15 +40,25 @@ services:
- ./public/uploads:/var/www/html/public/uploads - ./public/uploads:/var/www/html/public/uploads
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
# Development environment (dev.merchbay.app)
- "traefik.http.routers.merchbay-dev.rule=Host(`dev.merchbay.app`)" - "traefik.http.routers.merchbay-dev.rule=Host(`dev.merchbay.app`)"
- "traefik.http.routers.merchbay-dev.entrypoints=websecure" - "traefik.http.routers.merchbay-dev.entrypoints=websecure"
- "traefik.http.routers.merchbay-dev.tls=true" - "traefik.http.routers.merchbay-dev.tls=true"
- "traefik.http.routers.merchbay-dev.tls.certresolver=le" - "traefik.http.routers.merchbay-dev.tls.certresolver=letsencrypt"
- "traefik.http.services.merchbay-dev.loadbalancer.server.port=80" - "traefik.http.services.merchbay-dev.loadbalancer.server.port=80"
# HTTP to HTTPS redirect # Production environment (merchbay.com) - Uses DigiCert SSL
- "traefik.http.routers.merchbay-prod.rule=Host(`merchbay.com`) || Host(`www.merchbay.com`)"
- "traefik.http.routers.merchbay-prod.entrypoints=websecure"
- "traefik.http.routers.merchbay-prod.tls=true"
- "traefik.http.services.merchbay-prod.loadbalancer.server.port=80"
# HTTP to HTTPS redirect - Development
- "traefik.http.routers.merchbay-dev-http.rule=Host(`dev.merchbay.app`)" - "traefik.http.routers.merchbay-dev-http.rule=Host(`dev.merchbay.app`)"
- "traefik.http.routers.merchbay-dev-http.entrypoints=web" - "traefik.http.routers.merchbay-dev-http.entrypoints=web"
- "traefik.http.routers.merchbay-dev-http.middlewares=https-redirect" - "traefik.http.routers.merchbay-dev-http.middlewares=https-redirect"
# HTTP to HTTPS redirect - Production
- "traefik.http.routers.merchbay-prod-http.rule=Host(`merchbay.com`) || Host(`www.merchbay.com`)"
- "traefik.http.routers.merchbay-prod-http.entrypoints=web"
- "traefik.http.routers.merchbay-prod-http.middlewares=https-redirect"
- "traefik.http.middlewares.https-redirect.redirectscheme.scheme=https" - "traefik.http.middlewares.https-redirect.redirectscheme.scheme=https"
networks: networks:
- traefik-public - traefik-public

17
traefik-certs.yml Normal file
View File

@@ -0,0 +1,17 @@
# Traefik Dynamic Configuration for Custom SSL Certificates
# This file should be uploaded to /srv/traefik/dynamic/traefik-certs.yml on production server
tls:
certificates:
# Production SSL Certificate (DigiCert)
# Note: merchbay.com.crt should be a bundle containing www_merchbay_com.crt + DigiCertCA.crt
- certFile: /srv/certs/merchbay.com.crt
keyFile: /srv/certs/merchbay.com.key
stores:
- default
stores:
default:
defaultCertificate:
certFile: /srv/certs/merchbay.com.crt
keyFile: /srv/certs/merchbay.com.key