Skip to content

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_installedpendinginstallinginstalled | 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.

Distribution list page 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.

  1. Click Install
  2. Form prompts for SSH credentials every time — sshHost, sshPort (default 22), sshUser, and either sshPassword or a pasted private key
  3. httpPort/apiPort default to the row's existing values but are editable — rendered into the target's config
  4. 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 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 ...".

Install progress badge 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/pingPingCache 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.