Skip to content

Getting Started

maxlandon edited this page Jul 18, 2026 · 1 revision

Getting Started / Embedding · [User+Dev]

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

go get github.com/reeflective/team@latest

The 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.


The smallest possible teamserver (in-memory)

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.


Graft the CLI onto your own app (recommended)

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.


Serve remote clients (add a transport)

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() implements server.Handler; server.WithHandler registers 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 uses grpc.NewTeamClient() (see below).
  • The gRPC dialer also implements team.Client (Users/VersionServer), so client.WithDialer(...) alone is enough — you do not also need WithTeamClient.

Starting listeners from code (no CLI)

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 client-only binary

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.


Version stamping

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.


Where files live

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.


Next: Users & Authentication → · Writing a Transport →

Clone this wiki locally