Guide

Running several projects at once

ssg --watch serves one project. Four sites means four terminals, four scrollbacks and four things to remember to restart. ssg daemon is that, declared once.

1ssg daemon

It reads .ssg_projects from the current directory, starts every project in it, and keeps them running until you stop it.

The projects file

 1projects:
 2  - name: blog
 3    dir: /srv/blog
 4    port: 8801
 5
 6  - name: shop
 7    dir: /srv/shop
 8    port: 8802
 9
10  - name: docs
11    dir: ../docs-site          # relative to this file
12    config: .ssg.prod.yaml
13    args: ["--minify-all"]
14
15  - name: staging
16    dir: /srv/staging
17    disabled: true             # kept in the file, not running
Key Meaning
dir Required. The project root — where its config lives and where its build runs. Relative paths resolve against the projects file, so a checkout can be moved without editing it
name Identifies the project in the log and on reload. Defaults to the directory's base name
config The project's own config file, relative to dir. Empty lets ssg find it the way a plain build does
port Serves the project here. A port implies http: true — asking for one is the clearest statement that a server was meant
http Serve without naming a port, letting ssg choose. Only useful for one project: four sites on one machine want four numbers
host Bind address for this project's server
args Extra flags handed to the build verbatim — --minify-all, --drafts — so a project is not limited to what this file models
disabled Keep a project in the file without running it: the reason to comment one out, without commenting it out

Two projects may not share a name or a port, and a file that could not run is refused rather than half-started. A daemon that brings up three of four sites and says nothing about the fourth is worse than one that does not start.

Checking the file

1ssg daemon --once

Prints what would run and exits, leaving nothing behind — a projects-file check for CI:

   blog → /srv/blog    ssg [--watch --http --port=8801]
   docs → /srv/docs    ssg [--watch --config=.ssg.prod.yaml --minify-all]
   shop → /srv/shop    ssg [--watch --http --port=8802]
   (1 disabled)

Each project is an ordinary ssg --watch in its own directory. That is the point: the daemon adds supervision, not a second way to build a site.

Reloading

Save .ssg_projects and the fleet reconciles itself. A project whose settings did not change keeps running and keeps its port — only what actually changed is touched:

♻️  Reloading (.ssg_projects changed)
   ⏹️  docs restarting
   ▶️  docs (/srv/docs)
   ▶️  staging (/srv/staging)
   ✅ 4 project(s) running: [blog docs shop staging]

Editing one project's port must not rebuild the other three, and adding a fifth must not interrupt the four — otherwise "reload" is a restart wearing a different word.

SIGHUP reloads on demand, which is what a deploy script should send after writing the file:

1kill -HUP "$(pgrep -f 'ssg daemon')"

A projects file that no longer parses leaves everything running. The projects on disk are still serving, and stopping them over a typo would be the worse failure:

⚠️  .ssg_projects changed: yaml: line 4: did not find expected key
   Keeping the running projects — fix the file and save to retry.

What the daemon does about failures

  • A project that will not start is named and skipped; the rest come up. Three sites serving is better than none.
  • A project that exits on its own — a build that gave up on a bad config — is restarted. A site must not silently go unserved.
  • Stopping signals the project's whole process group, so a watch_runner child releases the port too. A project that ignores the signal is ended after a grace period, because the next start needs that port.

Output

Each project's own output is tagged, so one terminal carrying four builds stays readable:

[blog] 🔄 Loading content...
[shop] ✅ Site generated successfully to output/
[docs] 👀 Watching for changes in content, templates, data, config

--quiet silences the daemon's own lines; the projects still log.

Running it as a service

The daemon is a plain foreground process, so any supervisor can own it:

 1# /etc/systemd/system/ssg.service
 2[Unit]
 3Description=SSG — watch several projects
 4After=network.target
 5
 6[Service]
 7ExecStart=/usr/local/bin/ssg daemon --config /srv/.ssg_projects
 8ExecReload=/bin/kill -HUP $MAINPID
 9WorkingDirectory=/srv
10Restart=on-failure
11User=www-data
12
13[Install]
14WantedBy=multi-user.target

systemctl reload ssg then reconciles the fleet without stopping the projects that did not change.