The public page was install instructions and little else. It now shows what Runix actually looks like and says what it needs, so someone can decide whether to run the install command before running it.
332 lines
12 KiB
Markdown
332 lines
12 KiB
Markdown
<div align="center">
|
|
|
|
# Runix
|
|
|
|
**One console for your whole fleet.**
|
|
Containers, system services and custom daemons — managed the same way, on every host.
|
|
|
|
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Frunix-releases.s3-alpha-web.astra-dev.online%2Flatest.json&query=%24.version&label=release&color=3fb950" alt="Latest release">
|
|
<img src="https://img.shields.io/badge/platforms-linux%20amd64%20%7C%20arm64-555" alt="Platforms">
|
|
<img src="https://img.shields.io/badge/install-one%20command-3fb950" alt="One-command install">
|
|
<img src="https://img.shields.io/badge/dependencies-none-blue" alt="No runtime dependencies">
|
|
|
|
<br>
|
|
|
|
<img src="docs/images/dashboard.png" alt="Runix dashboard" width="100%">
|
|
|
|
</div>
|
|
|
|
---
|
|
|
|
This repository publishes the **release binaries and the installer**. The source
|
|
lives in a separate, private repository.
|
|
|
|
## What it is
|
|
|
|
Runix manages servers the way you'd want to: you say *restart that thing*, and
|
|
it doesn't matter whether "that thing" is a Docker container, a Compose
|
|
project, a systemd unit or a plain process you wrote yourself.
|
|
|
|
Everything is a **Runtime** with the same lifecycle — start, stop, restart,
|
|
logs, metrics, a shell — so the interface stays the same as your stack changes.
|
|
|
|
It ships as **two static binaries with no runtime dependencies**. The control
|
|
plane serves its own web UI from inside the executable; agents dial *out* to it
|
|
over WebSocket, so managed hosts need no inbound ports, no VPN and no public IP.
|
|
|
|
## Install
|
|
|
|
One command. It asks what the host should be, then does the rest:
|
|
|
|
```sh
|
|
curl -fsSL https://runix-releases.s3-alpha-web.astra-dev.online/install.sh | sudo sh
|
|
```
|
|
|
|
```
|
|
What should this host run?
|
|
1) Control plane + agent (single-host install)
|
|
2) Control plane only
|
|
3) Agent only — join a control plane running elsewhere
|
|
```
|
|
|
|
Pick **1** and you get a working system from an empty machine: it provisions
|
|
PostgreSQL in Docker, starts the control plane, registers the host with it, and
|
|
installs the agent with a token it mints itself. Then open the URL it prints
|
|
and log in.
|
|
|
|
Downloads come from object storage and need no account, no token and no login.
|
|
Every binary is checked against the release's `SHA256SUMS` before it is
|
|
installed — a mismatch aborts.
|
|
|
|
### Roles
|
|
|
|
| Command | What it installs |
|
|
|---|---|
|
|
| `install.sh --role all-in-one` | Control plane **and** an agent on this host |
|
|
| `install.sh --role server` | Control plane only |
|
|
| `install.sh --role agent --url https://runix.example.com --token <token>` | An agent joining an existing control plane |
|
|
|
|
<details>
|
|
<summary><b>Unattended installs and options</b></summary>
|
|
|
|
<br>
|
|
|
|
Every question is also a flag, so the same script works from Ansible or CI:
|
|
|
|
```sh
|
|
# Single-host install, no questions asked
|
|
curl -fsSL https://runix-releases.s3-alpha-web.astra-dev.online/install.sh | sudo sh -s -- \
|
|
--role all-in-one --yes
|
|
|
|
# Agent joining an existing control plane
|
|
curl -fsSL https://runix-releases.s3-alpha-web.astra-dev.online/install.sh | sudo sh -s -- \
|
|
--role agent --url https://runix.example.com --token rnx_agt_...
|
|
|
|
# Behind a reverse proxy: listen on loopback only
|
|
curl -fsSL https://runix-releases.s3-alpha-web.astra-dev.online/install.sh | sudo sh -s -- \
|
|
--role server --bind 127.0.0.1 --public-url https://runix.example.com -y
|
|
```
|
|
|
|
`-y` takes the recommended default for anything you didn't pass. Values with no
|
|
safe default — the role, an agent's URL and token — fail loudly instead of
|
|
being guessed.
|
|
|
|
| Flag | Purpose |
|
|
|---|---|
|
|
| `--db docker` / `--dsn` | Provision PostgreSQL in Docker, or use one you already run |
|
|
| `--bind`, `--port` | The IP and port the control plane listens on |
|
|
| `--public-url` | Where browsers reach the UI (sets the CORS allow-list) |
|
|
| `--version` | Install a specific release instead of the latest |
|
|
| `--s3`, `--gitea` | Where to download from |
|
|
| `--prefix` | Install root (default `/opt/runix`) |
|
|
| `--no-start` | Configure everything, start nothing |
|
|
|
|
`install.sh --help` lists them all.
|
|
|
|
</details>
|
|
|
|
Everything lives under one directory:
|
|
|
|
```
|
|
/opt/runix/bin/ runix-server, runix-agent
|
|
/opt/runix/etc/ server.env, agent.env (0600 — secrets live here)
|
|
/opt/runix/postgres/ docker-compose.yml, data/ (control plane)
|
|
/opt/runix/agent/ supervised daemon state (agent)
|
|
```
|
|
|
|
## Updating
|
|
|
|
Re-running the installer upgrades in place. Binaries are replaced and services
|
|
restarted, while your configuration — above all the JWT and encryption secrets
|
|
— is preserved:
|
|
|
|
```sh
|
|
curl -fsSL https://runix-releases.s3-alpha-web.astra-dev.online/install.sh | sudo sh -s -- -y
|
|
```
|
|
|
|
Configuration files are edited a key at a time rather than rewritten, so
|
|
anything you added by hand survives, and an upgrade with no flags leaves them
|
|
byte for byte identical.
|
|
|
|
Or update from the UI: an administrator gets an **Updates** page that checks
|
|
for the latest release and moves the control plane and each agent to it with
|
|
one click. Point a control plane at this release channel with:
|
|
|
|
```
|
|
RUNIX_UPDATE_MANIFEST=https://runix-releases.s3-alpha-web.astra-dev.online/latest.json
|
|
```
|
|
|
|
## What you get
|
|
|
|
### Every workload, one interface
|
|
|
|
Native daemons, Docker containers, Compose projects and systemd units, each in
|
|
its own tab, all with the same actions. Runix supervises native daemons itself
|
|
— restart policies, backoff, log capture — so a plain binary gets the same
|
|
treatment as a container.
|
|
|
|
<img src="docs/images/runtimes.png" alt="Runtimes" width="100%">
|
|
|
|
### A console that actually takes input
|
|
|
|
Not a log tail, and not a shell *next to* your process — the process's own
|
|
stdin. Type `stop` into a game server and the game server sees it. Copy and
|
|
paste work like they do in a terminal.
|
|
|
|
<img src="docs/images/console.png" alt="Interactive runtime console" width="100%">
|
|
|
|
### Per-host metrics and inventory
|
|
|
|
CPU, memory, disk and load, live and historical, plus the hardware and OS
|
|
details the agent reports on every heartbeat.
|
|
|
|
<img src="docs/images/server-overview.png" alt="Server overview" width="100%">
|
|
|
|
### A real file manager
|
|
|
|
Browse, edit with syntax highlighting, upload by drag-and-drop, download whole
|
|
directories as `tar.gz`, create archives, change permissions — with tabs, a
|
|
right-click context menu, sorting, search and multi-select. Transfers stream
|
|
frame by frame, so a large file is never buffered in the control plane's
|
|
memory.
|
|
|
|
<img src="docs/images/files.png" alt="File manager" width="100%">
|
|
|
|
### Terminals, into the host or into a container
|
|
|
|
A real PTY on the host, or a shell inside a running container, over the same
|
|
outbound connection the agent already holds open. Sessions survive a page
|
|
reload.
|
|
|
|
<img src="docs/images/terminal.png" alt="Host terminal" width="100%">
|
|
|
|
### Docker, beyond containers
|
|
|
|
Images, volumes and networks with disk usage and pruning — because they aren't
|
|
runtimes and pretending otherwise would have bent the model out of shape.
|
|
|
|
<img src="docs/images/docker.png" alt="Docker resources" width="100%">
|
|
|
|
### Scheduling, with a cron engine of its own
|
|
|
|
Restart something nightly, run a command weekly. Multiple control-plane
|
|
instances can share a database without ever double-running a task.
|
|
|
|
<img src="docs/images/schedule.png" alt="Scheduled tasks" width="100%">
|
|
|
|
### Access control down to a single container
|
|
|
|
Grant a user, a role or a group access to the whole fleet, one server, or one
|
|
specific runtime on one specific host — including file access scoped to just
|
|
that runtime's directory. Everything is a dropdown; nothing asks you to paste
|
|
a UUID.
|
|
|
|
<img src="docs/images/grants.png" alt="Permission grants" width="100%">
|
|
|
|
### Audit log
|
|
|
|
Who did what, to which resource, from which IP — recorded for every mutating
|
|
action.
|
|
|
|
<img src="docs/images/audit.png" alt="Audit log" width="100%">
|
|
|
|
### And the rest
|
|
|
|
- **Auth** — argon2id, JWT with rotating refresh tokens and reuse detection,
|
|
TOTP MFA with recovery codes, personal access tokens
|
|
- **Backup/restore** of configuration, deliberately without secrets
|
|
- **Self-update** for the control plane and agents, refusing any download
|
|
without a matching checksum
|
|
- **Plugins** — external processes speaking line-delimited JSON on stdio can
|
|
register entirely new runtime types
|
|
- **English and Russian** UI
|
|
|
|
## How it works
|
|
|
|
```
|
|
Browser ──────► runix-server ──────► PostgreSQL
|
|
(control plane) Redis (optional, multi-instance)
|
|
▲
|
|
│ agents dial OUT over WebSocket
|
|
│ (one connection: RPC + byte streams)
|
|
┌─────────────┼─────────────┐
|
|
│ │ │
|
|
runix-agent runix-agent runix-agent
|
|
docker systemd daemons
|
|
```
|
|
|
|
- **Agents connect outbound.** Managed hosts need no open ports. One connection
|
|
multiplexes correlated RPC calls and byte streams (logs, terminals, file
|
|
transfers) at once.
|
|
- **The runtime abstraction is small on purpose.** A core interface every
|
|
provider implements, plus optional capability interfaces it opts into. The
|
|
capability set travels to the UI, which renders only the actions that exist —
|
|
so no button is ever a "not supported" error waiting to happen.
|
|
|
|
## Requirements
|
|
|
|
| | |
|
|
|---|---|
|
|
| **OS** | Linux, `amd64` or `arm64` |
|
|
| **Install** | root (the installer writes to `/opt` and `/etc/systemd/system`) |
|
|
| **Service manager** | systemd (optional — without it the installer prints how to run the binaries) |
|
|
| **Database** | PostgreSQL 17, provisioned in Docker for you or one you already run (control plane only) |
|
|
| **Docker** | Optional. Needed to provision PostgreSQL and to manage Docker runtimes |
|
|
|
|
Agents have no dependencies at all: one static binary.
|
|
|
|
## Configuration
|
|
|
|
Both binaries are configured by environment, read from `/opt/runix/etc/*.env`.
|
|
|
|
<details>
|
|
<summary><b><code>runix-server</code> — the essentials</b></summary>
|
|
|
|
<br>
|
|
|
|
| Variable | Default | Description |
|
|
|---|---|---|
|
|
| `RUNIX_HTTP_ADDR` | `:8080` | Listen address; `127.0.0.1:8080` to sit behind a proxy |
|
|
| `RUNIX_DATABASE_DSN` | — | PostgreSQL connection string (required) |
|
|
| `RUNIX_JWT_SECRET` | — | Signing key for sessions (required; min 32 chars) |
|
|
| `RUNIX_ENCRYPTION_KEY` | — | Encrypts stored secrets such as TOTP (required) |
|
|
| `RUNIX_CORS_ORIGINS` | — | Browser origins allowed for API + WebSocket |
|
|
| `RUNIX_UPDATE_MANIFEST` | this release channel | Where the Updates page looks for new versions |
|
|
| `RUNIX_REDIS_ADDR` | — | Optional; shares events across control-plane instances |
|
|
|
|
The installer generates the secrets and preserves them across upgrades —
|
|
rotating them would invalidate every session and make stored MFA secrets
|
|
unreadable.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><b><code>runix-agent</code></b></summary>
|
|
|
|
<br>
|
|
|
|
| Variable | Default | Description |
|
|
|---|---|---|
|
|
| `RUNIX_AGENT_SERVER_URL` | — | Control-plane URL to dial (required) |
|
|
| `RUNIX_AGENT_TOKEN` | — | Enrollment token, minted by the control plane (required) |
|
|
| `RUNIX_AGENT_DATA_DIR` | `/opt/runix/agent` | Where supervised daemon state lives |
|
|
| `RUNIX_AGENT_SHELL` | login shell | Shell used for host terminals |
|
|
|
|
</details>
|
|
|
|
## Releases
|
|
|
|
Each release publishes, for `linux/amd64` and `linux/arm64`:
|
|
|
|
| File | |
|
|
|---|---|
|
|
| `runix-server_linux_<arch>` | The control plane, with the web UI embedded |
|
|
| `runix-agent_linux_<arch>` | The agent |
|
|
| `install.sh` | The installer |
|
|
| `SHA256SUMS` | Checksums for everything above |
|
|
|
|
Built automatically from source by CI on every version tag, and published here
|
|
and to object storage. `latest.json` names the newest version and each asset's
|
|
checksum — it is what the installer and the in-app updater read, and it is
|
|
written last so a half-finished upload is never advertised.
|
|
|
|
### Verify a download by hand
|
|
|
|
```sh
|
|
BASE=https://runix-releases.s3-alpha-web.astra-dev.online
|
|
curl -fsSLO $BASE/v0.5.2/runix-server_linux_amd64
|
|
curl -fsSL $BASE/v0.5.2/SHA256SUMS | sha256sum -c --ignore-missing
|
|
```
|
|
|
|
## Security notes
|
|
|
|
- Agents dial **out**; nothing connects in to a managed host.
|
|
- Every downloaded binary is verified against a published SHA-256 before it
|
|
replaces anything. An unverified download is refused, not warned about.
|
|
- `/opt/runix/etc/*.env` holds the secrets and is written `0600`.
|
|
- Access is deny-by-default: a permission can be granted globally, per server
|
|
group, per server, or on a single runtime, and the UI only renders controls
|
|
the account can actually use.
|
|
- Runix runs workloads you define, as the user you configure. Treat access to
|
|
the control plane as equivalent to shell on every managed host.
|