zkit

package module
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 13, 2026 License: MIT Imports: 16 Imported by: 0

README

zkit

Standard-library-first runtime base for http services, with default-safe building blocks and an explicit ops-admin endpoint.

Documentation: pkg.go.dev

Contents

Why zkit

In production, teams often fall into one of two extremes:

  • Adopt a heavy framework, and pay with glue-code noise, architectural lock-in, implicit “magic”, reduced control, and a larger supply-chain surface.
  • Build everything by hand, and pay with repetitive scaffolding, inconsistent conventions, and reliability gaps that reappear across services.

zkit takes a middle path: it embraces the Go standard library and tries hard not to interfere with your existing net/http habits, while smoothing out the boring runtime scaffolding and keeping operational knobs explicit and default-safe.

Batteries included

zkit ships a small set of operational building blocks you typically end up needing around net/http:

  • Service lifecycle (graceful start/shutdown)
  • Safe goroutines (panic/error observable runners)
  • Companion tasks that live alongside your primary HTTP service (periodic jobs and on-demand triggers)
  • Runtime tuning: typed, runtime-settable parameters (“knobs”) for operational control (e.g. feature toggles, thresholds, timeouts, sampling rates)
  • Publish process-owned diagnostic/config state for debugging and incident response as text/JSON (e.g. a static configuration view)

Instead of leaving you to wire these pieces together yourself, zkit provides a mountable (or standalone) admin surface that unifies them behind a small, operator-friendly control plane with text/JSON outputs—ready to use with minimal wiring.

Requirements

  • Go 1.21+

Install

go get github.com/evan-idocoding/zkit@latest

Import:

import "github.com/evan-idocoding/zkit"

Quick start

zkit’s primary entry point is NewDefaultService: it assembles a runnable, shutdownable service (servers + optional tasks/tuning/log level), with admin either mounted under a prefix or served on a dedicated port.

Mount admin under /-/ on the same server:

mux := http.NewServeMux()
mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
	_, _ = w.Write([]byte("hello"))
})

svc := zkit.NewDefaultService(zkit.ServiceSpec{
	Primary: &zkit.HTTPServerSpec{
		Addr:    ":8080",
		Handler: mux,
	},
	Admin: &zkit.AdminSpec{
		// For demos only. In production, protect admin with token/IP-based guards.
		ReadGuard: zkit.AllowAll(),
	},
	AdminMountPrefix: "/-/",
})

_ = svc.Run(context.Background())
Example 2: Worker-only + standalone admin server

If your process has no primary HTTP server (e.g. a worker), serve admin on its own port:

// A minimal worker process: run background jobs, and expose admin on a dedicated port.
mgr := task.NewManager()

// Example background job (runs periodically).
_ = mgr.MustAdd(task.Every(10*time.Second, func(ctx context.Context) error {
	// ... do work ...
	return nil
}), task.WithName("heartbeat"))

svc := zkit.NewDefaultService(zkit.ServiceSpec{
	TasksManager:       mgr,
	TasksExposeToAdmin: true,
	Admin: &zkit.AdminSpec{
		ReadGuard: zkit.Tokens([]string{"read-token"}),
	},
	// Handler may be omitted; zkit injects the admin handler for AdminStandaloneServer.
	AdminStandaloneServer: &zkit.HTTPServerSpec{
		Addr: ":8081",
	},
})

_ = svc.Run(context.Background())
Example 3: Enable runtime controls (log level, tuning, tasks, provided snapshot)

This example demonstrates the opt-in “runtime control plane” capabilities on the admin surface.

// ---- sources (owned by your process) ----
var lv slog.LevelVar   // slog.LevelVar backing /log/level
tu := tuning.New()    // runtime knobs backing /tuning/*
mgr := task.NewManager() // background jobs backing /tasks/*

// Register a runtime-tunable knob.
_, _ = tu.Bool("feature.x", false)
// Example runtime change (in production you may do this via the admin endpoint /tuning/set).
_ = tu.SetFromString("feature.x", "on")

// Register an on-demand job (triggerable by name).
_ = mgr.MustAdd(
	task.Trigger(func(ctx context.Context) error {
		// ... do work ...
		return nil
	}),
	task.WithName("rebuild-index"),
)

// ---- access control ----
read := zkit.Tokens([]string{"read-token"})   // for GET/HEAD endpoints
writeTokens := httpx.NewAtomicTokenSet() // hot-update token set (rotate without redeploy)
writeTokens.Update([]string{"write-token"})
write := zkit.HotTokens(writeTokens) // for POST endpoints (stronger token)

// ---- business handlers ----
mux := http.NewServeMux()
mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
	_, _ = w.Write([]byte("hello"))
})

// ---- admin assembly (reads + opt-in writes + provided snapshot) ----
adminSpec := zkit.AdminSpec{
	ReadGuard:          read,
	WriteGuard:         write,
	EnableLogLevelSet:  true,
	TuningWritesEnabled: true,
	TuningWriteAllowPrefixes: []string{"feature."},
	TaskWritesEnabled:  true,
	TaskWriteAllowNames:      []string{"rebuild-index"},
	ProvidedItems: map[string]any{
		// Common use case: publish static configuration snapshots.
		"config": map[string]any{"env": "prod"},
	},
}

// ---- service assembly ----
svc := zkit.NewDefaultService(zkit.ServiceSpec{
	Primary:            &zkit.HTTPServerSpec{Addr: ":8080", Handler: mux},
	LogLevelVar:        &lv,
	LogExposeToAdmin:   true,
	Tuning:             tu,
	TuningExposeToAdmin: true,
	TasksManager:       mgr,
	TasksExposeToAdmin: true,
	Admin:              &adminSpec,
	AdminMountPrefix:   "/-/",
})

_ = svc.Run(context.Background())

Architecture (high level)


                           +------------------------+
                           |      Your services     |
                           +-----------+------------+
                                       |
                  +--------------------+--------------------+
                  |                    |                    |
         +--------v---------+          |         +----------v---------+
         |  DefaultService  |------------------->|     DefaultAdmin   |
         +--------+---------+          |         +----------+---------+
                  |                    |                    |
                  |                    |                    |
                  v                    v                    |
  +-------------------------------------------------------------------------+
  |                        Base modules (opt-in)            |               |
  |                                                         |               |
  |                                                         |  +---------+  |
  |                                                         |->|  admin  |  |
  |                                                            +---------+  |
  |                                                                         |
  |  +--------+   +---------+    +----------+   +-----------+               |
  |  |   ops  |   |  httpx  |    | rt/task  |   | rt/tuning |               |
  |  +--------+   +---------+    +----------+   +-----------+               |
  |              +------------+  +-----------+                              |
  |              |  mw|client |  | rt/safego |                              |
  |              +------------+  +-----------+                              |
  +-------------------------------------------------------------------------+

Don't be intimidated by the diagram above: DefaultService and DefaultAdmin don't add extra capabilities—they simply assemble the base modules below with default parameters for an out-of-the-box experience.

You can skip them entirely and use the base modules directly. Everything is optional, and each piece can be used on its own.

When you need finer-grained control, use the subpackages directly:

  • admin: admin subtree assembler (explicit EnableXxx + explicit Guard)
  • ops: operational handlers (health/runtime/buildinfo/tasks/tuning/loglevel/provided snapshots)
  • httpx: net/http middleware chain helpers (recover/request id/real ip/access guard/timeout/body limit/cors)
  • httpx/client: HTTP client builder (independent transport + RoundTripper middlewares + I/O guard helpers)
  • rt/task: background task primitives + manager + snapshot/trigger-and-wait
  • rt/tuning: runtime-tunable parameters (typed vars, lock-free reads)
  • rt/safego: panic/error observable goroutine runner

Note: admin endpoints are text/JSON (not HTML pages).

Principles

  • Standard library first, standard-library-oriented
  • Default-safe, with sensible defaults
  • Minimize implicit behavior and “magic”; keep knobs explicit and configurable
  • Out-of-the-box as a whole, while still encouraging you to use modules independently as building blocks

What it is not

  • Not a framework: no router, no DSL, no new programming model, and no intrusion into your business code.
  • Not a kitchen-sink: zero dependencies beyond the standard library; focused on ops, not business features.
  • Not an observability or policy suite: no automatic oTel/Prom wiring; no built-in retries/circuit breakers/rate limits.
  • zkit includes an HTTP middleware subsystem (primarily used by the admin surface), and you can also use it directly with the standard library to build your own handler stack. But it does not aim to replace whatever router/mux/middleware chain you already prefer.

Default admin endpoints

zkit’s default admin surface exposes text/JSON endpoints (not HTML pages).

  • Always-on reads (guarded by AdminSpec.ReadGuard): /report, /healthz, /readyz, /buildinfo, /runtime.
  • Optional reads (available when the corresponding sources are wired): /log/level, /tuning/snapshot, /tuning/overrides, /tuning/lookup, /tasks/snapshot, /provided.
  • Writes: off by default; when enabled, endpoints are: /log/level/set, /tuning/set, /tuning/reset-default, /tuning/reset-last, /tasks/trigger, /tasks/trigger-and-wait. They require AdminSpec.WriteGuard, explicit enable flags, and allowlists where applicable (see “Security model” below).
  • Output formats: defaults to text; use ?format=text or ?format=json (where supported).

Security model (read vs write)

zkit’s admin surface is designed to be default-safe:

  • Reads are explicit and guarded: AdminSpec.ReadGuard is required and protects all read endpoints. A nil guard is an assembly error and will panic (fail-fast).
  • Writes are off by default: AdminSpec.WriteGuard == nil disables all write endpoints.
  • Write guard and allowlists: when WriteGuard is non-nil, enable write groups explicitly via EnableLogLevelSet, TuningWritesEnabled, TaskWritesEnabled; allowlist (empty = deny-all) applies for tuning and task writes.
  • Real IP is default-safe: if trusted proxies are not configured, proxy headers are ignored and IP checks fall back to RemoteAddr.

Stability & compatibility (v0.1.x)

zkit is experimental in v0.1.x. In general, Go APIs may change.

That said, we try to keep a small set of external contracts stable to reduce operational churn:

  • Default admin paths (relative to the admin subtree):
    • Always-on reads: /report, /healthz, /readyz, /buildinfo, /runtime
    • Opt-in capabilities (when wired/enabled): /log/*, /tuning/*, /tasks/*, /provided
  • Format negotiation via ?format=text|json (where supported).
  • Security posture: reads are guarded; writes are off by default; write endpoints require an explicit guard, explicit enable flags (EnableLogLevelSet, TuningWritesEnabled, TaskWritesEnabled), and allowlists (fail-closed).

Everything else should be treated as best-effort until v1.

License

MIT License. See LICENSE.

Support

Issues(pls open a GitHub issue) and PRs are welcome!

Documentation

Overview

Package zkit provides default-safe assembly helpers for building net/http services with an operator-friendly admin surface.

The main entry points are:

  • NewDefaultService: assemble a runnable, shutdownable Service (servers + optional tasks/tuning/log level), with admin either mounted under a prefix or served on a dedicated port.
  • NewDefaultAdmin: assemble the admin subtree as an http.Handler (fixed v1 paths).

You can start using zkit by only calling NewDefaultService and then writing your business handlers as usual. When you need more control, zkit exposes lower-level building blocks in subpackages (listed below).

Quick start: primary server + mounted admin

This example mounts admin under "/-/" on the same server:

mux := http.NewServeMux()
mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
	_, _ = w.Write([]byte("hello"))
})

svc := zkit.NewDefaultService(zkit.ServiceSpec{
	Primary: &zkit.HTTPServerSpec{
		Addr:    ":8080",
		Handler: mux,
	},
	Admin: &zkit.AdminSpec{
		// For demos only. In production, protect admin with token/IP-based guards.
		ReadGuard: zkit.AllowAll(),
	},
	AdminMountPrefix: "/-/",
})

_ = svc.Run(context.Background())

With the default admin kit, the always-on read endpoints include:

  • /report
  • /healthz
  • /readyz
  • /buildinfo
  • /runtime

Security model (read vs write)

zkit's admin endpoints are designed to be default-safe:

  • Reads are explicit and guarded:

  • AdminSpec.ReadGuard is required and protects all read endpoints.

  • A nil ReadGuard is treated as an assembly error and will panic (fail-fast).

  • Writes are off by default:

  • AdminSpec.WriteGuard == nil disables all write endpoints.

  • If WriteGuard is non-nil, enable write groups explicitly: EnableLogLevelSet for /log/level/set; TuningWritesEnabled / TaskWritesEnabled for tuning and task writes. Allowlist (empty = deny-all) applies for tuning/task writes.

  • /provided (custom diagnostic snapshot) is disabled by default because it is typically more sensitive; enable it explicitly by setting AdminSpec.ProvidedItems to a non-nil map.

Tip: it is common to use separate guards for reads and writes (e.g. a read token and a stronger write token).

Admin serving mode: mount vs standalone (mutually exclusive)

NewDefaultService supports two admin modes:

  • Mount: route requests to admin first when URL path matches the reserved prefix.

  • Prefix defaults to "/-/".

  • If a request hits the base path without the trailing slash (e.g. "/-"), it is redirected to the normalized prefix (e.g. "/-/") with HTTP 307.

  • Mount requires ServiceSpec.Primary (otherwise it panics).

  • Standalone: run admin on its own managed server (usually a dedicated port).

  • This is required for "worker-only" processes with no primary server.

Service lifecycle and defaults

Service provides Start/Wait/Shutdown/Run:

  • Start: starts managed components (tasks if enabled) and starts serving on managed servers (not idempotent)
  • Wait: waits until the service fully stops (idempotent)
  • Shutdown: triggers graceful shutdown (idempotent), using ShutdownTimeout (default: 30s)
  • Run: Start → wait for an exit condition (ctx.Done or OS signal) → Shutdown → Wait

Signal handling in Run:

  • Enabled by default (SignalsDisable=false).
  • Default signals:
  • Unix: SIGINT + SIGTERM
  • Non-Unix: os.Interrupt

HTTP server defaults (only when you use Addr+Handler and let zkit build *http.Server):

  • ReadHeaderTimeout: 5s
  • IdleTimeout: 60s

If you provide HTTPServerSpec.Server, zkit does not override your timeouts/BaseContext/ErrorLog/etc.

Optional components: tasks / tuning / log level

NewDefaultService can host these optional components and (optionally) expose them to admin:

  • Tasks: TasksManager!=nil = enabled and started; TasksExposeToAdmin = wire into admin when admin enabled.

  • Tuning: Tuning != nil = enabled; TuningExposeToAdmin = wire into admin when admin enabled.

  • Log: LogLevelVar!=nil = enabled; LogExposeToAdmin = wire /log/level (read) and optionally /log/level/set (write).

Note: if admin config (especially write groups) requires a component (e.g. task writes require a task manager), NewDefaultService may create the component to satisfy assembly, but write capabilities still require explicit enabling via AdminSpec.WriteGuard and the enable flags (EnableLogLevelSet, TuningWritesEnabled, TaskWritesEnabled) plus allowlists.

Spec reference (parameters at a glance)

ServiceSpec (NewDefaultService): SignalsDisable, Signals, ShutdownTimeout, Primary, Extra, Admin (*AdminSpec), AdminMountPrefix, AdminStandaloneServer, TasksManager, TasksExposeToAdmin, Tuning, TuningExposeToAdmin, LogLevelVar, LogExposeToAdmin, OnStart, OnShutdown, OnServeError.

AdminSpec (Admin field / NewDefaultAdmin): ReadGuard (required), TrustedProxies, TrustedHeaders, ReadyChecks, LogLevelVar, Tuning, TaskManager, TuningReadAllowPrefixes/Keys/Func, TaskReadAllowPrefixes/Names/Func, ProvidedItems, ProvidedMaxBytes, WriteGuard, EnableLogLevelSet, TuningWritesEnabled, TuningWriteAllowPrefixes/Keys/Func, TaskWritesEnabled, TaskWriteAllowPrefixes/Names/Func.

HTTPServerSpec (Primary, Extra, AdminStandaloneServer): Name, Critical, Server (or Addr+Handler).

Building blocks (when you need finer-grained control)

  • github.com/evan-idocoding/zkit/admin: admin subtree assembler (Option-based, explicit EnableXxx + explicit Guard)
  • github.com/evan-idocoding/zkit/ops: operational handlers (health/runtime/buildinfo/tasks/tuning/loglevel/provided snapshots)
  • github.com/evan-idocoding/zkit/httpx: net/http middleware chain helpers
  • github.com/evan-idocoding/zkit/httpx/client: HTTP client builder (independent transport + RoundTripper middlewares)
  • github.com/evan-idocoding/zkit/rt/task: background task primitives
  • github.com/evan-idocoding/zkit/rt/tuning: runtime-tunable parameters
  • github.com/evan-idocoding/zkit/rt/safego: panic/error observable goroutine runner

Package zkit re-exports Guard types and constructors from the admin subpackage so callers can configure admin without importing admin.

Index

Constants

View Source
const DefaultTokenHeader = admin.DefaultTokenHeader

DefaultTokenHeader is the default header name for token-based guards.

Variables

View Source
var (
	// ErrAlreadyStarted indicates Start/Run was called more than once.
	ErrAlreadyStarted = errors.New("zkit: service already started")
	// ErrNotStarted indicates Wait was called before Start.
	ErrNotStarted = errors.New("zkit: service not started")
)

Functions

func NewDefaultAdmin

func NewDefaultAdmin(spec AdminSpec) http.Handler

NewDefaultAdmin assembles a default-safe admin subtree handler from a flat spec.

Paths are fixed (v1 default kit). For custom paths or composition, use admin.New(...) + admin.EnableXxx(...).

Types

type AdminSpec

type AdminSpec struct {
	// Required. Protects all read endpoints.
	ReadGuard Guard

	// TrustedProxies: CIDRs or single IPs of trusted proxies. Empty = do not trust proxy headers, use RemoteAddr.
	TrustedProxies []string
	// TrustedHeaders: header names used to extract client IP (e.g. "X-Forwarded-For", "X-Real-IP"). Empty = default order.
	TrustedHeaders []string

	// Ready checks for /readyz. Empty = no checks (endpoint still responds OK).
	ReadyChecks []ReadyCheck

	// Optional read sources.
	LogLevelVar *slog.LevelVar
	Tuning      *tuning.Tuning
	TaskManager *task.Manager

	// Tuning read access: zero = no filter. AllowFunc mutually exclusive with AllowPrefixes/AllowKeys.
	TuningReadAllowPrefixes []string
	TuningReadAllowKeys     []string
	TuningReadAllowFunc     func(key string) bool

	// Task read access: zero = no filter. AllowFunc mutually exclusive with AllowPrefixes/AllowNames.
	TaskReadAllowPrefixes []string
	TaskReadAllowNames    []string
	TaskReadAllowFunc     func(name string) bool

	// Provided (sensitive). Non-nil = enable /provided with this map; nil = disabled.
	ProvidedItems    map[string]any
	ProvidedMaxBytes int // <= 0 uses ops default

	// Writes: nil = no write endpoints. Non-nil = guard for all write endpoints; individual groups gated by their Enable flag and allowlists.
	WriteGuard Guard

	// Enable /log/level/set. Requires WriteGuard != nil and LogLevelVar != nil.
	EnableLogLevelSet bool

	// Tuning writes: TuningWritesEnabled true = enable group (requires Tuning != nil). Allowlist applies; empty = deny-all. AllowFunc mutually exclusive with slices.
	TuningWritesEnabled      bool
	TuningWriteAllowPrefixes []string
	TuningWriteAllowKeys     []string
	TuningWriteAllowFunc     func(key string) bool

	// Task writes: TaskWritesEnabled true = enable group (requires TaskManager != nil). Allowlist applies; empty = deny-all. AllowFunc mutually exclusive with slices.
	TaskWritesEnabled      bool
	TaskWriteAllowPrefixes []string
	TaskWriteAllowNames    []string
	TaskWriteAllowFunc     func(name string) bool
}

AdminSpec configures NewDefaultAdmin. All fields are optional except ReadGuard.

Assembly errors are fail-fast and will panic.

Required

  • ReadGuard: must be non-nil; protects all read endpoints.

Optional reads (zero = disabled or no filter)

  • TrustedProxies: CIDRs or IPs of trusted proxies; empty = do not trust proxy headers, use RemoteAddr.
  • TrustedHeaders: header names used to extract client IP (e.g. X-Forwarded-For, X-Real-IP). Empty = default order.
  • ReadyChecks: /readyz checks; empty slice = endpoint still enabled, no checks.
  • LogLevelVar: enables /log/level (read) when non-nil.
  • Tuning + TuningReadAllow*: tuning read endpoints; Tuning must be non-nil. Read allowlist: zero = no filter.
  • TaskManager + TaskReadAllow*: /tasks/snapshot; TaskManager must be non-nil. Read allowlist: zero = no filter.
  • ProvidedItems: when non-nil, enables /provided with this map; nil = disabled. ProvidedMaxBytes optional (<=0 = default).

Note on overlap with ServiceSpec:

  • AdminSpec.{LogLevelVar,Tuning,TaskManager} are admin handler data sources. When NewDefaultService is used and both ServiceSpec.<X> and ServiceSpec.Admin.<X> are provided, Admin.<X> takes precedence for the admin subtree.
  • Service lifecycle ownership is controlled by ServiceSpec (e.g. Service starts/shuts down TasksManager only when ServiceSpec.TasksManager is non-nil or when Service creates/manages one for admin write needs).

Writes (WriteGuard nil = all write endpoints disabled)

  • WriteGuard: when non-nil, write endpoints may be enabled; this guard protects them. Required for any write.
  • EnableLogLevelSet: requires WriteGuard != nil and LogLevelVar != nil (coexistence).
  • Tuning write group (/tuning/set, reset-default, reset-last): set TuningWritesEnabled true to enable; requires Tuning != nil. Allowlist (empty = deny-all) applies.
  • Task write group (/tasks/trigger, trigger-and-wait): set TaskWritesEnabled true to enable; requires TaskManager != nil. Allowlist (empty = deny-all) applies.

Access allowlist rules (Tuning* and Task* Allow* fields)

  • For reads: zero (nil slices + nil func) = no filtering. Non-zero = allowlist applied.
  • For writes: set TuningWritesEnabled/TaskWritesEnabled to enable the group; allowlist then applies (empty = deny-all). AllowFunc is mutually exclusive with the slice fields (admin will panic if both set).

type Guard added in v0.1.2

type Guard = admin.Guard

Guard enforces request admission for admin endpoints. Use AllowAll, DenyAll, Tokens, IPAllowList, etc. to create one.

func AllowAll added in v0.1.2

func AllowAll() Guard

AllowAll returns a guard that allows all requests.

func Check added in v0.1.2

func Check(fn func(r *http.Request) bool) Guard

Check returns a guard backed by a custom fast predicate (must not block, no I/O).

func DenyAll added in v0.1.2

func DenyAll() Guard

DenyAll returns a guard that denies all requests with HTTP 403.

func HotTokens added in v0.1.2

func HotTokens(set TokenSetLike, opts ...TokenOption) Guard

HotTokens returns a guard that validates requests using a hot-update token set.

func HotTokensAndIPAllowList added in v0.1.2

func HotTokensAndIPAllowList(set TokenSetLike, cidrsOrIPs []string, opts ...TokenOption) Guard

HotTokensAndIPAllowList is like TokensAndIPAllowList but with a hot-update token set.

func HotTokensOrIPAllowList added in v0.1.2

func HotTokensOrIPAllowList(set TokenSetLike, cidrsOrIPs []string, opts ...TokenOption) Guard

HotTokensOrIPAllowList is like TokensOrIPAllowList but with a hot-update token set.

func IPAllowList added in v0.1.2

func IPAllowList(cidrsOrIPs ...string) Guard

IPAllowList returns a guard backed by a static IP allowlist (CIDRs or single IPs).

func Tokens added in v0.1.2

func Tokens(tokens []string, opts ...TokenOption) Guard

Tokens returns a guard that validates requests using a static token list.

func TokensAndIPAllowList added in v0.1.2

func TokensAndIPAllowList(tokens []string, cidrsOrIPs []string, opts ...TokenOption) Guard

TokensAndIPAllowList returns a guard that allows when token is allowed AND IP is allowlisted.

func TokensOrIPAllowList added in v0.1.2

func TokensOrIPAllowList(tokens []string, cidrsOrIPs []string, opts ...TokenOption) Guard

TokensOrIPAllowList returns a guard that allows when token is allowed OR IP is allowlisted.

type HTTPServerSpec

type HTTPServerSpec struct {
	// Name is used for error reporting (OnServeError) and diagnostics.
	// Empty name will be replaced by a default name (primary / extra#N / admin).
	Name string

	// Critical controls fail-fast behavior.
	//
	// When Critical is true (default), an unexpected Serve error triggers a global shutdown.
	// When Critical is false, unexpected errors do NOT trigger shutdown but are reported via OnServeError.
	//
	// If nil, it defaults to true.
	Critical *bool

	// Provide either:
	//   - Server: a fully configured *http.Server (zkit will not override timeouts, BaseContext, ErrorLog, etc)
	//   - or Addr + Handler: zkit will build a server with conservative defaults (ReadHeaderTimeout, IdleTimeout).
	//
	// Assembly rules:
	//   - If Server != nil, Addr/Handler fields are ignored.
	//   - If Server == nil, Addr must be non-empty and Handler must be non-nil (when used for Primary or Extra;
	//     when used as AdminStandaloneServer, Handler may be nil and zkit injects the admin handler).
	Server  *http.Server
	Addr    string
	Handler http.Handler
}

HTTPServerSpec describes a managed http.Server.

type ReadyCheck added in v0.1.2

type ReadyCheck struct {
	Name    string
	Func    func(context.Context) error
	Timeout time.Duration
}

ReadyCheck is a single readiness check for /readyz. Name is required (used in the report); Timeout is optional (zero = no extra timeout).

type Service

type Service struct {
	// Assembly outputs (optional depending on spec).
	PrimaryServer *http.Server
	ExtraServers  []*http.Server
	AdminServer   *http.Server
	AdminHandler  http.Handler

	TaskManager *task.Manager
	Tuning      *tuning.Tuning
	LogLevelVar *slog.LevelVar
	// contains filtered or unexported fields
}

Service is a runnable, shutdownable assembled service.

func NewDefaultService

func NewDefaultService(spec ServiceSpec) *Service

NewDefaultService assembles a default-safe runnable Service.

Assembly errors are fail-fast and will panic. Runtime errors are returned from Start/Wait/Run/Shutdown.

func (*Service) Run

func (s *Service) Run(ctx context.Context) error

Run is equivalent to Start → wait for exit condition → Shutdown → return.

It is NOT idempotent. If called after Start, it returns ErrAlreadyStarted.

func (*Service) Shutdown

func (s *Service) Shutdown(ctx context.Context) error

Shutdown triggers shutdown. It is idempotent.

If Start was never called, Shutdown returns nil. If Shutdown is already in progress, calling it again waits again using the new ctx.

func (*Service) Start

func (s *Service) Start(ctx context.Context) error

Start starts managed components. It is NOT idempotent.

func (*Service) Wait

func (s *Service) Wait() error

Wait waits until the service fully stops.

It is idempotent. If Start was never called, it returns ErrNotStarted.

type ServiceSpec

type ServiceSpec struct {
	// Signals: SignalsDisable true = Run() does not listen for OS signals. Signals nil/empty = default set (SIGINT+SIGTERM on Unix).
	SignalsDisable bool
	Signals        []os.Signal

	// ShutdownTimeout: <= 0 means default (30s).
	ShutdownTimeout time.Duration

	// Primary: optional. Required if admin is mounted (Admin != nil and AdminStandaloneServer == nil).
	Primary *HTTPServerSpec

	// Extra: additional servers; admin is not mounted onto them.
	Extra []*HTTPServerSpec

	// Admin: nil = admin disabled. Non-nil = admin enabled; use AdminMountPrefix or AdminStandaloneServer (mutually exclusive).
	Admin *AdminSpec

	// AdminMountPrefix: used when Admin != nil and AdminStandaloneServer == nil. Empty => "/-/".
	AdminMountPrefix string

	// AdminStandaloneServer: when set, admin is served on this server only (worker-only). Mutually exclusive with mount.
	AdminStandaloneServer *HTTPServerSpec

	// Tasks: Manager != nil = enabled and started. ExposeToAdmin = wire into admin when admin enabled.
	TasksManager       *task.Manager
	TasksExposeToAdmin bool

	// Tuning: non-nil = enabled. ExposeToAdmin = wire into admin when admin enabled.
	Tuning              *tuning.Tuning
	TuningExposeToAdmin bool

	// Log: LogLevelVar != nil = enabled. ExposeToAdmin = wire into admin when admin enabled.
	LogLevelVar      *slog.LevelVar
	LogExposeToAdmin bool

	// Lifecycle hooks and serve error observer.
	OnStart      []func(context.Context) error
	OnShutdown   []func(context.Context) error
	OnServeError func(name string, err error, critical bool)
}

ServiceSpec configures NewDefaultService. All fields are optional.

Admin: nil = admin disabled. When non-nil, admin is either mounted on Primary (AdminMountPrefix, default "/-/") or served on its own server (AdminStandaloneServer). Mount and Standalone are mutually exclusive.

Tasks/Tuning/Log: non-nil Manager/Tuning/LevelVar = enabled and started/wired; nil + ExposeToAdmin or admin write need causes Service to create a default instance.

Override & ownership rules (when both ServiceSpec.<X> and ServiceSpec.Admin.<X> are set):

  • Admin wiring uses Admin.<X> (Admin overrides Service-level source for the admin subtree).
  • Service lifecycle ownership is controlled by ServiceSpec fields:
  • TasksManager: when non-nil, Service starts/shuts down the task manager; when nil, Service does not manage it (even if Admin.TaskManager is set to expose tasks endpoints).
  • Tuning/LogLevelVar: not started/stopped by Service; they are data sources only.

type TokenOption added in v0.1.2

type TokenOption = admin.TokenOption

TokenOption configures token-based guards (e.g. header name).

func WithTokenHeader added in v0.1.2

func WithTokenHeader(name string) TokenOption

WithTokenHeader overrides the token header name for token-based guards.

type TokenSetLike added in v0.1.2

type TokenSetLike = admin.TokenSetLike

TokenSetLike is a token set for hot-update guards (e.g. HotTokens).

Directories

Path Synopsis
Package admin assembles a secure, explicit, side-loadable admin subtree (http.Handler).
Package admin assembles a secure, explicit, side-loadable admin subtree (http.Handler).
Package httpx provides small, stable and standard-library flavored net/http helpers.
Package httpx provides small, stable and standard-library flavored net/http helpers.
client
Package client provides a small, stable and standard-library flavored HTTP client builder.
Package client provides a small, stable and standard-library flavored HTTP client builder.
Package ops provides small, standard-library flavored net/http handlers for operational endpoints.
Package ops provides small, standard-library flavored net/http handlers for operational endpoints.
rt
safego
Package safego provides helpers for running functions with panic/error reporting.
Package safego provides helpers for running functions with panic/error reporting.
task
Package task provides small, standard-library flavored background task primitives.
Package task provides small, standard-library flavored background task primitives.
tuning
Package tuning provides runtime-tunable parameters for online hot changes.
Package tuning provides runtime-tunable parameters for online hot changes.
tuning/tuningslog
Package tuningslog provides small helpers to bind tuning variables to log/slog.
Package tuningslog provides small helpers to bind tuning variables to log/slog.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL