SManager
A simple server manager for self-hosted services, with VPN integration and support for local/systemd, Docker, and PHP runtimes.
When building a self-hosted setup, it’s common to end up with dozens of local services, Docker containers, and maintenance scripts scattered across your system. Tasks as simple as restarting or stopping a service can quickly become frustrating when there’s no clear overview of what is running, how components relate to each other, or how everything is managed.
SManager provides a simple and unified CLI for managing local, Docker, and PHP services, with built-in support for VPN integration and interactive metadata management.
Reseting all your music-related services becomes as simple as:
smanager group restart music
Features
- Unified service management for local/systemd, Docker, and PHP runtimes.
- Per-service custom start/stop/restart/status/update scripts.
- One-command updates (Docker Compose out of the box, opt-in git/composer heuristics).
- Update-availability detection in
status(Docker digests, git, GitHub releases, composer, scripts). - VPN integration (Tailscale provider support).
- Interactive metadata creation and editing, plus non-interactive
show/set/schemawith JSON output for panels and automation. - YAML-based global configuration.
- Rich service status reporting with optional JSON output.
Phylosophy
SManager is designed to work alongside your existing setup, not replace it. However here are some best practices to get the most out of SManager:
- Use one directory per service (service directory), put all the service-related files there (metadata, scripts, Dockerfiles, documentation, etc).
- Define one directory to store all the service directories (services root).
- Each time you create a new service, create a metadata file for it using
smanager metadata initand place it in its service directory.
SManager does not enforce any specific structure for your services, but following these guidelines will help you maintain a clear and organized setup that SManager can effectively manage. If you don't want to follow this structure, just point services_root to your $HOME and SManager will work with whatever it finds there.
Installation
pip install smanager
After installation, you need to configure SManager by running:
smanager config init
smanager config edit
An editor will open with the global configuration file. Set the services_root to the directory where you want to store your service directories, and configure the VPN provider if you are using one.
CLI Usage
Use this command pattern:
smanager <group> <command> [options]
Discover available commands at any level:
smanager --help
smanager <group> --help
smanager <group> <command> --help
Status Commands
Status is now a subcommand under each resource group:
smanager service status <service_id>
smanager group status <group_name>
smanager dependency status <dependency_name>
smanager all status
Optional JSON output is available for each status command:
smanager service status <service_id> --json
Info Commands
Info is available under each resource group and shows service name plus WebUI URL:
smanager service info <service_id>
smanager group info <group_name>
smanager dependency info <dependency_name>
smanager all info
Use --details to include extra operational columns:
smanager service info <service_id> --details
Metadata Commands
The metadata group provides both interactive workflows and non-interactive,
machine-friendly commands for service metadata files.
Non-interactive commands (for panels / automation)
These commands never prompt and are safe to run without a TTY. With --json,
they write a single JSON document to stdout and send human errors to stderr.
smanager --config /path/smanager.yaml metadata show <service_id> --json
smanager --config /path/smanager.yaml metadata set <service_id> --json # JSON on stdin
smanager --config /path/smanager.yaml metadata schema --json
metadata show <service_id> [--json]
Locates the service by id and returns its metadata file.
{
"id": "sonarr",
"path": "/srv/services/sonarr/sd_info.json",
"service_dir": "/srv/services/sonarr",
"metadata": { "...": "raw file content, unknown keys preserved" }
}
Without --json it prints a readable review table.
metadata set <service_id> [--file PATH] [--dry-run] [--json] [--allow-id-change]
Reads a metadata JSON object from --file or stdin (default), validates it with
the same rules as metadata validate, and writes it atomically (temp file in the
same directory + os.replace) keeping a <metadata>.bak backup. The received
object is written verbatim so unknown keys are preserved.
- Validation failure:
{"success": false, "errors": ["..."]}and exit code1(nothing is written). metadata.idmust equal<service_id>unless--allow-id-changeis passed.--dry-runvalidates and reports without writing (no backup either).- Success:
{"success": true, "path": "...", "warnings": ["..."], "dry_run": false}.
Errors such as a missing service, unreadable stdin, or an unknown config path are
also reported as {"success": false, "errors": [...]} when --json is used.
metadata schema [--json]
Prints a machine-readable description of the schema (sections, field paths and types, enums, defaults, and per-runtime requirements) suitable for generating forms in a UI.
{
"schema_version": 2,
"enums": { "runtime_type": ["docker", "local", "php"], "update_strategy": ["..."], "version_strategy": ["..."], "package_manager": ["..."] },
"scripts": { "actions": ["start", "stop", "restart", "status", "update", "check_update"] },
"runtime_requirements": { "docker": { "required": ["docker.compose_file"], "unless": ["scripts.start", "scripts.stop"] } },
"fields": [ { "path": "runtime.type", "type": "enum", "enum": "runtime_type", "required": true } ]
}
Interactive commands
The following commands are interactive (require a TTY).
Create Metadata
smanager metadata init
Optional output override:
smanager metadata init --output /path/to/sd_info.json
Edit Existing Metadata
smanager metadata edit
Edit a specific file directly:
smanager metadata edit --file /path/to/sd_info.json
Validate Metadata
Validate a single metadata file:
smanager metadata validate --file /path/to/sd_info.json
Validate all metadata files from a directory recursively:
smanager metadata validate --root /path/to/services
Use JSON output for CI/automation:
smanager metadata validate --root /path/to/services --json
Behavior summary:
- Interactive prompts with inline defaults.
- Runtime-aware sections (Docker, Local, and PHP).
- Multi-select support for
groupsanddepends_on. - Validation before writing changes.
PHP Runtime Metadata
For runtime.type: php, define the php section with:
start_command(required)stop_command(required)status_command(optional, if present takes precedence over healthcheck forstatus)healthcheck_url(optional, used by status checks)healthcheck_timeout(optional, defaults to 3)working_dir(optional)env(optional map of environment variables)
Custom Scripts
Any service can override start, stop, restart, status, and/or update
with a shell script. Provide any subset of them; the operations you do not
override keep using the default mechanism for the runtime type.
{
"scripts": {
"start": "scripts/start.sh",
"stop": "scripts/stop.sh",
"restart": "scripts/restart.sh",
"status": "scripts/status.sh",
"update": "scripts/update.sh",
"working_dir": ".",
"env": { "APP_ENV": "production" }
}
}
- Values are paths to
.shfiles, relative to the service directory (or absolute). - Scripts run with
bash(fallbacksh) fromworking_dir(default: the service directory), withenvmerged over the current environment. statusexit code drives the result:0= running, anything else = stopped.- A
restartscript owns the stop/start sequence; SManager still re-establishes the VPN HTTPS serve afterwards whenvpn.serve_httpsis enabled. - When
scripts.startandscripts.stopare both present,local.systemd_servicesanddocker.compose_filebecome optional.
Updating services
Update a single service, a group, a dependency, or everything:
smanager service update sonarr
smanager group update media
smanager dependency update qbittorrent
smanager all update --yes
Add --dry-run to print exactly what would run without executing anything
(smanager all update --dry-run does not require --yes).
Resolution order for update:
scripts.updateif defined — the script owns the whole update/restart.- Otherwise, the
updatemetadata section:docker(default for Docker services):docker compose pull --ignore-pull-failuresfollowed bydocker compose up -d --build.git:git -C <path> pull [origin <branch>], then an automatic restart.composer:composer --working-dir <path> update --no-interaction, then a restart.command: an inline command (shlex-split), which owns the update.none: disables updates explicitly.
{
"update": {
"strategy": "auto",
"path": "/opt/sonarr",
"branch": "main",
"command": "composer update --no-interaction"
}
}
With strategy: "auto" (opt-in), local/PHP services are inspected for a .git
directory or a composer.json, and the matching strategy is used. Local and PHP
services without scripts.update or an update section error out clearly.
Inactive services (active: false) are skipped by update, just like start.
Checking for updates
status can report whether a newer version is available. The check is opt-in
per service (a version section or scripts.check_update) and only runs when
--updates is passed:
smanager service status sonarr --updates
smanager all status --updates --json
The JSON status gains a nested update object:
"update": { "available": true, "current": "1.2.0", "latest": "1.3.1", "source": "github", "error": null }
How the current/latest versions are obtained, by version.latest.strategy:
script—scripts.check_updateprints the latest version on stdout (exit 0).command— an inline command prints the latest version;version.regexextracts it.github— GitHub Releases API (repo: owner/name, or derived from a github git remote);GITHUB_TOKEN/GH_TOKENraises the rate limit.git— compares the local checkout with the remote branch (git fetch+ ancestry).composer—composer outdated --direct --format=json.docker— compares local image digests with the registry (docker buildx imagetools/regctl/crane).package—apt/dnf/pacman/flatpakusingpackage_manager+package.auto— Docker uses the docker strategy; local/PHP detect a git remote (github → API) or a git/composer project.
{
"version": {
"current_command": "sonarr --version",
"regex": "v?([0-9]+\\.[0-9]+\\.[0-9]+)",
"latest": { "strategy": "auto" }
}
}
Current version resolution order: version.current_command, otherwise a git tag
(git describe --tags) / short SHA when the service is a git repo. If the current
version cannot be determined, available is null (unknown) instead of guessing.
Network/rate-limit/parse failures are reported in update.error and never break
status. The timeout can be tuned with SMANAGER_CHECK_TIMEOUT (seconds).
HTTPS over VPN without passwords
By default SManager runs the privileged Tailscale operations non-interactively and never blocks waiting for a password:
- It first tries the command as the current user (Tailscale operator mode).
- It falls back to
sudo -n(non-interactive; requires NOPASSWD).
To avoid sudo entirely, once per machine run:
sudo tailscale set --operator=$USER
Alternatively, grant passwordless access to the Tailscale binary:
echo "$USER ALL=(root) NOPASSWD: $(command -v tailscale)" | \
sudo tee /etc/sudoers.d/smanager-tailscale
sudo chmod 0440 /etc/sudoers.d/smanager-tailscale
start and restart (re)create the Tailscale HTTPS serve when
vpn.serve_https is true; stop takes it down. If the VPN is disconnected it
is brought up first. Failures are reported as clean errors, and a failing VPN
serve never leaves the service itself down.
Notes
metadata initcreates a new file.metadata editmodifies an existing file.metadata validatechecks script paths and runtime/version-specific rules.- Use command-specific
--helpfor the latest options and examples.
Metadata
Release files for smanager 0.9.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| smanager-0.9.0.tar.gz | 60.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| smanager-0.9.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 128.1 kB
Release files / smanager-0.9.0.tar.gz
| Download URL | smanager-0.9.0.tar.gz |
|---|---|
| Size | 60.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2379dba25f8b5362666b1dda08edb1b067cefb403079b5dbb78713d929668574
|
|
BLAKE2b-256 checksum How to use checksums |
172b315a84e6886d1b3a29cbe7010b605fb68343cbc87f34b935bb15691b93e5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.11
|
Release files / smanager-0.9.0-py3-none-any.whl
| Download URL | smanager-0.9.0-py3-none-any.whl |
|---|---|
| Size | 68.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ea699a288ac06edf8039ac67b3d6538431a506753d5092b8123cb56bb72e6e25
|
|
BLAKE2b-256 checksum How to use checksums |
c062e7b95d7f561f2a9cef4155cd808b1ad5f100c1e58594dd5871081455eac5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.11
|