Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -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."
32 changes: 30 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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):
Expand Down
19 changes: 19 additions & 0 deletions front/public/.htaccess
Original file line number Diff line number Diff line change
@@ -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.

<IfModule mod_rewrite.c>
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]
</IfModule>