ci(deploy): Auto-Deploy-Runner für Push auf main + reproduzierbarer API-Build
ci / quality (push) Failing after 35s
ci / build (push) Has been skipped
deploy / deploy (push) Successful in 53s

- .gitea/workflows/deploy.yml: deployt bei Push auf main via Host-Runner
  (git reset --hard origin/main in /opt/build/cockpit, docker compose up
  -d --build in /opt/stacks/cockpit, Healthcheck auf cockpit-api).
- Auto-Migrate: runMigrations() (drizzle-orm Migrator) wird beim API-Start
  aufgerufen; Schema wird vor dem Listen idempotent aktualisiert.
- API-Dockerfile: Workspace-Libs (@cockpit/db, @cockpit/shared) werden jetzt
  mit Emit kompiliert und main/exports im Image auf dist gebogen. Vorher war
  das Prod-Image nicht reproduzierbar baubar (Node konnte die TS-Quelle der
  Libs nicht laden → ERR_MODULE_NOT_FOUND). Dev-Modus und Web-/Vite-Build
  bleiben unberührt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Raffael SheikhandClaude Opus 4.8 committed 2026-06-05 18:51:17 +02:00
1 parent 9f3ee13094
commit 3b928d0ad0
5 files changed
+102 -1

No files matched your search

+50
View File
@@ -0,0 +1,50 @@
# Gitea Actions Deploy für das Cockpit.
# Läuft NUR bei Push auf main und deployt auf dem Homeserver, auf dem auch
# Gitea und der act_runner laufen. Der Job läuft im Host-Executor (runs-on: host),
# damit er Zugriff auf den Docker-Daemon und die Host-Pfade hat:
# /opt/build/cockpit → Source-Checkout (Build-Context, gehört dem Runner-User)
# /opt/stacks/cockpit → docker-compose.yml + .env (root-owned → via sudo)
name: deploy
on:
push:
branches: [main]
concurrency:
group: deploy-cockpit
cancel-in-progress: false
jobs:
deploy:
runs-on: host
steps:
- name: Source-Checkout auf origin/main aktualisieren
run: |
set -euo pipefail
git -C /opt/build/cockpit fetch --prune origin
git -C /opt/build/cockpit reset --hard origin/main
echo "Deployed commit:"
git -C /opt/build/cockpit log --oneline -1
- name: Container bauen & neu starten
run: |
set -euo pipefail
cd /opt/stacks/cockpit
sudo docker compose up -d --build --remove-orphans
- name: Auf API-Health warten
run: |
set -euo pipefail
for i in $(seq 1 36); do
status=$(sudo docker inspect -f '{{.State.Health.Status}}' cockpit-api 2>/dev/null || echo "missing")
echo " api health: $status"
if [ "$status" = "healthy" ]; then
echo "✅ cockpit-api ist healthy."
exit 0
fi
sleep 5
done
echo "❌ cockpit-api wurde nicht rechtzeitig healthy." >&2
sudo docker logs --tail 80 cockpit-api || true
exit 1
+8 -1
View File
@@ -15,7 +15,14 @@ COPY apps/api ./apps/api
COPY packages/db ./packages/db
COPY packages/shared ./packages/shared
RUN pnpm --filter api build
# Die Workspace-Libs werden zur Laufzeit (Node) als JS geladen. Ihre tsconfig hat
# jedoch noEmit:true und main zeigt auf die TS-Quelle — Node kann das nicht laden.
# Daher hier mit Emit kompilieren und main/exports im Image auf die dist-Artefakte
# umbiegen (nur im Image; Repo-Quelle, Dev-Modus und Web-/Vite-Build bleiben unberührt).
RUN pnpm exec tsc -p packages/shared/tsconfig.json --noEmit false \
&& pnpm exec tsc -p packages/db/tsconfig.json --noEmit false \
&& pnpm --filter api build \
&& node -e "const fs=require('fs');for(const p of ['packages/db','packages/shared']){const f=p+'/package.json';const j=JSON.parse(fs.readFileSync(f,'utf8'));const fix=s=>s.replace('./src/','./dist/').replace(/[.]ts$/,'.js');if(j.main){j.main=fix(j.main);j.types=j.main.replace(/[.]js$/,'.d.ts');}if(j.exports)for(const k in j.exports){if(typeof j.exports[k]==='string')j.exports[k]=fix(j.exports[k]);}fs.writeFileSync(f,JSON.stringify(j,null,2));}"
# --- runtime ---
FROM node:22-alpine AS runtime
+12
View File
@@ -1,6 +1,7 @@
import Fastify from 'fastify';
import cookie from '@fastify/cookie';
import { fastifyTRPCPlugin, type FastifyTRPCPluginOptions } from '@trpc/server/adapters/fastify';
import { runMigrations } from '@cockpit/db';
import { loadEnv } from './env.js';
import { logger } from './lib/logger.js';
import { registerAuthMiddleware } from './auth/middleware.js';
@@ -12,6 +13,17 @@ import { createContext } from './trpc.js';
async function main() {
const env = loadEnv();
// Schema-Migrationen vor dem Start anwenden (idempotent). Schlägt eine
// Migration fehl, startet der Server nicht — besser als mit veraltetem Schema.
try {
await runMigrations(env.DATABASE_URL);
logger.info('db migrations applied');
} catch (err) {
logger.error({ err }, 'db migration failed');
process.exit(1);
}
const app = Fastify({
logger: {
level: env.LOG_LEVEL,
+1
View File
@@ -1,5 +1,6 @@
export * from './schema.js';
export { createDbClient } from './client.js';
export { runMigrations } from './migrate.js';
export type { Db, CreateDbClientOptions } from './client.js';
export const DB_VERSION = '0.0.1';
+31
View File
@@ -0,0 +1,31 @@
import { drizzle } from 'drizzle-orm/node-postgres';
import { migrate } from 'drizzle-orm/node-postgres/migrator';
import pg from 'pg';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
/**
* Wendet alle ausstehenden Drizzle-Migrationen aus `packages/db/migrations` an.
* Idempotent: Drizzle führt Buch über bereits eingespielte Migrationen in
* `__drizzle_migrations` und überspringt sie. Wird beim API-Start aufgerufen,
* damit ein Deploy automatisch das Schema aktualisiert.
*
* Der Migrations-Ordner liegt eine Ebene über dieser Datei — funktioniert
* sowohl für die kompilierte Variante (`dist/migrate.js` → `../migrations`)
* als auch im Dev-Modus (`src/migrate.ts` → `../migrations`).
*/
export async function runMigrations(connectionString: string): Promise<void> {
const migrationsFolder = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
'..',
'migrations',
);
const pool = new pg.Pool({ connectionString, max: 1 });
try {
const db = drizzle(pool);
await migrate(db, { migrationsFolder });
} finally {
await pool.end();
}
}