feat: manual deploy-app workflow for apps/ with build support
Add workflow_dispatch-only deploy-app action, deploy-app.sh script, apps/_template for Cursor, and SOUL/README split between static webhook deploy and app compose profiles. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
name: Deploy App (manual)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
app:
|
||||
description: App slug (directory under apps/)
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout (validate app exists in repo)
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Validate app slug
|
||||
run: |
|
||||
APP="${{ inputs.app }}"
|
||||
if ! echo "$APP" | grep -qE '^[a-z0-9]+(-[a-z0-9]+)*$'; then
|
||||
echo "Invalid app slug: $APP"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -d "apps/$APP" ]; then
|
||||
echo "apps/$APP not found in repository"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build and deploy on boka
|
||||
env:
|
||||
DEPLOY_ROOT: ${{ secrets.DEPLOY_ROOT }}
|
||||
DEPLOY_GIT_TOKEN: ${{ secrets.DEPLOY_GIT_TOKEN }}
|
||||
DEPLOY_BRANCH: main
|
||||
run: |
|
||||
DEPLOY_ROOT="${DEPLOY_ROOT:-/home/frank/playground.schwenk.online}"
|
||||
chmod +x scripts/deploy-app.sh
|
||||
DEPLOY_ROOT="$DEPLOY_ROOT" DEPLOY_GIT_TOKEN="$DEPLOY_GIT_TOKEN" \
|
||||
scripts/deploy-app.sh "${{ inputs.app }}"
|
||||
@@ -7,18 +7,25 @@ Experimentier-Site auf [playground.schwenk.online](https://playground.schwenk.on
|
||||
1. Issue in Gitea öffnen, z. B. *„Onepager: Thema XY“*
|
||||
2. Kommentar: `@cursor recherchiere zu Thema XY und generiere einen Onepager als plain HTML`
|
||||
3. Gitea Action ([cursor-gitea-agent](https://gitea.schwenk.online/froxxxy/cursor-gitea-agent)) committet nach `public/<slug>/` auf `main`
|
||||
4. Push-Webhook triggert `git pull` im Container-Stack → nginx liefert die Seite aus
|
||||
4. Push-Webhook triggert `git pull` → nginx liefert statische Seiten aus
|
||||
|
||||
**Apps** (npm, Docker, DB): Code unter `apps/<slug>/`, Deploy nur manuell — siehe unten.
|
||||
|
||||
Content-Konventionen: siehe [SOUL.md](SOUL.md).
|
||||
|
||||
## Repo-Layout
|
||||
|
||||
```
|
||||
public/ # Webroot
|
||||
public/ # Webroot (statisch, auto-deploy per Push-Webhook)
|
||||
apps/ # Web-Apps mit Build/Docker (manueller Deploy-Workflow)
|
||||
nginx/ # nginx-Konfiguration
|
||||
hooks/ # Webhook-Skripte + hooks.json.template
|
||||
deploy-hook/ # Dockerfile für Webhook-Container (git pull)
|
||||
compose.yml # nginx + deploy-hook auf Traefik-Netz
|
||||
hooks/ # Webhook-Skripte (nur git pull für static)
|
||||
deploy-hook/ # Webhook-Container
|
||||
scripts/ # deploy-app.sh (Gitea Action auf boka)
|
||||
compose.yml # nginx + deploy-hook + optionale App-Profiles
|
||||
.gitea/workflows/
|
||||
cursor.yml # @cursor in Issues
|
||||
deploy-app.yml # manuell: Build + docker compose --profile
|
||||
```
|
||||
|
||||
Auf boka ist `/home/frank/playground.schwenk.online` ein **Git-Clone** dieses Repos.
|
||||
@@ -29,6 +36,8 @@ Auf boka ist `/home/frank/playground.schwenk.online` ein **Git-Clone** dieses Re
|
||||
2. **Repo-Secrets** (Namen ohne `GITEA_`-Prefix):
|
||||
- `CURSOR_API_KEY` — Cursor API key
|
||||
- `CURSOR_BOT_TOKEN` — Gitea API token des `cursor`-Users
|
||||
- `DEPLOY_GIT_TOKEN` — für App-Deploy-Workflow (`git pull` auf boka); kann identisch mit Bot-Token sein
|
||||
- `DEPLOY_ROOT` (optional) — Default `/home/frank/playground.schwenk.online`
|
||||
3. **Push-Webhook** (Repo → Einstellungen → Webhooks):
|
||||
- URL: `https://playground.schwenk.online/hooks/deploy`
|
||||
- Content type: `application/json`
|
||||
@@ -90,3 +99,15 @@ docker compose up -d
|
||||
## Beispiel-URL nach erstem Onepager
|
||||
|
||||
`https://playground.schwenk.online/thema-xy/` — wenn Cursor `public/thema-xy/index.html` angelegt hat.
|
||||
|
||||
## Apps deployen (manuell)
|
||||
|
||||
Für `apps/<slug>/` mit npm/Docker/DB — **nicht** über den Push-Webhook.
|
||||
|
||||
1. `@cursor` legt App unter `apps/<slug>/` an + `compose.yml`-Profile (Vorlage: `apps/_template/`)
|
||||
2. Push auf `main` (Webhook zieht nur Dateien, baut die App nicht)
|
||||
3. Gitea → Actions → **Deploy App (manual)** → Run workflow → `app` = `<slug>`
|
||||
|
||||
Der Workflow ruft `scripts/deploy-app.sh` auf boka auf: `git pull`, optional `npm ci && build`, dann `docker compose --profile <slug> up -d --build`.
|
||||
|
||||
**Nur manuell** — kein Push-, kein Issue-Trigger. Optional pro App: `.gitea/workflows/deploy-<slug>.yml` aus `apps/_template/workflow-deploy.yml.template` (ebenfalls nur `workflow_dispatch`).
|
||||
|
||||
@@ -42,14 +42,30 @@ Fränky — und ggf. Gäste mit Link zu einem konkreten Pfad (`/thema-xy/`).
|
||||
|
||||
- **Workdir (boka):** `/home/frank/playground.schwenk.online` — Git-Clone dieses Repos
|
||||
- **Webroot:** `public/` → nginx
|
||||
- **Deploy:** Gitea Push-Webhook → `https://playground.schwenk.online/hooks/deploy`
|
||||
- **Deploy static:** Push-Webhook → `hooks/deploy` → `git pull` only
|
||||
- **Deploy apps:** `.gitea/workflows/deploy-app.yml` → manual `workflow_dispatch`
|
||||
- **Cursor workflow:** `.gitea/workflows/cursor.yml` mit `push_to_default_branch: "true"`
|
||||
|
||||
## Project-Specific Rules
|
||||
|
||||
### Static content (`public/`)
|
||||
|
||||
- Content lebt **verzeichnisbasiert** unter `public/<kebab-slug>/` — typisch `index.html`, keine feste Dateiliste.
|
||||
- Cursor entscheidet selbst: neues Verzeichnis vs. `index2.html` / Assets im bestehenden Ordner.
|
||||
- Plain HTML/CSS only — kein npm, kein Bundler.
|
||||
- **Kein** `public/index.html` und keine Pflege einer Übersichtsseite, außer Fränky fordert es explizit.
|
||||
- Bot-Commits nur unter `public/` und nur was zum Issue passt — keine Drive-by-Änderungen an Infra-Dateien.
|
||||
- `@cursor` in Issue-Kommentaren: Aufgabe erledigen, kurz im Issue kommentieren, auf `main` pushen.
|
||||
- Push auf `main` → Webhook deployt **nur** `git pull` (kein Build).
|
||||
|
||||
### Apps (`apps/<slug>/`)
|
||||
|
||||
- Für Web-Apps, npm, Docker, Datenbank: Code unter `apps/<kebab-slug>/`.
|
||||
- `compose.yml` um Service mit `profiles: ["<slug>"]` ergänzen (Vorlage: `apps/_template/README.md`).
|
||||
- **Deploy:** `.gitea/workflows/deploy-app.yml` — **nur** `workflow_dispatch` (manuell in Gitea). Optional eigene `.gitea/workflows/deploy-<slug>.yml` aus `apps/_template/workflow-deploy.yml.template`.
|
||||
- Push-Webhook und `deploy-app` nicht vermischen: Webhook = statisch; App-Workflow = Build + `docker compose --profile <slug> up -d --build`.
|
||||
- Nach Implementierung im Issue kommentieren: Workflow manuell starten.
|
||||
|
||||
### Allgemein
|
||||
|
||||
- `@cursor` in Issue-Kommentaren: Aufgabe erledigen, kurz kommentieren, auf `main` pushen.
|
||||
- Neue Workflows nur unter `.gitea/workflows/`; App-Deploy-Workflows **nur** mit `workflow_dispatch` (kein `push`-Trigger), außer Fränky sagt es explizit.
|
||||
- Keine Drive-by-Änderungen an Infra ohne Bezug zum Issue.
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# App template (`apps/<slug>/`)
|
||||
|
||||
Cursor copies this folder when creating a non-trivial app (npm, Docker, database).
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
apps/<slug>/
|
||||
├── Dockerfile
|
||||
├── package.json # optional — Node apps
|
||||
├── package-lock.json
|
||||
└── src/ # or project-specific layout
|
||||
```
|
||||
|
||||
## `compose.yml` — add a profile
|
||||
|
||||
Append a service (replace `<slug>`):
|
||||
|
||||
```yaml
|
||||
<slug>:
|
||||
profiles: ["<slug>"]
|
||||
build: ./apps/<slug>
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.<slug>.rule=Host(`playground.schwenk.online`) && PathPrefix(`/<slug>`)"
|
||||
- "traefik.http.routers.<slug>.entrypoints=websecure"
|
||||
- "traefik.http.routers.<slug>.tls.certresolver=myresolver"
|
||||
- "traefik.http.services.<slug>.loadbalancer.server.port=3000"
|
||||
networks:
|
||||
- traefik
|
||||
```
|
||||
|
||||
Adjust port and PathPrefix to match the app. For Postgres/SQLite, add volumes and env in the same profile block.
|
||||
|
||||
## Deploy workflow
|
||||
|
||||
**Default:** use the shared manual workflow `.gitea/workflows/deploy-app.yml` (`workflow_dispatch`, input `app=<slug>`).
|
||||
|
||||
**Optional (stricter):** copy `workflow-deploy.yml.template` to `.gitea/workflows/deploy-<slug>.yml` — triggers **only** manually for that app.
|
||||
|
||||
## After implementation
|
||||
|
||||
1. Push to `main` (static webhook updates repo files only — does **not** build the app).
|
||||
2. Gitea → Actions → **Deploy App (manual)** → Run workflow → `app` = `<slug>`.
|
||||
|
||||
Comment in the issue: *„App unter apps/<slug>/ — bitte Workflow Deploy App manuell starten.“*
|
||||
@@ -0,0 +1,19 @@
|
||||
# Deploy <slug>
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build and deploy on boka
|
||||
env:
|
||||
DEPLOY_ROOT: ${{ secrets.DEPLOY_ROOT }}
|
||||
DEPLOY_GIT_TOKEN: ${{ secrets.DEPLOY_GIT_TOKEN }}
|
||||
run: |
|
||||
chmod +x scripts/deploy-app.sh
|
||||
DEPLOY_ROOT="${DEPLOY_ROOT:-/home/frank/playground.schwenk.online}" \
|
||||
DEPLOY_GIT_TOKEN="$DEPLOY_GIT_TOKEN" \
|
||||
scripts/deploy-app.sh "<slug>"
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Static deploy only: git pull for public/ and repo files.
|
||||
# App build/deploy: .gitea/workflows/deploy-app.yml (workflow_dispatch).
|
||||
set -euo pipefail
|
||||
|
||||
REPO_DIR="/repo"
|
||||
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build and start a compose profile for apps/<slug>/ on boka.
|
||||
# Called from .gitea/workflows/deploy-app.yml (workflow_dispatch only).
|
||||
set -euo pipefail
|
||||
|
||||
APP="${1:?usage: deploy-app.sh <app-slug>}"
|
||||
DEPLOY_ROOT="${DEPLOY_ROOT:-/home/frank/playground.schwenk.online}"
|
||||
BRANCH="${DEPLOY_BRANCH:-main}"
|
||||
APP_DIR="${DEPLOY_ROOT}/apps/${APP}"
|
||||
|
||||
if ! echo "${APP}" | grep -qE '^[a-z0-9]+(-[a-z0-9]+)*$'; then
|
||||
echo "Error: invalid app slug: ${APP}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "${APP_DIR}" ]; then
|
||||
echo "Error: ${APP_DIR} does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git_cmd() {
|
||||
git -c "safe.directory=${DEPLOY_ROOT}" -C "${DEPLOY_ROOT}" "$@"
|
||||
}
|
||||
|
||||
if [ -n "${DEPLOY_GIT_TOKEN:-}" ]; then
|
||||
pull_url="https://oauth2:${DEPLOY_GIT_TOKEN}@gitea.schwenk.online/froxxxy/playground.git"
|
||||
git_cmd pull "${pull_url}" "${BRANCH}"
|
||||
else
|
||||
git_cmd pull "https://gitea.schwenk.online/froxxxy/playground.git" "${BRANCH}"
|
||||
fi
|
||||
|
||||
if [ -f "${APP_DIR}/package.json" ] && [ -f "${APP_DIR}/package-lock.json" ]; then
|
||||
echo "Running npm ci && npm run build in apps/${APP}..."
|
||||
docker run --rm \
|
||||
-v "${APP_DIR}:/app" \
|
||||
-w /app \
|
||||
node:current-alpine \
|
||||
sh -c "npm ci && npm run build"
|
||||
fi
|
||||
|
||||
cd "${DEPLOY_ROOT}"
|
||||
export COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-playground}"
|
||||
docker compose --profile "${APP}" up -d --build
|
||||
|
||||
echo "Deploy completed for apps/${APP}"
|
||||
Reference in New Issue
Block a user