Documentation
¶
Overview ¶
Package server is the loopback HTTP mux for the web UI and the API (specs/000-product/contracts/api.md). Reads are assembled from the SQLite mirror. Write-through endpoints call Jira and re-read the issue into the mirror. Credentials are needed for those writes and for fetching attachment bytes that are not already on disk.
The server has no authentication: `gadak serve` refuses a non-loopback bind instead. Personal-state endpoints therefore never answer 401/403.
Index ¶
- Variables
- func GuardBrowser(next http.Handler, hostExempts ...func(*http.Request) bool) http.Handler
- func MergedPRLinks(devLinks []store.DevLink, attachments []store.DetailAttachment) json.RawMessage
- func PairedMirrorHostExempt(dir func() string) func(*http.Request) bool
- func PairedOriginHostExempt(dir func() string) func(*http.Request) bool
- func PairedTerminalHostExempt(dir func() string) func(*http.Request) bool
- func WebConfig(cfg *config.Config) ([]byte, error)
- func WebConfigBase(cfg *config.Config, prefix string) ([]byte, error)
- type Handler
- func (h *Handler) BindOriginHandler(next http.Handler)
- func (h *Handler) CheckNow(ctx context.Context, cacheDir string) UpdateStatus
- func (h *Handler) Close() error
- func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (h *Handler) SetSyncStarter(f func())
- func (h *Handler) Shutdown(ctx context.Context) error
- func (h *Handler) SnapshotSync() progressResponse
- func (h *Handler) StartUpdateCheck(ctx context.Context, cacheDir string)
- func (h *Handler) SyncActivityHooks() (phase func(string), progress func(fetched, changed int))
- func (h *Handler) Terminals() *term.Manager
- type LinkedPR
- type UpdateStatus
Constants ¶
This section is empty.
Variables ¶
var Version = "0.0.0-dev"
Version is the gadak release string exposed on GET settings/ under runtime. cmd/gadak should assign this from its ldflags version var at startup:
server.Version = version
Until that wiring lands, the default below is what the UI shows.
Functions ¶
func GuardBrowser ¶ added in v0.13.0
GuardBrowser wraps next so Host/Origin checks run before any route. Mount this on the top-level serve mux so routes registered outside Handler (/config.json, /healthz, /api/v1/workspaces, /w/) cannot skip the guard.
hostExempts widen only the Host check, for requests a later gate authenticates by credential instead of by name — today that is the paired origin passthrough (PairedOriginHostExempt) and the paired mirror REST (PairedMirrorHostExempt), whose Bearer requirements make the DNS-rebinding vector this check exists for unmountable (a browser cannot attach Authorization cross-origin without a preflight this server never answers). The Origin check is not exempted.
func MergedPRLinks ¶ added in v0.17.0
func MergedPRLinks(devLinks []store.DevLink, attachments []store.DetailAttachment) json.RawMessage
MergedPRLinks is ListLinkedPRs encoded as the linked_prs JSON array. Empty input returns nil so omitempty callers can drop the field.
func PairedMirrorHostExempt ¶ added in v0.17.3
PairedMirrorHostExempt lets GuardBrowser pass a DNS-named Host for mirror-REST requests while active pairing tokens exist — the same probe shape as PairedOriginHostExempt: an empty-bearer Authorize answers "does the gate have anything to check" without accepting anything. VerdictOff (or an unreadable store, which fails closed) keeps today's forbidden_host, so an unpaired serve never widens for the phone. dir is resolved per request: pairing.json can appear while a serve is running.
func PairedOriginHostExempt ¶ added in v0.17.0
PairedOriginHostExempt lets GuardBrowser pass a DNS-named Host — which the rebinding check otherwise rejects — for origin-passthrough requests while active pairing tokens exist. Measured on a real tailnet (GDK-443): tailscale serve forwards the original `<machine>.<tailnet>.ts.net` Host upstream, so without this every paired request died as forbidden_host before the Bearer gate could speak. Authorize with an empty bearer answers "do tokens exist" without accepting anything: VerdictOff (or an error, which fails closed) keeps today's rejection, VerdictReject means pairingGate will demand the Bearer right after this. dir is resolved per request — pairing.json can appear while a serve is running.
func PairedTerminalHostExempt ¶ added in v0.18.0
PairedTerminalHostExempt lets GuardBrowser pass a DNS-named Host for terminal requests, the third exemption beside the origin and mirror ones — but a stricter probe than either.
The other two ask only "does the gate have anything to check" with an empty bearer, and let the gate answer everything else. This one also refuses a bearer that authenticates for another surface: a serve or origin token on the terminal route is not exempted, so it dies at the guard as forbidden_host instead of learning that a shell endpoint exists. That is the GDK-863 ruling taken literally — a serve token may never open a shell, and there is no reason to tell it where the shell is. A request with no bearer, or an unknown one, is still exempted, so the gate can answer it 401 with a reason (an honest "pair this device" for someone who has not yet).
Types ¶
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler is the HTTP API plus optional update-check control. It implements http.Handler; mount it at "/api/".
func New ¶
New returns the API handler. Mount it at "/api/" — the patterns below carry their full paths, so nothing strips a prefix.
func NewWithCache ¶
NewWithCache is New plus an attachment byte cache. `gadak serve` passes one rooted under GADAK_HOME; tests pass nil when they do not exercise attachments.
func NewWorkspace ¶
func NewWorkspace(db *store.DB, cfg *config.Config, cache *attachcache.Cache, profile string) *Handler
NewWorkspace is NewWithCache bound to a named profile (for /w/<name>/ mounts). profile is used for runtime paths and display; it does not re-read global config.
func (*Handler) BindOriginHandler ¶ added in v0.16.0
BindOriginHandler pins the passthrough target, replacing whatever was there. nil unbinds, which is how a caller whose session went away puts the slot back to lazy — leaving a handler pinned to a session the caller has since closed is how one persist file ends up with two stores.
Serialised with standaloneOrigin so a bind cannot race lazy construction.
func (*Handler) CheckNow ¶ added in v0.16.0
func (h *Handler) CheckNow(ctx context.Context, cacheDir string) UpdateStatus
CheckNow bypasses the 24h disk cache, hits GitHub once, and records the result for GET update/ and for bootstrap/delta. Background checks stay silent; this path is the one that reports current / error / dev.
func (*Handler) Close ¶ added in v0.16.0
Close is Shutdown with a 3s bound — the same window cmd/gadak/serve.go uses for http.Server.Shutdown.
func (*Handler) ServeHTTP ¶
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements http.Handler.
func (*Handler) SetSyncStarter ¶
func (h *Handler) SetSyncStarter(f func())
SetSyncStarter registers a function that starts the background sync loop after the first credential is saved via onboarding connect. Fired at most once. cmdServe registers this when serve starts without a credential.
func (*Handler) Shutdown ¶ added in v0.16.0
Shutdown cancels background startSyncJob work and waits for those goroutines to return, or until ctx is done. A timed-out wait returns ctx.Err(); the job may still be running and still hold a database connection. Idempotent.
Returning from the job goroutine is not enough: database/sql rolls a cancelled Tx back from a helper goroutine (Tx.awaitDone), and that helper can still hold the pool connection after runSyncJob has returned. Waiting for InUse==0 is waiting for that writer, which is the WAL leak GDK-270 actually is.
func (*Handler) SnapshotSync ¶ added in v0.16.0
func (h *Handler) SnapshotSync() progressResponse
SnapshotSync is the debug document for "what background work is running right now": the same one-shot job + activity picture that GET /api/v1/issues/sync/progress/ already returns. No new endpoint — that GET already carries it; this is the in-process form.
func (*Handler) StartUpdateCheck ¶
StartUpdateCheck runs a GitHub release lookup immediately and every 24h. Results feed latest_version / release_url on bootstrap and delta when the running build is older. Records cacheDir even when disabled so a later user-initiated CheckNow still knows where the file lives. The background loop is a no-op when cfg.UpdateCheckEnabled() is false. Safe with no credential (Jira-independent). Background errors are silent.
func (*Handler) SyncActivityHooks ¶
SyncActivityHooks returns the Phase and Progress callbacks a background watch loop should report through, so the UI can say what the mirror is fetching and how far along. Safe for concurrent use; nil Handler returns nil funcs (callers pass them straight into sync.Options).
func (*Handler) Terminals ¶ added in v0.18.0
Terminals is the session core, for an in-process host that carries the terminal over its own transport instead of the WebSocket below.
Gadak.app is that host (GDK-892): it mounts this Handler behind the wails asset server, where there is no TCP listener for a ws:// URL to reach, and moves the same bytes over a wails GoStream. Everything above the socket — create, list, delete, the gate — is the REST surface it already uses.
Lazy exactly as the HTTP path is: a process that never opens a terminal still constructs no manager, because this is the same call handleTerminal* makes.
type LinkedPR ¶ added in v0.17.0
type LinkedPR struct {
Number int `json:"number"`
Title string `json:"title"`
URL string `json:"url"`
State string `json:"state"`
Repo *string `json:"repo"`
Author *string `json:"author"`
// LinkedBy / LinkedByID name who attached the link (dev_links actor,
// GDK-589) — a different axis from Author: a bot linking a human's PR
// keeps both. Absent for URL attachments, which carry no actor.
LinkedBy *string `json:"linked_by,omitempty"`
LinkedByID *string `json:"linked_by_id,omitempty"`
}
prLinksFromAttachments derives the linked_prs payload from mirrored URL attachments when no plugin enrichment supplies one. The enrichment (kind='prs') stays the winner: it can carry state and author, which a bare URL cannot. LinkedPR is one GitHub pull request derived from the mirror (dev_links and/or a PR-shaped URL attachment).
func ListLinkedPRs ¶ added in v0.17.0
func ListLinkedPRs(devLinks []store.DevLink, attachments []store.DetailAttachment) []LinkedPR
ListLinkedPRs merges the two mirrored PR sources: dev_links (the origin's development panel, GDK-497 — carries a state) and PR-shaped URL attachments (GDK-495 — carry none). Deduped by URL, dev_links winning, because a stated status beats an inferred blank.
type UpdateStatus ¶ added in v0.16.0
type UpdateStatus struct {
Current string `json:"current"`
Latest string `json:"latest,omitempty"`
URL string `json:"release_url,omitempty"`
Notes string `json:"release_notes,omitempty"`
NotesLen int `json:"release_notes_len"`
CheckedAt string `json:"checked_at,omitempty"`
Newer bool `json:"newer,omitempty"`
Status string `json:"status,omitempty"` // newer|current|error|dev — this CheckNow
Error string `json:"error,omitempty"`
LastUserCheckAt string `json:"last_user_check_at,omitempty"`
LastUserStatus string `json:"last_user_status,omitempty"`
}
UpdateStatus is GET/POST update/ and CheckNow: what the server currently knows about the latest published release, plus the outcome of a user-initiated check when one has run.