Repository navigation
Releases: cthiriet/sitesolide
Release list
sitesolide 0.4.1
sitesolide 0.4.1 closes a site on every address it answers on: Restricted and Anyone with the code now close its own domain too, and an app's other names send to that domain.
curl -fsSL https://github.com/cthiriet/sitesolide/releases/latest/download/install.sh | sh
sitesolide upgrade --dry-run
sitesolide upgradeUpgrading: read A site closes on every address first. Then deploy each app that serves its own domain, and, for a site that opens with a code and serves its own domain, run sitesolide lock in its folder once.
Closing a site closes it everywhere
- Restricted puts the portal in front of the site's own domain as in front of its preview. A domain's visitors are judged on the site's people, password access included, with a cookie for the domain alone.
- Anyone with the code closes the domain too, static sites included, and the link that carries the code leads to the domain once it is active.
- The dashboard offers all three accesses to a site on its own domain, names both addresses before a change, and the gatekeeper checks both over HTTPS before it says the change is done, restoring everything otherwise.
- A block says whose site it serves, and the locks and the portal read that, never a domain's name: a domain switched on later closes the moment it is served. The portal reads the site from the one header every protected block overwrites, so a visitor cannot choose whose people judge them.
A domain's other names
- An app's aliases, and the
wwwthe table adds to a second level domain, now answer with a permanent redirect to the domain. They used to reach Caddy's block of static files, which never wakes an app: every/api/*was a 404 on an app'swww. - That block refuses an app's folder outright: a name the table still carries and the app's block no longer claims answers 404 instead of the app's files.
Guards
sitesolide domain --activateon a closed site waits until what Caddy serves closes the domain: the block its manifest generates, a Caddyfile that closes static domains, a code's stanza in the current form.- A deployment refuses a domain name, an alias or their
wwwanother project declares, and an alias in the served zone. - Measured in a real Caddy in front of the real portal (
bin/tests/cli-domain-caddy.test.ts), then on a test machine against a real domain and certificate.
sitesolide 0.4.0
sitesolide 0.4 gives a team one simple model of who may do what, backs up any kind of application, PostgreSQL included, with restic, and upgrades a running machine in one command.
curl -fsSL https://github.com/cthiriet/sitesolide/releases/latest/download/install.sh | sh
sitesolide upgrade --dry-run
sitesolide upgradeUpgrading: read docs/upgrading.md first, its three sections for this release in order: Access: one registry, Anyone with the code, from the dashboard and Backups stored by restic. An app that reads X-Sitesolide-Role must be updated before the portal is: its values member and guest are gone.
sitesolide upgrade
- One command brings every component of a running machine to the release, measuring what is installed rather than trusting version numbers, and redeploys only what differs, in the order a running machine accepts.
--dry-runreads the machine and changes nothing. A failure stops at its component; running it again resumes there.
Who may do what: one model
- Each project has a general access, Public, Restricted or Anyone with the code, and people with access, each with one role on a ladder: Can open, Viewer, Developer, Admin. Sharing, guests, members and the team page are gone, merged into that one list.
- People sign in to the dashboard with the company's accounts, through the portal, and manage the projects their role reaches: secrets, deployments, restores, access. The owner keeps the machine.
- Every token belongs to a person and never exceeds their roles; it is revoked when they leave.
- One registry, held by the steward as root, every change of access kept 180 days for audit. The portal reads a projection of it. The stores from before are carried over at the first start.
- Anyone with the code is chosen from the dashboard like the other two;
sitesolide lockand the dashboard go through the same gatekeeper.
Backups for any kind of application
- A service's server database is saved consistently. A service declares the folder it keeps live and a command that leaves a consistent copy of it,
pg_basebackupfor PostgreSQL; the snapshot holds that copy instead of files copied while the server writes. A running PostgreSQL or MongoDB found undeclared fails its project's snapshot loudly rather than archiving something that would not restore. docs/manifest.md - Stored by restic. An hour stores what changed, compressed and deduplicated, instead of a whole archive; a repository on the machine serves restores, and a second one in an S3 or Backblaze B2 bucket is filled by
restic copy, encrypted with your passphrase and readable by stock restic with the machine gone. Dailyrestic checkof both, which the monitor watches;bin/deploy-backup.sh checkruns it now. The archives of 0.3 are imported once, proven byte for byte. dashboard/src/backup/README.md - Proven on Debian 13 with PostgreSQL 17 and restic 0.18 while rows were written, then against a real B2 bucket: restores from the machine and from the bucket alone, a killed copy, a stale lock, the checks.
One look
- Graphite surfaces and a petrol brand across the dashboard, the portal's pages and the logo; each secret file is its own card.
Fixes found on the way
- What a deployment sends gets the modes its readers need: a file sent 0600 no longer stops the service that reads it.
- A data file replaced during a backup is archived as the version opened, not the one listed.
- The lock commands shown in the dashboard are the CLI's, not the script it runs.
- The release kit no longer expects a module the portal stopped borrowing.
sitesolide 0.3.1
A fix for every installation since the zone moved to /etc/caddy/sitesolide.env.
sitesolide domain --activatealways failed with "invalid configuration, previous table restored", andsitesolide lockcould not close a preview:bin/generate-domains.shandbin/lock.shvalidated Caddy's configuration with the Cloudflare token alone, without the zone, so every address of the Caddyfile was empty and the validation refused any change. Nothing was broken on the machine: the previous table or lock was restored each time. Both now load the two files systemd hands Caddy, asbin/deploy-caddy.shalready did, and a test holds every script to it. Found activating a domain on a running machine.- The README is now short and for people;
llms.txtis the entry point for agents, with the deploy loop, the--jsoncontract, MCP, team tokens and the rules an agent never breaks.
Nothing to do on a running machine: the next domain --activate or lock from 0.3.1 works.
sitesolide 0.3.0
sitesolide 0.3 makes installing as easy as deploying: one executable, and a machine from nothing to serving in a few minutes.
curl -fsSL https://github.com/cthiriet/sitesolide/releases/latest/download/install.sh | sh
sitesolide machine create --provider hetzner --name web
sitesolide setup root@203.0.113.10 --zone example.com --email you@example.com
cd your-project && sitesolide deployUpgrading: nothing on a running machine changes, and nothing needs redeploying. Read docs/upgrading.md for what to do with the Terraform state a 0.2 workstation holds: never run terraform apply or destroy against it from a 0.3 checkout.
One executable
sitesolideships as one binary per platform (macOS and Linux, arm64 and x64), checked againstSHA256SUMSbyinstall.sh. It needs neither Bun nor a clone of this repository: the scripts, the Caddy configuration and the server components it deploys are inside, the dashboard and the portal already built.- The unpacked kit is read-only and versioned under
~/.cache/sitesolide/; deploying a component works on a throwaway copy. sitesolide --version, andhelpwithout any configuration.
A machine in one command
sitesolide machine create|list|destroy --provider hetznerorders the VM, its firewall and SSH key from Hetzner's API, with no Terraform and no state file. A refused order says which types are available instead, and undoes what it created. docs/machine.md
Installed in one command
sitesolide setup <user@host>does the whole install on any fresh Debian 13 machine: hardening (a deploy account, ssh by key only and closed to root, ufw, fail2ban, automatic security updates), the DNS records through Cloudflare's API, Caddy with its DNS module, Bun, and every service of the platform in the order a fresh machine accepts. docs/setup.md- Resumable and idempotent: run again after a failure, it continues at the step that failed; on an installed machine it reads everything and changes nothing, with no Caddy reload.
- It cannot lock you out: root login closes only after a login as the deploy account is proven, behind a timer that reopens it on failure, and fail2ban starts only once nothing setup does could get your address banned.
- It refuses to overwrite a configuration naming another server, and DNS records pointing elsewhere.
SITESOLIDE_CONFIG_DIRkeeps a second installation apart.
Measured on a blank Hetzner cx23: machine create in 40 s, then setup in 3 min 37 s, DNS records included, from nothing to the dashboard answering. A second setup run took 14 s, changed nothing and reloaded nothing, while a probe hitting every site each second saw no failed request.
Terraform leaves
infra/*.tf, cloud-init.yaml and bin/terraform.sh are gone: one machine, created once, did not need a state file, a second Cloudflare token, or a plan that could replace production when misread. One Cloudflare token now does both the records and the certificates.
Fixes found on the way
- Every deploy path resets a failed unit before restarting it: a release that crash-looped past systemd's start limit used to block the restart of its fix.
- From the binary, the api release landed unreadable by its service, and setup started a file the binary does not carry; both found and fixed on a real machine before this release.
setupnames a changed host key, which a provider reusing an address causes, and gives thessh-keygen -Rthat clears it.- The binary's first run unpacks its kit on macOS 15, which refuses to rename a read-only folder; found by the release build itself, on GitHub's macOS 15 runner.
- The release workflow can run by hand, building and checking everything without publishing, and logs the runner's toolchain.
sitesolide 0.2.1
Fixes for installing on a blank machine. Nothing changes for a machine already running 0.2.0: these scripts only failed where nothing was served yet.
Found by installing 0.2.0 from scratch on a test machine (Debian 13, Bun 1.4.2, Caddy 2.11.6) by following docs/install.md.
- Bun did not install. Its installer requires
unzip, which Debian 13's cloud image lacks. cloud-init installs it now, and the guide does for a machine made another way.user_datais ignored after creation, so Terraform plans no replacement of an existing machine. - The first
bin/deploy-caddy.shalways rolled back. A probe that got no answer read000000instead of000, so the two-minute wait for the first certificates never started; and with no project yet, the script queried the literal addresshttps://*.<zone>/. bin/deploy-steward.shfailed its own check when the dashboard was the only project, which is exactly where the guide deploys it.bin/deploy-loopback.sh closeundid the rule on a machine with no landing page yet, whose bare domain answers 404 by design. It shared the two probe defects above.
sitesolide 0.2: a cloud for small software
sitesolide 0.2 turns a one-person deployment tool into a small cloud for the software a team and its agents write: internal tools, dashboards, prototypes, with a handful of users each, on one server you own.
Upgrading a running machine: follow docs/upgrading.md. It was rehearsed on a machine installed from 0.1 with a probe hitting every site every second. Read its first table before deploying anything: the CLI is stricter, and some manifests that passed in 0.1 are refused.
For teams
- Sign in with your company account. Any OpenID Connect provider (Google Workspace, Microsoft Entra, Okta, Keycloak) in front of every protected site, through the portal. Apps receive
X-Sitesolide-User,X-Sitesolide-User-NameandX-Sitesolide-Role, with client-supplied values stripped on every block. portal/README.md - Share a site like a doc. Only admins, specific people, or everyone at a domain, from the dashboard's Sharing section,
sitesolide share alice@acme.com, the control API or MCP. A removal closes the door at the next request. - Deploy without root. Personal, scoped, revocable team tokens from the dashboard's Team page;
sitesolide login; the control API under/api/v1/; a root one-shot installer that never parses the archive itself. Private by default. docs/team.md - One audit log. Deployments, token changes, sharing, sign-ins, guest accesses, secrets, connectors and restores, on the Activity page, filterable and exportable.
For agents
sitesolide detectwrites the manifest a folder implies (static sites, generators, Bun and Node, FastAPI and Flask, Go);deploy --yesaccepts it.- Every command speaks
--json, with a hint on every refusal. sitesolide mcpserves detect, deploy, status, logs, lock status, sharing and share to any MCP client.- A Claude Code skill and
llms.txt. docs/agents.md
For the environment a company wants
- Egress you decide.
"egress": ["api.example.com"]lets a project reach only the hosts it lists, through a proxy that identifies the caller by its uid and refuses private, loopback, metadata and the machine's own addresses. - Connectors. A credential an admin defines and grants; the app calls
$SITESOLIDE_CONNECTORS/<name>/...and never holds it. egress/README.md
Data and reliability
- Backups. Every project's data snapshotted hourly, SQLite copied consistently, optionally to an S3-compatible bucket encrypted on the machine, and restored from the dashboard in one click, with the current data saved first. dashboard/src/backup/README.md
- Caddy restarts itself. The package's unit sets no
Restart=; the drop-in now setsRestart=always, proven on a test machine against the outage of 11 August 2026. - A monitor. Every minute: Caddy, every site over HTTPS, units, disk, memory, certificates, backups; a healthchecks.io-style heartbeat that notices the machine itself dying, and Slack, Discord, Google Chat or ntfy alerts. monitor/README.md
Security
Four reviews of the new code, each finding fixed with a test. Among them, a manifest's header value could hand Caddy's environment (the Cloudflare token) to whoever wrote it; a start beginning with + ran as root; spaces in an env value added assignments; install ran with the deployment account's sudo; .git and .env files could be published; a slug could replace a system unit. All are refused or confined now, on the SSH path and the token path alike.
Installing from nothing
The install guide was followed to the letter on a blank machine, and every gap fixed: Caddy with its DNS module and Bun are now installed by the guide, the scripts lay what a first install lacks, and the steps come in the order a fresh machine accepts. docs/install.md
Known limits
- Every Caddy reload resets the connections being opened at that instant, for about a tenth of a second; this predates 0.2.
- Connectors inject one header over HTTP; keys in a query string, OAuth refresh and non-HTTP services are not supported yet.
- Bun 1.4 no longer takes the TLS server name from the Host header: 0.1's gatekeeper probes fail under it, 0.2's do not. Upgrade sitesolide before upgrading Bun on a machine.
sitesolide 0.1
The state 0.2 upgrades from: one machine, every site. See docs/upgrading.md in 0.2.