Dynamic groups, activity intelligence, API keys, Swagger, maintenance #11

Merged
gsadmin merged 34 commits from development into main 2026-09-02 23:03:12 +00:00

34 Commits

Author SHA1 Message Date
Alphaeus Mote dc0df06b92 chore(logging): default max log file size to 5 MB
Lower ORCHESTRAD_LOG_MAX_SIZE_MB default from 100 to 5 so log files roll
sooner by default; README updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 19:01:31 -04:00
Alphaeus Mote 4f949e6d8f fix(auth): make API keys actually authenticate; add read/read-write scopes
API keys were never validated — the middleware only checked session tokens,
so an X-API-Key request always 401'd. Add auth.Service.ValidateAPIKey (looks
up the key hash, enforces enabled/revoked/expiry, loads the user + roles,
stamps last_used_utc) and route X-API-Key / bearer auth through it.

Add per-key scopes (migration 006): "read" (GET/HEAD only) or "readwrite"
(full access, default). The middleware rejects mutating requests from a
read-scoped key with 403. Create accepts a scope; list and create responses
include it; the UI create dialog has a scope selector and the list shows a
scope chip. Disable/re-enable and revoke (permanent) were already correct.

Verified live: RW key GET/POST ok; read key GET ok, POST 403; disable→401,
re-enable→200; revoke→401 and re-enable blocked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 19:01:30 -04:00
Alphaeus Mote c74dd63281 feat(api): OpenAPI/Swagger docs generated from the router + PowerShell example
- GET /api/docs serves a Swagger UI and GET /api/openapi.json serves an
  OpenAPI 3 spec built by walking the live chi router, so documented paths
  always match what the build serves. A small registry adds rich detail
  (request bodies, params, schemas) for the automation-critical operations
  (auth login, rule create/update/preview, connection introspection);
  RuleInput and friends are defined as reusable component schemas.
- Add docs/examples/Create-OrchestrADRule.ps1: a no-alias PowerShell sample
  that builds headers/body as typed dictionaries, serializes with
  ConvertTo-Json, logs in, creates a SyncGroupMembership rule, and runs it.
- README: new "API & Automation" section (Swagger + PowerShell), and a
  "Logging & Retention" section documenting log rotation and the new
  database maintenance/retention knobs.

Tests cover spec generation from a router and registry well-formedness.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 18:33:53 -04:00
Alphaeus Mote 8ee9b95cde feat(ui): make dashboard widgets drill in
Count tiles link to their resource pages; the run-stat cards open Activity,
Recent Rule Runs opens Rule Runs, and Connection Health opens Connections.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 18:25:15 -04:00
Alphaeus Mote 1e38b3b99a feat(maintenance): history retention + VACUUM; robust config import
Database maintenance:
- New MaintenanceService prunes rule_runs (+ their action detail) and
  audit_events older than their retention windows, then VACUUMs to reclaim
  space. Runs once at startup and then on an interval, bound to the run
  context. Configurable via ORCHESTRAD_RUN_RETENTION_DAYS (90),
  ORCHESTRAD_AUDIT_RETENTION_DAYS (180),
  ORCHESTRAD_MAINTENANCE_INTERVAL_HOURS (24), ORCHESTRAD_MAINTENANCE_VACUUM.
  This stops the database growing forever. (Log rotation already existed via
  lumberjack: ORCHESTRAD_LOG_MAX_SIZE_MB/_MAX_BACKUPS/_MAX_AGE_DAYS.)

Config import:
- Import now accepts either the wrapped {payload,dryRun} shape or a bare
  exported config object, so a file downloaded from Export re-imports
  directly (dryRun via ?dryRun=true) — useful for automation.

Test covers retention pruning with FK-cascaded action rows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 18:25:14 -04:00
Alphaeus Mote 154cadc792 feat(ui): per-rule history dialog showing objects added/removed
Add a History icon to each rule that opens a dialog listing the actual
objects this rule acted on — added, removed, moved — scoped to the rule via
the activity feed's ruleId filter, with category/result filters, paging, and
the per-action detail drill-in. Colour a "Completed" last-run result as
success (not amber) now that the rules list surfaces it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 17:48:28 -04:00
Alphaeus Mote 429e8bd398 fix(rules): record last run outcome on the rule row
The runner updated the rule_runs history but never wrote the rules table's
last_run_utc / last_run_result columns, so the rules list showed blank Last
Run / Last Result. Add RuleRepository.UpdateLastRun and call it from the
runner's finalize and fail paths with the run's completion time and status.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 17:48:27 -04:00
Alphaeus Mote 50147f357f feat(ui): show schedule + next run on the rules list
Add a Schedule column to the rules table that resolves each rule's
scheduleId to the schedule name and its next run time (or "Manual"),
pairing with the newly seeded built-in schedules.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 17:14:56 -04:00
Alphaeus Mote 1f77473d8f feat(server): trust local proxies by default, seed built-in schedules
Proxy / base-URL:
- ORCHESTRAD_TRUSTED_PROXIES now defaults to "local", trusting reverse
  proxies in loopback + RFC1918 + link-local/ULA ranges out of the box, so
  X-Forwarded-* (client IP, scheme, host) is honored behind an edge proxy
  without extra config. New keywords: local/private, all/any, none.
- OIDC redirect URI derivation now uses the trust-gated request base URL
  instead of reading X-Forwarded-Proto directly, and audit client IP now
  trusts the middleware-rewritten RemoteAddr rather than the raw (spoofable)
  X-Forwarded-For header. Both honor forwarded values only from trusted
  peers.

Schedules:
- Seed eight built-in schedules on startup (every 5/15/30 min, hourly,
  every 6/12h, daily, weekly), idempotent by name, so operators have
  ready-made cadences in the Schedules page and the rule editor's schedule
  dropdown without hand-building one.

Test covers the trusted-proxy keyword expansion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 17:14:49 -04:00
Alphaeus Mote 4c20fe1e15 feat(ui): live attribute + value pickers in the filter builder
The filter builder's attribute field now searches the selected connection's
full directory schema (scoped to the rule's object type) instead of a fixed
curated list, and the value field suggests the distinct values actually
present in the directory for the chosen attribute — both search-as-you-type
with the curated common attributes still shown first.

New AttributePicker and ValuePicker components (async MUI autocompletes over
ConnectionsApi.attributes / attributeValues), wired into each condition row;
memberOf still uses the group picker and raw-LDAP keeps a plain field.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 15:58:02 -04:00
Alphaeus Mote dc3c2c9811 feat(connections): schema attribute + distinct-value lookups for filter building
Add two directory introspection endpoints the rule filter builder uses:

- GET /ad-connections/{id}/attributes?objectType=&q= returns the schema
  attributes that APPLY to the given object type (User/Computer/Group). The
  applicable set is derived by walking the classSchema hierarchy from the
  object's class up through subClassOf to top, plus auxiliary classes,
  unioning each class's may/must-contain attributes; results are filtered by
  substring and returned with adminDescription. The per-(connection,object
  type) set is cached for 10 minutes so the walk is not repeated per
  keystroke. (Computer inherits user attributes, since AD's computer class
  subclasses user — reflected correctly.)
- GET /ad-connections/{id}/attribute-values?attribute=&objectType=&q= samples
  objects and returns the distinct values present for one attribute, so the
  value field can suggest real directory values.

Backed by a new bounded ldap Client.SearchWithLimit that tolerates the
server's size-limit response. Attribute names are validated against the LDAP
descriptor charset before use in a filter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 15:57:52 -04:00
Alphaeus Mote e513687b5f feat(ui): dynamic-group rule editor
Replace the old read-only rule form (which could only edit scalar metadata
and showed "author via config import" for conditions/actions) with a full
tabbed editor that authors the whole rule:

- Scope: connection, object type, and a searchable base-OU picker.
- Filter: a condition-group builder with attribute autocomplete (from the
  backend metadata), operator dropdowns, per-condition/-group NOT, AND/OR
  join within and across groups, and a raw-LDAP escape hatch. memberOf
  operators render a group picker for the value.
- Target & action: choose the action (Sync membership to group / Move to OU
  / Ensure group exists / Add to group), pick or paste the target group/OU,
  set the sync mode (Full / Managed / Add-only) and create-if-missing;
  multiple actions supported.
- Schedule: run manually, on an existing schedule, or create an interval or
  cron schedule inline.
- Preview: live dry-run against the directory (POST /rules/preview) showing
  match count, generated filter, planned +add/-remove, and sample matches.

New DirectoryPicker (search-or-type Autocomplete backed by the connection
directory endpoint) is reused for base OU, target group, target OU, and
memberOf values. API layer gains RuleMetadata/DirectoryObject/RuleInput
types and RulesApi.metadata/previewSpec + ConnectionsApi.directory.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 15:19:30 -04:00
Alphaeus Mote d5b8e693df fix(repository): flatten GetByID condition loading to avoid nested cursor
getConditionGroups held an open rows cursor while calling getConditions
per group, so each GetByID needed two pooled connections at once and could
deadlock under a small/exhausted pool. Read all groups first, close the
cursor, then load every group's conditions in a single bulk IN-query keyed
by condition_group_id. The repository round-trip test now runs against a
single-connection pool, which guards the no-nested-cursor invariant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 15:11:28 -04:00
Alphaeus Mote 5920b690d1 feat(rules): dynamic-group reconciliation + editor-facing APIs
Turns rules into Adaxes/Active-Roles style dynamic groups. The core new
capability is a set-level SyncGroupMembership action that reconciles a
target group's membership against the matched object set in one pass
instead of the old add-only, per-object behaviour.

Engine / reconciliation:
- New ActionSyncGroupMembership runs once per target group after the
  match: resolve (and optionally create) the group, read its current
  members, diff against the matched set, and apply the adds/removes.
- Three per-rule sync modes (types.SyncMode): FullSync (membership ==
  matched set; removes stale members incl. manual adds), ManagedAdd (adds
  matches, removes only members this rule added), AddOnly (never removes).
- Managed ownership tracked in a new managed_group_members table
  (migration 005) + repository, wired into the runner's engine so
  ManagedAdd removes only what it added.
- Adds/removes are recorded as AddToGroup / RemoveFromGroupIfNoLongerMatched
  run-actions so the activity feed categorises them as syncs/removals.
- Preview now computes an accurate, non-mutating diff for sync actions
  (+add / -remove / already-in-sync counts and per-member entries).
- memberOf and memberOf-recursive (LDAP_MATCHING_RULE_IN_CHAIN) operators;
  Regex no longer silently degrades to equals.
- Canonical group targets: CanonicalToLeafDN / NormalizeGroupTarget so a
  target group can be given as domain.com/OU/Group as well as a DN.

Editor-facing APIs (backend-first; UI comes next):
- Rule create/update now accept conditionGroups + actions and persist them
  via RuleRepository.ReplaceLogic (soft-delete + insert, preserving the
  rule_run_actions FK). Omitting them leaves existing logic untouched.
- POST /api/v1/rules/preview evaluates an unsaved draft (live match panel).
- GET /api/v1/rules/metadata serves the operator vocabulary (object types,
  operators, action types, sync modes, common attributes) so UI dropdowns
  stay in lock-step with the backend.
- GET /api/v1/ad-connections/{id}/directory searches groups/OUs for the
  target pickers.

Tests: reconciliation across all three modes + create-if-missing and the
missing-group error path (fake directory client); canonical leaf-DN
conversion; ReplaceLogic round-trip. Full suite green.

Note: RuleRepository.GetByID nests a query (getConditionGroups holds a
cursor while calling getConditions); safe under the production pool (25)
but a follow-up should flatten it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 14:57:30 -04:00
Alphaeus Mote 7effc09e8a fix(connections): List must return the full connection shape
The ad_connections List query selected only a subset of columns, so
use_start_tls, allow_invalid_certs, timeout_seconds, paging_enabled and
page_size came back as zero-values in the list response. Because the edit
form pre-populates directly from the list row, opening and saving a
connection silently overwrote those stored values with their zero values
— e.g. "Allow invalid certs" would never stick, and timeout/page size
reset to 0.

Select the full column set in List (matching GetByID) and scan the
previously dropped fields. Extend the repository round-trip test to set
these fields on create and assert List surfaces them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 14:15:36 -04:00
Alphaeus Mote e74cb454c8 feat(activity): add action-ledger intelligence and drill-in feed
Turn the rule_run_actions ledger into operator intelligence. A new
ActivityService rolls the ledger up by time window (24h / 7d / all-time)
into syncs, operations and removals with success/failure counts and the
number of distinct objects affected, plus all-time totals per action type
and a most-active-rules ranking. A companion filtered, paginated feed
answers the operational questions directly: what action ran, what
happened, to which object, triggered by whom, and when.

Backend:
- ActivityService.Summary() and ListActions(filter) over the joined
  rule_run_actions / rule_runs / rules tables, categorising each action
  type as Sync (AddToGroup/AddGroupToGroup), Removal
  (RemoveFromGroupIfNoLongerMatched) or Operation (everything else).
- GET /api/v1/activity/summary and GET /api/v1/activity (category,
  actionType, status, ruleId, search, pagination).
- Table-driven test seeding a run with mixed action types/statuses and
  asserting window roll-ups, distinct-object counts, top rules, and the
  category/status feed filters.

Frontend:
- New Activity page: window summary cards, by-action-type and
  most-active-rules tables, and a drill-in feed with category/result/
  object filters and a detail dialog that pretty-prints the action's
  details JSON and error.
- Wired into the vertical sidebar and horizontal navbar under Automation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 14:09:04 -04:00
Alphaeus Mote 6cb8e67c0c feat(ui): remove language selector from header
Drop the flag/language dropdown from both the vertical and horizontal
headers and delete the now-unused Language component. OrchestrAD ships a
single language, so the selector was dead UI chrome.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 14:08:44 -04:00
Alphaeus Mote dd183941dc fix(repository): parse TEXT timestamps as strings for modernc sqlite
The modernc.org/sqlite driver returns TEXT timestamp columns as strings
rather than time.Time (unlike the CGO mattn driver), so scanning directly
into *time.Time failed with "unsupported Scan" and surfaced as 500 errors
on the credentials and connections list endpoints.

Scan created/updated/started timestamps into strings and convert with the
existing parseTimeOrZero/parseNullTime helpers across credentials,
connections, schedules, rules, and rule_runs repositories. Add a
regression test that round-trips a credential and connection through a
migrated DB and asserts the timestamps come back parsed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 13:55:11 -04:00
Alphaeus Mote f0c81dfa51 feat(ui): Security page (OIDC + TLS config) and canonical name in preview
Add a Security admin page wiring the OIDC and TLS backends: an SSO/OIDC form
(issuer, client id/secret, redirect, scopes, claim mappings, default role) and a
TLS card showing the live certificate and a source picker - self-managed,
bring-your-own (PEM upload), or a Windows-store certificate (enumerate + select).
Add OidcApi/TlsApi clients + types, a Security nav entry, and a Canonical Name
column to the rule preview.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 13:06:30 -04:00
Alphaeus Mote 5674ca7849 feat(ui): dark-by-default, OrchestrAD branding, de-jarred login, SSO button
Default the theme to dark (toggle still available). Rebrand the logo to the
OrchestrAD icon + wordmark. Redesign the login into a calm, centered card on a
soft themed backdrop (removed the 3D character illustration and decorative
circles). Add a 'Sign in with SSO' button shown when OIDC is enabled, and adopt
the OIDC callback token from the URL fragment (hydrating the user via /auth/me).
Replace the hardcoded 'Mike/Admin' sidebar card with the real signed-in user and
a working sign-out; de-jar the search label.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 12:59:14 -04:00
Alphaeus Mote 7be92da2e6 feat(rules): canonical->DN targets and idempotent OU-path creation
Add CanonicalToOUDN/NormalizeOUTarget so an OU target can be given as a canonical
path (domain.com/OU/OU) or a DN, and Client.EnsureOUPath which idempotently
creates every OU down the path (parents before children). MoveToOu and group
creation now normalize the target and ensure the full OU path instead of only
the leaf. Unit-tested (conversion, escaped split) and verified against the test
AD: a canonical nested target creates each OU and moves the object; re-running is
a no-op.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 12:47:26 -04:00
Alphaeus Mote c840ed054c feat(rules): expose operator-friendly canonical names for matched objects
Add ldap.CanonicalName: prefer the directory's canonicalName attribute (now
requested in searches) and otherwise build domain.com/OU/OU/CN from the DN.
Populate MatchedObject.CanonicalName and return it in the rule preview API so
operators see canonical names instead of raw DNs. Unit-tested.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 12:43:09 -04:00
Alphaeus Mote b0decfe325 test(ad): opt-in integration tests exercising rules against a real AD
Add build-tagged (adtest) + env-gated integration tests that run the rule engine
against a live Active Directory over plain LDAP: create a unique test OU with
users/groups, execute a rule, and assert the AD outcome, then tree-delete the OU
on cleanup. Covers add-to-group-by-condition (matching users added, others not),
create-missing-group, and move-to-OU. Excluded from normal builds and CI; run
with -tags adtest and ORCHESTRAD_AD_TEST_* env. Verified green against the test
domain; no objects left behind.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 12:09:45 -04:00
Alphaeus Mote 06b1c860d4 feat(auth): OIDC/SSO login with UI-configurable, env-overriding settings
Add backend OIDC: /api/v1/auth/oidc/status|login|callback (authorization code +
PKCE, state+nonce cookies, ID-token verification via go-oidc) and admin
/auth/oidc/config (GET/PUT, validates the issuer via discovery on enable).
Config resolves app_settings (UI) > env > default, so SSO can be configured from
the UI while env still seeds it. LoginOIDC provisions/links federated users by
the stable (issuer, subject) pair, refusing to reuse a username held by a
different account. Verified: discovery + real authorize redirect + PKCE cookies;
config API with DB-over-env precedence; provisioning unit-tested.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 12:01:48 -04:00
Alphaeus Mote 0e671ff34c feat(installer): wizard with install-dir + network dialogs and finish page
Add a WiX UI wizard (WixToolset.UI.wixext): Welcome -> License (GPLv3) ->
Install directory -> Network (listen address + port) -> Ready -> Finish. The
install dir, listen address, and port are recorded in the registry and the
service reads them; the finish page shows https://localhost:<port>/ with the
default admin/admin credentials and an optional 'open in browser' box. Schedule
InitializeService after WriteRegistryValues so the first start binds the chosen
port. CI msi job adds the UI extension. Verified: install to a custom dir + port
binds that port (health OK), registry recorded, upgrade + uninstall clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 11:51:52 -04:00
Alphaeus Mote 6b4cd325c9 feat(config): read installer-recorded listen address/port from the registry
On Windows, resolve the listen address/port with precedence env > registry
(HKLM\Software\Grace Solutions\OrchestrAD ListenAddress/ListenPort, written by
the MSI wizard) > default. No-op off Windows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 11:51:52 -04:00
Alphaeus Mote 81468b8af1 feat(tls): real Windows My-store enumeration and CNG-backed serving
Implement ListWindowsCerts (LocalMachine + CurrentUser My stores: thumbprint,
subject, issuer, validity, SANs, has-private-key) and windows-store serving:
find the cert by thumbprint, acquire its CNG key, and serve TLS via a
crypto.Signer that signs through NCryptSignHash (RSA PKCS#1 v1.5 and PSS) so the
private key is never exported. Legacy CSP / non-RSA keys report a clear error.
Verified end to end: a CNG cert in LocalMachine\My serves a completed HTTPS
handshake (openssl confirms the served subject).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 11:30:48 -04:00
Alphaeus Mote 81753395be fix(tls): include per-adapter DNS suffixes in cert SANs
Connection-specific DNS suffixes are set per adapter (and via DHCP) without a
domain join, so read Domain/DhcpDomain from each Tcpip Interfaces subkey in
addition to the global Domain/NV Domain/SearchList. Verified on a workgroup host:
the cert now covers host.<adapter-suffix> and the suffix for every adapter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 11:23:20 -04:00
Alphaeus Mote a4698ee9f9 feat(tls): certificate modes (auto/provided/windows-store) + config API + richer SANs
Add runtime TLS modes resolved via settings (UI wins over env): auto
(self-managed), provided (bring-your-own upload), and windows-store (Windows
cert store, enumeration/serving scaffolded). New /api/v1/tls endpoints: status,
mode, certificate upload, and windows-store list; changes reload the live
manager without a restart, falling back to the self-managed cert if a configured
source is unavailable. Auto SANs now cover the hostname (CN), localhost, every
non-loopback IP, and host.<suffix> + the suffix for each detected DNS suffix
(Windows registry / resolv.conf). Bring-your-own is validated and unit-tested.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 11:21:00 -04:00
Alphaeus Mote 300866d5cf feat(tls): auto-managed HTTPS with renewal; disable for edge termination
Add internal/tlsmgr: on startup, load-or-generate the CA, issue/reuse the leaf,
export the chain under <data>/tls, and hot-swap the cert on renewal via
GetCertificate. The server listens HTTPS when a manager is present. TLS is on by
default for the standalone binary/service (tls.enabled setting or
ORCHESTRAD_TLS_ENABLED, UI wins over env); the container image defaults it off so
it runs plain HTTP behind a TLS-terminating proxy. Startup logs a clickable URL.
Verified end to end: HTTPS serves, plain HTTP rejected, disable serves HTTP.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 11:06:42 -04:00
Alphaeus Mote f38702dae5 feat(pki): self-managed CA (root+intermediate+leaf) with PEM/DER/PFX export
Add internal/pki: generate a Root CA -> Intermediate CA -> server leaf, re-issue
leaves under a persisted CA for renewal, and export the chain as PEM
(cert/key/fullchain), DER (.cer), and PKCS#12 (.pfx). Unit-tested: the chain
verifies, the PFX decodes with the CA chain, and a reloaded CA re-issues a
valid leaf.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 11:06:42 -04:00
Alphaeus Mote 5eae8cc102 feat(services): DB-over-env settings precedence resolver
Add SettingsService.ResolveString/Bool/Int resolving runtime config as
app_settings (UI) > environment variable > default. Env seeds bootstrap; any
value set in the UI persists to app_settings and wins. Foundation for OIDC and
TLS UI configuration. Unit-tested for the precedence order.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 10:56:06 -04:00
Alphaeus Mote 07b2fe021d feat(config): default listen address 0.0.0.0:18090
Change the default HTTP port from 8080 to 18090 and propagate it through the
Dockerfile (EXPOSE + healthcheck), docker-compose, and the README. Host default
stays 0.0.0.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 10:56:05 -04:00
Alphaeus Mote 2a66a115b5 docs: document install/service/CI flow and add UI screenshots
Add Installation (MSI/binary/Docker), Running & Service Management (the CLI
commands incl. idempotent initialize/remove), and CI/CD (two-job release +
required REGISTRY_PASSWORD secret) sections; note the pure-Go build. Add
dashboard/rules/credentials/login/schedules screenshots and a Screenshots
section.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 10:36:21 -04:00