mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-09-10 17:36:04 +00:00
b6766572df
* feat(navigation): make Compact Launcher the default desktop style Compact Launcher replaces Smart bar as the default desktop navigation style; Classic bar is fully retired (rendering, settings option, and persisted-value migration to Compact). Quick-link capacity increases from 7 to 8, and the recommended default set becomes Home, Fleet, Resources, Security, Update, and Schedules. Quick-link seeding and Reset now use a settled, role-aware eligibility computation distinct from the existing current-context display filtering, so an operator's recommended defaults reflect their role rather than which node happens to be active, and a Reset produces the same result regardless of node context. Also fixes two issues surfaced while touching this code: a disabled quick-link control showing a duplicate tooltip (native title plus the Radix tooltip), and the Navigate launcher panel being unable to scroll at constrained viewport heights (now uses the shared ScrollArea component). The launcher hamburger icon animates into an X on open, respecting Reduced motion. * test(e2e): fix the launcher morph and panel scroll navigation assertions Three assertions in the new navigation specs were wrong against a real browser, all in the tests rather than the product: - The morph check read getComputedStyle().transform, which Tailwind v4 no longer writes: rotate-45 compiles to `rotate:45deg` and the translate utilities to the standalone `translate` property, so both the open and closed reads returned "none". It now snapshots translate and rotate alongside transform. The animation itself was always correct, since Tailwind's transition-transform covers translate, scale, and rotate. - The reduced-motion check assumed motion starts enabled. A fresh install defaults to the Calm visual style, which turns Reduced motion on, so the clamp was already active and the pre-toggle duration assertion could never hold. It now drives the toggle in both directions from a known state. - The panel scroll check asserted a specific overflow measurement, which depends on Radix having applied its available-height variable at read time and on how many destinations the account can reach. It now asserts the property the fix actually guarantees: the ScrollArea viewport owns vertical scrolling while the outer menu only clips, with no horizontal overflow and the panel inside the viewport. Verified by running the spec against live dev servers: 7 passed, twice. * fix(nav): make the Navigate launcher panel actually scroll with the mouse Live QA found that the panel only reached destinations below the fold by keyboard; a real mouse wheel did nothing. The Radix ScrollArea viewport is sized with h-full, and a percentage height only resolves against a containing block with a definite height. The popper content is height:auto clamped by max-height, which is not definite, so nothing sized from it is either, so the viewport fell back to its full content height with no internal overflow, no scrollbar, and no response to wheel input, while an ancestor's overflow-hidden silently clipped everything past the fold. Moves the available-height cap onto the viewport itself and moves the masthead inside the scroll region so the cap needs no masthead-height arithmetic. Verified live: the previous structure measured scrollHeight === clientHeight === 594 with zero wheel movement; the fix measures 646/357 with the wheel reaching the true maximum. Also hardens the panel's e2e coverage, which passed on the broken structure because it asserted only computed overflow properties and never drove a real scroll: adds a keyboard-reach assertion using End rather than ArrowDown (ArrowDown landing on the first item is stock roving focus and proves nothing about scrolling), a genuine mouse-wheel assertion that scrolls to the true bottom rather than assuming one gesture covers the range, and a companion test proving the cap tracks the popper's available height rather than a hardcoded pixel value. Confirmed the rewritten test fails on the previous structure and passes on the fix.
121 lines
9.1 KiB
Plaintext
121 lines
9.1 KiB
Plaintext
---
|
|
title: Push a Shared Environment File to Every Node Running a Stack
|
|
sidebarTitle: Push shared secrets to your fleet
|
|
description: Author one encrypted env-var bundle on the control instance and push it to a stack running on every labeled node, with a diff you can read before anything is written.
|
|
---
|
|
|
|
Say the same `inventory-api` stack runs on two nodes in your fleet, each with its own `.env` file, and you need to rotate `DB_PASSWORD` on both without SSHing into either host or risking one node drifting out of sync with the other. This walks through building an encrypted secret bundle on your control instance, targeting both nodes by a shared label, and pushing it with a preview step that shows exactly what will change before you write anything.
|
|
|
|
By the end, one bundle holds the source of truth for the stack's environment, and a single push keeps every labeled node's `.env` in sync with it.
|
|
|
|
This tutorial covers creating a bundle, targeting nodes by label, and reading the push preview and results. It doesn't cover importing an existing `.env` from a running stack, editing a bundle to a new version, or the full audit and encryption model; see the [Fleet Secrets](/features/fleet-secrets) feature page for those.
|
|
|
|
## Prerequisites
|
|
|
|
- An **admin** account on the instance you're signed into. Fleet Secrets decrypts and writes credentials fleet-wide, so every route requires an administrator; there is no lesser role that can push a bundle.
|
|
- You're working from the **control instance** (the hub), not a remote node. Fleet Secrets is a hub concern: its API routes never proxy to a remote node, so the **Secrets** tab and its data always reflect the instance you're currently signed into.
|
|
- At least two nodes in your fleet (Local plus one enrolled node), each running a stack with the **same name** and declaring an env file via `env_file:` in its compose. This tutorial uses `inventory-api`:
|
|
|
|
```yaml
|
|
services:
|
|
api:
|
|
image: nginx:alpine
|
|
restart: unless-stopped
|
|
ports:
|
|
- "8099:80"
|
|
env_file:
|
|
- .env
|
|
```
|
|
|
|
Deploy this stack on both nodes before you start (an empty `.env` is fine; the push will populate it). A stack whose compose only has an inline `environment:` block won't show up as a push target, since Fleet Secrets writes to a file, not inline compose values.
|
|
- Both nodes carry a **shared label**. This tutorial uses `inventory` on both. Add labels from **Settings → Nodes** if they aren't set yet.
|
|
|
|
<Note>
|
|
Fleet Secrets is available on every Sencho installation; no Admiral requirement.
|
|
</Note>
|
|
|
|
<Steps>
|
|
<Step title="Open Fleet and create a bundle">
|
|
Open **Fleet**, select the **Secrets** tab, and click **New bundle** (or **Create your first bundle** if this is the first one on the instance).
|
|
|
|
<Frame>
|
|
<img src="/images/tutorials/set-up-fleet-secrets/secrets-tab-empty.png" alt="Fleet view with the Secrets tab selected, showing the empty state: heading 'One source of truth for env' and a Create your first bundle button." />
|
|
</Frame>
|
|
|
|
Give the bundle a **Name** (`inventory-api-env` here) and an optional **Description**. Add a `KEY=value` row for each variable the stack needs; click **Add key** for more rows. This tutorial uses `LOG_LEVEL=info`, `REGION=us-east`, and `DB_PASSWORD` set to a placeholder value you'll rotate later. Add a **Change note** describing the initial save.
|
|
|
|
<Frame>
|
|
<img src="/images/tutorials/set-up-fleet-secrets/create-bundle-filled.png" alt="New secret bundle sheet with Name inventory-api-env, Description 'Shared environment for the inventory-api stack', three key rows LOG_LEVEL=info, REGION=us-east, DB_PASSWORD=change-me-2026, and a change note reading 'Initial inventory-api bundle'." />
|
|
</Frame>
|
|
|
|
Click **Save**. The values are encrypted before the ciphertext is written to disk; the plaintext only lives in the editor while you're filling it in.
|
|
</Step>
|
|
<Step title="Confirm the bundle was saved">
|
|
The bundle now appears as a row in the **Secret bundles** table, at version `v1` with a key count matching what you entered.
|
|
|
|
<Frame>
|
|
<img src="/images/tutorials/set-up-fleet-secrets/bundle-created.png" alt="Secret bundles table showing one row: inventory-api-env, description 'Shared environment for the inventory-api stack', version v1, 3 keys, with edit, send, and delete icons on the right." />
|
|
</Frame>
|
|
</Step>
|
|
<Step title="Target the push by label and stack name">
|
|
Click the **Send** icon on the bundle's row to open the push wizard. On the **Target** tab, toggle **any** or **all** (any is fine when you have one label) and pick the label both nodes carry, `inventory` here. Enter the **Stack name** the bundle should write to, exactly as it appears on each node. The **Env file** dropdown populates from `env_file:` entries a representative target's compose declares; leave it at `.env` unless your stack uses a different filename.
|
|
|
|
<Frame>
|
|
<img src="/images/tutorials/set-up-fleet-secrets/push-target.png" alt="Push wizard Target tab. Target nodes section with the any toggle selected and the inventory label chosen. Target stack section with Stack name inventory-api and Env file dropdown showing .env." />
|
|
</Frame>
|
|
|
|
Click **Preview**. This step only reads; it doesn't write anything yet.
|
|
</Step>
|
|
<Step title="Read the preview before writing anything">
|
|
Each row is a node the label selector matched, with a summary of how many keys will be added, changed, or left unchanged. Expand a row to see the per-key breakdown. Since both nodes started with an empty `.env`, every key shows as added.
|
|
|
|
<Frame>
|
|
<img src="/images/tutorials/set-up-fleet-secrets/push-preview.png" alt="Push wizard Preview tab with two expanded node rows, Local and warehouse-02, each showing +3 added, ~0 changed, ·0 unchanged, and per-key ADDED rows for DB_PASSWORD, LOG_LEVEL, and REGION." />
|
|
</Frame>
|
|
|
|
This is the safety net: the diff shown here is exactly what the next click writes. If a row looks wrong, close the wizard, fix the bundle, and reopen Send to re-run Preview.
|
|
</Step>
|
|
<Step title="Push and confirm the result">
|
|
Click **Push to N nodes**. Sencho writes to each matched node in sequence and reports a per-node outcome on the **Results** tab: a green check with the same added/changed/unchanged counts on success, or a red error with the exact failure string.
|
|
|
|
<Frame>
|
|
<img src="/images/tutorials/set-up-fleet-secrets/push-results.png" alt="Push wizard Results tab with two green success rows: Local and warehouse-02, each showing +3 ~0 ·0." />
|
|
</Frame>
|
|
</Step>
|
|
</Steps>
|
|
|
|
## Verify it worked
|
|
|
|
Check from two independent surfaces so you're not trusting a single UI element.
|
|
|
|
**The Results tab itself**, shown above: both nodes report success with the counts matching the bundle's key count.
|
|
|
|
**The Audit Log.** Open the navigation launcher and, under **Security & review**, pick **Audit** (Smart bar: **More → Audit**). Reading newest first, the top entry reads `pushed secret: 1` and the one just below it `previewed secret push: 1`, both attributed to the account that ran the push.
|
|
|
|
<Frame>
|
|
<img src="/images/tutorials/set-up-fleet-secrets/audit-log-push.png" alt="Audit log showing two recent entries: 'admin pushed secret: 1' and 'admin previewed secret push: 1', each with a timestamp, node, and 200 status." />
|
|
</Frame>
|
|
|
|
If you have shell access to either node, `cat`-ing the stack's `.env` file is a third way to confirm the write landed, but the two UI surfaces above are enough for day-to-day verification.
|
|
|
|
## If something goes wrong
|
|
|
|
**A node lands in the failed column.** The most common first-time cause is the **Stack name** not matching exactly what's deployed on every target. Retarget the same bundle at a name with a typo, `inventory-apy` instead of `inventory-api`, and Preview still succeeds (it reads an empty, nonexistent file as "nothing set yet" and shows every key as added), but the actual push fails on both nodes: the local node returns an `ENOENT` filesystem error, and the proxied node returns `No env file exists for this stack` over HTTP.
|
|
|
|
<Frame>
|
|
<img src="/images/tutorials/set-up-fleet-secrets/push-failed-typo.png" alt="Push wizard Results tab with two red failure rows: Local showing an ENOENT no such file or directory error, and warehouse-02 showing 'failed to write env (HTTP 404: No env file exists for this stack)'." />
|
|
</Frame>
|
|
|
|
Preview cannot catch this class of mistake because it treats an unreadable target as an empty file rather than a missing stack. Fix the stack name on the Target tab and re-run Preview; a failed push is safe to retry immediately, since the overlay write is idempotent. See [Fleet Secrets · Troubleshooting](/features/fleet-secrets#troubleshooting) for the other common causes, including a stack whose compose doesn't declare the chosen env file at all.
|
|
|
|
## Related
|
|
|
|
<CardGroup cols={2}>
|
|
<Card title="Fleet Secrets" icon="key-round" href="/features/fleet-secrets">
|
|
Versioning, the import-from-stack flow, concurrency and lifecycle behavior, and the full audit trail.
|
|
</Card>
|
|
<Card title="Enroll a Remote Node" icon="server" href="/tutorials/enroll-a-remote-node">
|
|
Bring a second node into your fleet if you don't have one to label yet.
|
|
</Card>
|
|
</CardGroup>
|