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:
commit
31a027c93a
225 files changed
+38042
No files matched your search
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user