diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8417921..07a058f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -107,3 +107,20 @@ jobs: --exit-code; EXIT_CODE=$? docker rm -f shadow-db exit $EXIT_CODE + + installer: + name: OSS installer smoke test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Check shell syntax + run: | + sh -n scripts/install.sh + sh -n scripts/onecomputer/setup.sh + + - name: Check non-mutating plans + run: | + ./scripts/install.sh --dry-run --no-start --dir /tmp/onecomputer-ci + ./scripts/onecomputer/setup.sh --dry-run --no-start --source-dir . diff --git a/README.md b/README.md index a25b08c..5ddb231 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,36 @@ vendored into this application. ## Local development +For a fresh macOS, Linux, or WSL checkout, the recommended OSS path is: + +```bash +curl -fsSL https://raw.githubusercontent.com/ONE-Computer/onecomputer/main/scripts/install.sh | sh +``` + +The installer clones the public repository into `~/.onecomputer/src`, creates a +mode-600 `.env` with local-only secrets, installs locked JavaScript +dependencies, starts PostgreSQL with Docker Compose, applies migrations, and +starts the web/gateway development processes. It is safe to rerun and never +deletes database volumes or overwrites an existing `.env` secret. + +To prepare without starting the dev server: + +```bash +curl -fsSL https://raw.githubusercontent.com/ONE-Computer/onecomputer/main/scripts/install.sh | sh -s -- --no-start +``` + +For a checkout you already cloned: + +```bash +./scripts/install.sh --source-dir . +``` + +See [`docs/onecomputer/installation.md`](docs/onecomputer/installation.md) +for supported options, prerequisites, security behavior, and the distinction +between the OSS source path and the Azure deployment path. + +### Manual local development + Requirements: Node.js 18+, pnpm 9+, Docker, and Rust for the gateway. ```bash diff --git a/docs/onecomputer/installation.md b/docs/onecomputer/installation.md new file mode 100644 index 0000000..3342c54 --- /dev/null +++ b/docs/onecomputer/installation.md @@ -0,0 +1,91 @@ +# ONEComputer OSS installation + +The supported public installation path is a small source bootstrapper followed +by a deterministic local setup script. This keeps the first-run experience +short while keeping the source, package lockfile, database migrations, and +gateway code visible and reproducible. + +## One-line setup + +```bash +curl -fsSL https://raw.githubusercontent.com/ONE-Computer/onecomputer/main/scripts/install.sh | sh +``` + +The default flow: + +1. Clones `ONE-Computer/onecomputer` into `~/.onecomputer/src`. +2. Reuses an existing checkout if that path already contains Git metadata. +3. Checks Node.js, pnpm, Docker, and Docker Compose before changing the repo. +4. Creates `.env` from `.env.example` with mode `600` if it does not exist. +5. Generates missing local encryption and gateway secrets without printing them. +6. Chooses PostgreSQL port `5432`, falling back to `5433` when the default is busy. +7. Installs from `pnpm-lock.yaml`, generates Prisma, starts PostgreSQL, and migrates. +8. Starts `pnpm dev`, which runs the web app and Rust gateway from source. + +The script does not install Docker, Node.js, or Rust with `sudo`. Missing +prerequisites produce a direct installation instruction instead. This avoids a +privileged package-manager action hidden inside a `curl | sh` command. + +## Useful options + +```bash +# Prepare dependencies and PostgreSQL, then return to the shell. +curl -fsSL https://raw.githubusercontent.com/ONE-Computer/onecomputer/main/scripts/install.sh \ + | sh -s -- --no-start + +# Preview the plan without cloning or changing anything. +curl -fsSL https://raw.githubusercontent.com/ONE-Computer/onecomputer/main/scripts/install.sh \ + | sh -s -- --dry-run --no-start + +# Use an existing local checkout. +./scripts/install.sh --source-dir . --no-start + +# Use a non-default PostgreSQL port. +./scripts/onecomputer/setup.sh --postgres-port 5433 --no-start +``` + +The setup script also accepts `--app-port` and `--gateway-port`. Rerunning it +does not remove containers or volumes. Existing `.env` values are preserved; +only empty or known placeholder values are generated. + +## Prerequisites + +- Git +- Node.js 18 or newer +- pnpm 9, enabled with Corepack when available +- Docker Engine or Docker Desktop with Docker Compose v2 +- Rust/Cargo for the gateway development process + +The installer checks Node, pnpm, and Docker before making changes. Rust is +needed when `pnpm dev` starts the gateway; install it through +[rustup](https://rustup.rs/) if it is missing. + +## Security behavior + +- The installer has no telemetry or analytics. +- It never prints `.env` values, wallet material, API keys, or generated keys. +- It does not modify Git remotes, force-reset branches, or delete data volumes. +- `.env` is created with mode `600` and is not committed by the repository. +- The public source path is intentionally used for OSS development while the + Azure path remains governed by the deployment runbook and external OpenVTC + wallet boundary. + +Inspect the script before running it in a sensitive environment. For a reviewable +flow, clone the repository first and run `./scripts/install.sh --source-dir .`. + +## Updating + +Rerunning the installer reuses the existing checkout. It does not silently +discard local changes. Update deliberately: + +```bash +cd ~/.onecomputer/src +git pull --ff-only +./scripts/onecomputer/setup.sh --no-start +``` + +## Scope boundary + +This installer is for local OSS development and the local E2E path. It does not +provision Azure, Entra ID, OpenVTC/VTI services, TLS, DNS, or production +credentials. Use the Azure E2E runbook for those environments. diff --git a/scripts/install.sh b/scripts/install.sh old mode 100644 new mode 100755 index 5adf849..5f9e826 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,200 +1,125 @@ #!/bin/sh - -# OneCLI - Open-Source Credential Vault for AI Agents -# Source: https://github.com/onecli/onecli -# License: See repository for license details -# -# Usage: curl -fsSL https://onecli.sh/install | sh -# -# Custom bind host: -# export ONECLI_BIND_HOST=192.168.1.50 -# curl -fsSL https://onecli.sh/install | sh -# -# Custom PostgreSQL port (if 5432 is already in use): -# export POSTGRES_PORT=5433 -# curl -fsSL https://onecli.sh/install | sh +# ONEComputer OSS installer. # -# Custom app/gateway ports (e.g. for multi-user hosts): -# export ONECLI_APP_PORT=11254 -# export ONECLI_GATEWAY_PORT=11255 -# curl -fsSL https://onecli.sh/install | sh +# One-line install: +# curl -fsSL https://raw.githubusercontent.com/ONE-Computer/onecomputer/main/scripts/install.sh | sh # -# Pin a specific version: -# export ONECLI_VERSION=1.2.0 -# curl -fsSL https://onecli.sh/install | sh -# -# This script checks for Docker, downloads the docker-compose.yml, -# and starts OneCLI (app + PostgreSQL) on ports 10254 and 10255 by default. - -INSTALL_DIR="$HOME/.onecli" -COMPOSE_FILE="$INSTALL_DIR/docker-compose.yml" -COMPOSE_URL="https://raw.githubusercontent.com/onecli/onecli/main/docker/docker-compose.yml" -PROJECT_NAME="onecli" - -# Detect the correct bind host for Docker port bindings. -# Never 0.0.0.0 — that would expose services to the network. -detect_bind_host() { - # 1. Explicit env var — user knows best - if [ -n "$ONECLI_BIND_HOST" ]; then - echo "$ONECLI_BIND_HOST" - return - fi - - # 2. macOS — Docker Desktop, loopback works - if [ "$(uname -s)" = "Darwin" ]; then - echo "127.0.0.1" - return - fi - - # 3. WSL — same VM routing as macOS (check /proc, not env vars) - if [ -f /proc/sys/fs/binfmt_misc/WSLInterop ]; then - echo "127.0.0.1" - return - fi +# The installer bootstraps a source checkout and delegates environment setup +# to scripts/onecomputer/setup.sh. It never reads, prints, or uploads +# credentials, and it never destroys an existing checkout or database volume. + +set -eu + +REPO_SLUG="ONE-Computer/onecomputer" +REPO_URL="https://github.com/${REPO_SLUG}.git" +REF="${ONECOMPUTER_REF:-main}" +INSTALL_ROOT="${ONECOMPUTER_HOME:-$HOME/.onecomputer}" +SOURCE_DIR="${ONECOMPUTER_SOURCE_DIR:-}" +START=1 +SKIP_DEPS=0 +DRY_RUN=0 + +die() { + echo "onecomputer: error: $*" >&2 + exit 1 +} - # 4. Bare-metal Linux — bind to docker0 bridge IP - if command -v ip >/dev/null 2>&1; then - DOCKER0_IP=$(ip -4 addr show docker0 2>/dev/null | awk '/inet / {split($2, a, "/"); print a[1]; exit}') - if [ -n "$DOCKER0_IP" ]; then - echo "$DOCKER0_IP" - return - fi - fi +usage() { + cat <<'EOF' +Usage: install.sh [options] + +Clone or reuse a ONEComputer checkout, then prepare it for local development. + +Options: + --no-start Prepare the checkout without starting pnpm dev. + --skip-deps Do not run pnpm install or Prisma generation. + --dry-run Print the plan without cloning or changing files. + --dir PATH Use PATH as the install root (default ~/.onecomputer). + --source-dir PATH Use an existing checkout instead of cloning. + --ref REF Clone a branch or tag (default main). + --help Show this help. +EOF +} - # 5. Cannot determine safely - echo "" +detect_local_checkout() { + case "$0" in + */scripts/install.sh|scripts/install.sh) + local_dir=$(CDPATH= cd -- "$(dirname "$0")/.." 2>/dev/null && pwd) || true + if [ -f "$local_dir/package.json" ] && [ -f "$local_dir/scripts/onecomputer/setup.sh" ]; then + echo "$local_dir" + fi + ;; + esac } -main() { +while [ "$#" -gt 0 ]; do + case "$1" in + --no-start) START=0; shift ;; + --skip-deps) SKIP_DEPS=1; shift ;; + --dry-run) DRY_RUN=1; shift ;; + --dir) + [ "$#" -ge 2 ] || die "--dir requires a path" + INSTALL_ROOT=$2 + shift 2 + ;; + --source-dir) + [ "$#" -ge 2 ] || die "--source-dir requires a path" + SOURCE_DIR=$2 + shift 2 + ;; + --ref) + [ "$#" -ge 2 ] || die "--ref requires a branch or tag" + REF=$2 + shift 2 + ;; + --help|-h) usage; exit 0 ;; + *) die "unknown option '$1' (use --help)" ;; + esac +done + +if [ -z "$SOURCE_DIR" ]; then + SOURCE_DIR=$(detect_local_checkout || true) +fi + +if [ "$DRY_RUN" -eq 1 ]; then + echo "ONEComputer OSS setup plan" + echo " source: $([ -n "$SOURCE_DIR" ] && echo "$SOURCE_DIR" || echo "$INSTALL_ROOT/src")" + echo " ref: $REF" + echo " start: $([ "$START" -eq 1 ] && echo yes || echo no)" + echo " deps: $([ "$SKIP_DEPS" -eq 1 ] && echo skip || echo install)" + echo " data: Docker Compose PostgreSQL" echo "" - echo " OneCLI: give your agents access, not your secrets." - echo "" - - # ── Prerequisites ── - - if ! command -v docker >/dev/null 2>&1; then - echo "Error: Docker is not installed." >&2 - echo "" >&2 - echo "Install Docker first: https://docs.docker.com/get-docker/" >&2 - exit 1 - fi - - if ! docker info >/dev/null 2>&1; then - echo "Error: Docker daemon is not running." >&2 - echo "Please start Docker and try again." >&2 - exit 1 - fi + echo "No files will be cloned, changed, or started." + exit 0 +fi - if ! docker compose version >/dev/null 2>&1; then - echo "Error: Docker Compose is not available." >&2 - echo "Please install Docker Compose: https://docs.docker.com/compose/install/" >&2 - exit 1 - fi - - # ── Detect bind host ── +command -v git >/dev/null 2>&1 || die "git is required; install it from https://git-scm.com/downloads" - ONECLI_BIND_HOST=$(detect_bind_host) - if [ -z "$ONECLI_BIND_HOST" ]; then - echo "Error: Could not safely determine a bind address for OneCLI." >&2 - echo "" >&2 - echo "Please set ONECLI_BIND_HOST and try again:" >&2 - echo " export ONECLI_BIND_HOST=" >&2 - echo " curl -fsSL https://onecli.sh/install | sh" >&2 - exit 1 +if [ -z "$SOURCE_DIR" ]; then + SOURCE_DIR="$INSTALL_ROOT/src" + if [ -e "$SOURCE_DIR" ] && [ ! -d "$SOURCE_DIR/.git" ]; then + die "$SOURCE_DIR exists but is not a Git checkout; choose another --dir" fi - export ONECLI_BIND_HOST - echo " Bind host: $ONECLI_BIND_HOST" - - # ── Resolve version ── - - ONECLI_VERSION="${ONECLI_VERSION:-latest}" - export ONECLI_VERSION - echo " Version: $ONECLI_VERSION" - - # ── Download docker-compose.yml ── - - if ! mkdir -p "$INSTALL_DIR"; then - echo "Error: Failed to create $INSTALL_DIR" >&2 - exit 1 - fi - echo " Downloading docker-compose.yml..." - if command -v curl >/dev/null 2>&1; then - if ! curl -fsSL "$COMPOSE_URL" -o "$COMPOSE_FILE"; then - echo "Error: Failed to download docker-compose.yml from $COMPOSE_URL" >&2 - exit 1 - fi - elif command -v wget >/dev/null 2>&1; then - if ! wget -qO "$COMPOSE_FILE" "$COMPOSE_URL"; then - echo "Error: Failed to download docker-compose.yml from $COMPOSE_URL" >&2 - exit 1 - fi + if [ -d "$SOURCE_DIR/.git" ]; then + echo " Using existing checkout: $SOURCE_DIR" else - echo "Error: curl or wget is required." >&2 - exit 1 - fi - - # ── Stop existing services ── - - if docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" ps -q 2>/dev/null | grep -q .; then - echo " Stopping existing OneCLI services..." - if ! docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" down; then - echo "Error: Failed to stop existing OneCLI services." >&2 - exit 1 - fi - fi - - # ── Check for port conflicts ── - - PG_PORT="${POSTGRES_PORT:-5432}" - if command -v lsof >/dev/null 2>&1; then - if lsof -iTCP:"$PG_PORT" -sTCP:LISTEN -P -n >/dev/null 2>&1; then - echo "Error: Port $PG_PORT is already in use (probably a local PostgreSQL)." >&2 - echo "" >&2 - echo "Pick a free port for OneCLI's database:" >&2 - echo " export POSTGRES_PORT=5433" >&2 - echo " curl -fsSL https://onecli.sh/install | sh" >&2 - exit 1 - fi - elif command -v ss >/dev/null 2>&1; then - if ss -tlnp "sport = :$PG_PORT" 2>/dev/null | grep -q LISTEN; then - echo "Error: Port $PG_PORT is already in use (probably a local PostgreSQL)." >&2 - echo "" >&2 - echo "Pick a free port for OneCLI's database:" >&2 - echo " export POSTGRES_PORT=5433" >&2 - echo " curl -fsSL https://onecli.sh/install | sh" >&2 - exit 1 - fi - fi - - # ── Pull and start ── - - echo " Pulling latest images..." - if ! docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" pull; then - echo "Error: Failed to pull OneCLI images. Check your network connection." >&2 - exit 1 - fi - - echo " Starting OneCLI..." - if ! docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" up -d --wait; then - echo "" >&2 - echo "Error: Failed to start OneCLI." >&2 - exit 1 + mkdir -p "$INSTALL_ROOT" + echo " Cloning $REPO_SLUG ($REF)..." + git clone --branch "$REF" --depth 1 "$REPO_URL" "$SOURCE_DIR" fi - - # ── Success ── - - echo "" - echo " OneCLI is running!" - echo " ONECLI_URL: http://$ONECLI_BIND_HOST:${ONECLI_APP_PORT:-10254}" - echo "" - echo " Dashboard: http://$ONECLI_BIND_HOST:${ONECLI_APP_PORT:-10254}" - echo " Gateway: http://$ONECLI_BIND_HOST:${ONECLI_GATEWAY_PORT:-10255}" - echo "" - echo " Compose file: $COMPOSE_FILE" - echo "" - echo " To stop: docker compose -p $PROJECT_NAME -f $COMPOSE_FILE down" - echo " To update: curl -fsSL https://onecli.sh/install | sh" - echo "" -} - -main +else + SOURCE_DIR=$(CDPATH= cd -- "$SOURCE_DIR" 2>/dev/null && pwd) || die "source directory not found: $SOURCE_DIR" + [ -f "$SOURCE_DIR/package.json" ] || die "$SOURCE_DIR is not a ONEComputer checkout" +fi + +[ -f "$SOURCE_DIR/scripts/onecomputer/setup.sh" ] || die "setup script is missing from $SOURCE_DIR" + +echo "" +echo " Preparing ONEComputer from $SOURCE_DIR" +echo "" + +set -- --source-dir "$SOURCE_DIR" +[ "$START" -eq 1 ] && set -- "$@" --start || set -- "$@" --no-start +[ "$SKIP_DEPS" -eq 1 ] && set -- "$@" --skip-deps +export ONECOMPUTER_HOME="$INSTALL_ROOT" +exec sh "$SOURCE_DIR/scripts/onecomputer/setup.sh" "$@" diff --git a/scripts/onecomputer/setup.sh b/scripts/onecomputer/setup.sh new file mode 100755 index 0000000..40f0b0f --- /dev/null +++ b/scripts/onecomputer/setup.sh @@ -0,0 +1,269 @@ +#!/bin/sh +# Configure an existing ONEComputer OSS checkout for local development. +# +# This script is separate from install.sh so contributors can rerun setup +# against a checkout without cloning, changing Git remotes, or deleting data. +# It starts only PostgreSQL; the web app and Rust gateway run from source. + +set -eu + +ROOT=$(CDPATH= cd -- "$(dirname "$0")/../.." && pwd) +START=0 +SKIP_DEPS=0 +DRY_RUN=0 +POSTGRES_PORT_EXPLICIT=0 +if [ -n "${ONECOMPUTER_POSTGRES_PORT:-}" ] || [ -n "${POSTGRES_PORT:-}" ]; then + POSTGRES_PORT_EXPLICIT=1 +fi +POSTGRES_PORT="${ONECOMPUTER_POSTGRES_PORT:-${POSTGRES_PORT:-5432}}" +APP_PORT="${ONECOMPUTER_APP_PORT:-10254}" +GATEWAY_PORT="${ONECOMPUTER_GATEWAY_PORT:-10255}" + +die() { + echo "onecomputer: error: $*" >&2 + exit 1 +} + +say() { + echo " $*" +} + +usage() { + cat <<'EOF' +Usage: scripts/onecomputer/setup.sh [options] + +Prepare an existing ONEComputer checkout for local development. + +Options: + --start Start the web and gateway dev processes. + --no-start Prepare dependencies and database only. + --skip-deps Skip pnpm install and Prisma generation. + --dry-run Print the plan without changing files or services. + --postgres-port PORT Host port for PostgreSQL (default 5432). + --app-port PORT Web dashboard port (default 10254). + --gateway-port PORT Gateway port (default 10255). + --help Show this help. +EOF +} + +env_value() { + key=$1 + file=$2 + awk -F= -v key="$key" '$1 == key {sub(/^[^=]*=/, ""); print; exit}' "$file" +} + +set_env_value() { + key=$1 + value=$2 + file=$3 + temp="$file.tmp.$$" + awk -v key="$key" -v value="$value" ' + BEGIN { updated = 0 } + $0 ~ "^" key "=" { print key "=" value; updated = 1; next } + { print } + END { if (!updated) print key "=" value } + ' "$file" > "$temp" || { rm -f "$temp"; die "could not update $file"; } + mv "$temp" "$file" +} + +random_base64() { + if command -v openssl >/dev/null 2>&1; then + openssl rand -base64 32 | tr -d '\r\n' + elif command -v node >/dev/null 2>&1; then + node -e 'process.stdout.write(require("crypto").randomBytes(32).toString("base64"))' + else + die "openssl or node is required to generate local secrets" + fi +} + +port_in_use() { + port=$1 + if command -v lsof >/dev/null 2>&1; then + lsof -iTCP:"$port" -sTCP:LISTEN -P -n >/dev/null 2>&1 + return $? + fi + if command -v ss >/dev/null 2>&1; then + ss -ltn 2>/dev/null | awk -v port=":$port" '$4 ~ port "$" { found = 1 } END { exit !found }' + return $? + fi + return 1 +} + +check_node() { + command -v node >/dev/null 2>&1 || die "Node.js 18+ is required; install it from https://nodejs.org/" + node_major=$(node --version | sed 's/^v//' | cut -d. -f1) + case "$node_major" in + ''|*[!0-9]*) die "could not read the Node.js version" ;; + esac + [ "$node_major" -ge 18 ] || die "Node.js 18+ is required (found $(node --version))" +} + +check_pnpm() { + if ! command -v pnpm >/dev/null 2>&1 && command -v corepack >/dev/null 2>&1; then + say "Enabling the repository's pnpm package manager..." + corepack enable >/dev/null 2>&1 || true + fi + command -v pnpm >/dev/null 2>&1 || die "pnpm 9+ is required. Run: corepack enable && corepack prepare pnpm@9.0.0 --activate" +} + +check_docker() { + command -v docker >/dev/null 2>&1 || die "Docker is required; install Docker Desktop or Docker Engine from https://docs.docker.com/get-docker/" + docker info >/dev/null 2>&1 || die "Docker is installed but not running. Start Docker and retry." + docker compose version >/dev/null 2>&1 || die "Docker Compose v2 is required. Update Docker Desktop or install the Compose plugin." +} + +ensure_env() { + env_file="$ROOT/.env" + example="$ROOT/.env.example" + [ -f "$example" ] || die "$example is missing" + + if [ ! -f "$env_file" ]; then + cp "$example" "$env_file" + chmod 600 "$env_file" + say "Created .env from .env.example (mode 600)." + fi + + encryption_key=$(env_value SECRET_ENCRYPTION_KEY "$env_file" || true) + case "${encryption_key:-}" in + ''|change-me-to-secure-key) + set_env_value SECRET_ENCRYPTION_KEY "$(random_base64)" "$env_file" + say "Generated a local encryption key without printing it." + ;; + esac + + internal_secret=$(env_value GATEWAY_INTERNAL_SECRET "$env_file" || true) + if [ -z "${internal_secret:-}" ]; then + set_env_value GATEWAY_INTERNAL_SECRET "$(random_base64)" "$env_file" + say "Generated a local gateway secret without printing it." + fi + + set_env_value POSTGRES_PORT "$POSTGRES_PORT" "$env_file" + set_env_value ONECOMPUTER_APP_PORT "$APP_PORT" "$env_file" + set_env_value ONECOMPUTER_GATEWAY_PORT "$GATEWAY_PORT" "$env_file" + + database_url=$(env_value DATABASE_URL "$env_file" || true) + case "$database_url" in + postgresql://*@localhost:*/*|postgresql://*@127.0.0.1:*/*) + local_database_url=$(printf '%s\n' "$database_url" | sed -E "s/@(localhost|127\\.0\\.0\\.1):[0-9]+\\//@\\1:$POSTGRES_PORT\\//") + if [ "$local_database_url" != "$database_url" ]; then + set_env_value DATABASE_URL "$local_database_url" "$env_file" + fi + ;; + esac + + chmod 600 "$env_file" +} + +while [ "$#" -gt 0 ]; do + case "$1" in + --start) START=1; shift ;; + --no-start) START=0; shift ;; + --skip-deps) SKIP_DEPS=1; shift ;; + --dry-run) DRY_RUN=1; shift ;; + --postgres-port) + [ "$#" -ge 2 ] || die "--postgres-port requires a port" + POSTGRES_PORT=$2 + POSTGRES_PORT_EXPLICIT=1 + shift 2 + ;; + --app-port) + [ "$#" -ge 2 ] || die "--app-port requires a port" + APP_PORT=$2 + shift 2 + ;; + --gateway-port) + [ "$#" -ge 2 ] || die "--gateway-port requires a port" + GATEWAY_PORT=$2 + shift 2 + ;; + --source-dir) + [ "$#" -ge 2 ] || die "--source-dir requires a path" + ROOT=$2 + shift 2 + ;; + --help|-h) usage; exit 0 ;; + *) die "unknown option '$1' (use --help)" ;; + esac +done + +ROOT=$(CDPATH= cd -- "$ROOT" && pwd) +[ -f "$ROOT/package.json" ] || die "$ROOT is not a ONEComputer checkout" +[ -f "$ROOT/docker/docker-compose.yml" ] || die "$ROOT/docker/docker-compose.yml is missing" + +if [ "$POSTGRES_PORT_EXPLICIT" -eq 0 ] && [ -f "$ROOT/.env" ]; then + configured_postgres_port=$(env_value POSTGRES_PORT "$ROOT/.env" || true) + if [ -n "${configured_postgres_port:-}" ]; then + POSTGRES_PORT=$configured_postgres_port + fi +fi + +if [ "$DRY_RUN" -eq 1 ]; then + echo "ONEComputer local setup plan" + echo " checkout: $ROOT" + echo " postgres: $POSTGRES_PORT" + echo " dashboard: $APP_PORT" + echo " gateway: $GATEWAY_PORT" + echo " start: $([ "$START" -eq 1 ] && echo yes || echo no)" + echo " deps: $([ "$SKIP_DEPS" -eq 1 ] && echo skip || echo install)" + echo "" + echo "No files or services will be changed." + exit 0 +fi + +check_node +check_pnpm +check_docker + +postgres_service_running() { + docker compose -f "$ROOT/docker/docker-compose.yml" ps --status running --services 2>/dev/null \ + | grep -qx 'postgres' +} + +if port_in_use "$POSTGRES_PORT" && ! postgres_service_running; then + if [ "$POSTGRES_PORT_EXPLICIT" -eq 1 ]; then + die "PostgreSQL port $POSTGRES_PORT is already in use; choose --postgres-port 5433" + fi + if ! port_in_use 5433; then + POSTGRES_PORT=5433 + say "Port 5432 is busy; using PostgreSQL port 5433." + else + die "PostgreSQL ports 5432 and 5433 are busy; choose --postgres-port PORT" + fi +fi + +cd "$ROOT" +ensure_env + +if [ "$SKIP_DEPS" -eq 0 ]; then + say "Installing JavaScript dependencies from pnpm-lock.yaml..." + pnpm install --frozen-lockfile + say "Generating Prisma client..." + pnpm db:generate +fi + +export POSTGRES_PORT +say "Starting PostgreSQL with Docker Compose..." +docker compose -f "$ROOT/docker/docker-compose.yml" up -d --wait postgres + +if [ "$SKIP_DEPS" -eq 0 ]; then + say "Applying database migrations..." + pnpm db:migrate +fi + +echo "" +echo "ONEComputer is prepared." +echo " Checkout: $ROOT" +echo " Dashboard: http://127.0.0.1:$APP_PORT" +echo " Gateway: http://127.0.0.1:$GATEWAY_PORT" +echo " PostgreSQL: 127.0.0.1:$POSTGRES_PORT" +echo "" +echo " Stop database: docker compose -f $ROOT/docker/docker-compose.yml stop postgres" +echo " Re-run setup: sh $ROOT/scripts/onecomputer/setup.sh" + +if [ "$START" -eq 1 ]; then + echo "" + echo "Starting the web and gateway development processes..." + exec pnpm dev +fi + +echo " Start app: cd $ROOT && pnpm dev"