339 lines
13 KiB
Markdown
339 lines
13 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://github.com/svesnav/Runix/releases/latest/download/install.sh -o install.sh
|
|
chmod +x install.sh
|
|
./install.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://github.com/svesnav/Runix/releases/latest/download/install.sh -o install.sh
|
|
chmod +x install.sh
|
|
./install.sh --role all-in-one --yes
|
|
|
|
# Agent joining an existing control plane
|
|
curl -fsSL https://github.com/svesnav/Runix/releases/latest/download/install.sh -o install.sh
|
|
chmod +x install.sh
|
|
./install.sh --role agent --url https://runix.example.com --token rnx_agt_...
|
|
|
|
# Behind a reverse proxy: listen on loopback only
|
|
curl -fsSL https://github.com/svesnav/Runix/releases/latest/download/install.sh -o install.sh
|
|
chmod +x install.sh
|
|
./install.sh --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://github.com/svesnav/Runix/releases/latest/download/install.sh -o install.sh
|
|
chmod +x install.sh
|
|
./install.sh -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.
|