diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..c0126ab --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,77 @@ +name: Deploy + +on: + push: + branches: [master] + +concurrency: + group: deploy-production + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + + env: + SSH_HOST: ssh-cgicertif.alwaysdata.net + BACKEND_REMOTE_PATH: /home/cgicertif/passwd/back + FRONTEND_REMOTE_PATH: /home/cgicertif/www/passwd + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: npm + cache-dependency-path: | + back/package-lock.json + front/package-lock.json + + - name: Install backend dependencies + run: npm ci --omit=dev + working-directory: back + + - name: Build frontend + run: | + npm ci + VITE_API_URL=/api npm run build + working-directory: front + + - name: Configure SSH + run: | + mkdir -p ~/.ssh + printf '%s\n' "${{ secrets.ALWAYSDATA_SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + ssh-keyscan -H "$SSH_HOST" >> ~/.ssh/known_hosts + + - name: Deploy backend + run: | + rsync -avz --delete \ + --exclude '.env' \ + --exclude '.htaccess' \ + -e "ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=yes" \ + back/ \ + "${{ secrets.ALWAYSDATA_SSH_USER }}@${SSH_HOST}:${BACKEND_REMOTE_PATH}/" + + - name: Deploy frontend + run: | + rsync -avz --delete \ + -e "ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=yes" \ + front/dist/ \ + "${{ secrets.ALWAYSDATA_SSH_USER }}@${SSH_HOST}:${FRONTEND_REMOTE_PATH}/" + + - name: Restart Node.js site + run: | + status="$(curl --silent --show-error --write-out '%{http_code}' --output /dev/null \ + --request POST \ + --user "${{ secrets.ALWAYSDATA_API_KEY }} account=${{ secrets.ALWAYSDATA_ACCOUNT }}:${{ secrets.ALWAYSDATA_API_PASSWORD }}" \ + --data '' \ + "https://api.alwaysdata.com/v1/site/${{ secrets.ALWAYSDATA_BACKEND_SITE_ID }}/restart/")" + if [ "$status" != "204" ]; then + echo "Site restart failed with HTTP $status" + exit 1 + fi + echo "Site restarted successfully." diff --git a/README.md b/README.md index 9529be4..a0b2a0e 100644 --- a/README.md +++ b/README.md @@ -71,13 +71,15 @@ The app runs on port `3000` and proxies `/api` requests to the backend. ## Deploy instructions -### Backend +### Manual deploy + +#### Backend - Copy `back/` to your server - Configure `.env` for your MariaDB 11.8 instance - Run with Node.js 24: `npm start` -### Frontend +#### Frontend Build with the production API URL: @@ -88,6 +90,32 @@ VITE_API_URL=/api npm run build Copy `front/dist/` to your static web root (for example `www/passwd`). +The production `.htaccess` for the static site lives in `front/public/.htaccess` (copied into `dist/` on build). Uncomment the API proxy rule and set your Node.js internal port from AlwaysData → Web → Sites → Environment. + +### Automated deploy (GitHub Actions) + +Pushing to `master` runs the **Deploy** workflow (build, rsync over SSH, AlwaysData site restart). + +#### GitHub secrets + +| Secret | Description | +|--------|-------------| +| `ALWAYSDATA_SSH_PRIVATE_KEY` | SSH private key (public key added in AlwaysData → Remote access → SSH keys) | +| `ALWAYSDATA_SSH_USER` | SSH account name (e.g. `cgicertif`) | +| `ALWAYSDATA_API_KEY` | API key from AlwaysData profile | +| `ALWAYSDATA_ACCOUNT` | AlwaysData account name | +| `ALWAYSDATA_API_PASSWORD` | Account password (used for API basic auth) | +| `ALWAYSDATA_BACKEND_SITE_ID` | Numeric site ID of the Node.js backend (Web → Sites) | + +#### Remote paths + +| Component | Path on server | +|-----------|----------------| +| Backend | `/home/cgicertif/passwd/back` | +| Frontend | `/home/cgicertif/www/passwd` | + +The deploy workflow preserves the server-side `back/.env` and `back/.htaccess` (excluded from rsync). + ## Automated verification Run the full end-to-end check (MariaDB setup, frontend tests/build, API integration, frontend preview): diff --git a/front/public/.htaccess b/front/public/.htaccess new file mode 100644 index 0000000..38a69a1 --- /dev/null +++ b/front/public/.htaccess @@ -0,0 +1,19 @@ +# AlwaysData Apache — static site at www/passwd +# +# SPA routing: serve index.html for client-side routes (React Router paths). +# API proxy: uncomment and set NODE_INTERNAL_PORT to the port shown in +# AlwaysData → Web → Sites → your Node.js site → Environment. + + + RewriteEngine On + RewriteBase / + + # --- API proxy (uncomment and set your Node.js internal port) --- + # RewriteRule ^api/(.*)$ http://127.0.0.1:NODE_INTERNAL_PORT/api/$1 [P,L] + + # SPA fallback + RewriteRule ^index\.html$ - [L] + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule . /index.html [L] +