# AList Administrator & Developer Guide

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.

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

```text
                          ┌──────────────────────────────────────────┐
                          │                  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:

```go
// 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:

```text
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=50`MB, `max_backups=30`, `max_age=28`d, `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` |

```bash
# 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)

```ini
# /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
```

```bash
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**.

```yaml
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:

```nginx
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.

### 6.3 Transport & link security

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

```bash
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:

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

```bash
# 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:

```go
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:

```go
// 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
}
```

```go
// 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`:
   ```go
   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`:
   ```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).

---

_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. Unofficial; not affiliated with or reviewed by the project._
