None of this has ever been run on Windows.
The service wrapper, the process-group teardown and these scripts were all written and cross-compiled on macOS. No PowerShell in this directory has been executed, no
polyemesis.exehas been started, and no service has been registered with a real Service Control Manager. Treat every command below as a first draft that needs a throwaway VM, not as a runbook.Two failure modes to expect first, both described in
docs/INSTALL.md: FFmpeg not being on the service account'sPATH, and recordings being truncated on service stop (a service has no console, so the graceful-stop signal that makes FFmpeg finalise its file cannot be delivered — see Stopping the service below).If you need this working today, use Linux or Docker.
A console window you have to stay logged in to is not a deployment. These scripts register polyemesis with the Service Control Manager so it starts at boot, restarts on failure, and logs somewhere you can read after the fact.
# from an elevated PowerShell prompt. Download SHA256SUMS from the same
# release page as the .exe -- install.ps1 refuses an unverified binary.
.\install.ps1 -BinaryPath .\polyemesis.exe -Sha256SumsPath .\SHA256SUMSThe binary you are about to install is registered as a service running as
LocalSystem, so "it came over HTTPS from GitHub" is not the standard. Every
release publishes a SHA256SUMS asset beside the binaries, and install.ps1
will not install without checking against it:
.\install.ps1 -BinaryPath .\polyemesis.exe -Sha256SumsPath .\SHA256SUMS
# or, if you would rather paste the one line:
.\install.ps1 -BinaryPath .\polyemesis.exe -Sha256 <hash from SHA256SUMS>A mismatch always refuses; -AllowUnverified cannot get you past one, and
is not a workaround for it. It exists only for the case where you have no
checksum at all — a locally built polyemesis.exe, say — and it warns loudly.
This mirrors what scripts/install.sh has done on Linux for several releases.
Defaults:
| Binary | C:\Program Files\polyemesis\polyemesis.exe |
| Data | C:\ProgramData\polyemesis |
| Config | C:\ProgramData\polyemesis\config.yaml |
| Service account | LocalSystem |
| Startup | Automatic (delayed) |
| Web UI | http://localhost:8080 |
The data directory belongs under ProgramData, not under Program Files.
Program Files is read-only for services by design, and this directory is
written to constantly — the SQLite database, recordings, the secret box and any
generated TLS material all live there.
LocalSystem is the default because it needs no setup: it can write anywhere
and bind any port. It is also far more privileged than polyemesis needs.
To run as something smaller, pass -ServiceAccount. Whatever you choose has to
have all three of these, or the service will fail to start — or worse, start and
then fail the first time it tries to write a recording:
- The "Log on as a service" right. Grant it under
secpol.msc→ Local Policies → User Rights Assignment → Log on as a service. Built-in accounts (LocalService,NetworkService) and virtual service accounts (NT SERVICE\polyemesis) already have it. - Modify on the data directory.
install.ps1runsicaclsto grant this for any non-LocalSystemaccount, and warns if it could not. Read access alone is not enough: the database, recordings and TLS material are all written under it. - The right to bind the listening ports. Any service account can bind a
port above 1024. Ports below 1024 are not privileged on Windows the way
they are on Unix, but they are frequently already claimed — port 80 in
particular is often held by
http.sys(IIS, WinRM, or anything else using the HTTP Server API). If polyemesis terminates TLS itself it also tries to bind:80for the HTTP→HTTPS redirect and, inacmemode, for the Let's Encrypt HTTP-01 challenge. That bind failing is logged as a warning and HTTPS keeps serving, but ACME issuance will never complete until port 80 actually reaches this host.
Outbound connections to your streaming destinations need nothing special; Windows permits outbound by default.
install.ps1 adds two inbound rules scoped to the polyemesis executable:
- TCP
-WebPort(default 8080) for the web UI and API. -IngestProtocol-IngestPort(default TCP 1935) for ingest.
RTMP is TCP; SRT is UDP. If you switch the ingest mode in the UI, the
firewall rule does not follow — re-run with -IngestProtocol UDP -IngestPort <port>, or pass -SkipFirewall and manage the rules yourself.
A service does not inherit the PATH from your interactive shell. If FFmpeg is
only on your user PATH, the service will fail to start with a detection error
even though ffmpeg -version works fine in your terminal. Either put FFmpeg on
the system PATH or, better, set an absolute path in config.yaml:
ffmpeg:
binary: C:\ffmpeg\bin\ffmpeg.exe
probe: C:\ffmpeg\bin\ffprobe.exeA service has no stderr, so polyemesis writes to the Windows Event Log instead:
Get-EventLog -LogName Application -Source polyemesis -Newest 40Entries render with an "the description for Event ID … cannot be found" preamble. That is expected — polyemesis ships no compiled message resource, and the message text itself appears verbatim underneath.
On Stop, polyemesis drains the HTTP listener and then tears the FFmpeg
children down in order. The service reports STOP_PENDING with a wait hint
while it does that, so a Stop-Service or a Services-console stop will wait
for it.
Known limitation: a service stop truncates in-progress recordings. The
graceful stop is a CTRL_BREAK_EVENT, which is what makes FFmpeg flush and
write out its container index — and Windows delivers that only through a
console. A service has none, so the signal fails and the supervisor escalates
straight to terminating the process. An in-progress recording is left
unfinalised, and destinations drop rather than disconnecting politely.
Until that is fixed, if you record on Windows, stop the recording from the web UI and let it finalise before stopping the service. Running interactively from a console (see below) is not affected.
The fix is to allocate a console for the service process at startup; it is not implemented because it could not be tested. Tracked in the report accompanying this work.
A machine shutdown or reboot will not. Windows caps every service's
shutdown at WaitToKillServiceTimeout (5 seconds by default), regardless of
the wait hint. If you record and you reboot the host while recording, raise it:
Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control' `
-Name 'WaitToKillServiceTimeout' -Value '45000'The value is a string of milliseconds, and it is machine-wide — it delays every service on the box, so do not set it to something absurd. A reboot is required for it to take effect.
FFmpeg detection shells out to the binary and parses its banner. On a cold
filesystem, or with a virus scanner in the way, that can take several seconds.
The service reports START_PENDING with a rising checkpoint throughout, so the
SCM waits rather than declaring the start hung. You should not need to touch
ServicesPipeTimeout.
Installed recovery actions restart the service after 5 s, then 30 s, then 60 s, with the failure count resetting daily. The backoff is deliberate: a missing FFmpeg binary fails identically every time, and a tight restart loop only fills the event log.
.\uninstall.ps1 # keeps C:\ProgramData\polyemesis
.\install.ps1 -BinaryPath .\polyemesis.exe -Sha256SumsPath .\SHA256SUMSuninstall.ps1 never touches the data directory unless you pass -RemoveData,
and -RemoveData refuses a path that is a drive root, a top-level directory, or
one that holds neither polyemesis.db nor secret.key — the data directory is
whatever -DataDir says, so a typo or a tab-completed parent used to be a
recursive delete of it.
-Force skips both the on-air check and the typed confirmation.
-IgnoreLiveBroadcast skips only the on-air check, so ending a live broadcast
and skipping the confirmation are not the same keystroke.
Nothing here changes the console experience. Launched from a terminal, polyemesis detects it is not running under the SCM and behaves exactly as before, logging to stderr and stopping on Ctrl-C.