A small always-on Windows Service for Booth Fitness kiosks. It mirrors the
production HLS workout videos from Azure Blob Storage to local disk and serves
them from 127.0.0.1, so the gym app plays from local disk instead of streaming
from Azure on every view (avoiding repeat egress/bandwidth costs).
- On start, and then once per hour, downloads the HLS content from the production folder in Azure Blob Storage.
- Skips any file already present locally and unchanged (same size + not newer in blob) — steady-state runs download ~0 bytes.
- Mirrors the folder-per-video structure into
C:\Kiosk\Videos, and deletes local video folders no longer present in the blob. - Serves the videos over HTTP on loopback, e.g.
http://127.0.0.1:8080/videos/<video-folder>/index.master. - Logs a structured summary per run (checked / downloaded / skipped /
bytesDownloaded) so bandwidth can be monitored. (An Azure log sink is a
planned follow-up — see
Services/IDownloadActivityLog.cs.)
Non-secret settings live in appsettings.json. The blob
connection string is a secret and must not be committed — provide it via
user-secrets or a machine environment variable. (User-secrets are loaded in
every environment, including Production — see Program.cs.)
| Setting | Env var | Default | Notes |
|---|---|---|---|
BlobStorage:ConnectionString |
BlobStorage__ConnectionString |
(empty) | Secret. Storage account connection string. |
BlobStorage:ContainerName |
BlobStorage__ContainerName |
videos |
Container holding the content. |
BlobStorage:ProductionPrefix |
BlobStorage__ProductionPrefix |
production/ |
Blob prefix ("folder") to mirror. |
LocalVideos:Path |
LocalVideos__Path |
/Users/lukeanglea/Kiosk/Videos |
Local mirror root. Must be an absolute path for this OS. On the Windows kiosk set this to C:\Kiosk\Videos. |
Server:Url |
Server__Url |
http://127.0.0.1:8080 |
Loopback bind URL. |
Server:RequestPath |
Server__RequestPath |
/videos |
URL path videos are served under. |
Sync:IntervalHours |
Sync__IntervalHours |
1 |
Hours between runs (first run is immediate). |
Sync:MaxParallelDownloads |
Sync__MaxParallelDownloads |
4 |
Concurrent blob downloads. |
Sync:MaxVideosPerRun |
Sync__MaxVideosPerRun |
0 |
New video folders to download per run (0 = no cap). |
The startup log prints the resolved values so you can confirm the config:
Effective config — environment=Production | videosPath=... | container=videos | prefix=production/ | maxVideosPerRun=0 | connectionStringSet=True
Health check: GET http://127.0.0.1:8080/health → {"status":"ok"}.
-
Store the connection string in user-secrets — kept out of the repo. Quote the whole value; it contains
;, so an unquoted shell would truncate it:dotnet user-secrets set "BlobStorage:ConnectionString" "DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;EndpointSuffix=core.windows.net"
-
Run it:
dotnet run
Confirm the startup log shows connectionStringSet=True and a
Scanning ... under prefix 'production/' line. There is a single (Production)
configuration — no environment switching required.
# 1. Publish a self-contained build
dotnet publish -c Release -r win-x64 --self-contained -o C:\Kiosk\App
# 2. Put the connection string in C:\Kiosk\App\appsettings.Production.json
# (gitignored) or a machine environment variable, and set
# DOTNET_ENVIRONMENT=Production.
# 3. Create and start the service (auto-start on boot)
sc.exe create BoothFitnessVideoManager binPath= "C:\Kiosk\App\BoothFitnessVideoManager.exe" start= auto
sc.exe start BoothFitnessVideoManagerThe host uses UseWindowsService(), so the same executable runs as a console app
during development and as a service when installed.
Once the service is verified on a kiosk, point the gym app's video base URL at
http://127.0.0.1:8080/videos/ so it plays from the local mirror.