Architecture — Plik (System-Wide) | Plik

Architecture — Plik (System-Wide)

High-level architecture, package layering, data flows, and API reference for the entire Plik system. For sub-package details, see the scoped ARCHITECTURE.md in each root folder.


System Overview

Plik is a temporary file upload system with three main components:

ComponentLocationPurpose
Server (plikd)server/Go HTTP server — REST API, middleware chain, data/metadata backends
CLI Client (plik)client/Multi-platform command-line uploader with archive/crypto support and MCP server mode
Webappwebapp/Vue 3 SPA served by the server
Go Libraryplik/Public Go client library (used by CLI + e2e tests)

Core Abstractions

TypePackageDescription
Uploadserver/commonContainer for files — has TTL, options (OneShot, Stream, Removable), password protection, E2EE scheme
Fileserver/commonIndividual file within an upload — has status, size, type, md5
Userserver/commonAuthenticated user (local, Google, OVH, OIDC) — has quotas
Tokenserver/commonUpload token (UUID) — authenticates CLI clients on behalf of a user

High-Level Architecture


Package Layering

Dependency direction flows left to right. Packages only import from packages to their left.

common → context → metadata/data → middleware → handlers → cmd → server
PackagePurpose
server/commonShared types (Upload, File, User, Token), config, feature flags, utilities
server/contextCustom request context (predates Go stdlib context.Context) — carries config, backends, auth state, upload/file/user through the middleware chain
server/dataBackend interface + implementations (file, swift, s3, gcs, stream, testing)
server/metadataGORM-based metadata storage + migrations (via gormigrate)
server/middlewareMiddleware chain: auth, logging, source IP, pagination, upload/file resolution
server/handlersHTTP handler functions
server/cmdCLI commands (cobra): server start, user management, import/export
server/serverHTTP server setup, router configuration, backend initialization

Request Lifecycle

Every HTTP request flows through a middleware chain before reaching a handler:

Middleware Chains

The server defines several middleware chains composed from individual middlewares:

ChainMiddlewaresUsed For
emptyChainContext init only/health
stdChain+ SourceIP, Log, RecoverPublic endpoints (/config, /version)
authChain+ Authenticate(cookie), ImpersonateOAuth login endpoints
tokenChain+ Authenticate(cookie+token), ImpersonateUpload/file operations
authenticatedChainauthChain + AuthenticatedOnly/me/* endpoints
adminChain+ Authenticate(cookie), AdminOnly/stats, /users, /uploads

File Status State Machine

Files transition through these statuses during their lifecycle:

StatusConstantDownloadable?Description
missingFileMissingNoFile declared in upload but not yet uploaded (used for stream mode)
uploadingFileUploadingNoFile upload in progress
uploadedFileUploadedYesFile available for download
removedFileRemovedNoMarked for deletion, not yet cleaned
deletedFileDeletedNoDeleted from data backend

Limits & User Quota Overrides

Plik enforces limits at two levels: server-wide defaults (from config) and per-user overrides (stored on the User model, set by admins).

Server-Level Limits (config)

Config KeyDefaultDescription
MaxFileSizeStr10 GBMaximum size of a single file. Supports human-readable values ("10GB", "unlimited", "-1")
MaxUserSizeStr-1 (unlimited)Maximum total size of all uploads for a single user
DefaultTTLStr30d (2592000s)TTL applied when client sends TTL = 0
MaxTTLStr30d (2592000s)Maximum allowed TTL. 0 = no limit (infinite TTL allowed)
MaxFilePerUpload1000Maximum number of files in a single upload

Per-User Overrides

Each User has three quota fields that can be set by an admin (via POST /user/{userID} or plikd user update CLI):

User FieldTypeEffect
MaxFileSizeint64Overrides server MaxFileSize
MaxUserSizeint64Overrides server MaxUserSize
MaxTTLintOverrides server MaxTTL

Special Values

ValueMeaning
0Use server default — the user inherits the server-wide limit
-1Unlimited — no limit enforced for this user
> 0Specific limit — used as-is, regardless of server config

Resolution Order

When processing a request, limits are resolved via the custom Context:

  1. MaxFileSize (Context.GetMaxFileSize()): if user.MaxFileSize != 0, use it; otherwise fall back to config.MaxFileSize
  2. MaxUserSize (Context.GetUserMaxSize()): if user.MaxUserSize > 0, use that cap; if user.MaxUserSize < 0, unlimited; if 0, fall back to config.MaxUserSize. Only applies to authenticated users — anonymous uploads have no user size limit.
  3. MaxTTL (in Context.setTTL()): if user.MaxTTL != 0, use it; otherwise fall back to config.MaxTTL. When maxTTL > 0, infinite TTL is rejected and requested TTL is capped.

Note: Anonymous uploads (no token/session) use server defaults directly. Per-user overrides can be more permissive or more restrictive than server defaults — there is no "admin can only restrict" rule.


Public Endpoints (open — no auth required)

MethodPathHandlerDescription
GET/configGetConfigurationServer config (feature flags, limits)
GET/versionGetVersionBuild info
GET/qrcodeGetQrCodeGenerate QR code image (?url=, ?size=)
GET/healthHealthHealth check
MethodPathHandlerAuthDescription
POST/AddFileTokenQuick upload (auto-create upload + add file)
POST/uploadCreateUploadTokenCreate upload with options
GET/upload/{uploadID}GetUploadTokenGet upload metadata
DELETE/upload/{uploadID}RemoveUploadTokenDelete upload
POST/file/{uploadID}AddFileTokenAdd file to upload
POST/file/{uploadID}/{fileID}/{filename}AddFileTokenAdd file with known ID (stream mode)
DELETE/file/{uploadID}/{fileID}/{filename}RemoveFileTokenRemove file
HEAD/GET/file/{uploadID}/{fileID}/{filename}GetFileTokenDownload file
POST/stream/{uploadID}/{fileID}/{filename}AddFileTokenStream upload
HEAD/GET/stream/{uploadID}/{fileID}/{filename}GetFileTokenStream download
HEAD/GET/archive/{uploadID}/{filename}GetArchiveTokenDownload as zip

Authentication Endpoints

MethodPathHandlerAuthDescription
GET/auth/google/loginGoogleLoginCookieGet Google consent URL
GET/auth/google/callbackGoogleCallbackOpenOAuth callback
GET/auth/ovh/loginOvhLoginCookieGet OVH consent URL
GET/auth/ovh/callbackOvhCallbackOpenOAuth callback
GET/auth/oidc/loginOIDCLoginCookieGet OIDC consent URL
GET/auth/oidc/callbackOIDCCallbackOpenOIDC callback
POST/auth/local/loginLocalLoginCookieLogin with login/password
POST/auth/cli/initCLIAuthInitOpenInitiate CLI device auth session
POST/auth/cli/approveCLIAuthApproveSessionApprove CLI login (browser-side)
POST/auth/cli/pollCLIAuthPollOpenPoll for CLI auth result (secret required)
GET/auth/logoutLogoutOpenInvalidate session
MethodPathHandlerDescription
GET/meUserInfoCurrent user info
DELETE/meDeleteAccountDelete own account
GET/me/tokenGetUserTokensList tokens (paginated)
POST/me/tokenCreateTokenCreate upload token
DELETE/me/token/{token}RevokeTokenRevoke token
GET/me/uploadsGetUserUploadsList uploads (paginated)
DELETE/me/uploadsRemoveUserUploadsRemove all uploads
GET/me/statsGetUserStatisticsUser stats
MethodPathHandlerAuthDescription
GET/user/{userID}UserInfoAuthenticatedGet user info
POST/user/{userID}UpdateUserAuthenticatedUpdate user
DELETE/user/{userID}DeleteAccountAuthenticatedDelete user
MethodPathHandlerDescription
POST/userCreateUserCreate user
GET/statsGetServerStatisticsServer stats
GET/usersGetUsersList all users (paginated)
GET/uploadsGetUploadsList all uploads (paginated)

Authentication Flows

  1. User authenticates via /auth/{provider}/login → consent URL → /auth/{provider}/callback
  2. Server creates JWT session cookie (plik-session) signed with server-generated key
  3. XSRF cookie (plik-xsrf) must be echoed in X-XSRFToken header for mutating requests
  4. Cookies are Secure when EnhancedWebSecurity is enabled

Upload Token (per-upload)

  1. Every upload gets a random UploadToken on creation (returned in the POST /upload response)
  2. The token grants admin-like access to that specific upload — add files, remove files, delete the upload
  3. This enables anonymous (non-authenticated) uploads: whoever holds the token controls the upload
  4. Sent in the X-UploadToken header for subsequent requests on that upload

CLI Token (per-user)

  1. Authenticated user creates a CLI token via POST /me/token
  2. Token (UUID) sent in X-PlikToken header or stored in .plikrc config
  3. Authenticates CLI clients on behalf of a user — uploads are linked to the user's account for quota tracking

CLI Device Auth Flow

  1. CLI calls POST /auth/cli/init with hostname → receives a code, secret, and verification URL
  2. User opens the verification URL in their browser and approves the login (requires session cookie)
  3. CLI polls POST /auth/cli/poll with code + secret → receives the generated token once approved
  4. Token is automatically saved to ~/.plikrc — identical to tokens created via POST /me/token
  5. Sessions are ephemeral (5 min TTL), one-time use, and cleaned by the background routine

Auth Providers

ProviderConfig KeysUser ID Format
Local— (CLI-created users)local:{login}
GoogleGoogleApiClientID, GoogleApiSecretgoogle:{email}
OVHOvhApiKey, OvhApiSecretovh:{customerCode}
OIDCOIDCClientID, OIDCClientSecret, OIDCProviderURLoidc:{sub}

Configuration Model

TOML File (plikd.cfg)

Server configuration is a TOML file. See server/plikd.cfg for all options with inline comments.

Environment Variable Override

Any config parameter can be overridden via env var using SCREAMING_SNAKE_CASE with PLIKD_ prefix:

bash

PLIKD_DEBUG_REQUESTS=true ./plikd
PLIKD_DATA_BACKEND_CONFIG='{"Directory":"/var/files"}' ./plikd

Arrays are overridden, maps are merged.

Helm Chart Sync

The Helm chart (charts/plik/) mirrors the configuration model. When adding or modifying config fields in server/common/config.go or server/plikd.cfg, always update:

  • charts/plik/values.yaml — add the field under plikd: with its default value
  • charts/plik/templates/configmap.yaml — add the explicit key to the template
  • charts/plik/templates/secret.yaml — if the field is sensitive, add env var injection (PLIKD_ prefix)

Helm Chart Persistence

The chart supports two independent PVCs:

values.yaml keyDefault pathPurposePVC name
persistence/home/plik/server/filesUploaded file data<release>
dbPersistence/home/plik/server/dbSQLite metadata database<release>-db

Both are disabled by default (enabled: false) and fall back to emptyDir when disabled. For StatefulSet kind, both create volumeClaimTemplates (always provisioned). The default MetadataBackendConfig.ConnectionString is /home/plik/server/db/plik.db — this path is valid even without a PVC (the directory is created at runtime).

Feature Flags

ValueMeaning
disabledFeature is always off
enabledFeature is opt-in (user can turn on)
defaultFeature is opt-out (on by default)
forcedFeature is always on

Special Values

ValueMeaningUsed In
0Use server defaultUser quotas (maxFileSize, maxUserSize, maxTTL)
-1UnlimitedUser quotas

Data Backend Interface

go

type Backend interface {
    AddFile(file *common.File, reader io.Reader) (err error)
    GetFile(file *common.File) (reader io.ReadCloser, err error)
    RemoveFile(file *common.File) (err error)  // must not fail if file not found
}

Implementations

BackendPackageStorage
fileserver/data/fileLocal filesystem directory
s3server/data/s3Amazon S3 / compatible (MinIO) — supports SSE
swiftserver/data/swiftOpenStack Swift
gcsserver/data/gcsGoogle Cloud Storage
streamserver/data/streamIn-memory pipe (uploader → downloader, nothing stored)
testingserver/data/testingIn-memory, for tests

GORM-based with auto-migrations via gormigrate.

DriverConfigNotes
sqlite3ConnectionString = "/home/plik/server/db/plik.db"Default, standalone — path is inside the dbPersistence volume
postgresStandard GORM connection stringDistributed / HA
mysqlStandard GORM connection stringDistributed / HA

Tables

TableModelDescription
uploadsUploadUpload metadata, soft-deleted via DeletedAt
filesFileFile metadata, FK to uploads
usersUserUser accounts
tokensTokenUpload tokens, FK to users
settingsSettingServer settings (e.g., auth signing key)
cli_auth_sessionsCLIAuthSessionEphemeral CLI device auth sessions (auto-cleaned)
migrations(gormigrate)Schema migration history

Documentation

Plik maintains two layers of documentation for different audiences:

Human-Readable Docs (docs/)

A VitePress site deployed to GitHub Pages. Built with Vue 3 and Vite.

docs/
├── .vitepress/config.js   ← site config (nav, sidebar, search)
├── index.md               ← landing page (hero + features)
├── guide/                 ← getting started, configuration, security
├── features/              ← CLI client, web UI, authentication
├── backends/              ← data and metadata backend setup
├── reference/             ← HTTP API, Prometheus metrics, Go library
├── operations/            ← reverse proxy, cross-compilation
├── architecture/          ← links to in-repo ARCHITECTURE.md files
└── contributing.md        ← dev setup and build instructions
  • Build: make docs (or cd docs && npm run dev for local dev server)
  • Deploy: Automated via .github/workflows/pages.yml on push to master (also publishes the Helm chart via chart-releaser)
  • CI: Build is verified on every push/PR via .github/workflows/tests.yaml. Automated docker build (docker build comment) and deploy (docker deploy comment) are available on PRs.

Build pipeline (make docs):

  1. inject_version.sh — replaces __VERSION__ placeholders in markdown files with the current version from gen_build_info.sh. Only runs in CI ($CI env var); skipped locally to avoid dirtying source files.
  2. copy_architecture.sh — copies each ARCHITECTURE.md from the repo into docs/architecture/ with cross-link rewriting. These generated files are .gitignored.
  3. npm run build — runs the VitePress build (includes dead link checking).

Agent-Readable Docs (ARCHITECTURE.md files)

Scoped ARCHITECTURE.md files placed in each package directory, designed for AI coding assistants. Each file documents internal structure, key abstractions, data flow, and design decisions. See the list below.

Changelog (changelog/)

One file per version tag (e.g., changelog/1.3.8). Used by gen_build_info.sh to build the release history exposed via GET /version and displayed in the web UI's update notification.


Scoped Architecture Docs

For deeper details on each component: