Installation
Provisions a Castis binary (Streamer or cproxy, with more planned) onto a target Linux node over SSH — no agent required on the target. Used to bring a blank node (bare-metal, VM, or container) into service, without manually SSHing in and running install steps by hand.
The installer is binary-agnostic: a single Install() function in pkg/installer is driven by a BinaryProfile (tarball layout, dependency list, lib paths, start command) per binary type. Streamer and cproxy currently share this path; ELB/GSLB are the natural next additions.
API base: /api/v1/distribution/{streamers,caches}/
Pages: /distribution/streamers/:id, /distribution/caches/:id
Concepts
Node — any Linux host reachable over SSH, with nothing running on it yet. Can be a real bare-metal/VM server (sshd already running by default) or a test container (sshd must be bolted on explicitly — see Test Fixture Nodes below).
Row — a Streamer or Cache record in CoreAPI, registered before install via the normal POST /streamers or POST /caches flow. Install always runs against an existing row by ID — registration and provisioning are separate steps, for both binary types.
BinaryProfile — the per-binary specifics the generic installer needs: vendor tarball layout, binary name inside the tarball, install directory, OS dependency list, whether shared libs need ldconfig, and the start command shape. StreamerProfile and CproxyProfile are the two profiles defined today.
SSH credentials — supplied fresh on every install/uninstall call, never stored. There is no "saved credentials" concept anywhere in this flow — the install form always starts blank, regardless of which binary type is being installed.
installStatus — lifecycle on the row: not_installed → pending → installing → installed | failed. installStage shows the current step while installing. The exact stage sequence differs slightly per profile — see Key API Endpoints below.
Testing Installation
Register the node
Streamer: navigate to http://localhost:15173/distribution/streamers, click Add Streamer, fill in name/host/httpPort/apiPort.
Cache (cproxy): same flow at http://localhost:15173/distribution/caches.
Either way, the row appears with status: unknown and installStatus: not_installed. Expected — registration and installation are independent, and a ping at this point correctly reports offline.
Streamer or cache list showing a freshly registered, not-yet-installed node
Install — detail page
Navigate to the row's detail page, e.g. /distribution/streamers/7 or /distribution/caches/2.
- Click Install
- Form prompts for SSH credentials every time —
sshHost,sshPort(default 22),sshUser, and eithersshPasswordor a pasted private key httpPort/apiPortdefault to the row's existing values but are editable — rendered into the target's config- Submit — request returns immediately (
202), install runs in the background
The form is identical for both binary types — only the endpoint it posts to differs (/streamers/:id/install vs /caches/:id/install).
Install form — credentials are never pre-filled or remembered, regardless of binary type
Watching progress
The page polls install status automatically. Stage sequence differs by profile:
Streamer:
connecting → installing_dependencies → uploading_tarball → uploading_config
→ uploading_media → extracting → configuring_libs → starting → installed
Cproxy (shorter — no bundled sample media, no shared lib config needed):
connecting → installing_dependencies → uploading_tarball → uploading_config
→ extracting → starting → installed
A streamer install typically takes 30–90 seconds, dominated by installing_dependencies (longer package list — ffmpeg/codec-related shared libs). Cproxy installs noticeably faster — its dependency list is just ca-certificates, curl, iproute, tar.
If it lands on failed, the error message shown is the actual failing command's output, e.g. "dependency install failed: ... Problem: problem with installed package curl..." (resolved by adding --allowerasing to the dnf install — see Test Conditions below) or "extract failed: ... mv: cannot stat ...".
Live stage indicator — stage list length differs by binary type
Verify the install actually worked
installStatus: installed confirms the shell commands completed without error — it does not by itself confirm the process is serving traffic. Always follow up with a ping (works for both binary types, even though cproxy has no /api/ping — PingCache already accounts for this and calls /api/version instead):
curl -X POST http://localhost:3000/api/v1/distribution/streamers/7/ping
curl -X POST http://localhost:3000/api/v1/distribution/caches/2/ping
If installed but ping comes back offline, check the remote log directly:
ssh -p 2222 deploy@localhost "cat /castis/bin/streamer/streamer.log"
ssh -p 2222 deploy@localhost "cat /castis/bin/cproxy/cproxy.log"
For streamer specifically, confirm the sample media is being served (auto-exposed via doc-roots, no manual stream registration needed):
curl http://localhost:17080/kitchen.mp4/manifest.mpd
For cproxy, hit its config endpoint directly (also the Dockerfile's own healthcheck target):
curl http://localhost:8081/api/config
A 200 confirms the process is genuinely live, not just reporting installed.
Reinstall
Calling Install again on an already-installed row is safe for both binary types — the flow is idempotent. The starting stage always stops any existing process (pkill -f) before relaunching, so reinstalling doesn't leave two competing processes bound to the same port.
If an install is already pending/installing for a row, a second Install call is rejected with 409 Conflict rather than racing — this guards against double-clicking the button or having two tabs open against the same node, for either binary type.
Uninstall
From either detail page, Uninstall prompts for the same SSH credential shape as install, plus a purge toggle:
- Purge off (default) — stops the process, leaves install/data directories in place
- Purge on — additionally removes the binary's install directory (and, for streamer, the doc-root/stream-root/streamerdb data dirs)
curl -X DELETE "http://localhost:3000/api/v1/distribution/caches/2/install?purge=false" \
-H "Content-Type: application/json" \
-d '{"sshHost":"node_x","sshPort":22,"sshUser":"deploy","sshPassword":"deploy123"}'
After uninstall, ping the row again — expect offline.
Test Fixture Nodes (local/dev only)
A single blank Rocky9 container can serve as the install target for both binary types — installing streamer and cproxy onto the same node simultaneously, on different ports, is a normal and supported test configuration (this is how the current setup is verified: one fixture, two installs, two ping targets).
# cdn/node-x/Dockerfile
FROM rockylinux/rockylinux:9
RUN dnf install -y openssh-server sudo tar gzip passwd \
&& dnf clean all \
&& ssh-keygen -A
RUN useradd -m -s /bin/bash deploy \
&& echo "deploy:deploy123" | chpasswd \
&& echo "deploy ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/deploy
RUN sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]
# compose/infrastructure/cdn.yml
node_x:
build: ./cdn/node-x
container_name: streamer_x
networks:
- internal
- cdn_net
ports:
- "17080:18080" # streamer http
- "17081:18081" # streamer api
- "8080:8080" # cproxy listen
- "8081:8081" # cproxy api
- "2222:22" # manual SSH debugging only — CoreAPI reaches via `internal`, not this port
restart: unless-stopped
Note the service key is node_x (binary-agnostic naming — this fixture isn't "a streamer" or "a cproxy," it's a blank node that happens to run both during testing) while container_name stays streamer_x for now; align these if it causes confusion later, but functionally Docker resolves both to the same container regardless.
Real target nodes (bare-metal, VM) need none of the Dockerfile setup — sshd is already running as a normal part of the OS, and password auth should not be used there (use sshPrivateKey instead).
Test conditions covered
Verified against both StreamerProfile and CproxyProfile — useful as a regression checklist when changing the installer or adding a new profile.
| Condition | Expected result | Profiles verified |
|---|---|---|
| Fresh blank node, valid SSH creds | Reaches installed, ping returns online |
streamer, cproxy |
| Wrong SSH password | Fails at connecting, clear auth error, no side effects |
streamer |
| Unreachable host (bad hostname/DNS) | Fails at connecting with a DNS lookup error |
streamer |
| Host with nothing listening on the SSH port | Fails at connecting with connection refused |
streamer |
dnf install hits a pre-existing package conflict (e.g. curl vs curl-minimal) |
Fails at installing_dependencies with the real dnf error in installError — fixed by adding --allowerasing to the install command |
cproxy |
Vendor tarball has no lib/ directory |
configuring_libs stage is skipped entirely (profile-driven — len(profile.LibSubdirs) > 0 gate), no ldconfig attempted |
cproxy |
| Reinstall over an already-running process | Old process stopped first (pkill -f), new one starts cleanly on the same port |
streamer, cproxy |
| Two installs triggered against the same row simultaneously | Second call rejected 409, no interleaved/corrupted status |
streamer |
| Install completes but process crashes immediately after start | installStatus: installed but ping reports offline — always verify with ping, not install status alone |
streamer |
| Two different binaries installed onto the same node, different ports | Both run concurrently without interference; each profile's pkill -f only matches its own binary path, not the other's |
streamer + cproxy on node_x |
Key API Endpoints
POST /api/v1/distribution/streamers/:id/install start streamer install (async, 202)
GET /api/v1/distribution/streamers/:id/install/status poll streamer install stage/status
DELETE /api/v1/distribution/streamers/:id/install uninstall streamer (?purge=true|false)
POST /api/v1/distribution/caches/:id/install start cproxy install (async, 202)
GET /api/v1/distribution/caches/:id/install/status poll cproxy install stage/status
DELETE /api/v1/distribution/caches/:id/install uninstall cproxy (?purge=true|false)
POST /api/v1/distribution/streamers/:id/ping verify streamer liveness post-install
POST /api/v1/distribution/caches/:id/ping verify cproxy liveness post-install
Install request body (identical shape for both — only the host/port values and target endpoint differ):
{
"sshHost": "node_x",
"sshPort": 22,
"sshUser": "deploy",
"sshPassword": "deploy123",
"httpPort": 8080,
"apiPort": 8081,
"hasGpu": false
}
Full mechanics, the BinaryProfile shape, and known limitations are documented in pkg/installer.