Author SHA1 Message Date
KenzoandClaude Opus 5 88ad16c86f fix(backend): Publish repariert - spawn git ENOENT und kaputter Image-Build
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
Publish schlug bei jedem Versuch mit "spawn git ENOENT" fehl, obwohl git im
Container installiert ist. Node meldet ENOENT auch dann, wenn das cwd des
Kindprozesses nicht existiert: initialize() setzte simple-git auf den
Workspace, loeschte diesen per rm -rf und startete den Clone anschliessend
aus dem geloeschten Verzeichnis heraus.

Nebeneffekt davon: da Uploads unter GIT_WORKSPACE_DIR/public/images liegen,
hat jeder fehlgeschlagene Publish die hochgeladenen Bilder mitgeloescht.

git.service.ts:
- Clone laeuft aus dem Elternverzeichnis statt aus dem geloeschten Ziel
- Re-Clone nur noch wenn kein brauchbares Repo vorhanden ist
- Uploads werden ueber den Re-Clone hinweg gesichert (Repo-Stand gewinnt)
- commitAndPush scheitert nicht mehr, wenn es nichts zu committen gibt
- reset() nur bei echtem Repo, mit Ausnahme fuer public/images
- Token wird in Fehlermeldungen maskiert

Dockerfile:
- COPY von src/db/migrations entfernt. Das Verzeichnis war nie im Git und
  liess den Image-Build scheitern. Zur Laufzeit wird es nicht gebraucht,
  das Schema legt initDatabase() selbst an.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-11 09:40:29 +02:00
Gallus-maintanance 45ce268db6 Update events
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-16 16:21:46 +00:00
Gallus-maintanance c1ab18fd9f Update events
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-16 12:05:23 +00:00
Gallus-maintanance 52d6457797 Update events
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-05 17:26:42 +00:00
Gallus-maintanance 4c9be79282 Update events
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-01 11:57:50 +00:00
Gallus-maintanance 324f0f706f Update events
ci/woodpecker/push/woodpecker Pipeline was successful
2026-06-01 13:50:34 +00:00
Gallus-maintanance f7a8c1816f Update events
ci/woodpecker/push/woodpecker Pipeline was successful
2026-05-12 14:43:01 +00:00
Kenzo d14914a453 chore(pdf): update Getraenke_Gallus_2025 document
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-22 14:30:15 +02:00
Gallus-maintanance 2a1d56d2fc Update events
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-22 12:19:12 +00:00
Gallus-maintanance c8ed065486 Update events
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-22 11:52:56 +00:00
Gallus-maintanance 8e6c1924b8 Update events
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-20 12:48:48 +00:00
Gallus-maintanance 04dbd0bb0c Update events
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-30 18:21:01 +00:00
Gallus-maintanance 9b697fbb05 Update events
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-29 17:17:36 +00:00
26 changed files with 98 additions and 48 deletions
+2 -1
View File
@@ -39,8 +39,9 @@ RUN npm ci --omit=dev || npm install --production && \
RUN apk del python3 make g++ vips-dev RUN apk del python3 make g++ vips-dev
# Copy built files from builder # Copy built files from builder
# Hinweis: Drizzle-Migrationen werden nicht kopiert - das Schema wird zur
# Laufzeit von initDatabase() angelegt, src/db/migrations existiert nicht.
COPY --from=builder /app/dist ./dist COPY --from=builder /app/dist ./dist
COPY --from=builder /app/src/db/migrations ./dist/db/migrations
# Copy migration script and migrated images # Copy migration script and migrated images
COPY --from=builder /app/migrate-production.js ./migrate-production.js COPY --from=builder /app/migrate-production.js ./migrate-production.js
+72 -15
View File
@@ -1,43 +1,84 @@
import simpleGit, { SimpleGit } from 'simple-git'; import simpleGit, { SimpleGit } from 'simple-git';
import { mkdir, rm } from 'fs/promises'; import { mkdir, rm, cp } from 'fs/promises';
import { existsSync } from 'fs';
import path from 'path'; import path from 'path';
import { env } from '../config/env.js'; import { env } from '../config/env.js';
export class GitService { export class GitService {
private git: SimpleGit; private git: SimpleGit;
private workspaceDir: string; private workspaceDir: string;
private parentDir: string;
private repoUrl: string; private repoUrl: string;
private token: string; private token: string;
constructor() { constructor() {
this.workspaceDir = env.GIT_WORKSPACE_DIR; this.workspaceDir = env.GIT_WORKSPACE_DIR;
this.parentDir = path.dirname(this.workspaceDir);
this.repoUrl = env.GIT_REPO_URL; this.repoUrl = env.GIT_REPO_URL;
this.token = env.GIT_TOKEN; this.token = env.GIT_TOKEN;
this.git = simpleGit(); this.git = simpleGit();
} }
async initialize() { async initialize() {
// Ensure workspace directory exists // Elternverzeichnis muss existieren - simple-git startet git von dort aus
await mkdir(this.workspaceDir, { recursive: true }); await mkdir(this.parentDir, { recursive: true });
// Add token to repo URL for authentication // Add token to repo URL for authentication
const authenticatedUrl = this.repoUrl.replace( const authenticatedUrl = this.repoUrl.replace(
'https://', 'https://',
`https://oauth2:${this.token}@` `https://oauth2:${encodeURIComponent(this.token)}@`
); );
try { let usable = false;
// Check if repo already exists
await this.git.cwd(this.workspaceDir); if (existsSync(path.join(this.workspaceDir, '.git'))) {
await this.git.status(); try {
console.log('Repository already exists, pulling latest...'); this.git = simpleGit(this.workspaceDir);
await this.git.pull(); await this.git.status();
} catch { console.log('Repository already exists, pulling latest...');
// Clone if doesn't exist await this.git.pull();
usable = true;
} catch (error) {
// Token aus der Meldung entfernen - git gibt die Remote-URL mit aus
const msg = error instanceof Error ? error.message : String(error);
console.warn(
'Existing workspace unusable, re-cloning:',
msg.replace(/\/\/[^@\s/]*@/g, '//***@')
);
}
}
if (!usable) {
console.log('Cloning repository...'); console.log('Cloning repository...');
// Hochgeladene Bilder liegen im Workspace und wuerden beim Loeschen
// verschwinden - vorher wegsichern, nach dem Clone zurueckspielen
const imagesDir = path.join(this.workspaceDir, 'public', 'images');
const backupDir = path.join(this.parentDir, '.workspace-images-backup');
const hasImages = existsSync(imagesDir);
await rm(backupDir, { recursive: true, force: true });
if (hasImages) {
await cp(imagesDir, backupDir, { recursive: true });
}
await rm(this.workspaceDir, { recursive: true, force: true }); await rm(this.workspaceDir, { recursive: true, force: true });
// Aus dem existierenden Elternverzeichnis klonen, nicht aus dem
// soeben geloeschten Zielverzeichnis (sonst: spawn git ENOENT)
this.git = simpleGit(this.parentDir);
await this.git.clone(authenticatedUrl, this.workspaceDir); await this.git.clone(authenticatedUrl, this.workspaceDir);
await this.git.cwd(this.workspaceDir); this.git = simpleGit(this.workspaceDir);
if (hasImages) {
// force: false -> was schon im Repo liegt, bleibt unangetastet
await cp(backupDir, imagesDir, {
recursive: true,
force: false,
errorOnExist: false,
});
await rm(backupDir, { recursive: true, force: true });
}
} }
// Configure git user // Configure git user
@@ -47,6 +88,14 @@ export class GitService {
async commitAndPush(message: string): Promise<string> { async commitAndPush(message: string): Promise<string> {
await this.git.add('.'); await this.git.add('.');
const status = await this.git.status();
if (status.isClean()) {
// Nichts zu committen - aktuellen Stand zurueckgeben statt zu scheitern
const current = await this.git.log({ maxCount: 1 });
return current.latest?.hash || '';
}
await this.git.commit(message); await this.git.commit(message);
await this.git.push('origin', 'main'); await this.git.push('origin', 'main');
@@ -59,7 +108,15 @@ export class GitService {
} }
async reset() { async reset() {
await this.git.reset(['--hard', 'HEAD']); // Nur zuruecksetzen wenn wirklich ein Repo da ist - sonst laeuft git
await this.git.clean('f', ['-d']); // im Prozess-Arbeitsverzeichnis und raeumt dort auf
if (!existsSync(path.join(this.workspaceDir, '.git'))) {
return;
}
const git = simpleGit(this.workspaceDir);
await git.reset(['--hard', 'HEAD']);
// Uploads ausnehmen - die sind noch nicht committed und waeren sonst weg
await git.clean('f', ['-d', '-e', 'public/images']);
} }
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1021 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 KiB

Binary file not shown.
+24 -32
View File
@@ -28,52 +28,44 @@ Plätze sind begrenzt! Jetzt reservieren unter 🍀WA 077 232 27 70
`, `,
}, },
{ {
image: "/images/events/ml0s9u8a-d8eqee.jpeg", image: "/images/events/mr82c7eh-yp87hy.jpeg",
title: "St.Patricks Day", title: "Richie Live",
date: "2026-03-17", date: "2026-08-27",
description: ` description: `
🍀 Its Paddys Time! Freu dich auf echtes St.-Patricks-Day-Feeling mit LiveDudelsackmusik, Guinness vom Fass und natürlich grünem Bier. Zieh etwas Grünes an, bring deine Freunde mit und stoß mit uns an Sláinte! 🍻 SOLD OUT! - Richie hat das Image eines Weltenbummlers und Musikkünstlers. In der Ostschweiz ist er bereits bestens bekannt.🍀Warteliste: WA 077 232 27 70
`, `,
}, },
{ {
image: "/images/events/mk6wdnz2-rpxzvl.jpeg", image: "/images/events/mrnpux59-b3075p.jpeg",
title: "Pg Petricca - LIVE", title: "Hannes LIVE",
date: "2026-03-20", date: "2026-09-10",
description: ` description: `
LIVE Musik mit Pg Petricca! - Folk & Blues. Er ist zurück!🥰🍀Johannes ist wieder bei uns! Eine weitere unvergessliche Irish-Night!!🍀Sei dabei! Reservationen🍀via WA 077 232 27 70
Eintritt ist Frei / Hutkollekte
Reservation unter 🍀WA 077 232 27 70
`, `,
}, },
{ {
image: "/images/events/mmnlxydh-924q8d.jpeg", image: "/images/events/mrngoyyy-49rz7u.jpeg",
title: "Chris Live", title: "Danielle&Nina",
date: "2026-04-16", date: "2026-09-24",
description: ` description: `
Guitar & Songwriter. Ge­bo­ren in den USA und ge­prägt von ei­nem Le­ben vol­ler Be­we­gung und Mu­sik. SOLD OUT! - First time on stage und das direkt im Gallus Pub!🎤✨
🍀WA 077 232 27 70 Danielle & Nina bringen Irish Folk & liebevolle Cover Songs.🍀Warteliste: WA 077 232 27 70
`,
},
{
image: "/images/events/mmnlz7pc-wwdvwe.jpeg",
title: "Aushilfe gesucht",
date: "2026-03-12",
description: `
🍀4 Abende im Monat oder nach Absprache🍀Die besten Stammgäste 🍀Gute Entlöhnung 🍀Familiäre Atmosphäre und mehr! WA 077 232 27 70
`, `,
} }
]; ];
const images = [ const images = [
{ src: "/images/gallery/miywxkwh-m4xaww.webp", alt: "miywxkwh-m4xaww.webp" }, { src: "/images/gallery/mndhrrtl-s4o8dn.png", alt: "mndhrrtl-s4o8dn.png" },
{ src: "/images/gallery/miyxgbqr-n3zzrg.png", alt: "miyxgbqr-n3zzrg.png" }, { src: "/images/gallery/mndhtjvz-965yra.jpeg", alt: "mndhtjvz-965yra.jpeg" },
{ src: "/images/gallery/miyxgfh1-c7zawh.png", alt: "miyxgfh1-c7zawh.png" }, { src: "/images/gallery/mndi3625-lnnggc.jpeg", alt: "mndi3625-lnnggc.jpeg" },
{ src: "/images/gallery/miyxgjff-wjtyim.png", alt: "miyxgjff-wjtyim.png" }, { src: "/images/gallery/mndi7upa-qxukqg.jpeg", alt: "mndi7upa-qxukqg.jpeg" },
{ src: "/images/gallery/miyxgn6h-jsaltu.png", alt: "miyxgn6h-jsaltu.png" }, { src: "/images/gallery/mndi8kzg-oazjp8.jpeg", alt: "mndi8kzg-oazjp8.jpeg" },
{ src: "/images/gallery/mj67l5x3-pdasw8.jpeg", alt: "mj67l5x3-pdasw8.jpeg" }, { src: "/images/gallery/mndiabuo-m29xj0.jpeg", alt: "mndiabuo-m29xj0.jpeg" },
{ src: "/images/gallery/mj67mw2z-3pd81q.jpeg", alt: "mj67mw2z-3pd81q.jpeg" }, { src: "/images/gallery/mndicr59-n8suoy.jpeg", alt: "mndicr59-n8suoy.jpeg" },
{ src: "/images/gallery/mj67nwjs-6oaijj.jpeg", alt: "mj67nwjs-6oaijj.jpeg" }, { src: "/images/gallery/mndieqns-mjs7xe.jpeg", alt: "mndieqns-mjs7xe.jpeg" },
{ src: "/images/gallery/mj67ove6-el3pf7.png", alt: "mj67ove6-el3pf7.png" } { src: "/images/gallery/mndif8nv-aptjf6.jpeg", alt: "mndif8nv-aptjf6.jpeg" },
{ src: "/images/gallery/mndiggta-hnldkv.jpeg", alt: "mndiggta-hnldkv.jpeg" },
{ src: "/images/gallery/mndih8lp-woprt4.webp", alt: "mndih8lp-woprt4.webp" }
]; ];
--- ---