Merge pull request #66 from AnsoCode/feature/bootstrap-docs

docs: bootstrap user-facing documentation from codebase audit
This commit is contained in:
Anso
2026-03-22 23:56:08 -04:00
committed by GitHub
29 changed files with 1290 additions and 10 deletions
+3
View File
@@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- feat: automated documentation pipeline with Mintlify sync
- feat: CI job to auto-refresh doc screenshots on every develop push
- docs: bootstrap user-facing documentation from codebase audit — added configuration, stack management, editor, multi-node, and alerts & notifications pages; updated introduction, quickstart, and features overview; restructured mint.json navigation with Getting Started / Features / Reference / Operations groups
- docs: add Tier 2 feature pages — dashboard, resources hub, app store, global observability, and host console
- docs: add Tier 3 reference and operations pages — settings reference, troubleshooting, and backup & restore
### Fixed
- fix(ci): YAML syntax error in update-screenshots `if:` condition (`!` tag and `: ` in plain scalar); wrapped in `${{ }}`
+95
View File
@@ -0,0 +1,95 @@
---
title: Alerts & Notifications
description: Set threshold-based alerts on container metrics and route them to Discord, Slack, or any webhook.
---
Sencho can watch your containers for resource anomalies and notify you when thresholds are breached. Alerts are defined per stack, and notifications are delivered through external agents you configure.
<Frame>
<img src="/images/alerts-notifications/notifications-settings.png" alt="Notifications & Alerts settings showing Discord, Slack, and Webhook tabs" />
</Frame>
## How alerts work
Sencho's monitoring service samples container metrics every minute and evaluates all defined alert rules. When a rule's condition holds true for the configured duration, a notification is dispatched. A cooldown period prevents the same alert from firing repeatedly.
## Setting up a notification agent
At least one agent must be enabled before alerts can be delivered. Go to **Settings → Notifications**.
### Discord
1. In Discord, go to your server's **Settings → Integrations → Webhooks**
2. Click **New Webhook**, choose a channel, and copy the webhook URL
3. In Sencho, open **Settings → Notifications → Discord**, paste the URL, enable the toggle, and click **Save**
4. Click **Test** to send a test message
### Slack
1. In Slack, go to **api.slack.com/apps**, create an app, and add the **Incoming Webhooks** feature
2. Activate it and copy the generated webhook URL for your chosen channel
3. In Sencho, open **Settings → Notifications → Slack**, paste the URL, enable the toggle, and click **Save**
### Generic Webhook
Any HTTP endpoint that accepts a POST with a JSON body can receive Sencho alerts. Go to **Settings → Notifications → Webhook**, enter the URL, and click **Save**.
The payload format is:
```json
{
"level": "warning",
"message": "cpu_percent exceeded 90% for 5 minutes on stack my-app",
"timestamp": "2026-03-22T10:00:00.000Z"
}
```
## Creating stack alerts
Stack alerts are configured per-stack. Right-click a stack in the sidebar (or click the **⋮** button) and select **Alerts**.
<Frame>
<img src="/images/stack-management/stack-context-menu.png" alt="Stack context menu showing the Alerts option" />
</Frame>
The alerts sheet shows existing rules for the stack and a form to create new ones.
### Alert fields
| Field | Description |
|-------|-------------|
| **Metric** | The container metric to watch |
| **Operator** | Comparison operator: `>`, `>=`, `<`, `<=`, `==` |
| **Threshold** | The value to compare against |
| **Duration (minutes)** | How long the condition must hold before firing |
| **Cooldown (minutes)** | Minimum time between repeated notifications for this rule |
### Available metrics
| Metric | Unit | Description |
|--------|------|-------------|
| `cpu_percent` | % | CPU usage relative to total host cores |
| `memory_mb` | MB | RSS memory used by the container |
| `memory_percent` | % | Memory used as a fraction of the host total |
| `net_rx` | bytes/s | Inbound network rate |
| `net_tx` | bytes/s | Outbound network rate |
| `restart_count` | count | Number of times the container has restarted |
### Example: alert on high CPU
To alert when any container in a stack uses more than 80% CPU for over 5 consecutive minutes, with a 30-minute cooldown:
| Field | Value |
|-------|-------|
| Metric | `cpu_percent` |
| Operator | `>` |
| Threshold | `80` |
| Duration | `5` |
| Cooldown | `30` |
## Notification history
All dispatched notifications appear in the **notification bell** (top-right of the nav bar). Click it to see recent alerts with their level, message, and timestamp. Mark all as read or clear individual entries from there.
<Note>
Notifications only reach external agents (Discord, Slack, Webhook) if at least one agent is enabled. Dashboard notifications appear regardless.
</Note>
+71
View File
@@ -0,0 +1,71 @@
---
title: App Store
description: Browse and deploy pre-configured application templates in one click.
---
The **App Store** tab lets you browse a curated catalogue of Docker Compose templates and deploy any of them as a new stack with environment-specific configuration — no YAML required.
<Frame>
<img src="/images/app-store/app-store-overview.png" alt="App Store showing a grid of application templates with category filters" />
</Frame>
## Browsing templates
Templates are loaded from a remote registry (configurable in **Settings → App Store**). The default registry provides 190+ self-hosted application templates.
**Search:** Type in the search bar to filter templates by name or description in real-time.
**Categories:** Click any category pill to narrow the list:
`Automation` · `Books` · `Development` · `Documentation` · `Downloaders` · `Media` · `Monitoring` · `Networking` · `Other` · `Product`
Each template card shows:
- Application logo
- Name and short description
- Category tags
- Links to the GitHub repo and documentation (where available)
## Deploying a template
Click any template card to open the **deployment sheet** on the right side of the screen. Fill in the fields and click **Deploy**.
### Stack name
Pre-filled with the template name in lowercase. You can change it — the same [naming rules](/features/stack-management#creating-a-stack) apply (lowercase, hyphens, no spaces).
### Environment variables
Each template declares the variables it needs. Sencho pre-fills sensible defaults where available (e.g. `PUID=1000`, `PGID=1000`, `TZ` from your browser locale).
Edit any value before deploying. You can also click **Custom Env** to add arbitrary key-value pairs not defined by the template.
### Volumes
For each container mount point, enter the **host path** where that data should be stored. Suggested defaults (e.g. `./config`, `./data`) are shown as placeholders.
<Note>
Relative paths like `./config` are resolved relative to the stack directory inside your `COMPOSE_DIR`. Absolute paths map directly to the host filesystem.
</Note>
### Ports
For each exposed port, edit the **host port** (left side). The container port (right side) is fixed by the template and cannot be changed here — edit the compose file after deployment if needed.
### What happens on Deploy
1. Sencho creates a new stack directory in `COMPOSE_DIR`
2. Writes the generated `compose.yaml` and `.env` file
3. Runs `docker compose up -d`
4. On success: switches you to the Editor view for that stack
5. On failure: rolls back by running `docker compose down` and deleting the stack directory
A toast notification confirms success or shows the error message.
## Custom template registry
By default Sencho uses the LinuxServer.io template registry. To use your own:
1. Open **Settings → App Store**
2. Enter your registry URL (must serve a JSON array of template objects in Portainer v2 format)
3. Click **Save**
To force a refresh of the cached templates, click **Refresh Cache** in the same settings panel.
+60
View File
@@ -0,0 +1,60 @@
---
title: Dashboard
description: Real-time system stats, historical metrics, and a quick-start converter for your host machine.
---
The **Home** tab is the first thing you see after logging in. It shows a live snapshot of your host's health alongside container activity across all stacks.
<Frame>
<img src="/images/dashboard/dashboard-overview.png" alt="Sencho dashboard showing container stats, system stats, and historical charts" />
</Frame>
## Container stats
The top row summarises container state at a glance:
| Card | What it shows |
|------|---------------|
| **Active Containers** | Running containers — broken down as `N managed · N external` |
| **Exited Containers** | Stopped or crashed containers |
| **Docker Network** | Current inbound/outbound network throughput across all containers |
**Managed** means the container belongs to a stack in your `COMPOSE_DIR`. **External** means it exists on the Docker host but was started outside Sencho (e.g. by another Compose project or `docker run`).
## System stats
The second row shows host-level resource usage, polled every few seconds:
| Card | What it shows |
|------|---------------|
| **Host CPU** | Current CPU usage percentage and core count |
| **Host RAM** | Used / total memory in GB and percentage |
| **Host Disk** | Used / total disk space for the primary mount point |
When any value exceeds configured thresholds (set in **Settings → System Limits**), the card changes colour as a visual warning.
## Historical metrics charts
Two area charts display time-series data sampled at one-minute intervals, retained for up to 24 hours (configurable in **Settings → Developer**):
- **Normalized CPU Usage** — total CPU percentage across all managed containers, normalised over all host cores
- **Normalized RAM Usage** — total memory allocated by managed containers, in GB
The x-axis shows time labels. Hover over a data point to see the exact value at that moment.
<Note>
Charts only show data from the moment Sencho started. If you just installed Sencho, they will be mostly empty until metrics accumulate.
</Note>
## Convert `docker run` to Compose
The bottom panel provides a quick converter: paste any `docker run` command and Sencho transforms it into a valid `docker-compose.yaml` snippet.
```bash
# Example input
docker run -d --name myapp -p 8080:80 -e TZ=UTC -v /data:/app/data nginx:latest
```
Click **Convert** and the output YAML appears ready to copy. You can then create a new stack and paste it into the editor.
This is useful when migrating existing containers to managed Compose stacks without having to write YAML by hand.
+64
View File
@@ -0,0 +1,64 @@
---
title: Editor
description: Edit compose files and environment variables, and manage containers directly from the dashboard.
---
Selecting a stack opens the editor view — a split-pane layout with container management on the left and a full Monaco code editor on the right.
<Frame>
<img src="/images/editor/editor-overview.png" alt="Editor view showing container panel and Monaco editor" />
</Frame>
## Compose file editor
The right panel shows your `compose.yaml` with full syntax highlighting. By default the editor is **read-only** to prevent accidental changes.
Click **Edit** to enter edit mode. Your changes are unsaved until you explicitly save them.
### Save options
| Button | What it does |
|--------|--------------|
| **Save** | Writes the file to disk. Does not restart any containers. |
| **Save & Deploy** | Writes the file, then immediately runs `docker compose up -d`. Use this to apply compose changes in one step. |
| **Discard** | Reverts the editor to the last saved version. Unsaved changes are lost. |
## Environment file editor
Click the **.env** tab to switch to the environment file editor. If your `compose.yaml` references multiple env files (via `env_file:`), a dropdown lets you select which file to edit.
The `.env` editor has the same save/discard controls as the compose editor. Changes take effect the next time the stack is deployed.
<Note>
Sencho reads the `env_file:` paths from your `compose.yaml` to discover available env files. If no `env_file:` is declared, a default `.env` in the stack directory is used.
</Note>
## Container panel
The left panel lists all containers that belong to the selected stack. Each container shows:
- **Status badge** — `running`, `exited`, `starting`, or `unhealthy`
- **Live stats** — CPU %, RAM usage, and network I/O updated every 12 seconds
- **Port mappings** — host:container port pairs (if any)
### Container actions
Each container row has three action buttons:
| Button | What it does |
|--------|--------------|
| **Logs** (external link icon) | Opens a live log stream modal for that container |
| **Terminal** (terminal icon) | Opens an interactive bash session inside the container |
| **Copy ID** | Copies the container ID to the clipboard |
### Log viewer
The log viewer streams output from a single container in real-time using Server-Sent Events. Logs auto-scroll to the bottom as new lines arrive. Close the modal to stop the stream.
### Container terminal (exec)
The terminal modal gives you an interactive bash shell inside the running container — equivalent to `docker exec -it <id> bash`. It uses a full xterm.js emulator with color support and tab completion.
<Warning>
The container terminal requires the container to have `bash` (or `sh`) installed. Minimal images (e.g. Alpine-based) may need `sh` instead.
</Warning>
+58
View File
@@ -0,0 +1,58 @@
---
title: Global Observability
description: A unified, searchable log stream from every container across all your stacks.
---
The **Logs** tab aggregates output from all running containers into a single scrollable view. Instead of tailing logs one container at a time, you see everything in one place — with filtering to focus on what matters.
<Frame>
<img src="/images/global-observability/global-observability-overview.png" alt="Global Observability view showing real-time log lines from multiple containers" />
</Frame>
## Log format
Each line shows:
- **Timestamp** — when the log line was emitted
- **Stack name** — the Compose stack the container belongs to (colour-coded)
- **Container name** — the specific container
- **Level** — `INFO`, `WARN`, or `ERROR` (where detectable)
- **Message** — the raw log output
## Streaming modes
Sencho supports two modes for fetching logs, switchable via the **Developer mode** toggle:
| Mode | How it works | Best for |
|------|-------------|----------|
| **Standard** (default) | Polls all containers every N seconds (configurable in Settings → Developer) | General use; lower overhead |
| **Developer mode** | Server-Sent Events stream; logs arrive as they are emitted | Debugging; watching a specific event in real-time |
The default polling interval is 5 seconds. You can change it to 1, 3, 5, or 10 seconds in **Settings → Developer → Global Logs Refresh Rate**.
## Filtering logs
Use the controls above the log panel to narrow what you see:
| Control | What it does |
|---------|-------------|
| **Stack filter** | Multi-select dropdown — show only logs from chosen stacks |
| **Stream** | `ALL` / `STDOUT` / `STDERR` — filter by output stream |
| **Search** | Full-text filter on the message field (case-insensitive) |
Filters combine — you can show only `STDERR` from a specific stack while searching for a keyword.
## Controls
| Button | What it does |
|--------|-------------|
| **Clear** | Clears the current log buffer in the UI (does not delete logs from Docker) |
| **Auto-scroll** toggle | When enabled, the view scrolls to the bottom as new lines arrive |
## Capacity limits
To avoid browser memory issues, the log view keeps a maximum of **2,000 entries** in memory at a time. Older entries are dropped as new ones arrive. For deeper investigation, use the [per-container log viewer](/features/editor#log-viewer) in the Editor tab, or `docker compose logs` directly from the [Host Console](/features/host-console).
<Note>
Log retention on the backend is controlled by **Settings → Developer → Log Retention Days** (default: 30 days). This affects historical logs stored in Sencho's database, not the live stream.
</Note>
+54
View File
@@ -0,0 +1,54 @@
---
title: Host Console
description: An interactive terminal on your host OS, directly in the browser — no SSH required.
---
The **Console** tab opens a full interactive terminal session on the machine running Sencho. It behaves exactly like an SSH session, but without needing an SSH server, client, or key management.
<Frame>
<img src="/images/host-console/host-console-overview.png" alt="Host Console showing a PowerShell prompt inside the Sencho container working directory" />
</Frame>
## What it is
The host console spawns a real PTY (pseudo-terminal) process on the Sencho host using `node-pty`. Your keystrokes are sent over a WebSocket and the terminal output is streamed back in real-time. The terminal emulator is [xterm.js](https://xtermjs.org/), the same engine used by VS Code's built-in terminal.
Features:
- Full colour and cursor support
- Tab completion (via the host shell)
- Scrollback buffer (10,000 lines)
- Automatic terminal resizing when you resize the browser window
- Copy and paste
## Opening the console
Click **Console** in the top navigation bar. The session starts immediately in the stack's working directory if a stack is selected, otherwise in the `COMPOSE_DIR` root.
The header shows the connection status (**Connected** in green) and which node the console is attached to.
Click **Close Console** to end the session and terminate the shell process on the host.
## Shell type
The shell depends on the host OS:
- **Linux/macOS hosts:** `bash` or `sh`
- **Windows hosts (Docker Desktop):** PowerShell (as shown in the screenshot — the console opens inside the Sencho container's Windows environment)
## Security model
The host console has a stricter authentication requirement than other features:
- Requires a valid **browser session** (httpOnly cookie). Node-proxy tokens used for multi-node communication are explicitly blocked.
- Each session is issued a short-lived **console token** (60-second TTL) before the WebSocket is established.
- Because the console gives full shell access to the host, you should secure your Sencho instance with HTTPS and a strong password if it is exposed to a network.
<Warning>
The host console provides unrestricted shell access to the machine running Sencho. Do not expose Sencho on a public network without HTTPS and strong authentication.
</Warning>
## Common uses
- Inspecting files in your `COMPOSE_DIR` without leaving the browser
- Running `docker compose logs --follow` or `docker ps` directly
- Editing files with `nano` or `vim` for quick fixes
- Running maintenance scripts or one-off commands on the host
+80
View File
@@ -0,0 +1,80 @@
---
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:
| 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-manager.png" alt="Node switcher dropdown and node list" />
</Frame>
Node status indicators:
| Indicator | Meaning |
|-----------|---------|
| Green dot | Node is reachable and responding |
| Red dot | Node is unreachable |
| Gray dot | Status not yet checked |
## 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 considerations
- Node tokens grant full control over the remote Sencho instance. Rotate them if compromised via Settings → Nodes → Generate Token (the old token is invalidated).
- Use HTTPS between instances in production to prevent token interception.
- The host console and container exec terminals are blocked for node-proxy tokens — interactive shell access always requires a real browser session on that instance.
+22 -6
View File
@@ -5,15 +5,23 @@ description: A high-level tour of everything Sencho can do.
## Stack management
Deploy, start, stop, restart, and remove Docker Compose stacks through a point-and-click interface. Edit `docker-compose.yml` files directly in the built-in Monaco editor.
Deploy, start, stop, restart, and remove Docker Compose stacks through a point-and-click interface. Edit `compose.yaml` and `.env` files directly in the built-in Monaco editor with syntax highlighting. [Learn more →](/features/stack-management)
## Editor
Full in-browser code editor for your Compose and environment files. Toggle edit mode, save to disk, or save and deploy in one step. Per-container live stats and an interactive bash exec terminal live alongside the editor. [Learn more →](/features/editor)
## Multi-node support
Add remote Sencho instances as nodes. All dashboard operations — stack management, logs, stats — work identically whether you're targeting your local machine or a server on the other side of the world.
Add remote Sencho instances as nodes. All dashboard operations — stack management, logs, stats — work identically whether you're targeting your local machine or a server on the other side of the world. Uses a transparent HTTP proxy model; no SSH or shared Docker sockets required. [Learn more →](/features/multi-node)
## Real-time logs & stats
Stream container logs and resource metrics (CPU, memory, network I/O) live in the browser via WebSocket connections.
Stream container logs and resource metrics (CPU, memory, network I/O) live in the browser via WebSocket and Server-Sent Events connections. The Home dashboard shows historical CPU and RAM charts over the last 24 hours.
## Global observability
The **Logs** view aggregates output from all containers across all stacks into a single scrollable stream. Filter by stack, log level (stdout/stderr), or search for keywords. Switch to developer mode for real-time SSE streaming.
## Resources hub
@@ -23,10 +31,18 @@ View and manage all Docker images, volumes, and networks. Resources are classifi
|-------|---------|
| **Managed** | Owned by a Sencho stack |
| **External** | Part of another Compose project |
| **Unused / System** | Reclaimable or Docker-internal |
| **Unused / Reclaimable** | Safe to prune |
Run scoped prune operations to clean up Sencho-managed resources only, or target all Docker resources when needed.
## Notifications & alerts
## App Store
Configure threshold-based alerts for CPU and memory usage. Notifications surface in the dashboard and can be dismissed or cleared in bulk.
Browse 190+ pre-configured application templates. Filter by category (Media, Automation, Development, etc.), configure environment variables, volumes, and ports, and deploy with a single click.
## Host console
Open an interactive terminal on the host OS directly in the browser — full xterm.js emulation with color support. No SSH client required.
## Alerts & notifications
Configure threshold-based alerts (CPU, memory, network, restart count) per stack. Route notifications to Discord, Slack, or any generic webhook endpoint. [Learn more →](/features/alerts-notifications)
+75
View File
@@ -0,0 +1,75 @@
---
title: Resources Hub
description: Browse, filter, and clean up Docker images, volumes, networks, and unmanaged containers.
---
The **Resources** tab gives you a full view of everything Docker is storing on your host, broken down by type and ownership.
<Frame>
<img src="/images/resources/resources-overview.png" alt="Resources Hub showing disk footprint, quick clean panel, and images table" />
</Frame>
## Docker disk footprint
The stacked bar at the top visualises how your Docker disk usage is distributed:
| Segment | Meaning |
|---------|---------|
| **Sencho Managed** (green) | Images used by stacks in your `COMPOSE_DIR` |
| **External Projects** (orange) | Images used by Docker projects outside Sencho |
| **Reclaimable** (gray) | Unused images and dangling layers safe to delete |
Click any segment to automatically filter the tabs below to that category.
## Quick Clean panel
Four prune buttons let you reclaim disk space immediately. By default they operate on **Sencho-managed resources only** — they will not touch external Docker projects.
| Button | What it removes |
|--------|----------------|
| **Prune Unused Images** | Images with no running containers in Sencho stacks |
| **Prune Unused Volumes** | Volumes not attached to any Sencho container |
| **Prune Dead Networks** | Networks not connected to any Sencho container |
| **Purge Unmanaged Containers** | Containers Sencho doesn't recognise (started outside it) |
Each button has a **⋮ More options** menu that lets you target **all Docker resources** instead of Sencho-only. Use this carefully — it can affect other Compose projects running on the same host.
A confirmation dialog appears before any destructive operation, showing a summary of what will be removed.
## Resource tabs
### Images
Lists all Docker images on the host with their ID, repository tag, size, and status.
**Filter buttons:** `All` · `Managed` · `External`
**Status badges:**
- `In Use` + stack name — image is actively used by a running container
- `Unused` — image has no running containers; safe to delete
Click the trash icon on any row to delete an individual image. Sencho will warn you if the image is in use.
### Volumes
Lists all Docker volumes. Columns: name, driver, mount point, size, and managed status.
**Filter buttons:** `All` · `Managed` · `External`
<Warning>
Deleting a volume is permanent. Any data stored in it will be lost. Always back up important volume data before pruning.
</Warning>
### Networks
Lists all Docker networks. Columns: name, driver, scope (`local`, `global`, `swarm`), and managed status.
**Filter buttons:** `All` · `Managed` · `External` · `System`
System networks (like `bridge`, `host`, `none`) are shown but cannot be deleted.
### Unmanaged
Lists containers running on the host that are not part of any Sencho-managed stack. This includes containers started with `docker run`, or Compose projects outside your `COMPOSE_DIR`.
This view is useful for identifying orphaned containers after a failed deployment or after moving stacks in and out of `COMPOSE_DIR`.
+71
View File
@@ -0,0 +1,71 @@
---
title: Stack Management
description: Create, deploy, control, and remove Docker Compose stacks.
---
A **stack** in Sencho is a Docker Compose project: a directory inside your `COMPOSE_DIR` that contains at least a `compose.yaml` (or `docker-compose.yml`) file. Sencho automatically discovers every subdirectory as a stack.
<Frame>
<img src="/images/stack-management/create-stack-dialog.png" alt="Create New Stack dialog" />
</Frame>
## Creating a stack
Click **Create Stack** in the left sidebar. Enter a name and click **Create**.
**Naming rules:**
- Lowercase letters, numbers, and hyphens only (e.g. `my-app`, `nextcloud`)
- No spaces or special characters
- Must be unique — duplicates are rejected
Sencho creates a new directory inside `COMPOSE_DIR` with a blank `compose.yaml` file. You'll land in the editor automatically.
## The stack list
All discovered stacks appear in the left sidebar. Each shows a color-coded status dot:
| Color | Meaning |
|-------|---------|
| Green | All containers running |
| Red | One or more containers exited |
| Gray | No containers / status unknown |
Use the **search box** above the list to filter stacks by name.
## Deploying a stack
Select a stack and click **Deploy** in the stack header. This runs `docker compose up -d` — pulling images if needed and creating or recreating containers.
<Frame>
<img src="/images/editor/editor-overview.png" alt="Stack editor with control buttons" />
</Frame>
## Controlling a running stack
The stack header exposes four actions:
| Button | Command | What it does |
|--------|---------|--------------|
| **Stop** | `docker compose stop` | Stops containers without removing them. State is preserved. |
| **Restart** | `docker compose restart` | Restarts all containers in the stack. |
| **Update** | `docker compose pull` + `up -d` | Pulls the latest image tags and recreates containers. |
| **Delete** | `down` + removes files | Stops and removes containers, then deletes the stack directory. |
<Warning>
**Delete** is irreversible. It removes the stack directory — including `compose.yaml`, `.env`, and any bind-mounted files stored there. Back up important files before deleting.
</Warning>
## Stack context menu
Right-click or use the **⋮** button on any stack in the sidebar to access:
<Frame>
<img src="/images/stack-management/stack-context-menu.png" alt="Stack context menu showing Alerts option" />
</Frame>
- **Alerts** — configure metric-based alerting rules for this stack
- **Check for updates** — manually trigger an image update check
## Converting a `docker run` command
If you have an existing `docker run` command and want to turn it into a Compose stack, go to the **Home** tab and paste it into the "Convert Docker Run to Compose" field. Sencho converts it to YAML that you can save as a new stack.
+161
View File
@@ -0,0 +1,161 @@
---
title: Configuration
description: Environment variables, volume mounts, and the 1:1 path rule.
---
Sencho is configured entirely through environment variables and Docker volume mounts. There is no config file to edit inside the container.
## Required environment variables
| Variable | Description |
|----------|-------------|
| `JWT_SECRET` | Secret key used to sign session tokens. Use a long, random string (32+ characters). Changing this invalidates all active sessions. |
| `COMPOSE_DIR` | Absolute path to the directory that contains your Compose stacks. Every subdirectory inside becomes a stack in Sencho. |
## Optional environment variables
| Variable | Default | Description |
|----------|---------|-------------|
| `PORT` | `3000` | Port the Sencho HTTP server listens on. |
| `DATA_DIR` | `/app/data` | Directory where Sencho stores its SQLite database, node registry, and cached metrics. |
| `NODE_ENV` | `production` | Set automatically in the Docker image. Only change this for local development. |
## Required volume mounts
### Docker socket
Sencho needs access to the Docker daemon to manage containers:
```yaml
volumes:
- /var/run/docker.sock:/var/run/docker.sock
```
### Data directory
Sencho's database persists all your settings, nodes, alerts, and metrics history. Mount a named volume or host path so it survives container restarts:
```yaml
volumes:
- ./sencho-data:/app/data
```
<Warning>
Without a persistent data mount, Sencho will lose all configuration — including registered nodes, alerts, and settings — every time the container restarts.
</Warning>
### Compose directory — the 1:1 path rule
<Warning>
This is the most common source of deployment problems. Read carefully.
</Warning>
When Sencho runs `docker compose up`, it does so on your **host machine**. Docker resolves relative volume paths in your Compose files relative to the **host** path of the stack directory — not the path inside the Sencho container.
**The rule:** Mount your Compose directory at the **exact same path** inside the container as it exists on your host.
```yaml
# ✅ Correct — host path matches container path
volumes:
- /home/boris/docker:/home/boris/docker
environment:
- COMPOSE_DIR=/home/boris/docker
```
```yaml
# ❌ Wrong — paths differ, relative volumes will break
volumes:
- /home/boris/docker:/app/compose
environment:
- COMPOSE_DIR=/app/compose
```
If you use a simple path like `/opt/compose` on your host, mount it at `/opt/compose` in the container:
```yaml
volumes:
- /opt/compose:/opt/compose
environment:
- COMPOSE_DIR=/opt/compose
```
## Full docker-compose.yml example
```yaml
services:
sencho:
image: saelix/sencho:latest
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./sencho-data:/app/data
- /opt/compose:/opt/compose # 1:1 path rule
environment:
- JWT_SECRET=your-long-random-secret-here
- COMPOSE_DIR=/opt/compose
- DATA_DIR=/app/data
```
## Optional: global environment file
If your Compose stacks share common variables (e.g. `PUID`, `PGID`, `TZ`), you can pass an `env_file` to the Sencho container so those variables are available in the host environment when `docker compose` runs:
```yaml
services:
sencho:
image: saelix/sencho:latest
env_file:
- /opt/compose/globals.env # shared vars for all stacks
environment:
- JWT_SECRET=your-secret
- COMPOSE_DIR=/opt/compose
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /opt/compose:/opt/compose
- ./sencho-data:/app/data
```
## Reverse proxy setup
Sencho works behind any reverse proxy. The only requirement is that WebSocket connections are forwarded correctly (used for live logs, container terminals, and the host console).
### Nginx
```nginx
server {
listen 80;
server_name sencho.yourdomain.com;
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_read_timeout 3600s;
}
}
```
### Traefik (Docker labels)
```yaml
labels:
- "traefik.enable=true"
- "traefik.http.routers.sencho.rule=Host(`sencho.yourdomain.com`)"
- "traefik.http.services.sencho.loadbalancer.server.port=3000"
```
<Note>
Traefik handles WebSocket upgrades automatically for HTTP/1.1 backends. No extra configuration needed.
</Note>
## 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.
+16
View File
@@ -5,6 +5,10 @@ description: What Sencho is and why you might want it.
Sencho is a self-hosted Docker Compose management dashboard. It gives you a clean web UI to deploy, manage, and monitor your Docker Compose stacks — locally or across multiple remote servers — without touching a terminal.
<Frame>
<img src="/images/dashboard/dashboard-overview.png" alt="Sencho dashboard showing system stats and container metrics" />
</Frame>
## Key concepts
- **Stacks** — a Docker Compose project living in your `COMPOSE_DIR`. Sencho treats each subdirectory as a stack.
@@ -14,3 +18,15 @@ Sencho is a self-hosted Docker Compose management dashboard. It gives you a clea
<Note>
Sencho never accesses remote servers directly via SSH or Docker TCP. Remote management works by proxying API requests to another running Sencho instance.
</Note>
## What you can do
- **Deploy and control stacks** — create, start, stop, restart, and delete Compose stacks with one click
- **Edit files in-browser** — full Monaco editor for `compose.yaml` and `.env` files
- **Monitor in real-time** — live CPU, RAM, disk, and network stats with historical charts
- **Stream logs** — tail container logs individually or aggregate all stacks in one view
- **Manage resources** — browse, filter, and prune Docker images, volumes, and networks
- **Deploy from the App Store** — one-click deployment from a curated template registry
- **Run a host console** — interactive terminal on the host OS directly in the browser
- **Set alerts** — threshold-based notifications via Discord, Slack, or any webhook
- **Manage multiple servers** — add remote Sencho instances as nodes and switch between them seamlessly
+7 -2
View File
@@ -27,7 +27,12 @@ Open `http://localhost:3000` in your browser. On first boot you'll be prompted t
Replace `/opt/compose` with the path to your Compose projects directory. Every subdirectory inside it becomes a stack in Sencho.
</Note>
## Important: the 1:1 path rule
The `-v /opt/compose:/app/compose` mount above uses a simplified path for illustration. In practice, you must mount your compose directory at the **same path** inside and outside the container. See the [Configuration guide](/getting-started/configuration#compose-directory-the-11-path-rule) for details — this is the most common setup mistake.
## Next steps
- [Add a remote node](/features/overview) to manage another server from the same dashboard
- Browse your stacks, start/stop services, and tail logs from the dashboard
- [Configuration](/getting-started/configuration) — full environment variable reference, reverse proxy setup
- [Stack Management](/features/stack-management) — create and deploy your first stack
- [Multi-Node](/features/multi-node) — add a remote server to manage from this dashboard
Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

+25 -2
View File
@@ -16,13 +16,36 @@
"group": "Getting Started",
"pages": [
"getting-started/introduction",
"getting-started/quickstart"
"getting-started/quickstart",
"getting-started/configuration"
]
},
{
"group": "Features",
"pages": [
"features/overview"
"features/overview",
"features/dashboard",
"features/stack-management",
"features/editor",
"features/resources",
"features/app-store",
"features/global-observability",
"features/host-console",
"features/multi-node",
"features/alerts-notifications"
]
},
{
"group": "Reference",
"pages": [
"reference/settings"
]
},
{
"group": "Operations",
"pages": [
"operations/troubleshooting",
"operations/backup"
]
}
]
+139
View File
@@ -0,0 +1,139 @@
---
title: Backup & Restore
description: What to back up, how to restore it, and how to migrate Sencho to a new host.
---
Sencho stores all its state in two places: the **data directory** (SQLite database) and your **compose directory** (your actual stack files). Both need to be backed up for a complete recovery.
---
## What to back up
### 1. Data directory (`DATA_DIR`)
Default path: `/app/data` inside the container, mapped to wherever you mounted it on the host (e.g. `./sencho-data`).
Contains:
- `sencho.db` — SQLite database with all settings, nodes, alerts, metrics history, and notification history
This single file is everything Sencho knows about itself. Back it up and you can fully restore any Sencho installation.
### 2. Compose directory (`COMPOSE_DIR`)
The directory containing your stack subdirectories — your `compose.yaml` files, `.env` files, and any bind-mounted config files stored there.
This is your actual application data. It lives entirely outside Sencho and you almost certainly already have it on a schedule, but include it in any Sencho backup plan.
---
## Backing up
### Simple file copy
```bash
# Stop Sencho to ensure the SQLite WAL is flushed (recommended but not strictly required)
docker stop sencho
# Copy the data directory
cp -r /path/to/sencho-data /path/to/backup/sencho-data-$(date +%Y%m%d)
# Copy compose stacks
cp -r /opt/compose /path/to/backup/compose-$(date +%Y%m%d)
# Restart
docker start sencho
```
### SQLite online backup (without stopping)
SQLite supports hot backups via its `.backup` command. This is safe to run while Sencho is running:
```bash
sqlite3 /path/to/sencho-data/sencho.db ".backup '/path/to/backup/sencho.db'"
```
### Automated daily backup (cron example)
```cron
0 3 * * * sqlite3 /path/to/sencho-data/sencho.db ".backup '/backups/sencho-$(date +\%Y\%m\%d).db'" && find /backups -name "sencho-*.db" -mtime +30 -delete
```
This backs up the database at 3 AM daily and deletes backups older than 30 days.
---
## Restoring
### Restore from backup
1. Stop Sencho:
```bash
docker stop sencho
```
2. Replace the data directory with your backup:
```bash
rm -rf /path/to/sencho-data/*
cp /path/to/backup/sencho.db /path/to/sencho-data/sencho.db
```
3. Restore your compose directory if needed:
```bash
cp -r /path/to/backup/compose /opt/compose
```
4. Start Sencho:
```bash
docker start sencho
```
Sencho will read the restored database and resume with all your previous settings, nodes, and alert rules intact.
---
## Migrating to a new host
### Step 1: Prepare the new host
Install Docker and Docker Compose on the new machine. Create the same directory structure you use for your compose files, following the [1:1 path rule](/getting-started/configuration#compose-directory-the-11-path-rule).
### Step 2: Copy data
Transfer your backup files to the new host:
```bash
scp -r /path/to/sencho-data newhost:/path/to/sencho-data
scp -r /opt/compose newhost:/opt/compose
```
### Step 3: Deploy Sencho on the new host
Use the same `docker-compose.yml` you used on the old host (with the same `COMPOSE_DIR` and `DATA_DIR` paths):
```bash
docker compose up -d
```
### Step 4: Update remote node references
If other Sencho instances were pointing to your old host as a remote node, update their node config to use the new host's IP or hostname. Generate a new API token on the restored instance and distribute it.
### Step 5: Verify
- Log in and confirm your stacks, nodes, and alerts are all present
- Check that at least one stack deploys correctly
- Verify the node switcher shows the expected nodes with green status
---
## What is NOT backed up by this process
| Item | Location | Notes |
|------|----------|-------|
| Container data volumes | Wherever each stack's volumes are mounted on the host | Back these up separately per-application |
| Actual container images | Docker image cache | These are re-pulled on next deploy — no backup needed |
| Sencho logs (docker logs) | Container stdout | Not persisted beyond container lifetime |
<Note>
Sencho does not currently have a built-in backup scheduler or export function. The approaches above use standard OS tools and SQLite's own backup mechanism.
</Note>
+163
View File
@@ -0,0 +1,163 @@
---
title: Troubleshooting
description: Solutions to the most common Sencho setup and runtime problems.
---
## Containers won't start after deploy
**Symptom:** You click Deploy and containers immediately exit or never appear.
**Check:** Open the [Host Console](/features/host-console) and run:
```bash
docker compose -f /path/to/your/stack/compose.yaml logs
```
The most common causes:
- **Missing environment variable** — a required variable in your `.env` file is empty or has the wrong name.
- **Port already in use** — another container or host process is bound to the same port. Change the host port in the compose file.
- **Volume path does not exist** — a bind-mount path on the host doesn't exist yet. Create the directory manually.
---
## The 1:1 path rule — volumes resolve to wrong paths
**Symptom:** Stacks deploy but relative volume paths (e.g. `./config:/config`) point to the wrong location inside the container, or `docker compose` exits with a path error.
**Cause:** Your `COMPOSE_DIR` is mounted at a different path inside the Sencho container than it has on the host.
**Fix:** Your compose volume mount and `COMPOSE_DIR` environment variable must use the **same absolute path** on both sides:
```yaml
# docker-compose.yml for Sencho itself
volumes:
- /opt/docker:/opt/docker # same path inside and outside
environment:
- COMPOSE_DIR=/opt/docker
```
See [Configuration — the 1:1 path rule](/getting-started/configuration#compose-directory-the-11-path-rule) for a full explanation.
---
## "Permission denied" on the Docker socket
**Symptom:** Sencho starts but shows errors accessing Docker, or the stack list is empty even though containers exist.
**Cause:** The Sencho container cannot read `/var/run/docker.sock`.
**Fix:** Ensure the socket is mounted:
```yaml
volumes:
- /var/run/docker.sock:/var/run/docker.sock
```
On Linux, the Docker socket is owned by the `docker` group. The Sencho entrypoint detects the socket's GID automatically and adds the internal `sencho` user to the matching group. If you see permission errors despite a correct mount, check that the socket file is readable:
```bash
ls -la /var/run/docker.sock
# Expected: srw-rw---- 1 root docker ...
```
If the group is not `docker`, the auto-detection still works — Sencho reads the GID from the socket file at startup.
---
## Login page shows "Something went wrong"
**Symptom:** You enter credentials and get a generic error instead of being logged in.
**Possible causes and fixes:**
| Cause | Fix |
|-------|-----|
| `JWT_SECRET` is not set or is empty | Set a non-empty value for `JWT_SECRET` in your environment |
| Container restarted and session cookie is stale | Clear browser cookies for the Sencho domain and try again |
| Rate limit triggered (5 failed attempts in 15 min) | Wait 15 minutes, or restart the container to reset the limiter |
---
## WebSocket connections fail (logs/console not streaming)
**Symptom:** The log viewer or host console shows a spinner that never resolves, or you see "Disconnected" immediately after connecting.
**Cause:** A reverse proxy is not forwarding WebSocket upgrade headers.
**Fix:** Add WebSocket support to your proxy config:
```nginx
# Nginx
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
```
Traefik handles WebSocket upgrades automatically for HTTP/1.1 backends — no extra config needed.
---
## Remote node shows "Offline" or "Unknown"
**Symptom:** A node you added shows a red or gray status dot.
**Checks in order:**
1. **Is the remote Sencho instance running?** SSH to that machine and verify.
2. **Is the API URL correct?** It must include the protocol and port (e.g. `http://192.168.1.20:3001`). Open it in a browser — you should see a JSON response from `/api/health`.
3. **Is the token correct?** Tokens are long JWT strings. Even one missing character will cause auth to fail. Regenerate the token on the remote instance and update the node config.
4. **Is there a firewall blocking the port?** The primary Sencho host must be able to reach the remote host's Sencho port.
Click the **wifi icon** on the node row to re-test connectivity after making changes.
---
## Forgotten admin password
Sencho has no password recovery flow. To reset the password:
1. Stop the Sencho container
2. Connect to the SQLite database directly:
```bash
sqlite3 /path/to/data/sencho.db
```
3. Delete the existing credentials so Sencho re-enters first-boot setup mode:
```sql
DELETE FROM global_settings WHERE key IN ('auth_username', 'auth_password_hash', 'auth_jwt_secret');
```
4. Restart the container — the setup screen will appear on next visit.
<Warning>
This resets authentication entirely. All active sessions become invalid. Your stacks, nodes, and alert rules are not affected.
</Warning>
---
## Checking the health endpoint
Sencho exposes a health endpoint for monitoring and container health checks:
```bash
curl http://localhost:3000/api/health
# {"status":"ok","uptime":12345.67}
```
A `200` response confirms the backend is running. Use this endpoint in your uptime monitor or load balancer health check.
---
## Getting logs from the Sencho container itself
```bash
docker logs sencho
# or, to follow:
docker logs -f sencho
```
The backend logs all route errors and service failures to stdout. This is the first place to look when the UI shows an error with no useful message.
+126
View File
@@ -0,0 +1,126 @@
---
title: Settings Reference
description: Complete reference for every option in the Sencho Settings Hub.
---
Open the Settings Hub by clicking **Settings** in the top navigation bar. The left sidebar lists all available sections.
<Frame>
<img src="/images/settings/settings-overview.png" alt="Settings Hub showing the Account tab and the full section sidebar" />
</Frame>
---
## Account
**Scope:** Global (applies to this Sencho instance, not per-node)
Change the admin account password. All three fields are required.
| Field | Description |
|-------|-------------|
| **Current Password** | Your existing password for verification |
| **New Password** | Must be at least 6 characters |
| **Confirm New Password** | Must match New Password |
Click **Update Password** to apply. The change takes effect immediately; existing sessions remain valid.
---
## System Limits
**Scope:** Per-node (applies to the currently selected node)
Configure resource thresholds that trigger visual warnings on the dashboard stat cards. These are display thresholds only — Sencho does not throttle or kill containers when limits are reached.
| Setting | Range | Description |
|---------|-------|-------------|
| **Host CPU Limit** | 1100% | CPU percentage above which the CPU card turns orange/red |
| **Host RAM Limit** | 1100% | RAM percentage above which the RAM card turns orange/red |
| **Host Disk Limit** | 1100% | Disk percentage above which the Disk card turns orange/red |
| **Docker Janitor Threshold** | ≥ 0 GB | Minimum free disk space to maintain. When free space falls below this value, a warning is shown. Set to `0` to disable. |
| **Global Crash Alerts** | On / Off | When enabled, Sencho sends a notification whenever any managed container exits unexpectedly |
Click **Save** to apply. An unsaved-changes indicator appears when you have edits pending.
---
## Notifications
**Scope:** Global
Configure external destinations for alert notifications. Three agent types are supported, each on its own sub-tab: **Discord**, **Slack**, and **Webhook**.
For each agent:
| Field | Description |
|-------|-------------|
| **Enable toggle** | Activates or deactivates this agent. Disabled agents receive no messages even if a URL is saved. |
| **Webhook URL** | The endpoint Sencho will POST to when an alert fires |
Click **Save** to persist changes. Click **Test** to send a test payload immediately and verify delivery.
At least one agent must be enabled for stack alerts to deliver notifications. See [Alerts & Notifications](/features/alerts-notifications) for how to create alert rules.
---
## Appearance
**Scope:** Global (stored in the browser, not the server)
| Setting | Options | Description |
|---------|---------|-------------|
| **Theme** | Light / Dark / Auto | `Auto` follows the OS system preference. Changes apply immediately without a page reload. |
---
## Developer
**Scope:** Per-node (applies to the currently selected node)
Advanced settings for log streaming behaviour and data retention. Most users can leave these at their defaults.
| Setting | Default | Description |
|---------|---------|-------------|
| **Developer Mode** | Off | When on, the [Global Observability](/features/global-observability) view switches from polling to real-time Server-Sent Events streaming |
| **Global Logs Refresh Rate** | 5s | Polling interval in standard mode. Options: `1s`, `3s`, `5s`, `10s` |
| **Metrics Retention Hours** | 24 | How many hours of CPU/RAM history to keep for dashboard charts. Max: 8,760 (1 year) |
| **Log Retention Days** | 30 | How many days of notification history to keep in the database. Max: 365 |
Click **Save** to apply.
<Note>
Lower refresh rates (1s) increase backend CPU usage as Sencho polls Docker more frequently. Use only when actively debugging.
</Note>
---
## Nodes
**Scope:** Global
Manage connections to local and remote Sencho instances. This is the same interface as the [Multi-Node](/features/multi-node) feature — see that page for the full walkthrough.
Quick reference:
| Action | How |
|--------|-----|
| Add a remote node | Click **+ Add Node** |
| Generate a token for this instance | Click **Generate Token** |
| Test an existing node's connectivity | Click the wifi icon on any row |
| Edit a node | Click the pencil icon |
| Delete a node | Click the trash icon (remote nodes only) |
---
## App Store
**Scope:** Global
| Setting | Default | Description |
|---------|---------|-------------|
| **Template Registry URL** | LinuxServer.io registry | The URL Sencho fetches templates from. Must return a JSON array in Portainer v2 template format. Leave blank to restore the default. |
Click **Save** to update the URL. Click **Refresh Cache** to clear the cached template list and fetch fresh data from the registry immediately.
See [App Store](/features/app-store#custom-template-registry) for more on custom registries.