-
Notifications
You must be signed in to change notification settings - Fork 2
Getting Started
This page takes you from go get to a running teamserver with users and remote clients.
Embedding a teamclient or teamserver should fit in a handful of function calls.
- Install
- The smallest possible teamserver
- Graft the CLI onto your own app
- Serve remote clients (add a transport)
- A client-only binary
- Version stamping
- Where files live
go get github.com/reeflective/team@latestThe packages you will import:
import (
"github.com/reeflective/team" // shared types: User, Version, Client
"github.com/reeflective/team/server" // teamserver core
"github.com/reeflective/team/client" // teamclient core
"github.com/reeflective/team/log" // console/slog logging
servercmds "github.com/reeflective/team/server/commands" // teamserver CLI tree
clientcmds "github.com/reeflective/team/client/commands" // teamclient CLI tree
)The example gRPC transport lives under github.com/reeflective/team/example/transports/grpc.
It is a reference implementation you can copy — not a hard dependency.
A teamserver with no transport registered can still serve itself in memory. The functionality is complete and works identically regardless.
package main
import (
"log"
"github.com/reeflective/team/server"
"github.com/reeflective/team/server/commands"
)
func main() {
teamserver, err := server.New("smallserver")
if err != nil {
log.Fatal(err)
}
// Generate the server-side command tree. It also nests the client-only
// commands under a "client" subcommand (so the server can be a client of itself).
serverCmds := commands.Generate(teamserver, teamserver.Self())
if err := serverCmds.Execute(); err != nil {
log.Fatal(err)
}
}server.New only creates the application default directory — no files, logs, connections
or database interaction happen yet. Everything is deferred until it is actually needed.
commands.Generate(teamserver, teamclient) returns a plain *cobra.Command.
In a real tool you don't make the teamserver your main. You attach its command tree to
your application's own root command, where it becomes a teamserver subcommand alongside
everything else your program does.
// Your application already has a root command and its own subcommands.
rootCmd := newAppRootCommand() // "cracker", with its own logic
// Build the teamserver core (no transport needed for a purely in-memory server).
teamserver, err := server.New("cracker")
if err != nil {
log.Fatal(err)
}
// Generate the teamserver command tree and graft it under your root.
teamCmds := commands.Generate(teamserver, teamserver.Self())
rootCmd.AddCommand(teamCmds) // now: `cracker teamserver ...`
rootCmd.Execute()This is why users type cracker teamserver daemon, cracker teamserver client users, and
so on. See CLI Reference for the full tree.
Serving remote clients means giving the server a transport backend (a Handler) and giving
its self-client the matching Dialer. The example gRPC backend ships both.
import (
"github.com/reeflective/team/client"
"github.com/reeflective/team/server"
"github.com/reeflective/team/server/commands"
grpc "github.com/reeflective/team/example/transports/grpc/server"
)
func main() {
// A ready-made gRPC listener backend. It serves BOTH remote (mTLS-authenticated)
// and in-memory (unauthenticated) clients.
gTeamserver := grpc.NewListener()
// Register the gRPC backend with the teamserver: any gRPC teamclient can now connect.
teamserver, err := server.New("teamserver", server.WithHandler(gTeamserver))
if err != nil {
log.Fatal(err)
}
// Give the server's own in-memory self-client the matching client-side gRPC backend,
// bound to the same server, so the server can also be a client of itself.
gTeamclient := grpc.NewClientFrom(gTeamserver)
teamclient := teamserver.Self(client.WithDialer(gTeamclient))
serverCmds := commands.Generate(teamserver, teamclient)
if err := serverCmds.Execute(); err != nil {
log.Fatal(err)
}
}Notes:
-
grpc.NewListener()implementsserver.Handler;server.WithHandlerregisters it. The first handler registered becomes the default (self). -
grpc.NewClientFrom(server)builds an in-memory (bufconn) dialer bound to that server — no network, no TLS. A remote client instead usesgrpc.NewTeamClient()(see below). - The gRPC dialer also implements
team.Client(Users/VersionServer), soclient.WithDialer(...)alone is enough — you do not also needWithTeamClient.
You are not obliged to use the CLI. Serve directly:
// Non-blocking: start a listener on a host:port; returns a job ID you can close.
listenerID, err := teamserver.ServeAddr(gTeamserver.Name(), "localhost", 31350)
// ...
err = teamserver.ListenerClose(listenerID)
// Or block and run as a daemon (also starts persistent listeners). Ctrl-C / SIGTERM stops it.
err = teamserver.ServeDaemon("localhost", 31350)A program may ship a teamclient with no server code at all:
package main
import (
"log"
"github.com/reeflective/team/client"
"github.com/reeflective/team/client/commands"
grpc "github.com/reeflective/team/example/transports/grpc/client"
)
func main() {
// Remote gRPC dialer (Mutual TLS by default). It also implements team.Client.
gTeamclient := grpc.NewTeamClient()
teamclient, err := client.New("teamserver", client.WithDialer(gTeamclient))
if err != nil {
log.Fatal(err)
}
root := commands.Generate(teamclient) // "teamclient" tree: import, users, version
if err := root.Execute(); err != nil {
log.Fatal(err)
}
}The client is not connected to any server until a command needs it: the generated commands
call client.Connect() automatically, loading a config from disk (prompting to choose one
if several exist). See Users & Authentication for how a
config gets onto disk.
VersionClient() / VersionServer() report build info embedded at compile time. To update
your teamserver/teamclient version information, run from your module root:
go generate ./...This regenerates the embedded version info via the library's generation script.
By default the application uses ~/.<app>/ as its home directory, overridable at runtime
with the <APP>_ROOT_DIR environment variable (e.g. CRACKER_ROOT_DIR).
| Path | Purpose |
|---|---|
~/.<app>/teamserver/configs/ |
Server config (<app>.teamserver.json) and saved user CAs |
~/.<app>/teamserver/logs/ |
<app>.teamserver.log, audit.json
|
~/.<app>/teamserver/certs/ |
Users' CA PEM backups |
~/.<app>/teamclient/configs/ |
Imported remote-server configs (*.teamclient.cfg) |
~/.<app>/teamclient/logs/ |
<app>.teamclient.log |
The teamserver/teamclient subdirectory name is configurable with WithTeamDirectory,
the home path with WithHomeDirectory. Everything can also run fully in memory with
WithInMemory — see Testing & In-Memory Use and
Configuration.