diff --git a/README.md b/README.md index 09ce0cd..a233273 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ flowchart TD | `vpn-clients` | OpenVPN 3, WireGuard, Tunnelblick (macOS) | | `vm` | VM guest optimisations (QEMU/SPICE agents) | | `power-profile` | Sleep/idle/lid policy. `always-on` (default) or `vm`, via `-e power_profile=` | -| `arcane` | Arcane container UI, localhost-only. Off unless `-e soe_arcane_enabled=true` | +| `arcane` | Arcane container UI, localhost-only. Off unless `-e soe_arcane_enabled=true`. Add `-e soe_arcane_long_session=true` for a year-long login | | `local-services` | Persistent local ClickHouse + Redpanda for spikes. Off unless `-e soe_local_services_enabled=true` | ### Remote Login is not Desktop Sharing @@ -175,7 +175,7 @@ reports success -- the settings simply land where nobody sees them. - `contributor`: hyperi-ci and the tools its checks drive (semgrep, alint), gitleaks, trivy, hadolint, pip-audit, ansible-lint, pre-commit, act, and git-scrub for rewriting AI residue or a leaked secret out of git history -- gitleaks scans FULL history, so a secret removed from HEAD still fails the gate. macbash is here too: it flags the GNU-only bash constructs that break on macOS, and CONTRIBUTING.md asks for it on every shell change - `soe` / `soe-gui`: HyperI org policy: VPN clients, Claude Code, Slack, LibreOffice, RDP client, telemetry-disable, auto-updates, GNOME taskbar - `power-profile` (off by default, and deliberately not in `soe`): sleep, idle and lid policy, selected per machine. `always-on` (the default profile) never idle-suspends on mains power and does not sleep when the lid shuts -- for a repurposed laptop doing build work, or a desktop that has to answer ssh. `vm` never sleeps or suspends at all, for an unattended RDP guest that nobody can walk over and wake. Battery behaviour stays stock under `always-on`, because a machine that will not sleep in a bag cooks itself. Profiles are data files, so adding one is adding a file -- see [roles/power-profile/README.md](ansible/roles/power-profile/README.md) -- `arcane` (off by default): [Arcane](https://getarcane.app), a web UI for the containers on the box. Enable it with `-e soe_arcane_enabled=true` and you get a daemon on `http://localhost:3552` that comes back after a reboot and keeps itself updated. Works against docker-ce on Linux and colima on macOS. Bound to loopback because it holds the Docker socket, so whatever reaches that port owns the machine. Login is whatever Arcane seeds -- `arcane` / `arcane-admin` as upstream documents it. The role sets neither, and only clears the forced first-login password prompt, which it does by re-submitting that seeded password so the credentials stay unchanged. That needs the password policy relaxed to `basic` (`soe_arcane_password_policy`), because upstream's default `strong` policy rejects its own seeded password. There is still a login -- auto-login sits behind a `buildables` Go build tag that no published image is compiled with, so zero-auth is not available without building your own image +- `arcane` (off by default): [Arcane](https://getarcane.app), a web UI for the containers on the box. Enable it with `-e soe_arcane_enabled=true` and you get a daemon on `http://localhost:3552` that comes back after a reboot and keeps itself updated. Works against docker-ce on Linux and colima on macOS. Bound to loopback because it holds the Docker socket, so whatever reaches that port owns the machine. Login is whatever Arcane seeds -- `arcane` / `arcane-admin` as upstream documents it. The role sets neither, and only clears the forced first-login password prompt, which it does by re-submitting that seeded password so the credentials stay unchanged. That needs the password policy relaxed to `basic` (`soe_arcane_password_policy`), because upstream's default `strong` policy rejects its own seeded password. There is still a login -- auto-login sits behind a `buildables` Go build tag that no published image is compiled with, so zero-auth is not available without building your own image. The login lasts about a day by default, which is a prompt every morning on a dev box -- `-e soe_arcane_long_session=true` stretches it to a year from each login. It is off by default because Arcane holds the Docker socket, and it takes one log-out and log-in to take effect, since the session expiry is stamped at login - `local-services` (off by default): a persistent local ClickHouse and Redpanda for ad-hoc work -- somewhere to poke at a query or hand-feed a topic without waiting for a suite to build. Enable with `-e soe_local_services_enabled=true`. Deployed **stopped**: `restart: no`, so a reboot leaves them down and they cost nothing until `local-services up`, which pulls latest and takes seconds. Both capped at 1GB and bound to loopback. They are spike instances -- integration and e2e suites create and tear down their own containers, because a shared daemon makes a suite non-hermetic and order-dependent **Desktop UI** (`winlike` or `maclike` tag): GNOME extensions, a transparent taskbar (winlike) or a dock (maclike). diff --git a/ansible/roles/soe/defaults/main.yml b/ansible/roles/soe/defaults/main.yml index 6e0d9d1..173eea8 100644 --- a/ansible/roles/soe/defaults/main.yml +++ b/ansible/roles/soe/defaults/main.yml @@ -49,6 +49,25 @@ soe_arcane_timezone: UTC # says -- the request schema rejects anything shorter before the policy is read. soe_arcane_password_policy: basic +# Arcane's default login lasts about a day. Two knobs move that and BOTH are +# needed -- either alone still logs you out. +# +# soe_arcane_session_timeout_minutes sets the access token's life, and the +# access-token cookie is the only thing that survives a browser restart. The +# server clamps it to 15..525600 minutes and has no never-expire value, so a +# year from each login is the practical ceiling. +# +# soe_arcane_refresh_expiry stamps the session row's expiry at login and is +# never extended on refresh, so leaving it at the image's 168h means every +# request 401s at day seven whatever the timeout says. +# +# Off by default: Arcane holds the Docker socket, so a year-long cookie is a +# per-machine decision. Both settings stay ABSENT while the flag is off, rather +# than being written at a default, so a value set by hand in the UI survives. +soe_arcane_long_session: false +soe_arcane_session_timeout_minutes: 525600 +soe_arcane_refresh_expiry: 87600h + # ============================================================================ # Local services -- persistent ClickHouse + Redpanda for ad-hoc work, OPT-IN. # ============================================================================ diff --git a/ansible/roles/soe/tasks/arcane.yml b/ansible/roles/soe/tasks/arcane.yml index 9eedc33..8f70645 100644 --- a/ansible/roles/soe/tasks/arcane.yml +++ b/ansible/roles/soe/tasks/arcane.yml @@ -234,17 +234,22 @@ | map(attribute='value'))) }} when: not ansible_check_mode - - name: Work out the auto-update settings this host should have + # authSessionTimeout is added only when the long session is opted into, so a + # run with the flag off leaves whatever is there rather than stamping a + # default over a value set by hand in the UI. + - name: Work out the settings this host should have # noqa: var-naming[no-role-prefix] -- soe_ IS the role prefix here ansible.builtin.set_fact: - soe_arcane_settings_want: - autoUpdate: "{{ soe_arcane_auto_update | bool | lower }}" - autoUpdateExcludedContainers: "{{ soe_arcane_auto_update_exclude | join(',') }}" + soe_arcane_settings_want: >- + {{ {'autoUpdate': soe_arcane_auto_update | bool | lower, + 'autoUpdateExcludedContainers': soe_arcane_auto_update_exclude | join(',')} + | combine({'authSessionTimeout': soe_arcane_session_timeout_minutes | string} + if soe_arcane_long_session | bool else {}) }} - # `autoUpdate` is a database setting rather than an environment override, so - # asserting it needs the API. Values are strings upstream, not booleans. + # These are database settings rather than environment overrides, so asserting + # them needs the API. Values are strings upstream, not booleans or numbers. # Only PUT on a real difference, otherwise every run reports changed. - - name: Assert Arcane's auto-update settings + - name: Assert Arcane's managed settings ansible.builtin.uri: url: "http://127.0.0.1:{{ soe_arcane_port }}/api/environments/0/settings" method: PUT @@ -273,6 +278,12 @@ There is still a login: auto-login sits behind a `buildables` Go build tag that no published image is compiled with. + {% if soe_arcane_long_session %} + Long sessions are on ({{ soe_arcane_session_timeout_minutes }} minutes). + Log out and back in once -- the session expiry is stamped at login, so + the session you are already in keeps its old one. + {% endif %} + Stack: {{ soe_arcane_dir }} Updates: hyperi-update pulls and recreates it; Arcane's own updater keeps the containers it manages current. diff --git a/ansible/roles/soe/templates/arcane.env.j2 b/ansible/roles/soe/templates/arcane.env.j2 index 94269a8..de6c30a 100644 --- a/ansible/roles/soe/templates/arcane.env.j2 +++ b/ansible/roles/soe/templates/arcane.env.j2 @@ -9,3 +9,8 @@ TZ={{ soe_arcane_timezone }} ENCRYPTION_KEY={{ soe_arcane_encryption_key }} JWT_SECRET={{ soe_arcane_jwt_secret }} ADMIN_STATIC_API_KEY={{ soe_arcane_admin_api_key }} +{% if soe_arcane_long_session %} +# Only present when soe_arcane_long_session is on. Absent, the image's own 168h +# applies and every session dies at day seven. +JWT_REFRESH_EXPIRY={{ soe_arcane_refresh_expiry }} +{% endif %} diff --git a/docs/install-matrix.md b/docs/install-matrix.md index 5b86af7..fc979da 100644 --- a/docs/install-matrix.md +++ b/docs/install-matrix.md @@ -343,7 +343,7 @@ hyperi-ci. | LibreOffice (org office suite) | Linux | distro repo | | Nemo, GNOME extensions (gext), fonts | Linux | distro / uv-tool / vendored | | colima + Apple `container` (macOS only) | macOS | brew / github-binary | -| Arcane container UI (opt-in `soe_arcane_enabled`) | all | container image | +| Arcane container UI (opt-in `soe_arcane_enabled`; `soe_arcane_long_session` for a year-long login) | all | container image | | Local ClickHouse + Redpanda (opt-in `soe_local_services_enabled`) | all | container image | | removals / update_command / admin-scripts (opt-in `never`, on for soe) | Linux | tombstones + scripts | diff --git a/install.sh b/install.sh index acaab66..b833bc5 100755 --- a/install.sh +++ b/install.sh @@ -203,7 +203,8 @@ HyperI SOE (soe, soe-gui) - org policy, includes everything above: forgejo/codeberg tea (Forgejo/Gitea CLI) colima macOS container daemon + Apple container (macOS only) arcane Container management UI, localhost-only (OPT-IN: - -e soe_arcane_enabled=true) + -e soe_arcane_enabled=true; add + -e soe_arcane_long_session=true for a year-long login) local-services Persistent local ClickHouse + Redpanda for spikes, deployed stopped (OPT-IN: -e soe_local_services_enabled=true)