Skip to content

Architecture — Server (server/)

Internals of the Plik HTTP server. For system-wide overview, see the root ARCHITECTURE.md.


Package Structure

server/
├── main.go         ← entry point (calls cmd.Execute())
├── plikd.cfg       ← default configuration file
├── cmd/            ← CLI commands (cobra)
├── common/         ← shared types, config, feature flags, utilities
├── context/        ← custom request context
├── data/           ← data backend interface + implementations
├── handlers/       ← HTTP handler functions
├── metadata/       ← metadata backend (GORM)
├── middleware/     ← middleware chain components
└── server/         ← HTTP server setup + router

cmd/ — CLI Commands (Cobra)

The server binary plikd uses cobra for CLI management.

FileCommandDescription
root.goplikdStart the server (default command)
user.goplikd user create/show/update/list/deleteManage users
token.goplikd token create/list/deleteManage user tokens
file.goplikd file list/show/deleteManage uploads/files (delete requires --file, --upload, or --all)
clean.goplikd cleanRun metadata cleanup
import.goplikd import [input-file]Import metadata from gob + Snappy binary
export.goplikd export [output-file]Export metadata to gob + Snappy binary

Config loading order: --config flag → PLIKD_CONFIG env → ./plikd.cfg/etc/plikd.cfg.


common/ — Shared Types & Config

Core types used throughout the server:

FileContent
upload.goUpload struct — container for files with TTL, options, password, E2EE scheme. Sanitize() populates DownloadURL (= config.DownloadURL) alongside the legacy DownloadDomain field for backward compatibility.
file.goFile struct + status constants (missing/uploading/uploaded/removed/deleted)
user.goUser struct + provider constants (local/google/ovh/oidc/github), includes Theme field for webapp theme preference
token.goToken struct — prefixed opaque tokens (plik_ + Base62 random + CRC32 checksum). Backward-compatible with legacy UUIDv4 tokens.
cli_auth_session.goCLIAuthSession struct — ephemeral device auth sessions for CLI login
config.goConfiguration struct — TOML parsing + env var override. Initialize(*logger.Logger) strips path components from PlikDomain/DownloadDomain/aliases (warns if found) and computes DownloadURL = DownloadDomain + Path. URL helpers: GetServerURL(), GetDownloadBaseURL() (returns DownloadDomain+Path or GetServerURL()), GetFileURL(uploadID, fileID, name, stream), GetArchiveURL(uploadID, name) — centralise all download link construction.
feature_flags.goFeature flag types: disabled/enabled/default/forced
settings.goSetting struct — server-level key/value (e.g., auth signing key)
authentication.goSessionAuthenticator — JWT session cookie management
paging.goPagingQuery — pagination parameters
stats.goServerStats — upload/file/user counts
metrics.goPlikMetrics — Prometheus metric registry
version.goBuild info (version, git commit, build date)
utils.goGenerateRandomID(), StripPrefix(), IsPlikWebapp(), etc.

context/ — Custom Request Context

Historical note: This package predates Go's stdlib context.Context (added in Go 1.7). It provides a typed, mutex-protected struct that carries request-scoped values through the middleware chain.

The Context struct holds:

FieldTypeSet By
config*ConfigurationServer init
logger*LoggerServer init
metadataBackend*metadata.BackendServer init
dataBackenddata.BackendServer init
streamBackenddata.BackendServer init
authenticator*SessionAuthenticatorServer init
metrics*PlikMetricsServer init
sourceIPnet.IPSourceIP middleware
upload*UploadUpload middleware
file*FileFile middleware
user*UserAuthenticate middleware
token*TokenAuthenticate middleware
pagingQuery*PagingQueryPaginate middleware

All fields are accessed via getter/setter methods protected by a sync.RWMutex. Getters panic if a required field is nil (fail-fast pattern).

The context package also provides Chain — a composable middleware chain builder: NewChain(mw...).Append(mw...).Then(handler).


data/ — Data Backend

The Backend interface is minimal (3 methods):

go
type Backend interface {
    AddFile(file *common.File, reader io.Reader) (err error)
    GetFile(file *common.File) (reader io.ReadSeekCloser, err error)
    RemoveFile(file *common.File) (err error)
}

Implementations

PackageBackendNotes
data/fileLocal filesystemFiles stored in configurable directory.
data/s3Amazon S3 / MinIOSupports SSE-C/S3 encryption.
data/swiftOpenStack Swift
data/gcsGoogle Cloud Storage
data/streamIn-memory pipeBlocks uploader until downloader connects — nothing stored. Configurable StreamTimeoutStr releases blocked goroutines. On error, file resets to missing (retryable). RemoveFile closes the pipe to unblock cancelled uploads.
data/testingIn-memory mapFor tests only

metadata/ — Metadata Backend (GORM)

Uses GORM with gormigrate for schema management across SQLite3, PostgreSQL, and MySQL.

Key behaviors

  • SQLite3: WAL mode + foreign keys enabled on connect
  • Schema init: Auto-migrates Upload, File, User, Token, Setting, CLIAuthSession tables
  • Migrations: Versioned via gormigrate — see migrations.go
  • Cleaning: Clean() removes orphan files and tokens and CLI auth sessions
  • Metrics: GORM Prometheus plugin for DB stats

Files

FilePurpose
metadata.goBackend init, config, shutdown, clean
migrations.goSchema migration definitions
upload.goUpload CRUD + listing + expiration
file.goFile CRUD + status updates
user.goUser CRUD + listing; RemoveUserUploads bulk soft-deletes uploads + files in a single transaction
token.goToken CRUD + listing
cli_auth_session.goCLI auth session CRUD (create, get by code, update, delete expired)
setting.goServer settings key/value store
stats.goAggregate statistics queries
exporter.gogob + Snappy export of all metadata
importer.gogob + Snappy import

Import / Export

The plikd export and plikd import commands dump and restore all metadata (users, tokens, uploads, files, settings) to/from a single binary file.

  • Format: Go gob encoding compressed with Snappy. Architecture-independent (portable across amd64/arm64), streaming (constant memory), Go-specific (not human-readable).
  • Export order: users → tokens → uploads (including soft-deleted) → files → settings. CLI auth sessions are intentionally excluded (ephemeral).
  • Import: decodes sequentially, calls Create* on the metadata backend. Supports --ignore-errors to skip problematic records.
  • Use cases: backend migration (e.g. SQLite → PostgreSQL), backups, disaster recovery.

Note: Only metadata is exported — file data in the data backend must be migrated separately.

Migration Dump Tests

When adding a new migration, TestGenerateSQLDump and TestGenerateExport in migrations_test.go auto-generate dump files on first run. Each dump captures the full DB state after all migrations and test data have been applied. TestMigrationsFromSQLDumps and TestLoadExports then load all existing dumps and verify migrations can be replayed forward to the current schema.

Dump directory structure:

metadata/dumps/
├── sqlite3/      ← sqlite3 .dump output (generated by make test)
├── export/       ← gob + Snappy export   (generated by make test)
├── postgres/     ← pg_dump output        (generated by make test-backends)
├── mysql/        ← mysqldump output      (generated by make test-backends)
└── mariadb/      ← mariadb-dump output   (generated by make test-backends)

Two-stage generation process:

StageCommandBackendsInfrastructure
1make testsqlite3 + exportLocal, requires sqlite3 CLI (apt install sqlite3)
2make test-backendspostgres, mysql, mariadbDocker containers (e.g., plik.postgres), dumps via docker exec
  1. Run make test locally → generates sqlite3 + export dumps
  2. Run make test-backends locally → generates postgres, mysql, mariadb dumps

[!IMPORTANT] Always commit dump files in server/metadata/dumps/ After adding a new migration

CI workflow (.github/workflows/tests.yaml): Both jobs run a "Check for uncommitted changes" step after tests — git diff --exit-code + git ls-files --others — which fails the build if any files were generated but not committed (e.g., missing dump files for a new migration)

PostgreSQL 18+ compatibility: pg_dump now emits \restrict/\unrestrict psql meta-commands (CVE-2025-8714 sandbox). Since loadSQLDump executes dumps via sqlDB.Exec() (not psql), these are not valid SQL. The loader filters out all lines starting with \ before execution.


middleware/ — Middleware Chain

Each middleware is a function that takes a context.Context and optionally calls the next handler.

FileMiddlewarePurpose
context.goContext()Initialize context with server-level values
log.goLogRequest/response logging
recover.goRecoverPanic recovery → HTTP error response
source_ip.goSourceIPExtract client IP (supports X-Forwarded-For header)
authenticate.goAuthenticate(acceptToken)Parse session cookie / X-PlikToken header → set user/token
impersonate.goImpersonateAdmin impersonation support
upload.goUploadResolve {uploadID} → load upload + check auth
file.goFileResolve {fileID} → load file from upload
create_upload.goCreateUploadParse upload creation params for quick upload
paginate.goPaginateParse pagination query params
redirect.goRedirectOnFailureRedirect to webapp on error (for browser requests)
block_bot_download.goBlockBotDownloadBlock messaging app link preview bots from downloading one-shot/streaming files (returns 406)
user.goUserResolve {userID} → load user (admin or self)
cors.goCORSPreflightShort-circuits OPTIONS preflight requests with CORS headers (runs before Upload/File middleware)
limit_body.goLimitBodyWraps request body with http.MaxBytesReader (1 MiB) to reject oversized payloads on JSON API endpoints. In stdChain — auto-skips GET/HEAD/DELETE/OPTIONS and file upload paths (/, /file/*, /stream/*) which have their own stream-based size limiting.
download_domain.goRestrictDownloadDomainRouter-level middleware: blocks non-file routes on the download domain when PlikDomain is also set (redirects to PlikDomain)

handlers/ — HTTP Handlers

Each handler file contains one or more http.Handler functions.

FileHandlersDescription
create_upload.goCreateUploadCreate upload with options, validate config/quotas
add_file.goAddFileUpload file to existing upload (multipart). Detects content type via gabriel-vasile/mimetype magic-number sniffing (200+ formats). E2EE uploads are forced to application/octet-stream via age-header detection. On stream upload error, resets file to missing (retryable); on regular upload error, purges partial data and leaves file in uploading (not retryable).
get_upload.goGetUploadReturn upload metadata
get_file.goGetFileDownload file, handle OneShot, extend TTL, support HTTP range requests (via http.ServeContent for non-stream/non-oneshot). E2EE uploads: redirects webapp to download page, forces application/octet-stream
get_archive.goGetArchiveDownload all files as zip. Compression method controlled by EnableArchiveCompression (default: zip.Deflate). Disable to prevent CPU exhaustion DoS on public instances.
remove_file.goRemoveFileMark file as removed. Also mapped to DELETE /stream/... to allow cancelling a blocked stream upload (closes the in-memory pipe).
remove_upload.goRemoveUploadSoft-delete upload
misc.goGetConfiguration, GetVersion, GetQrCode, HealthUtility endpoints
local.goLocalLogin, LogoutLocal auth
google.goGoogleLogin, GoogleCallbackGoogle OAuth
ovh.goOvhLogin, OvhCallbackOVH OAuth
oidc.goOIDCLogin, OIDCCallbackOpenID Connect
github.goGitHubLogin, GitHubCallbackGitHub OAuth (optional org restriction)
cli_auth.goCLIAuthInit, CLIAuthApprove, CLIAuthPollCLI device auth flow
me.goUserInfo, PatchMe, DeleteAccount, GetUserStatistics, GetUserUploads, RemoveUserUploadsCurrent user; token filter param accepts both prefixed and legacy UUID formats (no DB lookup, works for revoked tokens)
token.goGetUserTokens, CreateToken, RevokeTokenToken management
user.goGetUsers, CreateUser, UpdateUserUser management
admin.goGetServerStatistics, GetUploadsAdmin endpoints

server/ — HTTP Server Setup

PlikServer is the main server struct. It:

  1. Initializes backends (metadata, data, stream) and authenticator
  2. Calls ensureDefaultAdmin() — creates a local admin user if DefaultAdminLogin is configured and the user does not yet exist (idempotent)
  3. Builds middleware chains (see root ARCHITECTURE.md for chain table)
  4. Configures gorilla/mux router with all routes
  5. Starts HTTP server via net.Listen + httpServer.Serve (supports ephemeral port allocation with ListenPort: 0)
  6. Starts cleaning routine (if auto-clean enabled)
  7. Starts metrics HTTP server (if configured)

After start, call GetListenPort() to retrieve the actual listen port (useful when configured with port 0).

Shutdown: graceful with configurable timeout, closes HTTP server + metadata backend.

Released under the MIT License.