Skip to content

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
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
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
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

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.

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

Metadata Backend

GORM-based with auto-migrations via gormigrate.

DriverConfigNotes
sqlite3ConnectionString = "plik.db"Default, standalone
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)
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/deploy-docs.yml on push to master
  • CI: Build is verified on every push/PR via .github/workflows/tests.yaml

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:

Released under the MIT License.