mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-26 11:49:16 +00:00
docs: remediate documentation gaps across quickstart, backup, config, API spec, and operations guides (#330)
- Fix Cyrillic character in quickstart image ref and correct registry to Docker Hub (saelix/sencho) - Correct backup guide WAL references (Sencho uses SQLite default journal mode) - Add SSL/TLS reverse proxy examples for Nginx, Traefik, and new Caddy configuration - Add missing env vars (PORT, DATA_DIR, NODE_ENV, FRONTEND_URL, SSO_LDAP_DISPLAY_NAME) to .env.example - Add upgrade & migration guide documenting automatic schema migrations - Add self-hosting best practices (1:1 path rule, Docker socket security, resource recs) - Add architecture overview (system design, request flow, database schema, multi-node model) - Add development & contributor guide (setup, tests, code style, PR workflow) - Update OpenAPI spec from v0.23.0 to v0.25.3 with Registries and Image Updates endpoints - Update docs.json navigation with all new pages and API groups
This commit is contained in:
@@ -7,6 +7,18 @@ JWT_SECRET=your-secure-jwt-secret-here
|
||||
# Directory containing docker-compose files
|
||||
COMPOSE_DIR=/path/to/your/compose/files
|
||||
|
||||
# HTTP server port (default: 3000)
|
||||
PORT=3000
|
||||
|
||||
# Database and state directory inside the container (default: /app/data)
|
||||
DATA_DIR=/app/data
|
||||
|
||||
# Node environment (set automatically in Docker image; only change for local dev)
|
||||
NODE_ENV=production
|
||||
|
||||
# Frontend URL for CORS in production (leave empty for same-origin setups)
|
||||
FRONTEND_URL=
|
||||
|
||||
# Global API rate limit (requests per minute per IP, production only)
|
||||
API_RATE_LIMIT=100
|
||||
|
||||
@@ -21,6 +33,7 @@ SSO_LDAP_SEARCH_BASE=ou=users,dc=example,dc=com
|
||||
SSO_LDAP_SEARCH_FILTER=(uid={{username}})
|
||||
SSO_LDAP_ADMIN_GROUP_DN=
|
||||
SSO_LDAP_DEFAULT_ROLE=viewer
|
||||
SSO_LDAP_DISPLAY_NAME=LDAP
|
||||
SSO_LDAP_TLS_REJECT_UNAUTHORIZED=true
|
||||
|
||||
# Google OIDC
|
||||
|
||||
@@ -27,6 +27,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Docs
|
||||
|
||||
* **quickstart:** fix Cyrillic character in Docker image reference and correct registry from GHCR to Docker Hub (`saelix/sencho`)
|
||||
* **backup:** correct WAL mode references — Sencho uses SQLite default journal mode, not WAL
|
||||
* **configuration:** add SSL/TLS examples for Nginx and Traefik reverse proxies, add Caddy configuration
|
||||
* **env:** add missing `PORT`, `DATA_DIR`, `NODE_ENV`, `FRONTEND_URL`, `SSO_LDAP_DISPLAY_NAME` to `.env.example`
|
||||
* **operations:** add upgrade & migration guide, self-hosting best practices page
|
||||
* **reference:** add architecture overview and development/contributor guide
|
||||
* **openapi:** update spec from v0.23.0 to v0.25.3, add Registries and Image Updates endpoint groups
|
||||
|
||||
### Fixed
|
||||
|
||||
* **error-handling:** surface silent errors across the codebase — added `console.warn`/`console.error` logging to 22 silent catch blocks across 10 files (services, index.ts, frontend Login). Errors in cleanup, migrations, SSO, fleet snapshots, shutdown, and validation are now visible in logs without changing any control flow. ENOENT guards added to file-system catches to distinguish missing files from permission errors.
|
||||
|
||||
+24
-2
@@ -121,14 +121,18 @@
|
||||
"group": "Reference",
|
||||
"pages": [
|
||||
"reference/settings",
|
||||
"reference/security-advisories"
|
||||
"reference/security-advisories",
|
||||
"reference/architecture",
|
||||
"reference/development"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Operations",
|
||||
"pages": [
|
||||
"operations/troubleshooting",
|
||||
"operations/backup"
|
||||
"operations/backup",
|
||||
"operations/upgrade",
|
||||
"operations/self-hosting"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -233,6 +237,24 @@
|
||||
"GET /api/scheduled-tasks/{id}/runs",
|
||||
"GET /api/scheduled-tasks/{id}/runs/export"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Registries",
|
||||
"pages": [
|
||||
"GET /api/registries",
|
||||
"POST /api/registries",
|
||||
"PUT /api/registries/{id}",
|
||||
"DELETE /api/registries/{id}",
|
||||
"POST /api/registries/{id}/test"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Image Updates",
|
||||
"pages": [
|
||||
"GET /api/image-updates",
|
||||
"POST /api/image-updates/refresh",
|
||||
"GET /api/image-updates/status"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -162,6 +162,41 @@ server {
|
||||
}
|
||||
```
|
||||
|
||||
### Nginx with SSL (Let's Encrypt)
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name sencho.yourdomain.com;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name sencho.yourdomain.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/sencho.yourdomain.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/sencho.yourdomain.com/privkey.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:3000;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
# WebSocket support
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 3600s;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Use [Certbot](https://certbot.eff.org/) to obtain and auto-renew certificates: `certbot --nginx -d sencho.yourdomain.com`.
|
||||
|
||||
### Traefik (Docker labels)
|
||||
|
||||
```yaml
|
||||
@@ -171,10 +206,36 @@ labels:
|
||||
- "traefik.http.services.sencho.loadbalancer.server.port=3000"
|
||||
```
|
||||
|
||||
### Traefik with SSL (Let's Encrypt)
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.sencho.rule=Host(`sencho.yourdomain.com`)"
|
||||
- "traefik.http.routers.sencho.entrypoints=websecure"
|
||||
- "traefik.http.routers.sencho.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.sencho.loadbalancer.server.port=3000"
|
||||
# HTTP to HTTPS redirect
|
||||
- "traefik.http.routers.sencho-http.rule=Host(`sencho.yourdomain.com`)"
|
||||
- "traefik.http.routers.sencho-http.entrypoints=web"
|
||||
- "traefik.http.routers.sencho-http.middlewares=redirect-to-https"
|
||||
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
|
||||
```
|
||||
|
||||
<Note>
|
||||
Traefik handles WebSocket upgrades automatically for HTTP/1.1 backends. No extra configuration needed.
|
||||
</Note>
|
||||
|
||||
### Caddy
|
||||
|
||||
```
|
||||
sencho.yourdomain.com {
|
||||
reverse_proxy localhost:3000
|
||||
}
|
||||
```
|
||||
|
||||
Caddy automatically obtains and renews SSL certificates via Let's Encrypt. WebSocket connections are forwarded without additional configuration.
|
||||
|
||||
## First boot
|
||||
|
||||
After starting Sencho, open it in your browser. If no admin account exists yet, you'll be taken to a setup screen to create one. This only appears once - subsequent visits go directly to the login page.
|
||||
|
||||
@@ -18,7 +18,7 @@ docker run -d \
|
||||
-v /opt/compose:/app/compose \
|
||||
-v sencho_data:/app/data \
|
||||
-e JWT_SECRET=change-me \
|
||||
ghcr.io/ansоcode/sencho:latest
|
||||
saelix/sencho:latest
|
||||
```
|
||||
|
||||
Open `http://localhost:3000` in your browser. On first boot you'll be prompted to create an admin account.
|
||||
|
||||
+296
-1
@@ -1,7 +1,7 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: Sencho API
|
||||
version: 0.23.0
|
||||
version: 0.25.3
|
||||
description: |
|
||||
REST API for Sencho, a self-hosted Docker Compose management dashboard.
|
||||
|
||||
@@ -66,6 +66,10 @@ tags:
|
||||
description: Multi-node fleet overview and snapshots (Pro license required)
|
||||
- name: Scheduled Tasks
|
||||
description: Configure recurring automated operations (Admiral license required)
|
||||
- name: Registries
|
||||
description: Manage private container registry credentials (Admiral license required). These endpoints are only accessible via browser sessions — API tokens receive `SCOPE_DENIED`.
|
||||
- name: Image Updates
|
||||
description: Check for available container image updates across stacks
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
@@ -420,6 +424,69 @@ components:
|
||||
error:
|
||||
type: ["string", "null"]
|
||||
|
||||
Registry:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
example: GitHub Container Registry
|
||||
url:
|
||||
type: string
|
||||
example: ghcr.io
|
||||
type:
|
||||
type: string
|
||||
enum: [dockerhub, ghcr, ecr, custom]
|
||||
username:
|
||||
type: string
|
||||
aws_region:
|
||||
type: ["string", "null"]
|
||||
description: AWS region (only present for ECR registries).
|
||||
created_at:
|
||||
type: integer
|
||||
description: Unix timestamp (seconds).
|
||||
|
||||
RegistryCreate:
|
||||
type: object
|
||||
required: [name, url, type, username, secret]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
maxLength: 100
|
||||
example: GitHub Container Registry
|
||||
url:
|
||||
type: string
|
||||
maxLength: 500
|
||||
example: ghcr.io
|
||||
type:
|
||||
type: string
|
||||
enum: [dockerhub, ghcr, ecr, custom]
|
||||
username:
|
||||
type: string
|
||||
example: my-user
|
||||
secret:
|
||||
type: string
|
||||
description: Password, token, or access key. Write-only — never returned in GET responses.
|
||||
aws_region:
|
||||
type: string
|
||||
description: Required when `type` is `ecr`.
|
||||
example: us-east-1
|
||||
|
||||
ImageUpdateStatus:
|
||||
type: object
|
||||
properties:
|
||||
stack_name:
|
||||
type: string
|
||||
description: Name of the stack.
|
||||
has_updates:
|
||||
type: integer
|
||||
enum: [0, 1]
|
||||
description: Whether any images in this stack have newer versions available.
|
||||
last_checked:
|
||||
type: ["integer", "null"]
|
||||
description: Unix timestamp of the last check.
|
||||
|
||||
responses:
|
||||
Unauthorized:
|
||||
description: Authentication required. Provide a valid Bearer token.
|
||||
@@ -2280,3 +2347,231 @@ paths:
|
||||
$ref: "#/components/responses/NotFound"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
|
||||
# ── Registries ──────────────────────────────────────────
|
||||
/api/registries:
|
||||
get:
|
||||
operationId: listRegistries
|
||||
tags: [Registries]
|
||||
summary: List registries
|
||||
description: |
|
||||
Returns all configured private container registries. Secrets are never included in the response.
|
||||
Requires Admiral license and admin role. API tokens receive `SCOPE_DENIED`.
|
||||
responses:
|
||||
"200":
|
||||
description: Array of registry objects.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Registry"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
post:
|
||||
operationId: createRegistry
|
||||
tags: [Registries]
|
||||
summary: Create registry
|
||||
description: |
|
||||
Adds a new private container registry. The secret is encrypted at rest using AES-256-GCM.
|
||||
Requires Admiral license and admin role. API tokens receive `SCOPE_DENIED`.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/RegistryCreate"
|
||||
responses:
|
||||
"201":
|
||||
description: Registry created.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [id]
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
"400":
|
||||
description: Validation error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
|
||||
/api/registries/{id}:
|
||||
put:
|
||||
operationId: updateRegistry
|
||||
tags: [Registries]
|
||||
summary: Update registry
|
||||
description: |
|
||||
Updates an existing registry. All fields are optional — only provided fields are changed.
|
||||
Requires Admiral license and admin role. API tokens receive `SCOPE_DENIED`.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/idPath"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/RegistryCreate"
|
||||
responses:
|
||||
"200":
|
||||
description: Registry updated.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/SuccessBoolean"
|
||||
"400":
|
||||
description: Validation error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
delete:
|
||||
operationId: deleteRegistry
|
||||
tags: [Registries]
|
||||
summary: Delete registry
|
||||
description: |
|
||||
Removes a registry and its encrypted credentials.
|
||||
Requires Admiral license and admin role. API tokens receive `SCOPE_DENIED`.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/idPath"
|
||||
responses:
|
||||
"200":
|
||||
description: Registry deleted.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/SuccessBoolean"
|
||||
"400":
|
||||
description: Invalid registry ID.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
|
||||
/api/registries/{id}/test:
|
||||
post:
|
||||
operationId: testRegistry
|
||||
tags: [Registries]
|
||||
summary: Test registry connection
|
||||
description: |
|
||||
Tests connectivity and authentication against a configured registry.
|
||||
Requires Admiral license and admin role. API tokens receive `SCOPE_DENIED`.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/idPath"
|
||||
responses:
|
||||
"200":
|
||||
description: Test result.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
success:
|
||||
type: boolean
|
||||
message:
|
||||
type: string
|
||||
"400":
|
||||
description: Invalid registry ID.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
|
||||
# ── Image Updates ───────────────────────────────────────
|
||||
/api/image-updates:
|
||||
get:
|
||||
operationId: getImageUpdates
|
||||
tags: [Image Updates]
|
||||
summary: Get image update status
|
||||
description: Returns the update availability status for all stacks.
|
||||
responses:
|
||||
"200":
|
||||
description: Array of update status objects per stack.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ImageUpdateStatus"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
|
||||
/api/image-updates/refresh:
|
||||
post:
|
||||
operationId: refreshImageUpdates
|
||||
tags: [Image Updates]
|
||||
summary: Trigger image update check
|
||||
description: |
|
||||
Triggers a background check for newer image versions across all stacks.
|
||||
Rate limited to one refresh per 10 minutes. Requires admin role.
|
||||
responses:
|
||||
"200":
|
||||
description: Refresh started.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
success:
|
||||
type: boolean
|
||||
message:
|
||||
type: string
|
||||
example: Image update check started in background.
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"429":
|
||||
description: Rate limited — wait at least 10 minutes between refreshes.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
|
||||
/api/image-updates/status:
|
||||
get:
|
||||
operationId: getImageUpdateCheckStatus
|
||||
tags: [Image Updates]
|
||||
summary: Check if update scan is running
|
||||
description: Returns whether an image update check is currently in progress.
|
||||
responses:
|
||||
"200":
|
||||
description: Check status.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [checking]
|
||||
properties:
|
||||
checking:
|
||||
type: boolean
|
||||
description: "`true` if a scan is currently running."
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
|
||||
@@ -31,7 +31,7 @@ This is your actual application data. It lives entirely outside Sencho and you a
|
||||
### Simple file copy
|
||||
|
||||
```bash
|
||||
# Stop Sencho to ensure the SQLite WAL is flushed (recommended but not strictly required)
|
||||
# Stop Sencho to ensure no active transactions (recommended but not strictly required)
|
||||
docker stop sencho
|
||||
|
||||
# Copy the data directory
|
||||
@@ -44,6 +44,10 @@ cp -r /opt/compose /path/to/backup/compose-$(date +%Y%m%d)
|
||||
docker start sencho
|
||||
```
|
||||
|
||||
<Note>
|
||||
Sencho uses SQLite's default journal mode (not WAL), so you will not see `-wal` or `-shm` sidecar files alongside `sencho.db`. If a `-journal` file exists when you copy, it indicates an interrupted write — SQLite will automatically resolve it the next time the database is opened.
|
||||
</Note>
|
||||
|
||||
### SQLite online backup (without stopping)
|
||||
|
||||
SQLite supports hot backups via its `.backup` command. This is safe to run while Sencho is running:
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
---
|
||||
title: Self-Hosting Best Practices
|
||||
description: Volume mounts, Docker socket security, networking, and resource recommendations for running Sencho in production.
|
||||
---
|
||||
|
||||
## The 1:1 path rule
|
||||
|
||||
This is the most common setup mistake. When Sencho deploys a Docker Compose stack, Docker resolves bind-mount volume paths relative to the **host filesystem**, not the Sencho container. If your compose files reference relative paths like `./data:/app/data`, Docker looks for `./data` on the host — starting from the directory where the compose file lives on the host.
|
||||
|
||||
This means the path to your compose directory **must be identical** inside and outside the container:
|
||||
|
||||
```yaml
|
||||
# Correct - same path on both sides
|
||||
volumes:
|
||||
- /opt/compose:/opt/compose
|
||||
environment:
|
||||
- COMPOSE_DIR=/opt/compose
|
||||
|
||||
# Wrong - different paths, relative volumes will break
|
||||
volumes:
|
||||
- /opt/compose:/app/compose
|
||||
```
|
||||
|
||||
See the [Configuration guide](/getting-started/configuration#compose-directory-the-11-path-rule) for a detailed explanation with examples.
|
||||
|
||||
---
|
||||
|
||||
## Volume mount checklist
|
||||
|
||||
Sencho requires three volume mounts to function correctly:
|
||||
|
||||
| Mount | Purpose | Required |
|
||||
|-------|---------|----------|
|
||||
| `/var/run/docker.sock:/var/run/docker.sock` | Docker Engine access for managing containers | Yes |
|
||||
| `./sencho-data:/app/data` | Persistent storage for SQLite database and encryption keys | Yes |
|
||||
| `/opt/compose:/opt/compose` | Your compose project files (must follow 1:1 path rule) | Yes |
|
||||
|
||||
<Note>
|
||||
The data directory contains `sencho.db` (all settings, nodes, alerts, metrics) and `encryption.key` (used to encrypt secrets at rest). Losing this directory means losing your Sencho configuration entirely.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Docker socket security
|
||||
|
||||
Mounting the Docker socket (`/var/run/docker.sock`) grants the container the ability to manage all containers, images, volumes, and networks on the host. This is equivalent to root access on the host machine.
|
||||
|
||||
Sencho mitigates this with privilege dropping:
|
||||
|
||||
1. The container starts as root to fix volume ownership and resolve Docker socket group permissions
|
||||
2. The entrypoint script (`docker-entrypoint.sh`) then drops to a non-root `sencho` user via `su-exec`
|
||||
3. All application code runs as the `sencho` user
|
||||
|
||||
If your environment requires stricter isolation, consider:
|
||||
|
||||
- Running Sencho on a dedicated Docker host
|
||||
- Using Docker's `--userns-remap` for user namespace isolation
|
||||
- Placing Sencho behind a reverse proxy with authentication (see [Configuration](/getting-started/configuration#reverse-proxy-setup))
|
||||
|
||||
---
|
||||
|
||||
## Resource recommendations
|
||||
|
||||
| Resource | Minimum | Recommended | Notes |
|
||||
|----------|---------|-------------|-------|
|
||||
| CPU | 1 core | 1-2 cores | More cores help with concurrent stack operations |
|
||||
| RAM | 256 MB | 512 MB | Higher for multi-node setups with many stacks |
|
||||
| Disk | 100 MB | 500 MB | Database is typically < 50 MB; allocate headroom for metrics retention |
|
||||
|
||||
Sencho itself is lightweight. The majority of resource usage on your host comes from the Docker containers it manages, not from Sencho.
|
||||
|
||||
---
|
||||
|
||||
## Networking
|
||||
|
||||
- **Listen port:** 3000 by default, configurable via the `PORT` environment variable
|
||||
- **Inbound:** Only the listen port needs to be reachable (directly or through a reverse proxy)
|
||||
- **Outbound:** No outbound connections are required for local-only setups. If you use multi-node management, Sencho needs HTTP/HTTPS access to remote Sencho instances on their configured API URLs
|
||||
- **Health check:** `GET /api/health` returns `200` when the application is ready. The Docker image includes a built-in `HEALTHCHECK` that polls this endpoint every 30 seconds
|
||||
|
||||
---
|
||||
|
||||
## Environment variable checklist
|
||||
|
||||
Quick reference for all environment variables. See [Configuration](/getting-started/configuration) for full details.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `JWT_SECRET` | *(required)* | Secret key for signing JWT tokens |
|
||||
| `COMPOSE_DIR` | `/app/compose` | Path to compose project files (1:1 rule applies) |
|
||||
| `PORT` | `3000` | HTTP server listen port |
|
||||
| `DATA_DIR` | `/app/data` | Database and encryption key directory |
|
||||
| `NODE_ENV` | `production` | Set automatically in Docker image |
|
||||
| `FRONTEND_URL` | *(empty)* | Frontend origin for CORS; leave empty for same-origin |
|
||||
| `API_RATE_LIMIT` | `100` | Max API requests per minute per IP |
|
||||
|
||||
SSO variables are documented separately in the [SSO Quickstart](/getting-started/sso-quickstart).
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
title: Upgrading Sencho
|
||||
description: How to update Sencho, what happens during upgrades, and the version policy.
|
||||
---
|
||||
|
||||
## Upgrade steps
|
||||
|
||||
Upgrading Sencho is a two-command process. Pull the latest image and recreate the container:
|
||||
|
||||
```bash
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Or if you're running with `docker run`:
|
||||
|
||||
```bash
|
||||
docker pull saelix/sencho:latest
|
||||
docker stop sencho && docker rm sencho
|
||||
# Re-run your original docker run command
|
||||
```
|
||||
|
||||
Sencho will apply any necessary database migrations automatically on startup. No manual steps are required.
|
||||
|
||||
---
|
||||
|
||||
## Automatic migrations
|
||||
|
||||
Sencho handles all schema changes internally. When the application starts, it runs a series of migration checks:
|
||||
|
||||
- **Schema evolution** — New columns are added via `ALTER TABLE ADD COLUMN`. If a column already exists, the operation is silently skipped.
|
||||
- **Legacy config migration** — If upgrading from a very early version that used a `sencho.json` file, the settings are automatically imported into SQLite.
|
||||
- **Admin account migration** — Legacy admin credentials stored in `global_settings` are migrated to the `users` table.
|
||||
- **Encryption migration** — Unencrypted node API tokens are automatically encrypted at rest using AES-256-GCM.
|
||||
- **SSO columns** — SSO provider fields are added to the users table if not present.
|
||||
- **Registry tables** — Private registry storage tables are created if they don't exist.
|
||||
- **RBAC tables** — Role assignment tables are created for granular permissions.
|
||||
- **Legacy cleanup** — Obsolete columns from pre-0.7 versions (SSH/TLS fields) are dropped.
|
||||
|
||||
You never need to run SQL commands, migration scripts, or any manual database operations.
|
||||
|
||||
---
|
||||
|
||||
## Backup before upgrading
|
||||
|
||||
Always back up your data directory before upgrading. If something goes wrong, restoring from backup is the only recovery path — Sencho does not support downgrading or rolling back database migrations.
|
||||
|
||||
```bash
|
||||
sqlite3 /path/to/sencho-data/sencho.db ".backup '/path/to/backup/sencho-pre-upgrade.db'"
|
||||
```
|
||||
|
||||
See the [Backup & Restore guide](/operations/backup) for full backup procedures.
|
||||
|
||||
---
|
||||
|
||||
## Pinning a specific version
|
||||
|
||||
If you prefer to control exactly which version you run, pin the image tag in your `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
image: saelix/sencho:0.25.3
|
||||
```
|
||||
|
||||
Check [GitHub Releases](https://github.com/AnsoCode/Sencho/releases) for available versions and changelogs.
|
||||
|
||||
---
|
||||
|
||||
## Version policy
|
||||
|
||||
Sencho follows [Semantic Versioning](https://semver.org/) (`MAJOR.MINOR.PATCH`):
|
||||
|
||||
| Change type | Version bump | Example |
|
||||
|-------------|-------------|---------|
|
||||
| Bug fixes, performance improvements | Patch | 0.25.0 → 0.25.1 |
|
||||
| New features | Minor | 0.25.x → 0.26.0 |
|
||||
| Breaking changes | Major | 0.x.y → 1.0.0 |
|
||||
|
||||
<Note>
|
||||
While the version is below 1.0, minor releases (0.x.0) may occasionally include breaking changes. These are always documented in the release notes. Once Sencho reaches 1.0, breaking changes will only occur in major releases.
|
||||
</Note>
|
||||
|
||||
Breaking changes are marked with `BREAKING CHANGE` in the [release notes](https://github.com/AnsoCode/Sencho/releases). Subscribe to the repository's releases to be notified of new versions.
|
||||
@@ -0,0 +1,105 @@
|
||||
---
|
||||
title: Architecture Overview
|
||||
description: System design, request flow, database schema, and deployment model.
|
||||
---
|
||||
|
||||
## System overview
|
||||
|
||||
Sencho is a self-contained application packaged as a single Docker container:
|
||||
|
||||
- **Frontend:** React 19 single-page application built with Vite and served as static files
|
||||
- **Backend:** Express.js (Node.js 22) REST API with WebSocket support
|
||||
- **Database:** SQLite via `better-sqlite3` — no external database required
|
||||
- **Container management:** Docker Engine API via the mounted Docker socket, plus Docker Compose CLI for stack operations
|
||||
|
||||
There are no external dependencies. No Redis, no PostgreSQL, no message queues. All state lives in a single SQLite file (`sencho.db`) inside the data directory.
|
||||
|
||||
---
|
||||
|
||||
## Request flow
|
||||
|
||||
### Browser sessions
|
||||
|
||||
1. The browser loads the React SPA from the Express static file server
|
||||
2. The frontend makes API calls via an `apiFetch` wrapper that injects the active node ID as an `x-node-id` header
|
||||
3. Express middleware evaluates the node ID:
|
||||
- **Local node:** The request passes through authentication middleware and hits the local route handler, which communicates with Docker via the mounted socket
|
||||
- **Remote node:** The request is transparently proxied to the remote Sencho instance using `http-proxy-middleware`. The backend strips the `x-node-id` header and injects `Authorization: Bearer <token>` for the remote node
|
||||
4. The response flows back through the same path to the browser
|
||||
|
||||
### API token access
|
||||
|
||||
External integrations (CI/CD pipelines, scripts) authenticate with Bearer tokens instead of cookies. The same route handlers process both authentication methods — the middleware accepts JWT tokens from either cookies or the `Authorization` header.
|
||||
|
||||
---
|
||||
|
||||
## Authentication model
|
||||
|
||||
| Method | Used by | Token storage |
|
||||
|--------|---------|--------------|
|
||||
| HTTP-only cookie (JWT) | Browser sessions | Set by Express on login, sent automatically |
|
||||
| Bearer token (JWT) | API integrations, node-to-node proxy | Passed in `Authorization` header |
|
||||
|
||||
API tokens have scopes that restrict their access: `read-only`, `deploy-only`, or `full-admin`. Tokens are hashed before storage.
|
||||
|
||||
SSO is supported via LDAP, Google OIDC, GitHub OAuth, and Okta OIDC. SSO users are mapped to local accounts on first login.
|
||||
|
||||
---
|
||||
|
||||
## Database
|
||||
|
||||
Sencho stores all state in a single SQLite database using the default journal mode (DELETE/ROLLBACK). The schema includes approximately 20 tables:
|
||||
|
||||
| Table group | Tables | Purpose |
|
||||
|-------------|--------|---------|
|
||||
| **Identity** | `users`, `role_assignments`, `sso_config` | User accounts, RBAC, SSO provider configs |
|
||||
| **Nodes** | `nodes` | Local and remote node connection details |
|
||||
| **Configuration** | `global_settings` | Key-value application settings |
|
||||
| **Automation** | `webhooks`, `webhook_executions`, `scheduled_tasks`, `scheduled_task_runs` | Webhook definitions/history, cron tasks |
|
||||
| **Monitoring** | `stack_alerts`, `notification_history`, `container_metrics`, `agents` | Alert rules, notifications, time-series metrics, notification channels |
|
||||
| **Fleet** | `fleet_snapshots`, `fleet_snapshot_files` | Multi-node backup snapshots |
|
||||
| **Security** | `api_tokens`, `audit_log` | API token hashes, action audit trail |
|
||||
| **Updates** | `stack_update_status` | Image update availability tracking |
|
||||
| **Registries** | Stored via `RegistryService` | Private container registry credentials (encrypted at rest) |
|
||||
| **State** | `system_state` | Internal application state |
|
||||
|
||||
All schema migrations run automatically on startup. See [Upgrading Sencho](/operations/upgrade#automatic-migrations) for details.
|
||||
|
||||
---
|
||||
|
||||
## Multi-node architecture
|
||||
|
||||
Sencho uses a **Distributed API** model for managing multiple hosts:
|
||||
|
||||
- **Local node:** Communicates directly with the Docker Engine via the mounted socket (`/var/run/docker.sock`)
|
||||
- **Remote nodes:** Each remote host runs its own independent Sencho instance. The primary instance acts as a transparent HTTP proxy, routing requests to remote instances using stored API URLs and long-lived JWT tokens
|
||||
|
||||
This means:
|
||||
- No SSH, SFTP, or remote Docker TCP socket connections
|
||||
- Each node is fully autonomous and can operate independently
|
||||
- The primary instance proxies both HTTP requests and WebSocket connections
|
||||
- Node routing is controlled via the `x-node-id` request header (HTTP) or `?nodeId=` query parameter (WebSocket)
|
||||
|
||||
---
|
||||
|
||||
## WebSocket channels
|
||||
|
||||
| Path | Purpose | Auth |
|
||||
|------|---------|------|
|
||||
| `/api/stacks/{stackName}/logs` | Live container log streaming | Cookie or query param token |
|
||||
| `/ws` | Host console terminal (PTY) | Cookie-based, admin only |
|
||||
|
||||
WebSocket connections bypass Express middleware. Authentication is handled manually by parsing cookies and verifying JWTs inside the `upgrade` event handler.
|
||||
|
||||
---
|
||||
|
||||
## Build and deployment
|
||||
|
||||
The Docker image uses a multi-stage build:
|
||||
|
||||
1. **Frontend build stage** — Compiles the React SPA with Vite
|
||||
2. **Backend build stage** — Compiles TypeScript to JavaScript
|
||||
3. **Native module stage** — Cross-compiles native dependencies (`bcrypt`, `better-sqlite3`, `node-pty`) for the target architecture
|
||||
4. **Production stage** — Alpine-based Node.js 22 runtime with Docker CLI and Docker Compose installed
|
||||
|
||||
The final image supports both `linux/amd64` and `linux/arm64` architectures and is published to [Docker Hub](https://hub.docker.com/r/saelix/sencho) as `saelix/sencho`.
|
||||
@@ -0,0 +1,136 @@
|
||||
---
|
||||
title: Development Guide
|
||||
description: Set up a local development environment and contribute to Sencho.
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Node.js 22** or later
|
||||
- **Docker** and **Docker Compose** installed and running
|
||||
- **Git**
|
||||
|
||||
---
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/AnsoCode/Sencho.git
|
||||
cd Sencho
|
||||
|
||||
# Install backend dependencies
|
||||
cd backend && npm install
|
||||
|
||||
# Install frontend dependencies
|
||||
cd ../frontend && npm install
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Running in development
|
||||
|
||||
Open two terminal windows:
|
||||
|
||||
**Terminal 1 — Backend** (Express + nodemon, port 3000):
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
npm run dev
|
||||
```
|
||||
|
||||
**Terminal 2 — Frontend** (Vite dev server, port 5173):
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm run dev
|
||||
```
|
||||
|
||||
The Vite dev server proxies all `/api` and `/ws` requests to `localhost:3000`, so you access the app at `http://localhost:5173`.
|
||||
|
||||
On first boot, you'll see a setup screen to create an admin account.
|
||||
|
||||
<Note>
|
||||
The backend requires a Docker socket to be available. On macOS and Linux this is `/var/run/docker.sock`. On Windows, ensure Docker Desktop is running.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Running tests
|
||||
|
||||
### Backend unit tests (Vitest)
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
npm test
|
||||
```
|
||||
|
||||
Runs all test files in `src/__tests__/`. Each test file runs in an isolated forked process with its own database instance.
|
||||
|
||||
### End-to-end tests (Playwright)
|
||||
|
||||
```bash
|
||||
# From the project root
|
||||
npm run test:e2e
|
||||
```
|
||||
|
||||
E2E tests run against a live development instance (backend on port 3000, frontend on port 5173). Make sure both are running before executing tests.
|
||||
|
||||
For interactive test debugging:
|
||||
|
||||
```bash
|
||||
npm run test:e2e:ui
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Code style
|
||||
|
||||
- **TypeScript** with `strict: true` in both packages. No `any` casts or `@ts-ignore`.
|
||||
- **ESLint 9** flat config. Run `npm run lint` in both `backend/` and `frontend/` directories.
|
||||
- **Frontend styling:** Tailwind CSS with [shadcn/ui](https://ui.shadcn.com/) components. See the design system section in `CLAUDE.md` for color, typography, and component patterns.
|
||||
- **Backend patterns:** Express route handlers in `src/index.ts`, business logic in `src/services/`. All new endpoints must use `authMiddleware`. SQL must use parameterized queries.
|
||||
|
||||
---
|
||||
|
||||
## Pull request workflow
|
||||
|
||||
1. Create a branch from `main` (e.g. `feat/my-feature`, `fix/my-bug`)
|
||||
2. Use [Conventional Commits](https://www.conventionalcommits.org/) for all commit messages:
|
||||
- `feat:` — new user-facing feature (bumps minor version)
|
||||
- `fix:` — bug fix (bumps patch version)
|
||||
- `docs:` — documentation changes (no version bump)
|
||||
- `chore:`, `ci:`, `refactor:`, `test:`, `perf:` — internal changes (no version bump)
|
||||
3. Update `CHANGELOG.md` under `## [Unreleased]` for user-facing changes
|
||||
4. Update or create documentation in `/docs` if behavior changes
|
||||
5. Open a PR targeting `main`
|
||||
6. Ensure CI passes before requesting review
|
||||
|
||||
Keep PRs focused — one feature or fix per PR.
|
||||
|
||||
---
|
||||
|
||||
## Project structure
|
||||
|
||||
```
|
||||
Sencho/
|
||||
├── backend/ # Express.js API + Docker orchestration
|
||||
│ ├── src/
|
||||
│ │ ├── index.ts # Route definitions (monolithic)
|
||||
│ │ ├── services/ # Business logic (17 services)
|
||||
│ │ └── __tests__/ # Vitest unit tests
|
||||
│ └── package.json
|
||||
├── frontend/ # React 19 + Vite SPA
|
||||
│ ├── src/
|
||||
│ │ ├── components/ # UI components
|
||||
│ │ ├── context/ # React context providers
|
||||
│ │ ├── lib/ # Utilities (apiFetch, etc.)
|
||||
│ │ └── App.tsx # Root router
|
||||
│ └── package.json
|
||||
├── docs/ # Mintlify documentation site
|
||||
├── e2e/ # Playwright E2E test specs
|
||||
├── Dockerfile # Multi-stage production build
|
||||
├── docker-compose.yml # Quick-start deployment
|
||||
└── package.json # Root scripts (test, test:e2e)
|
||||
```
|
||||
|
||||
For more detail on how the components relate, see the [Architecture Overview](/reference/architecture).
|
||||
Reference in New Issue
Block a user