mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-26 20:00:08 +00:00
efbd20fed5
* feat(labels): add stack_labels schema and DatabaseService CRUD methods
* feat(labels): add label CRUD, assignment, and bulk action API routes
* feat(labels): add oklch label color palette for light and dark themes
* feat(labels): add LabelPill and LabelDot reusable components
* feat(labels): add LabelAssignPopover component for inline label management
* feat(labels): add label pill bar, label dots, and label assignment to sidebar
* feat(labels): add label filtering and label dots to fleet view
* feat(labels): add label-scoped bulk actions (deploy/stop/restart all)
* docs: add Stack Labels feature documentation
* fix(labels): use context menu sub-menu for label assignment and add settings integration
Replace broken Popover-inside-ContextMenu pattern with native Radix
ContextMenuSub for reliable label toggling on right-click. Wrap
ContextMenuSubContent in a Portal to prevent overflow clipping. Add
"Manage labels..." item that opens Settings directly to Labels section.
Fix close button overlap in LabelsSection header. Add LabelsSection
settings component with full CRUD, assignment counts, and ProGate.
Add initialSection prop to SettingsModal for deep-linking. Include
screenshots for documentation.
* docs: update stack labels documentation with screenshots and corrected instructions
* fix(labels): address security and quality issues from code review
- Add NaN validation on parseInt(req.params.id) in label routes
- Scope updateLabel/deleteLabel by nodeId to prevent cross-node IDOR
- Validate labelIds belong to correct node in setStackLabels
- Add requireAdmin check on bulk action endpoint
- Replace error: any with error: unknown and proper narrowing
- Remove unused Label import from index.ts
- Remove unused isPro prop from LabelsSection
- Add strokeWidth={1.5} to Check icons per design system
* chore: update CHANGELOG with stack labels feature
* feat(nodes): add per-node scheduling and update visibility
Add Schedules and Updates columns to the Nodes table showing active
task counts, next run times, and auto-update status per node. A calendar
action button navigates to filtered schedule/auto-update views.
Backend changes:
- Add node_id to stack_update_status table (migration + unique index)
- Cascade cleanup on node deletion (scheduled_tasks + update status)
- Pre-check target node existence/status before executing scheduled tasks
- New GET /api/nodes/scheduling-summary endpoint
- New GET /api/image-updates/fleet endpoint with 2-minute cache
- Parallelize remote node fetches with Promise.allSettled
- Wrap deleteNode cascade in a transaction
Frontend changes:
- NodeManager: Schedules/Updates columns with summary data fetch
- EditorLayout: sencho-navigate event listener for cross-component nav
- ScheduledOperationsView/AutoUpdatePoliciesView: filterNodeId prop,
filter bar UI, pre-selected node in create dialog
189 lines
8.9 KiB
Plaintext
189 lines
8.9 KiB
Plaintext
---
|
|
title: Multi-Node Management
|
|
description: Connect multiple Sencho instances and manage all your servers from a single dashboard.
|
|
---
|
|
|
|
Sencho's multi-node feature lets you manage Docker Compose stacks on multiple servers - all from the same browser tab. Each server runs its own Sencho instance, and your primary instance acts as a transparent proxy to the others.
|
|
|
|
<Frame>
|
|
<img src="/images/multi-node/node-manager.png" alt="Node Manager showing a local and a remote node, both Online" />
|
|
</Frame>
|
|
|
|
## How it works
|
|
|
|
There is no central server. Each Sencho instance manages its own host independently. When you select a remote node, your browser's API calls are proxied through your local Sencho instance to the remote one, authenticated by a long-lived Bearer token. No SSH. No shared Docker sockets.
|
|
|
|
## The local node
|
|
|
|
Your primary Sencho installation is always listed as **Local**. It is the default node, marked with a star, and cannot be deleted. All operations on the local node run directly against the host's Docker socket.
|
|
|
|
## Adding a remote node
|
|
|
|
### Step 1: Generate a token on the remote machine
|
|
|
|
On the **remote** Sencho instance (the server you want to add), open **Settings → Nodes** and click **Generate Token**. Copy the generated token - you'll only see it once.
|
|
|
|
<Note>
|
|
The token is a long-lived JWT scoped to `node_proxy`. Anyone with this token can fully control that Sencho instance, so treat it like a password.
|
|
</Note>
|
|
|
|
### Step 2: Add the node on your primary instance
|
|
|
|
On your **primary** Sencho instance, open **Settings → Nodes** and click **+ Add Node**. Fill in:
|
|
|
|
<Frame>
|
|
<img src="/images/multi-node/add-node-form.png" alt="Add Node form with Name, Type, API URL, and Token fields" />
|
|
</Frame>
|
|
|
|
| Field | Description |
|
|
|-------|-------------|
|
|
| **Name** | A display name (e.g. `prod-server`, `media-box`) |
|
|
| **Type** | Select **Remote** |
|
|
| **Sencho API URL** | The full HTTP/HTTPS URL of the remote instance (e.g. `http://192.168.1.20:3001`) |
|
|
| **API Token** | The token you generated in Step 1 |
|
|
|
|
Click **Create**. Sencho immediately tests the connection and shows the result.
|
|
|
|
### Step 3: Verify connectivity
|
|
|
|
A successful connection shows the remote node as **Online** with a green badge. If it shows **Offline** or **Unknown**, check:
|
|
- The remote Sencho instance is running and reachable from your primary host
|
|
- The API URL is correct (include the port if non-standard)
|
|
- The token was copied correctly without extra whitespace
|
|
|
|
Click the **wifi icon** (test connection) on any node row at any time to re-check status.
|
|
|
|
## The 1:1 path rule for remote nodes
|
|
|
|
The Compose directory path matters on remote nodes too. When you register a remote node, its `COMPOSE_DIR` is whatever that remote instance was configured with. Make sure the remote Sencho's `COMPOSE_DIR` follows the [1:1 path rule](/getting-started/configuration#compose-directory-the-11-path-rule) on that remote host.
|
|
|
|
## Switching between nodes
|
|
|
|
The **node switcher** dropdown in the top-left of the sidebar shows the currently active node. Click it to switch to any registered node. All views - dashboard stats, stack list, editor, resources, logs - immediately reflect the selected node.
|
|
|
|
<Frame>
|
|
<img src="/images/multi-node/node-switcher-dropdown.png" alt="Node switcher dropdown expanded showing available nodes" />
|
|
</Frame>
|
|
|
|
Node status indicators:
|
|
|
|
| Indicator | Meaning |
|
|
|-----------|---------|
|
|
| Green dot | Node is reachable and responding |
|
|
| Red dot | Node is unreachable |
|
|
| Gray dot | Status not yet checked |
|
|
|
|
## Per-node scheduling and update indicators
|
|
|
|
The Nodes table surfaces scheduling and update status at a glance for each node:
|
|
|
|
<Frame>
|
|
<img src="/images/per-node-scheduling/nodes-table-overview.png" alt="Nodes table showing Schedules and Updates columns for each node" />
|
|
</Frame>
|
|
|
|
| Column | What it shows |
|
|
|--------|---------------|
|
|
| **Schedules** | Number of active scheduled tasks targeting this node, plus a relative countdown to the next run (e.g. "next 2h") |
|
|
| **Updates** | Whether auto-update policies are enabled ("Auto" badge), and how many stacks have pending image updates (pulsing blue dot with count) |
|
|
|
|
Click the **calendar icon** on any node row to jump directly to the Schedules view filtered to that node. From there you can create, edit, or manage scheduled tasks scoped to the selected node. The filter bar shows which node you're viewing, with a **Clear filter** button to return to the full list.
|
|
|
|
When a node is deleted, all scheduled tasks and update status data associated with it are automatically cleaned up.
|
|
|
|
## Editing and deleting nodes
|
|
|
|
Click the **pencil icon** on any remote node row to edit its name, URL, or token. Click the **trash icon** to remove it. The local node cannot be edited or deleted.
|
|
|
|
## Security
|
|
|
|
### Token security
|
|
|
|
Node tokens are JWTs with a configurable expiry that grant full control over the remote Sencho instance. Treat them like passwords:
|
|
|
|
- **Rotate immediately** if a token is compromised: Settings → Nodes → Generate Token on the remote instance. The old token is invalidated instantly.
|
|
- Tokens have a configurable expiry. Generate a new token on the remote instance to renew access.
|
|
- Tokens are **encrypted at rest** in Sencho's database. Even if the database file is extracted, the tokens cannot be read without the instance's encryption key.
|
|
- Tokens are scoped to `node_proxy` — they cannot access the host console or container exec terminals. Interactive shell access always requires a real browser session on that specific instance.
|
|
|
|
### Transport encryption
|
|
|
|
Sencho delegates transport encryption to your infrastructure rather than implementing TLS at the application layer. This is the same approach used by [Dockge](https://github.com/louislam/dockge) and other self-hosted tools — it avoids certificate management burden while letting you use the encryption layer that best fits your environment.
|
|
|
|
<Warning>
|
|
**Never send node tokens over plain HTTP across the public internet.** A token intercepted in transit grants full control of the remote instance. Always use one of the approaches below when nodes communicate over untrusted networks.
|
|
</Warning>
|
|
|
|
Sencho shows an inline warning when you enter an HTTP URL in the Add Node form as a reminder:
|
|
|
|
<Frame>
|
|
<img src="/images/multi-node/http-warning.png" alt="Add Node form showing an inline warning when an HTTP URL is entered" />
|
|
</Frame>
|
|
|
|
There are three recommended approaches depending on your deployment:
|
|
|
|
#### Private network (LAN or VPC)
|
|
|
|
If all your Sencho instances are on the same local network, VPC, or subnet — HTTP is perfectly fine. The token never leaves the private network, so there is no interception risk.
|
|
|
|
```
|
|
http://192.168.1.50:3000 ← safe on a private LAN
|
|
http://10.0.1.20:3000 ← safe inside a VPC
|
|
```
|
|
|
|
#### VPN tunnel (WireGuard, Tailscale)
|
|
|
|
<Tip>
|
|
This is the simplest approach for connecting nodes across the internet. A VPN encrypts all traffic between your servers at the network layer — no certificate management required.
|
|
</Tip>
|
|
|
|
With a mesh VPN like [Tailscale](https://tailscale.com) or [WireGuard](https://www.wireguard.com/), each server gets a private IP on the VPN. Use those IPs as your Sencho API URLs:
|
|
|
|
```
|
|
http://100.64.0.2:3000 ← Tailscale IP, encrypted by the VPN tunnel
|
|
```
|
|
|
|
All traffic between nodes is encrypted by the VPN — Sencho does not need to do anything additional.
|
|
|
|
#### Reverse proxy (Caddy, Nginx, Traefik)
|
|
|
|
If you prefer TLS termination at each node, place a reverse proxy in front of each Sencho instance. [Caddy](https://caddyserver.com/) is the simplest option — it auto-provisions HTTPS certificates from Let's Encrypt with zero configuration:
|
|
|
|
<CodeGroup>
|
|
```text Caddyfile
|
|
sencho.example.com {
|
|
reverse_proxy localhost:3000
|
|
}
|
|
```
|
|
|
|
```nginx nginx.conf
|
|
server {
|
|
listen 443 ssl;
|
|
server_name sencho.example.com;
|
|
|
|
ssl_certificate /etc/letsencrypt/live/sencho.example.com/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/sencho.example.com/privkey.pem;
|
|
|
|
location / {
|
|
proxy_pass http://localhost:3000;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
}
|
|
}
|
|
```
|
|
</CodeGroup>
|
|
|
|
Then use the HTTPS URL when adding the remote node:
|
|
|
|
```
|
|
https://sencho.example.com ← TLS terminated by Caddy/Nginx
|
|
```
|
|
|
|
### Why Sencho doesn't implement application-layer TLS
|
|
|
|
Some tools auto-generate self-signed TLS certificates between their server and agents (e.g. Portainer's standard agent). In practice, this provides minimal security benefit — the certificates are self-signed with no verification, meaning they don't protect against man-in-the-middle attacks. It is effectively security theater.
|
|
|
|
Sencho takes a deliberate approach: infrastructure-level encryption (VPN, reverse proxy, or private networking) is more robust, easier to manage, and doesn't impose certificate rotation burden on the user. This is the same model used by Dockge, Yacht, and other self-hosted orchestrators.
|