Skip to content

Common Workflows

maxlandon edited this page Jul 18, 2026 · 1 revision

Common Workflows · [User+Dev]

End-to-end recipes for the tasks operators, admins and integrators actually perform. Each recipe states its intent and links to the page with the full detail. Command details are in the CLI Reference; the Go API is in Server API and Client API.

Commands are shown on the standalone teamserver/teamclient binaries. In a real tool the tree is grafted under your app, so prefix with your app name (myapp teamserver ...) — see Getting Started.


1. Admin: stand up a shared server

Bring a teamserver up, onboard operators, and run it as a daemon.

# Create yourself and any operators (each produces a *.teamclient.cfg to hand out).
teamserver user --name Michael --host teamserver.example.com
teamserver user --name Bob     --host teamserver.example.com --save ~/handout/

# Register persistent listeners, review, then run the blocking daemon.
teamserver listen --host 0.0.0.0 --port 31337 --persistent
teamserver status
teamserver daemon --host 0.0.0.0 --port 31337

Intent: get from empty state to a running, operator-ready server. Persistent listeners come back automatically under the daemon. Hand each generated *.teamclient.cfg to its operator. See Users & Authentication.

From code, the same bring-up is UserCreateListenerAddServeDaemon:

teamserver, _ := server.New("myapp", server.WithHandler(grpcStack))
cfg, _ := teamserver.UserCreate("Michael", "teamserver.example.com", 0)
_ = os.WriteFile("Michael.teamclient.cfg", mustJSON(cfg), 0o600)
_ = teamserver.ListenerAdd(grpcStack.Name(), "0.0.0.0", 31337) // persist
_ = teamserver.ServeDaemon("0.0.0.0", 31337)                   // blocks until SIGTERM

2. Operator: onboard and use

Receive a config file from the admin, import it, and query the server.

teamclient import ~/Michael_teamserver.example.com.teamclient.cfg
teamclient import --default ~/Michael_teamserver.example.com.teamclient.cfg  # also set default

teamclient users     # who else is on the team
teamclient version   # client + server build info

Intent: a one-time import, then transparent connect-on-command. The client prompts to choose a config only if several exist and none is the default. version prints both the local client build and the remote server build (see Client API → Queries).


3. Deploy under systemd

Generate and install a unit that runs the daemon on boot.

teamserver systemd --binpath /usr/local/bin/myapp --host 0.0.0.0 --port 31337 \
  --user myapp --save /etc/systemd/system/myapp-teamserver.service
sudo systemctl enable --now myapp-teamserver

Intent: hand the daemon lifecycle to the OS. The unit wraps teamserver daemon with the chosen bind address and service user. Without --save, the unit is printed to stdout so you can review or redirect it.


4. Manage listeners at runtime

teamserver listen --host 10.0.0.5 --port 32333 --listener gRPC --persistent
teamserver status            # find the listener ID
teamserver close 3f9ab21c    # stop + unpersist by ID prefix

Intent: add/remove binds without restarting. --persistent controls whether a bind survives into the config and comes back under the daemon; --listener picks a registered handler by name (omit it to use the default). From code this is ServeAddr (returns a job ID) and ListenerClose(id) — see Server API → Serving & listeners.


5. Rotate / revoke an operator

teamserver delete Bob                                        # revokes cert + token, kicks live sessions
teamserver user --name Bob --host teamserver.example.com     # re-issue a fresh config

Intent: immediate revocation followed by re-issue. Deletion clears the in-memory token cache, so a revoked operator is refused on their next request, not just at reconnect. In code: UserDelete("Bob") then UserCreate("Bob", ...). See Users & Authentication → User lifecycle.


6. Share operators between servers (CA import/export)

Users live in a users Certificate Authority. Export the whole CA from one server and import it into another so several teamservers trust the same operator set (e.g. a primary and a warm standby, or a compute pool fronted by many hosts).

# On server A: export the users CA (cert + private key) to a file.
teamserver export ~/myapp-users.teamserver.ca

# On server B: import server A's users CA (its configs may also be imported directly).
teamserver import ~/myapp-users.teamserver.ca

Go API — UsersGetCA / UsersSaveCA move the same PEM material:

certPEM, keyPEM, err := primary.UsersGetCA() // export
standby.UsersSaveCA(certPEM, keyPEM)         // import — operators now authenticate on both

Intent: one operator identity, many servers. After import, an operator's existing config authenticates against the second server too (matching host/port aside). The exported file is JSON of the form {"certificate": "...", "private_key": "..."}. See Users & Authentication → Sharing users between teamservers.


7. Embedded / in-memory use (developers)

Run the whole core without touching the host filesystem or network — for tests, demos, or a program that isn't ready to persist anything yet.

// Everything in memory: filesystem + SQLite. No file logs, no disk artifacts.
teamserver, _ := server.New("myapp", server.WithInMemory())
self := teamserver.Self(client.WithInMemory())

// Drive the core directly — no transport, TLS or tokens on the in-memory path.
cfg, _ := teamserver.UserCreate("alice", "localhost", 0)
users, _ := self.Users() // answered straight by the server

Intent: a fully functional teamserver that leaves no trace. The public API is unchanged; only the backends move to memory. To exercise a real transport in memory instead, use a bufconn-style dialer like the gRPC example's grpc.NewClientFrom(server). Full treatment, including test patterns, in Testing & In-Memory Use.


8. Embedded console app (developers)

For closed-loop / readline apps that reuse one connection across many commands, build the client with client.WithNoDisconnect() and wire commands with the PreRunNoDisconnect runner so they don't disconnect after each execution.

teamclient, _ := client.New("myapp",
    client.WithDialer(dialer),
    client.WithNoDisconnect(), // keep the connection across commands
)

Intent: one long-lived connection driving many interactive commands. See Client API → command tree & runners.


Related: Users & Authentication → · Getting Started → · CLI Reference → · Testing & In-Memory Use →

Clone this wiki locally