-
Notifications
You must be signed in to change notification settings - Fork 2
Server API
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
- Serving & listeners
- The Handler interface
- Users, authentication & PKI
- Logging
- Config, directories, filesystem, database
- Options
- Errors
- Command tree
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.
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.
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.
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. |
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. |
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.
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.
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.
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 →