diff --git a/.gitea/workflows/build-push.yml b/.gitea/workflows/build-push.yml new file mode 100644 index 0000000..3acd8f4 --- /dev/null +++ b/.gitea/workflows/build-push.yml @@ -0,0 +1,57 @@ +name: Build and Push Docker Image + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + tag: + description: 'Docker image tag (e.g., v1.0.0, latest)' + required: false + default: 'latest' + push_to_registry: + description: 'Push to registry?' + required: false + default: 'true' + +jobs: + build-and-push: + runs-on: ubuntu-latest + container: + image: catthehacker/ubuntu:act-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 + 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:buildcache + cache-to: type=registry,ref=${{ secrets.DOCKER_REGISTRY_URL }}/merchbay:buildcache,mode=max diff --git a/.gitea/workflows/deploy-dev.yml b/.gitea/workflows/deploy-dev.yml new file mode 100644 index 0000000..2242bf7 --- /dev/null +++ b/.gitea/workflows/deploy-dev.yml @@ -0,0 +1,150 @@ +name: Deploy Development + +on: + push: + branches: + - dev + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + container: + image: catthehacker/ubuntu:act-latest + + steps: + # 1️⃣ Checkout code + - name: Checkout code + shell: sh + run: | + git clone $GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git /workspace/repo + cd /workspace/repo + git checkout $GITHUB_REF_NAME + + # 2️⃣ Build image + - name: Build Docker image + shell: sh + run: | + cd /workspace/repo + docker build -t merchbay:dev . + docker save merchbay:dev | gzip > merchbay_dev.tar.gz + + # 3️⃣ Setup SSH + - name: Setup SSH + shell: sh + env: + DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} + DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} + run: | + mkdir -p ~/.ssh + chmod 700 ~/.ssh + echo "$DEPLOY_SSH_KEY" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts + + # 4️⃣ Upload artifacts + - name: Upload image and compose + shell: sh + env: + DEPLOY_USER: ${{ secrets.DEPLOY_USER }} + DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} + run: | + scp -i ~/.ssh/id_ed25519 \ + /workspace/repo/merchbay_dev.tar.gz \ + /workspace/repo/docker-compose.yml \ + ${DEPLOY_USER}@${DEPLOY_HOST}:/tmp/ + + # 5️⃣ Deploy on server + - name: Deploy on server + shell: sh + env: + DEPLOY_USER: ${{ secrets.DEPLOY_USER }} + DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} + + run: | + ssh -i ~/.ssh/id_ed25519 $DEPLOY_USER@$DEPLOY_HOST << 'EOF' + set -e + + DEPLOY_DIR="/var/www/apps/merchbay_dev" + mkdir -p "$DEPLOY_DIR" + + echo "📦 Loading image" + docker load < /tmp/merchbay_dev.tar.gz + + echo "📄 Updating compose file" + cp /tmp/docker-compose.yml "$DEPLOY_DIR/" + + 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_*, IMAGES_DIRECTORY, PRODUCTION_PRIVATE_SERVER" + 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 + + echo "⏳ Waiting for app container" + sleep 15 + + if docker ps --format '{{.Names}}' | grep -q merchbay_app; then + 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_dev.tar.gz /tmp/docker-compose.yml + docker image prune -f + + echo "✅ Deployment completed" + EOF + + + # 6️⃣ Health check + - name: Health check + shell: sh + run: | + echo "⏳ Waiting for app to be ready..." + sleep 20 + + echo "🔍 Testing health check (ignoring SSL cert for now)..." + HTTP_CODE=$(curl -k -s -o /dev/null -w "%{http_code}" --max-time 30 https://dev.merchbay.app || echo "000") + + if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "302" ] || [ "$HTTP_CODE" = "301" ]; then + echo "✅ Health check passed! (HTTP $HTTP_CODE)" + echo "⚠️ Note: Using -k to ignore SSL cert. Check Traefik logs if cert not ready." + 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://dev.merchbay.app" + exit 1 + fi diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..859274b --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,81 @@ +name: Deploy Production + +on: + push: + branches: + - main + - master + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + container: + image: catthehacker/ubuntu:act-latest + + steps: + - name: Checkout code + shell: sh + run: | + git clone $GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git /workspace/repo || true + cd /workspace/repo + git fetch origin $GITHUB_REF_NAME + git checkout $GITHUB_REF_NAME + git pull origin $GITHUB_REF_NAME + + - name: Build Docker Image + shell: sh + run: | + cd /workspace/repo + docker build -t merchbay:latest . + docker save merchbay:latest | gzip > merchbay.tar.gz + + - name: Setup SSH and Deploy + shell: sh + run: | + mkdir -p ~/.ssh + chmod 700 ~/.ssh + echo "$PROD_DEPLOY_SSH_KEY" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + ssh-keygen -y -f ~/.ssh/deploy_key > /dev/null 2>&1 || { echo "Error: Invalid SSH key format"; exit 1; } + + cd /workspace/repo + scp -o StrictHostKeyChecking=no -i ~/.ssh/deploy_key merchbay.tar.gz docker-compose.yml "$PROD_DEPLOY_USER@$PROD_DEPLOY_HOST:/tmp/" + + ssh -o StrictHostKeyChecking=no -i ~/.ssh/deploy_key "$PROD_DEPLOY_USER@$PROD_DEPLOY_HOST" " + DEPLOY_DIR='/var/www/merchbay' + mkdir -p \$DEPLOY_DIR + cd /tmp + docker load < merchbay.tar.gz + cp docker-compose.yml \$DEPLOY_DIR/ + cd \$DEPLOY_DIR + + # .env file should already exist on server with all required variables + # Required: DB_*, IMAGES_DIRECTORY, PRODUCTION_PRIVATE_SERVER + # If it doesn't exist, deployment will fail (this is intentional for security) + + docker compose down || true + docker image prune -f + docker network inspect traefik-public >/dev/null 2>&1 || docker network create traefik-public + export DOMAIN=merchbay.app + export APP_URL=https://merchbay.app + docker compose up -d + sleep 10 + docker compose exec -T app php artisan migrate --force + 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 + rm -f /tmp/merchbay.tar.gz /tmp/docker-compose.yml + 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 + shell: sh + run: | + sleep 10 + curl -f https://merchbay.app || exit 1 diff --git a/Dockerfile b/Dockerfile index 8a08879..0fe21a9 100755 --- a/Dockerfile +++ b/Dockerfile @@ -1,46 +1,71 @@ -# Use the official PHP image based on Alpine Linux -FROM php:5.6-fpm-alpine +# Use PHP 7.4 with Apache (compatible with Laravel 5.2 and ARM64) +FROM php:7.4-apache -# Install system dependencies and PHP extensions -RUN apk --update --no-cache add \ - nginx \ +# Install system dependencies +RUN apt-get update && apt-get install -y \ + git \ + curl \ libpng-dev \ - libjpeg-turbo-dev \ - freetype-dev \ + libonig-dev \ + libxml2-dev \ libzip-dev \ zip \ unzip \ - libmcrypt-dev \ + libfreetype6-dev \ + libjpeg62-turbo-dev \ + openssh-client \ && docker-php-ext-configure gd --with-freetype --with-jpeg \ - && docker-php-ext-install gd pdo pdo_mysql zip mcrypt + && docker-php-ext-install -j$(nproc) gd -# Set the working directory in the container -WORKDIR /var/www +# Install PHP extensions +RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath zip -# Clear cache -# RUN apt-get clean && rm -rf /var/lib/apt/lists/* - -# Copy the Laravel application files to the container -COPY . . - -# Set appropriate permissions for Laravel storage and bootstrap cache -RUN chown -R www-data:www-data storage bootstrap +# Enable Apache mod_rewrite +RUN a2enmod rewrite # Install Composer -RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer +COPY --from=composer:1.10 /usr/bin/composer /usr/bin/composer -# Install Laravel dependencies -RUN composer install --no-plugins --no-scripts +# Set working directory +WORKDIR /var/www/html -# Generate Laravel application key +# Copy existing application directory contents +COPY . /var/www/html + +# Create storage directories and set permissions +RUN mkdir -p storage/framework/views \ + storage/framework/cache \ + storage/framework/sessions \ + storage/logs \ + bootstrap/cache + +# Set proper ownership and permissions +RUN chown -R www-data:www-data /var/www/html \ + && chmod -R 775 /var/www/html/storage \ + && chmod -R 775 /var/www/html/bootstrap/cache + +# Create .env file if it doesn't exist +RUN if [ ! -f .env ]; then cp .env.example .env; fi + +# Install PHP dependencies without running post-install scripts +RUN composer install --no-dev --no-scripts --no-interaction + +# Generate application key RUN php artisan key:generate -# Create directory for the socket and set permissions -RUN mkdir -p /run/php && chown www-data:www-data /run/php +# Optimize autoloader +RUN composer dump-autoload --optimize --no-dev -# Copy the www.conf file to PHP-FPM pool.d directory -# COPY www.conf /usr/local/etc/php-fpm.d/www.conf +# Configure Apache DocumentRoot to point to Laravel's public directory +ENV APACHE_DOCUMENT_ROOT=/var/www/html/public +RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf +RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf -# Expose port 9000 and start php-fpm server -EXPOSE 9000 -CMD ["php-fpm"] \ No newline at end of file +# Suppress Apache ServerName warning +RUN echo "ServerName localhost" >> /etc/apache2/apache2.conf + +# Expose port 80 +EXPOSE 80 + +# Start Apache +CMD ["apache2-foreground"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 1107cbf..7f223f9 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,45 +1,44 @@ -version: '3' services: - - #PHP Service app: - build: - context: . - dockerfile: Dockerfile - image: digitalocean.com/php - container_name: app + image: merchbay:dev + container_name: merchbay_app restart: unless-stopped - tty: true environment: - SERVICE_NAME: app - SERVICE_TAGS: dev - working_dir: /var/www + - APP_ENV=${APP_ENV:-production} + - APP_DEBUG=${APP_DEBUG:-false} + - APP_URL=${APP_URL:-http://localhost} + - DB_CONNECTION=mysql + - DB_HOST=${DB_HOST} + - DB_PORT=${DB_PORT:-3306} + - DB_DATABASE=${DB_DATABASE} + - DB_USERNAME=${DB_USERNAME} + - DB_PASSWORD=${DB_PASSWORD} + - IMAGES_DIRECTORY=${IMAGES_DIRECTORY} + - PRODUCTION_PRIVATE_SERVER=${PRODUCTION_PRIVATE_SERVER} volumes: - - ./:/var/www - - ./php/local.ini:/usr/local/etc/php/conf.d/local.ini + - ./storage:/var/www/html/storage + - ./public/uploads:/var/www/html/public/uploads + labels: + - "traefik.enable=true" + - "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: - - app-network + - traefik-public + - crew-app-net + - default - #Nginx Service - webserver: - image: nginx:alpine - container_name: webserver - restart: unless-stopped - tty: true - ports: - - "10003:80" - - "10443:443" - volumes: - - ./:/var/www - - ./nginx/conf.d/:/etc/nginx/conf.d/ - networks: - - app-network - -#Docker Networks networks: - app-network: - driver: bridge -#Volumes -volumes: - dbdata: - driver: local \ No newline at end of file + traefik-public: + external: true + crew-app-net: + external: true + default: + driver: bridge \ No newline at end of file