Die Anleitung sagt "./scripts/update.sh" - auf Linux kam dort "Permission denied". Die drei .sh-Dateien lagen mit Modus 644 statt 755 im Index. Ursache ist der Entwicklungsrechner: Unter Windows steht core.filemode=false, git nimmt ein fehlendes Ausfuehrungsrecht dort gar nicht wahr. Gemeldet hat es der Betrieb, nicht der Bau. Wer von 1.1.0 oder aelter kommt, braucht einmalig "bash scripts/update.sh" - das alte Skript ist ja noch das nicht ausfuehrbare. Steht jetzt in README und Installationsanleitung. Zusaetzlich setzt update.sh nach dem Umschalten selbst ein chmod +x auf scripts/*.sh. Das faengt den Fall ab, dass jemand Dateien von Hand kopiert und das Recht dabei verliert - genau so kam man bisher an dieses Skript, bevor es in der laufenden Fassung existierte. Neu: tests/skripte.test.ts prueft nicht Verhalten, sondern Verpackung - Ausfuehrungsrecht im Index, Shebang, scripts/ im Docker-Abbild, die Sicherungsskripte in der Compose-Datei. Es ist der zweite Fehler dieser Art (vorher fehlte scripts/ ganz im Abbild, 1.0.1); beide Male war der Code richtig und kein Test sah hin, wie die Dateien beim Server ankommen. Gegenprobe gemacht: mit zurueckgesetztem Recht schlaegt er fehl und nennt die Datei. Nebenbei aufgefallen: scripts/version.mjs datierte in UTC. Diese Fassung waere um 00:08 deutscher Zeit auf den Vortag datiert worden. Rechnet jetzt in Europe/Berlin.
88 lines
3.3 KiB
TypeScript
88 lines
3.3 KiB
TypeScript
import { execFileSync } from 'node:child_process'
|
|
import { readFileSync } from 'node:fs'
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
/**
|
|
* Die Betriebsskripte müssen im Repository so liegen, dass sie auf dem
|
|
* Server auch laufen.
|
|
*
|
|
* Anlass sind zwei Fehler, die beide erst im Betrieb aufgefallen sind und
|
|
* beide nichts mit dem Programm zu tun hatten, sondern mit der Verpackung:
|
|
*
|
|
* 1. `scripts/` fehlte im Docker-Abbild — `node scripts/totp-geheimnis.mjs`
|
|
* lief ins Leere (behoben in 1.0.1).
|
|
* 2. Die `.sh`-Dateien lagen mit Modus 644 im Repository. Die Anleitung
|
|
* sagt `./scripts/update.sh`, auf Linux kam „Permission denied".
|
|
*
|
|
* Beide Male hat kein Test angeschlagen, weil beide Male der Code richtig
|
|
* war. Diese Datei prüft deshalb nicht Verhalten, sondern Verpackung.
|
|
*
|
|
* Der Ausführbarkeitsbit wird aus dem Git-Index gelesen, nicht vom
|
|
* Dateisystem: Unter Windows steht `core.filemode=false`, dort sagt das
|
|
* Dateisystem nichts aus. Maßgeblich ist, was ausgecheckt wird.
|
|
*/
|
|
|
|
function gitVerfuegbar(): boolean {
|
|
try {
|
|
execFileSync('git', ['rev-parse', '--git-dir'], { stdio: 'pipe' })
|
|
return true
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
/** Liefert [Modus, Pfad] je Datei, so wie sie im Index steht. */
|
|
function eintraegeImIndex(muster: string): [string, string][] {
|
|
const ausgabe = execFileSync('git', ['ls-files', '-s', muster], { encoding: 'utf8' })
|
|
return ausgabe
|
|
.split('\n')
|
|
.filter((zeile) => zeile.trim() !== '')
|
|
.map((zeile) => {
|
|
const [angaben, pfad] = zeile.split('\t')
|
|
return [angaben!.split(' ')[0]!, pfad!]
|
|
})
|
|
}
|
|
|
|
const mitGit = gitVerfuegbar()
|
|
|
|
describe.skipIf(!mitGit)('Betriebsskripte', () => {
|
|
it('legt jedes Shell-Skript ausführbar ab', () => {
|
|
const skripte = eintraegeImIndex('scripts/*.sh')
|
|
|
|
// Wenn hier nichts steht, stimmt das Muster nicht mehr - das waere
|
|
// ein stiller Ausfall der Pruefung.
|
|
expect(skripte.length).toBeGreaterThan(0)
|
|
|
|
for (const [modus, pfad] of skripte) {
|
|
expect(modus, `${pfad} muss ausführbar sein (100755, ist ${modus})`).toBe('100755')
|
|
}
|
|
})
|
|
|
|
it('gibt jedem Shell-Skript eine Shebang-Zeile', () => {
|
|
for (const [, pfad] of eintraegeImIndex('scripts/*.sh')) {
|
|
const ersteZeile = readFileSync(pfad, 'utf8').split('\n')[0] ?? ''
|
|
expect(ersteZeile, `${pfad} braucht eine Shebang-Zeile`).toMatch(/^#!/)
|
|
}
|
|
})
|
|
|
|
it('nimmt scripts/ ins Docker-Abbild auf', () => {
|
|
// Der Fehler aus 1.0.1: Die Wartungsskripte lagen im Repository, aber
|
|
// nicht im Behaelter. Aufgefallen ist es erst, als sie gebraucht wurden.
|
|
const dockerfile = readFileSync('Dockerfile', 'utf8')
|
|
// Die Zeile traegt zusaetzliche Schalter (--from, --chown) - deshalb
|
|
// nicht auf eine feste Reihenfolge festlegen, sondern nur darauf, dass
|
|
// scripts/ ins Abbild kopiert wird.
|
|
expect(dockerfile).toMatch(/^COPY.*\/scripts\s+\.\/scripts\s*$/m)
|
|
})
|
|
|
|
it('bindet die Sicherungsskripte aus scripts/ in die Compose-Datei ein', () => {
|
|
// Sie werden als Volume in den Sicherungsdienst gehaengt. Ein
|
|
// umbenanntes Skript faellt sonst erst um 03:15 Uhr auf - im Log,
|
|
// das niemand liest.
|
|
const compose = readFileSync('docker-compose.yml', 'utf8')
|
|
for (const name of ['sicherung.sh', 'wiederherstellung.sh']) {
|
|
expect(compose, `${name} wird nicht eingebunden`).toContain(`./scripts/${name}:`)
|
|
}
|
|
})
|
|
})
|