Sysible Linux Operations Platform (SLOP) is the front door to the Sysible suite. It puts one URL, one TLS certificate and one sign-in in front of every Sysible app on a host, and adds the pieces a platform needs once there is more than one app on it: a user store, an update console, a config time machine and a read-only window onto what everything has been doing.
It is deliberately thin. SLOP does not replace, fork or re-host the apps — it fronts them. Each app is still its own container stack, still built from its own official repository, and still works without SLOP in front of it.
What you get at https://<server-ip>/:
| Path | What is there |
|---|---|
/ | The portal — one card per installed app, each with a live health dot |
/controller/ | Sysible Controller |
/slep/ | Sysible Linux Engineering Platform |
/connect/ | Sysible Connect |
/flashback/ | Flashback — every version of every tracked config file |
/visualizer/ | Visualizer — activity, logs and fleet topology |
/login, /account | Sign in, and change your own password |
/admin | Administration — accounts, configuration, apps, software & services |
manuals/shots/slop/portal-signed-in.png and re-run python3 tools/generate_manuals.py slop.It is not a hypervisor, an orchestrator, or a replacement for the Controller. It does not manage the apps' lifecycle beyond updating them — starting, stopping and configuring a fleet is the Controller's job, and authoring automation is SLEP's.
are missing, via Docker's official script with a distribution-package fallback.
sudo. The installer re-executes itself under sudo if you forget.history. A fleet with large config sets will want more.
There is no DNS requirement and nothing to configure. SLOP answers on whatever address the host has.
From a clone of the SLOP repository, on the host that will run it:
git clone https://github.com/sysiblesoftware/Sysible-Linux-Operations-Platform slop
cd slop
sudo ./install.sh
That brings up the whole stack. Three forms are accepted:
| Command | What it does |
|---|---|
sudo ./install.sh | Apps and gateway — the whole stack |
sudo ./install.sh apps | Only Controller, SLEP and Connect |
sudo ./install.sh gateway | Only the gateway, portal, Flashback, Visualizer and updater |
The installer clones Controller, SLEP and Connect into /opt/sysible-src/<repo> and brings each up through the suite's unified sysiblectl CLI — which it also installs, so you can manage everything afterwards from one command. The gateway comes up from the checkout you ran it in.
Nothing is re-hosted. Every app is cloned from its own official repository and built from source on your host.
.env and survives every later rebuild.Open https://<server-ip>/ and you will be redirected to /login.
On first run SLOP creates one superuser. The username is admin unless you set SLOP_ADMIN_USER. If you did not set SLOP_ADMIN_PASSWORD, a random password is generated and printed once to the idp service's log:
sudo docker compose logs idp | head -20
You are required to change it at first sign-in. To skip that — only on a host where you set the password yourself — set SLOP_ADMIN_FORCE_CHANGE=0.
manuals/shots/slop/login.png and re-run python3 tools/generate_manuals.py slop.The portal is the root page. It shows one card per app, each with a live health dot, so "is Connect up?" is answered without opening Connect.
The dots are not guesses. The portal calls same-origin health paths (/healthz/controller, /healthz/slep, and so on) and the gateway proxies each to that app's own health endpoint. Same-origin avoids every cross-domain and CORS problem that a dashboard of links to other hosts would otherwise have.
A dot has three states: healthy, unhealthy, and still being checked. An app that is not installed on this host is not shown as broken — it is simply absent.
Administration → Accounts (/admin) is where users are created, roles are set, passwords are reset and accounts are removed. It is superuser-only.
There are three roles, and they mean the same thing in every app behind SLOP:
| Role | Can |
|---|---|
superuser | Everything, including managing accounts and updating software |
operator | Do the work — run jobs, restore configs, change app state |
auditor | Read. Nothing an auditor does changes anything |
manuals/shots/slop/admin-accounts.png and re-run python3 tools/generate_manuals.py slop.Any signed-in user can open Your account (/account) to change their own password. Minimum length is 10 characters by default (SLOP_MIN_PASSWORD_LEN). A sign-in lasts 12 hours (SLOP_SESSION_TTL).
Repeated failed sign-ins are throttled: 8 attempts per source in a rolling 5-minute window by default.
Administration → Configuration (/admin/settings) shows the settings this SLOP is actually running with, each with a line on what it does — the bootstrap admin, password policy, session lifetime, login throttling and the upstreams.
It is read-only on purpose. These are environment variables set on the containers, so the file on disk and the running service can never disagree about what is in effect: the page reports what the process holds, not what somebody meant to set. To change one, edit the compose .env and recreate the stack.
manuals/shots/slop/admin-configuration.png and re-run python3 tools/generate_manuals.py slop.Administration → Apps (/admin/apps) hosts each app's own administration UI in place, so the settings that belong to the Controller stay in the Controller rather than being copied into a second screen that drifts out of date.
Administration → Software & services (/admin/updates) is the update console for everything on the host: Controller, SLEP, Connect and SLOP itself.
Each product shows one line of state — up to date, an update available with the two commits, or why it cannot be checked — plus whether its containers are running. Each row offers:
last. One product failing does not stop the rest.
wedged service can be recovered without a shell on the host.
manuals/shots/slop/admin-updates.png and re-run python3 tools/generate_manuals.py slop.An update is a real job with a live log. Availability is remembered for a few minutes so that merely browsing the console does not query every git remote on every page; Check now forces a fresh look.
A checkout with local modifications to tracked files is not updated, because a pull would overwrite them. The row says so and names the command that shows them. Untracked files — including the .env the installer writes — are not local modifications and do not block anything.
If the console says an update is available and git pull on the host says "Already up to date", check the path shown on the row: SYSIBLE_<APP>_DIR can point somewhere other than /opt/sysible-src/<repo>, and the two of you may be looking at different checkouts.
Flashback is the config time machine: every version of every tracked config file on every host, with a diff, a download, a viewer and a one-click restore. It ships inside the SLOP stack and is reached at /flashback/.
It has its own manual — see the Sysible Flashback guide.
Visualizer is the read-only window onto the platform: who did what across every app, the logs those apps expose, and the fleet drawn as a picture. It ships inside the SLOP stack and is reached at /visualizer/.
It has its own manual — see the Sysible Visualizer guide.
There is one origin and no DNS. SLOP answers on 443 for whatever address the host has, and everything lives on that origin addressed by path:
https://<server-ip>/ the portal
https://<server-ip>/controller/ → SLOP_CONTROLLER_UPSTREAM (host port 8800)
https://<server-ip>/slep/ → SLOP_SLEP_UPSTREAM (host port 8810)
https://<server-ip>/connect/ → SLOP_CONNECT_UPSTREAM (host port 8700)
https://<server-ip>/flashback/ → the flashback service
https://<server-ip>/visualizer/ → the visualizer service
Why paths and not subdomains. One origin means one session cookie and zero DNS: no apex record, no wildcard certificate, no /etc/hosts entries, and it works on a raw IP from any machine on the network. Each app is built with its prefix as its front-end base path, so the browser asks for /controller/assets/...; the gateway strips the prefix and the app sees its own root paths, cookies and websockets unchanged.
Port 80 exists only to redirect to 443.
Caddy owns TLS for every site, and there are three ways to run it.
Internal CA (the default). Caddy mints one certificate under a fixed internal name and serves it for every raw-IP request. This is why the first visit warns. To make the warning go away properly, export the root and trust it on the machines that will use SLOP:
sudo docker compose cp gateway:/data/caddy/pki/authorities/local/root.crt .
Public certificates. Point a real DNS name at the host, give the :443 site that name and set an ACME email in the global block. Caddy fetches and renews Let's Encrypt certificates automatically.
Your own certificate. Mount a certificate and key and use tls /path/cert.pem /path/key.pem.
The apps keep serving their own HTTPS internally; the gateway reaches them over the loopback interface and does not verify those internal certificates, which are self-signed by design.
In Community Edition SLOP owns identity. It ships a small identity provider with the user store, the login page and the account screens, and the apps behind it no longer show their own login.
On every proxied request the gateway asks the IdP whether the browser is signed in. A yes lets the request through; a no redirects to /login?next=… so you land back where you were going.
Before proxying, the gateway does three things, and the order matters:
X-Sysible-User, X-Sysible-Role andX-Sysible-Auth headers. A browser must never be able to assert who it is.
Each app honours an asserted identity only when its trust flag is on *and* the shared secret matches. Someone reaching an app directly cannot know the secret, so they cannot forge the headers — and if the secret is unset the apps fail closed and ignore identity headers entirely rather than trusting them.
.env at mode 0600, and it should never be passed on a command line, where any local user can read it out of /proc.Everything on the host is managed with one command. SLOP is a product like the others:
sysiblectl status # every product, at a glance
sysiblectl slop restart # restart the gateway stack
sysiblectl controller update # pull and rebuild one product
sysiblectl update all # everything
sysiblectl slop logs # follow a product's logs
The verbs are start, stop, restart, status, logs, update, rebuild, backup and destroy. rebuild is the one that builds images from source; start will build first if a product has never been built on this host.
SLOP keeps its state in named Docker volumes:
| Volume | Holds |
|---|---|
slop-idp-data | The user store — accounts, roles, password hashes |
slop-caddy-data | TLS certificates and the internal CA |
flashback-data | Every captured config version |
The app checkouts under /opt/sysible-src are ordinary git clones and can be re-cloned. The volumes cannot — back them up. sysiblectl slop backup captures the stack's volumes; losing slop-idp-data means recreating every account, and losing flashback-data means losing the config history.
The portal shows grey dots. The dot means "still checking". If it stays grey, that app's health endpoint is not answering — check sysiblectl status and the app's own logs. Grey is not the same as red: red means it answered and said it was unhealthy.
A browser warning on every visit. Expected with the default internal CA. Trust the root certificate (see TLS) rather than clicking through.
An app shows its own login page. Its SSO trust flag or shared secret is missing, so it has failed closed and fallen back to local authentication. Check that app's .env for SYSIBLE_SSO_SHARED_SECRET and its trust flag.
Sign-in says too many attempts. The throttle has tripped: 8 failures per source in 5 minutes by default. Wait it out or raise SLOP_LOGIN_MAX_ATTEMPTS.
An update will not start. See When an update is refused above.
Domain=,which is both the correct cookie for a raw IP and the thing that makes a single sign-in cover every app.
it is handed. Defence does not depend on the gateway alone.
ignored, not trusted. No pinned fingerprint means a vendor key is refused, not installed.
It therefore accepts exactly one thing from a caller: a key from a fixed allowlist. It never accepts a path, a repository, a branch or a command.
nosniff, a frame policy and a referrer policy.
your host, and vendor signing keys are pinned by fingerprint — an unrecognised key fails the build rather than being trusted.
| Port | Who | Notes |
|---|---|---|
| 80 | Gateway | Redirects to 443 |
| 443 | Gateway | The only published front door |
| 8800 | Controller | Its own console, fronted at /controller/ |
| 8810 | SLEP | Fronted at /slep/ |
| 8700 | Connect | Fronted at /connect/ |
| 9000 | Controller backend | The agent/API port |
The IdP, Flashback's console, the Visualizer and the updater are not published on the host. They are reachable only through the gateway, which is the point.
Set these in the compose .env next to docker-compose.yml.
| Variable | Default | Meaning |
|---|---|---|
SLOP_ADMIN_USER | admin | First-run superuser name |
SLOP_ADMIN_PASSWORD | generated | First-run password; printed once to the idp log if unset |
SLOP_ADMIN_FORCE_CHANGE | 1 | Require a password change at first sign-in |
SLOP_MIN_PASSWORD_LEN | 10 | Minimum password length |
SLOP_SESSION_TTL | 43200 | Sign-in lifetime, in seconds (12 hours) |
SLOP_LOGIN_MAX_ATTEMPTS | 8 | Failed sign-ins per source before throttling |
SLOP_LOGIN_WINDOW_S | 300 | The window those failures are counted over |
SYSIBLE_SSO_SHARED_SECRET | generated | Proves a request came through the gateway |
SLOP_CONTROLLER_UPSTREAM | host.docker.internal:8800 | Where the Controller is |
SLOP_SLEP_UPSTREAM | host.docker.internal:8810 | Where SLEP is |
SLOP_CONNECT_UPSTREAM | host.docker.internal:8700 | Where Connect is |
SYSIBLE_SRC_DIR | /opt/sysible-src | Where the app checkouts live |
SYSIBLE_FLASHBACK_KEEP | 50 | Versions kept per file (see the Flashback guide) |