feat(scheduled-ops): add scheduled operations for Team Pro users (#231)

Adds the ability to schedule recurring Docker operations (stack restarts,
fleet snapshots, system prunes) via cron expressions with full execution
history logging. Includes Run Now for on-demand execution.
This commit is contained in:
Anso
2026-03-28 23:56:37 -04:00
committed by GitHub
parent 89c4a9cb77
commit 31e1795af0
31 changed files with 1552 additions and 160 deletions
+1
View File
@@ -98,6 +98,7 @@
"features/fleet-backups",
"features/audit-log",
"features/api-tokens",
"features/scheduled-operations",
"features/sso",
"features/licensing"
]
+9 -9
View File
@@ -7,7 +7,7 @@ description: Generate scoped API tokens for CI/CD pipelines, scripts, and automa
API Tokens require a Sencho **Team Pro** license. Personal Pro and Community Edition do not include this feature.
</Note>
API tokens let you authenticate external tools CI/CD pipelines, deployment scripts, monitoring integrations without sharing user credentials. Each token is scoped to a specific permission level so you can follow the principle of least privilege.
API tokens let you authenticate external tools - CI/CD pipelines, deployment scripts, monitoring integrations - without sharing user credentials. Each token is scoped to a specific permission level so you can follow the principle of least privilege.
## Permission scopes
@@ -15,9 +15,9 @@ Every token is created with one of three permission levels:
| Scope | Allowed actions |
|-------|----------------|
| **Read Only** | `GET` requests only view stacks, containers, metrics, and settings |
| **Read Only** | `GET` requests only - view stacks, containers, metrics, and settings |
| **Deploy Only** | Everything in Read Only, plus stack operations: deploy, down, restart, stop, start, update |
| **Full Admin** | Full stack and container management all read and write operations on stacks, containers, images, and system metrics |
| **Full Admin** | Full stack and container management - all read and write operations on stacks, containers, images, and system metrics |
Choose the narrowest scope that fits your use case. A CI pipeline that only deploys stacks should use **Deploy Only**, not Full Admin.
@@ -42,7 +42,7 @@ These restrictions ensure that API tokens cannot escalate privileges or modify t
1. Open **Settings Hub** and navigate to the **API Tokens** tab (visible to Team Pro admins only).
2. Click **Create Token**.
3. Enter a descriptive name (e.g., "GitHub Actions deploy"), select a permission scope, and optionally choose an expiration period (30, 60, 90 days, or 1 year). Tokens without an expiration must be revoked manually.
4. Click **Create**. The raw token is displayed **once** copy it immediately.
4. Click **Create**. The raw token is displayed **once** - copy it immediately.
<Frame>
<img src="/images/api-tokens/api-tokens-overview.png" alt="API Tokens management view in Settings Hub" />
@@ -84,11 +84,11 @@ If a token attempts an action outside its scope, Sencho returns a `403` response
## Revoking a token
Click the trash icon next to any token in the API Tokens settings tab. Revocation is immediate any in-flight or future requests using the revoked token will receive a `401` response.
Click the trash icon next to any token in the API Tokens settings tab. Revocation is immediate - any in-flight or future requests using the revoked token will receive a `401` response.
## Security model
- **Hashed storage** Only a SHA-256 hash of the token is stored in the database. The raw token is never persisted.
- **Audit trail** All actions performed via API tokens are recorded in the [Audit Log](/features/audit-log) under the creating user's username.
- **Optional expiry** Tokens can be created with an expiration period (30 days, 60 days, 90 days, or 1 year). Tokens without an expiry must be revoked manually when no longer needed.
- **Scope enforcement** Permission checks happen at the middleware level before any route handler executes, ensuring consistent enforcement across all endpoints.
- **Hashed storage** - Only a SHA-256 hash of the token is stored in the database. The raw token is never persisted.
- **Audit trail** - All actions performed via API tokens are recorded in the [Audit Log](/features/audit-log) under the creating user's username.
- **Optional expiry** - Tokens can be created with an expiration period (30 days, 60 days, 90 days, or 1 year). Tokens without an expiry must be revoked manually when no longer needed.
- **Scope enforcement** - Permission checks happen at the middleware level before any route handler executes, ensuring consistent enforcement across all endpoints.
+2 -2
View File
@@ -44,8 +44,8 @@ Navigate to the **Audit** tab in the sidebar (visible to Team Pro admins only).
### Filtering
- **Username filter** Search for actions by a specific user
- **Method filter** Filter by HTTP method (POST, PUT, DELETE, PATCH)
- **Username filter** - Search for actions by a specific user
- **Method filter** - Filter by HTTP method (POST, PUT, DELETE, PATCH)
Pagination is built in for navigating large audit histories.
+1 -1
View File
@@ -27,7 +27,7 @@ You can upgrade directly from **Settings > License** in your Sencho dashboard. T
- **Community users** see both **Personal Pro** and **Team Pro** options with feature highlights and direct checkout links.
- **Personal Pro users** see a **Team Pro** upgrade card for when you need unlimited accounts.
- **Team Pro users** are on the highest tier no upgrade cards are shown.
- **Team Pro users** are on the highest tier - no upgrade cards are shown.
Clicking an upgrade button opens the Lemon Squeezy checkout in a new tab. After completing the purchase, you'll receive a license key by email.
+1 -1
View File
@@ -69,7 +69,7 @@ Create point-in-time snapshots of every compose file and environment file across
## Audit log
Track every mutating action across your Sencho instance with a searchable audit trail. See who deployed, stopped, deleted, or changed settings with timestamps, user attribution, and node context. Team Pro only. [Learn more →](/features/audit-log)
Track every mutating action across your Sencho instance with a searchable audit trail. See who deployed, stopped, deleted, or changed settings - with timestamps, user attribution, and node context. Team Pro only. [Learn more →](/features/audit-log)
## Licensing & billing
+1 -1
View File
@@ -53,7 +53,7 @@ Admins can manage accounts in **Settings → Users**. From there you can:
With a Team Pro license, users can also be created automatically when they log in via SSO (LDAP, Google, GitHub, or Okta). SSO users appear in the Users list alongside local accounts. They are assigned a role based on identity provider group membership or claim mapping.
SSO users cannot log in with a password they must always authenticate through their identity provider.
SSO users cannot log in with a password - they must always authenticate through their identity provider.
To set up identity provider authentication, see [SSO Authentication →](/features/sso).
+85
View File
@@ -0,0 +1,85 @@
---
title: Scheduled Operations
description: Automate recurring Docker operations like stack restarts, fleet snapshots, and system prunes on a cron schedule.
---
<Note>
Scheduled Operations requires a Sencho **Team Pro** license.
Personal Pro and Community Edition do not include this feature.
</Note>
## Overview
Scheduled Operations lets you automate recurring maintenance tasks across your infrastructure. Define a cron schedule, choose an action, and Sencho handles the rest - including a full execution history log so you always know what ran and when.
## Supported Actions
| Action | Target | Description |
|--------|--------|-------------|
| **Restart Stack** | A specific stack on a specific node | Restarts all containers in the stack via the Docker Engine API |
| **Fleet Snapshot** | All nodes | Creates a fleet-wide backup of all compose files and `.env` files |
| **System Prune** | A specific node (or the default node) | Prunes unused containers, images, networks, and volumes |
## Creating a Scheduled Task
1. Navigate to the **Schedules** tab in the top navigation bar (visible to Team Pro admins).
2. Click **New Schedule**.
3. Fill in the form:
- **Name** - a descriptive label (e.g. "Nightly staging restart").
- **Action** - choose Restart Stack, Fleet Snapshot, or System Prune.
- **Stack / Node** - if you chose Restart Stack, select the target stack and the node it runs on.
- **Cron Expression** - standard 5-field cron format. A human-readable preview appears below the input.
- **Enabled** - toggle the task on or off.
4. Click **Create**.
## Cron Expression Reference
Sencho uses standard 5-field cron expressions:
```
┌───────────── minute (059)
│ ┌─────────── hour (0–23)
│ │ ┌───────── day of month (131)
│ │ │ ┌─────── month (112)
│ │ │ │ ┌───── day of week (07, 0 and 7 = Sunday)
│ │ │ │ │
* * * * *
```
### Common Examples
| Expression | Description |
|-----------|-------------|
| `0 3 * * *` | Every day at 3:00 AM |
| `0 */6 * * *` | Every 6 hours |
| `0 3 * * 0` | Every Sunday at 3:00 AM |
| `30 2 1 * *` | 1st of every month at 2:30 AM |
| `0 0 * * 1-5` | Midnight on weekdays |
## Managing Tasks
- **Enable/Disable** - Use the toggle switch in the task list to pause or resume a schedule without deleting it.
- **Edit** - Click the pencil icon to update the task name, schedule, or target.
- **Delete** - Click the trash icon to permanently remove the task and all its execution history.
## Execution History
Click the history icon on any task to view its execution log. Each entry shows:
- **Timestamp** - when the task ran.
- **Status** - success or failure.
- **Duration** - how long the execution took.
- **Details** - output message or error description.
Execution history is retained for 30 days.
## How It Works
The Scheduler Service runs in the background and checks for due tasks every 60 seconds. When a task's next run time has passed:
1. The scheduler verifies your Team Pro license is active.
2. It executes the configured action using the same internal services that power the UI buttons (restart, snapshot, prune).
3. Results are logged to the execution history.
4. The next run time is recalculated from the cron expression.
If a task is still running from a previous execution, the scheduler skips it to prevent overlap.
+27 -27
View File
@@ -1,13 +1,13 @@
---
title: SSO & LDAP Authentication
description: Authenticate with your existing identity provider LDAP, Google, GitHub, or Okta.
description: Authenticate with your existing identity provider - LDAP, Google, GitHub, or Okta.
---
<Note>
SSO requires a Sencho **Team Pro** license. Personal Pro and Community Edition do not include this feature.
</Note>
Sencho Team Pro lets your team sign in using existing identity providers instead of managing separate credentials. SSO works **alongside** password authentication it does not replace it.
Sencho Team Pro lets your team sign in using existing identity providers instead of managing separate credentials. SSO works **alongside** password authentication - it does not replace it.
## Supported providers
@@ -25,7 +25,7 @@ Sencho Team Pro lets your team sign in using existing identity providers instead
1. User enters their directory username and password on the Sencho login page
2. Sencho binds to LDAP with a service account, searches for the user, then verifies their password
3. If this is their first login, a Sencho account is automatically created
4. Sencho issues a JWT and the user is logged in identical to a password login
4. Sencho issues a JWT and the user is logged in - identical to a password login
### OIDC / OAuth flow (Google, GitHub, Okta)
@@ -43,8 +43,8 @@ All OIDC flows use **PKCE** (Proof Key for Code Exchange) and a **state paramete
When a user logs in via SSO for the first time, Sencho automatically creates a local account:
- **Username** is derived from their identity provider profile (display name, email prefix, or login handle)
- **Role** is assigned based on [role mapping](#role-mapping) defaults to Viewer if no mapping matches
- **Password** is set to an unusable placeholder SSO users cannot log in with a password
- **Role** is assigned based on [role mapping](#role-mapping) - defaults to Viewer if no mapping matches
- **Password** is set to an unusable placeholder - SSO users cannot log in with a password
- **Seat limits** from your license apply. If admin seats are full, the user is downgraded to Viewer. If all seats are full, login is denied with a clear error message.
On subsequent logins, the existing account is reused. The user's email is updated if it changed at the provider.
@@ -76,8 +76,8 @@ If the user's ID token contains a `groups` claim with the value `sencho-admins`,
SSO can be configured two ways:
1. **Settings UI** Go to **Settings → SSO** in the Sencho dashboard. Enable providers, enter credentials, and test connections from the UI. Changes take effect immediately without restarting.
2. **Environment variables** Set `SSO_*` variables in your Docker Compose file. These seed the database on first boot. After that, the database configuration is authoritative.
1. **Settings UI** - Go to **Settings → SSO** in the Sencho dashboard. Enable providers, enter credentials, and test connections from the UI. Changes take effect immediately without restarting.
2. **Environment variables** - Set `SSO_*` variables in your Docker Compose file. These seed the database on first boot. After that, the database configuration is authoritative.
### Via Settings UI
@@ -114,12 +114,12 @@ Environment variables are useful for initial deployment or infrastructure-as-cod
| Variable | Default | Description |
|----------|---------|-------------|
| `SSO_LDAP_ENABLED` | `false` | Enable LDAP authentication |
| `SSO_LDAP_URL` | | LDAP server URL (e.g., `ldap://ldap.example.com:389` or `ldaps://ldap.example.com:636`) |
| `SSO_LDAP_BIND_DN` | | Service account DN for searching users |
| `SSO_LDAP_BIND_PASSWORD` | | Service account password (encrypted at rest in the database) |
| `SSO_LDAP_SEARCH_BASE` | | Base DN for user searches (e.g., `ou=users,dc=example,dc=com`) |
| `SSO_LDAP_URL` | - | LDAP server URL (e.g., `ldap://ldap.example.com:389` or `ldaps://ldap.example.com:636`) |
| `SSO_LDAP_BIND_DN` | - | Service account DN for searching users |
| `SSO_LDAP_BIND_PASSWORD` | - | Service account password (encrypted at rest in the database) |
| `SSO_LDAP_SEARCH_BASE` | - | Base DN for user searches (e.g., `ou=users,dc=example,dc=com`) |
| `SSO_LDAP_SEARCH_FILTER` | `(uid={{username}})` | LDAP filter template. Use `(sAMAccountName={{username}})` for Active Directory |
| `SSO_LDAP_ADMIN_GROUP_DN` | | DN of the group whose members receive the Admin role |
| `SSO_LDAP_ADMIN_GROUP_DN` | - | DN of the group whose members receive the Admin role |
| `SSO_LDAP_DEFAULT_ROLE` | `viewer` | Role assigned to LDAP users not in the admin group |
| `SSO_LDAP_TLS_REJECT_UNAUTHORIZED` | `true` | Whether to verify the LDAP server's TLS certificate |
@@ -128,25 +128,25 @@ Environment variables are useful for initial deployment or infrastructure-as-cod
| Variable | Default | Description |
|----------|---------|-------------|
| `SSO_OIDC_GOOGLE_ENABLED` | `false` | Enable Google SSO |
| `SSO_OIDC_GOOGLE_CLIENT_ID` | | OAuth client ID from Google Cloud Console |
| `SSO_OIDC_GOOGLE_CLIENT_SECRET` | | OAuth client secret (encrypted at rest) |
| `SSO_OIDC_GOOGLE_CLIENT_ID` | - | OAuth client ID from Google Cloud Console |
| `SSO_OIDC_GOOGLE_CLIENT_SECRET` | - | OAuth client secret (encrypted at rest) |
### GitHub OAuth
| Variable | Default | Description |
|----------|---------|-------------|
| `SSO_OIDC_GITHUB_ENABLED` | `false` | Enable GitHub SSO |
| `SSO_OIDC_GITHUB_CLIENT_ID` | | OAuth app client ID from GitHub Developer Settings |
| `SSO_OIDC_GITHUB_CLIENT_SECRET` | | OAuth app client secret (encrypted at rest) |
| `SSO_OIDC_GITHUB_CLIENT_ID` | - | OAuth app client ID from GitHub Developer Settings |
| `SSO_OIDC_GITHUB_CLIENT_SECRET` | - | OAuth app client secret (encrypted at rest) |
### Okta OIDC
| Variable | Default | Description |
|----------|---------|-------------|
| `SSO_OIDC_OKTA_ENABLED` | `false` | Enable Okta SSO |
| `SSO_OIDC_OKTA_ISSUER_URL` | | Okta issuer URL (e.g., `https://dev-123456.okta.com`) |
| `SSO_OIDC_OKTA_CLIENT_ID` | | Okta application client ID |
| `SSO_OIDC_OKTA_CLIENT_SECRET` | | Okta client secret (encrypted at rest) |
| `SSO_OIDC_OKTA_ISSUER_URL` | - | Okta issuer URL (e.g., `https://dev-123456.okta.com`) |
| `SSO_OIDC_OKTA_CLIENT_ID` | - | Okta application client ID |
| `SSO_OIDC_OKTA_CLIENT_SECRET` | - | Okta client secret (encrypted at rest) |
### General
@@ -163,22 +163,22 @@ Environment variables are useful for initial deployment or infrastructure-as-cod
If Sencho is behind a reverse proxy (nginx, Traefik, Caddy), you **must** set `SSO_CALLBACK_URL` to your external URL. Otherwise, OAuth callbacks will fail.
</Warning>
Set `SSO_CALLBACK_URL` to the URL users access Sencho from for example, `https://sencho.example.com`. Sencho uses this to construct the OAuth redirect URI that your identity provider calls back to.
Set `SSO_CALLBACK_URL` to the URL users access Sencho from - for example, `https://sencho.example.com`. Sencho uses this to construct the OAuth redirect URI that your identity provider calls back to.
If not set, Sencho auto-detects the URL from the request's `Host` header and protocol, which works for direct access but fails behind proxies that rewrite the host.
## Security
- **PKCE** All OIDC flows use `code_challenge_method=S256` to prevent authorization code interception
- **State parameter** A cryptographic random value protects against CSRF attacks on the OAuth callback
- **Encrypted secrets** LDAP bind passwords and OIDC client secrets are encrypted at rest with AES-256-GCM
- **No local password** SSO users are created with an unusable password hash. They cannot bypass SSO by using the password login form
- **Admin-only configuration** Only Team Pro administrators can enable or configure SSO providers
- **PKCE** - All OIDC flows use `code_challenge_method=S256` to prevent authorization code interception
- **State parameter** - A cryptographic random value protects against CSRF attacks on the OAuth callback
- **Encrypted secrets** - LDAP bind passwords and OIDC client secrets are encrypted at rest with AES-256-GCM
- **No local password** - SSO users are created with an unusable password hash. They cannot bypass SSO by using the password login form
- **Admin-only configuration** - Only Team Pro administrators can enable or configure SSO providers
## Troubleshooting
### LDAP connection refused
Verify the LDAP server is reachable from the Sencho container. If LDAP is on the host machine, use `host.docker.internal` (Docker Desktop) or the host's LAN IP address not `localhost`.
Verify the LDAP server is reachable from the Sencho container. If LDAP is on the host machine, use `host.docker.internal` (Docker Desktop) or the host's LAN IP address - not `localhost`.
### TLS certificate errors
If your LDAP server uses a self-signed certificate, set `SSO_LDAP_TLS_REJECT_UNAUTHORIZED=false`. For production, install a trusted certificate instead.
@@ -187,7 +187,7 @@ If your LDAP server uses a self-signed certificate, set `SSO_LDAP_TLS_REJECT_UNA
The redirect URI registered in your identity provider must exactly match what Sencho sends. Check:
1. `SSO_CALLBACK_URL` is set to your external URL (e.g., `https://sencho.example.com`)
2. The callback URL in your provider's settings is `https://sencho.example.com/api/auth/sso/oidc/<provider>/callback`
3. Protocol matches don't mix `http` and `https`
3. Protocol matches - don't mix `http` and `https`
### SSO buttons not appearing on login page
SSO providers only appear on the login page when they are both **configured** and **enabled**. Check Settings → SSO to verify the provider is active.
+7 -7
View File
@@ -63,13 +63,13 @@ Right-click or use the **⋮** button on any stack in the sidebar to access:
<img src="/images/stack-management/stack-context-menu.png" alt="Stack context menu" />
</Frame>
- **Alerts** configure metric-based alerting rules for this stack
- **Check for updates** manually trigger an image update check
- **Deploy** run `docker compose up -d`
- **Stop** stop all containers in the stack
- **Restart** restart all containers in the stack
- **Update** pull latest images and recreate containers
- **Delete** stop and remove the stack (admin only)
- **Alerts** - configure metric-based alerting rules for this stack
- **Check for updates** - manually trigger an image update check
- **Deploy** - run `docker compose up -d`
- **Stop** - stop all containers in the stack
- **Restart** - restart all containers in the stack
- **Update** - pull latest images and recreate containers
- **Delete** - stop and remove the stack (admin only)
## Converting a `docker run` command