Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Using hush from an agent needed a clone and docs/MCP.md. It now needs one
command, and the instructions are served by the deployment itself.
`go install github.com/orchard9/hush/cmd/hush-mcp@latest` is the whole
install: cmd/hush-mcp imports only the standard library, so module graph
pruning never reaches the private go-chassis dependency cmd/hushd needs.
Verified against an empty module cache and the public proxy, then create ->
reveal end to end against production with the resulting binary.
The page carries the per-client configuration for Claude Code, Codex CLI,
Gemini CLI, VS Code, Claude Desktop, Cursor and omp. Each command was run
against the installed client rather than copied from documentation, which is
how the differences on it are there at all: VS Code's wrapper key is
`servers`, not `mcpServers`; gemini defaults to project scope, not user;
Claude Code rejects `--env` immediately before the server name.
The shared browser crypto moves from base.html into templates/crypto.html,
which the two pages that encrypt parse and this one does not. An empty
`{{define}}` cannot replace a non-empty one — text/template reads an empty
body as no definition — so the shell holds the call and the partial holds the
code, and the docs page ships no script at all.
Three things this exposed, fixed here:
- The public Ingress enumerates paths, so a handler without one 404s at the
edge while working in `make dev`. The Ingress is now its own manifest:
hush.yaml pins a `:bootstrap` image that does not exist, so re-applying it
to publish a path would roll the workload onto an unpullable image.
`make deploy-ingress` applies the route alone.
- release.sh guarded HEAD against `@{upstream}`, which is the GitHub mirror
here, while Kaniko clones Gitea. A commit pushed to one and not the other
would have built the previous commit silently. It now fetches and compares
the branch that actually gets built.
- smoke.sh checks that /mcp serves the install command, so a stale rollout or
an unexecutable template fails the release instead of being found later.
Confirmed it fails: against production before this deploy it reported 404.
66 lines
2.3 KiB
Go
66 lines
2.3 KiB
Go
package main
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/orchard9/go-chassis/config"
|
|
)
|
|
|
|
// Config is every knob hushd has. Loaded once at boot; a missing or malformed
|
|
// required value stops the process rather than being defaulted, so a
|
|
// misconfigured pod crashloops visibly instead of serving something subtly
|
|
// wrong.
|
|
type Config struct {
|
|
Env string
|
|
Port int
|
|
|
|
// RedisURL is REQUIRED. There is an in-memory store in the codebase for
|
|
// tests, and requiring this is what makes it impossible to select by
|
|
// accident in production: a pod with no REDIS_URL does not boot, rather
|
|
// than booting with a store that loses every secret on restart.
|
|
RedisURL string
|
|
|
|
// RateLimit bounds anonymous creates per client IP. Reveals are not
|
|
// limited by this: a recipient gets exactly one successful reveal by
|
|
// construction, so there is nothing to throttle, and throttling would let
|
|
// one noisy NAT block a colleague's delivery.
|
|
RateLimitCreates int
|
|
RateLimitWindow time.Duration
|
|
|
|
// TrustedProxyHops is how many Traefik hops sit in front of hushd, used to
|
|
// pick the real client IP out of X-Forwarded-For. Wrong-high lets a caller
|
|
// spoof their IP and evade the rate limit; wrong-low rate-limits the
|
|
// ingress itself and throttles everyone together.
|
|
TrustedProxyHops int
|
|
|
|
// AllowOrigins is the CORS allowlist. Empty is correct for the deployed
|
|
// service: every page is same-origin, so no cross-origin caller is
|
|
// legitimate.
|
|
AllowOrigins []string
|
|
|
|
// RequireAuthToCreate closes anonymous create if the service is abused.
|
|
// Reveal stays anonymous regardless — the recipient is external and holds
|
|
// no credential.
|
|
RequireAuthToCreate bool
|
|
CreateToken string
|
|
}
|
|
|
|
func loadConfig() (Config, error) {
|
|
l := config.New()
|
|
cfg := Config{
|
|
Env: l.OneOf("APP_ENV", "dev", "dev", "staging", "prod"),
|
|
Port: l.Port("HUSH_PORT", 18500),
|
|
RedisURL: l.Required("REDIS_URL"),
|
|
RateLimitCreates: l.Int("HUSH_RATE_LIMIT_CREATES", 30),
|
|
RateLimitWindow: l.Duration("HUSH_RATE_LIMIT_WINDOW", 10*time.Minute),
|
|
TrustedProxyHops: l.Int("HUSH_TRUSTED_PROXY_HOPS", 1),
|
|
AllowOrigins: l.Strings("HUSH_ALLOW_ORIGINS", nil),
|
|
RequireAuthToCreate: l.Bool("HUSH_REQUIRE_AUTH", false),
|
|
CreateToken: l.String("HUSH_CREATE_TOKEN", ""),
|
|
}
|
|
if err := l.Err(); err != nil {
|
|
return Config{}, err
|
|
}
|
|
return cfg, nil
|
|
}
|