#!/bin/sh # studio — homelab operator CLI (POSIX sh): init|up|down|logs|status. # init — rootless .env writer. REFUSES privileged-dev unless ALLOW_PRIVILEGED=1. # up — docker compose up -d (dev override only with ALLOW_PRIVILEGED=1). # down — docker compose down (passes extra args, e.g. -v for volume drills). # logs — docker compose logs (passes service/flag args). # status — docker compose ps + config sanity. set -eu HERE=$(CDPATH= cd -- "$(dirname -- "$0")" >/dev/null 2>&1 && pwd) PKGDIR=$(CDPATH= cd -- "$HERE/.." >/dev/null 2>&1 && pwd) ENVFILE="$PKGDIR/.env" COMPOSE="$PKGDIR/compose.yaml" DEV_OVERRIDE="$PKGDIR/compose.override.dev.yaml" usage() { echo "usage: studio {init|up|down|logs|status} [args...]" >&2 echo " init write rootless .env (refuses privileged-dev without ALLOW_PRIVILEGED=1)" >&2 echo " up docker compose up -d" >&2 echo " down docker compose down [extra args]" >&2 echo " logs docker compose logs [service...]" >&2 echo " status docker compose ps + config check" >&2 exit 2 } gen_password() { if command -v openssl >/dev/null 2>&1; then openssl rand -base64 24 | tr -d '\n' else # fallback: bun hash of pid+time (no key material logged) bun -e 'console.log(require("node:crypto").randomBytes(18).toString("base64"))' 2>/dev/null || echo "CHANGE_ME_$(date +%s)" fi } cmd_init() { if [ -f "$ENVFILE" ]; then echo "studio init: $ENVFILE exists; refusing to overwrite (delete it to re-init)" >&2 exit 1 fi if [ "${PRIVILEGED_DEV:-0}" = "1" ] && [ "${ALLOW_PRIVILEGED:-0}" != "1" ]; then echo "studio init: REFUSED privileged-dev without explicit ALLOW_PRIVILEGED=1" >&2 echo "Rootless is the default. Re-run with ALLOW_PRIVILEGED=1 only on a trusted local machine." >&2 exit 1 fi PASS=$(gen_password) { echo "# Generated by scripts/studio init — rootless defaults. Do not commit." echo "OPENCODE_PASSWORD=$PASS" echo "DISTRO_PORT=${DISTRO_PORT:-4096}" echo "GATEWAY_PORT=${GATEWAY_PORT:-8080}" echo "FLEET_PORT=${FLEET_PORT:-7070}" echo "FLEET_AUTONOMY=${FLEET_AUTONOMY:-balanced}" echo "HOST_UID=$(id -u)" echo "HOST_GID=$(id -g)" } > "$ENVFILE" chmod 600 "$ENVFILE" echo "studio init: wrote $ENVFILE (0600, OPENCODE_PASSWORD generated, ports ${DISTRO_PORT:-4096}/${GATEWAY_PORT:-8080}/${FLEET_PORT:-7070})" } compose_files() { if [ "${ALLOW_PRIVILEGED:-0}" = "1" ]; then echo "studio: privileged dev override ENABLED (explicit ALLOW_PRIVILEGED=1)" >&2 printf '%s\n' "-f" "$COMPOSE" "-f" "$DEV_OVERRIDE" else printf '%s\n' "-f" "$COMPOSE" fi } cmd_up() { # shellcheck disable=SC2046 docker compose $(compose_files) --env-file "$ENVFILE" up -d "$@" } cmd_down() { # shellcheck disable=SC2046 docker compose $(compose_files) --env-file "$ENVFILE" down "$@" } cmd_logs() { # shellcheck disable=SC2046 docker compose $(compose_files) --env-file "$ENVFILE" logs "$@" } cmd_status() { # shellcheck disable=SC2046 docker compose $(compose_files) --env-file "$ENVFILE" ps # shellcheck disable=SC2046 docker compose $(compose_files) --env-file "$ENVFILE" config -q && echo "studio status: compose config OK" } [ $# -ge 1 ] || usage CMD="$1"; shift case "$CMD" in init) cmd_init "$@" ;; up) cmd_up "$@" ;; down) cmd_down "$@" ;; logs) cmd_logs "$@" ;; status) cmd_status "$@" ;; *) usage ;; esac