diff --git a/README.md b/README.md index bc50138..c9a950c 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,109 @@ +
+ # Runix -Runix is an infrastructure management platform: one control plane manages a -fleet of hosts through lightweight agents that dial out over WebSocket -(NAT-friendly — nothing connects *in* to your servers). Manage Docker, -Compose, systemd units and native daemons; browse and edit files; open -terminals and consoles; schedule tasks; and update the whole fleet from the -UI. +**One console for your whole fleet.** +Containers, system services and custom daemons — managed the same way, on every host. -This repository holds the **release binaries and the installer**. The source -lives in a separate repository. +Latest release +Platforms +One-command install +No runtime dependencies + +
+ +Runix dashboard + +
+ +--- + +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 sets up a host. Run it as root; it asks what the host should be -(control plane, agent, or both), checks prerequisites, installs under -`/opt/runix`, and wires up systemd. +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 ``` -Binaries are served from object storage, so this needs no account and no -token. To pin a version, add `--version v0.5.0`. +``` +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 | |---|---| -| `sudo sh install.sh --role all-in-one` | Control plane **and** an agent on this host (single-host setup) | -| `sudo sh install.sh --role server` | Control plane only | -| `sudo sh install.sh --role agent --url https://runix.example.com --token ` | An agent that joins an existing control plane | +| `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 ` | An agent joining an existing control plane | -The all-in-one and server roles can provision PostgreSQL in Docker for you -(`--db docker`) or use one you already run (`--dsn postgres://…`). Add `-y` -for an unattended install that takes sensible defaults. `sh install.sh ---help` lists every flag. +
+Unattended installs and options + +
+ +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. + +
Everything lives under one directory: @@ -45,41 +114,218 @@ Everything lives under one directory: /opt/runix/agent/ supervised daemon state (agent) ``` -## Update +## 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: +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 ``` -Or update from the UI: an administrator sees an **Updates** page that checks -object storage for the latest release and updates the control plane and each -agent with one click. Point a control plane at this repository with: +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. + +Runtimes + +### 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. + +Interactive runtime console + +### 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. + +Server overview + +### 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. + +File manager + +### 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. + +Host terminal + +### 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. + +Docker resources + +### 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. + +Scheduled tasks + +### 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. + +Permission grants + +### Audit log + +Who did what, to which resource, from which IP — recorded for every mutating +action. + +Audit log + +### 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`. + +
+runix-server — the essentials + +
+ +| 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. + +
+ +
+runix-agent + +
+ +| 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 | + +
+ ## Releases Each release publishes, for `linux/amd64` and `linux/arm64`: -- `runix-server_linux_` — the control plane -- `runix-agent_linux_` — the agent -- `install.sh` — this installer -- `SHA256SUMS` — checksums; the installer verifies every download against it +| File | | +|---|---| +| `runix-server_linux_` | The control plane, with the web UI embedded | +| `runix-agent_linux_` | The agent | +| `install.sh` | The installer | +| `SHA256SUMS` | Checksums for everything above | -Binaries are built automatically from source on tag by the CI runner and -published to object storage as well as to this repository. `latest.json` -beside them names the newest version and every asset's checksum — that is -what the installer and the in-app updater read. +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 +### Verify a download by hand ```sh -curl -fsSLO https://runix-releases.s3-alpha-web.astra-dev.online/v0.5.0/runix-server_linux_amd64 -curl -fsSL https://runix-releases.s3-alpha-web.astra-dev.online/v0.5.0/SHA256SUMS | sha256sum -c --ignore-missing +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. diff --git a/docs/images/audit.png b/docs/images/audit.png new file mode 100755 index 0000000..616f490 Binary files /dev/null and b/docs/images/audit.png differ diff --git a/docs/images/console.png b/docs/images/console.png new file mode 100755 index 0000000..0ec5d66 Binary files /dev/null and b/docs/images/console.png differ diff --git a/docs/images/dashboard.png b/docs/images/dashboard.png new file mode 100755 index 0000000..1d99bad Binary files /dev/null and b/docs/images/dashboard.png differ diff --git a/docs/images/docker.png b/docs/images/docker.png new file mode 100755 index 0000000..92d2c3d Binary files /dev/null and b/docs/images/docker.png differ diff --git a/docs/images/files.png b/docs/images/files.png new file mode 100755 index 0000000..4643720 Binary files /dev/null and b/docs/images/files.png differ diff --git a/docs/images/grants.png b/docs/images/grants.png new file mode 100755 index 0000000..99ef7ee Binary files /dev/null and b/docs/images/grants.png differ diff --git a/docs/images/runtimes.png b/docs/images/runtimes.png new file mode 100755 index 0000000..d913789 Binary files /dev/null and b/docs/images/runtimes.png differ diff --git a/docs/images/schedule.png b/docs/images/schedule.png new file mode 100755 index 0000000..f4914a8 Binary files /dev/null and b/docs/images/schedule.png differ diff --git a/docs/images/server-overview.png b/docs/images/server-overview.png new file mode 100755 index 0000000..76438f3 Binary files /dev/null and b/docs/images/server-overview.png differ diff --git a/docs/images/terminal.png b/docs/images/terminal.png new file mode 100755 index 0000000..7e4217f Binary files /dev/null and b/docs/images/terminal.png differ