Commit Graph

51 Commits

Author SHA1 Message Date
wmantly c7d599de85 release(v2.2.0): Windows local-discovery, route pinning, prompt reconnect 2026-08-10 17:31:05 -07:00
wmantly 230b7fb172 Merge pull request #13 from theta42/feat/windows-local-discovery
feat(discovery): Windows local-discovery + route pinning + prompt reconnect
2026-08-10 17:25:28 -07:00
wmantly b2ad8f4844 feat(discovery): Windows local-discovery + local route pinning + prompt reconnect
Completes the mDNS local-discovery feature on Windows (was Linux-only since
v2.1.2). Three parts:

1. Windows hosts override (hosts_override_windows.go): %SystemRoot%...\\hosts,
   CRLF-aware read/write, ipconfig /flushdns after each change. The agent runs
   as a SYSTEM service so elevation is a non-issue. hosts_override.go split
   into shared rewrite logic + platform files; the hosts tests now run the real
   Windows write path on CI instead of skipping.

2. Local route pinning (local_route*.go): the hosts override only fixes name
   resolution -- the packet path is the routing table's job. If the WG mesh
   tunnel is up with AllowedIPs covering the LAN (or full-tunnel 0.0.0.0/0) it
   swallows the direct connection. Discovery now pins a /32 host route via the
   owning local interface (route.exe metric 1 on Windows, ip route replace on
   Linux) and drops it on revert. Closes a gap in the shipped Linux path too.

3. Prompt reconnect: apply/revert signals the WS loop so it reconnects
   immediately instead of waiting out the 5s backoff.

Route/hosts code is injectable + unit tested; go test passes natively on
Windows (this machine), and linux/amd64 + windows/arm64 cross-builds are clean.
2026-08-10 17:24:21 -07:00
wmantly a3eeed8112 release(v2.1.3): fix Windows CI failure from v2.1.2's release build
release / agent-darwin-amd64 (push) Successful in 17s
release / agent-darwin-arm64 (push) Successful in 26s
release / agent-linux-amd64 (push) Successful in 29s
release / agent-linux-arm64 (push) Successful in 29s
release / agent-windows-amd64 (push) Successful in 29s
release / agent-linux-arm7 (push) Successful in 30s
release / agent-windows-arm64 (push) Successful in 28s
release / tray/helper/setup (push) Has been cancelled
release / Attach to GitHub release (push) Has been cancelled
v2.1.3
2026-08-10 19:12:04 -04:00
wmantly f76f50c704 fix(test): skip Linux-only hosts-file tests on non-Linux CI runners
v2.1.2's release CI failed on the Windows build leg: TestApplyHostsOverride_*
call applyHostsOverride(), which correctly refuses unconditionally on
non-Linux (hosts_override.go) -- but the tests didn't account for `go
test ./...` running on every platform the CI matrix builds for, only
Linux. Skip them on non-Linux with a clear reason instead.
2026-08-10 19:11:38 -04:00
wmantly 1837d18da8 release(v2.1.2): Linux mDNS local-discovery
release / agent-darwin-amd64 (push) Successful in 54s
release / agent-darwin-arm64 (push) Successful in 30s
release / agent-linux-amd64 (push) Successful in 29s
release / agent-linux-arm64 (push) Successful in 29s
release / agent-linux-arm7 (push) Successful in 28s
release / agent-windows-amd64 (push) Successful in 28s
release / agent-windows-arm64 (push) Successful in 28s
release / tray/helper/setup (push) Has been cancelled
release / Attach to GitHub release (push) Has been cancelled
Adds the CHANGELOG entry for this pass's mDNS work (local_discovery.go,
hosts_override.go -- see prior commit for the two real bugs found via
live testing), bumps AgentVersion, and documents prefer_local_directory
in agent.yml.example.

Also backfills CHANGELOG entries for v2.1.0/v2.1.1 (Windows agent,
WireGuard client, installer, CI), which were tagged and released but
never documented here.
v2.1.2
2026-08-10 19:02:00 -04:00
wmantly 9fdbb8aab0 feat(mdns): Linux local-discovery -- skip the WAN relay when on-site
Implements the Linux half of AGENT_LOCAL_DISCOVERY_SPEC.md: when a local
theta-gateway/theta-proxy announces itself via mDNS as fronting this
agent's ServerURL host, skip the relay/WAN path and talk to it directly.
Off by default (config.PreferLocalDirectory / prefer_local_directory)
since it changes host name resolution.

- local_discovery.go: polls for _theta-suite._tcp every 30s via
  hashicorp/mdns, matches the TXT "hosts" field against the agent's own
  target host, applies/clears a hosts-file override on change. Presence/
  absence of the mDNS announcement IS the "on this LAN or not" signal --
  no separate network detection needed, since multicast doesn't cross
  routers/VLANs.
- hosts_override.go: writes a single marked, idempotent block into
  /etc/hosts (never touches anything else in the file); clearing removes
  the block entirely rather than leaving empty markers.
- HARD RULE preserved: this only ever changes DNS resolution, never TLS
  trust -- nothing here touches certificate validation, so a spoofed
  rogue mDNS announcement produces a TLS failure against the real
  hostname's cert, not a silent MITM.

Verified end-to-end with real containers (Node mDNS announcer + this
actual Go binary, not mocked), which caught two real bugs neither showed
up in code review:

1. mdns.Lookup()'s DefaultParams() requests both IPv4 and IPv6. The
   underlying client sends the v4 query (which got a real, valid
   response per a packet capture), then sends the v6 query, and if THAT
   send fails (no IPv6 route -- common on plain v4 hosts/containers) the
   whole Query() returns that error synchronously, before ever entering
   the response-listening loop. The v4 response was silently discarded.
   Fixed by building QueryParam manually with DisableIPv6: true instead
   of using the Lookup() convenience wrapper.
2. The original hosts-file writer used write-tmp-then-rename for
   atomicity. /etc/hosts is frequently a bind mount (every container
   runtime does this) -- rename() onto a bind-mounted file fails with
   EBUSY, since you can't atomically replace a mountpoint. Switched to
   truncate-and-rewrite in place; the process-local mutex already
   serializes writers, so the lost atomicity is a small, acceptable
   tradeoff against a confirmed hard failure.

Full cycle verified: announcer starts -> agent discovers it -> hosts
override applied -> announcer stops -> override cleanly reverts, no
stale entry, no discovery trace left.

Windows/macOS remain unbuilt -- need platform-native testing this
environment can't do (see AGENT_LOCAL_DISCOVERY_SPEC.md §3's open
question: hosts-file edits vs. a local stub resolver, per-OS elevation
and DNS-cache behavior).
2026-08-10 17:54:39 -04:00
wmantly d937f8dcba fix(installer): silent install kept empty server_url; tray now starts; self-update uses GitHub releases
Fresh-install bug report fixes:

- Silent installs wrote server_url: '' -- CurPageChanged fires even when the
  wizard is walked programmatically in silent mode, so it read the empty edit
  boxes and clobbered the /SERVER_URL /JOIN_KEY command-line params. Guard the
  read with WizardSilent() so silent installs keep the params and interactive
  installs keep the wizard values. (This is also why the service exited on
  first connect and the Directory showed the agent as 'v2.0.0': the agent never
  connected, so the server fell back to its placeholder version.)
- The tray post-install launch had skipifsilent, so a silent install (the
  common path from the Directory's Windows command) never started the tray.
  Removed it -- the tray starts in silent installs too.
- theta-agent update (cli.go) downloaded from the SSO /resources path, which no
  longer serves binaries (they are GitHub release artifacts now) -- 404.
  Pointed it at releases/latest/download via releaseAssetURL.
2026-08-09 23:23:42 -07:00
wmantly 2c42960c61 Merge feat/windows-agent: Windows agent (platform ops, service wrapper, WireGuard, IAM, tray, installer, CI) 2026-08-09 20:40:51 -07:00
wmantly db6253e263 ci(release): sign only Windows PE files, hash after signing 2026-08-09 20:40:19 -07:00
wmantly 5013148ffe fix(installer): Theta Directory branding, visible URL/join-key fields, GUI tray, service autostart
User-reported install fixes:

- Branding: every user-facing 'SSO Manager' string now says 'Theta Directory'
  (agent logs, CLI usage, agent.yml.example, installer wizard).
- Wizard page: the URL/join-key text boxes were never shown. The layout used
  Surface.Width (0 at wizard init) instead of SurfaceWidth and combined
  WordWrap with AutoSize (mutually exclusive in VCL). Rewritten with the
  canonical Inno pattern (SurfaceWidth + ScaleY + explicit label height).
- No console window after install: the tray and helper now build as
  GUI-subsystem binaries (-H=windowsgui) in build_all.sh and
  scripts/setup-build-env.ps1. The agent stays a console app for foreground
  debugging (as a service it never shows a console).
- The daemon never came up after install: install-service now starts the
  service immediately, so the tray IPC socket exists right away and the tray
  connects instead of logging 'actively refused' until a reboot.

Verified: go build/vet/test green; tray+helper PE subsystem = GUI (2), agent =
console (3); installer compiles; tray runs silently.
2026-08-09 20:33:06 -07:00
wmantly da158b0adc ci(release): dedupe windows agent exes (build-agent matrix owns them) 2026-08-09 19:46:56 -07:00
wmantly 98daa81a92 ci(release): build every binary on GitHub and attach to releases
Per decision: no binaries committed to the repo; everything is built on GitHub
Actions and hosted as release artifacts (releases/latest/download/<artifact>).

- .github/workflows/release.yml: matrix builds the agent for
  linux(amd64/arm64/armv7), windows(amd64/arm64), darwin(amd64/arm64); tray for
  linux/windows; helper for windows; the fully-offline Inno setup.exe compiles on
  a windows runner via scripts/setup-build-env.ps1 -SkipGo -Build -CI. The
  publish job merges everything, writes SHA256SUMS, optionally signs with Azure
  Trusted Signing (secret-gated), and attaches to the tag's release.
- Replaces build-windows.yml (removed) — one release pipeline for all platforms.
- Untracks the committed dist binaries (they stay gitignored for local dev and
  are produced by CI now).
- DESIGN-WINDOWS.md §9 updated: consumers (install.sh, the SSO modal) download
  from GitHub release artifacts; SSO may mirror them into /resources for air-gap.
2026-08-09 19:40:12 -07:00
wmantly d18de7d109 test(tray): add pngToIco + toWindowsIcon structure tests
Overlooked when the tray dir was gitignored by theta-agent-*; the negation in
.gitignore now lets the test be tracked.
2026-08-09 19:07:12 -07:00
wmantly 016a8fc6b6 fix(tray): Windows tray icon now loads — PNG->ICO with BMP entries
fyne.io/systray requires .ico content on Windows, and LoadImage cannot read
PNG-in-ICO. The embedded icons are PNG, so SetIcon always failed and no tray
icon ever appeared. pngToIco decodes the PNG and re-encodes classic BMP
(XOR + AND mask) entries at 16/32/48px — the format LoadImage has always
supported. Verified: the 'unable to set icon' error is gone and the tray
process stays up.

feat(installer): wizard page, Start Menu icons, silent install params

- Wizard page asks for the SSO Manager URL + join key, with an 'Open SSO
  install-agent page' button (ShellExec); values feed agent.yml
- /SERVER_URL /JOIN_KEY /AUTH_TOKEN /PUBLIC_KEY and /B64_CONFIG (base64 of a
  full agent.yml, decoded by an inline B64Decode) drive the same result in
  /SILENT mode, so the SSO's Install Agent modal can emit one Windows command
- [Icons]: Theta Agent Tray / Open Agent Config / Uninstall in Start Menu
- WireGuard client launches its UI at the end of the MSI; taskkill after the
  msiexec step closes it (the tunnel is agent-managed)
- tray starts right after install (not just at next logon)
- Inno 7 fixes: controls parent to Page.Surface, SaveStringsToUTF8FileWithoutBOM

Adds pngToIco structure tests (valid ICO dir + 32bpp DIBs) and a platform
passthrough test for toWindowsIcon.

Rebuilds all tracked dist binaries + the setup.exe.
2026-08-09 19:06:00 -07:00
wmantly ba2a619db5 build(win): idempotent setup script + verified vendor manifest; real installer build
The Windows CI/CD story is now one idempotent script that both local dev and
GitHub Actions run, so they cannot drift.

scripts/setup-build-env.ps1:
- idempotent: skips anything already present/valid; safe to re-run (verified
  no-op on second run)
- Go toolchain: pinned 1.22.2, user-space zip extract, no admin; accepts >= 1.22
- Inno Setup: pinned 7.0.2 from jrsoftware GitHub release, per-user install
  (/CURRENTUSER, no admin), sha256-verified installer
- vendor assets: fetches WireGuard MSI, VC++ redist, OpenCredential CP into
  installer/windows/vendor/ and verifies each against the pinned manifest;
  writes .sha256 sidecars
- -Build: builds agent/tray/helper for windows amd64+arm64, runs go test, and
  compiles the installer; -CI: fail loudly for workflows

installer/windows/vendor-manifest.json: pinned urls + sha256 for all three
third-party assets and the toolchain; nothing large is committed to git
(installer/windows/vendor/ is gitignored).

installer/windows/installer.iss: bundle + silently install the real
OpenCredential installer (Inno-built -> /VERYSILENT) and read /SERVER_URL and
/JOIN_KEY via {param:...}; validated by compiling with Inno Setup 7.0.2.

.github/workflows/build-windows.yml now delegates the entire build to the script
(setup-go + setup-build-env.ps1 -SkipGo -Build -CI), then hashes, optionally
signs with Azure Trusted Signing, attaches to the release, and publishes to the
SSO resource tree.

Built locally: dist/theta-agent-2.1.0-windows-amd64-setup.exe (63MB fully
offline bundle) plus windows amd64/arm64 agent, tray, and helper, all verified.
2026-08-09 18:26:03 -07:00
wmantly e613874de5 feat(windows): WireGuard client, auto-VPN, IAM, tray enrichment, installer, CI
Second milestone of the Windows parity work (DESIGN-WINDOWS.md §13).

WireGuard mesh client (signed, WSS-delivered):
- wireguard_apply / wireguard_remove commands: Ed25519-verified, gated on a new
  wireguard capability. Linux applies via wg-quick up/down; Windows installs the
  peer config as a WireGuardTunnel service via wireguard.exe
  (/installtunnelservice, /uninstalltunnelservice)
- state polling in the home monitor drives the blue tray icon and auto-VPN:
  connect when away from home + auto_vpn, disconnect on return (2m cooldown)
- tray VPN toggle and the auto-VPN checkbox are now live; the preference
  persists to agent.yml (PersistAutoVPN)

IAM on Windows (iam_windows.go):
- allowed_login_groups -> net localgroup; ssh_keys -> per-profile
  authorized_keys + %ProgramData%\ssh\administrators_authorized_keys;
  revoke_users -> helper logs off all of the user's WTS sessions;
  sudo_rules logged as no direct equivalent

Tray enrichment:
- Open Config (opens agent.yml), Clear enrollment (re-enroll) menu items
- set_auto_vpn persists; vpn_connect/vpn_disconnect/reinit/open_config commands
  handled by the daemon (tray_server.go)

Packaging & release:
- installer/windows/installer.iss: fully-offline Inno Setup bundle (agent,
  tray, helper, vendor-signed WireGuard MSI, OpenCredential CP, VC++ redist),
  /SILENT /SERVER_URL /JOIN_KEY parameters, SYSTEM service + HKLM Run tray
  autostart, Users-writable %ProgramData%\Theta42 for the IPC socket
- .github/workflows/build-windows.yml: build + test, pinned vendor downloads,
  ISCC compile, Azure Trusted Signing (OIDC), SHA256SUMS, GH release attach,
  optional SSO resource publish
- agent.yml.example documents auto_vpn, wireguard, service_name,
  desktop_helper, public_ip_detect

Tests:
- wireguard_apply/remove dispatch (allowed + capability-denied), PersistAutoVPN,
  ClearEnrollment; dispatch tests pin linuxPlatformOps with a temp WireGuard conf
- end-to-end verified against a local mock SSO on Windows: join-key enrollment
  (token persisted, join key blanked), discovery/telemetry pushed, signed
  arbitrary_bash verified + executed via powershell -EncodedCommand; tray IPC
  socket binds %ProgramData%\Theta42; LDAP byte-pump binds 127.0.0.1:389;
  helper update swap verified

Rebuilds all tracked dist binaries (v2.1.0).
2026-08-09 17:49:40 -07:00
wmantly 4a619f7adc feat(windows): platform ops, service wrapper, helper, and air-gap paths
First Windows parity milestone (DESIGN-WINDOWS.md §13 build order item 1).

- Add a PlatformOps abstraction so command dispatch is OS-neutral:
  - linuxPlatformOps keeps today's systemctl/journalctl/bash behavior (deliberately
    untagged so shared dispatch tests run on Windows CI)
  - windowsPlatformOps maps reboot/shutdown to shutdown.exe, service control to
    sc.exe (stop+start for restart), fetch_logs to Get-WinEvent, arbitrary_bash to
    powershell -EncodedCommand (byte-exact under arbitrary quoting), and declines
    configure_ldap (Windows logon goes through OpenCredential)
- Run theta-agent as a Windows service (x/sys/windows/svc): SYSTEM auto-start,
    SCM stop/shutdown handling; CLI install-service/remove-service via svc/mgr
- Add theta-agent-helper (session-0 companion): lock/display_off/logout via
    user32/wtsapi32, and staged self-update (wait for service stop, swap the
    locked exe, sc start)
- Self-update becomes platform-aware: Linux renames over the running binary;
    Windows stages .new and hands the swap to the helper (running exe is locked)
- Platform paths: agent.yml and tray.sock under %ProgramData%\Theta42 (the
    service runs as SYSTEM while the tray runs as the user, so the per-user temp
    dir no longer works for tray IPC); LDAP byte-pump falls back to TCP loopback
- config: service_name, desktop_helper, public_ip_detect (air-gap: skips
    external public-IP lookups in telemetry + home monitor), wireguard block
- cli: platform-aware config path + self-update artifact name + service restart
- tests: dispatch tests pin linuxPlatformOps; 0600 mode assertions gated to
    POSIX so the suite is green on Windows

Rebuilds all tracked dist binaries (v2.1.0).
2026-08-09 17:10:07 -07:00
wmantly 7a6eb84d36 docs(design): add DESIGN-WINDOWS.md for the Windows agent
Covers full Windows parity for the theta-agent: remote operations executors,
Windows service layout, WireGuard mesh client (WS-pushed config), LDAP directory
logins via a vendored OpenCredential credential provider, enriched tray,
fully-offline Inno installer, and GitHub Actions + Azure Trusted Signing build
with the SSO holding all release resources. Optimized for air-gapped deployment.
2026-08-09 16:13:13 -07:00
wmantly 565a171797 fix(tray): support Windows socket paths and always run the tray companion on Windows
The tray IPC socket was hardcoded to /run/theta/tray.sock and /tmp/theta-tray.sock,
which cannot be bound on Windows (they resolve to C:\run\... and C:\tmp\... and need
admin rights). The daemon now binds a Unix socket under the per-user temp dir on
Windows, and the tray companion dials the same path.

The tray also exited immediately on Windows because it checked DISPLAY/WAYLAND_DISPLAY,
which are never set there. That graphical-session guard is now Windows-only; the tray
always runs on Windows.
2026-08-09 16:11:42 -07:00
wmantly e13aa6a15c release(v2.0.1): rebuild binaries with correct version string 2026-08-09 15:35:06 -04:00
wmantly 775878437e release(v2.0.1): fix version CLI reporting, secrets access permissions, and tray companion autostart 2026-08-09 14:46:19 -04:00
wmantly a3a21c3884 docs(changelog): add v2.0.0 release notes (#12) 2026-08-09 00:15:00 -04:00
wmantly a423fec835 feat(telemetry): include logged_users and host_details in periodic stream and detect systemd logind user sessions (#11) 2026-08-09 00:09:23 -04:00
wmantly 37e7ffd25d fix(tray): render full-color badge disk icons from theta42.svg and fix home LAN detection for local servers 2026-08-08 23:29:44 -04:00
wmantly 16cab898ed feat(tray): update tray icons to use rasterized official theta42.svg asset 2026-08-08 23:27:29 -04:00
wmantly c676c658ed feat(tray): desktop tray icon companion with Theta logo, color-coded status, and home LAN detection (#10)
- tray_icons.go / cmd/theta-agent-tray: Renders iconic Theta 42 logo in status colors:
    - 🔴 Red: Not connected to directory
    - 🟡 Yellow: Connected to directory, but not on home LAN
    - 🟢 Green: Connected to directory on home LAN
    - 🔵 Blue: Connected to directory with active WireGuard tunnel
- home_detect.go: Compares agent public IP with home site public IP
- tray_server.go / tray_ipc.go: Unix socket IPC daemon server (/run/theta/tray.sock or /tmp/theta-tray.sock)
- cmd/theta-agent-tray: Desktop GUI binary with system tray menu (Auto-connect toggle, Connect/Disconnect VPN)
- Auto-detects DISPLAY / WAYLAND_DISPLAY environment variables
2026-08-08 23:16:20 -04:00
wmantly 34f6f685fc bump: version v2.0.0 and rebuild multiarch binaries (#9) 2026-08-08 21:33:32 -04:00
wmantly 2a4fb21440 docs: update README to reflect Theta Directory and Theta Suite 2.0 branding (#8) 2026-08-08 21:22:04 -04:00
wmantly efae05686e feat(telemetry): add hostnamectl system details, df -h device partition filtering, who logged-in users fallback, robust desktop controls, multiarch build (#7) 2026-08-08 19:35:43 -04:00
wmantly 57d5dfc174 release: v1.8.0 - Active Logged-in Users, Physical Disks & Desktop Operations 2026-08-08 18:12:16 -04:00
wmantly 4aa233f507 Merge release branch release-v1.7.0 2026-08-08 15:38:53 -04:00
wmantly 48248475c4 release: v1.7.0 - Linux ARM, Windows, and macOS theta-agent binaries 2026-08-08 15:38:53 -04:00
wmantly aa1f85a9d5 feat: release v1.6.0 with get-secret CLI, Zero-Trust LDAP tunnel, auto-updates and service restarts v1.6.0 2026-08-07 23:26:13 -04:00
wmantly 8f0158eb9f Add LDAP byte-pump tunnel, secrets rendering, and IAM engine
See CHANGELOG.md for the full breakdown. Summary:

- ldap_tunnel.go: serves a local unix socket for SSSD/PAM and relays raw
  bytes to the SSO over the existing WSS channel (ldap_tunnel messages);
  the agent never parses LDAP (DESIGN.md §4). Adds safeWriter to
  serialize WebSocket writes now that telemetry, heartbeat, the LDAP
  tunnel, and command responses all share one connection.
- secrets.go: renders local templates ({{ bao "path#key" }} placeholders)
  by fetching node-scoped values from the SSO and writing the target
  atomically at 0600, on a signed render_secrets command (DESIGN.md §5).
  demo/ has minimal bash + Node consumers of the rendered file.
- iam.go: applies signed node IAM pushes -- sudoers.d rules (visudo -c
  validated), SSH AuthorizedKeysCommand keys, /etc/security/access.conf,
  and revocation via sss_cache -E + pkill -u (DESIGN.md §6).
- Capability reporting: the agent's enabled capabilities ride along in
  its discovery frame so the SSO can show them in the Directory.
- DESIGN.md: the v2 protocol design this implements.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 17:12:58 -04:00
wmantly d128807431 Merge pull request #6 from theta42/fix/rebuild-agent-binary
fix: rebuild the prebuilt linux/amd64 binary for join keys (v1.5.1)
v1.5.1
2026-08-06 10:44:41 -04:00
wmantly 7d36318885 fix: rebuild the prebuilt linux/amd64 binary for join keys (v1.5.1)
theta-suite's setup.sh installs the committed theta-agent-linux-amd64
rather than building from source, so a stale binary means the fix in this
repo never reaches the host.

The v1.5.0 binary predated join-key support: setup.sh would write a
join_key into agent.yml that the running agent did not understand, and it
would have looped on "close 4001: Unauthorized" -- the same trap the
v1.3.0 heartbeat fix hit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:43:51 -04:00
wmantly 877e6caffa Merge pull request #5 from theta42/feat/join-key-enrollment
feat: join-key enrollment (v1.5.0, protocol v1.2.0 §1.1)
v1.5.0
2026-08-06 10:40:05 -04:00
wmantly 0a5011cd42 feat: join-key enrollment (v1.5.0, protocol v1.2.0 1.1)
Installing the agent with one key is now all it takes to add a host.

New `join_key` config field, presented while auth_token is empty. The SSO
exchanges it for this agent's own token and the public key it must pin,
both delivered in the config frame; the agent persists them and blanks
the join key. Nothing has to be copied between two machines by hand.

PersistEnrollment rewrites only the credential lines -- line-based rather
than a YAML round-trip -- so operator comments, the capability matrix and
formatting survive. It re-reads afterwards so the credential is live
without a restart, and keeps the file 0600.

The connect URL carries ?hostname= so a self-enrolling host is named
after itself, and the agent refuses to connect at all (with a long
back-off) when it has no credential rather than presenting an empty one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:34:06 -04:00
wmantly dc14274edc Merge pull request #4 from theta42/fix/agent-enrollment-auth
sec: fail-closed verification + server-issued enrollment (v1.4.0)
v1.4.0
2026-08-05 18:55:12 -04:00
wmantly d47d08ecab sec: fail-closed verification + server-issued enrollment (v1.4.0)
Implements protocol v1.2.0.

verifySignature() returned true when no public_key was configured,
logging "skipping signature verification". The SSO installer never wrote
a public_key, so a default install executed reboot, service_restart,
configure_ldap, arbitrary_bash and update_binary UNVERIFIED from anything
that could reach its socket. An agent that cannot verify now refuses.

Canonicalization also disagreed with the server. Go's encoding/json
escapes <, > and & by default; JSON.stringify does not. Any payload
containing them hashed differently on each side and failed verification
-- for arbitrary_bash that is most real scripts (`>` redirection, `&&`).
Now uses json.Encoder with SetEscapeHTML(false), trailing newline
trimmed.

The SSO now rejects tokens it did not issue. Handles its close codes
(4001/4002/4003/4004) and backs off 5 minutes on an enrollment failure
instead of retrying every 5s forever. The connect log no longer prints
the URL, which carried ?token=.

install.sh gains --public-key and warns when none is configured.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 18:43:26 -04:00
wmantly 51750d01ec fix: ship the rebuilt binary with heartbeat_ack fix + test (v1.3.1) (#3)
The prebuilt theta-agent-linux-amd64 was built before the v1.3.0 heartbeat_ack
fix, so the installed agent still logged 'Unknown command type: heartbeat_ack'.
Rebuild it with the fix; add a test asserting heartbeat_ack is silently ignored
(no response, no command, no log).
v1.3.1
2026-08-05 02:28:58 -04:00
wmantly 52379c2434 docs: changelog v1.3.0 v1.3.0 2026-08-04 18:54:43 -04:00
wmantly 48d17e0e9f fix: silently ignore heartbeat_ack instead of logging 'Unknown command type' (v1.3.0) (#2)
The server replies to the agent's own heartbeat with heartbeat_ack; the agent
had no case for it, so it fell through to the unknown-command handler, logged
'Unknown command type: heartbeat_ack' every minute, and answered with a spurious
error response. heartbeats are fire-and-forget acks — nothing to run, nothing to
reply.
2026-08-04 18:54:00 -04:00
wmantly 6500fadafb Merge pull request #1 from theta42/fix/install-sssd-auto-v1.2.1
feat(install): add automatic SSSD and PAM package installation v1.2.1
v1.2.1
2026-08-03 15:02:08 -04:00
wmantly 6348c4c060 feat(install): add automatic SSSD and PAM package installation v1.2.1 2026-08-03 15:01:57 -04:00
wmantly 821ce5f991 release: v1.2.0 - Pure Go update engine, enhanced journal log fetcher, and protocol concurrency fixes v1.2.0 2026-08-03 02:13:11 -04:00
wmantly e2ec7e7234 Add build binary v1.1.0 2026-08-03 02:01:14 -04:00
wmantly 0493076575 Enterprise release: add self-updates, command signing, and telemetry enhancements 2026-08-03 01:59:27 -04:00
wmantly 8a97bc7fbd feat: websocket client and command router v0.1.0 v1.0.0 2026-08-02 01:39:48 -04:00