Commit Graph

57 Commits

Author SHA1 Message Date
bc65b9157d refactor: SDK images and secrets modules with submodule splits
Split images.rs (1809L) into mod.rs + builders.rs (per-service build
functions). Split secrets.rs (1727L) into mod.rs + seeding.rs (KV
get_or_create, seed_openbao) + db_engine.rs (PostgreSQL static roles).
Moves BuildTarget enum from cli.rs into images/mod.rs with conditional
clap::ValueEnum derive behind the "cli" feature.
2026-03-21 14:37:47 +00:00
8e51e0b3ae refactor: SDK kube, openbao, and tools modules
Move kube (client, apply, exec, secrets, kustomize_build) into kube/
submodule with tools.rs as a child. Move openbao BaoClient into
openbao/ submodule.
2026-03-21 14:35:43 +00:00
b92700d363 refactor: SDK core modules — error, config, output, constants
Move foundational modules into sunbeam-sdk. All crate-internal references
remain unchanged since these are sibling modules within the SDK crate.
2026-03-21 14:34:23 +00:00
2ffedb95cb refactor: workspace scaffolding — sunbeam-sdk + sunbeam binary crate
Convert the single binary crate into a Cargo workspace with two members:
sunbeam-sdk (library) and sunbeam (thin binary). Moves build.rs to the
SDK with adjusted .git/HEAD path for the nested layout.
2026-03-21 14:34:15 +00:00
b6daf608af chore: suppress dead_code warning on exit code constants 2026-03-20 21:33:00 +00:00
b92c6ad18c feat: Python upstream — onboard/offboard, mailbox, Projects, --no-cache
Python changes that were ported to Rust in preceding commits:
- User onboard/offboard with mailbox + Projects provisioning
- Welcome email with job title/department
- --no-cache build flag
- Date validation, apply confirmation, build targets
2026-03-20 21:32:23 +00:00
8d6e815a91 feat: --no-cache build flag and Sol build target
- Add --no-cache flag to sunbeam build (passes --no-cache to buildctl)
- Add Sol (virtual librarian) as a build target
- Wire no_cache through all build functions and dispatch
2026-03-20 21:31:42 +00:00
f75f61f238 feat: user provisioning — mailbox, Projects, welcome email
Onboarding now provisions app-level accounts:
- create_mailbox: Django ORM via kubectl exec into messages-backend
- setup_projects_user: knex.js via kubectl exec into projects pod
- Welcome email includes job title and department when provided

Offboarding cleans up:
- delete_mailbox: removes mailbox + Django user
- cleanup_projects_user: soft-deletes Planka user + memberships

All provisioning is best-effort (warns on failure, doesn't block).
2026-03-20 21:30:27 +00:00
c6aa1bd8ce feat: complete pm subcommands with board discovery and user resolution
Planka:
- Board discovery via GET /api/projects (no hardcoded IDs)
- String IDs (snowflake) throughout — TicketRef::Planka holds String
- Create auto-discovers first board/list, or matches --target by name
- Close finds "Done"/"Closed" list and moves card automatically
- Assign resolves users via search, supports "me" for self-assign
- Ticket IDs use p:/g: short prefixes

Gitea:
- Assign uses PATCH on issue (not POST /assignees which needs collaborator)
- Create requires --target org/repo

All pm subcommands tested against live Planka + Gitea instances.
2026-03-20 21:16:55 +00:00
ffc0fe917b feat: split auth into sso/git, Planka token exchange, board discovery
Auth:
- sunbeam auth login runs SSO (Hydra OIDC) then Git (Gitea PAT)
- SSO callback auto-redirects browser to Gitea token page
- sunbeam auth sso / sunbeam auth git for individual flows
- Gitea PAT verified against API before saving

Planka:
- Token exchange via /api/access-tokens/exchange-using-token endpoint
- Board discovery via GET /api/projects
- String IDs (snowflake) handled throughout

Config:
- kubectl-style contexts: --context flag > current-context > "local"
- Removed --env flag
- Per-domain auth token storage
2026-03-20 19:25:10 +00:00
ded0ab442e refactor: remove --env flag, use --context like kubectl
Context resolution: --context flag > current-context from config > "local".
No more production/local distinction in the CLI flags — the context
determines everything (domain, kube-context, ssh-host, infra-dir).

Remove Env enum entirely. Production detection is now "context has ssh-host".
2026-03-20 15:23:54 +00:00
88b02acdd1 feat: kubectl-style contexts with per-domain auth tokens
Config now supports named contexts (like kubectl), each bundling
domain, kube-context, ssh-host, infra-dir, and acme-email. Legacy
flat config auto-migrates to a "production" context on load.

- sunbeam config set --domain sunbeam.pt --host user@server
- sunbeam config use-context production
- sunbeam config get (shows all contexts)

Auth tokens stored per-domain (~/.local/share/sunbeam/auth/{domain}.json)
so local and production don't clobber each other. pm and auth commands
read domain from active context instead of K8s cluster discovery.
2026-03-20 15:17:57 +00:00
3a5e1c62ba fix: use predictable client_id via pre-seeded K8s secret
Pre-create oidc-sunbeam-cli secret with CLIENT_ID=sunbeam-cli before
hydra-maester reconciles. No cluster access needed at login time.
2026-03-20 15:08:59 +00:00
1029ff0747 fix: auth login UX — timeout, Ctrl+C, suppress K8s error, center HTML
- 5-minute timeout on callback wait (Ctrl+C now works)
- Skip K8s client_id lookup when no cluster configured (removes noisy ERROR)
- Center the success page HTML to match Sunbeam Studios branding
2026-03-20 14:31:59 +00:00
43b5a4eef9 fix: URL-encode scope parameter with %20 instead of + 2026-03-20 14:30:31 +00:00
7fab2a7f3c fix: auth login domain resolution with --domain flag
Domain resolves from: --domain flag > cached token > config
production_host > cluster discovery. Clear error when none available.
2026-03-20 14:29:08 +00:00
184ad85c60 fix: install rustls ring crypto provider at startup
Rustls 0.23 requires an explicit CryptoProvider. Enable the ring
feature and call install_default() before any TLS operations.
2026-03-20 14:15:16 +00:00
5bdb78933f feat: unified project management across Planka and Gitea
New src/pm.rs module with sunbeam pm subcommand:
- Planka client: cards, boards, lists, comments, assignments
  via OIDC token exchange for Planka JWT
- Gitea client: issues, comments, labels, milestones
  via OAuth2 Bearer token
- Unified Ticket type with p:/g: ID prefixes
- pm list: parallel fetch from both sources, merged display
- pm show/create/comment/close/assign across both systems
- Auth via crate::auth::get_token() (Hydra OAuth2)
2026-03-20 14:11:16 +00:00
d4421d3e29 feat: OAuth2 CLI authentication with PKCE and token caching
New src/auth.rs module:
- Authorization Code + PKCE flow via localhost redirect
- OIDC discovery from Hydra well-known endpoint
- Browser-based login (opens system browser automatically)
- Token caching at ~/.local/share/sunbeam/auth.json (0600 perms)
- Automatic refresh when access token expires (refresh valid 7 days)
- get_token() for use by other modules (pm, etc.)
- cmd_auth_login/logout/status subcommands
2026-03-20 14:10:37 +00:00
aad469e9c6 fix: stdin password, port-forward retry, seed advisory lock
- set-password reads from stdin when password arg omitted
- Port-forward proxy retries on pod restart instead of failing
- cmd_seed acquires PID-based advisory lockfile to prevent concurrent runs
2026-03-20 13:37:33 +00:00
dff4588e52 fix: employee ID pagination, add async tests
- next_employee_id now paginates through all identities (was limited to 200)
- Add #[tokio::test] tests: ensure_tunnel noop, BaoClient connection error,
  check_update_background returns quickly when forge URL empty
2026-03-20 13:37:25 +00:00
019c73e300 fix: S3 auth signature tested against AWS reference vector
Refactor s3_auth_headers into deterministic s3_auth_headers_at that
accepts a timestamp. Add test with AWS example credentials and fixed
date verifying canonical request, string-to-sign, and final signature.
2026-03-20 13:37:17 +00:00
e95ee4f377 fix: rewrite users.rs to fully async (was blocking tokio runtime)
Replace all blocking I/O with async equivalents:
- tokio::process::Command instead of std::process::Command
- tokio::time::sleep instead of std::thread::sleep
- reqwest::Client (async) instead of reqwest::blocking::Client
- All helper functions (api, find_identity, generate_recovery, etc.) now async
- PortForward::Drop uses start_kill() (sync SIGKILL) for cleanup
- send_welcome_email wrapped in spawn_blocking for lettre sync transport
2026-03-20 13:31:45 +00:00
24e98b4e7d fix: CNPG readiness, DKIM SPKI format, kv_patch, container name
- Check CNPG Cluster CRD status.phase instead of pod Running phase
- DKIM public key: use SPKI format (BEGIN PUBLIC KEY) matching Python
- Use kv_patch instead of kv_put for dirty paths (preserves external fields)
- Vault KV only written when password is newly generated
- Gitea exec passes container name Some("gitea")
- Fix openbao comment (400 not 409)
2026-03-20 13:29:59 +00:00
6ec0666aa1 fix: SSH tunnel leak, cmd_bao injection, discovery cache, DNS async
- Store SSH tunnel child in static Mutex (was dropped immediately)
- cmd_bao: use env(1) for VAULT_TOKEN instead of sh -c (no shell injection)
- Cache API discovery across kube_apply documents (was per-doc roundtrip)
- Replace blocking ToSocketAddrs with tokio::net::lookup_host
- Remove double YAML->JSON->string->JSON serialization in kube_apply
- ResultExt::ctx now preserves all SunbeamError variants
2026-03-20 13:29:51 +00:00
bcfb443757 refactor: deduplicate constants, fix secret key mismatch, add VSS pruning
- New src/constants.rs: single source for MANAGED_NS (includes monitoring)
  and GITEA_ADMIN_USER, imported by all modules that previously had copies
- Fix checks.rs reading wrong key names from gitea-admin-credentials secret
- Add VaultStaticSecret pruning in pre_apply_cleanup (H1)
- Fix cert_manager_present check (was always true after canonicalize)
- Add warnings for silent failures in pre_apply_cleanup
- Fix os_api dead variable assignment
- Set TLS private key permissions to 0600
- Redact Gitea admin password in print_urls
2026-03-20 13:29:35 +00:00
503e407243 feat: implement OpenSearch ML setup and model_id injection
ensure_opensearch_ml: cluster settings, model registration/deployment
(all-mpnet-base-v2), ingest + search pipelines for hybrid BM25+neural.

inject_opensearch_model_id: reads model_id from ingest pipeline,
writes to matrix/opensearch-ml-config ConfigMap.

os_api helper: kube exec curl inside opensearch pod.
2026-03-20 13:16:00 +00:00
bc5eeaae6e feat: implement secrets.rs with OpenBao HTTP API
Full cmd_seed implementation using openbao::BaoClient:
- OpenBao init/unseal via HTTP API (no kubectl exec)
- KV v2 seeding with get_or_create pattern and dirty-path tracking
- Kubernetes auth method + VSO policy configuration
- Database secrets engine with vault PG user and static roles
- DKIM key generation via rsa + pkcs8 crates
- Kratos admin identity seeding via port-forward + reqwest

cmd_verify: VSO E2E test with test sentinel, sync poll, cleanup.
2026-03-20 13:15:53 +00:00
7fd8874d99 refactor: migrate all modules from anyhow to SunbeamError
Replace anyhow::{bail, Context, Result} with crate::error::{Result,
SunbeamError, ResultExt} across all modules. Each module uses the
appropriate error variant (Kube, Secrets, Build, Identity, etc).
2026-03-20 13:15:45 +00:00
cc0b6a833e refactor: add thiserror error tree and tracing logging
SunbeamError enum with typed variants (Kube, Config, Network, Secrets,
Build, Identity, ExternalTool, Io, Json, Yaml, Other) each mapping to
a process exit code. ResultExt trait replaces anyhow's .context().

main.rs initializes tracing-subscriber with RUST_LOG env filter and
routes all errors to exit codes via SunbeamError::exit_code().

Removes anyhow dependency.
2026-03-20 13:15:26 +00:00
ec235685bf feat: Phase 2 feature modules + comprehensive test suite (142 tests)
services.rs:
- Pod status with unicode icons, grouped by namespace
- VSO sync status (VaultStaticSecret/VaultDynamicSecret via kube-rs DynamicObject)
- Log streaming via kube-rs log_stream + futures::AsyncBufReadExt
- Pod get in YAML/JSON format
- Rollout restart with namespace/service filtering

checks.rs:
- 11 health check functions (gitea, postgres, valkey, openbao, seaweedfs, kratos, hydra, people, livekit)
- AWS4-HMAC-SHA256 S3 auth header generation using sha2 + hmac
- Concurrent execution via tokio JoinSet
- mkcert root CA trust for local TLS

secrets.rs:
- Stub with cmd_seed/cmd_verify (requires live cluster for full impl)

users.rs:
- All 10 Kratos identity operations via reqwest + kubectl port-forward
- Welcome email via lettre SMTP through port-forwarded postfix
- Employee onboarding with auto-assigned ID, HR metadata
- Offboarding with Kratos + Hydra session revocation

gitea.rs:
- Bootstrap without Lima VM: admin password, org creation, OIDC auth source
- Gitea API via kubectl exec curl

images.rs:
- BuildEnv detection, buildctl build + push via port-forward
- Per-service builders for all 17 build targets
- Deploy rollout, node image pull, uv Dockerfile patching
- Mirror scaffolding (containerd operations marked TODO)

cluster.rs:
- Pure K8s cmd_up: cert-manager, linkerd, rcgen TLS certs, core service wait
- No Lima VM operations

manifests.rs:
- Full cmd_apply: kustomize build, two-pass convergence, ConfigMap restart detection
- Pre-apply cleanup, webhook wait, mkcert CA, tuwunel OAuth2 redirect patch

Test coverage: 142 tests across 14 modules (44 in checks, 27 in cli, 13 in images, 12 in tools, 12 in services, 11 in users, 10 in manifests, 9 in kube, 9 in cluster, 7 in update, 6 in gitea, 4 in openbao, 3 in output, 2 in config).
2026-03-20 12:45:07 +00:00
42c2a74928 feat: Phase 1 foundations — kube-rs client, OpenBao HTTP client, self-update
kube.rs:
- KubeClient with lazy init from kubeconfig + context selection
- SSH tunnel via subprocess (port 2222, forward 16443->6443)
- Server-side apply for multi-document YAML via kube-rs discovery
- Secret get/create, namespace ensure, exec in pod, rollout restart
- Domain discovery from gitea-inline-config secret
- kustomize_build with embedded binary, domain/email/registry substitution
- kubectl and bao CLI passthrough commands

openbao.rs:
- Lightweight Vault/OpenBao HTTP API client using reqwest
- System ops: seal-status, init, unseal
- KV v2: get, put, patch, delete with proper response parsing
- Auth: enable method, write policy, write roles
- Database secrets engine: config, static roles
- Replaces all kubectl exec bao shell commands from Python version

update.rs:
- Self-update from latest mainline commit via Gitea API
- CI artifact download with SHA256 checksum verification
- Atomic self-replace (temp file + rename)
- Background update check with hourly cache (~/.local/share/sunbeam/)
- Enhanced version command with target triple and build date

build.rs:
- Added SUNBEAM_TARGET and SUNBEAM_BUILD_DATE env vars

35 tests pass.
2026-03-20 12:37:02 +00:00
80c67d34cb feat: Rust rewrite scaffolding with embedded kustomize+helm
Phase 0 of Python-to-Rust CLI rewrite:

- Cargo.toml with all dependencies (kube-rs, reqwest, russh, rcgen, lettre, etc.)
- build.rs: downloads kustomize v5.8.1 + helm v4.1.0 at compile time, embeds as bytes, sets SUNBEAM_COMMIT from git
- src/main.rs: tokio main with anyhow error formatting
- src/cli.rs: full clap derive struct tree matching all Python argparse subcommands
- src/config.rs: SunbeamConfig serde struct, load/save ~/.sunbeam.json
- src/output.rs: step/ok/warn/table with exact Python format strings
- src/tools.rs: embedded kustomize+helm extraction to cache dir
- src/kube.rs: parse_target, domain_replace, context management
- src/manifests.rs: filter_by_namespace with full test coverage
- Stub modules for all remaining features (cluster, secrets, images, services, checks, gitea, users, update)

23 tests pass, cargo check clean.
2026-03-20 12:24:21 +00:00
d5b963253b refactor: cross-platform tool downloads, configurable infra dir and ACME email
- Make tool downloads platform-aware (darwin/linux, arm64/amd64)
- Add buildctl to bundled tools
- Add get_infra_dir() with config fallback for REPO_ROOT resolution
- Add ACME email to sunbeam config (set/get)
- Add REGISTRY_HOST_IP substitution in kustomize builds
- Update Kratos admin identity schema to employee
- Fix logs command to use production tunnel and context
2026-03-10 19:37:02 +00:00
c82f15b190 feat: add tuwunel/matrix support with OpenSearch ML post-apply hooks
- Add matrix to MANAGED_NS and tuwunel to restart/build targets
- Add post-apply hooks for matrix namespace:
  - _patch_tuwunel_oauth2_redirect: reads client_id from hydra-maester
    Secret and patches OAuth2Client redirectUris dynamically
  - _inject_opensearch_model_id: reads model_id from ingest pipeline
    and writes to ConfigMap for tuwunel deployment env var injection
- Add post-apply hook for data namespace:
  - _ensure_opensearch_ml: idempotently registers/deploys all-mpnet-base-v2
    (768-dim) model, creates ingest + hybrid search pipelines
- Add tuwunel secrets to OpenBao seed (OIDC, TURN, registration token)
- Refactor secret seeding to only write dirty paths (avoid VSO churn)
- Add ACME email fallback from config when not provided via CLI flag
2026-03-10 19:23:30 +00:00
928323e481 fix(cli): unify proxy build path, fix Gitea password sync
- Collapse proxy build to single path using real Dockerfile via remote buildkitd
- Remove cross-compile and Dockerfile.package codepath
- Fix missing --must-change-password=false in Gitea admin password sync
2026-03-08 20:37:53 +00:00
956a883985 chore: added AGENTS.md file for various models.
Signed-off-by: Sienna Meridian Satterwhite <sienna@r3t.io>
2026-03-07 17:24:09 +00:00
507b4d3fb7 feat(config): add production host and infrastructure directory configuration
- Add usage: sunbeam config [-h] action ...

positional arguments:
  action
    set       Set configuration values
    get       Get current configuration
    clear     Clear configuration

options:
  -h, --help  show this help message and exit subcommand with set/get/clear actions
- Store configuration in  with production_host and infra_directory
- Integrate with production environment detection, prioritizing config over SUNBEAM_SSH_HOST
- Add comprehensive test coverage with 11 new tests
- Update CLI help and error messages for better user experience
2026-03-07 16:08:38 +00:00
cbf5c12980 docs: update repository URLs to use HTTPS remotes for src.sunbeam.pt 2026-03-07 15:01:00 +00:00
133fc98440 docs: add comprehensive README with professional documentation 2026-03-07 14:54:29 +00:00
33d7774f5b chore: added license
Signed-off-by: Sienna Meridian Satterwhite <sienna@r3t.io>
2026-03-07 14:54:17 +00:00
1a977814ad docs: add comprehensive documentation for sunbeam CLI 2026-03-07 14:50:36 +00:00
28c266e662 feat(cli): partial apply with namespace filter
sunbeam apply [namespace] builds the full kustomize overlay (preserving
all image substitutions and patches) then filters the output to only
resources in the given namespace before applying. Cleanup and ConfigMap
restart detection are also scoped to the target namespace.

- manifests.py: _filter_by_namespace(), scoped pre_apply_cleanup()
- cli.py: namespace positional arg for apply; meet added to build choices
- tests: 17 new tests covering filter logic and CLI dispatch
2026-03-06 12:05:19 +00:00
2569978f47 feat(cli): meet build/seed support, production kube tunnel, gitea OIDC bootstrap
- secrets.py: seed secret/meet (django-secret-key, application-jwt-secret-key)
- images.py: add sunbeam build meet (meet-backend + meet-frontend from source)
- kube.py: production SSH tunnel support, domain discovery from cluster, cmd_bao
- gitea.py: configure Hydra as OIDC auth source; mark admin account as private
- services.py: minor VSO sync status and services list fixes
- users.py: add cmd_user_enable
2026-03-06 12:05:10 +00:00
c759f2c014 feat(users): add disable/enable lockout commands; fix table output
- Add cmd_user_disable: disables Kratos identity (state: inactive) and
  revokes all sessions. Provides emergency lockout — user cannot log in
  again; existing Django app sessions expire within SESSION_COOKIE_AGE (1h).

- Add cmd_user_enable: re-enables a previously disabled identity.

- Wire disable/enable as subcommands of 'sunbeam user'.

- Fix cmd_user_list: table() args were swapped and result was not printed.
2026-03-03 18:07:51 +00:00
cb5a290b0c feat: auto-restart deployments on ConfigMap change after sunbeam apply
Snapshot ConfigMap resourceVersions before and after kubectl apply.
For any ConfigMap whose resourceVersion changed, find all Deployments
in the same namespace that mount it as a volume and issue a rollout
restart. Eliminates the need to manually restart pods after editing
ConfigMaps (e.g. services.json, nginx configs).
2026-03-03 16:09:04 +00:00
1a3df1fd8c feat: add sunbeam build integration target
Builds the integration-service Docker image from the sunbeam/ root
context (needs both integration/packages/ for the widget source and
integration-service/ for nginx config and logos), pushes to Gitea,
pre-seeds into k3s containerd, and rolls the deployment.
2026-03-03 16:08:55 +00:00
de12847cf1 feat: add impress image mirroring and docs secret seeding
images.py: extend AMD64_ONLY_IMAGES with the three impress (La Suite Docs)
images — impress-backend, impress-frontend, impress-y-provider. Always pull
the amd64 manifest + layers by digest unconditionally before the blob check;
the prior guard skipped the pull when the index blob was present but layers
were missing, causing the OCI import to fail on arm64 hosts.

secrets.py: add docs KV path (django-secret-key, collaboration-secret) to
_seed_openbao so a fresh sunbeam seed generates all required credentials for
the impress deployment.
2026-03-03 14:23:42 +00:00
14dd685398 feat: add kratos-admin-ui build target and user management commands
- images.py: add 'kratos-admin' build target (deno task build →
  docker buildx → containerd pre-seed → rollout restart)
- secrets.py: seed kratos-admin-ui secrets (cookie, csrf, admin identity);
  fix _seed_kratos_admin_identity to return (recovery_link, recovery_code)
  and print both in cmd_seed output
- users.py: new module with cmd_user_{list,get,create,delete,recover}
  via port-forwarded kratos-admin API
- cli.py: add 'user' verb dispatching to users.py subcommands
- tools.py: minor tool resolution updates
2026-03-03 11:32:09 +00:00
b917aa3ce9 fix: specify -c openbao container in cmd_bao kubectl exec
Without -c, kubectl defaults to the first container (linkerd-proxy),
causing 'bao' commands to fail with 'executable not found'.
2026-03-03 11:31:56 +00:00