Files
Archivum/ARCHITECTURE.md
Bjorn Blomberg 5819a6bdce
All checks were successful
build-and-push / build (push) Successful in 1m7s
feat(gitsync): webhook-triggad synk + helt manuellt läge
- POST /api/git-hook: HMAC-SHA256-verifierad (X-Gitea-Signature),
  reagerar bara på pushar till live-grenen, svarar 202 och synkar async
- webhook-hemlighet genereras server-side (headless via config.json),
  roteras med gitRegenerateWebhookSecret
- auto_sync_minutes=0 + webhook av ⇒ ingen automatisk hämtning alls;
  webhook på ⇒ catch-up-synk vid uppstart (missade event)
- admin-UI: webhook-toggle, target-URL + secret med copy/rotate

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 23:52:01 +02:00

27 KiB

Archivum — Arkitektur och designdokumentation

Innehållsförteckning

  1. Systemöversikt
  2. Frontend
  3. Backend
  4. Exekveringsflöden
  5. Kodstruktur
  6. Containerisering och driftsättning

1. Systemöversikt

Archivum är en självhostad wiki/dokumenthanterare. Alla dokument lagras som AsciiDoc-filer (.adoc) på disk och versionshanteras automatiskt med Git. Frontenden är en SPA (Single-Page Application) som kommunicerar med backenden uteslutande via GraphQL.

Webbläsare
   │  HTTPS / HTTP
   ▼
┌──────────────────────────────┐
│  Go HTTP-server :4000        │
│  ┌──────────┐  ┌──────────┐ │
│  │ /graphql │  │  SPA /   │ │
│  └────┬─────┘  └──────────┘ │
│       │                      │
│  ┌────▼──────────────────┐   │
│  │  GraphQL dispatcher   │   │
│  └─┬──────┬──────┬───────┘   │
│  Auth   Storage  Git         │
│  (SQLite)(disk) (os/exec)    │
└──────────────────────────────┘

Teknologistack

Lager Teknik
Frontend Vue 3 (Composition API), Vite, TypeScript, Tailwind CSS
State Pinia
Routing Vue Router 4
Editor TipTap 2 (visuell, med custom image extension), CodeMirror 6 (AsciiDoc-källa)
AsciiDoc Asciidoctor.js (rendering i webbläsaren)
PWA vite-plugin-pwa (Service Worker, manifest)
Backend Go 1.22, HTTP-standardbibliotek
Databas SQLite via modernc.org/sqlite (CGO-fri, multi-arch)
Autentisering OIDC/SSO (Authentik) + lokala konton (bcrypt) + LDAP, sessions-tokens i minnet
RBAC / ACL Roll via OIDC-grupper; allow/deny-ACL per sökväg för användare & grupper
Versionshantering Git via os/exec
Containerisering Docker (multi-stage, multi-arch: amd64 + arm64)

2. Frontend

Paket och beroenden

frontend/
├── src/
│   ├── App.vue             # Rotemponent — kontrollerar setup/login/app-läge
│   ├── main.ts             # Appstart, monterar Vue + Pinia + Router
│   ├── lib/
│   │   ├── gql.ts          # Minimal GraphQL-klient (fetch-baserad)
│   │   └── crypto.ts       # Lösenords-hashing (SHA-256 via Web Crypto API)
│   ├── stores/
│   │   ├── app.ts          # Global state: token, requiresSetup, login/logout
│   │   └── theme.ts        # Mörkt/ljust läge, persistas i localStorage
│   ├── router/index.ts     # SPA-routes
│   ├── views/              # Sidkomponenter
│   ├── components/         # Återanvändningsbara UI-komponenter
│   │   ├── editor/         # Visuell och källkodseditor
│   │   ├── layout/         # AppLayout, Sidebar, ThemeToggle
│   │   └── wizard/         # Installationsguide (SetupWizard)
│   ├── bridge/
│   │   └── asciidoc-bridge.ts  # AsciiDoc ↔ TipTap JSON-konvertering
│   └── assets/main.css     # Tailwind-bas + CSS-variabler

Routing

Sökväg Komponent Åtkomstkrav
/ HomeView Inloggad
/doc/:slug DocumentView Inloggad
/admin AdminView Inloggad, admin
/setup SetupWizard Ej konfigurerat

Routeskydd hanteras inte med router guards utan direkt i App.vue — se avsnitt 4.1.

GraphQL-klienten (lib/gql.ts)

Alla API-anrop görs med en enkel fetch-wrapper. Klienten:

  • Läser JWT-token från localStorage och skickar den som Authorization: Bearer <token>
  • Kastar ett Error om svaret innehåller GraphQL-errors
  • Hanterar automatisk utloggning vid UNAUTHORIZED-fel

Lösenordshantering (lib/crypto.ts)

Lösenordet hashas på klienten innan det skickas till servern med:

SHA-256(lowercase(username) + ":" + password)

Web Crypto API (crypto.subtle.digest) används — ingen extern beroende. Username fungerar som domän-separator (salt) så att samma lösenord ger olika digest för olika konton. Backenden tar emot denna digest och lagrar bcrypt(digest).

PWA

Konfigureras i vite.config.ts via vite-plugin-pwa:

  • Service Worker med skipWaiting + clientsClaim (ny SW aktiveras omedelbart)
  • Cachar statiska assets (JS, CSS, fonts)
  • HTML och /graphql går alltid till nätverket (NetworkFirst)
  • navigateFallback: null — förhindrar att SW returnerar cachat HTML för routes

3. Backend

Paket

backend/
├── cmd/server/main.go      # Startpunkt — laddar config, startar HTTP-server
├── internal/
│   ├── config/config.go    # Config-struktur, Load/Save, ErrRequireSetup
│   ├── auth/auth.go        # Session-hantering, bcrypt, LDAP-bind
│   ├── db/db.go            # SQLite-wrapper — users-tabell
│   ├── storage/storage.go  # Filhantering — Read/Write/List .adoc-filer
│   ├── git/git.go          # Git-wrapper via os/exec — Commit/Log/Diff
│   └── graph/
│       ├── server.go       # HTTP-handler + handgjord GraphQL-dispatcher
│       ├── resolver.go     # Resolver-stub (plats för gqlgen-genererad kod)
│       └── schema.graphql  # GraphQL-schema

GraphQL-dispatcher

Backenden använder inte gqlgen-genererad kod i produktion. server.go implementerar en handgjord dispatcher som parsar query-strängen med strings.Contains och router till respektive handler-funktion. Detta ger enkel deployment utan kodgenerering men kräver manuell underhåll av schema och handlers.

Autentisering och sessioner

Login-flöde (lokalt konto):
  1. Frontend: kontrollerar authType (query userAuthType) → "local"
  2. Frontend: SHA-256(username:password) → skickas som "password"
  3. Backend: bcrypt.CompareHashAndPassword(stored_hash, received_digest)
  4. Vid match: slumpmässig hex-token genereras och sparas i minnet
  5. Token returneras till klient, lagras i localStorage

Login-flöde (LDAP-konto):
  1. Frontend: kontrollerar authType → "ldap"
  2. Frontend: skickar klartextlösenord i fältet "ldapPassword" (EJ hashat)
  3. Backend: hämtar användarens DN via LDAP-sökning (baseDN + filter)
  4. Backend: binder till LDAP med DN + klartextlösenord (ldapBind)
  5. Vid lyckad bind: session skapas med roll "user"

Login-flöde (gäst):
  1. Frontend: klickar "Continue as Guest" → skickar username="guest", password=""
  2. Backend: identifierar gäst, skapar session utan lösenordskontroll, roll "guest"
  3. Gäst ser bara dokument/mappar som admin explicit gett tillåtelse via ACL

Session-storage: in-memory map[token]*Session (försvinner vid omstart)
Session-livstid: 24 timmar (konfigurerat i auth.go)

**Rollbaserad UI-synlighet:**
Frontendens Pinia-store (`app.ts`) lagrar den inloggade användarens roll (`admin`, `user`, `guest`) via query `currentUserRole` som körs direkt efter inloggning. Admin-panelslänken i sidofältet visas bara om `role === 'admin'`.

Viktigt: Sessions lagras enbart i minnet. Vid server-omstart måste alla klienter logga in igen. LDAP-konfigurationsändringar (via admin-panelen) nollställer inte sessioner — befintliga sessioner är fortfarande giltiga.

Gästanvändare

En inbyggd guest-användare skapas automatiskt vid installation och vid serverstart (migrations-safe). Gästen:

  • Kräver inget lösenord
  • Har roll "guest" (skild från "user" och "admin")
  • Kan som standard inte se något
  • Admin ger tillgång per dokument eller mapp via ACL-panelen

Autentisering via OIDC (Authentik)

Archivum är en confidential OIDC-klient mot Authentik. Flödet är rent browser-redirect och ligger utanför GraphQL (två HTTP-endpoints):

Login-sida → GET /auth/oidc/login
   → 302 till Authentik (authorize, scope: openid profile email groups, state)
   → användaren autentiseras (Authentik → OpenLDAP)
   → 302 tillbaka GET /auth/oidc/callback?code&state
        ├─ validerar state (CSRF), byter code→token (backend↔Authentik, TLS)
        ├─ verifierar id_token (JWKS via go-oidc), läser preferred_username + groups
        ├─ mappar grupp → roll:  Archivum-admin → admin, annars user
        ├─ speglar användare + grupper till SQLite (för admin-UI + ACL-mål)
        ├─ login-grind: användare/grupp måste vara tillåten
        └─ 302 till /oidc/callback#token=…  (SPA:n läser token ur fragmentet)

Konfiguration ligger i config.oidc (issuer, client_id/secret, grupp-mappning) och sätts i admin-panelen. Discovery körs i bakgrunden vid start/ändring så att en otillgänglig IdP inte blockerar servern. Se docs/AUTHENTIK_SETUP.md.

Åtkomstkontroll (allow/deny-ACL)

ACL-poster lagras i SQLite-tabellen acl. Varje post kopplar en sökväg (slug eller mappnamn) till ett subjekt (användare eller grupp), en effekt (allow/deny) och sju rättighetsflaggor:

Flagga Innebär
canSearch Syns i sökning/listning
canView Syns i mapplistning
canRead Kan öppna och läsa innehållet
canEdit Kan redigera och spara
canCreate Kan skapa nya dokument i mappen
canDelete Kan radera
canMove Kan flytta/byta namn

Effektiv rättighet (db.EffectiveAccess) beräknas för (användare, sökväg, handling) genom att kombinera alla poster som gäller subjektet — användaren själv plus alla dess grupper (från OIDC-claimet eller lokalt medlemskap) — över sökvägen och alla dess överliggande mappar:

  1. Standard = deny.
  2. En allow på handlingen ger tillgång.
  3. En deny på handlingen vinner alltid över allow.

Alltså: tillgång ges bara om någon post tillåter och ingen post nekar. Sökvägar är hierarkiska (en allow på mappen docs gäller allt under docs/).

Enforcement sker för alla icke-admins (inloggade användare, Archivum-reader och den publika/gäst-användaren) i alla operationer — listning, läsning, spara, radera, flytta, skapa, historik/diff och bilduppladdning. Admin kringgår ACL.

Känd begränsning: /media/<bild> serveras utan ACL eftersom bilder laddas via <img> utan Authorization-header. Skydda känsliga bilder på annat sätt (t.ex. signerade URL:er) om det behövs.

Setup-detektion — needsSetup

Backenden räknar ett system som oklonfigurerat (REQUIRE_SETUP) om något av följande är sant:

  1. config.json saknas eller är ofullständig (cfg == nil)
  2. Databasen kunde inte öppnas (database == nil)
  3. Databasen är tom — ingen adminanvändare har skapats (!database.HasUsers())

db.HasUsers() gör en enkel SELECT COUNT(*) FROM users och returnerar true om minst ett konto finns. Detta innebär att en nyskapad, tom SQLite-fil (som Go skapar vid db.New()) fortfarande triggar setup-läge.

DB-sökväg — resolveDBPath()

Setup-handleren bestämmer DB-sökvägen dynamiskt för att fungera i både Docker och dev-miljö:

resolveDBPath(configPath):
  ├─ Kan /data/db skapas/skrivas? → /data/db/archivum.db  (Docker-volym)
  └─ Annars               → <config-katalog>/archivum.db  (dev-miljö)

Git-synk mot remote (SSH)

Wiki-repot kan synkas mot ett remote git-repo (t.ex. Gitea) över SSH. Implementation: internal/git/sync.go (remote-operationer + mutex) och internal/graph/gitsync.go (GraphQL-handlers, nyckelhantering, bakgrunds- ticker). Admin-UI: frontend/src/components/GitSyncSection.vue med MergeConflictDialog.vue som popup vid konflikter.

  • SSH-nyckel: servern genererar själv ett ed25519-nyckelpar vid första aktivering, lagrat i <config-dir>/ssh/ (0600). Publika nyckeln visas i admin-panelen och registreras som deploy key hos git-värden. GIT_SSH_COMMAND pekar på nyckeln + egen known_hosts (accept-new) eftersom appen kör utan hemkatalog. Privata nyckeln exponeras aldrig via API:t.
  • Live-gren (live_branch): grenen som bakgrundssynken håller uppdaterad. Bakgrundssynk (intervall i auto_sync_minutes, 0 = manuell) gör endast fast-forward-pulls; allt som kräver riktig merge flaggas för admin (attention i gitSyncStatus).
  • Read-only (read_only): pull tillåts men aldrig push (spärras i backend).
  • Grenar: skapa och byt gren i admin-panelen; remote-grenar spåras automatiskt vid byte.
  • Merge-konflikter: pull lämnar mergen öppen och UI:t visar en popup där man väljer lokal/remote per fil eller för allt; alternativt avbryt mergen.
  • Reset från remote: tvingar lokala grenen att matcha remoten; lokalt läge sparas alltid först i en backup-gren (backup/<timestamp>). Används också för bootstrap när historikerna är orelaterade (UNRELATED_HISTORIES).
  • Webhook (webhook_enabled/webhook_secret): git-värden kan trigga synk direkt vid push via POST /api/git-hook i stället för (eller utöver) intervall-pollning. Anropet verifieras med HMAC-SHA256 över råa bodyn (Giteas X-Gitea-Signature); hemligheten genereras server-side och roteras med gitRegenerateWebhookSecret. Endast pushar till live-grenen triggar; vid uppstart görs en catch-up-synk (event som missats medan servern var nere). Med auto_sync_minutes: 0 och webhook av sker ingen automatisk hämtning alls.
  • Headless-konfiguration: allt kan sättas via config.json-sektionen git_sync + omstart, eller via GraphQL-mutationen updateGitSync — inget webbgui krävs.

Konfiguration

config.json hanteras av internal/config:

  • storage_path — rotkatalog för .adoc-filer och Git-repo
  • db_path — sökväg till SQLite-fil
  • jwt_secret — används för session-token generation (ej JWT i klassisk mening)
  • listen_addr — TCP-adress att lyssna på, t.ex. :4000
  • public_url — extern bas-URL (t.ex. https://archivum.brasse-pc.eu), används för att härleda OIDC-redirect-URI
  • git_sync — se avsnittet ovan: enabled, remote_url, live_branch, read_only, auto_sync_minutes, webhook_enabled, webhook_secret
  • oidc — valfri OIDC/SSO-konfiguration (Authentik):
    • enabled, issuer, client_id, client_secret, redirect_url
    • groups_claim (default groups), username_claim (default preferred_username)
    • admin_group (default Archivum-admin), reader_group (default Archivum-reader)
  • ldap — valfri LDAP-konfiguration (direkt bind, äldre alternativ):
    • url — LDAP-server URL, t.ex. ldap://openldap:389
    • base_dn — rot-DN att söka ifrån, t.ex. dc=example,dc=com
    • admin_user — tjänstekonto DN för uppslag (valfritt)
    • admin_pass — tjänstekontoets lösenord (valfritt)

Om filen saknas returnerar Load() ErrRequireSetup — backenden startar i setup-läge.


4. Exekveringsflöden

4.1 Appstart och setup-detektion

Browser laddar /
      │
      ▼
App.vue onMounted()
      │
      ├─ theme.init()          (laddar tema från localStorage)
      │
      ├─ app.checkStatus()     → POST /graphql { systemStatus }
      │       │                  Backend: needsSetup = cfg==nil || db==nil || !db.HasUsers()
      │       │
      │       ├─ svar: "REQUIRE_SETUP"
      │       │       → app.requiresSetup = true
      │       │       → visas: <SetupWizard /> (renderas direkt, ej via router)
      │       │
      │       └─ svar: "OK"
      │               → app.requiresSetup = false
      │               ├─ app.token finns: → visas: <AppLayout />
      │               └─ app.token saknas: → visas: <LoginView />
      │
      └─ ready = true          (spinner försvinner)

SetupWizard, LoginView och AppLayout renderas direkt i App.vue (inte via <router-view>) för att undvika race conditions mellan router.replace() och Vue-renderingen.

4.2 Installationsguiden (SetupWizard)

Steg 1: Ange storage-sökväg, admin-användare, lösenord
Steg 2: Valfri LDAP-konfiguration
Steg 3: Bekräftelse

Vid submit:
  1. SHA-256(adminUser:adminPass) → hashedPass
  2. POST /graphql mutation Setup { setup(input: {..., adminPass: hashedPass}) }
     ├─ Backend: resolveDBPath() → bestämmer db_path (Docker vs dev)
     ├─ Backend: skapar SQLite-DB och admin-användare med bcrypt(hashedPass)  ← FÖRST
     ├─ Backend: sparar config.json                                           ← SEDAN
     └─ Backend: initierar storage-katalog och Git-repo
  3. app.requiresSetup = false
  4. app.login(adminUser, adminPass)  → SHA-256 igen → POST /graphql mutation Login
  5. Redirect till /

Ordningen är viktig: om DB-skapandet eller user-insert misslyckas sparas ingen config.json. Nästa sidladdning ger återigen REQUIRE_SETUP och guiden visas på nytt. Tidigare sparades config först, vilket kunde lämna ett halvfärdigt tillstånd där backenden startade med config men utan adminanvändare.

4.3 Inloggningsflöde

Användaren fyller i användarnamn + lösenord (eller klickar "Continue as Guest")
      │
      ▼
app.login(username, password)
      │
      ├─ query userAuthType(username) → "local" | "ldap" | "guest"
      │
      ├─ authType == "local":
      │       SHA-256(lowercase(username):password) → hashed
      │       → POST /graphql mutation Login(u, p: hashed)
      │
      ├─ authType == "ldap":
      │       → POST /graphql mutation Login(u, p: "", ldapPassword: plaintext)
      │
      └─ authType == "guest":
              → POST /graphql mutation Login(u: "guest", p: "")
              │
              ▼
         handleLogin (server.go)
              │
              ├─ "guest": returnerar token utan lösenordskontroll
              │
              ├─ "ldap" (is_ldap=1 i DB): ldapBind(username, ldapPassword)
              │       → OK: session med roll "user"
              │
              └─ "local" (is_ldap=0): bcrypt.Compare(stored, hashed)
                      → OK: session med roll baserad på DB-värde

Gäst-session:
  - Roll "guest" — se ACL-enforcement i handleDocuments / handleFolders
  - Kan bara se dokument/mappar som admin explicit beviljat via ACL

4.4 Spara dokument

Användaren redigerar och klickar Spara
      │
      ▼
POST /graphql mutation SaveDocument
    { slug, content (AsciiDoc), commitMessage, author }
      │
      ▼
handleSaveDocument (server.go)
      │
      ├─ Kontrollerar session (Bearer token → Manager.Validate())
      ├─ storage.Write(slug, content)   → skriver slug.adoc till disk
      └─ git.Commit(slug.adoc, message, author, email)
              │
              └─ os/exec: git add → git commit

4.5 Versionshistorik och diff

GET /graphql { history(slug) }
      │
      └─ git.Log(slug.adoc) → os/exec: git log --format=...
             → []LogEntry { Hash, Author, Date, Subject }

GET /graphql { diff(slug, fromHash, toHash) }
      │
      └─ git.Diff(slug.adoc, from, to) → os/exec: git diff
             → unified diff-sträng

5. Kodstruktur

Fullständig katalogstruktur

Archivum/
├── backend/
│   ├── cmd/server/main.go          # Entrypoint
│   ├── config.json                 # Lokal dev-konfiguration (gitignoreras ej — bör .gitignore:as i produktion)
│   ├── go.mod / go.sum
│   └── internal/
│       ├── auth/auth.go            # Session, bcrypt, LDAP
│       ├── config/config.go        # Config-struktur och fil-I/O
│       ├── db/db.go                # SQLite users-tabell
│       ├── git/git.go              # Git-wrapper
│       ├── storage/storage.go      # .adoc fil-I/O
│       └── graph/
│           ├── schema.graphql      # GraphQL-schema (source of truth)
│           ├── resolver.go         # Resolver-stub
│           └── server.go           # HTTP-server + dispatcher + alla handlers
│
├── frontend/
│   ├── index.html
│   ├── vite.config.ts              # Vite + PWA + dev-proxy konfiguration
│   ├── tailwind.config.js
│   ├── package.json
│   └── src/
│       ├── App.vue                 # Rotkomponent med setup/login/app-routing
│       ├── main.ts
│       ├── lib/
│       │   ├── gql.ts              # GraphQL fetch-klient
│       │   └── crypto.ts           # SHA-256 lösenordshashing
│       ├── stores/
│       │   ├── app.ts              # Global app-state (Pinia)
│       │   └── theme.ts            # Temahantering (Pinia)
│       ├── router/index.ts
│       ├── views/
│       │   ├── HomeView.vue        # Dokumentlista
│       │   ├── DocumentView.vue    # Visa/redigera dokument
│       │   ├── AdminView.vue       # Administrationspanel
│       │   └── LoginView.vue       # Inloggningsformulär
│       ├── components/
│       │   ├── editor/
│       │   │   ├── VisualEditor.vue   # TipTap WYSIWYG-editor
│       │   │   └── SourceEditor.vue   # CodeMirror AsciiDoc-källeditor
│       │   ├── layout/
│       │   │   ├── AppLayout.vue      # Huvudlayout med sidebar
│       │   │   ├── Sidebar.vue        # Dokumentnavigering
│       │   │   └── ThemeToggle.vue    # Ljust/mörkt läge-knapp
│       │   └── wizard/
│       │       └── SetupWizard.vue    # Installationsguide (3 steg)
│       ├── bridge/
│       │   └── asciidoc-bridge.ts    # AsciiDoc ↔ TipTap JSON
│       └── assets/main.css
│
├── docker/
│   ├── Dockerfile                  # Multi-stage build (frontend → backend → runtime)
│   ├── entrypoint.sh               # PUID/PGID-hantering, katalogskapande
│   ├── docker-compose.yml          # Dev/standard deploy
│   └── docker-compose.prod.yml     # Produktionsinställningar
│
└── ARCHITECTURE.md                 # Detta dokument

Nyckelgränssnitt och datakontrakt

GraphQL-schema (urval):

type Query {
  systemStatus: SystemStatus!
  userAuthType(username: String!): String!  # "local" | "ldap" | "guest"
  currentUserRole: String!                  # Returnerar rollen för inloggad session ("admin" | "user" | "guest")
  documents(prefix: String): [DocumentMeta!]!
  document(slug: String!): Document
  folders: [String!]!
  history(slug: String!): [CommitEntry!]!
  diff(slug: String!, fromHash: String!, toHash: String!): String!
  acl(path: String!): [ACLEntry!]!
  aclSubjects: ACLSubjects!                 # Användare + grupper för ACL-picker (inkl. guest)
  ldapBrowse(url: String, baseDN: String, adminUser: String, adminPass: String): LDAPTree!
}

type Mutation {
  login(username: String!, password: String!, ldapPassword: String): String!
  logout: Boolean!
  setup(input: SetupInput!): Boolean!
  saveDocument(input: SaveDocumentInput!): Document!
  deleteDocument(slug: String!): Boolean!
  updateLdapConfig(input: LDAPInput): Boolean!
  importLdapSubject(type: String!, name: String!): Boolean!
  setAcl(input: ACLInput!): Boolean!
  removeAcl(id: Int!): Boolean!
  createFolder(path: String!): Boolean!
  moveDocument(oldSlug: String!, newSlug: String!): Boolean!
}

input LDAPInput {
  url:       String!
  baseDN:    String
  adminUser: String!
  adminPass: String   # Utelämnas → befintligt lösenord behålls
}

6. Containerisering och driftsättning

Dockerfile (multi-stage)

Stage 1 (frontend-builder) — node:20-alpine
  npm install && npm run build → /app/frontend/dist

Stage 2 (backend-builder) — golang:1.22-alpine
  CGO_ENABLED=0 go build → /archivum (statisk binär)

Stage 3 (runtime) — alpine:3.20
  Installerar: su-exec, git
  Kopierar: dist/ → /srv/archivum/ui
            archivum → /usr/local/bin/archivum
            entrypoint.sh
  Volumes: /config, /data
  Port: 4000
  Entrypoint: entrypoint.sh

Volymer och kataloger

Volym Innehåll Env-variabel
/config config.json DOCKER_PATH
/data wiki/ (AsciiDoc + Git-repo), db/archivum.db

Miljövariabler

Variabel Standard Beskrivning
DOCKER_PATH Host-sökväg som monteras som /config
UI_DIR /srv/archivum/ui Sökväg till kompilerad frontend
PUID 1000 Filsystemsägare (UID)
PGID 1000 Filsystemsägare (GID)
TZ Europe/Stockholm Tidszon
HOST_PORT 8080 Extern port i docker-compose

Multi-arch build

Bilden stöder linux/amd64 (server/desktop) och linux/arm64 (Raspberry Pi 5):

docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t registry:5000/archivum:latest \
  -f docker/Dockerfile --push .

entrypoint.sh — PUID/PGID-hantering

Containern startar som root. entrypoint.sh:

  1. Skapar /config, /data/wiki, /data/db om de saknas
  2. Ändrar ägare på volymerna till PUID:PGID
  3. Kör applikationen som PUID:PGID via su-exec

docker-compose.yml (minimal .env)

DOCKER_PATH=/opt/archivum    # Host-sökväg — config/ och data/ skapas här
HOST_PORT=8080
PUID=1000
PGID=1000
TZ=Europe/Stockholm

Uppgraderingsförfarande

  1. Bygg/hämta ny image
  2. docker compose down
  3. docker compose up -d

Inga databas-migreringar behövs i de flesta uppgraderingar; SQLite-schemat är bakåtkompatibelt (CREATE TABLE IF NOT EXISTS).