README: install and update from object storage

This commit is contained in:
svesnav
2026-07-27 13:04:23 +03:00
parent 3faa4927cf
commit 0ae771786a
2 changed files with 70 additions and 32 deletions
+59 -18
View File
@@ -1,7 +1,7 @@
#!/bin/sh
# Runix installer — the one command that sets a host up.
#
# curl -fsSL https://vcs.astra-dev.online/svesnav/runix/raw/branch/main/install.sh | sudo sh
# curl -fsSL https://runix-releases.s3-alpha-web.astra-dev.online/install.sh | sudo sh
#
# Asks what this host should be (control plane, agent, or both), checks
# the prerequisites, installs under /opt/runix and wires up systemd.
@@ -19,9 +19,15 @@
set -eu
PREFIX=${RUNIX_PREFIX:-/opt/runix}
# Releases are served from a Gitea instance by default. Point GITEA at a
# GitHub-style host by clearing it (RUNIX_GITEA=) and setting a download
# base, if you mirror the release elsewhere.
# Releases are served from object storage by default: it needs no account,
# so a plain `curl … | sh` works on a fresh box. The layout is
#
# <base>/latest.json names the newest version
# <base>/<version>/<asset> the binaries, install.sh and SHA256SUMS
#
# Clear it (RUNIX_S3=) to fall back to the git forge below.
S3_BASE=${RUNIX_S3:-https://runix-releases.s3-alpha-web.astra-dev.online}
# The forge is the fallback source, used when S3_BASE is empty.
GITEA=${RUNIX_GITEA:-https://vcs.astra-dev.online}
REPO=${RUNIX_REPO:-svesnav/runix}
VERSION=${RUNIX_VERSION:-latest}
@@ -86,8 +92,9 @@ Options:
--server-binary PATH Install a local control-plane build
--agent-binary PATH Install a local agent build
--version VERSION Release to install (default: latest)
--repo OWNER/NAME Release repository (default: $REPO)
--gitea URL Gitea instance serving releases (default: $GITEA)
--s3 URL Object store serving releases (default: $S3_BASE)
--repo OWNER/NAME Release repository, when not using --s3
--gitea URL Gitea instance serving releases (fallback source)
--repo-token TOK Read token, if the instance requires sign-in
--prefix PATH Install root (default: $PREFIX)
--no-start Install and configure, but do not start services
@@ -95,7 +102,7 @@ Options:
-h, --help Show this help
Environment equivalents: RUNIX_PREFIX, RUNIX_VERSION, RUNIX_REPO,
RUNIX_GITEA, RUNIX_TOKEN, RUNIX_DATABASE_DSN, RUNIX_ADMIN_PASSWORD,
RUNIX_S3, RUNIX_GITEA, RUNIX_TOKEN, RUNIX_DATABASE_DSN, RUNIX_ADMIN_PASSWORD,
RUNIX_HTTP_HOST, RUNIX_AGENT_SERVER_URL, RUNIX_AGENT_TOKEN,
RUNIX_POSTGRES_PORT.
@@ -125,7 +132,8 @@ while [ $# -gt 0 ]; do
--agent-binary) AGENT_BIN="$2"; shift 2 ;;
--version) VERSION="$2"; shift 2 ;;
--repo) REPO="$2"; shift 2 ;;
--gitea) GITEA="$2"; shift 2 ;;
--s3) S3_BASE="$2"; shift 2 ;;
--gitea) GITEA="$2"; S3_BASE=""; shift 2 ;;
--repo-token|--github-token) DL_TOKEN="$2"; shift 2 ;;
--prefix) PREFIX="$2"; shift 2 ;;
--no-start) NO_START=1; shift ;;
@@ -136,7 +144,9 @@ while [ $# -gt 0 ]; do
done
if [ -z "$DOWNLOAD_BASE" ]; then
if [ -n "$GITEA" ]; then
if [ -n "$S3_BASE" ]; then
DOWNLOAD_BASE="$S3_BASE"
elif [ -n "$GITEA" ]; then
DOWNLOAD_BASE="$GITEA/$REPO/releases"
else
DOWNLOAD_BASE="https://github.com/$REPO/releases"
@@ -802,6 +812,16 @@ RESOLVED_TAG=""
resolve_tag() {
if [ "$VERSION" != latest ]; then RESOLVED_TAG="$VERSION"; return 0; fi
[ -n "$RESOLVED_TAG" ] && return 0
# Object storage serves no API, so the newest version is named in a
# small manifest published beside the binaries.
if [ -n "$S3_BASE" ]; then
_man=$(mktemp)
if fetch "$S3_BASE/latest.json" "$_man" "application/json"; then
RESOLVED_TAG=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$_man" | head -n1)
fi
rm -f "$_man"
[ -n "$RESOLVED_TAG" ] && return 0
fi
if [ -n "$GITEA" ]; then
_loc=$(redirect_of "$DOWNLOAD_BASE/latest" 2>/dev/null || true)
RESOLVED_TAG=$(printf '%s' "$_loc" | sed -n 's#.*/releases/tag/##p')
@@ -843,19 +863,26 @@ asset_id() {
# try_download NAME DEST — quiet, returns non-zero if the asset is absent.
try_download() {
_name=$1; _dest=$2
if [ -n "$GITEA" ]; then
# Distinct variable names: POSIX sh has no locals, and resolve_tag
# below calls fetch, which would otherwise overwrite the _dest we were
# handed — sending the binary to the manifest's temp file instead of
# where the caller expects it.
_tdname=$1; _tddest=$2
if [ -n "$S3_BASE" ]; then
resolve_tag || return 1
fetch "$S3_BASE/$RESOLVED_TAG/$_tdname" "$_tddest" || return 1
elif [ -n "$GITEA" ]; then
# Gitea assets download by tag; the token (if any) rides in fetch.
resolve_tag || return 1
fetch "$DOWNLOAD_BASE/download/$RESOLVED_TAG/$_name" "$_dest" || return 1
fetch "$DOWNLOAD_BASE/download/$RESOLVED_TAG/$_tdname" "$_tddest" || return 1
elif [ -n "$DL_TOKEN" ]; then
_id=$(asset_id "$_name") || return 1
_id=$(asset_id "$_tdname") || return 1
fetch "https://api.github.com/repos/$REPO/releases/assets/$_id" \
"$_dest" "application/octet-stream" || return 1
"$_tddest" "application/octet-stream" || return 1
elif [ "$VERSION" = latest ]; then
fetch "$DOWNLOAD_BASE/latest/download/$_name" "$_dest" || return 1
fetch "$DOWNLOAD_BASE/latest/download/$_tdname" "$_tddest" || return 1
else
fetch "$DOWNLOAD_BASE/download/$VERSION/$_name" "$_dest" || return 1
fetch "$DOWNLOAD_BASE/download/$VERSION/$_tdname" "$_tddest" || return 1
fi
}
@@ -1016,6 +1043,14 @@ install_server() {
mkdir -p "$PREFIX"
install_binary runix-server "$SERVER_BIN"
# Applying an update from the UI replaces the binary in place, which
# means writing a new file into this directory and renaming it over the
# old one — so the service user has to own the directory, not just the
# file. Without this the update fails with a bare permission error long
# after the download has succeeded.
chown "$SERVER_USER" "$BIN_DIR" 2>/dev/null || true
chown "$SERVER_USER" "$BIN_DIR/runix-server" 2>/dev/null || true
[ "$DB_MODE" = docker ] && provision_postgres
[ -n "$DSN" ] || fail "no database configured"
@@ -1094,7 +1129,11 @@ NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=$CONFIG_DIR
# The binary directory is writable so the control plane can replace its own
# binary when an administrator applies an update from the UI. That is the
# price of in-app updates: the service can rewrite the file it runs from.
# Drop \$BIN_DIR here to forbid it, and upgrade by re-running this script.
ReadWritePaths=$CONFIG_DIR $BIN_DIR
[Install]
WantedBy=multi-user.target
@@ -1376,7 +1415,9 @@ fi
if [ "$ROLE" = server ]; then
echo
echo " Add hosts from the UI (Servers → Add server), then run on each:"
if [ -n "$GITEA" ]; then
if [ -n "$S3_BASE" ]; then
say " curl -fsSL $S3_BASE/install.sh | sudo sh -s -- \\"
elif [ -n "$GITEA" ]; then
say " curl -fsSL $GITEA/$REPO/raw/branch/main/install.sh | sudo sh -s -- \\"
else
say " curl -fsSL $DOWNLOAD_BASE/latest/download/install.sh | sudo sh -s -- \\"