feat(aether): initial commit – self-hosted Cloud, Phasen 1–7

Go-Backend (chi, pgx, golang-migrate) + React/TS-Frontend als PWA.
Enthalten: Auth inkl. OIDC, Dateien mit Versionen und Papierkorb,
Freigaben (Links + intern), WebDAV, Gruppenordner, Admin/Theming,
Volltextsuche mit OCR, eigener PDF-Betrachter und WOPI-Office
(Collabora/OnlyOffice).

Nicht im Repo: deploy/.env (Sitzungsschlüssel, DB- und Admin-Passwort)
sowie alle Daten- und Build-Verzeichnisse – siehe .gitignore.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Opus 5 committed 2026-08-24 09:25:36 +02:00
commit 31a027c93a
225 files changed
+38042

No files matched your search

+66
View File
@@ -0,0 +1,66 @@
package documents
import (
"fmt"
"os"
"path/filepath"
"strings"
"syscall"
)
// minFreeBytes ist die Untergrenze, unter der die Texterkennung pausiert.
//
// OCR ist das einzige Stück dieser Anwendung, das von sich aus und ohne
// Zutun eines Menschen Platz verbraucht: Es arbeitet eine Warteschlange ab,
// solange etwas darin liegt. Eine Cloud, die ihre eigene Platte vollschreibt,
// nimmt Uploads nicht mehr an – und das wäre dann kein OCR-Problem mehr,
// sondern ein Ausfall.
//
// 2 GiB ist mit Absicht großzügig: Der Sidecar einer Erkennung ist kaum größer
// als ein Megabyte, tesseract und ghostscript brauchen währenddessen aber
// Arbeitsdateien, deren Größe von der Auflösung der Vorlage abhängt. Lieber
// eine Weile nichts erkennen als eine volle Platte.
const minFreeBytes = 2 << 30 // 2 GiB
// freeBytes meldet den freien Platz auf dem Dateisystem, auf dem path liegt.
func freeBytes(path string) (uint64, error) {
var st syscall.Statfs_t
if err := syscall.Statfs(path, &st); err != nil {
return 0, fmt.Errorf("statfs %s: %w", path, err)
}
// Bavail, nicht Bfree: Bfree zählt auch die Reserve mit, die nur root
// benutzen darf – der Server läuft aber als unprivilegierter Nutzer und
// käme daran nie heran.
return st.Bavail * uint64(st.Bsize), nil
}
// sweepTemp räumt Zwischendateien früherer Läufe weg.
//
// Im Normalfall räumt der Arbeiter selbst auf. Wird der Container aber hart
// beendet – `docker kill`, ein Absturz, Stromausfall –, läuft kein defer mehr,
// und die angefangene Datei bleibt liegen. Ohne diesen Durchgang sammelt sich
// das über Monate an, ausgerechnet auf der Platte, die auch die Dateien trägt.
//
// Es wird nur weggeräumt, was diese Anwendung selbst angelegt hat: das Präfix
// "ocr-" im Zwischenverzeichnis. Fremde Dateien bleiben unangetastet.
func sweepTemp(dir string) (int, int64) {
entries, err := os.ReadDir(dir)
if err != nil {
return 0, 0
}
var count int
var bytes int64
for _, e := range entries {
if e.IsDir() || !strings.HasPrefix(e.Name(), "ocr-") {
continue
}
full := filepath.Join(dir, e.Name())
if info, err := e.Info(); err == nil {
bytes += info.Size()
}
if err := os.Remove(full); err == nil {
count++
}
}
return count, bytes
}