From 5707eb30dd6c695bd70c3734224c902c416e1789 Mon Sep 17 00:00:00 2001 From: Sebastian Frank Date: Thu, 26 Feb 2026 12:36:53 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20update=20deployment=20scrip?= =?UTF-8?q?ts=20and=20configuration;=20enhance=20CI/CD=20process=20with=20?= =?UTF-8?q?new=20scripts=20for=20staging=20and=20production?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/deploy.yml | 149 ++--------------------- AGENTS.md | 32 +++++ README.md | 209 ++++++++++++++++++++++---------- docker-compose-local.yml | 17 --- frontend/spa.html | 1 - scripts/ci-deploy.sh | 62 ++++++++++ scripts/ci-modify-config.sh | 45 +++++++ scripts/ci-staging.sh | 37 ++++++ scripts/ci-upload-sourcemaps.sh | 32 +++++ 9 files changed, 364 insertions(+), 220 deletions(-) create mode 100755 scripts/ci-deploy.sh create mode 100755 scripts/ci-modify-config.sh create mode 100755 scripts/ci-staging.sh create mode 100755 scripts/ci-upload-sourcemaps.sh diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 6b40982..ed1378c 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -1,9 +1,6 @@ name: deploy to production on: "push" -# push: -# branches: -# - master jobs: deploy: @@ -14,7 +11,7 @@ jobs: volumes: - /data:/data steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 lfs: true @@ -23,11 +20,10 @@ jobs: - run: | git fetch --force --tags - # setup node 20 - - name: setup node 20 - uses: actions/setup-node@v3 + - name: setup node + uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 - name: install dependencies run: | @@ -35,40 +31,7 @@ jobs: yarn install - name: modify config - run: | - sed -i 's#\(sentryEnvironment.*\)".*"#\1"${GITHUB_REF_NAME}"#g' frontend/src/config.ts - sed -i 's#//\( sentry\\.init.*\)#\1#g' frontend/src/config.ts - sed -i 's#metrictCall = false#metrictCall = true#g' frontend/src/config.ts - - set -o allexport - . ./.env - echo "PROJECT_RELEASE=${SENTRY_PROJECT}.r`git rev-list HEAD --count`-`git describe --all --long | sed 's+/+-+'`" >> .env - . ./.env - set +o allexport - echo ______ .env ______ - cat .env - echo - sed -i 's#\(const release = \).*#\1"'${PROJECT_RELEASE}'"#g' api/hooks/config-client.js - sed -i 's#\(const originURL = \).*#\1"'${LIVE_URL}'"#g' api/hooks/config-client.js - - # bash scripts/preload-meta.sh frontend/spa.html - # bash scripts/preload-meta.sh frontend/spa.html > frontend/_spa.html - # cp frontend/_spa.html frontend/spa.html - - export stamp=`date +%s` - sed -i s/__TIMESTAMP__/$stamp/g frontend/spa.html - # sed -i s/__TIMESTAMP__/$stamp/g frontend/serviceworker.js - # cat frontend/serviceworker.js - - test -d api/templates && test -L api/templates/spa.html && rm api/templates/spa.html || test -d api/templates && test -f api/templates/spa.html && rm api/templates/spa.html - test -d api/templates && cp frontend/spa.html api/templates/spa.html - cp frontend/spa.html api/templates/spa.html - echo ______ frontend/spa.html ______ - cat frontend/spa.html - - sed -i 's#\(PREVIEW_URL=\).*#\1'${LIVE_URL}/preview'#g' api/config.yml.env - echo ______ api/config.yml.env ______ - cat api/config.yml.env + run: ./scripts/ci-modify-config.sh - name: build env: @@ -88,114 +51,22 @@ jobs: run: | yarn build:server - - name: build legacy + - name: upload sourcemaps to sentry env: - FORCE_COLOR: "true" - run: | - yarn build:legacy + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + run: ./scripts/ci-upload-sourcemaps.sh - name: staging - # only if branch is dev if: github.ref == 'refs/heads/dev' env: - # /data/ORG/PROJECT/BRANCH API_BASEDIR: /data/${{ github.repository }}/${{ github.ref_name }} COMPOSE_PROJECT_NAME: ${{ github.repository }}-${{ github.ref_name }} - run: | - # read .env - set -o allexport - . ./.env - . ./api/config.yml.env - set +o allexport - - # replace / with - - COMPOSE_PROJECT_NAME=`echo $COMPOSE_PROJECT_NAME | sed 's+/+-+g'` - - mkdir -p $API_BASEDIR/frontend - rsync -av api $API_BASEDIR/ - rsync -av frontend/dist $API_BASEDIR/frontend/ - rsync -av frontend/assets $API_BASEDIR/frontend/ - sed -i 's#\(PREVIEW_URL=\).*#\1'${STAGING_URL}/preview'#g' $API_BASEDIR/api/config.yml.env - - docker compose -f docker-compose-staging.yml -p $COMPOSE_PROJECT_NAME up -d --build --remove-orphans - - reloadUrl=${STAGING_URL}/api/_/admin/reload - curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer ${ADMIN_TOKEN}" -d '{}' "${reloadUrl}" - - # clear ssr cache - ssrUrl=${STAGING_URL}/api/ssr - curl -X POST -H "Content-Type: application/json" -d '{}' "${ssrUrl}?clear=1" + run: ./scripts/ci-staging.sh - name: deploy - # only if branch is master if: github.ref == 'refs/heads/master' env: RSYNC_USER: ${{ github.repository }} RSYNC_PASS: ${{ github.token }} BRANCH: ${{ github.ref_name }} - run: | - # read .env - set -o allexport - . ./.env - . ./api/config.yml.env - set +o allexport - - - # if RSYNC_USER or RSYNC_KEY is not set, exit - if [ -z "${RSYNC_USER}" ]; then - echo "RSYNC_USER missing, exiting" - exit 1 - fi - if [ -z "${RSYNC_PASS}" ]; then - echo "RSYNC_PASS missing, exiting" - exit 1 - fi - if [ -z "${RSYNC_HOST}" ]; then - echo "RSYNC_HOST missing, exiting" - exit 1 - fi - if [ -z "${RSYNC_PORT}" ]; then - echo "RSYNC_PORT missing, exiting" - exit 1 - fi - - echo "Deploying ${BRANCH} to ${RSYNC_HOST} on port ${RSYNC_PORT} as ${RSYNC_USER}" - - excludes="" - if [ "${BRANCH}" = "master" ]; then - excludes='--exclude=src --exclude=*.map' - echo "master deploy, excluding $excludes" - fi - - SSH_CMD="sshpass -p ${RSYNC_PASS} ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -p ${RSYNC_PORT} -l ${RSYNC_USER}" - - # sync frontend - rsync -rlcgD --perms -i -u -v --stats --progress \ - --delete \ - -e "${SSH_CMD}" \ - $excludes \ - frontend/ \ - ${RSYNC_HOST}:./frontend/ \ - - # sync api config - rsync -rlcgD --perms -i -u -v --stats --progress \ - --delete \ - -e "${SSH_CMD}" \ - api/ \ - ${RSYNC_HOST}:./api/ - - # create media directory - mkdir media - chmod 770 media - rsync -rlcgD --perms -i -u -v --stats --progress \ - -e "${SSH_CMD}" \ - media \ - ${RSYNC_HOST}:./ - - - reloadUrl=${LIVE_URL}/api/_/admin/reload - curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer ${ADMIN_TOKEN}" -d '{}' "${reloadUrl}" - - # clear ssr cache - ssrUrl=${LIVE_URL}/api/ssr - curl -X POST -H "Content-Type: application/json" -d '{}' "${ssrUrl}?clear=1" + run: ./scripts/ci-deploy.sh diff --git a/AGENTS.md b/AGENTS.md index 493cea2..c8af462 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,6 +12,8 @@ Tibi CMS starter template — Svelte 5 SPA with esbuild, SSR via goja, and Playw ## Setup commands +> **New project from this template?** Follow the step-by-step guide in [README.md](README.md). + - Install deps: `yarn install` - Start dev: `make docker-up && make docker-start` - Start with mock data: set `MOCK=1` in `.env`, then `make docker-up && make docker-start` @@ -59,6 +61,36 @@ Tibi CMS starter template — Svelte 5 SPA with esbuild, SSR via goja, and Playw - API access to collections uses the reverse proxy: `CODING_URL/api/` (e.g. `CODING_URL/api/content`). - Auth via `Token` header with ADMIN_TOKEN from `api/config.yml.env`. +## Reference repositories + +These sibling repos in the workspace provide documentation, types, and reference implementations: + +| Repository | Path | Purpose | +| --- | --- | --- | +| **tibi-types** | `../../cms/tibi-types` | TypeScript type definitions for hooks, collections, permissions, etc. Included via `tsconfig.json` — **read-only, do not modify**. Key file: `index.d.ts`. JSON schemas in `schemas/api-config/`. | +| **tibi-server** | `../../cms/tibi-server` | Go source code of the server. `docs/` has detailed documentation (16 files), `examples/` has sample projects. | +| **tibi-admin-nova** | `../../cms/tibi-admin-nova` | Admin UI reference project. Key file: `types/admin.d.ts` (1147 lines — all admin types: `AdminCollection`, `AdminCollectionField`, `AdminCollectionMeta`, `AdminDashboard`, etc.). Use as reference for collection field configs, dashboard setup, and fieldLists. | + +### When to consult which repo + +- **Write collection YAML** → `tibi-server/docs/04-collections.md` + `tibi-types/schemas/api-config/collection.json` (JSON schema) +- **Write/debug hooks** → `tibi-server/docs/06-hooks.md` + `tibi-types/index.d.ts` (context types) +- **Configure admin UI** → `tibi-admin-nova/types/admin.d.ts` (field types, meta, dashboard) + `tibi-admin-nova/api/collections/` (real examples) +- **Permissions** → `tibi-server/docs/05-authentication.md` +- **Realtime/SSE** → `tibi-server/docs/07-realtime.md` +- **Images/uploads** → `tibi-server/docs/08-file-upload-images.md` +- **LLM integration** → `tibi-server/docs/09-llm-integration.md` + +### TypeScript types + +`tibi-types` is included via `tsconfig.json`: + +```json +"include": ["frontend/src/**/*", "types/**/*", "./../../cms/tibi-types", "api/**/*"] +``` + +Project-specific types (e.g. `Ssr`, `ApiOptions`, `ContentEntry`) live in `types/global.d.ts`. + ## Code style - Follow the existing code style and conventions used in the project. diff --git a/README.md b/README.md index 2956c21..6435369 100644 --- a/README.md +++ b/README.md @@ -2,87 +2,170 @@ Starter Kit für SPAs(s) `;)` mit Svelte und TibiCMS inkl. SSR -## Neues Projekt starten +## Neues Projekt erstellen — Schritt-für-Schritt -Nachdem du dieses Repository als Vorlage geklont hast, passe die Platzhalter an dein Projekt an: +Diese Anleitung ist so geschrieben, dass ein autonomer Agent oder ein Entwickler sie 1:1 abarbeiten kann, um ein eigenständiges tibi-Projekt mit eigener Code-Server-Subdomain zu erstellen. -1. **`.env`**: Ersetze `__PROJECT_NAME__` mit deinem Projektnamen (z.B. `mein-projekt`). - Die folgenden URLs werden automatisch abgeleitet: - - `CODING_URL=https://mein-projekt.code.testversion.online` - - `STAGING_URL=https://dev-mein-projekt.staging.testversion.online` -2. **`frontend/spa.html`** / **`api/templates/spa.html`**: Ersetze `__PROJECT_TITLE__` mit dem Seitentitel. -3. **`api/config.yml.env`**: Passe `ADMIN_TOKEN`, Datenbank-Name und weitere Secrets an. -4. **`docker-compose-local.yml`**: Suche nach `project_name__` und ersetze mit deinem Projektnamen (Container-Benennung). -5. **Demo-Inhalte entfernen**: Die Demo-Seite besteht aus diesen Dateien, die für ein echtes Projekt entfernt/ersetzt werden können: - - `frontend/src/blocks/` — Block-Komponenten (HeroBlock, FeaturesBlock, RichtextBlock, AccordionBlock, ContactFormBlock, BlockRenderer, NotFound) - - `frontend/mocking/content.json` — Demo-Mockdaten für Content - - `frontend/mocking/navigation.json` — Demo-Mockdaten für Navigation - - `api/collections/content.yml` — Content-Collection-Konfiguration - - `api/collections/navigation.yml` — Navigation-Collection-Konfiguration - - `tests/e2e/demo.spec.ts` — Demo-E2E-Tests - - `video-tours/tours/demo-showcase.tour.ts` — Demo-Video-Tour -6. **`frontend/src/App.svelte`**: Passe Header, Footer und Content-Loading an dein Datenmodell an. +### Voraussetzungen + +- Zugang zum Code-Server unter `*.code.testversion.online` +- `git`, `yarn`, `make`, `docker compose` verfügbar +- Traefik-Reverse-Proxy läuft auf dem Host (verwaltet `*.code.testversion.online`-Subdomains automatisch via Docker-Labels) + +### 1. Repository klonen (falls noch nicht geschehen) + +Wenn du bereits in einem geklonten Projekt arbeitest, überspringe diesen Schritt und gehe direkt zu **Schritt 2**. ```sh -# Platzhalter ersetzen (Beispiel für Linux/Mac): -sed -i 's/__PROJECT_NAME__/mein-projekt/g' .env -sed -i 's/__PROJECT_TITLE__/Mein Projekt/g' frontend/spa.html api/templates/spa.html +# Im Workspace-Verzeichnis (z.B. /WM_Dev/src/gitbase.de/cms/) +git clone https://gitbase.de/cms/tibi-svelte-starter.git mein-projekt +cd mein-projekt +git remote rename origin template +# Neues Remote-Repo anlegen (z.B. auf gitbase.de) und als origin setzen: +# git remote add origin https://gitbase.de/org/mein-projekt.git ``` -## Wozu? +### 2. Platzhalter ersetzen -Via Svelte wird eine SPA (Single-Page-App) programmiert. Dazu wird der Code einmal für den Browser aufgebreitet und außerdem für den Server kompiliert und transpiliert. Der Server-Code wird in einem tibi-server SSR-Hook (server side rendering) eingebunden und generiert dort fertiges HTML anhand der aktuelle Route für SEO und optimierte Ladezeiten. +Alle Platzhalter müssen durch die echten Projektwerte ersetzt werden. Hier die vollständige Liste: -Die Navigation innerhalb der APP im Browser löst dagegen nur API-Aufrufe aus ohne jedesmal einen SSR-Prozess anzustoßen. - -Um die SSR-Last so gering wie möglich zu halten, wurde ein Caching in der "ssr"-Collection der API implementiert. - -## Toolchain - -### git - -nach `git clone ...` +| Platzhalter | Wo | Ersetzen durch | Beispiel | +| --- | --- | --- | --- | +| `__PROJECT_NAME__` | `.env` | Projektname (kebab-case, wird für URLs und Container verwendet) | `mein-projekt` | +| `__TIBI_NAMESPACE__` | `.env` | Tibi-Namespace (snake_case, wird als DB-Prefix und in API-URLs verwendet) | `mein_projekt` | +| `__NAMESPACE__` | `api/config.yml`, `frontend/.htaccess` | Gleicher Wert wie `TIBI_NAMESPACE` | `mein_projekt` | ```sh -git lfs install -git lfs pull +# Projektname (kebab-case) — für URLs, Docker-Container, Subdomains +PROJECT=mein-projekt +# Tibi-Namespace (snake_case) — für DB, API-Pfade +NAMESPACE=mein_projekt + +sed -i "s/__PROJECT_NAME__/$PROJECT/g" .env +sed -i "s/__TIBI_NAMESPACE__/$NAMESPACE/g" .env +sed -i "s/__NAMESPACE__/$NAMESPACE/g" api/config.yml frontend/.htaccess ``` -### Abhängigkeiten laden +**Ergebnis in `.env`:** + +```dotenv +PROJECT_NAME=mein-projekt +TIBI_NAMESPACE=mein_projekt +CODING_URL=https://mein-projekt.code.testversion.online +STAGING_URL=https://dev-mein-projekt.staging.testversion.online +``` + +### 3. Seitentitel anpassen + +Der Seitentitel wird dynamisch über `` in `frontend/src/App.svelte` gesetzt. Die Demo-App verwendet dafür die Konstante `SITE_NAME`. In einem neuen Projekt wird `App.svelte` typischerweise komplett neu geschrieben — dabei einfach sicherstellen, dass `` mit einem passenden `` vorhanden ist. SSR injiziert ihn dann automatisch über den `<!--HEAD-->`-Platzhalter in `spa.html`. + +### 4. Admin-Token prüfen + +`api/config.yml.env` enthält bereits ein `ADMIN_TOKEN`. Für Produktionsprojekte durch ein sicheres Token ersetzen: + +```sh +echo "ADMIN_TOKEN=$(openssl rand -hex 20)" > api/config.yml.env +``` + +### 5. Subdomains prüfen + +Traefik erkennt die Docker-Labels automatisch. Nach `make docker-up` sind folgende URLs verfügbar: + +| Dienst | URL | +| --- | --- | +| Website (BrowserSync) | `https://{PROJECT_NAME}.code.testversion.online/` | +| Tibi Admin | `https://{PROJECT_NAME}-tibiadmin.code.testversion.online/` | +| Tibi Server API | `https://{PROJECT_NAME}-tibiserver.code.testversion.online/` | +| Maildev | `https://{PROJECT_NAME}-maildev.code.testversion.online/` | + +**Es ist KEINE manuelle Traefik-Konfiguration nötig.** Die Subdomains werden automatisch über das Docker-Label `online.testversion.code.subdomain=${PROJECT_NAME}` registriert. Traefik überwacht Docker-Events und erstellt die Routen dynamisch. + +### 6. Starten ```sh yarn install -``` - -### Entwickeln auf dem Code-Server mit Docker Compose Stack - -```sh -make docker-start +make docker-up # Stack im Hintergrund starten # oder -make docker-up -make docker-down - -# "make help" zeigt alle Kommandos +make docker-start # Stack im Vordergrund (CTRL-C zum Stoppen) ``` -| UI | URL | -| --- | --- | -| Website | <https://tibi-svelte-starter.code.testversion.online/> | -| Tibi Admin | <https://tibi-svelte-starter-tibiadmin.code.testversion.online/> | -| Maildev | <https://tibi-svelte-starter-maildev.code.testversion.online/> | - -### Bauen +Prüfe, ob alles läuft: ```sh -# moderne Browser -yarn build - -# alte Browser (IE11) -yarn build:legacy - -# serverseitiges Rendering -yarn build:server - -# Admin-Module -yarn build:admin +make docker-ps # Container-Status anzeigen +make docker-logs # Logs verfolgen +``` + +### 7. Mock-Modus (Frontend ohne tibi-server) + +Für schnelle UI-Entwicklung ohne Backend: + +```sh +# In .env setzen: +MOCK=1 +# Dann: +make docker-up +``` + +Mock-Daten liegen in `frontend/mocking/` als JSON-Dateien. Fehlende Endpunkte liefern 404. + +### 8. Demo-Inhalte entfernen + +Für ein echtes Projekt die Demo-Dateien entfernen/ersetzen: + +| Datei/Ordner | Inhalt | +| --- | --- | +| `frontend/src/blocks/` | Demo-Block-Komponenten (HeroBlock, FeaturesBlock, etc.) | +| `frontend/mocking/content.json` | Demo-Mockdaten für Content | +| `frontend/mocking/navigation.json` | Demo-Mockdaten für Navigation | +| `api/collections/content.yml` | Content-Collection-Konfiguration | +| `api/collections/navigation.yml` | Navigation-Collection-Konfiguration | +| `tests/e2e/` | Demo-E2E-Tests | +| `video-tours/tours/` | Demo-Video-Tour | + +Danach `frontend/src/App.svelte` (Header, Footer, Content-Loading) an das eigene Datenmodell anpassen. + +### 9. Build und Deployment + +```sh +yarn build # Frontend für moderne Browser +yarn build:server # SSR-Bundle (für tibi-server goja-Hooks) +yarn build:admin # Admin-Module (optional) +yarn validate # TypeScript + Svelte prüfen (muss 0 Fehler/Warnungen zeigen) +``` + +### Checkliste für autonome Agents + +```text +[ ] Repository geklont und Remote gesetzt +[ ] __PROJECT_NAME__ in .env ersetzt +[ ] __TIBI_NAMESPACE__ in .env ersetzt +[ ] __NAMESPACE__ in api/config.yml und frontend/.htaccess ersetzt +[ ] App.svelte mit eigenem Template und <svelte:head> (inkl. <title>) erstellt +[ ] ADMIN_TOKEN in api/config.yml.env gesetzt +[ ] yarn install ausgeführt +[ ] make docker-up gestartet +[ ] Website unter https://{PROJECT_NAME}.code.testversion.online/ erreichbar +[ ] yarn validate zeigt 0 Fehler und 0 Warnungen +[ ] yarn build und yarn build:server erfolgreich +``` + +--- + +## Architektur + +Via Svelte wird eine SPA (Single-Page-App) programmiert. Der Code wird einmal für den Browser aufbereitet und außerdem für den Server kompiliert und transpiliert. Der Server-Code wird in einem tibi-server SSR-Hook eingebunden und generiert fertiges HTML anhand der aktuellen Route — für SEO und optimierte Ladezeiten. + +Die Navigation innerhalb der App löst nur API-Aufrufe aus, ohne jedes Mal SSR anzustoßen. Ein Cache in der `ssr`-Collection minimiert die SSR-Last. + +**SSR-Details:** + +- `<title>` und `<meta description>` kommen über `<svelte:head>` aus `App.svelte` → SSR injiziert sie in den `<!--HEAD-->`-Platzhalter von `spa.html` +- `<html lang>` wird vom SSR-Hook (`api/hooks/ssr/get_read.js`) anhand der URL-Sprache gesetzt +- SSR-Bundle wird mit `yarn build:server` erstellt und landet in `api/hooks/lib/app.server.js` + +**Weiteres Build-Target:** + +```sh +yarn build:admin # Admin-Module ``` diff --git a/docker-compose-local.yml b/docker-compose-local.yml index 5e3070c..04716f3 100644 --- a/docker-compose-local.yml +++ b/docker-compose-local.yml @@ -131,23 +131,6 @@ services: - ./tmp/mongo-data:/data/db user: ${CODER_UID}:${CODER_GID} - adminmongo: - profiles: - - tibi - - tibi-dev - image: gitbase.de/server/adminmongo - environment: - CONN_NAME: mongo - DB_HOST: mongo - PORT: 1234 - expose: - - 1234 - labels: - - traefik.enable=true - - online.testversion.code.subdomain=${PROJECT_NAME}-adminmongo - - traefik.http.routers.${PROJECT_NAME}-adminmongo.middlewares=${PROJECT_NAME}-adminmongo - - traefik.http.middlewares.${PROJECT_NAME}-adminmongo.basicauth.usersfile=${PWD}/.basic-auth-code - maildev: profiles: - tibi diff --git a/frontend/spa.html b/frontend/spa.html index c82819f..944138a 100644 --- a/frontend/spa.html +++ b/frontend/spa.html @@ -22,7 +22,6 @@ <body> <div id="appContainer"><!--HTML--></div> <script type="module" src="/dist/index.mjs?t=__TIMESTAMP__"></script> - <script nomodule src="/dist/index.es5.js?t=__TIMESTAMP__"></script> <!-- <script src="//cc.webmakers.de/cc.js" defer diff --git a/scripts/ci-deploy.sh b/scripts/ci-deploy.sh new file mode 100755 index 0000000..84ab848 --- /dev/null +++ b/scripts/ci-deploy.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +set -euo pipefail + +# CI step: deploy to production via rsync +# Called from .gitea/workflows/deploy.yml +# Required env: RSYNC_USER, RSYNC_PASS, BRANCH + +# Read .env +set -o allexport +. ./.env +. ./api/config.yml.env +set +o allexport + +# Validate required env vars +for var in RSYNC_USER RSYNC_PASS RSYNC_HOST RSYNC_PORT; do + if [ -z "${!var:-}" ]; then + echo "${var} missing, exiting" + exit 1 + fi +done + +echo "Deploying ${BRANCH} to ${RSYNC_HOST} on port ${RSYNC_PORT} as ${RSYNC_USER}" + +# Exclude source and sourcemaps on master deploy +excludes="" +if [ "${BRANCH}" = "master" ]; then + excludes='--exclude=src --exclude=*.map' + echo "master deploy, excluding ${excludes}" +fi + +SSH_CMD="sshpass -p ${RSYNC_PASS} ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -p ${RSYNC_PORT} -l ${RSYNC_USER}" + +# Sync frontend +rsync -rlcgD --perms -i -u -v --stats --progress \ + --delete \ + -e "${SSH_CMD}" \ + ${excludes} \ + frontend/ \ + "${RSYNC_HOST}":./frontend/ + +# Sync API config and hooks +rsync -rlcgD --perms -i -u -v --stats --progress \ + --delete \ + -e "${SSH_CMD}" \ + api/ \ + "${RSYNC_HOST}":./api/ + +# Ensure media directory exists on remote +mkdir -p media +chmod 770 media +rsync -rlcgD --perms -i -u -v --stats --progress \ + -e "${SSH_CMD}" \ + media \ + "${RSYNC_HOST}":./ + +# Reload tibi-server config +reloadUrl="${LIVE_URL}/api/_/admin/reload" +curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer ${ADMIN_TOKEN}" -d '{}' "${reloadUrl}" + +# Clear SSR cache +ssrUrl="${LIVE_URL}/api/ssr" +curl -X POST -H "Content-Type: application/json" -d '{}' "${ssrUrl}?clear=1" diff --git a/scripts/ci-modify-config.sh b/scripts/ci-modify-config.sh new file mode 100755 index 0000000..ec45c68 --- /dev/null +++ b/scripts/ci-modify-config.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euo pipefail + +# CI step: modify config for deployment +# Called from .gitea/workflows/deploy.yml + +# Enable sentry +sed -i 's#\(sentryEnvironment.*\)".*"#\1"'"${GITHUB_REF_NAME}"'"#g' frontend/src/config.ts +sed -i 's#//\( sentry\\.init.*\)#\1#g' frontend/src/config.ts +sed -i 's#metrictCall = false#metrictCall = true#g' frontend/src/config.ts + +# Build release string from git info +set -o allexport +. ./.env +echo "PROJECT_RELEASE=${SENTRY_PROJECT}.r$(git rev-list HEAD --count)-$(git describe --all --long | sed 's+/+-+')" >> .env +. ./.env +set +o allexport + +echo "______ .env ______" +cat .env +echo + +# Inject release and origin URL into hooks +sed -i 's#\(const release = \).*#\1"'"${PROJECT_RELEASE}"'"#g' api/hooks/config-client.js +sed -i 's#\(const originURL = \).*#\1"'"${LIVE_URL}"'"#g' api/hooks/config-client.js + +# Inject cache-busting timestamp +stamp=$(date +%s) +sed -i "s/__TIMESTAMP__/${stamp}/g" frontend/spa.html + +# Copy spa.html to api/templates (remove symlink or file first) +if [ -d api/templates ]; then + [ -L api/templates/spa.html ] && rm api/templates/spa.html + [ -f api/templates/spa.html ] && rm api/templates/spa.html + cp frontend/spa.html api/templates/spa.html +fi + +echo "______ frontend/spa.html ______" +cat frontend/spa.html + +# Set preview URL +sed -i 's#\(PREVIEW_URL=\).*#\1'"${LIVE_URL}"'/preview#g' api/config.yml.env + +echo "______ api/config.yml.env ______" +cat api/config.yml.env diff --git a/scripts/ci-staging.sh b/scripts/ci-staging.sh new file mode 100755 index 0000000..78292af --- /dev/null +++ b/scripts/ci-staging.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +set -euo pipefail + +# CI step: deploy to staging environment +# Called from .gitea/workflows/deploy.yml +# Required env: API_BASEDIR, COMPOSE_PROJECT_NAME + +# Read .env +set -o allexport +. ./.env +. ./api/config.yml.env +set +o allexport + +# Sanitize compose project name (replace / with -) +COMPOSE_PROJECT_NAME=$(echo "${COMPOSE_PROJECT_NAME}" | sed 's+/+-+g') +export COMPOSE_PROJECT_NAME + +# Sync files to staging directory +mkdir -p "${API_BASEDIR}/frontend" +rsync -av api "${API_BASEDIR}/" +rsync -av frontend/dist "${API_BASEDIR}/frontend/" +rsync -av frontend/assets "${API_BASEDIR}/frontend/" +sed -i 's#\(PREVIEW_URL=\).*#\1'"${STAGING_URL}"'/preview#g' "${API_BASEDIR}/api/config.yml.env" + +# Start staging stack +docker compose -f docker-compose-staging.yml -p "${COMPOSE_PROJECT_NAME}" up -d --build --remove-orphans + +# Wait for API to be ready +sleep 5 + +# Reload tibi-server config +reloadUrl="${STAGING_URL}/api/_/admin/reload" +curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer ${ADMIN_TOKEN}" -d '{}' "${reloadUrl}" + +# Clear SSR cache +ssrUrl="${STAGING_URL}/api/ssr" +curl -X POST -H "Content-Type: application/json" -d '{}' "${ssrUrl}?clear=1" diff --git a/scripts/ci-upload-sourcemaps.sh b/scripts/ci-upload-sourcemaps.sh new file mode 100755 index 0000000..c144e9b --- /dev/null +++ b/scripts/ci-upload-sourcemaps.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +# CI step: upload sourcemaps to Sentry +# Called from .gitea/workflows/deploy.yml +# Required env: SENTRY_AUTH_TOKEN (passed from workflow secrets) + +# Read .env +set -o allexport +. ./.env +set +o allexport + +# Skip gracefully if no token is configured +if [ -z "${SENTRY_AUTH_TOKEN:-}" ]; then + echo "SENTRY_AUTH_TOKEN not set, skipping sourcemap upload" + exit 0 +fi + +echo "SENTRY_URL=${SENTRY_URL:-}" +echo "SENTRY_ORG=${SENTRY_ORG:-}" +echo "SENTRY_PROJECT=${SENTRY_PROJECT:-}" +echo "PROJECT_RELEASE=${PROJECT_RELEASE:-}" + +echo "Injecting debug IDs..." +npx sentry-cli sourcemaps --log-level info inject frontend/dist + +echo "Creating release and uploading sourcemaps..." +npx sentry-cli sourcemaps --log-level info upload \ + --release="${PROJECT_RELEASE:-${SENTRY_PROJECT}.dirty}" \ + --url-prefix='~/dist' \ + --ext js --ext mjs --ext map \ + frontend/dist