Jesse A. Green / notebook

AList Administrator & Developer Guide

Generated June 2026 from AlistGo/alist at d0cec67

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

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.db by 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 holds config.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:

  1. A request arrives for a path like /onedrive/photos/x.jpg.
  2. internal/fs finds the storage whose mount path is the longest prefix (/onedrive).
  3. It strips the mount prefix and calls the driver's methods (List, Link, Put, …) with the remaining path.
  4. The driver talks to the actual backend and returns model.Obj / model.Link values.

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_concurrency and 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, restrict cors.allow_origins to 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-prefix to drop the ALIST_ prefix (e.g. plain DB_TYPE). Set force: true (or ALIST_FORCE=true) to make config.json authoritative 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 token setting (admin API token) and index_progress are "single" settings managed by AList, not freely editable. Retrieve the admin token with alist admin token or POST /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 own start/stop/kill daemon 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), set site_url to that full URL — server/router.go groups all routes under conf.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 with alist cancel2fa.
  • WebAuthn / passkeys — go-webauthn; registration and login endpoints under /authn.
  • SSO (OIDC) — coreos/go-oidc; configured via the sso_* 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_secret defaults 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.go checks PermWebdavManage + PermWrite for PUT/MKCOL, PermMove/PermRename for MOVE, and PermCopy for COPY; server/ftp.go and server/sftp.go require PermFTPAccess to authenticate at all.

  • Server TLS — set scheme.https_port, cert_file, key_file; optionally force_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, with link_expiration controlling validity. Verified by the sign.Verify middleware on /d, /p, archive routes, etc.
  • Privacy regex — privacy_regs scrubs 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_https if serving both.
  • [ ] Narrow cors.allow_origins from * to your front-end origin.
  • [ ] Restrict or disable the guest user; require login for sensitive trees.
  • [ ] Enable sign_all (or per-storage sign) + link_expiration for public direct links.
  • [ ] Lock filesystem permissions on data/ (it contains jwt_secret and 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.json contains the jwt_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:

  1. Create drivers/<name>/ with driver.go, meta.go, types.go, util.go.
  2. Define the storage type embedding model.Storage and your Addition:
    type MyDrive struct {
        model.Storage
        Addition
    }
    
  3. Implement Meta + Reader (Config, GetAddition, Init, Drop, List, Link) and any optional capabilities (Put, Move, …). Return errs.NotImplement for unimplemented optional methods so the internal archive/fallback tools can take over where applicable.
  4. Declare config and register in meta.go:
    var config = driver.Config{ Name: "MyDrive", DefaultRoot: "/" }
    func init() {
        op.RegisterDriver(func() driver.Driver { return &MyDrive{} })
    }
    
  5. Wire it in — ensure the package is imported by drivers/all.go (blank import) so its init() runs.
  6. Assert conformance: var _ driver.Driver = (*MyDrive)(nil).
  7. Rebuild — drivers are compiled in; there is no runtime plugin loading.

Tip

Follow the upload guidance in the Put doc comment: support cancellation via ctx, report progress via the UpdateProgress callback (or driver.Progress/ReaderUpdatingProgress), and apply server upload limits with driver.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/task group 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).