This guide is the operational and architectural reference for AList — a self-hosted, single-binary file-list server written in Go (Gin + GORM) that unifies 90+ storage backends behind one tree and re-serves it over HTTP, WebDAV, FTP, SFTP, S3, and MCP. It is written for the people who deploy, secure, operate, extend, and maintain an AList instance.
For end-user tasks (browsing, mounting storages, sharing), see the AList User Guide. This document assumes you run the process.
1. Executive Summary
AList is an opinionated, batteries-included storage gateway:
- One process, no dependencies. A statically-linked Go binary embeds an HTTP server, an embedded SQLite database, an optional full-text index (Bleve), and the entire driver fleet. No external runtime, message broker, or database is required to run.
- Driver registry architecture. Every storage backend is a self-registering driver implementing a common Go interface. Drivers are compiled in; adding one is a matter of implementing the interface and importing the package.
- Multi-protocol front door. The same mounted tree is exposed as a web UI / REST API (Gin), a WebDAV server, an FTP/FTPS server, an SFTP server, an S3-compatible endpoint, and an MCP server for AI assistants — each gated by the same permission model.
- State lives in
data/. Configuration (config.json), the database (data.dbby default), logs, temp files, and the Bleve index all live under one directory. Back that up and you have backed up the instance.
Build/runtime facts (as of this commit)
Language ........ Go 1.25
Web framework ... github.com/gin-gonic/gin v1.10
ORM ............. gorm.io/gorm v1.25 (sqlite / mysql / postgres drivers)
CLI ............. github.com/spf13/cobra v1.8
Config env ...... github.com/caarlos0/env/v9
Search .......... bleve v2.4 (embedded) | meilisearch (external) | database
Default HTTP .... 0.0.0.0:5244
Important
The single most important operational fact about AList: the
data/directory is the instance. It holdsconfig.json(including the JWT secret), the database (storages, users, roles, settings, the admin password hash), and the index. Protect it, back it up, and never commit it to version control.
2. Architecture
2.1 High-level component map
┌──────────────────────────────────────────┐
│ alist │
│ (single Go process) │
│ │
HTTP/WS :5244 ───────►│ Gin engine ── middlewares ── handlers │
HTTPS (opt) ───────►│ │ │
WebDAV /dav ───────►│ │ server/ (protocol adapters) │
FTP/FTPS :5221 ───────►│ │ │
SFTP :5222 ───────►│ ▼ │
S3 :5246 ───────►│ internal/fs (virtual filesystem) │
MCP :5248 ───────►│ │ │
│ ▼ │
│ internal/op (storage ops + driver reg) │
│ │ │
│ ▼ │
│ drivers/* (90+ self-registering) │
│ │ │
│ ▼ internal/db (GORM) │
│ cloud / object / network backends data.db
└──────────────────────────────────────────┘
2.2 Package layout
| Package | Responsibility |
|---|---|
main.go |
Entry point; calls cmd.Execute() |
cmd/ |
Cobra CLI: server, admin, storage, start/stop/restart/kill, mcp, version, … |
internal/bootstrap/ |
Startup sequence: load config, init DB, init data, load storages, init index/tasks/FRP |
internal/conf/ |
The Config struct (maps to config.json) and the runtime settings key constants |
internal/driver/ |
The Driver interface and supporting types — the contract every backend implements |
internal/op/ |
Storage operations layer: driver registry, storage lifecycle, settings, users, caching |
internal/fs/ |
Virtual filesystem: path resolution across mounts, list/get/link/copy/move/put |
internal/db/ |
GORM models and queries (storages, users, roles, settings, metas, …) |
internal/model/ |
Domain types: User, Storage, Obj, Link, Meta, Role, … |
internal/stream/ |
Streaming, rate limiting (client/server up/down speed limits) |
internal/net/ |
HTTP client construction, concurrency limiting, range handling |
internal/search/ |
Search abstraction: db, db_non_full_text, bleve, meilisearch backends |
internal/offline_download/ |
Offline-download tools: aria2, qbit, transmission, http, 115, pikpak, thunder, guangyapan |
internal/archive/ |
Archive inspection/extraction: zip, 7z, rar, iso9660 |
internal/frp/ |
FRP (fast reverse proxy) client integration for NAT traversal |
drivers/ |
The storage driver fleet (one package per backend) |
server/ |
Protocol servers/adapters: router.go (HTTP routes), webdav.go, ftp.go, sftp.go, s3.go, handles/ (REST handlers), middlewares/, mcp/ |
pkg/ |
Reusable utilities: task, utils, cron, sign, gowebdav client, qbittorrent, aria2, generics |
2.3 The virtual filesystem and driver layer
The heart of AList is the indirection between a path and a driver:
- A request arrives for a path like
/onedrive/photos/x.jpg. internal/fsfinds the storage whose mount path is the longest prefix (/onedrive).- It strips the mount prefix and calls the driver's methods (
List,Link,Put, …) with the remaining path. - The driver talks to the actual backend and returns
model.Obj/model.Linkvalues.
Drivers register themselves at package-init time:
// every driver's meta.go ends with:
func init() {
op.RegisterDriver(func() driver.Driver {
return &Onedrive{}
})
}
The drivers package is blank-imported (_ "…/drivers") from cmd/root.go, so importing the CLI pulls in all driver init()s, populating the registry. This is why the driver set is fixed at compile time and why adding a driver requires a rebuild.
2.4 Startup sequence
Init() (in cmd/common.go) runs before any command that touches data:
bootstrap.InitConfig() → read/create data/config.json, apply ALIST_* env overrides
bootstrap.Log() → configure logrus + lumberjack rotation
bootstrap.InitDB() → open GORM (sqlite/mysql/postgres), auto-migrate schema
[legacy-role patch] → convert pre-3.46 role format if detected
data.InitData() → seed admin user, guest user, default settings on first run
bootstrap.InitStreamLimit()
bootstrap.InitIndex() → open the configured search backend
bootstrap.InitUpgradePatch()
alist server then additionally runs InitOfflineDownloadTools(), LoadStorages() (initialize every enabled storage), InitTaskManager(), and InitFRP() before binding listeners.
3. Requirements & Sizing
| Dimension | Guidance |
|---|---|
| OS / arch | Any Go target: Linux/macOS/Windows/FreeBSD on amd64/arm64/arm/386 |
| CPU | Minimal at idle; download proxying is I/O-bound. 1 vCPU handles a home instance |
| Memory | Tens of MB baseline; rises with proxied transfers, large copies, and Bleve indexing |
| Disk | Binary (~tens of MB) + data/ (config + DB + logs + optional index). Proxied files use temp_dir transiently |
| Database | Embedded SQLite by default. Use MySQL/PostgreSQL only for HA or very large user/storage counts |
| Network | One inbound port (5244). Outbound to every configured storage provider |
Tip
Memory pressure almost always comes from proxy mode and cross-storage copies buffering data, or from Bleve index builds. If you redirect (302) instead of proxy where possible, AList stays lean. Tune
max_concurrencyand per-task worker counts (§4) to bound memory.
4. Configuration Mastery
AList has two distinct configuration layers. Confusing them is the most common operator mistake.
| Layer | Where it lives | Changed by | Examples |
|---|---|---|---|
Config file (Config) |
data/config.json (+ ALIST_* env) |
Editing the file / env, then restart | Ports, database, TLS, CORS, task workers, protocol toggles |
| Settings (runtime) | The database | Admin web UI / setting API, live |
Site title, preview types, SSO, LDAP, aria2, speed limits, hide rules |
Important
Server bind addresses, ports, the database connection, TLS, and protocol enable/disable are config-file settings — they require a restart. Almost everything else a user sees (site title, preview behavior, SSO/LDAP, offline-download endpoints, speed limits) is a runtime setting stored in the DB and editable live in the admin panel.
4.1 config.json reference
config.json is auto-created with defaults on first run and re-marshaled (adding any new fields) on every startup. The complete structure and defaults:
Top-level
| Key | Type | Default | Purpose |
|---|---|---|---|
force |
bool | false |
If true, skip reading ALIST_* env overrides |
site_url |
string | "" |
Public base URL (set for sub-path / reverse-proxy deployments) |
cdn |
string | "" |
CDN base for static assets |
jwt_secret |
string | random(16) | HMAC secret for JWT auth tokens (secret) |
token_expires_in |
int | 48 |
JWT lifetime in hours |
temp_dir |
string | data/temp |
Scratch directory for proxied/streamed transfers |
bleve_dir |
string | data/bleve |
Bleve full-text index location |
dist_dir |
string | "" |
Override path to the bundled front-end assets |
delayed_start |
int | 0 |
Seconds to sleep before server binds (lets dependencies come up) |
max_connections |
int | 0 |
Global concurrent-request cap (0 = unlimited) |
max_concurrency |
int | 64 |
Max concurrent upstream HTTP connections per transfer |
tls_insecure_skip_verify |
bool | false |
Dangerous — disable upstream TLS verification |
last_launched_version |
string | "" |
Bookkeeping for upgrade patches (managed automatically) |
scheme — listeners & TLS
| Key | Default | Purpose |
|---|---|---|
address |
0.0.0.0 |
Bind address for all HTTP-family listeners |
http_port |
5244 |
HTTP port (-1 disables) |
https_port |
-1 |
HTTPS port (-1 disables) |
force_https |
false |
Redirect HTTP→HTTPS when both are enabled |
cert_file / key_file |
"" |
TLS certificate / key paths |
unix_file |
"" |
Unix-socket path (alternative to TCP) |
unix_file_perm |
"" |
Octal permission for the socket file |
enable_h2c |
false |
Allow HTTP/2 cleartext (h2c) |
database
| Key | Default | Purpose |
|---|---|---|
type |
sqlite3 |
sqlite3, mysql, or postgres |
host / port |
"" / 0 |
DB server (MySQL/Postgres) |
user / password / name |
"" |
Credentials and database name |
db_file |
data/data.db |
SQLite file path |
table_prefix |
x_ |
Prefix for all tables |
ssl_mode |
"" |
Postgres sslmode |
dsn |
"" |
Full DSN override (bypasses the fields above) |
Protocol servers
| Block | Keys (defaults) |
|---|---|
s3 |
enable=false, port=5246, ssl=false |
ftp |
enable=false, listen=":5221", find_pasv_port_attempts=50, idle_timeout=900, connection_timeout=30, default_transfer_binary=false, plus active/passive IP-check toggles |
sftp |
enable=false, listen=":5222" |
mcp |
enable=false, port=5248 |
tasks — background-worker concurrency
A TaskConfig (workers, max_retry, task_persistant) per task type, under env prefix TASKS_:
| Task | Default workers | Default retries |
|---|---|---|
download |
5 | 1 |
transfer |
5 | 2 |
upload |
5 | 0 |
copy |
5 | 2 |
decompress |
5 | 2 |
decompress_upload |
5 | 2 |
s3_transition |
5 | 2 |
allow_retry_canceled |
— | false |
cors, log, meilisearch
| Block | Defaults |
|---|---|
cors |
allow_origins, allow_methods, allow_headers all ["*"] |
log |
enable=true, name=data/log/log.log, max_size=50MB, max_backups=30, max_age=28d, compress=false |
meilisearch |
host=http://localhost:7700, api_key, index_prefix |
Warning
The default CORS policy is wide open (
*for origins, methods, and headers). For any internet-facing deployment, restrictcors.allow_originsto your actual front-end origin(s).
4.2 Environment-variable overrides
Unless force is true, AList overlays environment variables onto the config after loading the file, using the ALIST_ prefix and the struct's nested env tags (via caarlos0/env). Prefix chains follow the struct nesting:
| Config field | Environment variable |
|---|---|
jwt_secret |
ALIST_JWT_SECRET |
scheme.http_port |
ALIST_HTTP_PORT |
scheme.https_port |
ALIST_HTTPS_PORT |
database.type |
ALIST_DB_TYPE |
database.host |
ALIST_DB_HOST |
database.password |
ALIST_DB_PASS |
s3.enable |
ALIST_S3_ENABLE |
ftp.listen |
ALIST_FTP_LISTEN |
tasks.download.workers |
ALIST_TASKS_DOWNLOAD_WORKERS |
# Run with Postgres and a fixed JWT secret, no config edits:
ALIST_DB_TYPE=postgres \
ALIST_DB_HOST=db \
ALIST_DB_PORT=5432 \
ALIST_DB_USER=alist \
ALIST_DB_PASS=secret \
ALIST_DB_NAME=alist \
ALIST_JWT_SECRET=$(openssl rand -hex 32) \
./alist server
Tip
Pass
--no-prefixto drop theALIST_prefix (e.g. plainDB_TYPE). Setforce: true(orALIST_FORCE=true) to makeconfig.jsonauthoritative and ignore the environment entirely — useful when you manage config purely by file.
4.3 Runtime settings (database-backed)
These are not in config.json — they live in the DB and are edited live in the admin panel (or via the setting API). Grouped by domain (constant names from internal/conf/const.go):
| Group | Representative keys |
|---|---|
| Site | site_title, announcement, allow_indexed, allow_mounted, robots_txt, allow_register, default_role, logo, favicon, main_color |
| Preview | text_types, audio_types, video_types, image_types, proxy_types, audio_autoplay, video_autoplay, thumbnail_size, preview_archives_by_default, readme_autorender, filter_readme_scripts |
| Global | hide_files, customize_head, customize_body, link_expiration, sign_all, privacy_regs, ocr_api, filename_char_mapping, forward_direct_link_params, webauthn_login_enabled, max_devices, device_evict_policy, device_session_ttl |
| Index | search_index, auto_update_index, ignore_paths, max_index_depth |
| Offline download | aria2_uri/aria2_secret, transmission_uri/transmission_seedtime, qbittorrent_url/qbittorrent_seedtime, plus 115_temp_dir, pikpak_temp_dir, thunder_temp_dir, guangyapan_temp_dir |
| SSO | sso_login_enabled, sso_client_id, sso_client_secret, sso_login_platform, sso_oidc_username_key, sso_auto_register, sso_default_dir, sso_default_permission, … |
| LDAP | ldap_login_enabled, ldap_server, ldap_manager_dn, ldap_manager_password, ldap_user_search_base, ldap_user_search_filter, ldap_default_permission, ldap_default_dir |
| S3 access | s3_buckets, s3_access_key_id, s3_secret_access_key |
| FTP | ftp_public_host, ftp_pasv_port_map, ftp_proxy_user_agent, ftp_mandatory_tls, ftp_implicit_tls, ftp_tls_private_key_path, ftp_tls_public_cert_path |
| FRP | frp_enabled, frp_server_addr, frp_server_port, frp_auth_token, frp_proxy_name, frp_proxy_type, frp_remote_port, frp_tls_enable, frp_stcp_secret_key, … |
| Traffic / speed | max_client_download_speed, max_client_upload_speed, max_server_download_speed, max_server_upload_speed, and per-task thread counts (offline_download_task_threads_num, upload_task_threads_num, copy_task_threads_num, …) |
Note
The
tokensetting (admin API token) andindex_progressare "single" settings managed by AList, not freely editable. Retrieve the admin token withalist admin tokenorPOST /api/admin/setting/reset_token.
5. Deployment
5.1 systemd (binary)
# /etc/systemd/system/alist.service
[Unit]
Description=AList file-list server
After=network.target
[Service]
Type=simple
User=alist
WorkingDirectory=/opt/alist
ExecStart=/opt/alist/alist server --data /opt/alist/data
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
sudo useradd -r -s /usr/sbin/nologin alist
sudo chown -R alist:alist /opt/alist
sudo systemctl daemon-reload
sudo systemctl enable --now alist
journalctl -u alist -f
Tip
Prefer
systemd/Docker over AList's ownstart/stop/killdaemon for production — you get supervision, log capture, and restart policy for free. The built-in daemon is a convenience for quick setups.
5.2 Docker / Compose
The official image (xhofe/alist) has WORKDIR /opt/alist, declares a volume at /opt/alist/data, and exposes 5244 and 5245.
services:
alist:
image: xhofe/alist:latest
container_name: alist
ports:
- "5244:5244"
volumes:
- "./alist-data:/opt/alist/data"
environment:
- PUID=1000
- PGID=1000
- UMASK=022
restart: unless-stopped
To run an enabled protocol (e.g. S3 on 5246, FTP on 5221), enable it in config.json and publish the matching port.
5.3 Reverse proxy & sub-path
Put a TLS-terminating reverse proxy in front and set site_url so AList builds correct links:
location / {
proxy_pass http://127.0.0.1:5244;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebDAV / large uploads:
client_max_body_size 0;
}
- For a sub-path mount (
https://host/alist), setsite_urlto that full URL —server/router.gogroups all routes underconf.URL.Path. - For WebDAV behind a proxy, forward all HTTP methods (
PROPFIND,MKCOL,MOVE,COPY,LOCK,UNLOCK,PROPPATCH).
5.4 Choosing a database
| Backend | When to use | DSN/notes |
|---|---|---|
| SQLite (default) | Single instance, the vast majority of deployments | db_file path; zero setup |
| MySQL | Existing MySQL infra, larger user/storage counts | set type=mysql, host/port/user/pass/name |
| PostgreSQL | Existing Postgres infra | set type=postgres; provide ssl_mode |
Caution
There is no migration tool between database backends. Moving SQLite → Postgres means exporting and re-importing data manually; AList will not convert it for you. Pick your backend before you accumulate state. All backends share the same GORM schema and
table_prefix.
6. Security
6.1 Authentication & sessions
AList supports several auth mechanisms, layered:
- Local accounts — username + password (bcrypt-style hash), JWT issued on login (
jwt_secret,token_expires_in). - Two-factor (TOTP) —
pquerna/otp; enrol via/auth/2fa/generate+/verify. Remove from admin withalist cancel2fa. - WebAuthn / passkeys —
go-webauthn; registration and login endpoints under/authn. - SSO (OIDC) —
coreos/go-oidc; configured via thesso_*settings, with optional auto-registration, default dir, and default permissions. - LDAP —
gopkg.in/ldap.v3; bind via manager DN, search users, map to default permission/dir. - Admin API token — a static bearer token for automation and WebDAV admin access (
alist admin token).
Important
jwt_secretdefaults to a random 16-char string regenerated only if absent. Set it explicitly (ALIST_JWT_SECRET) in any multi-instance or reproducible deployment so tokens remain valid across restarts and across replicas.
6.2 The permission model
Access control is role-based and path-scoped. A user has roles; each role carries permission scopes (a permission bitmask attached to a path). On each request, MergeRolePermissions(user, path) ORs together the permissions of every role scope whose path is an ancestor of the requested path.
Permission bits (server/common/role_perm.go, bit index = order):
| Bit | Permission | Governs |
|---|---|---|
| 0 | See hides | Viewing hidden files |
| 1 | Access without password | Bypassing per-path passwords |
| 2 | Add offline download | Queuing offline-download tasks |
| 3 | Write | Upload / create |
| 4 | Rename | Renaming |
| 5 | Move | Moving |
| 6 | Copy | Copying |
| 7 | Remove | Deleting |
| 8 | WebDAV read | WebDAV GET/PROPFIND |
| 9 | WebDAV manage | WebDAV PUT/MKCOL/MOVE/COPY/DELETE/PROPPATCH |
| 10 | FTP access | FTP/SFTP login |
| 11 | FTP manage | FTP/SFTP modifications |
| 12 | Read archives | Browsing inside archives |
| 13 | Decompress | Extracting archives |
| 14 | Path limit | Confining the user to their base_path subtree |
| 15 | MCP access | MCP tool use (read) |
| 16 | MCP manage | MCP tool use (write) |
The guest user defines anonymous access; the admin user is unconditionally privileged.
Note
Protocol handlers enforce these bits directly. For example,
server/webdav.gochecksPermWebdavManage+PermWriteforPUT/MKCOL,PermMove/PermRenameforMOVE, andPermCopyforCOPY;server/ftp.goandserver/sftp.gorequirePermFTPAccessto authenticate at all.
6.3 Transport & link security
- Server TLS — set
scheme.https_port,cert_file,key_file; optionallyforce_https. Many operators terminate TLS at a reverse proxy instead. - Signed links — per-storage Enable sign (or global
sign_all) adds a?sign=HMAC token to direct links, withlink_expirationcontrolling validity. Verified by thesign.Verifymiddleware on/d,/p, archive routes, etc. - Privacy regex —
privacy_regsscrubs sensitive strings (IPs, tokens) from responses/logs. - Upstream TLS — keep
tls_insecure_skip_verify=false. AList prints a bilingual security warning when it is enabled because it exposes all storage traffic to MITM.
6.4 Hardening checklist
- [ ] Reset the admin password (
alist admin set …) and enable 2FA. - [ ] Set an explicit
jwt_secret. - [ ] Terminate TLS (proxy or built-in) and set
force_httpsif serving both. - [ ] Narrow
cors.allow_originsfrom*to your front-end origin. - [ ] Restrict or disable the guest user; require login for sensitive trees.
- [ ] Enable
sign_all(or per-storage sign) +link_expirationfor public direct links. - [ ] Lock filesystem permissions on
data/(it containsjwt_secretand all credentials). - [ ] Keep
tls_insecure_skip_verify=false. - [ ] Limit
max_connections/ per-task workers to bound resource use under abuse. - [ ] Use a non-root system user; in Docker set
PUID/PGID.
6.5 Malicious-content posture
AList serves content from backends you configure; it does not scan files. Treat any storage you mount as trusted-by-you. The filter_readme_scripts setting strips scripts from rendered READMEs to mitigate stored-XSS via uploaded README.md. When allowing guest uploads, scope the writable path tightly with Meta rules and the Write permission.
7. Operations
7.1 Process control
| Action | Command |
|---|---|
| Foreground | alist server --data <dir> |
| Daemon start / stop / restart / kill | alist start / stop / restart / kill |
| Graceful shutdown | SIGINT/SIGTERM — AList drains HTTP/HTTPS/Unix/FTP/SFTP/MCP with a 1s timeout |
7.2 Storage administration (CLI)
alist storage list # interactive table: ID, driver, mount path, enabled
alist storage disable /broken # disable a storage without deleting it
The web admin panel and the /api/admin/storage/* endpoints provide full CRUD plus enable, disable, and load_all (re-initialize every storage).
7.3 Logging
Logging uses logrus with lumberjack rotation, configured in the log block: file data/log/log.log, 50 MB max size, 30 backups, 28-day retention. --debug raises verbosity; --log-std mirrors to stdout (default under Docker via the entrypoint).
7.4 Search index lifecycle
For Bleve/Meilisearch search:
POST /api/admin/index/build # full (re)build from "/"
POST /api/admin/index/update # incremental update of given paths
POST /api/admin/index/stop # stop a running build
POST /api/admin/index/clear # drop the index
GET /api/admin/index/progress # live progress
max_index_depth (default 20) bounds recursion; ignore_paths excludes subtrees; per-storage Disable index removes a mount from indexing. auto_update_index keeps the index current as files change.
7.5 Background tasks
Long operations (copy, transfer, upload, decompress, offline-download, S3 transition) run as tasks via xhofe/tache, each with its own worker pool and retry policy (§4.1). Manage them under /api/admin/task/* or the management panel: list undone/done, info, cancel, retry, delete, and bulk variants (cancel_some, retry_failed, clear_succeeded, …).
7.6 Backup & restore
# Stop AList (or snapshot a consistent SQLite copy), then archive data/:
systemctl stop alist
tar -czf alist-backup-$(date +%F).tgz -C /opt/alist data
systemctl start alist
Restoring is the inverse: drop data/ into place and start. Because data/ includes config.json, the database, and the index, this is a complete instance backup. For MySQL/Postgres, back up the database separately and keep config.json alongside it.
Caution
data/config.jsoncontains thejwt_secret, and the database contains every storage credential and the admin hash. Encrypt backups and store them securely.
7.7 Upgrades
Replace the binary (or pull a new image) and restart. On startup AList re-marshals config.json (adding new fields), runs GORM migrations, and applies any version-gated upgrade patches (tracked via last_launched_version). Back up data/ before a major upgrade.
8. Internals & Extensibility
8.1 The Driver interface
A storage backend is any type that satisfies driver.Driver, which composes two required sub-interfaces plus optional capability interfaces:
type Driver interface {
Meta
Reader
}
type Meta interface { // required
Config() Config
GetStorage() *model.Storage
SetStorage(model.Storage)
GetAddition() Additional // driver-specific config struct
Init(ctx context.Context) error
Drop(ctx context.Context) error
}
type Reader interface { // required
List(ctx, dir, args) ([]model.Obj, error)
Link(ctx, file, args) (*model.Link, error)
}
Optional capability interfaces — implement only what the backend supports; the fs layer feature-detects them:
| Interface | Method(s) | Adds |
|---|---|---|
Mkdir / MkdirResult |
MakeDir |
Create directories |
Move / MoveResult |
Move |
Move objects |
Rename / RenameResult |
Rename |
Rename objects |
Copy / CopyResult |
Copy |
Copy objects |
Remove |
Remove |
Delete objects |
Put / PutResult |
Put |
Upload streams |
PutURL / PutURLResult |
PutURL |
Upload a URL directly (offline-download fast path) |
Getter |
Get |
Fetch a single object by path |
GetRooter |
GetRoot |
Custom root resolution |
ArchiveReader / ArchiveGetter / ArchiveDecompress… |
archive ops | Native archive support |
Reference |
InitReference |
Wrap another storage (Crypt/Alias/Chunker) |
ProxyDriver |
ShouldProxyDownloads |
Per-storage proxy override |
The *Result variants return the created/modified model.Obj so the fs layer can update caches without re-listing.
8.2 Driver Config and Addition
Two structs define a driver's behavior and its user-facing form:
// behavior flags (internal/driver/config.go)
type Config struct {
Name string // display name, unique
LocalSort bool // sort in AList, not the backend
OnlyLocal bool
OnlyProxy bool // links must be proxied
NoCache bool
NoUpload bool
NeedMs bool // needs user interaction (e.g. SMS code)
DefaultRoot string // default root path/id
Alert string // banner: info|success|warning|danger
NoOverwriteUpload bool
ProxyRangeOption bool
}
// per-storage config, rendered as a form (struct tags drive the UI)
type Addition struct {
driver.RootPath // or driver.RootID
Field string `json:"field" type:"select" required:"true" options:"a,b,c" default:"a"`
}
The struct tags (type, required, options, default, help) are reflected into form items (driver.Item) and exposed via /api/admin/driver/info, so the front-end builds the Add-Storage form automatically.
8.3 Adding a new storage driver
Use drivers/template as the skeleton:
- Create
drivers/<name>/withdriver.go,meta.go,types.go,util.go. - Define the storage type embedding
model.Storageand yourAddition:type MyDrive struct { model.Storage Addition } - Implement
Meta+Reader(Config,GetAddition,Init,Drop,List,Link) and any optional capabilities (Put,Move, …). Returnerrs.NotImplementfor unimplemented optional methods so the internal archive/fallback tools can take over where applicable. - Declare
configand register inmeta.go:var config = driver.Config{ Name: "MyDrive", DefaultRoot: "/" } func init() { op.RegisterDriver(func() driver.Driver { return &MyDrive{} }) } - Wire it in — ensure the package is imported by
drivers/all.go(blank import) so itsinit()runs. - Assert conformance:
var _ driver.Driver = (*MyDrive)(nil). - Rebuild — drivers are compiled in; there is no runtime plugin loading.
Tip
Follow the upload guidance in the
Putdoc comment: support cancellation viactx, report progress via theUpdateProgresscallback (ordriver.Progress/ReaderUpdatingProgress), and apply server upload limits withdriver.NewLimitedUploadStream/ServerUploadLimitWaitN. For parallel chunked uploads, bound concurrency with a weighted semaphore to cap memory.
8.4 Data model essentials
| Model | Key fields |
|---|---|
Storage |
MountPath (unique), Driver, Order, CacheExpiration, Addition (JSON blob), Disabled, DisableIndex, EnableSign, Sort{OrderBy,OrderDirection,ExtractFolder}, Proxy{WebProxy,WebdavPolicy,ProxyRange,DownProxyUrl,DownProxySign} |
User |
username, password hash, Role(s), BasePath, Disabled, SSH keys, 2FA secret |
Role |
name, PermissionScopes (path + permission bitmask) |
Meta |
path-scoped rules: password, hide, readme, write |
Obj / Link |
a listed object; a resolved download link (URL, header, range, or reader) |
The Storage.Addition field stores the driver-specific config as a JSON string; AList unmarshals it into the driver's Addition struct on Init. The Proxy.WebdavPolicy value selects WebDAV serving mode: native, 302_redirect, or use_proxy_url.
8.5 HTTP API surface
The REST API (≈160 routes, server/router.go) is grouped under conf.URL.Path + /api:
| Group | Auth | Examples |
|---|---|---|
/api/auth/* |
none/login | login, login/hash, login/ldap, register, sso, 2fa/* |
/api/authn/* |
webauthn | webauthn_begin_login, webauthn_finish_registration |
/api/me/* |
user | me, me/update, me/sshkey/*, me/sessions |
/api/public/* |
none | settings, offline_download_tools, archive_extensions, share/* |
/api/fs/* |
user | list, get, search, mkdir, rename, move, copy, remove, put, form, link, add_offline_download, archive/* |
/api/share/* |
user (not guest) | create, update, disable, list, delete |
/api/task/* |
user (not guest) | per-type list/info/cancel/retry/delete/bulk |
/api/admin/* |
admin | meta/*, user/*, role/*, storage/*, driver/*, setting/*, index/*, message/*, label/*, session/* |
Non-API surfaces: direct download/proxy (/d, /p), share (/s, /sd, /sp), archive (/ad, /ap, /ae), WebDAV (/dav), S3 (/s3 or the standalone port), and the SPA static handler (NoRoute).
Note
Settings endpoints include driver-token helpers (
set_aria2,set_qbit,set_transmission,set_115,set_pikpak,set_thunder,set_guangyapan) and FRP control (set_frp,stop_frp,frp_runtime). The legacy/api/admin/taskgroup is retained for backward compatibility with older automation.
8.6 MCP server
server/mcp builds an MCP server (mark3labs/mcp-go) registering three tool groups — read, manage, upload — each wrapped by toolHandlerWithAuth so every tool resolves and requires an authenticated user. It runs either as a Streamable-HTTP handler (mcp.enable, port 5248) or over STDIO (alist mcp --user <name>), the latter binding all operations to one fixed user. MCP tool use is gated by the MCP access / MCP manage permissions.
9. Troubleshooting (Operator)
Symptom: changes to config.json have no effect
Cause: Either AList isn't reading that file (wrong --data), or an ALIST_* environment variable is overriding it.
Resolution: Confirm the reading config file: log line points at your file. Check for ALIST_* env vars (they win unless force=true). Restart after editing — config-file values are read only at startup.
Symptom: server won't bind / failed to start http
Cause: Port in use, or http_port set to -1 (disabled) with no HTTPS/Unix listener.
Resolution: Ensure at least one of http_port, https_port, or unix_file is active; free the port; check address.
Symptom: tokens invalidated on every restart / across replicas
Cause: jwt_secret was left to its random default and changed.
Resolution: Set an explicit jwt_secret (ALIST_JWT_SECRET) and deploy the same value everywhere.
Symptom: storage stuck in error status after upgrade
Cause: A driver's expected token/cookie format changed, or upstream API broke (e.g. Google Photos API policy change, issue #8518).
Resolution: Re-authenticate the storage; check the storage Status field for the driver error; consult the GitHub issues for that backend. Disable the storage to keep the rest of AList serving.
Symptom: high memory during large copies or proxied downloads
Cause: Proxy buffering and per-task concurrency (issues #9374, #9196).
Resolution: Lower max_concurrency and the relevant tasks.*.workers; prefer 302 redirect over proxy; use native cloud offline-download for big transfers.
Symptom: SIGSEGV / nil-pointer panic mounting a specific drive
Cause: A driver-specific bug (e.g. Weiyun, issue #9551; intermittent crashes, #9190).
Resolution: Disable the offending storage (alist storage disable <path>), capture the stack from the log, and report it upstream with the driver name and AList version.
Symptom: WebDAV/FTP clients can read but not write
Cause: Missing WebDAV manage / FTP manage (+ Write) permission on the user's role. Resolution: Grant the management permission for the relevant path scope; re-test. Remember admin-over-WebDAV uses the API token as a Bearer credential.
10. Quick Reference
Startup sequence: InitConfig → Log → InitDB → InitData → InitStreamLimit → InitIndex → InitUpgradePatch then (for server) InitOfflineDownloadTools → LoadStorages → InitTaskManager → InitFRP → bind listeners.
Default ports: HTTP 5244 · HTTPS -1 · FTP :5221 · SFTP :5222 · S3 5246 · MCP 5248 (only HTTP on by default).
Config layers: config.json (+ ALIST_* env) = infra, restart-required. DB settings = runtime, live-editable.
Data directory (data/): config.json, data.db (SQLite default), log/, temp/, bleve/. Back this up.
Key CLI:
| Command | Purpose |
|---|---|
alist server |
Run the server |
alist admin set <pw> / random / token |
Admin credential management |
alist cancel2fa |
Remove admin 2FA |
alist storage list / disable <path> |
Storage admin |
alist mcp --user <name> |
MCP over STDIO |
alist version |
Build info |
Database backends: sqlite3 (default) · mysql · postgres — no cross-backend migration tool; shared schema, table_prefix default x_.
Permission bits (0–16): SeeHides, AccessWithoutPassword, AddOfflineDownload, Write, Rename, Move, Copy, Remove, WebdavRead, WebdavManage, FTPAccess, FTPManage, ReadArchives, Decompress, PathLimit, MCPAccess, MCPManage.
Required driver methods: Config, GetStorage, SetStorage, GetAddition, Init, Drop, List, Link. Everything else is an optional capability interface.
Core dependencies: Gin (HTTP), GORM (DB), Cobra (CLI), resty (HTTP client), bleve (search), tache (tasks), mcp-go (MCP), webauthn/otp/go-oidc/ldap (auth), frp (tunneling).