#!/bin/sh set -eu VERSION="4.0.0" INSTALL_DIR="" REPOSITORY="YuanYeYouTao/Yuki" # This source installer targets the matching future Yuki release artifacts. # Published releases retain their own installers and immutable image names. BOT_IMAGE="ghcr.io/yuanyeyoutao/yuki" usage() { printf '%s\n' "Usage: install.sh [--dir PATH] [--version X.Y.Z] (configure only)" } fail() { printf 'Error: %s\n' "$1" >&2 exit 1 } while [ "$#" -gt 0 ]; do case "$1" in --dir) [ "$#" -ge 2 ] || fail "--dir requires a path" INSTALL_DIR=$2 shift 2 ;; --version) [ "$#" -ge 2 ] || fail "--version requires X.Y.Z" VERSION=$2 shift 2 ;; -h|--help) usage exit 0 ;; *) fail "unknown argument: $1" ;; esac done printf '%s\n' "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' || \ fail "version must use X.Y.Z" [ -t 0 ] && [ -t 1 ] || fail "configuration requires an interactive terminal" command -v docker >/dev/null 2>&1 || fail "Docker is not installed" docker compose version >/dev/null 2>&1 || fail "the Docker Compose CLI plugin is not available" docker info >/dev/null 2>&1 || fail "Docker Engine is not running" architecture=$(docker info --format '{{.Architecture}}' 2>/dev/null || true) operating_system=$(docker info --format '{{.OSType}}' 2>/dev/null || true) [ "$operating_system" = linux ] || fail "Docker must be running Linux containers" case "$architecture" in amd64|x86_64) platform=linux/amd64 ;; arm64|aarch64) platform=linux/arm64 ;; *) fail "Yuki $VERSION supports linux/amd64 and linux/arm64; Docker reports $architecture" ;; esac if [ -z "$INSTALL_DIR" ]; then if [ -f "docker-compose.yml" ] && [ -f ".env.example" ]; then INSTALL_DIR=$PWD else INSTALL_DIR=$PWD/yuki fi fi mkdir -p "$INSTALL_DIR" INSTALL_DIR=$(cd "$INSTALL_DIR" && pwd) [ -w "$INSTALL_DIR" ] || fail "installation directory is not writable" existing=false if [ -f "$INSTALL_DIR/docker-compose.yml" ] && [ -f "$INSTALL_DIR/.env.example" ]; then existing=true elif [ -n "$(find "$INSTALL_DIR" -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null)" ]; then fail "installation directory is not empty and is not a Yuki deployment" fi # Bootstrap only an empty directory; existing deployments keep every managed file. if [ "$existing" = false ]; then temporary=$(mktemp -d "${TMPDIR:-/tmp}/yuki-install.XXXXXX") trap 'rm -rf "$temporary"' EXIT HUP INT TERM download() { url=$1 output=$2 if command -v curl >/dev/null 2>&1; then curl --fail --silent --show-error --location "$url" --output "$output" elif command -v wget >/dev/null 2>&1; then wget -q "$url" -O "$output" else fail "curl or wget is required to download the release" fi } base="https://github.com/$REPOSITORY/releases/download/v$VERSION" archive="yuki-$VERSION-deploy.tar.gz" download "$base/$archive" "$temporary/$archive" download "$base/SHA256SUMS" "$temporary/SHA256SUMS" expected=$(awk -v name="$archive" '$2 == name {print $1}' "$temporary/SHA256SUMS") [ -n "$expected" ] || fail "release checksum does not list $archive" if command -v sha256sum >/dev/null 2>&1; then actual=$(sha256sum "$temporary/$archive" | awk '{print $1}') elif command -v shasum >/dev/null 2>&1; then actual=$(shasum -a 256 "$temporary/$archive" | awk '{print $1}') else fail "sha256sum or shasum is required" fi [ "$actual" = "$expected" ] || fail "release archive checksum mismatch" tar -xzf "$temporary/$archive" -C "$temporary" source="$temporary/yuki-$VERSION-deploy" [ -d "$source" ] || fail "release archive layout is invalid" cp -R "$source/." "$INSTALL_DIR/" fi image="$BOT_IMAGE:$VERSION" printf '%s\n' "Pulling $image for $platform" docker pull --platform "$platform" "$image" docker run --rm -it \ --platform "$platform" \ --user "$(id -u):$(id -g)" \ --entrypoint yuki \ --volume "$INSTALL_DIR:/deploy" \ --workdir /deploy \ "$image" setup --deployment-root /deploy printf '%s\n' "Configuration saved. No services were stopped or started and no database was upgraded." printf '%s\n' "Review $INSTALL_DIR/Yuki-$VERSION-Upgrade.md before starting or upgrading the deployment." printf '%s\n' "Upgrade guide: https://github.com/$REPOSITORY/blob/v$VERSION/docs/upgrade-$VERSION.md" printf '%s\n' "Path file tools and terminals require the Linux host Manager; Compose does not install it." printf '%s\n' "After preparing the persistent-environment prerequisites, explicitly run on the Docker host:" printf '%s\n' 'sudo sh ./deploy/sandbox/install-manager.sh --deployment-root /absolute/deployment/path --bot-container BOT_CONTAINER' printf 'Use this deployment root: %s\n' "$INSTALL_DIR" printf '%s\n' 'Then check: docker compose exec --user 10001:10001 bot yuki setup environment-check'