Diagnostics commands¶
Global flags are in the CLI hub.
gdsgate doctor¶
Runs a read-only health checklist against the local configuration and state, then exits. Every warning or failure carries a one-line fix.
| Group | Checks |
|---|---|
| configuration | File paths exist and are readable, 0600 on secrets, invariants such as ha needing PostgreSQL, connector-backend validity. |
| features | What is enabled, what is off, and why. |
| state | Store persistence, schema and CA consistency (Authority); transport-identity freshness (node); cached logins (client). |
The analysed role comes from --role, installed role metadata, or the
configuration's declared sections. An unrelated database in the home directory
does not make a client or Connector an Authority. Empty Connector configurations,
Proxy configurations with separate ports, and agent hosts retain their own role.
Periodic metrics and node health checks use the running component's role.
On a host with several components, select the role or an explicit file:
--role accepts authority, proxy, connector, agent-host, client and
all. Without an explicit path it selects that role's installed configuration;
client selects the account's context. JSON includes role and config_source.
Previous-result snapshots are separate per role and configuration file.
For a running server, runtime.reload reports the loaded path, generation and
last refusal. JSON exposes the same information in runtime_reload; an
unreachable channel yields null. See configuration reload.
Nothing is changed. A store schema behind this
binary is a failure naming
gdsgate authority store migrate;
doctor does not apply it.
A parameter in secret-reference notation
is reported by its source: file, variable or command. The 0600 check applies
to the file form only; for a variable or command the report says there is no
file to check. doctor does not resolve references; an unresolvable one is
reported by the step that resolves it.
Platform-specific checks:
| Check | Platform |
|---|---|
state.client_net (the client helper service can issue a name-resolution rule and a network adapter) |
Windows |
sandbox.console (a sandboxed program at a console can read its screen; see Delegation → The sandbox on Windows) |
Windows |
sandbox.userns_confinement, sandbox.unix_socket |
Linux |
state.connector.catalogue |
Linux |
On Windows the 0600 check reads the file's access-control list; there is no
separate mode row.
For a PostgreSQL backend in
session_role mode, doctor
connects with the backend's service account and runs a SELECT against
pg_catalog for each session role: whether it exists, whether the service
account can assume it, and what it can write. Grants to PUBLIC and tables the
role owns are reported apart from ordinary grants. A profile with
read_only = true that can write is a failure.
| Condition | Result |
|---|---|
| CockroachDB backend | Reported as unchecked for this probe. |
| Several databases in the cluster | Each profile is checked in every database the service account reaches (<resource>.<db> opens any of them), up to twelve per run. The report says when it stopped short. |
| Database does not answer within five seconds | Reported as unchecked. |
| Backend unreachable | Warning. |
--json emits a machine-readable report. Exit code 2 if any check fails, 0
when only warnings or info remain; usable as a systemd ExecStartPre= or a
container healthcheck.
doctor runs on configurations a service refuses to start on. A
profile that stops authority, proxy,
connector and all is a config.profile finding here. A --config naming a
missing file, or a file that is not valid TOML, fails the command.
The report:
gdsgate doctor · authority · stand01.example.com · 0.10.0
Configuration
✓ deployment profile pilot
✓ registration listener TLS · 127.0.0.1:50050
✓ join relay stand01.example.com:50064
✓ public join off
Features
✓ audit chain anchoring ✓ JIT approvals ✓ node revocation ✓ delegation grants
✓ inventory receipts ✓ Cedar policy ✓ health check – external OIDC – WebAuthn step-up
State
✓ store postgres://stand@localhost:5432/stand
✓ store schema current
! policy file differs from active version 10
✓ CA 5 · consistent
1 warning · 0 failures
To do
! policy file differs from active version 10
keep the file: gdsgate authority policy push /etc/gdsgate/policy.cedar
keep the store: gdsgate authority policy pull --output /etc/gdsgate/policy.cedar
Details: gdsgate doctor --explain · machine-readable: gdsgate doctor --json
| Mark | Word | Meaning |
|---|---|---|
✓ |
ok |
Passed. |
! |
warn |
Degraded; listed under To do. |
✗ |
fail |
Failed; listed under To do; exit code 2. |
– |
off / info |
Feature off, or a fact with nothing to do. |
? |
? |
Not checked from here. |
The words replace the marks when stdout is not a terminal or the locale is not
UTF-8. Colour only on a terminal; NO_COLOR turns it off. --quiet prints the
summary and To do only, and nothing when every check passed.
Four or more identical findings (cached logins, CAs) print as one line with a count.
--explain prints under each line its check id, what was checked, the figures
behind the verdict, and every member of a folded line. --json carries every
check, with the same text in title, detail, remedy and explain.
Each run saves a snapshot to ~/.gdsgate/last-doctor.json and compares it with
the previous one. Under --explain a changed check is annotated
↓ worse than last run, ↑ better than last run (failure now a warning) or
↑ recovered (nothing left to do), and the summary adds N worse than last
run. A check that covers several items (cached logins, roles) is compared item
by item.
Shared service state parent¶
On Linux, state.service_root checks /var/lib/gdsgate on every doctor run,
including client-only configurations and runs as root. It reads metadata
without following a link. Expected: a directory with mode 0755; individual
role directories and keys retain private permissions.
| State | Verdict | Remedy |
|---|---|---|
Directory, mode 0755 |
ok | None |
Directory, mode 0700 or another mode |
fail, with actual and expected modes | sudo chmod 0755 /var/lib/gdsgate, or rerun service install as root |
| Old shared-layout link | fail | Rerun service install as root to migrate the recognized layout |
| Another entry at that path | fail | Preserve and move the conflicting entry, then install again |
| Absent parent | info | A privileged service install creates it |
Root can bypass search permissions, so a successful read by the operator
does not establish service access. Doctor checks the mode directly and
changes no permissions. Change only the parent; a recursive chmod would
expose private role state. Verify with another doctor run, then start the
service using service commands.
What this node declares against what the cluster says¶
On a node with [[connector.backends]], one state.connector.catalogue check
per entry compares it with the cluster's answer at this node's last
registration. doctor reads that answer from the running connector over the
control socket in [enroll].state_dir; it connects nowhere else and writes
nothing. Linux only; a Windows connector's report has no such row.
| Value | Status | Meaning |
|---|---|---|
ssh_profile=... onward_auth=... |
ok | The cluster serves this resource to the node; the value is what the catalogue declares. |
kept back: <reason> |
fail | Held back with the cluster's reason: not_in_catalog, mode_not_confirmed, owned_by_another_node, gate_not_offered. The first two carry the authority resources set command that resolves them. |
mode disputed |
fail | The catalogue and this file disagree on onward_auth or ssh_profile; the node serves it under neither. --explain prints both values. |
not registered yet |
warn | The entry was added after the last declaration; run reload --role connector, then check admission. An older peer requires restart. |
not checked: ... |
warn | No connector running, not registered yet, or the socket unreachable. |
A startup gate_not_offered can mean the current process is acquiring its
capability receipt. It does not imply a missing catalogue row. For persistent
withholding, inspect the receipt/renewal checks and the Authority response:
Connector startup diagnostics.
doctor does not write to the catalogue. The authority resources set command
a failure names needs an administrative identity.
Recordings waiting for delivery¶
On a node with central recording, three state.recordings.* checks read the
state of the delivery pass. None
run under mode = "disk".
| Check | Reads | Reports | Status |
|---|---|---|---|
state.recordings.pending |
the recordings directory | Casts waiting for a pass, total size, age of the oldest. | info when anything waits; warn when the directory exists and cannot be read. An empty directory is reported as empty. |
state.recordings.stuck |
.delivery.json |
Casts the last pass could not deliver, by reason; when that pass ran and what it moved. | warn when a pass left anything behind (needs a person); ? when no pass has written a journal yet. |
state.recordings.store |
a handshake with Authority | Whether the central store answers. | Runs only when pending is non-empty; five-second limit. |
A successful store handshake confirms the route and certificates; the last
upload attempt and its error are in stuck. Under
authority_transport = "tunneled" the store check is not made (the route is
the connector's reverse tunnel); the finding is ? and quotes the last pass.
state.recordings.sweep warns under mode = "central" with
sweep_interval_secs = 0: no
pass runs on the node, including at start-up, and only
recordings push delivers
recordings.
Recordings a killed connector left behind¶
On a node with central recording, two checks report what a killed connector left. Each reads one file.
| Check | Reads | Reports |
|---|---|---|
state.recordings.closed_at_start |
.startup.json |
What the last start did with recordings the previous run left half-written: closed, lost an unfinished last line, dropped with no event, could not be closed. |
state.recordings.late_close |
.delivery.json |
Casts the last delivery pass held back for lack of a close event on the chain, split into: no open on the chain for the session, or still no close after one was written. |
| Check | Condition | Status | What to do |
|---|---|---|---|
closed_at_start |
A file could not be closed; it stays as .cast.partial. |
warn | Check permissions on the directory the remedy names. |
closed_at_start |
Start-up closed something. | info | Each session needs a close event; see late_close. |
closed_at_start |
Recordings directory does not exist. | info, nothing recorded yet |
None. |
closed_at_start |
Directory exists, no .startup.json. |
?, no report yet |
None. |
late_close |
Anything waiting. | warn | A cast with no open on the chain is not moved by any pass; keep or discard it. |
late_close |
No delivery journal yet. | not printed | state.recordings.stuck reports the missing journal. |
gdsgate --config node.toml doctor
gdsgate --config node.toml doctor --explain
gdsgate --config node.toml doctor --json
gdsgate config schema [--role <role>]¶
Prints the JSON Schema (draft 2020-12) of the configuration file to stdout.
The published copy is reference/config.schema.json.
| Flag | Default | Meaning |
|---|---|---|
--role <role> |
every key | Only the top-level keys this role reads: authority, proxy, connector, client, agent-host. |
Each property carries type, allowed values (oneOf of const), default
and a one-sentence description. Objects reject keys they do not declare
(additionalProperties: false).
| Annotation | On | Value |
|---|---|---|
x-gdsgate-roles |
every top-level key | The roles that read the key and everything under it. |
x-gdsgate-profiles |
keys a profile requires | The profiles that refuse to start without the key; see the profile matrix. |
Editors that read Taplo directives validate a
file against it with a first line of
#:schema https://gate.gds.dev/docs/0.11/reference/config.schema.json.
gdsgate config check <file> --role <role>¶
Checks one configuration file for one role, offline, and prints every problem
with its key path and fix. Opens no connection and no store, runs no helper
command, and reads no configuration other than <file>.
| Flag | Default | Meaning |
|---|---|---|
<file> |
required | The configuration file. |
--role <role> |
required | authority, proxy, connector, client or agent-host. |
--profile <profile> |
the file's [security].profile |
Judge under dev, pilot or production. |
--json |
off | Print a JSON array of {path, severity, message, fix}. |
| Finding | Severity | Source |
|---|---|---|
| TOML that does not parse | error | The parser; the only finding, path empty. |
| Unknown key | error | Keys no field claims; the fix names the key within two edits of it. |
| Wrong type or value outside the allowed set | error | The parser, one per top-level key; the rest of the file is still checked. |
| Profile refusal or warning | error or warning | The checks the role runs at start; the text report names the profile matrix row in brackets. |
| Missing or unreadable file, cross-key rule, invariant | error or warning | The configuration checks of gdsgate doctor. |
For proxy, connector and agent-host the node counts as enrolled when
[enroll] names an endpoint (or a join relay) and either a token is supplied
([enroll].token, GDSGATE_ENROLL_TOKEN in proxy.env or connector.env
beside the file, which the unit reads, or GDSGATE_ENROLL_TOKEN in the
environment of config check) or [enroll].state_dir already holds
an identity. [audit].export_path not yet on disk is no finding: Authority
creates it on first start. The store checks (replica-keyrings,
sealed-row-keys) are not run.
| Exit code | Meaning |
|---|---|
0 |
No findings. |
1 |
At least one error. |
2 |
Warnings only. |
gdsgate config check /etc/gdsgate/authority.toml --role authority
gdsgate config check /etc/gdsgate/authority.toml --role authority --profile production --json
error audit.export_pth: unknown key: the parser ignores it
fix: rename it to `audit.export_path`, or remove it
error store_auto_migrate: invalid type: string "yes", expected a boolean
fix: give `store_auto_migrate` the type `gdsgate config schema` shows for it
error store_url [store-postgres]: `store_url` names no PostgreSQL store (sqlite:///var/lib/gdsgate/store.db?mode=rwc): one writer, lost with the host
fix: `store_url = "postgres://gdsgate@db/gdsgate"`, password in `store_password_file`; `setup prepare --store-url`.
warning audit.export_path [audit-export]: `[audit].export_path` is unset: no off-cluster copy of the audit log
fix: `[audit].export_path = "/var/lib/gdsgate/authority/audit.jsonl"` (Windows: `%ProgramData%\gdsgate\state\authority\audit.jsonl`), shipped by a log collector.
3 error(s), 1 warning(s): authority.toml, role authority, profile as declared
[
{
"path": "store_url",
"severity": "error",
"message": "`store_url` names no PostgreSQL store (...): one writer, lost with the host",
"fix": "`store_url = \"postgres://gdsgate@db/gdsgate\"`, password in `store_password_file`; `setup prepare --store-url`."
}
]
| Symptom | Cause | What to do |
|---|---|---|
error (file): TOML parse error at line N |
The file is not valid TOML. | Fix the line named; no other check runs until it parses. |
unknown key: the parser ignores it |
A misspelled or removed key. | Rename it as the fix says, or remove it. gdsgate config schema lists the keys. |
invalid type: ..., expected ... |
A value of the wrong type, or a word outside the allowed set. | Use the type or value gdsgate config schema gives for the key. |
not found: <path> |
A file the configuration names is absent on this host. | Create it, or run the check on the host the file is for. |
audit export file: the service cannot write it: <path> |
gdsgate-authority.service is installed with ProtectSystem=strict and the path is outside its state directory and ReadWritePaths=. Authority starts with the export off. |
Move [audit].export_path under /var/lib/gdsgate/authority, or add its directory to ReadWritePaths= in a drop-in. |
the client-facing listener has no TLS for --role proxy |
No [proxy].public_cert_file pair and nothing to enrol with. |
Set the pair, or [enroll].endpoint and [enroll].token. |
Reading a failure¶
Start with --explain: the check id and the source it names. Missing identity, invalid TOML
or Cedar, a pending store migration, and an unreachable route each have their
own remedy. Remove tokens, private keys and full policy contents from JSON
before attaching it to an incident ticket.