#!/bin/sh # Omni core one-line installer — TEMPLATE. # # The omnigto.com / latest / get.omnigto.com placeholders are # baked at deploy time by the deploy workflow (from the server's .env), and # @@COMPOSE@@ is replaced with docker/compose.core.yaml (comments stripped), so # the embedded compose is never a hand-kept copy. This produces the concrete # install.sh that Caddy serves at https://get.omnigto.com (root; /install.sh stays # as an alias). The served command is: # # curl -fsSL https://get.omnigto.com | sudo sh # # Installs a single Omni solver core on this host: writes /opt/omni/{compose.yaml,.env}, # then brings the core up via Docker Compose. The core dials the Omni relay OUTBOUND # (no inbound ports) and pairs with your browser UI by the account id you enter below. set -eu OMNI_DOMAIN="omnigto.com" # root domain; app./registry. derive from it IMAGE_TAG="latest" INSTALL_DIR="/opt/omni" err() { echo "Error: $*" >&2; exit 1; } # --- prerequisites ----------------------------------------------------------- [ "$(id -u)" -eq 0 ] || err "run as root, e.g. curl -fsSL https://get.omnigto.com | sudo sh" # We prompt (Docker install consent + account key) on the terminal, since stdin # is the curl pipe. No tty = no way to ask, so bail early with guidance. [ -r /dev/tty ] || err "no terminal to prompt on; download the script and run it directly instead of piping." # Omni ships x86_64 images only. On an ARM VPS (the cheapest Hetzner / Oracle # free tier — exactly what a cost-conscious user picks) `docker compose pull` # would otherwise fail with a cryptic "no matching manifest for linux/arm64", # AFTER the user already installed Docker and entered their key. Fail fast, in # plain language, up front. arch=$(uname -m) case "$arch" in x86_64 | amd64) : ;; *) err "Omni needs an x86_64 (Intel/AMD) server; yours is $arch (e.g. ARM). It cannot run here." ;; esac # On a host that already has an install, re-running is either an update (the # common case — refresh the embedded compose.yaml and pull a newer image) or a # reconfigure (enter a new key/name, rewriting .env). Ask which; default to a # non-destructive update so the account key and OMNI_CORE_ID are never silently # rewritten. A fresh host (no .env) is a plain install. MODE=install if [ -e "$INSTALL_DIR/.env" ]; then printf 'Found an existing Omni core at %s.\n [U] Update — get the latest Omni, keep your current key (default)\n [R] Reconfigure — enter a new account key or core name\n [Q] Quit\nChoose [U/r/q]: ' "$INSTALL_DIR" > /dev/tty IFS= read -r reply < /dev/tty || err "could not read a reply." case "$reply" in [Rr]*) MODE=reconfigure ;; [Qq]*) err "left the existing install untouched." ;; *) MODE=update ;; esac fi # Docker Engine + the Compose v2 plugin. If Docker is missing, offer to install # it via Docker's official convenience script (get.docker.com): it covers the # mainstream server distros (Debian/Ubuntu, RHEL/CentOS/Fedora/Rocky/Alma, SLES) # and installs the compose plugin too. Ask first — it adds a system repo + root # daemon — but default to yes so a plain `curl … | sh` stays one Enter away. if ! command -v docker >/dev/null 2>&1; then printf 'Docker is not installed. Install it now via get.docker.com? [Y/n] ' > /dev/tty IFS= read -r reply < /dev/tty || err "could not read a reply." case "$reply" in [Nn]*) err "Docker is required. Install it (https://docs.docker.com/engine/install/) and re-run." ;; *) echo "Installing Docker via get.docker.com..." curl -fsSL https://get.docker.com | sh || err "Docker install failed; install it manually and re-run." ;; esac fi docker compose version >/dev/null 2>&1 || \ err "Docker Compose v2 plugin is missing; install it (https://docs.docker.com/compose/install/) and re-run." # --- account key (interactive; stdin is the curl pipe, so read the terminal) -- if [ "$MODE" = update ]; then # An update reuses the existing credentials — never re-prompts. Pull the # token out of .env for the registry login below. ACCOUNT_TOKEN=$(sed -n 's/^OMNI_ACCOUNT_TOKEN=//p' "$INSTALL_DIR/.env") [ -n "$ACCOUNT_TOKEN" ] || err "no OMNI_ACCOUNT_TOKEN in $INSTALL_DIR/.env; re-run and choose Reconfigure." else # The key pairs this core with the owner's browser panel: the panel's # entitlement token carries the same token in its `sub` claim (see WEB.md). printf 'Enter your Omni account key (omni_..., shown on your account page): ' > /dev/tty IFS= read -r ACCOUNT_TOKEN < /dev/tty || err "could not read the account key." ACCOUNT_TOKEN=$(printf '%s' "$ACCOUNT_TOKEN" | tr -d '[:space:]') [ -n "$ACCOUNT_TOKEN" ] || err "account key must not be empty." case "$ACCOUNT_TOKEN" in omni_?*) : ;; *) err "that does not look like an Omni account key (expected omni_...): $ACCOUNT_TOKEN" ;; esac # Name shown for this core in the panel. Without it the core falls back to # gethostname() INSIDE the container — a random 12-hex container id — so the # user's first sight of the panel is a core named `a1b2c3d4e5f6`. Default to the # host's real name; let the user override. default_core_id=$(hostname 2>/dev/null || uname -n) printf 'Name for this core [%s]: ' "$default_core_id" > /dev/tty IFS= read -r CORE_ID < /dev/tty || err "could not read a reply." CORE_ID=$(printf '%s' "$CORE_ID" | tr -d '[:space:]') [ -n "$CORE_ID" ] || CORE_ID=$default_core_id fi # --- write files ------------------------------------------------------------- mkdir -p "$INSTALL_DIR" # compose.yaml is (re)written from the embedded @@COMPOSE@@ in every mode — this # is how an update picks up compose changes (new env, volumes, services), not # just a newer image. .env is left untouched on update so the key/name survive. cat > "$INSTALL_DIR/compose.yaml" <<'COMPOSE' name: omni-core services: core: image: registry.${OMNI_DOMAIN}/omnigto/omni-core:${TAG:-latest} environment: OMNI_RELAY_URL: "wss://app.${OMNI_DOMAIN}/core" OMNI_ACCOUNT_TOKEN: "${OMNI_ACCOUNT_TOKEN:?OMNI_ACCOUNT_TOKEN is missing}" OMNI_CORE_ID: "${OMNI_CORE_ID:-}" volumes: - omni:/omni restart: unless-stopped stop_grace_period: 5m volumes: omni: name: omni COMPOSE if [ "$MODE" != update ]; then cat > "$INSTALL_DIR/.env" </dev/null || true) docker compose up -d new_core_image=$(docker compose images -q core 2>/dev/null || true) if [ -n "$old_core_image" ] && [ "$old_core_image" != "$new_core_image" ]; then docker image rm "$old_core_image" >/dev/null 2>&1 || true fi echo if [ "$MODE" = update ]; then echo "Done. The core is updated and running." else # Print only the truncated key tag (omni_XXXX…), matching the logging policy — # terminals get screenshotted and shell history gets pasted into support tickets. token_tag=$(printf '%s' "$ACCOUNT_TOKEN" | cut -c1-9) echo "Done. The core '$CORE_ID' is running and dialing wss://app.$OMNI_DOMAIN/core as ${token_tag}…." fi echo "Logs: cd $INSTALL_DIR && docker compose logs -f" echo "Update: re-run the same install command and choose Update." echo "Open https://app.$OMNI_DOMAIN and sign in to the account that owns this key to reach this core."