Nightly Build / build (push) Successful in 7m47s
A `systemctl --user` unit runs under the per-user manager, which systemd starts at first login and stops when the user's last session ends — so closing the SSH session that started Skald killed it, and it never came up at boot. No crash and nothing in the journal: the whole cgroup is simply torn down. Both installers now enable lingering after installing the unit, and update.sh carries the same helper so an installation predating this fix is healed by an ordinary update. A failure to enable it only ever warns, with the manual command — it must not abort an install. Two things found on the way there: update.sh matched `case "$OS" in Linux) ... Darwin)`, but $OS had already been normalized to lowercase at the top of the file, so stop_service and start_service were both silent no-ops. None of the ordering the file documents at its head was executing: the tarball went over the running binary (ETXTBSY, aborting the update mid-way) and the safety-net restart in cleanup() was a no-op too, leaving the box down. Neither workflow published install.sh / install-nightly.sh to the web root, so the scripts served by builds.skaldagent.net were hand-copied and drifting from the repo — an installer fix would reach every existing box through update.sh but never a new one. Nightly publishes the nightly installer, release publishes the release one, both with the same atomic temp-and-rename the tarballs use. Also on the unit: dropped `After=docker.service`, which a user manager silently ignores rather than honouring advisorily, and moved `Restart=on-failure` to `always` — run.sh exits 0 on any graceful shutdown, including one nobody asked for, which on-failure reads as a clean stop. That is also what absorbs the boot race against Docker now that lingering makes us start at boot.
542 lines
22 KiB
Bash
Executable File
542 lines
22 KiB
Bash
Executable File
#!/usr/bin/env sh
|
|
# install.sh — install the latest release of Skald Circle
|
|
#
|
|
# Usage:
|
|
# curl -fsSL https://builds.skaldagent.net/install.sh | bash
|
|
#
|
|
# Supports Linux (systemd) and macOS ARM64 (launchd).
|
|
# Default install dir: ~/.local/share/skald-circle (override with SKALD_DIR).
|
|
#
|
|
# If Docker is missing, the installer can optionally install it.
|
|
|
|
set -eu
|
|
|
|
# ── User overrides ────────────────────────────────────────────────────────────
|
|
INSTALL_DIR="${SKALD_DIR:-$HOME/.local/share/skald-circle}"
|
|
|
|
# ── Detect interactive stdin ──────────────────────────────────────────────────
|
|
# We need this early: the confirmation prompt reads from /dev/tty when stdin is
|
|
# piped (curl | bash), but we only show it when a terminal is actually available.
|
|
if [ -t 0 ]; then
|
|
IS_INTERACTIVE=true
|
|
else
|
|
IS_INTERACTIVE=false
|
|
fi
|
|
|
|
# ── Colours (if terminal) ─────────────────────────────────────────────────────
|
|
if [ -t 1 ]; then
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'
|
|
BOLD='\033[1m'
|
|
NC='\033[0m'
|
|
else
|
|
RED=''; GREEN=''; YELLOW=''; CYAN=''; BOLD=''; NC=''
|
|
fi
|
|
|
|
info() { printf "${GREEN}%s${NC}\n" "$*"; }
|
|
warn() { printf "${YELLOW}⚠ %s${NC}\n" "$*"; }
|
|
err() { printf "${RED}✖ %s${NC}\n" "$*"; }
|
|
header(){ printf "\n${BOLD}%s${NC}\n" "$*"; }
|
|
banner(){ printf "\n${CYAN}${BOLD}%s${NC}\n" "$*"; }
|
|
# ── Network IP helper ──────────────────────────────────────────────────────────
|
|
# Returns the primary non-loopback network IP, or empty string if unavailable.
|
|
get_network_ip() {
|
|
if command -v ip >/dev/null 2>&1; then
|
|
ip route get 1 2>/dev/null | awk '{print $7}'
|
|
elif command -v ifconfig >/dev/null 2>&1; then
|
|
ifconfig 2>/dev/null | grep -E 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | head -1
|
|
elif command -v hostname >/dev/null 2>&1; then
|
|
hostname -I 2>/dev/null | awk '{print $1}'
|
|
fi
|
|
}
|
|
|
|
|
|
|
|
# ── Prompt helpers (work from piped stdin too) ────────────────────────────────
|
|
# Both read from /dev/tty when stdin is piped (curl | bash), so the user can
|
|
# always see and answer.
|
|
|
|
prompt_enter() {
|
|
local msg="${1:-Press Enter to continue or Ctrl+C to cancel}"
|
|
if [ "$IS_INTERACTIVE" = true ]; then
|
|
printf "%s" "$msg" >&2
|
|
read -r _ || true
|
|
elif (: </dev/tty) 2>/dev/null; then
|
|
printf "%s" "$msg" >/dev/tty
|
|
IFS= read -r _ </dev/tty || true
|
|
fi
|
|
}
|
|
|
|
prompt_yes_no() {
|
|
local msg="${1:-Continue? [Y/n]}"
|
|
local ans
|
|
if [ "$IS_INTERACTIVE" = true ]; then
|
|
printf "%s" "$msg" >&2
|
|
read -r ans || ans="y"
|
|
elif (: </dev/tty) 2>/dev/null; then
|
|
printf "%s" "$msg" >/dev/tty
|
|
IFS= read -r ans </dev/tty || ans="y"
|
|
else
|
|
ans="n"
|
|
fi
|
|
case "$(printf "%s" "$ans" | tr '[:upper:]' '[:lower:]' | tr -d ' ')" in
|
|
""|"y"|"yes") return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
# Like prompt_yes_no but defaults to NO — for destructive confirmations.
|
|
prompt_yes_no_default_no() {
|
|
local msg="${1:-Continue? [y/N]}"
|
|
local ans=""
|
|
if [ "$IS_INTERACTIVE" = true ]; then
|
|
printf "%s" "$msg" >&2
|
|
read -r ans || ans=""
|
|
elif (: </dev/tty) 2>/dev/null; then
|
|
printf "%s" "$msg" >/dev/tty
|
|
IFS= read -r ans </dev/tty || ans=""
|
|
fi
|
|
case "$(printf "%s" "$ans" | tr '[:upper:]' '[:lower:]' | tr -d ' ')" in
|
|
"y"|"yes") return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
# ── Stop a running instance (for reinstall over an existing install) ──────────
|
|
# Stops the service and waits, bounded, for the process to exit — so the
|
|
# extraction never overwrites a live binary (ETXTBSY on Linux).
|
|
stop_existing_service() {
|
|
case "$OS" in
|
|
linux)
|
|
command -v systemctl >/dev/null 2>&1 && \
|
|
systemctl --user stop skald-circle.service 2>/dev/null || true
|
|
;;
|
|
darwin)
|
|
command -v launchctl >/dev/null 2>&1 && \
|
|
launchctl unload "$HOME/Library/LaunchAgents/com.skald.circle.plist" 2>/dev/null || true
|
|
;;
|
|
esac
|
|
if command -v pgrep >/dev/null 2>&1; then
|
|
local i=0
|
|
while pgrep -f "${INSTALL_DIR}/bin/skald" >/dev/null 2>&1; do
|
|
i=$((i + 1))
|
|
[ "$i" -gt 20 ] && break
|
|
sleep 1
|
|
done
|
|
else
|
|
sleep 2
|
|
fi
|
|
}
|
|
|
|
# ── systemd user lingering ────────────────────────────────────────────────────
|
|
# A `systemctl --user` unit runs under the per-user manager (user@UID.service),
|
|
# which systemd starts at first login and STOPS when the user's last session
|
|
# ends — taking every user service down with it. So without lingering the server
|
|
# dies the moment you close the SSH session that started it, and never comes up
|
|
# at boot. Enabling it is the whole difference between "runs while I'm logged
|
|
# in" and "is a daemon".
|
|
enable_linger() {
|
|
local target="${USER:-$(id -un)}"
|
|
|
|
if ! command -v loginctl >/dev/null 2>&1; then
|
|
warn "loginctl not found — cannot enable lingering."
|
|
echo " The server will stop when you log out of this machine."
|
|
return 0
|
|
fi
|
|
|
|
case "$(loginctl show-user "$target" --property=Linger 2>/dev/null || true)" in
|
|
*=yes) info "✔ Lingering already enabled for ${target}"; return 0 ;;
|
|
esac
|
|
|
|
# Enabling linger for yourself is normally allowed without elevation; fall
|
|
# back to sudo, non-interactive first so `curl | bash` never blocks on a
|
|
# password prompt it has no terminal to answer.
|
|
if loginctl enable-linger "$target" 2>/dev/null \
|
|
|| sudo -n loginctl enable-linger "$target" 2>/dev/null \
|
|
|| { [ "$IS_INTERACTIVE" = true ] && sudo loginctl enable-linger "$target"; }; then
|
|
info "✔ Lingering enabled — the server keeps running after you log out"
|
|
else
|
|
warn "Could not enable lingering for ${target}."
|
|
echo " Without it, the server stops as soon as your last session ends"
|
|
echo " and does not start at boot. Run this once, as an administrator:"
|
|
echo ""
|
|
echo " sudo loginctl enable-linger ${target}"
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
# ── Docker install helper ─────────────────────────────────────────────────────
|
|
install_docker() {
|
|
if [ "$OS" = "linux" ]; then
|
|
info "▶ Installing Docker via get.docker.com …"
|
|
curl -fsSL https://get.docker.com | sh
|
|
info "✔ Docker installed"
|
|
|
|
# Add current user to docker group so they don't need sudo for every command
|
|
if command -v usermod >/dev/null 2>&1; then
|
|
sudo usermod -aG docker "$USER"
|
|
warn "You may need to log out and back in for the docker group to take effect."
|
|
fi
|
|
info "✔ User added to docker group"
|
|
elif [ "$OS" = "darwin" ]; then
|
|
if command -v brew >/dev/null 2>&1; then
|
|
info "▶ Installing Docker Desktop via Homebrew …"
|
|
brew install --cask docker
|
|
info "✔ Docker Desktop installed. Open it from Applications to complete setup."
|
|
else
|
|
err "Homebrew not found. Please install Docker Desktop manually from:"
|
|
err " https://docs.docker.com/desktop/setup/install/mac-install/"
|
|
err "Then re-run this installer."
|
|
exit 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
ask_install_docker() {
|
|
echo ""
|
|
header "🐳 Docker"
|
|
echo ""
|
|
if command -v docker >/dev/null 2>&1 && docker version >/dev/null 2>&1; then
|
|
info "✔ Docker is installed and the daemon is running."
|
|
return 0
|
|
elif command -v docker >/dev/null 2>&1; then
|
|
echo " Docker CLI is present but the daemon is not running."
|
|
echo " Please start Docker before starting the server."
|
|
echo ""
|
|
return 0
|
|
else
|
|
warn "Docker is required but not found."
|
|
echo ""
|
|
echo " Skald uses Docker to run sandboxed user containers."
|
|
echo " The server will not start without it."
|
|
echo ""
|
|
if prompt_yes_no " Install Docker now? [Y/n] "; then
|
|
install_docker
|
|
echo ""
|
|
else
|
|
warn "Skipping Docker installation."
|
|
echo " You can install it later: https://docs.docker.com/engine/install/"
|
|
echo ""
|
|
fi
|
|
fi
|
|
}
|
|
|
|
|
|
# ── Optional dependency checks ────────────────────────────────────────────────
|
|
# These are just warnings — the server starts without them, but some MCP servers
|
|
# or plugins won't work.
|
|
|
|
check_optional_deps() {
|
|
echo ""
|
|
header "🔧 Optional dependencies"
|
|
echo ""
|
|
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
info "✔ Python 3 found ($(python3 --version 2>&1 | head -1))"
|
|
else
|
|
warn "Python 3 not found — the TTS plugins and host-run connectors will not work."
|
|
echo " Install it from https://www.python.org/downloads/"
|
|
echo ""
|
|
fi
|
|
|
|
if command -v node >/dev/null 2>&1; then
|
|
info "✔ Node.js found ($(node --version 2>&1 | head -1))"
|
|
else
|
|
warn "Node.js not found — WhatsApp MCP server will not work."
|
|
echo " Install it from https://nodejs.org/ (version 18 or later)"
|
|
echo ""
|
|
fi
|
|
}
|
|
# ── Platform detection ────────────────────────────────────────────────────────
|
|
OS="$(uname -s)"
|
|
ARCH="$(uname -m)"
|
|
|
|
case "$OS" in
|
|
Linux) OS="linux" ;;
|
|
Darwin) OS="darwin" ;;
|
|
*) err "Unsupported OS: $OS"; exit 1 ;;
|
|
esac
|
|
|
|
case "$ARCH" in
|
|
x86_64)
|
|
ARCH="amd64"
|
|
if [ "$OS" = "darwin" ]; then
|
|
err "Intel Macs are not supported. Apple Silicon (M1+) only."
|
|
exit 1
|
|
fi
|
|
;;
|
|
aarch64|arm64)
|
|
ARCH="arm64"
|
|
;;
|
|
*) err "Unsupported architecture: $ARCH"; exit 1 ;;
|
|
esac
|
|
|
|
# ── Dependency checks ─────────────────────────────────────────────────────────
|
|
command -v curl >/dev/null 2>&1 || { err "curl is required but not installed."; exit 1; }
|
|
|
|
if [ "$OS" = "linux" ]; then
|
|
command -v systemctl >/dev/null 2>&1 || { warn "systemd not found — service will not be installed automatically."; NOSYSTEMD=1; }
|
|
elif [ "$OS" = "darwin" ]; then
|
|
command -v launchctl >/dev/null 2>&1 || { err "launchctl not found."; exit 1; }
|
|
fi
|
|
|
|
# ── Banner + fetch latest version ─────────────────────────────────────────────
|
|
banner "╔══════════════════════════════════════════╗"
|
|
banner "║ Skald Circle — Installer ║"
|
|
banner "╚══════════════════════════════════════════╝"
|
|
echo ""
|
|
|
|
info "🔍 Looking up latest release …"
|
|
BASE_URL="https://builds.skaldagent.net"
|
|
LATEST_URL="${BASE_URL}/releases/LATEST"
|
|
VERSION="$(curl -fsSL "$LATEST_URL" | head -1 | tr -d '[:space:]')"
|
|
|
|
if [ -z "$VERSION" ]; then
|
|
err "Could not determine latest release version."
|
|
err "Check ${LATEST_URL} or try install-nightly.sh for the latest build."
|
|
exit 1
|
|
fi
|
|
|
|
TARBALL_URL="${BASE_URL}/releases/${VERSION}/skald-circle-${VERSION}-${OS}-${ARCH}.tar.gz"
|
|
|
|
# ── Summary + confirmation ────────────────────────────────────────────────────
|
|
echo ""
|
|
header "Installation summary"
|
|
echo ""
|
|
echo " What : Skald Circle ${VERSION}"
|
|
echo " Platform : ${OS}/${ARCH}"
|
|
echo " Install to : ${INSTALL_DIR}"
|
|
echo " Service : $( [ "$OS" = "linux" ] && echo "systemd (user)" || echo "launchd" )"
|
|
echo ""
|
|
echo " The installer will check for Docker and offer to install it if missing."
|
|
echo " Python 3 and Node.js are optional — needed for some MCP servers."
|
|
echo ""
|
|
|
|
prompt_enter "Press Enter to continue or Ctrl+C to cancel "
|
|
|
|
# ── Docker check & install ────────────────────────────────────────────────────
|
|
ask_install_docker
|
|
|
|
# ── Optional dependency check ─────────────────────────────────────────────────
|
|
check_optional_deps
|
|
|
|
# ── Existing installation? ────────────────────────────────────────────────────
|
|
# This installer targets a fresh install; the supported in-place upgrade path is
|
|
# update.sh (keeps your data, restarts the service safely). If an install is
|
|
# already here, point the user there and only reinstall over it on request.
|
|
if [ -x "$INSTALL_DIR/bin/skald" ]; then
|
|
echo ""
|
|
warn "An existing installation was found at ${INSTALL_DIR}."
|
|
echo " To upgrade in place (keeping your database and config), use:"
|
|
echo " ${INSTALL_DIR}/update.sh"
|
|
echo ""
|
|
if prompt_yes_no_default_no " Reinstall over it instead? Data is kept, the service restarts. [y/N] "; then
|
|
info "⏹️ Stopping the running instance before reinstalling …"
|
|
stop_existing_service
|
|
else
|
|
info "Aborted — run ${INSTALL_DIR}/update.sh to upgrade."
|
|
exit 0
|
|
fi
|
|
echo ""
|
|
fi
|
|
|
|
# ── Download & extract ────────────────────────────────────────────────────────
|
|
info "↓ Downloading Skald Circle ${VERSION} …"
|
|
mkdir -p "$INSTALL_DIR"
|
|
curl -fsSL "$TARBALL_URL" | tar xz -C "$INSTALL_DIR" --strip-components=1
|
|
|
|
if [ ! -x "$INSTALL_DIR/bin/skald" ]; then
|
|
err "Download or extraction failed — skald binary not found."
|
|
exit 1
|
|
fi
|
|
|
|
info "✔ Extracted to ${INSTALL_DIR}"
|
|
|
|
# ── Python venv (best-effort) ─────────────────────────────────────────────────
|
|
# Create the venv inline instead of calling run.sh (which also launches the
|
|
# server and would hang the installer).
|
|
info "🔧 Setting up Python virtual environment …"
|
|
VENV_DIR="${INSTALL_DIR}/.venv"
|
|
REQUIREMENTS="${INSTALL_DIR}/requirements.txt"
|
|
|
|
# A venv is usable only if python3 AND pip both work. Ubuntu's `python3 -m venv`
|
|
# without the python3-venv package leaves a venv with python3 but no pip — detect
|
|
# that and recreate, so a broken venv never survives a restart.
|
|
if [ ! -f "$VENV_DIR/bin/python3" ] || ! "$VENV_DIR/bin/python3" -m pip --version >/dev/null 2>&1; then
|
|
rm -rf "$VENV_DIR"
|
|
if command -v uv >/dev/null 2>&1; then
|
|
uv venv --seed "$VENV_DIR" && uv pip install -r "$REQUIREMENTS" \
|
|
&& info "✔ Python venv ready (uv)" \
|
|
|| warn "Python venv setup failed — the TTS plugins and host-run connectors will be unavailable."
|
|
elif command -v python3 >/dev/null 2>&1; then
|
|
python3 -m venv "$VENV_DIR" && "$VENV_DIR/bin/pip" install -r "$REQUIREMENTS" \
|
|
&& info "✔ Python venv ready (pip)" \
|
|
|| warn "Python venv setup failed — the TTS plugins and host-run connectors will be unavailable."
|
|
else
|
|
warn "python3 not found — the TTS plugins and host-run connectors will be unavailable."
|
|
fi
|
|
else
|
|
info "✔ Python venv already exists"
|
|
fi
|
|
|
|
# ── Install daemon ────────────────────────────────────────────────────────────
|
|
if [ "$OS" = "linux" ] && [ -z "${NOSYSTEMD:-}" ]; then
|
|
header "⚡ Installing systemd user service …"
|
|
|
|
mkdir -p "$HOME/.config/systemd/user"
|
|
|
|
cat > "$HOME/.config/systemd/user/skald-circle.service" <<- SERVICE
|
|
[Unit]
|
|
Description=Skald Circle (release ${VERSION})
|
|
Documentation=https://skaldagent.net
|
|
# No After=docker.service here: this is a *user* unit, and docker.service is a
|
|
# system unit the user manager knows nothing about — the dependency would be
|
|
# silently ignored. Docker may therefore still be starting when we do; the
|
|
# server fails fast when the daemon is unreachable and Restart brings it back a
|
|
# few seconds later, so boot ordering settles itself.
|
|
|
|
[Service]
|
|
Type=simple
|
|
ExecStart=${INSTALL_DIR}/run.sh
|
|
WorkingDirectory=${INSTALL_DIR}
|
|
# always, not on-failure: run.sh exits 0 on any graceful shutdown, including one
|
|
# nobody asked for (a stray SIGTERM to the server), which on-failure would treat
|
|
# as a clean stop and leave the box down. An explicit "systemctl --user stop"
|
|
# is unaffected — systemd never restarts after a requested stop.
|
|
Restart=always
|
|
RestartSec=5
|
|
Environment=SKALD_BIN=${INSTALL_DIR}/bin/skald
|
|
Environment=SKALD_SETUP_BIN=${INSTALL_DIR}/bin/skald-setup
|
|
|
|
[Install]
|
|
WantedBy=default.target
|
|
SERVICE
|
|
|
|
systemctl --user daemon-reload
|
|
systemctl --user enable --now skald-circle.service
|
|
|
|
info "✔ Service installed and started"
|
|
|
|
enable_linger
|
|
|
|
echo ""
|
|
echo " Status: systemctl --user status skald-circle"
|
|
echo " Logs: journalctl --user -u skald-circle -f"
|
|
|
|
elif [ "$OS" = "darwin" ]; then
|
|
header "⚡ Installing launchd agent …"
|
|
|
|
mkdir -p "$HOME/Library/LaunchAgents" "$INSTALL_DIR/logs"
|
|
|
|
PLIST="$HOME/Library/LaunchAgents/com.skald.circle.plist"
|
|
|
|
cat > "$PLIST" <<- PLIST
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
|
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
<plist version="1.0">
|
|
<dict>
|
|
<key>Label</key>
|
|
<string>com.skald.circle</string>
|
|
|
|
<key>ProgramArguments</key>
|
|
<array>
|
|
<string>${INSTALL_DIR}/run.sh</string>
|
|
</array>
|
|
|
|
<key>WorkingDirectory</key>
|
|
<string>${INSTALL_DIR}</string>
|
|
|
|
<key>RunAtLoad</key>
|
|
<true/>
|
|
<key>KeepAlive</key>
|
|
<true/>
|
|
|
|
<key>StandardOutPath</key>
|
|
<string>${INSTALL_DIR}/logs/stdout.log</string>
|
|
<key>StandardErrorPath</key>
|
|
<string>${INSTALL_DIR}/logs/stderr.log</string>
|
|
|
|
<key>EnvironmentVariables</key>
|
|
<dict>
|
|
<key>SKALD_BIN</key>
|
|
<string>${INSTALL_DIR}/bin/skald</string>
|
|
<key>SKALD_SETUP_BIN</key>
|
|
<string>${INSTALL_DIR}/bin/skald-setup</string>
|
|
</dict>
|
|
</dict>
|
|
</plist>
|
|
PLIST
|
|
|
|
# Idempotent: unload any previously-loaded agent first, so a re-install
|
|
# reloads the freshly written plist instead of failing on "already loaded".
|
|
launchctl unload "$PLIST" 2>/dev/null || true
|
|
launchctl load "$PLIST"
|
|
|
|
info "✔ Agent installed and started"
|
|
echo ""
|
|
echo " Status: launchctl list com.skald.circle"
|
|
echo " Logs: tail -f ${INSTALL_DIR}/logs/stdout.log"
|
|
|
|
elif [ -n "${NOSYSTEMD:-}" ]; then
|
|
warn "systemd not available — start manually: ${INSTALL_DIR}/run.sh"
|
|
fi
|
|
|
|
# ── Welcome + first-run setup ─────────────────────────────────────────────────
|
|
echo ""
|
|
header "👋 Welcome to Skald Circle!"
|
|
|
|
if [ -x "$INSTALL_DIR/bin/skald-setup" ]; then
|
|
echo ""
|
|
echo " The server is running. Now let's create your admin account."
|
|
echo " You'll be asked for a username and password."
|
|
echo ""
|
|
# skald-setup uses the relative path `database/system.db`, so we cd to the
|
|
# install directory first. When running via curl | bash the cwd is ~, which
|
|
# would create `~/database/system.db` instead of the correct location.
|
|
cd "$INSTALL_DIR"
|
|
if [ "$IS_INTERACTIVE" = true ]; then
|
|
"bin/skald-setup"
|
|
elif (: </dev/tty) 2>/dev/null; then
|
|
"bin/skald-setup" </dev/tty
|
|
else
|
|
echo " From a terminal, run:"
|
|
echo " cd ${INSTALL_DIR} && ./bin/skald-setup"
|
|
fi
|
|
echo ""
|
|
fi
|
|
|
|
# ── Done ──────────────────────────────────────────────────────────────────────
|
|
echo ""
|
|
info "✅ Skald Circle ${VERSION} installed successfully!"
|
|
echo ""
|
|
|
|
# Write channel tag and version for future updates
|
|
echo "release" > "$INSTALL_DIR/.release-channel"
|
|
echo "$VERSION" > "$INSTALL_DIR/.release-version"
|
|
|
|
# ── Hint about optional deps ──────────────────────────────────────────────────
|
|
if [ -f "${INSTALL_DIR}/requirements-optional.txt" ]; then
|
|
echo ""
|
|
info "💡 Optional GPU/ML dependencies available:"
|
|
info " ${INSTALL_DIR}/requirements-optional.txt"
|
|
echo " Install them manually if you use the Orpheus TTS plugin:"
|
|
echo " cd ${INSTALL_DIR} && .venv/bin/pip install -r requirements-optional.txt"
|
|
fi
|
|
|
|
echo " Server : ${INSTALL_DIR}/run.sh"
|
|
echo " Binary : ${INSTALL_DIR}/bin/skald"
|
|
echo " Setup : ${INSTALL_DIR}/bin/skald-setup"
|
|
NET_IP="$(get_network_ip)"
|
|
ADMIN_URL="${NET_IP:+http://${NET_IP}:9000}"
|
|
echo " Admin console: ${ADMIN_URL:-http://localhost:9000} (on the machine, or use the IP above from another device)"
|
|
echo " Server IP: ${NET_IP:-localhost} (network address if available)"
|
|
echo ""
|
|
echo ""
|
|
echo " Start: $( [ "$OS" = "linux" ] && echo "systemctl --user start skald-circle" || echo "launchctl start com.skald.circle" )"
|
|
echo " Stop: $( [ "$OS" = "linux" ] && echo "systemctl --user stop skald-circle" || echo "launchctl stop com.skald.circle" )"
|
|
echo " Logs: $( [ "$OS" = "linux" ] && echo "journalctl --user -u skald-circle -f" || echo "tail -f ${INSTALL_DIR}/logs/stdout.log" )"
|
|
echo ""
|