dev #2

Merged
webmaster merged 34 commits from dev into main 2025-12-20 20:04:57 +00:00
11 changed files with 1509 additions and 14 deletions
Showing only changes of commit 0cedc90031 - Show all commits

View File

@@ -1,25 +1,33 @@
APP_ENV=local # Application Configuration
APP_DEBUG=true APP_ENV=production
APP_KEY=SomeRandomString APP_DEBUG=false
APP_URL=http://localhost APP_KEY=base64:YOUR_APP_KEY_HERE
APP_URL=https://merchbay-admin.yourdomain.com
# Database Configuration - External MySQL
DB_CONNECTION=mysql DB_CONNECTION=mysql
DB_HOST=127.0.0.1 DB_HOST=your-mysql-host
DB_PORT=3306 DB_PORT=3306
DB_DATABASE=homestead DB_DATABASE=merchbay_admin
DB_USERNAME=homestead DB_USERNAME=your-mysql-user
DB_PASSWORD=secret DB_PASSWORD=your-mysql-password
# Traefik Domain Configuration
DOMAIN=merchbay-admin.yourdomain.com
# Cache & Session
CACHE_DRIVER=file CACHE_DRIVER=file
SESSION_DRIVER=file SESSION_DRIVER=file
QUEUE_DRIVER=sync QUEUE_DRIVER=sync
# Redis (Optional)
REDIS_HOST=127.0.0.1 REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null REDIS_PASSWORD=null
REDIS_PORT=6379 REDIS_PORT=6379
# Mail Configuration
MAIL_DRIVER=smtp MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525 MAIL_PORT=2525
MAIL_USERNAME=null MAIL_USERNAME=null
MAIL_PASSWORD=null MAIL_PASSWORD=null

View File

@@ -0,0 +1,46 @@
name: Build and Push Docker Image
on:
push:
tags:
- 'v*'
workflow_dispatch:
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Registry
uses: docker/login-action@v2
with:
registry: ${{ secrets.DOCKER_REGISTRY_URL }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ secrets.DOCKER_REGISTRY_URL }}/merchbay_admin
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ secrets.DOCKER_REGISTRY_URL }}/merchbay_admin:buildcache
cache-to: type=registry,ref=${{ secrets.DOCKER_REGISTRY_URL }}/merchbay_admin:buildcache,mode=max

View File

@@ -0,0 +1,111 @@
name: Deploy Development
on:
push:
branches:
- dev
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build Docker Image
run: |
docker build -t merchbay_admin:dev .
- name: Save Docker Image
run: |
docker save merchbay_admin:dev | gzip > merchbay_admin_dev.tar.gz
- name: Deploy to Development Server via SSH
uses: appleboy/scp-action@master
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
port: ${{ secrets.DEPLOY_PORT || 22 }}
source: "merchbay_admin_dev.tar.gz,docker-compose.yml"
target: "/tmp/merchbay_admin_dev_deploy"
- name: Execute Development Deployment Script
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
port: ${{ secrets.DEPLOY_PORT || 22 }}
script: |
# Set deployment directory for dev
DEPLOY_DIR="/var/www/merchbay_admin_dev"
# Create deployment directory if it doesn't exist
mkdir -p $DEPLOY_DIR
# Load the Docker image
cd /tmp/merchbay_admin_dev_deploy
docker load < merchbay_admin_dev.tar.gz
# Copy docker-compose.yml to deployment directory
cp docker-compose.yml $DEPLOY_DIR/
# Navigate to deployment directory
cd $DEPLOY_DIR
# Update environment file for dev
cat > .env << EOF
APP_ENV=staging
APP_DEBUG=false
APP_URL=https://dev.merchbay.app
DB_HOST=${{ secrets.DEV_DB_HOST }}
DB_PORT=${{ secrets.DEV_DB_PORT || 3306 }}
DB_DATABASE=${{ secrets.DEV_DB_DATABASE }}
DB_USERNAME=${{ secrets.DEV_DB_USERNAME }}
DB_PASSWORD=${{ secrets.DEV_DB_PASSWORD }}
DOMAIN=dev.merchbay.app
EOF
# Stop existing container
docker compose down || true
# Remove old image
docker image prune -f
# Ensure Traefik network exists
docker network inspect traefik-public >/dev/null 2>&1 || docker network create traefik-public
# Update docker-compose for dev
export DOMAIN=dev.merchbay.app
export APP_URL=https://dev.merchbay.app
# Start the application
docker compose up -d
# Wait for container to be ready
sleep 10
# Run migrations
docker compose exec -T app php artisan migrate --force
# Clear and cache configuration
docker compose exec -T app php artisan config:cache
docker compose exec -T app php artisan route:cache
docker compose exec -T app php artisan view:cache
# Cleanup
rm -rf /tmp/merchbay_admin_dev_deploy
echo "Development deployment completed successfully!"
echo "Application available at: https://dev.merchbay.app"
- name: Health Check
run: |
sleep 10
curl -f https://dev.merchbay.app || exit 1

122
.gitea/workflows/deploy.yml Normal file
View File

@@ -0,0 +1,122 @@
name: Deploy Production
on:
push:
branches:
- main
- master
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
# If using self-hosted runner, change to:
# runs-on: self-hosted
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Registry (Optional)
if: ${{ secrets.DOCKER_REGISTRY_URL != '' }}
uses: docker/login-action@v2
with:
registry: ${{ secrets.DOCKER_REGISTRY_URL }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build Docker Image
run: |
docker build -t merchbay_admin:latest .
- name: Save Docker Image
run: |
docker save merchbay_admin:latest | gzip > merchbay_admin.tar.gz
- name: Deploy to Server via SSH
uses: appleboy/scp-action@master
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
port: ${{ secrets.DEPLOY_PORT || 22 }}
source: "merchbay_admin.tar.gz,docker-compose.yml"
target: "/tmp/merchbay_admin_deploy"
- name: Execute Production Deployment Script
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.PROD_DEPLOY_HOST }}
username: ${{ secrets.PROD_DEPLOY_USER }}
key: ${{ secrets.PROD_DEPLOY_SSH_KEY }}
port: ${{ secrets.PROD_DEPLOY_PORT || 22 }}
script: |
# Set deployment directory for production
DEPLOY_DIR="/var/www/merchbay_admin"
# Create deployment directory if it doesn't exist
mkdir -p $DEPLOY_DIR
# Load the Docker image
cd /tmp/merchbay_admin_deploy
docker load < merchbay_admin.tar.gz
# Copy docker-compose.yml to deployment directory
cp docker-compose.yml $DEPLOY_DIR/
# Navigate to deployment directory
cd $DEPLOY_DIR
# Update environment file for production
cat > .env << EOF
APP_ENV=production
APP_DEBUG=false
APP_URL=https://merchbay.app
DB_HOST=${{ secrets.PROD_DB_HOST }}
DB_PORT=${{ secrets.PROD_DB_PORT || 3306 }}
DB_DATABASE=${{ secrets.PROD_DB_DATABASE }}
DB_USERNAME=${{ secrets.PROD_DB_USERNAME }}
DB_PASSWORD=${{ secrets.PROD_DB_PASSWORD }}
DOMAIN=merchbay.app
EOF
# Stop existing container (disconnect from Traefik network gracefully)
docker compose down || true
# Remove old image (optional, keeps only latest)
docker image prune -f
# Ensure Traefik network exists
docker network inspect traefik-public >/dev/null 2>&1 || docker network create traefik-public
# Update docker-compose for production
export DOMAIN=merchbay.app
export APP_URL=https://merchbay.app
# Start the application (will auto-connect to Traefik with paid SSL)
docker compose up -d
# Wait for container to be ready
sleep 10
# Run migrations
docker compose exec -T app php artisan migrate --force
# Clear and cache configuration
docker compose exec -T app php artisan config:cache
docker compose exec -T app php artisan route:cache
docker compose exec -T app php artisan view:cache
# Cleanup
rm -rf /tmp/merchbay_admin_deploy
echo "Production deployment completed successfully!"
echo "Application available at: https://merchbay.app"
- name: Health Check
run: |
sleep 10
curl -f https://merchbay.app || exit 1

405
DEPLOYMENT-PORTAINER.md Normal file
View File

@@ -0,0 +1,405 @@
# Deployment with Portainer and Traefik
This guide covers deploying MerchBay Admin using your existing Portainer and Traefik setup.
## Prerequisites
- ✅ Gitea self-hosted with Gitea runners configured
- ✅ Portainer installed and accessible
- ✅ Traefik reverse proxy running with:
- `web` entrypoint (port 80)
- `websecure` entrypoint (port 443)
- Let's Encrypt certificate resolver named `letsencrypt`
- External network named `traefik`
## Deployment Methods
### Method 1: Portainer Stack Deployment (Recommended)
#### Step 1: Prepare the Image
Build the Docker image via Gitea Actions or manually:
```bash
# Using Gitea Actions (will build automatically on push)
git push origin main
# OR build manually
docker build -t merchbay_admin:latest .
```
#### Step 2: Deploy in Portainer
1. **Access Portainer**`Stacks``Add stack`
2. **Stack Configuration:**
- Name: `merchbay-admin`
- Build method: `Web editor`
3. **Paste the content from `docker-compose.portainer.yml`** and update:
- `APP_URL`: Your domain (e.g., `https://merchbay-admin.yourdomain.com`)
- `DB_HOST`: Your MySQL host
- `DB_DATABASE`: Database name
- `DB_USERNAME`: Database username
- `DB_PASSWORD`: Database password
- Traefik Host rule: Replace `merchbay-admin.yourdomain.com` with your actual domain
4. **Deploy the stack**
5. **Run Initial Setup:**
- Go to `Containers` → Find `merchbay_admin_app`
- Click `Console` → Connect with `/bin/bash`
- Run:
```bash
php artisan migrate --force
php artisan config:cache
php artisan route:cache
```
### Method 2: Gitea Actions CI/CD (Automated)
#### Step 1: Configure Gitea Secrets
In your Gitea repository → `Settings` → `Secrets` → `Actions`, add:
| Secret Name | Value | Description |
|------------|-------|-------------|
| `DEPLOY_HOST` | `192.168.1.100` | Your server IP/hostname |
| `DEPLOY_USER` | `deploy` | SSH username |
| `DEPLOY_SSH_KEY` | `-----BEGIN RSA...` | Private SSH key |
| `DEPLOY_PORT` | `22` | SSH port (optional) |
| `DEPLOY_DIR` | `/var/www/merchbay_admin` | Deployment directory |
| `DOMAIN` | `merchbay-admin.yourdomain.com` | Your domain for Traefik |
#### Step 2: Prepare Deployment Server
```bash
# SSH into your server
ssh deploy@your-server
# Create deployment directory
sudo mkdir -p /var/www/merchbay_admin
sudo chown $USER:$USER /var/www/merchbay_admin
cd /var/www/merchbay_admin
# Create .env file
nano .env
```
Add to `.env`:
```env
APP_ENV=production
APP_DEBUG=false
APP_URL=https://merchbay-admin.yourdomain.com
DB_HOST=your-mysql-host
DB_PORT=3306
DB_DATABASE=merchbay_admin
DB_USERNAME=your-mysql-user
DB_PASSWORD=your-mysql-password
DOMAIN=merchbay-admin.yourdomain.com
```
#### Step 3: Ensure Traefik Network Exists
```bash
docker network inspect traefik >/dev/null 2>&1 || docker network create traefik
```
#### Step 4: Deploy
Simply push to your main branch:
```bash
git add .
git commit -m "Deploy application"
git push origin main
```
Gitea Actions will automatically:
1. Build the Docker image
2. Transfer to your server
3. Deploy with docker-compose
4. Connect to Traefik network
5. Run migrations
6. Cache configurations
### Method 3: Manual Deployment with Script
```bash
# On your server
cd /var/www/merchbay_admin
# Pull latest code
git pull origin main
# Run deployment script
./deploy.sh
```
## Traefik Configuration
### Verify Traefik Setup
Ensure your Traefik configuration includes:
```yaml
# traefik.yml or docker-compose.yml
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
certificatesResolvers:
letsencrypt:
acme:
email: your-email@example.com
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web
```
### External Network
Ensure Traefik network exists and is external:
```bash
# Create network if it doesn't exist
docker network create traefik
# Verify
docker network inspect traefik
```
## DNS Configuration
Point your domain to your server:
```
Type: A Record
Name: merchbay-admin (or @ for root domain)
Value: YOUR_SERVER_IP
TTL: 3600
```
## Verification
### 1. Check Container Status
**Via Portainer:**
- Navigate to `Containers`
- Verify `merchbay_admin_app` is running
**Via CLI:**
```bash
docker ps | grep merchbay_admin
```
### 2. Check Traefik Dashboard
If you have Traefik dashboard enabled:
- Look for `merchbay-admin@docker` router
- Verify it's connected to the correct service
### 3. Test Application
```bash
# Test HTTPS
curl -I https://merchbay-admin.yourdomain.com
# Should return: HTTP/2 200
```
### 4. Check Logs
**Via Portainer:**
- `Containers` → `merchbay_admin_app` → `Logs`
**Via CLI:**
```bash
docker logs merchbay_admin_app -f
```
## Troubleshooting
### Issue: Container not accessible via domain
**Check Traefik labels:**
```bash
docker inspect merchbay_admin_app | grep -A 20 Labels
```
**Verify network connection:**
```bash
docker network inspect traefik | grep merchbay_admin
```
**Solution:**
```bash
# Reconnect to Traefik network
docker network connect traefik merchbay_admin_app
```
### Issue: SSL certificate not generating
**Check Traefik logs:**
```bash
docker logs traefik | grep letsencrypt
```
**Common fixes:**
1. Ensure port 80 is accessible (Let's Encrypt HTTP challenge)
2. Verify DNS is pointing to your server
3. Check email in Traefik's ACME configuration
4. Ensure `acme.json` has correct permissions (600)
### Issue: Database connection failed
**Check environment variables:**
```bash
docker exec merchbay_admin_app env | grep DB_
```
**Test connection:**
```bash
docker exec merchbay_admin_app php artisan tinker
>>> DB::connection()->getPdo();
```
### Issue: 502 Bad Gateway
**Possible causes:**
1. Application not fully started
2. Wrong port in Traefik label (should be 80)
3. Application crashed
**Check:**
```bash
# Container status
docker ps -a | grep merchbay_admin
# Application logs
docker logs merchbay_admin_app --tail 100
# Restart container
docker restart merchbay_admin_app
```
## Updating the Application
### Via Gitea Actions (Automatic)
```bash
git add .
git commit -m "Update application"
git push origin main
```
### Via Portainer
1. Go to `Stacks` → `merchbay-admin`
2. Click `Editor`
3. Update image or configuration
4. Click `Update the stack`
5. Enable "Re-pull image and redeploy"
### Manual Update
```bash
ssh deploy@your-server
cd /var/www/merchbay_admin
./deploy.sh
```
## Rollback
### Via Portainer
1. Go to `Stacks` → `merchbay-admin`
2. Click on the stack
3. Find previous version in `Stack History` (if available)
4. Revert to previous version
### Manual Rollback
```bash
# List available backups
ls -lh /var/www/merchbay_admin/backups/
# Restore from backup
cd /var/www/merchbay_admin
tar -xzf backups/backup_YYYYMMDD_HHMMSS.tar.gz
# Restart
docker compose restart
```
## Security Best Practices
1. **Use Strong Database Passwords**: Generate with `openssl rand -base64 32`
2. **Restrict SSH Access**: Use key-based authentication only
3. **Firewall Rules**: Only allow necessary ports (80, 443, 22)
4. **Regular Backups**: Automated backups of database and storage
5. **Keep Docker Updated**: Regularly update Docker and images
6. **Monitor Logs**: Set up log monitoring/alerting
7. **Use Secrets**: Never commit sensitive data to repository
8. **HTTPS Only**: Ensure HTTP redirects to HTTPS
## Performance Optimization
### PHP-FPM Configuration
Consider switching to PHP-FPM for better performance:
```dockerfile
FROM php:7.4-fpm-alpine
# ... additional configuration
```
### Use Redis for Cache/Sessions
```env
CACHE_DRIVER=redis
SESSION_DRIVER=redis
REDIS_HOST=redis
```
Add Redis service to docker-compose:
```yaml
services:
redis:
image: redis:alpine
networks:
- default
```
### Enable OPcache
Already included in Dockerfile, verify:
```bash
docker exec merchbay_admin_app php -i | grep opcache
```
## Monitoring
### View Real-time Logs in Portainer
1. Navigate to `Containers`
2. Click on `merchbay_admin_app`
3. Select `Logs` tab
4. Enable `Auto-refresh`
### Set Up Alerts
Configure Portainer notifications:
1. `Settings` → `Notifications`
2. Add webhook or email notification
3. Set up container health checks
## Support
For issues:
1. Check application logs in Portainer
2. Verify Traefik configuration
3. Test database connectivity
4. Review Gitea Actions logs if using CI/CD

264
DEPLOYMENT.md Normal file
View File

@@ -0,0 +1,264 @@
# Gitea Actions Deployment Guide
This repository uses Gitea Actions for automated deployment to your server.
## Workflows
### 1. Deploy Workflow (`.gitea/workflows/deploy.yml`)
Automatically deploys the application when code is pushed to `main` or `master` branch.
**Steps:**
1. Builds Docker image
2. Transfers image to deployment server
3. Stops existing container
4. Starts new container
5. Runs database migrations
6. Clears and caches Laravel configuration
### 2. Build and Push Workflow (`.gitea/workflows/build-push.yml`)
Builds and pushes Docker images to a registry when a version tag is created.
## Required Secrets
Configure these secrets in your Gitea repository settings:
`Settings``Secrets``Actions`
### Deployment Secrets
| Secret Name | Description | Example |
|------------|-------------|---------|
| `DEPLOY_HOST` | Deployment server hostname or IP | `192.168.1.100` or `example.com` |
| `DEPLOY_USER` | SSH username for deployment | `deploy` or `ubuntu` |
| `DEPLOY_SSH_KEY` | Private SSH key for authentication | `-----BEGIN RSA PRIVATE KEY-----...` |
| `DEPLOY_PORT` | SSH port (optional, defaults to 22) | `22` |
| `DEPLOY_DIR` | Deployment directory (optional) | `/var/www/merchbay_admin` |
### Docker Registry Secrets (Optional)
Only required if using the build-push workflow or private registry:
| Secret Name | Description | Example |
|------------|-------------|---------|
| `DOCKER_REGISTRY_URL` | Docker registry URL | `registry.example.com` or `docker.io` |
| `DOCKER_USERNAME` | Registry username | `myuser` |
| `DOCKER_PASSWORD` | Registry password or token | `mypassword` |
### Database Configuration on Server
Create a `.env` file in your deployment directory with database credentials:
```bash
# On your deployment server
sudo mkdir -p /var/www/merchbay_admin
sudo nano /var/www/merchbay_admin/.env
```
Add your database configuration:
```env
DB_HOST=your-mysql-host
DB_PORT=3306
DB_DATABASE=merchbay_admin
DB_USERNAME=your-mysql-user
DB_PASSWORD=your-mysql-password
APP_ENV=production
APP_DEBUG=false
APP_KEY=base64:YOUR_APP_KEY_HERE
```
## Setup Instructions
### 1. Generate SSH Key for Deployment
On your local machine or CI server:
```bash
# Generate a new SSH key pair
ssh-keygen -t rsa -b 4096 -f ~/.ssh/deploy_key -N ""
# Copy the public key to your deployment server
ssh-copy-id -i ~/.ssh/deploy_key.pub user@your-server
# Copy the private key content for Gitea secret
cat ~/.ssh/deploy_key
```
### 2. Configure Gitea Secrets
1. Go to your Gitea repository
2. Navigate to `Settings``Secrets``Actions`
3. Add each required secret listed above
4. For `DEPLOY_SSH_KEY`, paste the entire private key content
### 3. Prepare Deployment Server
On your deployment server, install Docker and Docker Compose:
```bash
# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
# Add your user to docker group
sudo usermod -aG docker $USER
# Install Docker Compose
sudo apt-get update
sudo apt-get install docker-compose-plugin
# Create deployment directory
sudo mkdir -p /var/www/merchbay_admin
sudo chown $USER:$USER /var/www/merchbay_admin
# Create .env file with database credentials
nano /var/www/merchbay_admin/.env
```
### 4. Update docker-compose.yml for Production
Ensure your `docker-compose.yml` references the `.env` file:
```yaml
services:
app:
environment:
- DB_HOST=${DB_HOST}
- DB_PORT=${DB_PORT}
- DB_DATABASE=${DB_DATABASE}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
```
## Triggering Deployment
### Automatic Deployment
Push to main/master branch:
```bash
git add .
git commit -m "Deploy updates"
git push origin main
```
### Manual Deployment
1. Go to your Gitea repository
2. Click on `Actions`
3. Select `Deploy MerchBay Admin` workflow
4. Click `Run workflow`
## Monitoring Deployment
### View Workflow Logs
1. Go to `Actions` tab in your Gitea repository
2. Click on the running/completed workflow
3. View logs for each step
### Check Application Logs
On your deployment server:
```bash
cd /var/www/merchbay_admin
docker compose logs -f app
```
### Verify Deployment
```bash
# Check container status
docker compose ps
# Test application
curl http://localhost:8080
# Access application shell
docker compose exec app bash
```
## Rollback Procedure
If deployment fails, you can quickly rollback:
```bash
# On deployment server
cd /var/www/merchbay_admin
# Stop current container
docker compose down
# Load previous image (if available)
docker images # Find previous image ID
docker tag <previous-image-id> merchbay_admin:latest
# Start with previous version
docker compose up -d
```
## Troubleshooting
### SSH Connection Issues
```bash
# Test SSH connection from CI to server
ssh -i ~/.ssh/deploy_key user@your-server
# Check SSH key permissions
chmod 600 ~/.ssh/deploy_key
```
### Docker Permission Issues
```bash
# On deployment server, ensure user is in docker group
sudo usermod -aG docker $USER
newgrp docker
```
### Migration Failures
```bash
# Manually run migrations
docker compose exec app php artisan migrate --force
# Check database connection
docker compose exec app php artisan tinker
>>> DB::connection()->getPdo();
```
## Security Best Practices
1. **Use SSH keys, not passwords** for server authentication
2. **Restrict SSH key** to only deployment commands if possible
3. **Use secrets** for all sensitive data, never commit to repository
4. **Set proper file permissions** on deployment server (755 for directories, 644 for files)
5. **Enable firewall** on deployment server and restrict access
6. **Use HTTPS** with SSL certificates in production
7. **Regular backups** of database and uploaded files
## Advanced Configuration
### Using Docker Registry
To use a private registry:
1. Add registry secrets to Gitea
2. Update deployment script to pull from registry instead of transferring image
3. Use the build-push workflow to automate image publishing
### Zero-Downtime Deployment
For zero-downtime deployments, consider:
1. Using a load balancer
2. Running multiple container instances
3. Implementing blue-green deployment strategy
### Environment-Specific Deployments
Create separate workflows for staging and production:
- `.gitea/workflows/deploy-staging.yml` (triggered on `develop` branch)
- `.gitea/workflows/deploy-production.yml` (triggered on `main` branch)

316
TRAEFIK-SSL-CONFIG.md Normal file
View File

@@ -0,0 +1,316 @@
# Traefik SSL Configuration for MerchBay Admin
This document explains how to configure paid SSL certificates for production and automatic Let's Encrypt for development.
## Network Configuration
All deployments use the external network: `traefik-public`
```bash
# Ensure the network exists
docker network inspect traefik-public >/dev/null 2>&1 || docker network create traefik-public
```
## Development (dev.merchbay.app) - Automatic SSL
Development uses Let's Encrypt for automatic SSL certificate generation.
### Traefik Configuration
Ensure your Traefik has Let's Encrypt configured:
```yaml
# traefik.yml or dynamic config
certificatesResolvers:
letsencrypt:
acme:
email: admin@merchbay.app
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web
```
### Application Labels (Already configured in docker-compose)
```yaml
labels:
- "traefik.http.routers.merchbay-admin-dev.tls.certresolver=letsencrypt"
```
## Production (merchbay.app) - Paid SSL Certificate
Production uses a paid SSL certificate (e.g., from GoDaddy, Namecheap, Cloudflare).
### Step 1: Prepare SSL Certificate Files
You should have these files from your SSL provider:
- `merchbay.app.crt` - Certificate file
- `merchbay.app.key` - Private key file
- `ca-bundle.crt` - CA bundle (optional, for chain)
Create a combined certificate file:
```bash
# Create SSL directory in Traefik
mkdir -p /opt/traefik/certs
# Copy your certificate and key
sudo cp merchbay.app.crt /opt/traefik/certs/
sudo cp merchbay.app.key /opt/traefik/certs/
# If you have a CA bundle, create a full chain
cat merchbay.app.crt ca-bundle.crt > /opt/traefik/certs/merchbay.app-fullchain.crt
# Set proper permissions
sudo chmod 600 /opt/traefik/certs/*.key
sudo chmod 644 /opt/traefik/certs/*.crt
```
### Step 2: Configure Traefik File Provider
Create a dynamic configuration file for Traefik:
```bash
sudo nano /opt/traefik/dynamic/certs.yml
```
Add:
```yaml
# /opt/traefik/dynamic/certs.yml
tls:
certificates:
- certFile: /certs/merchbay.app-fullchain.crt
keyFile: /certs/merchbay.app.key
stores:
- default
stores:
default:
defaultCertificate:
certFile: /certs/merchbay.app-fullchain.crt
keyFile: /certs/merchbay.app.key
```
### Step 3: Update Traefik docker-compose.yml
Ensure Traefik has file provider enabled and certificates mounted:
```yaml
services:
traefik:
image: traefik:v2.10
command:
- "--providers.docker=true"
- "--providers.docker.network=traefik-public"
- "--providers.file.directory=/etc/traefik/dynamic"
- "--providers.file.watch=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
# Let's Encrypt for dev
- "--certificatesresolvers.letsencrypt.acme.email=admin@merchbay.app"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /opt/traefik/certs:/certs:ro
- /opt/traefik/dynamic:/etc/traefik/dynamic:ro
- traefik-letsencrypt:/letsencrypt
networks:
- traefik-public
```
### Step 4: Restart Traefik
```bash
cd /path/to/traefik
docker compose restart traefik
# Verify certificates are loaded
docker compose logs traefik | grep -i cert
```
## Application Configuration
### Development Branch (dev)
File: `docker-compose.portainer.dev.yml`
- Domain: `dev.merchbay.app`
- SSL: Let's Encrypt (automatic)
- Certificate Resolver: `letsencrypt`
```yaml
labels:
- "traefik.http.routers.merchbay-admin-dev.rule=Host(`dev.merchbay.app`)"
- "traefik.http.routers.merchbay-admin-dev.tls.certresolver=letsencrypt"
```
### Production Branch (main)
File: `docker-compose.portainer.yml`
- Domain: `merchbay.app`
- SSL: Paid certificate (via Traefik file provider)
- No certresolver label (uses default store)
```yaml
labels:
- "traefik.http.routers.merchbay-admin.rule=Host(`merchbay.app`)"
- "traefik.http.routers.merchbay-admin.tls=true"
# No certresolver - uses file provider certificate
```
## Gitea Secrets Configuration
### Development Secrets
```
DEV_DB_HOST=dev-mysql-host
DEV_DB_PORT=3306
DEV_DB_DATABASE=merchbay_admin_dev
DEV_DB_USERNAME=dev_user
DEV_DB_PASSWORD=dev_password
```
### Production Secrets
```
PROD_DEPLOY_HOST=prod-server-ip
PROD_DEPLOY_USER=deploy
PROD_DEPLOY_SSH_KEY=-----BEGIN RSA PRIVATE KEY-----...
PROD_DEPLOY_PORT=22
PROD_DB_HOST=prod-mysql-host
PROD_DB_PORT=3306
PROD_DB_DATABASE=merchbay_admin
PROD_DB_USERNAME=prod_user
PROD_DB_PASSWORD=prod_password
```
### Shared Secrets (if using same server)
```
DEPLOY_HOST=your-server-ip
DEPLOY_USER=deploy
DEPLOY_SSH_KEY=-----BEGIN RSA PRIVATE KEY-----...
DEPLOY_PORT=22
```
## Verification
### Check Development SSL
```bash
# Check certificate issuer (should be Let's Encrypt)
echo | openssl s_client -servername dev.merchbay.app -connect dev.merchbay.app:443 2>/dev/null | openssl x509 -noout -issuer
# Should show: issuer=C = US, O = Let's Encrypt, CN = R3
```
### Check Production SSL
```bash
# Check certificate issuer (should be your SSL provider)
echo | openssl s_client -servername merchbay.app -connect merchbay.app:443 2>/dev/null | openssl x509 -noout -issuer
# Should show your paid SSL provider
# Check certificate validity
echo | openssl s_client -servername merchbay.app -connect merchbay.app:443 2>/dev/null | openssl x509 -noout -dates
```
### Verify in Browser
1. Visit https://dev.merchbay.app
- Certificate should be issued by "Let's Encrypt Authority X3"
2. Visit https://merchbay.app
- Certificate should be issued by your paid SSL provider
## Troubleshooting
### Development SSL Not Working
```bash
# Check Let's Encrypt logs
docker logs traefik | grep letsencrypt
# Verify acme.json permissions
ls -l /path/to/letsencrypt/acme.json
# Should be: -rw------- (600)
# Check DNS
dig dev.merchbay.app +short
# Should return your server IP
```
### Production SSL Not Working
```bash
# Verify Traefik can read certificates
docker exec traefik ls -l /certs/
# Check dynamic configuration is loaded
docker exec traefik cat /etc/traefik/dynamic/certs.yml
# Verify certificate format
openssl x509 -in /opt/traefik/certs/merchbay.app-fullchain.crt -text -noout
# Check private key
openssl rsa -in /opt/traefik/certs/merchbay.app.key -check
```
### Certificate Mismatch
```bash
# Verify certificate and key match
openssl x509 -noout -modulus -in /opt/traefik/certs/merchbay.app.crt | openssl md5
openssl rsa -noout -modulus -in /opt/traefik/certs/merchbay.app.key | openssl md5
# Both should output the same hash
```
## Renewing Certificates
### Development (Let's Encrypt)
Automatic renewal every 60 days. No action needed.
### Production (Paid SSL)
Before certificate expiration:
1. Download new certificate from your SSL provider
2. Update files in `/opt/traefik/certs/`
3. Restart Traefik: `docker compose restart traefik`
4. Verify: `curl -vI https://merchbay.app`
## DNS Configuration
### Development
```
Type: A
Name: dev.merchbay.app
Value: YOUR_SERVER_IP
TTL: 3600
```
### Production
```
Type: A
Name: merchbay.app (or @)
Value: YOUR_SERVER_IP
TTL: 3600
Type: A
Name: www.merchbay.app
Value: YOUR_SERVER_IP
TTL: 3600
```
## Security Best Practices
1. **Keep certificates private**: Never commit SSL keys to git
2. **Use strong permissions**: 600 for private keys, 644 for certificates
3. **Monitor expiration**: Set reminders 30 days before expiration
4. **Use HSTS**: Add header after SSL is working correctly
5. **Enable OCSP stapling**: Improves SSL performance
6. **Regular updates**: Keep Traefik updated for security patches

105
deploy.sh Executable file
View File

@@ -0,0 +1,105 @@
#!/bin/bash
# Deployment script for MerchBay Admin
# This can be used for manual deployments or called from CI/CD
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Configuration
DEPLOY_DIR="${DEPLOY_DIR:-/var/www/merchbay_admin}"
APP_NAME="merchbay_admin"
BACKUP_DIR="${DEPLOY_DIR}/backups"
echo -e "${GREEN}Starting deployment of ${APP_NAME}...${NC}"
# Create backup directory
mkdir -p "${BACKUP_DIR}"
# Backup current state
if [ -d "${DEPLOY_DIR}/storage" ]; then
echo -e "${YELLOW}Creating backup...${NC}"
BACKUP_FILE="${BACKUP_DIR}/backup_$(date +%Y%m%d_%H%M%S).tar.gz"
tar -czf "${BACKUP_FILE}" -C "${DEPLOY_DIR}" storage .env 2>/dev/null || true
echo -e "${GREEN}Backup created: ${BACKUP_FILE}${NC}"
fi
# Navigate to deployment directory
cd "${DEPLOY_DIR}"
# Check if .env exists
if [ ! -f .env ]; then
echo -e "${RED}Error: .env file not found!${NC}"
echo "Please create .env file with database credentials"
exit 1
fi
# Pull latest code (if using git deployment)
if [ -d .git ]; then
echo -e "${YELLOW}Pulling latest code...${NC}"
git pull origin main || git pull origin master
fi
# Stop existing containers
echo -e "${YELLOW}Stopping existing containers...${NC}"
docker compose down
# Build new image
echo -e "${YELLOW}Building Docker image...${NC}"
docker compose build --no-cache
# Start containers
echo -e "${YELLOW}Starting containers...${NC}"
docker compose up -d
# Wait for container to be ready
echo -e "${YELLOW}Waiting for application to start...${NC}"
sleep 10
# Run migrations
echo -e "${YELLOW}Running database migrations...${NC}"
docker compose exec -T app php artisan migrate --force || {
echo -e "${RED}Migration failed! Rolling back...${NC}"
docker compose down
exit 1
}
# Clear and cache configuration
echo -e "${YELLOW}Optimizing application...${NC}"
docker compose exec -T app php artisan config:cache
docker compose exec -T app php artisan route:cache
docker compose exec -T app php artisan view:cache
# Set proper permissions
echo -e "${YELLOW}Setting permissions...${NC}"
docker compose exec -T app chown -R www-data:www-data /var/www/html/storage
docker compose exec -T app chmod -R 755 /var/www/html/storage
# Health check
echo -e "${YELLOW}Performing health check...${NC}"
sleep 5
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080)
if [ "$HTTP_STATUS" -eq 200 ]; then
echo -e "${GREEN}✓ Deployment successful! Application is running.${NC}"
echo -e "${GREEN}✓ HTTP Status: ${HTTP_STATUS}${NC}"
# Keep only last 5 backups
cd "${BACKUP_DIR}"
ls -t backup_*.tar.gz 2>/dev/null | tail -n +6 | xargs -r rm
echo -e "${GREEN}Deployment completed successfully!${NC}"
else
echo -e "${RED}✗ Health check failed! HTTP Status: ${HTTP_STATUS}${NC}"
echo -e "${YELLOW}Check logs: docker compose logs app${NC}"
exit 1
fi
# Display container status
echo -e "${YELLOW}Container status:${NC}"
docker compose ps

View File

@@ -0,0 +1,48 @@
version: '3.8'
# Development Stack - Portainer Configuration
# Deploy this through Portainer UI: Stacks -> Add Stack -> Web Editor
# Branch: dev
services:
app:
image: merchbay_admin:dev
container_name: merchbay_admin_dev
restart: unless-stopped
environment:
- APP_ENV=staging
- APP_DEBUG=false
- APP_URL=https://dev.merchbay.app
- DB_CONNECTION=mysql
- DB_HOST=your-mysql-host
- DB_PORT=3306
- DB_DATABASE=merchbay_admin_dev
- DB_USERNAME=your-mysql-user
- DB_PASSWORD=your-mysql-password
volumes:
- app_storage_dev:/var/www/html/storage
- app_uploads_dev:/var/www/html/public/uploads
labels:
- "traefik.enable=true"
- "traefik.http.routers.merchbay-admin-dev.rule=Host(`dev.merchbay.app`)"
- "traefik.http.routers.merchbay-admin-dev.entrypoints=websecure"
- "traefik.http.routers.merchbay-admin-dev.tls=true"
- "traefik.http.routers.merchbay-admin-dev.tls.certresolver=letsencrypt"
- "traefik.http.services.merchbay-admin-dev.loadbalancer.server.port=80"
# HTTP to HTTPS redirect
- "traefik.http.routers.merchbay-admin-dev-http.rule=Host(`dev.merchbay.app`)"
- "traefik.http.routers.merchbay-admin-dev-http.entrypoints=web"
- "traefik.http.routers.merchbay-admin-dev-http.middlewares=https-redirect"
- "traefik.http.middlewares.https-redirect.redirectscheme.scheme=https"
networks:
- traefik-public
volumes:
app_storage_dev:
driver: local
app_uploads_dev:
driver: local
networks:
traefik-public:
external: true

View File

@@ -0,0 +1,51 @@
version: '3.8'
# Production Stack - Portainer Configuration
# Deploy this through Portainer UI: Stacks -> Add Stack -> Web Editor
# Branch: main - Uses paid SSL certificate
services:
app:
image: merchbay_admin:latest
container_name: merchbay_admin_prod
restart: unless-stopped
environment:
- APP_ENV=production
- APP_DEBUG=false
- APP_URL=https://merchbay.app
- DB_CONNECTION=mysql
- DB_HOST=your-mysql-host
- DB_PORT=3306
- DB_DATABASE=merchbay_admin
- DB_USERNAME=your-mysql-user
- DB_PASSWORD=your-mysql-password
volumes:
- app_storage:/var/www/html/storage
- app_uploads:/var/www/html/public/uploads
# Mount paid SSL certificates
- /path/to/ssl/certs:/etc/ssl/certs:ro
labels:
- "traefik.enable=true"
- "traefik.http.routers.merchbay-admin.rule=Host(`merchbay.app`)"
- "traefik.http.routers.merchbay-admin.entrypoints=websecure"
- "traefik.http.routers.merchbay-admin.tls=true"
# Use custom TLS configuration (file provider for paid cert)
# Ensure Traefik has file provider configured with your paid SSL cert
- "traefik.http.services.merchbay-admin.loadbalancer.server.port=80"
# HTTP to HTTPS redirect
- "traefik.http.routers.merchbay-admin-http.rule=Host(`merchbay.app`)"
- "traefik.http.routers.merchbay-admin-http.entrypoints=web"
- "traefik.http.routers.merchbay-admin-http.middlewares=https-redirect"
- "traefik.http.middlewares.https-redirect.redirectscheme.scheme=https"
networks:
- traefik-public
volumes:
app_storage:
driver: local
app_uploads:
driver: local
networks:
traefik-public:
external: true

View File

@@ -7,11 +7,10 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
container_name: merchbay_admin_app container_name: merchbay_admin_app
restart: unless-stopped restart: unless-stopped
ports:
- "8080:80"
environment: environment:
- APP_ENV=production - APP_ENV=${APP_ENV:-production}
- APP_DEBUG=false - APP_DEBUG=${APP_DEBUG:-false}
- APP_URL=${APP_URL:-http://localhost}
- DB_CONNECTION=mysql - DB_CONNECTION=mysql
- DB_HOST=${DB_HOST:-localhost} - DB_HOST=${DB_HOST:-localhost}
- DB_PORT=${DB_PORT:-3306} - DB_PORT=${DB_PORT:-3306}
@@ -21,4 +20,24 @@ services:
volumes: volumes:
- ./storage:/var/www/html/storage - ./storage:/var/www/html/storage
- ./public/uploads:/var/www/html/public/uploads - ./public/uploads:/var/www/html/public/uploads
network_mode: bridge labels:
- "traefik.enable=true"
- "traefik.http.routers.merchbay-admin.rule=Host(`${DOMAIN:-merchbay-admin.localhost}`)"
- "traefik.http.routers.merchbay-admin.entrypoints=websecure"
- "traefik.http.routers.merchbay-admin.tls=true"
- "traefik.http.routers.merchbay-admin.tls.certresolver=letsencrypt"
- "traefik.http.services.merchbay-admin.loadbalancer.server.port=80"
# HTTP to HTTPS redirect
- "traefik.http.routers.merchbay-admin-http.rule=Host(`${DOMAIN:-merchbay-admin.localhost}`)"
- "traefik.http.routers.merchbay-admin-http.entrypoints=web"
- "traefik.http.routers.merchbay-admin-http.middlewares=https-redirect"
- "traefik.http.middlewares.https-redirect.redirectscheme.scheme=https"
networks:
- traefik-public
- default
networks:
traefik-public:
external: true
default:
driver: bridge