feat: update deployment scripts and configuration; enhance CI/CD process with new scripts for staging and production

This commit is contained in:
2026-02-26 12:36:53 +00:00
parent 965a505e15
commit 5707eb30dd
9 changed files with 364 additions and 220 deletions

View File

@@ -1,9 +1,6 @@
name: deploy to production name: deploy to production
on: "push" on: "push"
# push:
# branches:
# - master
jobs: jobs:
deploy: deploy:
@@ -14,7 +11,7 @@ jobs:
volumes: volumes:
- /data:/data - /data:/data
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
lfs: true lfs: true
@@ -23,11 +20,10 @@ jobs:
- run: | - run: |
git fetch --force --tags git fetch --force --tags
# setup node 20 - name: setup node
- name: setup node 20 uses: actions/setup-node@v4
uses: actions/setup-node@v3
with: with:
node-version: 20 node-version: 22
- name: install dependencies - name: install dependencies
run: | run: |
@@ -35,40 +31,7 @@ jobs:
yarn install yarn install
- name: modify config - name: modify config
run: | run: ./scripts/ci-modify-config.sh
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
- name: build - name: build
env: env:
@@ -88,114 +51,22 @@ jobs:
run: | run: |
yarn build:server yarn build:server
- name: build legacy - name: upload sourcemaps to sentry
env: env:
FORCE_COLOR: "true" SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: | run: ./scripts/ci-upload-sourcemaps.sh
yarn build:legacy
- name: staging - name: staging
# only if branch is dev
if: github.ref == 'refs/heads/dev' if: github.ref == 'refs/heads/dev'
env: env:
# /data/ORG/PROJECT/BRANCH
API_BASEDIR: /data/${{ github.repository }}/${{ github.ref_name }} API_BASEDIR: /data/${{ github.repository }}/${{ github.ref_name }}
COMPOSE_PROJECT_NAME: ${{ github.repository }}-${{ github.ref_name }} COMPOSE_PROJECT_NAME: ${{ github.repository }}-${{ github.ref_name }}
run: | run: ./scripts/ci-staging.sh
# 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"
- name: deploy - name: deploy
# only if branch is master
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master'
env: env:
RSYNC_USER: ${{ github.repository }} RSYNC_USER: ${{ github.repository }}
RSYNC_PASS: ${{ github.token }} RSYNC_PASS: ${{ github.token }}
BRANCH: ${{ github.ref_name }} BRANCH: ${{ github.ref_name }}
run: | run: ./scripts/ci-deploy.sh
# 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"

View File

@@ -12,6 +12,8 @@ Tibi CMS starter template — Svelte 5 SPA with esbuild, SSR via goja, and Playw
## Setup commands ## Setup commands
> **New project from this template?** Follow the step-by-step guide in [README.md](README.md).
- Install deps: `yarn install` - Install deps: `yarn install`
- Start dev: `make docker-up && make docker-start` - Start dev: `make docker-up && make docker-start`
- Start with mock data: set `MOCK=1` in `.env`, then `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/<collection>` (e.g. `CODING_URL/api/content`). - API access to collections uses the reverse proxy: `CODING_URL/api/<collection>` (e.g. `CODING_URL/api/content`).
- Auth via `Token` header with ADMIN_TOKEN from `api/config.yml.env`. - 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 ## Code style
- Follow the existing code style and conventions used in the project. - Follow the existing code style and conventions used in the project.

209
README.md
View File

@@ -2,87 +2,170 @@
Starter Kit für SPAs(s) `;)` mit Svelte und TibiCMS inkl. SSR 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`). ### Voraussetzungen
Die folgenden URLs werden automatisch abgeleitet:
- `CODING_URL=https://mein-projekt.code.testversion.online` - Zugang zum Code-Server unter `*.code.testversion.online`
- `STAGING_URL=https://dev-mein-projekt.staging.testversion.online` - `git`, `yarn`, `make`, `docker compose` verfügbar
2. **`frontend/spa.html`** / **`api/templates/spa.html`**: Ersetze `__PROJECT_TITLE__` mit dem Seitentitel. - Traefik-Reverse-Proxy läuft auf dem Host (verwaltet `*.code.testversion.online`-Subdomains automatisch via Docker-Labels)
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). ### 1. Repository klonen (falls noch nicht geschehen)
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) Wenn du bereits in einem geklonten Projekt arbeitest, überspringe diesen Schritt und gehe direkt zu **Schritt 2**.
- `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.
```sh ```sh
# Platzhalter ersetzen (Beispiel für Linux/Mac): # Im Workspace-Verzeichnis (z.B. /WM_Dev/src/gitbase.de/cms/)
sed -i 's/__PROJECT_NAME__/mein-projekt/g' .env git clone https://gitbase.de/cms/tibi-svelte-starter.git mein-projekt
sed -i 's/__PROJECT_TITLE__/Mein Projekt/g' frontend/spa.html api/templates/spa.html 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. | Platzhalter | Wo | Ersetzen durch | Beispiel |
| --- | --- | --- | --- |
Um die SSR-Last so gering wie möglich zu halten, wurde ein Caching in der "ssr"-Collection der API implementiert. | `__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` |
## Toolchain | `__NAMESPACE__` | `api/config.yml`, `frontend/.htaccess` | Gleicher Wert wie `TIBI_NAMESPACE` | `mein_projekt` |
### git
nach `git clone ...`
```sh ```sh
git lfs install # Projektname (kebab-case) — für URLs, Docker-Container, Subdomains
git lfs pull 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 `<svelte:head>` 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 `<svelte:head>` mit einem passenden `<title>` 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 ```sh
yarn install yarn install
``` make docker-up # Stack im Hintergrund starten
### Entwickeln auf dem Code-Server mit Docker Compose Stack
```sh
make docker-start
# oder # oder
make docker-up make docker-start # Stack im Vordergrund (CTRL-C zum Stoppen)
make docker-down
# "make help" zeigt alle Kommandos
``` ```
| UI | URL | Prüfe, ob alles läuft:
| --- | --- |
| 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
```sh ```sh
# moderne Browser make docker-ps # Container-Status anzeigen
yarn build make docker-logs # Logs verfolgen
```
# alte Browser (IE11)
yarn build:legacy ### 7. Mock-Modus (Frontend ohne tibi-server)
# serverseitiges Rendering Für schnelle UI-Entwicklung ohne Backend:
yarn build:server
```sh
# Admin-Module # In .env setzen:
yarn build:admin 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
``` ```

View File

@@ -131,23 +131,6 @@ services:
- ./tmp/mongo-data:/data/db - ./tmp/mongo-data:/data/db
user: ${CODER_UID}:${CODER_GID} 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: maildev:
profiles: profiles:
- tibi - tibi

View File

@@ -22,7 +22,6 @@
<body> <body>
<div id="appContainer"><!--HTML--></div> <div id="appContainer"><!--HTML--></div>
<script type="module" src="/dist/index.mjs?t=__TIMESTAMP__"></script> <script type="module" src="/dist/index.mjs?t=__TIMESTAMP__"></script>
<script nomodule src="/dist/index.es5.js?t=__TIMESTAMP__"></script>
<!-- <script <!-- <script
src="//cc.webmakers.de/cc.js" src="//cc.webmakers.de/cc.js"
defer defer

62
scripts/ci-deploy.sh Executable file
View File

@@ -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"

45
scripts/ci-modify-config.sh Executable file
View File

@@ -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

37
scripts/ci-staging.sh Executable file
View File

@@ -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"

32
scripts/ci-upload-sourcemaps.sh Executable file
View File

@@ -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