fix: Skripte lagen ohne Ausfuehrungsrecht im Repository
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.
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
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}:`)
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user