Nightly Build / build (push) Successful in 7m50s
Four things found while re-reading the family of scripts around the logout fix. Both installers piped curl straight into tar, so a truncated download half-extracted — and the installer explicitly supports reinstalling over an existing install, which turned an interrupted download into a tree mixing old and new files with no error saying so. They now download to a temp file and verify the archive in a staging dir before writing anything to the install directory: the ordering update.sh has had since it was written, for the same reason. update.sh never removed files deleted upstream. Extracting over the install dir only adds and overwrites, so a renamed page under docs/ kept being mounted read-only into every container for the assistant to read, and a removed command kept being discovered. It now prunes, from the directories the tarball owns end to end (web, commands, skills, docs), whatever the already-verified staging copy does not have. Pruning after the extraction rather than replacing the directory keeps every intermediate state a complete install. agents/ is deliberately excluded: dropping in an agent is a documented extension point, so that directory is not ours alone and pruning it would delete somebody's work. uninstall.sh fed `docker ps -aq --filter 'name=skald-'` to `docker rm -f`. Docker's name filter is a regex matched anywhere in the name, not a prefix, so any unrelated container merely containing "skald-" was force-removed. Anchored to ^skald-. uninstall.sh also matched uname's raw Linux/Darwin while its three siblings normalize to lowercase. It was correct on its own, but being the odd one out of four copy-paste relatives is precisely how update.sh acquired its no-op case arms, so it now normalizes like the others. Finally, the uninstaller reports that lingering is still enabled and how to turn it off, rather than disabling it: it is a persistent per-user setting other user services may rely on by now, so taking it back silently would stop those too.
562 lines
23 KiB
Bash
Executable File
562 lines
23 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 ────────────────────────────────────────────────────────
|
|
# Download to a temp file and verify the archive BEFORE touching the install dir
|
|
# — the same ordering update.sh uses, and for the same reason. Piping curl
|
|
# straight into tar half-extracts a truncated download, which on the
|
|
# reinstall-over-an-existing-install path above leaves a tree mixing old and new
|
|
# files: worse than either version, and with no error to say so.
|
|
info "↓ Downloading Skald Circle ${VERSION} …"
|
|
|
|
TMP_TARBALL="$(mktemp -t skald-install.XXXXXX.tar.gz)"
|
|
STAGING="$(mktemp -d -t skald-install-staging.XXXXXX)"
|
|
trap 'rm -f "$TMP_TARBALL" 2>/dev/null || true; rm -rf "$STAGING" 2>/dev/null || true' EXIT
|
|
|
|
curl -fsSL -o "$TMP_TARBALL" "$TARBALL_URL"
|
|
|
|
info "🔎 Verifying archive …"
|
|
tar xzf "$TMP_TARBALL" -C "$STAGING" --strip-components=1
|
|
if [ ! -x "$STAGING/bin/skald" ]; then
|
|
err "Downloaded archive is invalid — skald binary not found."
|
|
err "Nothing was written to ${INSTALL_DIR}."
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$INSTALL_DIR"
|
|
tar xzf "$TMP_TARBALL" -C "$INSTALL_DIR" --strip-components=1
|
|
|
|
if [ ! -x "$INSTALL_DIR/bin/skald" ]; then
|
|
err "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 ""
|