Documentation
¶
Overview ¶
Package bay is the orchestration layer: it turns "make me a bay" into a worktree, a port block, a set of containers, and a hostname, and turns "remove it" back into nothing.
The pieces below it each do one thing and are independently testable. This package exists because creating and destroying them has to be atomic: a worktree without containers is a checkout nobody asked for, containers without a worktree are pointing at a directory that no longer exists, and a port block without either is a leak that is invisible until allocation starts failing.
Index ¶
- Constants
- Variables
- func DeriveAlias(branch string) string
- func EnvName(ref string) string
- type Bay
- type Booter
- type CreateOptions
- type Info
- type Manager
- func (m *Manager) Adopt(b *Bay)
- func (m *Manager) Broker() *broker.Broker
- func (m *Manager) Close() error
- func (m *Manager) Create(ctx context.Context, opts CreateOptions) (*Bay, error)
- func (m *Manager) Describe(ctx context.Context, b *Bay) (Info, error)
- func (m *Manager) Destroy(ctx context.Context, name string, force bool) error
- func (m *Manager) Focus(ctx context.Context, name string) error
- func (m *Manager) Get(name string) (*Bay, bool)
- func (m *Manager) List(ctx context.Context) ([]Info, error)
- func (m *Manager) NewBooter(worktree, name string) *Booter
- func (m *Manager) Proxy() *proxy.Proxy
- func (m *Manager) RunTask(ctx context.Context, name, task string) (*engine.TaskResult, error)
- func (m *Manager) Scrubber() *scrub.Scrubber
- func (m *Manager) Secrets() *Secrets
- func (m *Manager) SetSecret(ref, value string)
- func (m *Manager) VerifyManifest(ctx context.Context, candidate []byte, patcher verify.Patcher) (*verify.Result, error)
- type Options
- type Secrets
- type ServiceInfo
Constants ¶
const MaxAlias = 12
MaxAlias is the longest useful label. Past this a browser tab truncates it and the label stops doing its job.
Variables ¶
var ManifestNames = []string{"devbay.yaml", "devbay.yml"}
ManifestNames are the files a bay is described by, in precedence order.
Functions ¶
func DeriveAlias ¶
DeriveAlias turns a branch name into a short label.
Agents produce branch names like feat/refactor-auth-middleware-to-support-refresh-token-rotation, which is useless in a tab strip or an `ls` column. The meaningful head of the name is almost always enough to tell two bays apart.
Types ¶
type Bay ¶
type Bay struct {
Name string
Branch string
// Alias is the short human label. Agents generate branch names far too
// long to read in a tab strip, so a bay carries a separate name capped
// short enough to survive being truncated.
Alias string
Worktree string
Adopted bool
Manifest *manifest.Manifest
Engine *engine.Engine
}
Bay is one running instance of a repository.
type Booter ¶
type Booter struct {
// contains filtered or unexported fields
}
Booter boots a candidate manifest in a throwaway bay and tears it down.
The bay is disposable on purpose. Verification exists to answer one question -- does this configuration actually work -- and answering it by leaving containers, volumes and a port block behind would make `devbay init` a command that quietly costs resources every time it is run.
func (*Booter) Boot ¶
Boot brings a candidate up and reports the first thing that went wrong.
The failure carries the service and its own logs, because a patcher handed "boot failed" can do nothing useful with it, and the cause is nearly always in what the container printed: a missing variable, a refused connection, a migration that has not run.
type CreateOptions ¶
type CreateOptions struct {
Name string
Branch string
From string
Alias string
// Boot brings the services up. When false the worktree is prepared and
// the manifest validated, but nothing is started.
Boot bool
}
CreateOptions describe a bay to create.
type Info ¶
type Info struct {
Name string `json:"name"`
Alias string `json:"alias"`
Branch string `json:"branch"`
State string `json:"state"`
Worktree string `json:"worktree"`
Adopted bool `json:"adopted,omitempty"`
URLs map[string]string `json:"urls,omitempty"`
Ports map[string]int `json:"ports,omitempty"`
Services []ServiceInfo `json:"services,omitempty"`
MemoryMB int64 `json:"memory_mb,omitempty"`
Focused bool `json:"focused,omitempty"`
}
Info is the serialisable view of a bay, and is what crosses the MCP boundary.
type Manager ¶
type Manager struct {
RepoRoot string
Log func(format string, args ...any)
// contains filtered or unexported fields
}
Manager owns every bay for one repository.
func (*Manager) Adopt ¶
Adopt registers an already-created bay, used when the daemon restarts and finds containers it did not start in this process.
func (*Manager) Destroy ¶
Destroy removes a bay completely.
The order matters: containers, volumes and network first, then the port block, then the worktree. Removing the worktree while containers still bind-mount it would leave them pointing at a path that no longer exists.
type Options ¶
type Options struct {
// Dir is any path inside the repository.
Dir string
// StatePath is the SQLite file; empty uses ~/.devbay/state.db.
StatePath string
// WorktreeRoot is where new worktrees go; empty uses ~/.devbay/worktrees.
WorktreeRoot string
// ProxyPort is the host port for bay hostnames; 0 tries 80 then 8080.
ProxyPort int
// AdminPort is where the proxy's config API is published, on loopback.
AdminPort int
// NoProxy disables hostname routing, leaving bays reachable at
// 127.0.0.1:<port> only.
NoProxy bool
// Egress enforces per-service network allowlists. Off by default because
// it costs a privileged sidecar per service; on, a service reaches only
// what its manifest declares.
Egress bool
// AuditPath is the credential log; empty uses ~/.devbay/audit.jsonl.
AuditPath string
// SecretCommand is a secret manager to shell out to, with {ref}
// substituted -- for example ["op", "read", "op://{ref}"]. Configured by
// the developer, never by a manifest.
SecretCommand []string
Log func(format string, args ...any)
}
Options configure a Manager.
type Secrets ¶
type Secrets struct {
// contains filtered or unexported fields
}
Secrets resolves ${secret:path} references at container spawn time.
This is deliberately the smallest thing that can be correct rather than an attempt at a secrets manager. The ecosystem already converged on one shape -- `op run --`, `direnv exec`, `sops exec-env`, `dotenvx run --` all inject values into a subprocess and hold nothing -- so the useful thing to build is a consumer of those, not a competitor to them. What lives here is the registry that resolution and scrubbing share, plus an environment fallback.
Two properties matter more than where the values come from:
- A value registered here is registered with the scrubber in the same call. Resolving a secret without teaching the scrubber about it would hand an application a credential that devbay could no longer recognise in that application's own logs.
- Lookup happens when a container is created, not when a manifest is read, so a value is never held longer than it has to be.
func NewSecrets ¶
NewSecrets returns a registry that reports every value it hands out to s.
func (*Secrets) Lookup ¶
Lookup resolves a reference, falling back to the environment.
The environment fallback is what makes devbay work with every tool listed above without integrating with any of them: `op run -- devbay run ...` already puts the values in devbay's environment, and this finds them there.
type ServiceInfo ¶
type ServiceInfo struct {
Name string `json:"name"`
State string `json:"state"`
URL string `json:"url,omitempty"`
}
ServiceInfo is one service's observable state.