Skip to content

Server API

maxlandon edited this page Jul 18, 2026 · 1 revision

Server API · [Dev]

A guided tour of the public github.com/reeflective/team/server package. It is a map of the surface with just enough prose to know when to reach for each call; full signatures and per-method docs live at https://pkg.go.dev/github.com/reeflective/team/server.


Constructor & identity

teamserver, err := server.New("myapp", opts...)

New(app string, opts ...Options) (*Server, error) creates the core for an application named app. It only resolves the app home directory (~/.<app>/, or <APP>_ROOT_DIR); the database, certificates and log files are all created lazily on first use. Create one teamserver per application name.

Method What it does
Name() string The application name (not the binary name).
Self(opts ...client.Options) *client.Client An in-memory teamclient of this server. The server is its team.Client backend automatically (via WithTeamClient(ts)), so Users()/VersionServer() are answered directly. Pass client.WithDialer(...) to route the self-client through a real transport instead.
VersionServer() (team.Version, error) This server binary's build/version info.

Self() is what makes "a program is a client of itself" real — see Testing & In-Memory Use and Getting Started.


Serving & listeners

A Handler is a registered transport stack (keyed by Name()); serving one on a host:port creates a Listener (a bind job you can close by ID). All serving calls are non-blocking except ServeDaemon.

// Non-blocking: start a named handler on an address; returns a job ID you can close.
id, err := teamserver.ServeAddr(gTeamserver.Name(), "0.0.0.0", 31337)
// ...
err = teamserver.ListenerClose(id)

// Blocking: run as a daemon. Also starts persistent listeners; stops on SIGTERM.
err = teamserver.ServeDaemon("0.0.0.0", 31337)
Method Notes
Serve(cli *client.Client, opts ...Options) error Serve the default in-memory listener and connect the given self-client (uses an internal {User: "server"} config, so no prompt, no TLS). Handy in tests.
ServeAddr(name, host string, port uint16, opts ...Options) (id string, err error) Start handler name on host:port. Empty/unknown name falls back to the default (self) handler. Non-blocking; returns the job ID.
ServeDaemon(host string, port uint16, opts ...Options) error Blocking. If host/port are blank it uses the config's DaemonMode values. Starts all persistent listeners, then blocks until SIGTERM.
Handlers() map[string]Handler A copy of the registered handler map.
Listeners() []*job The currently running listener jobs (for persistent-but-stopped ones, read GetConfig().Listeners).
ListenerAdd(name, host string, port uint16) error Persist a listener in the config. Does not start it — call ServeAddr.
ListenerRemove(id string) Drop a persistent listener from the config. Does not stop a running one.
ListenerClose(id string) error Stop a running listener by ID (ErrListenerNotFound if unknown).
ListenerStartPersistents() error Start every listener saved in the config. Honors WithContinueOnError.

The default port for teamserver applications is 31416 (WithDefaultPort changes it). See Common Workflows for daemon + systemd recipes.


The Handler interface

type Handler interface {
    Name() string                          // unique key, e.g. "gRPC"
    Init(s *Server) error                  // transport-agnostic prep (credentials, middleware)
    Listen(addr string) (net.Listener, error) // transport-specific bind; MUST NOT block
}

Register handlers with WithHandler. Every error these methods return is treated as critical and aborts the serve. The first handler registered becomes the default (self). The full walkthrough — including the two-phase Init/Listen contract and the gRPC reference — is in Writing a Transport.


Users, authentication & PKI

The heart of the library. Full treatment (identity model, mutual TLS, config files, import/export) is in Users & Authentication; the surface:

cfg, err := teamserver.UserCreate("alice", "localhost", 0) // 0 ⇒ configured daemon port
data, _ := json.Marshal(cfg)                               // → alice_localhost.teamclient.cfg

user, err := teamserver.Authenticate(rawToken)             // the identity primitive
err = teamserver.UserDelete("alice")                       // revokes cert + token immediately
Method Notes
UserCreate(name, lhost string, lport uint16) (*client.Config, error) Mint a user + its client.Config (token + TLS material). name must be alphanumeric (plus _/-); lhost required; lport 0 ⇒ configured daemon port. Errors: ErrUserConfig, ErrCertificate, ErrDatabase.
UserDelete(name string) error Delete cert + token, clear the token cache (live sessions refused on next request).
Users() ([]team.User, error) All users with online/last-seen, computed from the live token cache.
Authenticate(rawToken string) (*team.User, error) The identity primitive — verify a token, learn who. No authorization data, by design. ErrUnauthenticated for unknown/deleted.
UsersTLSConfig() (*tls.Config, error) Server-side mutual-TLS config: RequireAndVerifyClientCert, users CA, TLS 1.3 minimum. Use it in your Handler.Listen.
UsersGetCA() (cert, key []byte, err error) / UsersSaveCA(cert, key []byte) Export / import the whole users CA to share an operator set between servers.

Logging

Thin façade over the log package; details and styling in Logging.

Method Notes
NamedLogger(pkg, stream string) *slog.Logger A tagged logger; pkg renders in the aligned package column. Never nil.
AuditLogger() (*slog.Logger, error) The independent JSON audit logger (default audit.json), logs at Debug so every request is recorded.
SetLogLevel(level int) Adjust console + file levels together (no-op for a WithLogger handler).
SetLogFormat(format log.Format) Console/stdout format: console / text / json.

Config, directories, filesystem, database

cfg := teamserver.GetConfig()        // *server.Config (daemon bind, log level, persistent listeners)
_   = teamserver.SaveConfig(cfg)
path := teamserver.ConfigPath()      // ~/.<app>/teamserver/configs/<app>.teamserver.cfg
Group Methods
Config GetConfig() *Config, SaveConfig(*Config) error, ConfigPath() string
Directories HomeDir(), TeamDir(), LogsDir(), ConfigsDir(), CertificatesDir() — each creates the directory on demand
Filesystem Filesystem() *assets.FS — on-disk, or in-memory under WithInMemory()
Database Database() *gorm.DB (a fresh session, never nil), DatabaseConfig() *db.Config

The Config struct carries DaemonMode (host/port), a Log section (file level, gRPC payload logging, TLS key logging) and the list of persistent Listeners. Its default port is 31416 and default file log level is Info.


Options

Pass at New(...), and (for the reusable ones) at the Serve* calls. Some may only be set once. Full reference table in Configuration → Server options.

Option Purpose
WithInMemory() Route the filesystem + SQLite entirely to memory (also disables file logs). Non-SQLite DBs error.
WithDefaultPort(uint16) Default daemon/listener port (library default 31416).
WithHandler(Handler) Register a transport stack. Repeatable; first becomes default.
WithDatabase(*gorm.DB) Use an existing gorm DB (auto-migrates users + certs).
WithDatabaseConfig(*db.Config) Connect to a DB from a config.
WithDatabaseKey(string) Transparent encryption-at-rest for the default file SQLite (adiantum VFS; pure-Go/wasm builds; key never persisted).
WithHomeDirectory(path) / WithTeamDirectory(name) Relocate ~/.<app>/ and the teamserver subdir.
WithNoLogs(bool) / WithLogFile(path) Silence file logging / relocate the log file.
WithLogger(slog.Handler) Replace the console+file backend wholesale (disables runtime level knobs).
WithConsoleOptions(func(*log.ConsoleOptions)) Restyle the built-in console, keep the loggers.
WithLogFormat(log.Format) Console stream format: console / text / json.
WithContinueOnError(bool) Keep starting persistent listeners after one fails (joins errors).

See Logging for the three logging options and Writing a Transport for WithHandler.


Errors

Sentinel errors you can match with errors.Is:

ErrNoListener, ErrListener, ErrListenerNotFound, ErrUnauthenticated, ErrUserConfig, ErrCertificate, ErrDatabase, ErrDatabaseConfig, ErrConfig, ErrTeamServer, ErrLogging, ErrDirectory, ErrDirectoryUnwritable, ErrSecureRandFailed.

Every error returned by the API is logged before it is returned.


Command tree

root := server_commands.Generate(teamserver, teamserver.Self()) // *cobra.Command
rootCmd.AddCommand(root) // → `myapp teamserver ...`

server/commands.Generate(teamserver *Server, teamclient *client.Client) *cobra.Command returns the full teamserver tree, with the teamclient tree nested under a client subcommand (so a server can be a client of itself). See CLI Reference.


Related: Client API → · Writing a Transport → · Users & Authentication → · Configuration →

Clone this wiki locally