AList is a Go file-listing server that wraps many storage backends. The useful parts are its Cobra commands, Gin server setup, provider registry, and protocol boundaries.

AList: what a Go file server looks like when storage providers are the product


AList is a file-listing and WebDAV server for a long list of storage providers: local disk, S3, Google Drive, OneDrive, Aliyun Drive, FTP/SFTP, SMB, Dropbox, Azure Blob Storage, and plenty of region-specific services. The interesting Go lesson is how the project keeps that many providers behind one server without making the HTTP layer know every provider’s quirks.

The repository is a conventional large Go application: Cobra commands in cmd/, protocol servers under server/, provider implementations under drivers/, and public assets embedded through public/.

Cobra at the edge

AList uses Cobra for operational commands: start the server, manage the admin user, list or disable storage, generate language files, and run MCP support.

The server command is the main entry point:

var ServerCmd = &cobra.Command{
	Use:   "server",
	Short: "Start the server at the specified address",
	Run: func(cmd *cobra.Command, args []string) {
		// initialize config, database, routes, and servers
	},
}

That keeps operational concerns out of main. The command package owns flags and process setup; the server packages own HTTP behaviour. It is a simple boundary, but it pays off when an app has more lifecycle commands than just serve.

Gin as a thin routing layer

The HTTP server starts with a plain Gin engine:

r := gin.New()
r.Use(
	gin.LoggerWithWriter(log.StandardLogger().Out),
	gin.RecoveryWithWriter(log.StandardLogger().Out),
)

var httpHandler http.Handler = r
httpSrv = &http.Server{Addr: httpBase, Handler: httpHandler}

AList can expose several protocol surfaces from the same process: normal HTTP, HTTPS, Unix socket, S3-compatible routes, WebDAV, FTP, SFTP, and MCP HTTP. The Go lesson is to keep each server as an http.Server or protocol-specific listener and shut them down through shared process handling.

The shutdown path uses a context timeout:

ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
_ = httpSrv.Shutdown(ctx)

That is the right default for a server that may be running in Docker, systemd, or a NAS environment. Give in-flight work a short window, then exit.

Provider registration is the real architecture

The storage providers are collected in drivers/all.go. The exact list changes over time, but the pattern is stable: each provider lives in its own package and registers through a common driver interface.

That is the main design choice in AList. The HTTP layer should not care whether a file comes from S3, SMB, Google Drive, or a local path. It asks the storage abstraction to list, open, move, copy, rename, or remove objects. Provider-specific authentication, pagination, path rules, and download URLs stay inside the driver.

For Go applications that integrate many third-party systems, this is usually the cleanest shape:

  • one package per provider
  • small shared interfaces
  • provider metadata collected in one registry
  • server code talks to the interface, not the provider

AList is a good case study because the provider count is high enough to stress that boundary.

Protocols are separate from providers

AList does not only serve a web UI. It also exposes file access through WebDAV, S3-compatible routes, FTP, and SFTP. That creates a second axis of variation: many providers behind many protocols.

The useful structure is to keep protocols under server/ and providers under drivers/. WebDAV should not know how Google Drive tokens are refreshed. The Google Drive driver should not know how WebDAV maps PROPFIND to directory listings. The shared file operations sit between them.

That separation is boring in the best way. Once the boundary is clear, adding a storage provider and adding a protocol are different jobs.

Operational details matter

AList has commands for admin password generation, token display, two-factor reset, storage listing, and storage disabling. Those commands are not glamorous, but they are what make a self-hosted server survivable after the first install.

There is also a reminder in the README that AList proxies or redirects file access rather than owning the user’s data. That should shape how you read the code: it is mostly orchestration, authentication, metadata, and protocol translation.

What to take from AList

The useful Go lessons are in the boundaries. Cobra handles process operations. Gin handles HTTP routing. http.Server instances model the exposed services. Storage providers sit behind driver interfaces. Protocol adapters live separately from provider implementations.

That is the pattern to copy from AList: when a product is mostly integrations, make the integration boundary explicit early. The rest of the code gets easier to reason about.