#!/bin/sh # Installs Solel//os on this Linux machine, which becomes your server: # # curl -fsSL https://solel.io/install | sh # # What it does, in order: # 1. Installs Docker with Docker's own script (get.docker.com), if it isn't here. # 2. Downloads the latest Solel//os server bundle from # https://github.com/Solel-io/releases and checks its checksum, and its # signature too when cosign is installed. # 3. Puts it in /opt/solel, with a .env that pins every image to that release. # 4. Uses an AMD or NVIDIA graphics card for the AI model when there is one, # and picks the model that suits it. # 5. Starts Solel//os and tells you the address to open. # # Run it again and it leaves your settings alone and starts Solel//os again. # Solel//os updates itself from Settings after that. # # SOLEL_DIR=/srv/solel puts it in another folder. SOLEL_VERSION=0.21.0 # installs that version instead of the latest. set -eu DIR=${SOLEL_DIR:-/opt/solel} RELEASES=https://github.com/Solel-io/releases/releases BUNDLE=solel-os-server.tar.gz # Only Solel//os's release workflow signs the bundle. SIGNER=https://github.com/Solel-io/solel-os/.github/workflows/release.yml@refs/heads/main ISSUER=https://token.actions.githubusercontent.com if [ -t 1 ]; then BOLD=$(printf '\033[1m'); PLAIN=$(printf '\033[0m'); else BOLD=; PLAIN=; fi say() { printf '%s\n' "$*"; } step() { printf '\n%s%s%s\n' "$BOLD" "$*" "$PLAIN"; } fail() { printf '\nSolel//os is not installed: %s\n' "$*" >&2; exit 1; } has() { command -v "$1" >/dev/null 2>&1; } # The graphics card the AI model can use: amd, nvidia or cpu. VRAM is its # memory in GB, when the card says. find_gpu() { GPU=cpu VRAM='' told='' for vendor in /sys/class/drm/card[0-9]*/device/vendor; do [ -r "$vendor" ] || continue case $(cat "$vendor") in 0x10de) if has nvidia-ctk || has nvidia-container-runtime-hook; then GPU=nvidia if has nvidia-smi; then VRAM=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits 2>/dev/null | head -n 1 | awk '{ printf "%d", $1 / 1024 }') fi return fi [ -n "$told" ] && continue told=1 say "This machine has an NVIDIA card, but Docker can't use it without the NVIDIA Container Toolkit, so the AI model runs on the processor for now." ;; 0x1002) [ -e /dev/dri ] || continue GPU=amd bytes=$(cat "${vendor%/vendor}/mem_info_vram_total" 2>/dev/null || true) [ -n "$bytes" ] && VRAM=$((bytes / 1073741824)) return ;; esac done } main() { [ "$(uname -s)" = Linux ] || fail "Solel//os runs on Linux. Run this on the Linux machine that will be your server." case $(uname -m) in x86_64 | amd64) ;; *) fail "Solel//os needs a 64-bit Intel or AMD processor for now, and this one is $(uname -m)." ;; esac for tool in curl tar sha256sum; do has "$tool" || fail "it needs $tool. Install it and run this again." done if [ "$(id -u)" -eq 0 ]; then SUDO= elif has sudo; then SUDO=sudo say "Some steps need administrator rights, so sudo may ask for your password." else fail "run it as root, or install sudo first." fi tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT INT TERM if ! has docker; then step "Installing Docker" curl -fsSL https://get.docker.com -o "$tmp/get-docker.sh" || fail "couldn't download Docker's install script." $SUDO sh "$tmp/get-docker.sh" || fail "Docker didn't install. Install Docker Engine (https://docs.docker.com/engine/install/) and run this again." fi if has systemctl; then $SUDO systemctl enable --now docker >/dev/null 2>&1 || true; fi $SUDO docker info >/dev/null 2>&1 || fail "Docker is installed but isn't running. Start it and run this again." $SUDO docker compose version >/dev/null 2>&1 || fail "Docker Compose isn't installed. Install the docker-compose-plugin package and run this again." if [ -f "$DIR/.env" ] && [ -f "$DIR/compose.yaml" ]; then step "Solel//os is already set up in $DIR" say "Your settings stay as they are. Starting it again." else busy= if has ss; then for port in 80 443 8080; do [ -n "$(ss -Hltn "sport = :$port" 2>/dev/null)" ] && busy="$busy $port" done fi [ -z "$busy" ] || fail "something else on this machine is using port$busy. Solel//os needs ports 80, 443 and 8080." root=$($SUDO docker info --format '{{.DockerRootDir}}' 2>/dev/null || echo /var/lib/docker) free=$(df -Pk "$root" 2>/dev/null | awk 'NR == 2 { printf "%d", $4 / 1048576 }') if [ -n "$free" ] && [ "$free" -lt 20 ]; then say "Only $free GB is free for Docker. Solel//os and its AI model need about 20 GB to start, and more as your files grow." fi if [ -n "${SOLEL_VERSION:-}" ]; then base=$RELEASES/download/v${SOLEL_VERSION#v}; else base=$RELEASES/latest/download; fi step "Downloading Solel//os" curl -fsSL "$base/$BUNDLE" -o "$tmp/$BUNDLE" || fail "couldn't download $base/$BUNDLE" curl -fsSL "$base/$BUNDLE.sha256" -o "$tmp/$BUNDLE.sha256" || fail "couldn't download $base/$BUNDLE.sha256" (cd "$tmp" && sha256sum -c --status "$BUNDLE.sha256") || fail "the download is damaged: its checksum doesn't match. Run this again." if has cosign; then curl -fsSL "$base/$BUNDLE.sigstore.json" -o "$tmp/$BUNDLE.sigstore.json" || fail "couldn't download the bundle's signature." cosign verify-blob --new-bundle-format --bundle "$tmp/$BUNDLE.sigstore.json" --certificate-identity "$SIGNER" \ --certificate-oidc-issuer "$ISSUER" "$tmp/$BUNDLE" >/dev/null 2>&1 || fail "the download isn't signed by Solel//os's release workflow." say "Signature checked." fi tar -xzf "$tmp/$BUNDLE" -C "$tmp" [ -f "$tmp/solel-os/compose.yaml" ] && [ -f "$tmp/solel-os/release.env" ] || fail "the download isn't a Solel//os server bundle." say "Solel//os $(cat "$tmp/solel-os/version.txt"), into $DIR" find_gpu files=compose.yaml model= case $GPU in amd) files=compose.yaml:compose.amd.yaml ;; nvidia) files=compose.yaml:compose.nvidia.yaml ;; esac # The model Settings > Models would suggest: Angel-1 on the processor or # a small card, Seraphim-1 on a card of 20 GB or more, Dominion-1 otherwise. if [ "$GPU" = cpu ] || { [ -n "$VRAM" ] && [ "$VRAM" -lt 8 ]; }; then model=Solel-io/Angel-1:IQ4_XS elif [ -n "$VRAM" ] && [ "$VRAM" -ge 20 ]; then model=Solel-io/Seraphim-1:IQ4_XS fi case $GPU in cpu) say "The AI model runs on the processor." ;; *) say "The AI model runs on the ${GPU} graphics card${VRAM:+ (${VRAM} GB)}." ;; esac password=$(od -An -N24 -tx1 /dev/urandom | tr -d ' \n') { say "# Written by https://solel.io/install. Updates change the image lines;" say "# the rest is this server's own." say "COMPOSE_FILE=$files" say "COMPOSE_PROFILES=genesis" say "# Lets the owner sign in over plain http while setting up. Set it to false" say "# once Solel//os has its own https address, then run: docker compose up -d" say "SOLEL_INSECURE_COOKIES=true" say "GENESIS_MEMORY_DB_PASSWORD=$password" [ -z "$model" ] || say "SOLEL_MODEL=$model" grep '^SOLEL_[A-Z_]*_IMAGE=' "$tmp/solel-os/release.env" } >"$tmp/env" $SUDO mkdir -p "$DIR" $SUDO cp -R "$tmp/solel-os/." "$DIR/" $SUDO install -m 600 "$tmp/env" "$DIR/.env" fi step "Downloading the apps and the AI model's runtime" say "This is a few GB, so it can take a while." (cd "$DIR" && $SUDO docker compose pull) || fail "the download stopped. Run this again to pick up where it left off." step "Starting Solel//os" (cd "$DIR" && $SUDO docker compose up -d) || fail "it didn't start. See what happened with: cd $DIR && ${SUDO:+$SUDO }docker compose logs" tries=0 until curl -fs -o /dev/null http://127.0.0.1:8080/api/health; do tries=$((tries + 1)) [ "$tries" -lt 60 ] || fail "it started, but isn't answering yet. See what happened with: cd $DIR && ${SUDO:+$SUDO }docker compose logs core" sleep 2 done ip=$(ip route get 1.1.1.1 2>/dev/null | awk '{ for (i = 1; i < NF; i++) if ($i == "src") { print $(i + 1); exit } }') [ -n "$ip" ] || ip=$(hostname -I 2>/dev/null | awk '{ print $1 }') step "Solel//os is running" say "Open http://${ip:-this-machine}:8080 on a computer or phone on the same network to make the owner account." say "The AI model downloads the first time Solel//os starts, so Jethro is ready once it has." say "" say "Solel//os lives in $DIR. It updates itself from Settings." } main "$@"