Files
archivmail/cmd/archivmail/cmd_migrate_tenant_dirs.go
T
sysops a15fa37619 feat(PROJ-65): Physische Tenant-Trennung im Storage-Layer (Hardlink-Ordner)
Jeder Tenant bekommt ein eigenes Verzeichnis store/tenant_<id>/, das per
Hardlink auf die kanonische content-adressierte Datei zeigt — das bestehende
Cross-Tenant-Dedup-Modell (email_refs M:N, PROJ-32/37) bleibt dadurch
erhalten, kein Speicherplatz-Mehrverbrauch. Neues CLI-Subcommand
`archivmail migrate-tenant-dirs` zieht Bestandsdaten einmalig nach
(idempotent). Zusätzlich neuer Status-Check checkStoragePermissions
(warnt bei zu offenen store_path-Rechten, analog checkEncryption/PROJ-49).

DB-gestützte Zugriffskontrolle bleibt der maßgebliche Zugriffspfad im Code;
die Tenant-Ordner sind eine zusätzliche Defense-in-Depth-Ebene für manuelle
Dateisystem-Audits. Kein lokaler go build möglich, QA folgt auf Testserver.
2026-07-04 13:03:52 +02:00

54 lines
1.6 KiB
Go

package main
import (
"context"
"flag"
"log/slog"
"os"
"archivmail/config"
"archivmail/internal/storage"
)
// runMigrateTenantDirs backfills per-tenant hardlink directories (PROJ-65)
// for mails that were archived before this version. New mails get their
// tenant hardlink at Save() time already — this is a one-time catch-up run
// for the existing archive after upgrading. Idempotent: safe to re-run,
// only fills in missing links.
//
// Usage: archivmail migrate-tenant-dirs [-config /etc/archivmail/config.yml]
func runMigrateTenantDirs(args []string) {
fs := flag.NewFlagSet("migrate-tenant-dirs", flag.ExitOnError)
configPath := fs.String("config", "/etc/archivmail/config.yml", "path to config file")
fs.Parse(args)
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
cfg, err := config.Load(*configPath)
if err != nil {
logger.Error("failed to load config", "err", err)
os.Exit(1)
}
storeCfg := storage.Config{
Dir: cfg.Storage.StorePath,
Keyfile: cfg.Storage.Keyfile,
DSN: cfg.Database.DSN(),
CompressEnabled: cfg.Storage.Compress,
}
mailStore, err := storage.New(storeCfg)
if err != nil {
logger.Error("storage init failed", "err", err)
os.Exit(1)
}
defer mailStore.Close()
logger.Info("migrate-tenant-dirs: starting backfill")
linked, errCount, err := mailStore.BackfillTenantDirs(context.Background())
if err != nil {
logger.Error("migrate-tenant-dirs: failed", "err", err)
os.Exit(1)
}
logger.Info("migrate-tenant-dirs: complete", "linked", linked, "errors", errCount)
}