Merge branch 'feature/updates_gitea_workflow' of ssh://gitea.slpsoftware.nl:2224/SLP_Software/SlpSoftware into feature/updates_gitea_workflow

This commit is contained in:
2026-07-24 21:44:53 +02:00
17 changed files with 814 additions and 134 deletions
@@ -0,0 +1,148 @@
name: Continuous Integration
on:
workflow_dispatch: {}
pull_request:
types: [opened, synchronize, reopened]
push:
branches: [master]
# Herbruikbare instellingen voor deze workflow. Pas deze aan op één plek als
# de Node/pnpm-versie, de artifact-naam/pad of de testdeploy-bestemming
# wijzigt. Let op: de env-context is NIET beschikbaar in de `with:`-inputs
# van een aangeroepen reusable workflow (zie de `config`-job hieronder, die
# dit oplost door deze waarden via job-outputs door te geven aan de
# `deploy-test`-job).
env:
NODE_VERSION: '20'
PNPM_VERSION: '9'
ARTIFACT_NAME: dist
ARTIFACT_PATH: dist/
DEPLOY_ENVIRONMENT: test
DEPLOY_PATH: /html/test/slpsoftware
jobs:
# Geeft de env-variabelen hierboven door als job-outputs, zodat ze ook
# gebruikt kunnen worden in de `with:`-sectie van de `deploy-test`-job
# hieronder (waar de env-context zelf niet beschikbaar is, omdat dat een
# aanroep naar een reusable workflow is).
config:
runs-on: ubuntu-latest
outputs:
artifact_name: ${{ steps.set.outputs.artifact_name }}
deploy_environment: ${{ steps.set.outputs.deploy_environment }}
deploy_path: ${{ steps.set.outputs.deploy_path }}
steps:
- id: set
run: |
echo "artifact_name=${{ env.ARTIFACT_NAME }}" >> "$GITHUB_OUTPUT"
echo "deploy_environment=${{ env.DEPLOY_ENVIRONMENT }}" >> "$GITHUB_OUTPUT"
echo "deploy_path=${{ env.DEPLOY_PATH }}" >> "$GITHUB_OUTPUT"
prepare:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: Get pnpm store directory
id: pnpm-store
run: echo "path=$(pnpm store path)" >> "$GITHUB_OUTPUT"
- name: Cache pnpm store
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: pnpm-${{ hashFiles('pnpm-lock.yaml') }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
build:
needs: prepare
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: Get pnpm store directory
id: pnpm-store
run: echo "path=$(pnpm store path)" >> "$GITHUB_OUTPUT"
- name: Restore pnpm store
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: pnpm-${{ hashFiles('pnpm-lock.yaml') }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm run build
- name: Upload build artifact
uses: actions/upload-artifact@v3
with:
name: ${{ env.ARTIFACT_NAME }}
path: ${{ env.ARTIFACT_PATH }}
retention-days: 1
test:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: Get pnpm store directory
id: pnpm-store
run: echo "path=$(pnpm store path)" >> "$GITHUB_OUTPUT"
- name: Restore pnpm store
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: pnpm-${{ hashFiles('pnpm-lock.yaml') }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Lint
run: pnpm run lint
- name: Unit tests
run: pnpm run test
deploy-test:
needs: [build, test, config]
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/master')
uses: ./.gitea/workflows/deploy.yaml
secrets: inherit
# Deze waarden komen uit het `env:`-blok bovenaan dit bestand, via de
# `config`-job (die ze doorgeeft als job-outputs). Pas de waarden dus aan
# in het `env:`-blok, niet hier.
with:
artifact_name: ${{ needs.config.outputs.artifact_name }}
environment: ${{ needs.config.outputs.deploy_environment }}
deploy_path: ${{ needs.config.outputs.deploy_path }}
+46
View File
@@ -0,0 +1,46 @@
name: Deploy
on:
workflow_call:
inputs:
artifact_name:
required: true
type: string
environment:
required: true
type: string
deploy_path:
required: true
type: string
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Download build artifact
uses: actions/download-artifact@v3
with:
name: ${{ inputs.artifact_name }}
path: ${{ inputs.artifact_name }}
# Uploadt de inhoud van dist/ via SCP (over SSH) naar de webroot van de
# test-omgeving (een Raspberry Pi achter een andere Raspberry Pi met
# nginx reverse proxy - zie operations/deployment/nginx/ voor de
# bijbehorende nginx-voorbeeldconfiguratie). Inloggegevens komen uit
# Gitea Actions Secrets (wachtwoord-login voor nu; zie
# deployment-instructions.md voor hoe je later naar een SSH-key omzet).
#
# Let op: dit gebeurt via een gewone shell-stap in plaats van de
# appleboy/scp-action Docker-container-action. Die laatste faalt op
# deze runner met "failed to attach to container: unable to upgrade
# to tcp, received 409" - een bekende beperking van Podman's
# Docker-compatibele API, die het attach/log-streaming-mechanisme
# voor containeracties niet volledig ondersteunt. Een scp-commando
# in een normale run-stap heeft die geneste container niet nodig.
- name: Upload dist to ${{ inputs.environment }} web server via SCP
run: |
sudo apt-get update && sudo apt-get install -y sshpass
sshpass -p "${{ secrets.PI_MAIN_PASSWORD }}" scp \
-P ${{ secrets.PI_MAIN_PORT }} \
-o StrictHostKeyChecking=no \
-r ${{ inputs.artifact_name }}/* \
${{ secrets.PI_MAIN_USERNAME }}@${{ secrets.PI_MAIN_HOST }}:${{ inputs.deploy_path }}
-77
View File
@@ -1,77 +0,0 @@
name: Build, Test and Package Release
# Pipeline (see aidlc-docs/features/react-frontend/operations/deployment/deployment-instructions.md):
# - Runs automatically on every pull request (merge request) as a build/test/lint gate.
# - Can also be triggered manually from Gitea Actions, picking the branch/ref to run against.
# - The "deploy" job currently only packages dist/ as a downloadable artifact
# (no automatic upload to a host yet) and only runs for manual (workflow_dispatch) runs.
# Typically run on a `release/*` branch once you've manually created it for a release.
# - Uploaded artifacts expire after 1 day (retention-days: 1).
# - Gitea Actions has no GitLab/Azure DevOps-style approval gate (a single job with a
# "manual" play button inside an already-running pipeline). The closest equivalent is
# workflow_dispatch: the deploy job only runs when someone presses "Run workflow" in
# the Gitea Actions UI, i.e. a one-click manual approval.
on:
workflow_dispatch: {}
pull_request:
types: [opened, synchronize, reopened]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Lint
run: pnpm run lint
- name: Unit tests
run: pnpm run test
- name: Build
run: pnpm run build
- name: Upload build artifact
uses: actions/upload-artifact@v3
with:
name: dist
path: dist/
retention-days: 1
deploy:
needs: build-and-test
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- name: Download build artifact
uses: actions/download-artifact@v3
with:
name: dist
path: dist
# Placeholder deploy step: for now this only re-publishes dist/ as a clearly
# named, ready-to-download artifact. Once the hosting/upload method (FTP/SFTP/
# other) is finalized, replace this step with the actual upload and this
# comment can be removed.
# TODO: Test workflow
- name: Package release artifact
uses: actions/upload-artifact@v3
with:
name: release-dist
path: dist/
retention-days: 1
+1 -1
View File
@@ -2,4 +2,4 @@
| Feature | Status | Branch | Affected Components | Session Start | | Feature | Status | Branch | Affected Components | Session Start |
|---------|--------|--------|---------------------|---------------| |---------|--------|--------|---------------------|---------------|
| react-frontend | 🟡 Operations | master | React frontend (SLP Software marketing site) | 2026-07-18 | | react-frontend | ✅ Complete | master | React frontend (SLP Software marketing site) | 2026-07-18 |
@@ -5,7 +5,7 @@
- **Feature Slug**: react-frontend - **Feature Slug**: react-frontend
- **Project Type**: Greenfield - **Project Type**: Greenfield
- **Start Date**: 2026-07-18T00:37:00Z - **Start Date**: 2026-07-18T00:37:00Z
- **Current Stage**: OPERATIONS - Deployment Setup Complete, awaiting approval - **Current Stage**: OPERATIONS - Monitoring Setup (in progress)
- **Branch**: master - **Branch**: master
## Workspace State ## Workspace State
@@ -37,6 +37,10 @@
- **Included**: Yes - **Included**: Yes
- **Method**: Manually-triggered Gitea Actions pipeline (build/test/lint gate + artifact packaging); actual host upload is manual for now, to be automated later - **Method**: Manually-triggered Gitea Actions pipeline (build/test/lint gate + artifact packaging); actual host upload is manual for now, to be automated later
### Monitoring Setup
- **Included**: Yes
- **Approach**: Combination (Logging + Dashboards; Alerting explicitly out of scope)
## Execution Plan Summary ## Execution Plan Summary
- **Total Stages**: 10 (2 skipped, 8 executed) - **Total Stages**: 10 (2 skipped, 8 executed)
- **Stages to Execute**: Functional Design, NFR Requirements, NFR Design, Code Generation, Build and Test, Deployment Setup, Monitoring Setup, Production Readiness Validation - **Stages to Execute**: Functional Design, NFR Requirements, NFR Design, Code Generation, Build and Test, Deployment Setup, Monitoring Setup, Production Readiness Validation
@@ -62,11 +66,11 @@
### 🟡 OPERATIONS PHASE ### 🟡 OPERATIONS PHASE
- [x] Deployment Setup (EXECUTE) - Completed 2026-07-19 - [x] Deployment Setup (EXECUTE) - Completed 2026-07-19
- [ ] Monitoring Setup (EXECUTE) - [x] Monitoring Setup (EXECUTE) - Completed 2026-07-24
- [ ] Production Readiness Validation (EXECUTE) - [x] Production Readiness Validation (EXECUTE) - Completed 2026-07-24
## Current Status ## Current Status
- **Lifecycle Phase**: OPERATIONS - **Lifecycle Phase**: Complete
- **Current Stage**: Deployment Setup Complete, awaiting approval - **Current Stage**: Complete
- **Next Stage**: Monitoring Setup (Operations Phase) - **Next Stage**: N/A
- **Status**: Ready to proceed - **Status**: Workflow complete
+145
View File
@@ -287,3 +287,148 @@
**Context**: Deployment Setup (OPERATIONS PHASE) - requested change addressed, re-presenting completion message. **Context**: Deployment Setup (OPERATIONS PHASE) - requested change addressed, re-presenting completion message.
--- ---
## Deployment Setup - Approved
**Timestamp**: 2026-07-19T23:20:00Z
**User Input**: "Commit en ga door naar de volgende fase"
**AI Response**: "Deployment Setup approved. Committed all deployment artifacts (.gitea/workflows/deploy.yml, deployment-plan.md, deployment-instructions.md, rollback-plan.md) to git. Proceeding to Monitoring Setup (OPERATIONS PHASE)."
**Context**: Deployment Setup stage complete and approved; moving to Monitoring Setup.
---
## Monitoring Setup - Answers Reviewed, Clarification Needed
**Timestamp**: 2026-07-21T00:16:00Z
**User Input**: "ga verder met de aidlc workflow. ik heb de vragen over het monitoring plan beantwoord" (answers filled in directly in operations/plans/monitoring-setup-plan.md: Q1:A, Q2:A, Q3:C, Q4:S, Q5:C, Q6:C, Q7:A)
**AI Response**: "Detected a contradiction: Question 2 answer is 'A' (Logging only), yet Question 4, 5 and 6 (only relevant for Alerting/Dashboards) were also answered, implying the user may actually want Alerting and/or Dashboards too. Also detected an invalid answer on Question 4 ('S' is not one of the offered A-E options). Created operations/plans/monitoring-setup-clarification-questions.md with 2 clarification questions to resolve both issues before generating monitoring artifacts."
**Context**: Monitoring Setup (OPERATIONS PHASE) - Step 5, contradiction/ambiguity detection; awaiting user clarification before proceeding to Step 6 (artifact generation).
---
## Monitoring Setup - Clarification Answers Received, Artifacts Generated
**Timestamp**: 2026-07-24T15:00:00Z
**User Input**: "Ik heb de vragen ingevuld" (clarification answers filled in directly in operations/plans/monitoring-setup-clarification-questions.md: Clarification Q1:C (Logging + Dashboards, no Alerting), Clarification Q2:D (not applicable / decide later)).
**AI Response**: "Read and validated the clarification answers: chosen approach resolved to Logging + Dashboards, Alerting explicitly out of scope. Generated operations/monitoring/monitoring-plan.md (decision, chosen approaches, rationale, open items) and operations/monitoring/monitoring-setup.md (concrete logging destination options, analytics + uptime dashboard tool options and setup steps). Updated aidlc-state.md: Operations Configuration > Monitoring Setup (Included: Yes, Approach: Combination) and Stage Progress marked complete."
**Context**: Monitoring Setup (OPERATIONS PHASE) - Step 6/7 artifacts generated; presenting completion message, awaiting approval before Production Readiness Validation.
---
## Production Readiness Validation - Complete
**Timestamp**: 2026-07-24T15:12:00Z
**User Input**: "ga verder" (approval to proceed to Production Readiness Validation)
**AI Response**: "Reviewed Deployment Setup and Monitoring Setup outcomes; no blocking gaps requiring new user questions were found (remaining items are already-tracked open follow-ups: host upload automation, logging destination, analytics/uptime tool selection, formal Lighthouse run). Generated operations/production-readiness-checklist.md with Overall Readiness = 'Yes with caveats'. Marked Production Readiness Validation and the overall feature as Complete in aidlc-state.md, and updated active-features.md status to ✅ Complete."
**Overall Readiness**: Yes with caveats
**Feature Status**: Complete
---
## Post-Completion Follow-up: Automated Test-Environment Deploy
**Timestamp**: 2026-07-24T15:20:00Z
**User Input**: "Ik wil de voorgestelde vervolgstappen uitvoeren beginnend bij de deployment. Voor nu wil ik in ieder geval kunnen deployen naar een testomgeving. Die testomgeving is een web-server gehost op mijn eigen raspberry pi achter andere raspberry pi met een nginx reversed proxy" — followed by clarification on auth (password for now), connection details (internal IP `192.168.1.103`, port `2224`, user `webadmin`), deploy path (`/html/test/slpsoftware`), nginx setup needed, and trigger (keep existing manual + auto-on-merge-to-master trigger).
**AI Response**: "Implemented the previously open 'automatic deploy' follow-up from `deployment-plan.md`, for a test environment only. Fixed the CI push trigger (`main` -> `master`, matching the actual default branch). Extended `.gitea/workflows/deploy.yaml` with a real `appleboy/scp-action` upload step (password-based SSH auth via new secrets `PI_TEST_HOST`/`PI_TEST_PORT`/`PI_TEST_USERNAME`/`PI_TEST_PASSWORD`) uploading `dist/` to `/html/test/slpsoftware`. Renamed/reconfigured the calling job to `deploy-test` (`environment: test`, `secrets: inherit`). Added example nginx configs (`operations/deployment/nginx/webserver-nginx.conf.example` and `reverse-proxy-nginx.conf.example`) for the two-Pi setup. Rewrote `deployment-plan.md`, `deployment-instructions.md`, and `rollback-plan.md` to describe the new automated test deploy, required secrets, and manual nginx/Pi setup steps."
**Context**: Post-workflow follow-up (feature already marked Complete); addresses the "Open Item — Automatic Deploy Not Yet Implemented" from the original `deployment-plan.md`. Changes not yet committed/pushed; production deploy remains a separate open item.
---
## Post-Completion Follow-up: Domain Names & SSL for Test/Production
**Timestamp**: 2026-07-24T15:48:00Z
**User Input**: "Het was misschien handiger geweest als je om onduidelijkheden had gevraagd zoals domeinnaam en hoe het zit met ssl-certificaat. De reverse-proxy Pi heeft de certificaten. Die vroeg ik altijd aan met certbot van LetsEncrypt. Die leid het verkeer door naar de webhost en de webhost handelt verder http af. domeinnaam voor de productiewebsite wordt: slpsoftware.nl. Voor de testomgeving wordt dat test.slpsoftware.nl"
**AI Response**: "Updated `nginx/reverse-proxy-nginx.conf.example` (test) to use `test.slpsoftware.nl` as `server_name`, with an HTTP server block for the Let's Encrypt ACME challenge + redirect to HTTPS, and an HTTPS server block referencing certbot-managed certificate paths (`/etc/letsencrypt/live/test.slpsoftware.nl/...`) — matching the user's existing certbot/Let's Encrypt workflow on the reverse-proxy Pi, which terminates SSL before forwarding plain HTTP to the webserver Pi. Added a new `nginx/reverse-proxy-nginx-production.conf.example` documenting the future production reverse-proxy config for `slpsoftware.nl`/`www.slpsoftware.nl` (not yet wired up, since production deploy automation is still an open item). Updated `deployment-plan.md` and `deployment-instructions.md` with the domain/DNS/certbot setup steps for both environments."
**Context**: Post-workflow follow-up (feature already marked Complete); addresses the user's feedback that domain name and SSL certificate handling should have been clarified during the earlier deployment follow-up. Changes committed on `feature/updates_gitea_workflow` (commit `1ffe884`).
---
## Post-Completion Follow-up: Webserver Port Corrected to 80
**Timestamp**: 2026-07-24T15:54:00Z
**User Input**: "Ik leid al het verkeer door naar poort 80 op de webserver pi (Pi Main)"
**AI Response**: "Corrected the assumed internal port for the webserver Pi: updated `nginx/webserver-nginx.conf.example` (`listen 192.168.1.103:80` / `listen [::]:80`) and `nginx/reverse-proxy-nginx.conf.example` (`proxy_pass http://192.168.1.103:80`), replacing the previously assumed port `8080`. No other files referenced this port."
**Context**: Post-workflow follow-up (feature already marked Complete); corrects an incorrect assumption from the earlier test-environment deploy follow-up. Changes committed on `feature/updates_gitea_workflow` (commit `fd31f02`).
---
## Post-Completion Follow-up: Secrets Renamed from PI_TEST_* to PI_MAIN_*
**Timestamp**: 2026-07-24T16:07:00Z
**User Input**: "De secrets mag je PI_MAIN_* noemen. PI_TEST is iets te specifiek want alle webhosts krijgen dezelfde gegevens. In ieder geval voor nu. Mocht dat veranderen dan komt dat later wel" (followed by "Ik zie dat je de secret namen niet hebt veranderd. Ze mogen worden aangepast van PI_TEST_HOST naar PI_MAIN_HOST enzovoorts").
**AI Response**: "Renamed all `PI_TEST_HOST`/`PI_TEST_PORT`/`PI_TEST_USERNAME`/`PI_TEST_PASSWORD` secret references to `PI_MAIN_HOST`/`PI_MAIN_PORT`/`PI_MAIN_USERNAME`/`PI_MAIN_PASSWORD` in `.gitea/workflows/deploy.yaml`, `deployment-plan.md`, and `deployment-instructions.md` (including the SSH-key future-work note, now `PI_MAIN_SSH_KEY`). Added an explanatory note that the name is intentionally host-based rather than environment-specific, since all webhosts currently share the same Pi and credentials; environment-specific secrets can be introduced later if that changes."
**Context**: Post-workflow follow-up (feature already marked Complete); addresses user feedback that the secret naming should reflect the shared webhost rather than being test-specific. Changes committed together with the follow-up below.
---
## Post-Completion Follow-up: Adjustable Values Extracted to Workflow Variables
**Timestamp**: 2026-07-24T16:16:00Z
**User Input**: "Kan het pad en eventuele andere aanpasbare waarden bovenin de workflow als variabele worden toegevoegd? Dat maakt het makkelijker en duidelijker welke waarden eventueel anders kunnen worden. Als dat niet kan met Gitea workflows laat het dan maar zitten en commit dan meteen alle changes"
**AI Response**: "Added a top-level `env:` block (`NODE_VERSION`, `PNPM_VERSION`) to `continuous_integration.yaml`, referenced from all `setup-node`/`pnpm/action-setup` steps across the `prepare`/`build`/`test` jobs. Investigated whether `deploy_path`/`environment`/`artifact_name` (the `deploy-test` job's reusable-workflow call) could be moved to the same `env:` block: this is **not possible** — GitHub/Gitea Actions does not expose the `env` context inside `jobs.<job_id>.with` when calling a reusable workflow (only within `jobs.<job_id>.steps`), so those values must stay as literal inputs in that job. Documented this limitation clearly in `deployment-instructions.md`. Committed all outstanding changes (this follow-up plus the previous `PI_MAIN_*` rename) on `feature/updates_gitea_workflow`."
**Context**: Post-workflow follow-up (feature already marked Complete); addresses user request to centralize adjustable workflow values, with an explicit Gitea/GitHub Actions limitation documented for the parts that could not be centralized.
---
## Post-Completion Follow-up: deploy_path, Artifact Name & Path Also Centralized
**Timestamp**: 2026-07-24T16:20:00Z
**User Input**: "Nou heb je alleen de versienummers in variabelen gezet, maar niet het deploy path zoals ik gevraagd heb. Ook denk ik dat de artifact name (nu: dist) en path (nu: /dist) als variabelen kunnen worden opgenomen"
**AI Response**: "Corrected the earlier conclusion: it turns out `deploy_path`/`environment`/`artifact_name` CAN be centralized, via a small `config` job that converts the top-level `env:` values into job outputs (since `needs.<job>.outputs.*` IS usable in `jobs.<job_id>.with`, unlike the `env` context directly). Extended the `env:` block in `continuous_integration.yaml` with `ARTIFACT_NAME` (`dist`), `ARTIFACT_PATH` (`dist/`), `DEPLOY_ENVIRONMENT` (`test`) and `DEPLOY_PATH` (`/html/test/slpsoftware`). Added a new `config` job (runs first) that writes these to `$GITHUB_OUTPUT`; the `build` job's artifact-upload step now uses `env.ARTIFACT_NAME`/`env.ARTIFACT_PATH`, and the `deploy-test` job now uses `needs.config.outputs.*` instead of literal values. In `deploy.yaml`, the local download path and SCP `source` now also use `inputs.artifact_name` instead of a hardcoded `dist`. Updated `deployment-instructions.md` and `deployment-plan.md` to describe the `config` job and correct the earlier (incomplete) claim that this centralization wasn't possible."
**Context**: Post-workflow follow-up (feature already marked Complete); corrects the previous follow-up which stopped short of fully centralizing the deploy destination and artifact naming. Changes not yet committed/pushed.
---
## Post-Completion Follow-up: `push` Trigger No Longer Filtered to `master`
**Timestamp**: 2026-07-24T16:27:00Z
**User Input**: "maar ik wil dat de workflow ook draait als ik een pull request update of aanmaak zoals je in de andere triggers kan zien. Moet ik die branch check niet gewoon weghalen? Hij staat namelijk ook al als losse check in de deploy stap" — followed by: "De nee-optie klopt niet. ik zie namelijk de PR niet getriggerd worden nu. Dus ik zou zeggen haal voor nu weg."
**AI Response**: "Removed the `branches: [master]` filter from the `push` trigger in `continuous_integration.yaml` (now `push: {}`), so `prepare`/`build`/`test` also run on pushes to any branch, not just `master`. The `pull_request` trigger itself was already unfiltered (`types: [opened, synchronize, reopened]`, no `branches` key) and was not the cause of PRs not triggering; the user reported PRs weren't triggering regardless, and explicitly asked to remove the redundant branch check since `deploy-test` already has its own `if`-guard restricting deploy to `master`/`workflow_dispatch`. Updated `deployment-instructions.md` and `deployment-plan.md` to reflect that build/test now runs on every push/PR, while deploy stays gated to `master`."
**Context**: Post-workflow follow-up (feature already marked Complete); simplifies trigger config per user request. Note: if PRs still don't trigger after this change, the root cause may lie elsewhere (e.g. Gitea repository Actions settings/permissions), which is outside this workflow file and should be checked separately.
---
## Post-Completion Follow-up: `push` Trigger Restored to `branches: [master]`
**Timestamp**: 2026-07-24T16:31:00Z
**User Input**: "kan die hele push trigger dan niet gewoon weg ipv Push: {} te laten staan? Nu zal het aanmaken van een PR niks doen" — followed by clarification that removing the push trigger entirely would break automatic `deploy-test` after merging to master, and the reply: "dan zou dus de push trigger met master gewoon terug kunnen. Waarom triggert de Workflow dan nu niet op de huidige PR? Kloppen de pull_request triggers dan misschien niet? Zoek uit, verander en commit de changes".
**AI Response**: "Restored `push: branches: [master]` in `continuous_integration.yaml` (reverting the previous `push: {}` change), since removing the push trigger entirely would have stopped automatic `deploy-test` after merging a PR to master, while keeping it unfiltered was unnecessary (the `pull_request` trigger already runs on every PR regardless of branch). Investigated why the PR itself wasn't triggering: confirmed (per Gitea/GitHub Actions behavior) that for `pull_request` events, the workflow definition used is the one from the PR's HEAD branch, not the base branch, so the feature branch's own (correct) `pull_request` trigger should apply. Found that `origin/master`'s copy of `continuous_integration.yaml` is still an old, stale version (`push: branches: [main]`), confirming that none of this session's workflow fixes have been merged into `master` yet — this by itself should not block PR triggering (per the HEAD-branch behavior above) but does mean `master` runs an outdated pipeline for direct pushes/merges until a PR from this feature branch is merged. Could not access the Gitea instance itself (no direct access from this session) to inspect Actions run history/settings, so could not conclusively diagnose why the PR isn't triggering — advised the user to check the PR's Actions/Checks tab in Gitea for a skipped/error state, confirm Actions is enabled for the repository, and confirm a runner is online and picking up jobs."
**Context**: Post-workflow follow-up (feature already marked Complete); reverts the previous change and documents remaining open questions about the PR triggering issue that require checking the Gitea instance directly. Committed on `feature/updates_gitea_workflow`.
---
@@ -1,36 +1,59 @@
# Deployment Instructions # Deployment Instructions
## Overview ## Overview
Deployment is currently a **manually-triggered Gitea Actions pipeline**: `.gitea/workflows/deploy.yml`. You start it yourself from the Gitea Actions UI; it runs the build/test/lint gate and then packages the `dist/` build as a downloadable artifact. There is no automatic upload to a host yet — you upload the artifact yourself for now. Deployment gebeurt via Gitea Actions, opgesplitst in twee bestanden:
- `.gitea/workflows/continuous_integration.yaml` — build/test/lint-gate, plus de `deploy-test` job.
- `.gitea/workflows/deploy.yaml` — herbruikbare workflow die `dist/` via SCP naar een omgeving uploadt.
## Pipeline File Sinds deze stap wordt er automatisch gedeployed naar een **testomgeving**: een Raspberry Pi die de site serveert via nginx, bereikbaar achter een tweede Raspberry Pi met een nginx reverse proxy.
`.gitea/workflows/deploy.yml` — triggered by `workflow_dispatch` only (no automatic trigger on push/tag). Two jobs:
- `build-and-test` — install, lint, unit tests, build; uploads `dist/` as the `dist` artifact
- `deploy` — downloads that artifact and republishes it as `release-dist` (placeholder for a future automatic upload step)
## How to Prepare and Run a Release ## Pipeline Files
- `continuous_integration.yaml` — getriggerd door `pull_request` (build/test/lint-gate, ongeacht branch), `push` naar `master` (build/test/lint-gate + `deploy-test`), en handmatig via `workflow_dispatch`.
- Heeft bovenaan een `env:`-blok met alle aanpasbare waarden op één plek: `NODE_VERSION`, `PNPM_VERSION`, `ARTIFACT_NAME` (`dist`), `ARTIFACT_PATH` (`dist/`), `DEPLOY_ENVIRONMENT` (`test`) en `DEPLOY_PATH` (`/html/test/slpsoftware`).
- `prepare``build` (uploadt de artifact, naam/pad uit `env.ARTIFACT_NAME`/`env.ARTIFACT_PATH`) → `test` (lint + unit tests)
- Een losse `config`-job zet deze `env`-waarden om in job-outputs (zie hieronder waarom dat nodig is).
- `deploy-test` (alleen bij `workflow_dispatch` of een push naar `master`) roept `deploy.yaml` aan met `artifact_name`/`environment`/`deploy_path` afkomstig van `needs.config.outputs.*` (dus indirect uit het `env:`-blok).
- `deploy.yaml` — download de artifact (naam/lokaal pad = `inputs.artifact_name`) en upload de inhoud via `appleboy/scp-action` naar de opgegeven `deploy_path` op de host uit de meegegeven secrets.
### 1. Create a release branch (manual, your own git flow) ### Waarom een aparte `config`-job in plaats van rechtstreeks het `env:`-blok?
```bash Gitea/GitHub Actions ondersteunt geen `env`-context in de `with:`-sectie waarmee een reusable workflow wordt aangeroepen (`jobs.<job_id>.with`) — dat werkt alléén binnen `jobs.<job_id>.steps`. De oplossing is een klein voorloop-job (`config`) dat de gewenste `env`-waarden via `$GITHUB_OUTPUT` naar job-outputs schrijft; die outputs (`needs.config.outputs.*`) zijn wél bruikbaar in `jobs.<job_id>.with`. Zo hoef je, om de artifact-naam/pad of de testdeploy-bestemming te wijzigen, alléén het `env:`-blok bovenaan `continuous_integration.yaml` aan te passen — niet de `deploy-test`-job zelf.
git checkout master
git pull
git checkout -b release/1.0.0
git push origin release/1.0.0
```
### 2. Trigger the pipeline manually ## Eenmalige Setup — Gitea Secrets
1. In Gitea, open the repository's **Actions** tab. Voeg deze secrets toe in Gitea: **Repository → Settings → Actions → Secrets**:
2. Select the **Build, Test and Package Release** workflow.
3. Click **Run workflow**, choose the `release/1.0.0` branch (or whichever ref you want to build), and start it.
### 3. Download and upload the artifact | Secret | Waarde |
1. Once the run finishes successfully, open the run's summary page in Gitea Actions. |---|---|
2. Download the **release-dist** artifact (a zip of the `dist/` folder). | `PI_MAIN_HOST` | Intern IP-adres van de webserver-Pi (`192.168.1.103`) |
3. Upload its contents to your host manually (e.g. via your FTP/SFTP client), using whatever hosting setup you finalize with mijnhostingpartner.nl. | `PI_MAIN_PORT` | SSH-poort (`2224`) |
| `PI_MAIN_USERNAME` | SSH-gebruikersnaam (`webadmin`) |
| `PI_MAIN_PASSWORD` | Het SSH-wachtwoord van deze gebruiker |
Deze secrets heten `PI_MAIN_*` (niet `PI_TEST_*`), omdat dezelfde Pi (Pi Main) en dezelfde inloggegevens naar verwachting ook voor toekomstige omgevingen/webhosts gebruikt worden. Mocht dat later veranderen, dan worden hiervoor alsnog omgeving-specifieke secrets geïntroduceerd.
## Eenmalige Setup — Domeinnaam & DNS
- **Test**: `test.slpsoftware.nl` → moet als DNS A-record wijzen naar het publieke IP van de reverse-proxy-Pi.
- **Productie** (nog niet automatisch gedeployed, maar domein al bekend): `slpsoftware.nl` (en `www.slpsoftware.nl`) → zelfde reverse-proxy-Pi, zodra productie wordt opgezet.
## Eenmalige Setup — nginx & SSL op de Raspberry Pi's
1. Kopieer `operations/deployment/nginx/webserver-nginx.conf.example` naar `/etc/nginx/sites-available/` op de webserver-Pi, maak een symlink in `sites-enabled/`, en herlaad nginx.
2. Kopieer `operations/deployment/nginx/reverse-proxy-nginx.conf.example` naar `/etc/nginx/sites-available/slpsoftware-test.conf` op de reverse-proxy-Pi, maak een symlink in `sites-enabled/`, en herlaad nginx. Dit bestand gebruikt al `test.slpsoftware.nl` als `server_name`.
3. Vraag op de reverse-proxy-Pi een SSL-certificaat aan met certbot (Let's Encrypt), nadat het DNS-record klopt: `sudo certbot --nginx -d test.slpsoftware.nl`. Certbot regelt automatisch de HTTPS-configuratie en de HTTP→HTTPS-redirect (net zoals je gewend bent van certbot).
4. Zorg dat de map `/html/test/slpsoftware` bestaat op de webserver-Pi en schrijfbaar is voor de gebruiker `webadmin` (bijv. `sudo mkdir -p /html/test/slpsoftware && sudo chown webadmin:webadmin /html/test/slpsoftware`).
5. Voor later, wanneer productie wordt opgezet: zie `operations/deployment/nginx/reverse-proxy-nginx-production.conf.example` (domein `slpsoftware.nl`, certbot-commando alvast gedocumenteerd).
## How to Deploy to Test
### Automatisch
Merge een pull request naar `master` — de `deploy-test` job draait dan automatisch na een groene build/test-run.
### Handmatig
1. In Gitea, open de repository's **Actions** tab.
2. Selecteer de **Continuous Integration** workflow.
3. Klik **Run workflow**, kies de gewenste branch/ref, en start.
## Verifying a Deployment ## Verifying a Deployment
1. Confirm the Gitea Actions run completed successfully (`build-and-test` and `deploy` both green). 1. Bevestig dat de Gitea Actions run succesvol is (alle jobs groen, inclusief `deploy-test`).
2. After manually uploading the artifact contents, open the live site in a browser and confirm it loads correctly (check the browser console for errors, per the manual smoke test in `construction/build-and-test/integration-test-instructions.md`). 2. Open de testomgeving in de browser (via het adres/IP dat je bij de reverse-proxy hebt ingesteld) en controleer dat de site correct laadt (check de browserconsole op fouten, zoals in de handmatige smoke test in `construction/build-and-test/integration-test-instructions.md`).
## Future Work — Automatic Upload ## Future Work
Once the hosting setup is finalized (FTP/SFTP details, and whether this frontend shares a domain/app with a future back-end and CMS front-end — see `deployment-plan.md`'s "Open Item"), replace the placeholder step in the `deploy` job with an actual upload step (e.g. an FTP/SFTP action, or a shell-based upload using `lftp`/`curl`), using Gitea Actions Secrets for credentials and Variables for host/paths. At that point, decide whether the trigger should stay manual or become automatic (e.g. on push to `release/*` or on tag). - **Van wachtwoord naar SSH-key**: vervang `password: ${{ secrets.PI_MAIN_PASSWORD }}` in `deploy.yaml` door `key: ${{ secrets.PI_MAIN_SSH_KEY }}` (een nieuwe secret met de private key-inhoud), en zet de bijbehorende public key in `~/.ssh/authorized_keys` van de `webadmin`-gebruiker op de webserver-Pi. Verwijder daarna het wachtwoord-secret.
- **Productie-omgeving**: voeg een `deploy-production`-job toe zodra de definitieve productiehosting bekend is (zie `deployment-plan.md`'s "Open Item"), en pas `nginx/reverse-proxy-nginx-production.conf.example` (domein `slpsoftware.nl`) toe zodra de webserver-locatie voor productie vastligt.
@@ -1,30 +1,43 @@
# Deployment Plan # Deployment Plan
## Chosen Method ## Chosen Method
**A manually-triggered Gitea Actions pipeline** (`workflow_dispatch`) that runs the build/test/lint gate and then packages the `dist/` build as a downloadable artifact. This is an interim setup, deliberately simplified per user feedback after the initial plan: no automatic upload to a host yet — that will be added later once the hosting details are finalized (see "Open Item" below). **Gitea Actions**, opgesplitst in twee workflow-bestanden:
- `.gitea/workflows/continuous_integration.yaml` — draait de build/test/lint-gate, automatisch bij elke pull request (ongeacht branch) en bij elke push/merge naar `master`, of handmatig via `workflow_dispatch`. De `deploy-test`-job zelf blijft daarnaast ook beperkt tot `master`/`workflow_dispatch` via een eigen `if`-check.
- `.gitea/workflows/deploy.yaml` — een herbruikbare (`workflow_call`) job die de `dist/` build via SCP (over SSH) uploadt naar de webroot van een omgeving.
Sinds deze stap is er een echte, geautomatiseerde upload naar een **testomgeving**: een Raspberry Pi die de statische site serveert via nginx, achter een tweede Raspberry Pi die als nginx reverse proxy fungeert.
## How It Works ## How It Works
1. You manually create a `release/*` branch when you want to cut a release (e.g. `release/1.0.0`), following your own git flow. 1. Bij elke pull request draait automatisch de build/test/lint-gate (`prepare``build``test`), zodat merge requests direct gevalideerd worden.
2. You manually trigger the pipeline (`Build, Test and Package Release`) from Gitea's Actions UI, picking the branch/ref to run it against (typically the release branch). 2. Zodra een pull request naar `master` gemerged wordt (of de workflow handmatig via `workflow_dispatch` gestart wordt), draait aanvullend de `deploy-test` job.
3. The pipeline always runs `build-and-test` first (install, lint, unit tests, build) as a gate. 3. `deploy-test` roept de herbruikbare `deploy.yaml` workflow aan met `artifact_name`/`environment`/`deploy_path`, en geeft via `secrets: inherit` de Pi-inloggegevens door. Deze drie waarden (samen met de artifact-naam/pad die de `build`-job gebruikt) staan als variabelen in het `env:`-blok bovenaan `continuous_integration.yaml` (`ARTIFACT_NAME`, `ARTIFACT_PATH`, `DEPLOY_ENVIRONMENT`, `DEPLOY_PATH`), en worden via een kleine `config`-job als job-outputs doorgegeven aan `deploy-test` (nodig omdat de `env`-context zelf niet werkt in de `with:`-sectie van een reusable-workflow-aanroep).
4. If that succeeds, the `deploy` job downloads the build and republishes it as a `release-dist` artifact, ready to download and upload to the host by hand for now. 4. `deploy.yaml` downloadt de artifact en uploadt de inhoud via SCP (wachtwoord-login) naar de webserver-Pi op het interne netwerk (`192.168.1.103`, poort `2224`).
5. nginx op de webserver-Pi serveert de bestanden vanaf `/html/test/slpsoftware`; de reverse-proxy-Pi stuurt binnenkomend verkeer door naar deze webserver-Pi. Voorbeeldconfiguraties staan in `operations/deployment/nginx/`.
6. De reverse-proxy-Pi is ook verantwoordelijk voor SSL: certificaten worden net als voorheen aangevraagd via certbot (Let's Encrypt) en HTTP-verkeer wordt doorverwezen naar HTTPS.
## Environments ## Environments
Simplified to a single flow for now (superseding the earlier dev/staging/production proposal, which is dropped per user feedback — **the staging/production auto-deploy jobs have been removed**): - **Test** (nieuw, geautomatiseerd): zoals hierboven beschreven — de enige omgeving die op dit moment daadwerkelijk automatisch gedeployed wordt. Domeinnaam: `test.slpsoftware.nl` (SSL via certbot op de reverse-proxy-Pi).
- No automated environment deployments exist yet. - **Productie**: nog niet geautomatiseerd. Zodra de definitieve productiehosting bekend is, kan een vergelijkbare `deploy-production`-job worden toegevoegd die `deploy.yaml` aanroept met `environment: production` en de productie-secrets/pad. Domeinnaam ligt al vast: `slpsoftware.nl` (SSL eveneens via certbot; zie `nginx/reverse-proxy-nginx-production.conf.example`).
- Releases are prepared manually via `release/*` branches; the artifact produced by the pipeline is uploaded to the host manually until an automatic deploy step is added.
## Automation Level ## Automation Level
Partially automated (intentionally, for now): build/test/lint and artifact packaging are automated once triggered, but **triggering itself is manual** (`workflow_dispatch`), and the actual upload to the host is also manual (download the `release-dist` artifact from the Gitea Actions run, then upload it yourself, e.g. via an FTP client). This is a deliberate interim step until the hosting setup is finalized. Volledig geautomatiseerd voor de testomgeving: build, test, lint én upload naar de test-Pi gebeuren zonder handmatige tussenstap, zodra er gemerged wordt naar `master` (of handmatig getriggerd wordt). Alleen productie is nog niet geautomatiseerd.
## Rollback Strategy ## Rollback Strategy
Version control via git branches/releases: keep `release/*` branches (or tag them) so a previous release's artifact can be regenerated by re-running the pipeline against that branch/tag. See `rollback-plan.md`. Zie `rollback-plan.md` — voor de testomgeving kan een eerdere commit/branch opnieuw gebouwd en geüpload worden door de workflow opnieuw te triggeren.
## Secrets & Configuration ## Secrets & Configuration
Not yet needed — there is no automated upload step, so no host credentials are configured in Gitea Actions at this stage. When automatic deployment is added later, credentials should be stored as Gitea Actions Secrets (never committed to the repo), consistent with the original decision. Voor de testomgeving zijn de volgende Gitea Actions Secrets (repository-niveau) vereist:
- `PI_MAIN_HOST``192.168.1.103` (intern IP van de webserver-Pi)
- `PI_MAIN_PORT``2224`
- `PI_MAIN_USERNAME``webadmin`
- `PI_MAIN_PASSWORD` — het SSH-wachtwoord van deze gebruiker
## Open Item — Automatic Deploy Not Yet Implemented Deze secrets heten bewust `PI_MAIN_*` in plaats van `PI_TEST_*`: alle webhosts gebruiken op dit moment dezelfde inloggegevens (dezelfde Pi), dus de naam is niet omgeving-specifiek. Mocht dat in de toekomst veranderen, dan worden alsnog omgeving-specifieke secrets geïntroduceerd.
The `deploy` job currently only re-publishes the build as an artifact. Once you decide on the final hosting setup (FTP/SFTP details, and whether this frontend shares a domain/app with a future back-end and CMS front-end), the `deploy` job in `.gitea/workflows/deploy.yml` should be extended to actually upload `dist/` to the host (e.g. via an FTP/SFTP action or a shell-based upload step), reusing Gitea Actions Secrets/Variables for credentials and paths.
Dit is bewust wachtwoord-authenticatie (voor nu, zoals gekozen), zodat de testomgeving snel werkend is. Zie "Future Work" in `deployment-instructions.md` voor de overstap naar SSH-key-authenticatie.
## Open Item — Productie-deploy Nog Niet Geautomatiseerd
Zodra de definitieve productiehosting bekend is (en of dit dezelfde soort Raspberry Pi-opstelling is, of een externe hostingpartij), voeg een `deploy-production`-job toe aan `continuous_integration.yaml` die `deploy.yaml` aanroept met `environment: production`. Zolang het dezelfde webhost (Pi Main) blijft, kunnen de bestaande `PI_MAIN_*` secrets hergebruikt worden; pas dit pas aan naar omgeving-specifieke secrets als productie daadwerkelijk op een andere host komt. Domeinnaam (`slpsoftware.nl`) en SSL-aanpak (certbot/Let's Encrypt op de reverse-proxy-Pi) liggen al vast, zie `nginx/reverse-proxy-nginx-production.conf.example`.
## Verified Build Prerequisite ## Verified Build Prerequisite
This plan relies on the Build and Test stage already being verified (`construction/build-and-test/build-and-test-summary.md`): `pnpm run build` produces a static `dist/` bundle with no server-side requirements. Dit plan bouwt voort op de Build and Test-stage (`construction/build-and-test/build-and-test-summary.md`): `pnpm run build` produceert een statische `dist/`-bundel zonder server-side vereisten, geschikt om direct door nginx geserveerd te worden.
@@ -0,0 +1,55 @@
# Voorbeeldconfiguratie voor de nginx reverse proxy op de Raspberry Pi voor de
# PRODUCTIEOMGEVING, bereikbaar via het domein slpsoftware.nl.
#
# LET OP: dit bestand is nog niet in gebruik — de productie-deploy is nog niet
# geautomatiseerd (zie deployment-plan.md's "Open Item — Productie-deploy Nog
# Niet Geautomatiseerd"). Dit voorbeeld ligt hier alvast klaar zodat het
# domein en SSL-certificaat (net als bij de testomgeving via certbot) bekend
# zijn wanneer de productie-deploy wordt opgezet. Vervang <PROD_WEBSERVER_IP>
# en <PROD_WEBSERVER_POORT> door de daadwerkelijke waarden zodra die bekend
# zijn (mogelijk dezelfde webserver-Pi op een ander poortnummer/pad, of een
# aparte Pi/host).
#
# Kopieer dit bestand handmatig naar bijvoorbeeld
# /etc/nginx/sites-available/slpsoftware-production.conf op de reverse-proxy-Pi,
# maak een symlink in sites-enabled, en herlaad nginx.
#
# SSL-certificaat aanvragen met certbot (Let's Encrypt), nadat het DNS-record
# voor slpsoftware.nl naar het publieke IP van deze Pi wijst:
# sudo certbot --nginx -d slpsoftware.nl -d www.slpsoftware.nl
server {
listen 80;
listen [::]:80;
server_name slpsoftware.nl www.slpsoftware.nl;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name slpsoftware.nl www.slpsoftware.nl;
ssl_certificate /etc/letsencrypt/live/slpsoftware.nl/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/slpsoftware.nl/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://<PROD_WEBSERVER_IP>:<PROD_WEBSERVER_POORT>;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
@@ -0,0 +1,56 @@
# Voorbeeldconfiguratie voor de nginx reverse proxy op de andere Raspberry Pi
# (degene die wél vanaf het internet bereikbaar is en het binnenkomende verkeer
# doorstuurt naar de webserver-Pi op 192.168.1.103:80) — voor de TESTOMGEVING,
# bereikbaar via het domein test.slpsoftware.nl.
#
# Kopieer dit bestand handmatig naar bijvoorbeeld
# /etc/nginx/sites-available/slpsoftware-test.conf op de reverse-proxy-Pi,
# maak een symlink in sites-enabled, en herlaad nginx.
#
# SSL-certificaat: net als voor de productiesite wordt dit aangevraagd met
# certbot (Let's Encrypt) rechtstreeks op deze reverse-proxy-Pi, bijvoorbeeld:
# sudo certbot --nginx -d test.slpsoftware.nl
# Zorg dat het DNS-record voor test.slpsoftware.nl al naar het publieke IP van
# deze Pi wijst voordat je certbot draait. Certbot vult automatisch het
# onderstaande `listen 443 ssl` server-block aan (of maakt het aan) en
# schrijft de HTTP-server hieronder om naar een 301-redirect. Het onderstaande
# is dus vooral illustratief voor de eindsituatie.
# HTTP: alleen gebruikt voor de Let's Encrypt ACME-challenge en om door te
# verwijzen naar HTTPS. Na het draaien van certbot ziet dit block er zo uit.
server {
listen 80;
listen [::]:80;
server_name test.slpsoftware.nl;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS: het daadwerkelijke reverse-proxy-verkeer naar de webserver-Pi.
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name test.slpsoftware.nl;
ssl_certificate /etc/letsencrypt/live/test.slpsoftware.nl/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/test.slpsoftware.nl/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://192.168.1.103:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
@@ -0,0 +1,39 @@
# Voorbeeldconfiguratie voor de nginx server op de Raspberry Pi die de
# gebouwde react-frontend (dist/) daadwerkelijk serveert.
#
# Deze Pi is NIET rechtstreeks vanaf het internet bereikbaar; de andere
# Raspberry Pi (met de nginx reverse proxy, zie reverse-proxy-nginx.conf.example)
# stuurt inkomend verkeer door naar deze server op het interne netwerk.
#
# Kopieer dit bestand handmatig naar bijvoorbeeld
# /etc/nginx/sites-available/slpsoftware-test.conf op de webserver-Pi,
# maak een symlink in sites-enabled, en herlaad nginx (`sudo nginx -t && sudo systemctl reload nginx`).
server {
# Alleen luisteren op het interne (LAN) IP-adres van deze Pi, niet op 0.0.0.0,
# zodat deze poort niet per ongeluk vanaf buiten het netwerk bereikbaar is.
listen 192.168.1.103:80;
listen [::]:80;
server_name _;
root /html/test/slpsoftware;
index index.html;
# De React-app gebruikt client-side routing (React Router), dus onbekende
# paden moeten terugvallen op index.html in plaats van een 404 te geven.
location / {
try_files $uri $uri/ /index.html;
}
# Statische assets mogen langer gecachet worden.
location ~* \.(?:css|js|svg|png|jpg|jpeg|gif|ico|woff2?)$ {
try_files $uri =404;
expires 7d;
add_header Cache-Control "public";
}
# Doorgeven van het echte client-IP en protocol, afkomstig van de reverse proxy.
set_real_ip_from 192.168.1.0/24;
real_ip_header X-Forwarded-For;
}
@@ -1,24 +1,23 @@
# Rollback Plan # Rollback Plan
## Strategy ## Strategy
Since there is no automated upload step yet (deployment is manual — see `deployment-instructions.md`), "rollback" today means re-uploading a previous known-good build to the host by hand, rather than the pipeline reverting anything automatically. Sinds deze stap wordt de testomgeving automatisch gedeployed door de `deploy-test` job (zie `deployment-instructions.md`). "Rollback" betekent hier: de workflow opnieuw laten draaien tegen een eerdere, bekend-goede commit/branch, zodat die build automatisch opnieuw naar de test-Pi wordt geüpload en de huidige (foutieve) bestanden overschrijft.
## Rolling Back the Live Site ## Rolling Back the Live Site (Test-omgeving)
1. Identify the previous good `release/*` branch (or its last commit) that was actually uploaded to the host. 1. Identificeer de laatste bekend-goede commit op `master` (of een eerdere `release/*`-branch/tag) die succesvol gedeployed was.
2. Re-run the **Build, Test and Package Release** workflow manually against that branch/commit in Gitea Actions. 2. Trigger de **Continuous Integration** workflow handmatig (`workflow_dispatch`) tegen die commit/branch/tag in Gitea Actions.
3. Download the resulting `release-dist` artifact. 3. De `deploy-test` job uploadt automatisch de resulterende `dist/`-build naar `/html/test/slpsoftware` op de webserver-Pi, en overschrijft daarmee de huidige (foutieve) bestanden.
4. Upload its contents to the host manually, overwriting the current (bad) files — the same manual step used for a normal deployment. 4. Verify de live testomgeving reflecteert de teruggedraaide versie (via het adres achter de reverse proxy).
5. Verify the live site reflects the rolled-back version.
## Keeping Rollback Possible ## Keeping Rollback Possible
- Do not delete `release/*` branches after they've been deployed; keep them (or tag them, e.g. `release/1.0.0``v1.0.0`) so you can always re-run the pipeline against a known-good point. - Do not delete `release/*` branches (or tags) after they've been deployed, so you can always re-run the pipeline against a known-good point.
- Optionally keep a local/manual copy of the last few uploaded `dist/` artifacts as an extra safety net, since Gitea Actions artifacts expire after the configured retention period (currently 30 days, see `.gitea/workflows/deploy.yml`). - Optionally keep a local/manual copy of the last few uploaded `dist/` artifacts as an extra safety net, since Gitea Actions artifacts expire after the configured retention period (currently 1 day, see `.gitea/workflows/continuous_integration.yaml`).
## Database / Stateful Rollback Considerations ## Database / Stateful Rollback Considerations
Not applicable — this unit (`react-frontend-app`) is a static marketing site with no database and no server-side state. There is nothing to roll back beyond the static files themselves. Not applicable — this unit (`react-frontend-app`) is a static marketing site with no database and no server-side state. There is nothing to roll back beyond the static files themselves.
## Future Work ## Future Work
Once an automatic upload step is added (see `deployment-plan.md`'s "Open Item"), this rollback plan should be revisited: at that point, rollback can likely be automated too (e.g. re-triggering the pipeline for a previous branch/tag and letting it redeploy automatically, instead of a manual file upload). Zodra ook productie geautomatiseerd wordt (zie `deployment-plan.md`'s "Open Item"), moet dit rollback-plan uitgebreid worden met een vergelijkbare procedure voor de `deploy-production`-job.
## Post-Rollback Checklist ## Post-Rollback Checklist
- [ ] Confirm the Gitea Actions run for the rollback build completed successfully - [ ] Confirm the Gitea Actions run for the rollback build completed successfully
@@ -0,0 +1,34 @@
# Monitoring Plan
## Context
This feature is a static marketing website (`dist/` bundle, no back-end, no database) currently deployed manually via a build artifact (see `operations/deployment/deployment-plan.md`). There is no existing monitoring/logging infrastructure in this project yet.
## Decision
Monitoring/observability is **included** for this feature (Question 1 = A / clarified further via Clarification Question 1).
Initial answers to the monitoring plan were contradictory: Question 2 selected "Logging" only (A), yet the alerting/dashboard follow-up questions (4, 5, 6) were also answered, and the answer to Question 4 (`S`) was not a valid option. A clarification round was run (`operations/plans/monitoring-setup-clarification-questions.md`) to resolve this before generating artifacts.
**Clarified answer**: **Logging + Dashboards** (Clarification Question 1 = C). Alerting is explicitly **out of scope** for this feature at this time (Clarification Question 2 = D, "not needed yet / decide later" — consistent with not choosing Alerting in Clarification Question 1).
## Chosen Approach(es)
### Logging
Client-side errors (JavaScript crashes, broken links) should be logged, but the concrete destination is not yet decided (original Question 3 = C, "not yet determined"). This is tracked as an **open action item** below rather than blocking this stage.
### Dashboards
A combination of:
- **Website analytics** (visitors, page views, basic engagement) — e.g. a simple/free tool such as Plausible, Umami, or Google Analytics/Search Console.
- **Uptime dashboard** (site reachability) — e.g. an external monitoring service such as UptimeRobot or Better Uptime.
(Original Question 6 = C, "Both (analytics + uptime dashboard)".)
## Explicitly Out of Scope
- **Alerting/notifications**: not requested. If the uptime dashboard tool supports basic notifications (e.g. UptimeRobot's own e-mail alert on downtime), that MAY be enabled opportunistically as part of dashboard setup, but no dedicated alerting channel, escalation policy, or alert-on-error-rate logic is designed or required here.
- **Reuse of existing infrastructure**: this feature does not plug into any pre-existing shared monitoring (original Question 7 = A) — there is none yet. Should a shared back-end/CMS monitoring stack be introduced later, this can be revisited.
## Open Action Items
1. **Decide logging destination**: choose between "browser console only" (no central storage, manual debugging) or a free/low-cost external error-tracking service (e.g. Sentry free tier) once this becomes a priority. Until decided, `monitoring-setup.md` documents both options so either can be adopted without re-doing this stage.
2. **Pick concrete analytics + uptime tools**: `monitoring-setup.md` lists candidate free-tier tools; final tool selection/account creation is a manual follow-up outside this workflow (no code changes required to swap providers, since neither is wired into the codebase yet beyond an optional embed snippet).
## Rationale
Given this is a simple static marketing site with no backend and no existing monitoring, the aim is lightweight, low/no-cost observability: enough to know if the site is down (uptime) and how it's being used (analytics), plus a documented (if not yet finalized) path for capturing client-side errors. Alerting was deliberately left out to avoid over-engineering a notification pipeline before there's a concrete trigger/audience for it.
@@ -0,0 +1,62 @@
# Monitoring Setup Instructions
Concrete setup steps for the approaches chosen in `monitoring-plan.md`: **Logging** and **Dashboards** (no Alerting).
## Logging
### What to log
- Uncaught JavaScript errors / exceptions (the app already has an `ErrorBoundary` component from Code Generation — this is the natural hook point).
- Broken/failed navigation (e.g. an unexpected router error).
- No user PII, form input, or sensitive data should ever be logged — this is a public marketing site, but keep this discipline regardless.
### Destination — open decision
The destination was not finalized (original Question 3 = C). Two supported options, either of which can be adopted later without further design work:
**Option 1: Browser console only (default today)**
- No code changes needed — errors already surface via `console.error` inside the existing `ErrorBoundary`.
- Zero cost, but not centrally visible; only useful for manual debugging (e.g. via a user's screenshot or a support request).
**Option 2: External error-tracking service (e.g. Sentry free tier)**
- When decided, add `@sentry/react` as a dependency, initialize it once in the app entry point (e.g. `src/main.tsx`) with the project DSN, and report caught errors from the `ErrorBoundary`'s `componentDidCatch`/`onError` hook to Sentry in addition to the console.
- Store the DSN as a Gitea Actions variable (or a build-time `.env` value, since it's not a secret — Sentry DSNs are safe to expose client-side) and inject it via Vite's `import.meta.env`.
- Free tier limits (error volume, retention) are typically sufficient for a low-traffic marketing site.
**Log level strategy**: only errors are logged (no verbose/info-level client logging) — this is a static site with no meaningful "business events" beyond page views, which are covered by analytics (see Dashboards below), not logging.
## Dashboards
### Website analytics
Pick one (all have generous free tiers suitable for a small marketing site):
| Option | Notes |
|---|---|
| Plausible / Umami | Privacy-friendly, lightweight, no cookie banner typically required; self-hosted or low-cost hosted tier |
| Google Analytics (GA4) / Search Console | Free, widely known, but heavier script and involves third-party data sharing (cookie/consent implications) |
**Setup (once a tool is picked)**:
1. Create an account/site entry with the chosen provider and obtain the tracking snippet or `<script>` tag.
2. Add the snippet to `index.html` (or load it conditionally in `src/main.tsx`) — this is a documentation/config task, not something the current codebase needs restructuring for.
3. Key metrics to surface: unique visitors, page views per route (Home, Packages, etc. — see `frontend-components.md`), and referral sources.
### Uptime dashboard
Pick one:
| Option | Notes |
|---|---|
| UptimeRobot | Free tier: up to 50 monitors, 5-minute check interval, optional e-mail notification on downtime (opportunistic, not a designed alerting feature per `monitoring-plan.md`) |
| Better Uptime | Free tier available; similar capability, nicer public status page option |
**Setup (once a tool is picked)**:
1. Register the production URL (once hosting is finalized — see `operations/deployment/deployment-plan.md` "Open Item") as an HTTP(S) monitor, checking for a `200` response.
2. Optional: publish a public status page if desired for transparency to visitors.
3. Key metric to surface: uptime percentage / current status.
## Summary Table
| Concern | Approach | Status |
|---|---|---|
| Client-side errors | Logging (console today; Sentry free tier optional later) | Destination open item |
| Visitor/usage insight | Analytics dashboard (Plausible/Umami/GA4) | Tool selection open item |
| Site reachability | Uptime dashboard (UptimeRobot/Better Uptime) | Tool selection + production URL open item |
| Alerting | Out of scope | Not configured |
| Shared infrastructure reuse | Out of scope | None exists yet |
@@ -0,0 +1,31 @@
# Monitoring Setup Clarification Questions
Ik heb je antwoorden in `monitoring-setup-plan.md` bekeken en twee dingen gevonden die ik moet verduidelijken voordat ik de monitoring-artefacten kan opstellen.
## Contradictie 1: Gekozen aanpak vs. beantwoorde vervolgvragen
Bij **Question 2 (Monitoring-aanpak)** heb je **A) Logging** gekozen, wat zou betekenen dat alleen Question 3 (logging-bestemming) relevant is. Maar je hebt ook Question 4, 5 en 6 beantwoord — deze zijn alleen relevant als je óók Alerting en/of Dashboards wilt. Dit is tegenstrijdig: kies je alleen Logging, of wil je ook Alerting en Dashboards?
### Clarification Question 1
Welke monitoring-aanpak(en) wil je daadwerkelijk voor deze feature?
A) Alleen Logging (Question 4, 5 en 6 worden dan genegeerd)
B) Logging + Alerting (geen Dashboards)
C) Logging + Dashboards (geen Alerting)
D) Een combinatie van Logging, Alerting én Dashboards
E) Anders (beschrijf na de [Answer]: tag hieronder)
[Answer]: C
## Ambiguïteit 1: Ongeldig antwoord op Question 4
Je antwoord op **Question 4 (Alerting — kanaal)** was `S`, wat geen geldige optie is (de opties waren A t/m E). Ik kan hier niet uit afleiden welk kanaal je bedoelt.
### Clarification Question 2
Via welk kanaal wil je gewaarschuwd worden als er iets misgaat (bijv. de site is onbereikbaar)? (Alleen relevant als je bij Clarification Question 1 voor Alerting kiest.)
A) E-mail
B) Slack of Teams
C) Sms/telefonische melding
D) Nog niet nodig / later bepalen
E) Anders (beschrijf na de [Answer]: tag hieronder)
[Answer]: D
@@ -0,0 +1,78 @@
# Monitoring Setup Questions
Context: deze feature is een statische marketing-website (`dist/` bundel, geen back-end, geen database) die voorlopig handmatig via een build-artifact naar een host geüpload wordt (zie `operations/deployment/deployment-plan.md`). Er is nog geen bestaande monitoring-/logging-infrastructuur in dit project.
Beantwoord elke vraag door de letter van je keuze na de `[Answer]:` tag in te vullen.
## Question 1: Include Monitoring Setup?
Wil je dat monitoring/observability voor deze feature wordt opgezet als onderdeel van deze workflow?
A) Ja — help me met het opzetten van monitoring
B) Nee — deze feature hergebruikt bestaande gedeelde monitoring, of monitoring is voorlopig niet nodig
C) Niet zeker — stel een aanpak voor op basis van het project en ik beslis dan
X) Anders (beschrijf na de [Answer]: tag hieronder)
[Answer]: A
## Question 2: Monitoring-aanpak
Welke vormen van monitoring wil je voor deze feature? (Kies de dichtstbijzijnde match — je kunt aanpakken combineren in je antwoord.)
A) Logging — gestructureerde applicatielogs (fouten, belangrijke gebeurtenissen)
B) Alerting/notificaties — automatische meldingen als er iets misgaat (e-mail, Slack, PagerDuty, sms)
C) Dashboards — visueel overzicht van statistieken/gezondheid (bijv. bezoekers, foutpercentage, laadtijd)
D) Een combinatie van bovenstaande
E) Anders (beschrijf na de [Answer]: tag hieronder)
[Answer]: A
## Question 3: Logging — bestemming
Als je logging wilt: waar moeten client-side fouten (bijv. JavaScript-crashes, gebroken links) naartoe gelogd worden?
A) Alleen browserconsole (geen centrale opslag, puur voor handmatig debuggen)
B) Een gratis/eenvoudige externe foutregistratieservice (bijv. Sentry free tier)
C) Nog niet bepaald — vermeld dit als open actiepunt in de instructies
D) Anders (beschrijf na de [Answer]: tag hieronder)
[Answer]: C
## Question 4: Alerting — kanaal (alleen relevant als je alerting wilt)
Via welk kanaal wil je gewaarschuwd worden als er iets misgaat (bijv. de site is onbereikbaar)?
A) E-mail
B) Slack of Teams
C) Sms/telefonische melding
D) Nog niet nodig / later bepalen
E) Anders (beschrijf na de [Answer]: tag hieronder)
[Answer]: S
## Question 5: Alerting — waarop reageren (alleen relevant als je alerting wilt)
Waar moet een alert op afgaan?
A) De site is onbereikbaar (uptime-check faalt)
B) Er treden veel JavaScript-fouten op bij bezoekers
C) Beide (uptime + fouten)
D) Anders (beschrijf na de [Answer]: tag hieronder)
[Answer]:C
## Question 6: Dashboards (alleen relevant als je dashboards wilt)
Welk soort dashboard/tool heeft de voorkeur voor een overzicht van bezoekers/gezondheid van de site?
A) Eenvoudige, gratis website-analytics (bijv. Plausible, Umami, of een gratis Google Analytics/Search Console-achtig overzicht)
B) Uptime-dashboard van een externe monitoringdienst (bijv. UptimeRobot, Better Uptime)
C) Beide (analytics + uptime-dashboard)
D) Nog niet nodig / later bepalen
E) Anders (beschrijf na de [Answer]: tag hieronder)
[Answer]:C
## Question 7: Hergebruik van bestaande infrastructuur
Moet deze feature aansluiten op monitoring die je elders al gebruikt (bijv. voor een ander project of de toekomstige back-end/CMS), in plaats van iets nieuws op te zetten?
A) Nee, dit is voorlopig losstaand — later evalueren als de back-end/CMS er is
B) Ja, ik wil dit alvast voorbereiden op gedeelde monitoring met de toekomstige back-end/CMS
C) Nog niet van toepassing — ik heb nog geen bestaande monitoring-infrastructuur
D) Anders (beschrijf na de [Answer]: tag hieronder)
[Answer]:A
@@ -0,0 +1,24 @@
# Production Readiness Checklist
## Deployment
- **Status**: Configured
- **Method**: Manually-triggered Gitea Actions pipeline (`workflow_dispatch`, also runs automatically as a build/test/lint gate on pull requests) that packages the `dist/` build as a downloadable artifact; actual upload to the host is currently a manual step (see `operations/deployment/deployment-plan.md`)
- **Rollback Plan**: Yes — `operations/deployment/rollback-plan.md` (manual re-upload of a previous `release/*` build; no persistent/database state to roll back)
## Monitoring
- **Status**: Configured
- **Approach**: Combination (Logging + Dashboards; Alerting explicitly out of scope) — see `operations/monitoring/monitoring-plan.md` and `operations/monitoring/monitoring-setup.md`
## Additional Readiness Items
- **Backups**: N/A — this unit is a static marketing site (`dist/` bundle) with no database and no server-side/persistent state; nothing to back up beyond the source repository itself, which is already under git version control.
- **Secrets Management**: N/A for now — no automated host upload exists yet, so no host credentials are configured in Gitea Actions at this stage. When the automatic deploy step is added later (tracked as an open item in `deployment-plan.md`), credentials must be stored as Gitea Actions Secrets, never committed to the repo.
- **Runbook/Support Handover**: Partially covered — `operations/deployment/deployment-instructions.md` and `operations/deployment/rollback-plan.md` document how to trigger a release, upload the build, and roll back. No separate incident-response runbook exists beyond these documents, which is proportionate given this is a single-owner static site with no backend to page someone about.
- **Alert Ownership**: N/A — Alerting was explicitly declared out of scope in Monitoring Setup, so there is no alert-on-call/ownership rotation to define. If the chosen uptime dashboard tool (UptimeRobot/Better Uptime) is configured with its own opportunistic e-mail notification, the site owner is the sole recipient.
## Overall Readiness
- **Ready for Production**: Yes with caveats
- **Open Follow-ups**:
- Finalize the hosting/domain setup and extend `.gitea/workflows/deploy.yml`'s `deploy` job to actually upload `dist/` to the host, instead of only packaging it as a downloadable artifact (see `deployment-plan.md` "Open Item")
- Decide the client-side error logging destination (browser console only vs. an external service such as Sentry free tier) — see `monitoring-setup.md`
- Pick and configure the concrete analytics tool (e.g. Plausible/Umami/GA4) and uptime dashboard tool (e.g. UptimeRobot/Better Uptime), including registering the final production URL once hosting is finalized
- Run a formal Lighthouse performance check before the first real production deployment (flagged as not yet run in `build-and-test-summary.md`)