From 3554c3deffc257ea54b5d7e72866a6f72847e2b0 Mon Sep 17 00:00:00 2001 From: saladday <1203511142@qq.com> Date: Wed, 30 Sep 2026 19:37:34 +0800 Subject: [PATCH] fix(installer): preserve configuration and recover safely from failures --- CONTRIBUTING.md | 28 ++- README.md | 38 ++-- install.sh | 354 ++++++++++++++++++++++--------- scripts/check-installer-cases.py | 263 +++++++++++++++++++++++ scripts/check-installer.sh | 29 ++- 5 files changed, 583 insertions(+), 129 deletions(-) create mode 100644 scripts/check-installer-cases.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7be6216c..28fe7f7d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2589,17 +2589,23 @@ or filesystem isolation. Automatic installation remains separate. Workspace model Provider credentials belong to the Parsar catalog and are sent through the write-only Core Session execution extension. Operator defaults remain an independent Core deployment option. An unconfigured product can start; Core operations return an explicit unavailable error. -- Root Compose deploys the product and its database. It mounts - `PARSAR_CORE_CONFIG_DIR` read-only for workspace bindings and caller key files. - The installer creates an empty binding list without overwriting existing - configuration. The product does not start a runtime container. -- The installer pulls the server image, prepares the data directory for the - image's actual UID/GID, verifies writability, then starts the product. Only the - preparation container runs as root. It generates stable database/master keys. -- The installer is a thin Compose wrapper. Its options cover the installation - directory, web bind/port, server image and validation. Native runtime history - migration and sandbox image options have been removed; no legacy deployment - compatibility or data backfill is provided by this cutover. +- Root Compose deploys the product and database; Core is a separate deployment. + Keep operator installation instructions in [README.md#quick-start](README.md#quick-start). +- The installer remains a standalone Bash 3.2-compatible Compose wrapper. Preserve + existing settings and secrets unless explicitly overridden. Stage and validate + configuration and pull images before committing files or preparing mounts. + Persist generated secrets only after verifying the actual Docker data mounts are + empty; inability to inspect data requires recovery of the original secrets. + Once mount preparation begins, retain matching configuration on failure; do not + attempt automatic database rollback. Serialize installers per canonical home and + clean temporary files and locks on normal exits and handled signals. +- Prepare data mounts for the image's configured UID/GID using root only for the + one-off preparation. Initialize missing Core bindings without replacing existing + contents; new bindings must be readable by the server and editable by the operator. + Persistent containers must not reference temporary Compose overrides. Wait for + database health and the server's `/readyz`; never remove unrelated orphan containers. +- Native runtime history migration and sandbox image options remain outside this + product/Core installation path; do not reintroduce legacy deployment backfills. - Services exposed through a deployment platform may gain an ingress network, but they must remain explicitly attached to the Compose `default` network when they depend on internal service DNS names such as `postgres`. diff --git a/README.md b/README.md index f58df205..ac2a43b7 100644 --- a/README.md +++ b/README.md @@ -32,24 +32,36 @@ keeps its own database for members, business assets, permissions and collaborati ## Quick Start -Requires Docker with Docker Compose v2. +Requires Bash, Docker running Linux containers, and Docker Compose v2.15 or newer +(on Linux, macOS, or WSL2). Start Docker before installing. ```bash curl -fsSL https://raw.githubusercontent.com/MiniMax-AI-Dev/parsar/main/install.sh | bash ``` -Open and create the first owner account in the web -setup flow. The first registered user is the administrator. - -The installer writes generated config, secrets, database files, and runtime -state under `~/.parsar/`. - -For local development from a checkout: - -```bash -make docker-build -./install.sh --image parsar:dev -``` +Open and create the first administrator account. +Configuration, secrets and data default to `~/.parsar/`. Back up this directory +before upgrading; keep `.env` with its matching database and encrypted data. + +Rerun the installation command to upgrade. Existing settings are preserved; +explicit options or exported `PARSAR_*` installation settings override them. +For a custom directory or port, append options with `bash -s -- --home /absolute/path +--port 19090`. Use `--help` for all options and `--dry-run` to validate without +saving configuration or starting containers. Advanced settings belong in `.env`. +Use `--compose-file /absolute/path/docker-compose.yml` to retain a custom Compose +file; otherwise the installer uses the current checkout's file or downloads it. + +The installer prints commands for status, logs and shutdown. Download, configuration +or image-pull failures leave existing files and services unchanged. Later failures +retain configuration and data so the same command can be retried after fixing the +reported error. It does not roll back database migrations or remove unrelated +containers, volumes or images. Normal exits and handled interruptions remove +temporary files and the installation lock; after a forced kill or power loss, +remove `/.install.lock` and `/.install.*` only after confirming no +installer is running. Never delete `.env` to resolve an installation error. + +For a locally built server image, run `make docker-build`, then +`PARSAR_IMAGE_PULL_POLICY=never ./install.sh --image parsar:dev` from the checkout. The default Compose stack starts PostgreSQL and the Parsar web control plane. Deploy Core separately and configure a distinct Core project credential for each diff --git a/install.sh b/install.sh index c66530d9..e9b25828 100755 --- a/install.sh +++ b/install.sh @@ -5,7 +5,7 @@ COMPOSE_URL="https://raw.githubusercontent.com/MiniMax-AI-Dev/parsar/main/docker usage() { cat <<'USAGE' -Install or upgrade Parsar with Docker Compose. +Install or upgrade Parsar with Docker Compose v2.15 or newer. Usage: ./install.sh [options] @@ -13,32 +13,27 @@ Usage: Options: --home PATH Install directory. Default: ~/.parsar - --port PORT Web UI port. Default: 18080 - --bind ADDRESS Web UI bind address. Default: 127.0.0.1 + --port PORT Web UI port. First-install default: 18080 + --bind ADDRESS Web UI bind address. First-install default: 127.0.0.1 --image IMAGE Override the server image. --compose-file PATH Use an existing Compose file. - --dry-run Prepare and validate without starting containers. + --dry-run Validate without changing configuration or containers. --help Show this help. -Advanced Compose settings can be added directly to ~/.parsar/.env. +Existing .env settings are preserved unless explicitly overridden. +Advanced Compose settings can be added directly to /.env. Configure workspace Core bindings in /core/workspaces.json after setup. Core owns execution environments and model-provider credentials. USAGE } -log() { - printf '[parsar-install] %s\n' "$*" -} - -die() { - printf '[parsar-install] ERROR: %s\n' "$*" >&2 - exit 1 -} +log() { printf '[parsar-install] %s\n' "$*"; } +die() { printf '[parsar-install] ERROR: %s\n' "$*" >&2; exit 1; } expand_path() { case "$1" in "~") printf '%s\n' "$HOME" ;; - "~/"*) printf '%s/%s\n' "$HOME" "${1#~/}" ;; + "~/"*) printf '%s/%s\n' "$HOME" "${1#'~/'}" ;; /*) printf '%s\n' "$1" ;; *) die "path must be absolute or start with ~/: $1" ;; esac @@ -52,19 +47,60 @@ random_hex() { fi } -ensure_env() { - local key="$1" value="$2" - grep -q "^${key}=" "$env_file" 2>/dev/null || printf '%s=%s\n' "$key" "$value" >>"$env_file" +has_env() { + grep -Eq "^[[:space:]]*(export[[:space:]]+)?${1}[[:space:]]*[:=]" "$env_file" } set_env() { - local key="$1" value="$2" temp_file="${env_file}.tmp.$$" - grep -v "^${key}=" "$env_file" 2>/dev/null >"$temp_file" || true - printf '%s=%s\n' "$key" "$value" >>"$temp_file" - mv "$temp_file" "$env_file" + local key="$1" value="$2" + [[ "$value" != *$'\n'* && "$value" != *$'\r'* ]] || die "$key must be a single line" + # Escape literals before Compose performs dotenv interpolation. + value="${value//\\/\\\\}" + value="${value//\"/\\\"}" + value="${value//\$/\$\$}" + awk -v key="$key" '$0 !~ "^[[:space:]]*(export[[:space:]]+)?" key "[[:space:]]*[:=]"' \ + "$env_file" >"$stage/env.next" + printf '%s="%s"\n' "$key" "$value" >>"$stage/env.next" + mv "$stage/env.next" "$env_file" +} + +ensure_env() { has_env "$1" || set_env "$1" "$2"; } + +cleanup() { + local status=$? + trap - EXIT + if [ "$status" -ne 0 ]; then + if [ "$committing" = true ] && [ "$starting" = false ]; then + if [ -f "$stage/old.env" ]; then mv "$stage/old.env" "$final_env"; else rm -f "$final_env"; fi + if [ "$downloaded" = true ]; then + if [ -f "$stage/old.compose" ]; then mv "$stage/old.compose" "$final_compose"; else rm -f "$final_compose"; fi + fi + fi + log "Installation stopped. Data is retained; correct the error and rerun the same command." + fi + [ -z "$stage" ] || rm -rf "$stage" + if [ "$locked" = true ]; then + rm -f "$lock_dir/pid" + rmdir "$lock_dir" + fi + exit "$status" +} + +begin_install() { + umask 077 + mkdir -p "$home" + home="$(cd "$home" && pwd -P)" + lock_dir="$home/.install.lock" + if ! mkdir "$lock_dir" 2>/dev/null; then + die "installation lock exists: $lock_dir. Wait for the other installer; after a crash, verify it is no longer running before removing this lock." + fi + locked=true + printf '%s\n' "$$" >"$lock_dir/pid" + stage="$(mktemp -d "$home/.install.XXXXXX")" } detect_docker() { + local version engine if docker compose version >/dev/null 2>&1 /dev/null 2>&1 /dev/null 2>&1 && @@ -72,141 +108,265 @@ detect_docker() { sudo -n docker info >/dev/null 2>&1 2 || (BASH_REMATCH[1] == 2 && BASH_REMATCH[2] >= 15))) \ + || die "Docker Compose v2.15 or newer is required" + engine="$(docker_run info --format '{{.OSType}}')" + [ "$engine" = linux ] || die "Docker must be configured to run Linux containers" } -docker_run() { - "${DOCKER[@]}" "$@" /dev/null 2>&1; then - curl -fsSL "$COMPOSE_URL" -o "$compose_file" + curl -fsSL --connect-timeout 15 --max-time 120 --retry 3 "$COMPOSE_URL" -o "$compose_file" elif command -v wget >/dev/null 2>&1; then - wget -qO "$compose_file" "$COMPOSE_URL" + wget --timeout=30 --tries=3 -qO "$compose_file" "$COMPOSE_URL" else die "curl or wget is required" fi } wait_for_health() { - for _ in $(seq 1 60); do - if compose exec -T parsar-server wget -qO- http://127.0.0.1:8080/healthz >/dev/null 2>&1; then + local attempt service container state ready + for ((attempt=0; attempt<60; attempt++)); do + ready=true + for service in postgres parsar-server; do + container="$(compose ps -aq "$service")" || return 1 + [ -n "$container" ] || { ready=false; continue; } + state="$(docker_run inspect --format '{{.State.Status}} {{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$container")" || return 1 + case "$state" in + 'running healthy'|'running none') ;; + exited*|dead*|*unhealthy) return 1 ;; + *) ready=false ;; + esac + done + if [ "$ready" = true ] && compose exec -T parsar-server wget -T 5 -qO- http://127.0.0.1:8080/readyz >/dev/null 2>&1; then return 0 fi - sleep 1 + sleep 2 done return 1 } +verify_empty_data() { + local service target + for service in postgres parsar-server; do + if [ "$service" = postgres ]; then target=/var/lib/postgresql/data; else target=/var/lib/parsar; fi + # Inspect actual mounts through Docker, including named volumes and root-owned paths. + compose run -T --rm --no-deps --user 0:0 --workdir / --entrypoint /bin/sh "$service" \ + -ec 'entries=$(ls -A "$1") && [ -z "$entries" ]' sh "$target" \ + || die "$service data is not empty or cannot be inspected; restore the original secrets in $final_env" + done +} + prepare_data_directory() { local data_owner - local PARSAR_IMAGE_PULL_POLICY=never - export PARSAR_IMAGE_PULL_POLICY data_owner="$(compose run -T --rm --no-deps --workdir / --entrypoint /bin/sh parsar-server \ -c 'printf "%s:%s" "$(id -u)" "$(id -g)"')" [[ "$data_owner" =~ ^[0-9]+:[0-9]+$ ]] || die "could not determine the server image user" - - # Only the one-off preparation runs as root; the server keeps its image user. - compose run -T --rm --no-deps --user 0:0 --workdir / --entrypoint /bin/sh parsar-server \ - -c 'chown -Rh "$1" /var/lib/parsar' sh "$data_owner" + # Only preparation runs as root; the server retains its configured user. + compose -f "$stage/prepare.yml" run -T --rm --no-deps --user 0:0 \ + --volume "$stage:/parsar-installer:ro" --workdir / --entrypoint /bin/sh parsar-server \ + -ec 'chown -Rh "$1" /var/lib/parsar + if [ -L /run/secrets/core/workspaces.json ]; then + echo "Core workspace configuration must not be a symlink" >&2; exit 1 + fi + if [ ! -e /run/secrets/core/workspaces.json ]; then + initial=$(mktemp /run/secrets/core/.workspaces.XXXXXX) + trap '\''rm -f "$initial"'\'' EXIT + printf "[]\n" > "$initial" + chmod 644 "$initial" + chown "$(stat -c %u:%g /parsar-installer)" /run/secrets/core "$initial" + ln "$initial" /run/secrets/core/workspaces.json + fi' sh "$data_owner" compose run -T --rm --no-deps --workdir / --entrypoint /bin/sh parsar-server \ - -c 'test -w /var/lib/parsar && mkdir -p /var/lib/parsar/.parsar && test -w /var/lib/parsar/.parsar' \ - || die "server data directory is not writable by its configured user" + -c 'test -w /var/lib/parsar && mkdir -p /var/lib/parsar/.parsar && test -w /var/lib/parsar/.parsar && test -r /run/secrets/core/workspaces.json' \ + || die "server user cannot write its data directory or read Core workspace configuration" } +stage="" locked=false starting=false committing=false downloaded=false +image_override=() +trap cleanup EXIT +trap 'exit 130' INT +trap 'exit 143' TERM +trap 'exit 129' HUP + home="${PARSAR_HOME:-$HOME/.parsar}" -port="${PARSAR_LOCAL_PORT:-18080}" -bind="${PARSAR_BIND_ADDR:-127.0.0.1}" +port="${PARSAR_LOCAL_PORT:-}" +bind="${PARSAR_BIND_ADDR:-}" server_image="${PARSAR_SERVER_IMAGE:-}" compose_source="${PARSAR_COMPOSE_FILE:-}" dry_run=false while [ "$#" -gt 0 ]; do case "$1" in - --home) home="${2:?missing value for --home}"; shift 2 ;; - --port) port="${2:?missing value for --port}"; shift 2 ;; - --bind) bind="${2:?missing value for --bind}"; shift 2 ;; - --image) server_image="${2:?missing value for --image}"; shift 2 ;; - --compose-file) compose_source="${2:?missing value for --compose-file}"; shift 2 ;; + --home|--port|--bind|--image|--compose-file) + [ "$#" -ge 2 ] && [ -n "$2" ] && [[ "$2" != --* ]] || die "missing value for $1" + case "$1" in + --home) home="$2" ;; + --port) port="$2" ;; + --bind) bind="$2" ;; + --image) server_image="$2" ;; + --compose-file) compose_source="$2" ;; + esac + shift 2 ;; --dry-run) dry_run=true; shift ;; --help|-h) usage; exit 0 ;; *) die "unknown option: $1" ;; esac done -case "$port" in (*[!0-9]*|"") die "--port must be numeric" ;; esac - +if [ -n "$port" ]; then + [[ "$port" =~ ^[0-9]{1,5}$ ]] && ((10#$port >= 1 && 10#$port <= 65535)) \ + || die "--port must be between 1 and 65535" + port="$((10#$port))" +fi home="$(expand_path "$home")" -mkdir -p "$home/postgres" "$home/data" -umask 077 -env_file="$home/.env" -touch "$env_file" +[[ "$home" != *$'\n'* && "$home" != *$'\r'* && "$home" != *:* ]] || die "home path cannot contain newlines or colons" +if [ -n "$compose_source" ]; then + compose_source="$(expand_path "$compose_source")" + [ -f "$compose_source" ] || die "compose file does not exist: $compose_source" +fi +detect_docker +begin_install +final_env="$home/.env" +[ ! -L "$final_env" ] && { [ ! -e "$final_env" ] || [ -f "$final_env" ]; } \ + || die "$final_env must be a regular file, not a symlink" +env_file="$stage/install.env" +if [ -f "$final_env" ]; then + cp "$final_env" "$env_file" + cp "$final_env" "$stage/old.env" +else + touch "$env_file" +fi if [ -n "$compose_source" ]; then - compose_file="$(expand_path "$compose_source")" - [ -f "$compose_file" ] || die "compose file does not exist: $compose_file" + final_compose="$compose_source" elif [ -f "$PWD/docker-compose.yml" ]; then - compose_file="$PWD/docker-compose.yml" + final_compose="$PWD/docker-compose.yml" else - compose_file="$home/docker-compose.yml" - download_compose + final_compose="$home/docker-compose.yml" + [ ! -L "$final_compose" ] && { [ ! -e "$final_compose" ] || [ -f "$final_compose" ]; } \ + || die "$final_compose must be a regular file, not a symlink" + [ ! -f "$final_compose" ] || cp "$final_compose" "$stage/old.compose" + downloaded=true +fi +project_dir="$(cd "$(dirname "$final_compose")" && pwd -P)" +compose_file="$final_compose" +if [ "$downloaded" = true ]; then + compose_file="$stage/docker-compose.yml" + download_compose || die "could not download Compose configuration; existing files were not changed" + [ -s "$compose_file" ] || die "downloaded Compose configuration is empty" fi -set_env PARSAR_LOCAL_PORT "$port" -set_env PARSAR_BIND_ADDR "$bind" -set_env PARSAR_PG_DATA_DIR "$home/postgres" -set_env PARSAR_DATA_DIR "$home/data" +if [ -n "$port" ]; then set_env PARSAR_LOCAL_PORT "$port"; else ensure_env PARSAR_LOCAL_PORT 18080; fi +if [ -n "$bind" ]; then set_env PARSAR_BIND_ADDR "$bind"; else ensure_env PARSAR_BIND_ADDR 127.0.0.1; fi +ensure_env PARSAR_PG_DATA_DIR "$home/postgres" +ensure_env PARSAR_DATA_DIR "$home/data" +ensure_env PARSAR_CORE_CONFIG_DIR "$home/core" [ -z "$server_image" ] || set_env PARSAR_SERVER_IMAGE "$server_image" -ensure_env PARSAR_PG_PASSWORD "$(random_hex 24)" -ensure_env PARSAR_MASTER_KEY "$(random_hex 32)" -core_config_dir="${PARSAR_CORE_CONFIG_DIR:-$home/core}" -core_config_dir="$(expand_path "$core_config_dir")" -mkdir -p "$core_config_dir" -if [ ! -f "$core_config_dir/workspaces.json" ]; then - printf '[]\n' > "$core_config_dir/workspaces.json" - chmod 755 "$core_config_dir" - chmod 644 "$core_config_dir/workspaces.json" -fi -set_env PARSAR_CORE_CONFIG_DIR "$core_config_dir" -detect_docker -log "Using $compose_file" -log "Using $env_file" -compose config --quiet +# Exported variables take precedence over --env-file; persist managed overrides. +for key in PARSAR_PG_DATA_DIR PARSAR_DATA_DIR PARSAR_CORE_CONFIG_DIR PARSAR_PG_PASSWORD PARSAR_MASTER_KEY PARSAR_IMAGE_PULL_POLICY; do + if [ -n "${!key:-}" ]; then + value="${!key}" + case "$key" in *_DIR) value="$(expand_path "$value")" ;; esac + set_env "$key" "$value" + fi +done +unset PARSAR_LOCAL_PORT PARSAR_BIND_ADDR PARSAR_SERVER_IMAGE PARSAR_PG_DATA_DIR +unset PARSAR_DATA_DIR PARSAR_CORE_CONFIG_DIR PARSAR_PG_PASSWORD PARSAR_MASTER_KEY PARSAR_IMAGE_PULL_POLICY + +generated_secrets=false +existing_secrets=false +if [ -f "$final_env" ] && { has_env PARSAR_PG_PASSWORD || has_env PARSAR_MASTER_KEY; }; then existing_secrets=true; fi +for key in PARSAR_PG_PASSWORD PARSAR_MASTER_KEY; do + if ! has_env "$key"; then + if [ "$existing_secrets" = true ]; then + die "$key is missing; restore its original value in $final_env, or supply both secrets when preconfiguring a new installation" + fi + if [ "$key" = PARSAR_PG_PASSWORD ]; then size=24; else size=32; fi + set_env "$key" "$(random_hex "$size")" + generated_secrets=true + fi +done +log "Using $final_compose" +log "Using $final_env" +cat >"$stage/validate.yml" <<'YAML' +services: + postgres: + environment: + POSTGRES_PASSWORD: ${PARSAR_PG_PASSWORD:?Set a nonempty database password} + parsar-server: + environment: + PARSAR_MASTER_KEY: ${PARSAR_MASTER_KEY:?Set a nonempty master key} +YAML +compose -f "$stage/validate.yml" config --quiet || die "invalid Compose configuration; existing files were not changed" +existing_containers="$(compose ps -aq)" || die "could not inspect the existing Compose project" +if [ "$generated_secrets" = true ] && [ -n "$existing_containers" ]; then + die "existing containers have no matching secrets; restore $final_env before upgrading" +fi +services="$(compose config --services)" +for service in postgres parsar-server; do + printf '%s\n' "$services" | grep -qx "$service" || die "Compose configuration is missing $service" +done if [ "$dry_run" = true ]; then - log "Dry run complete" + log "Dry run complete; configuration and containers were not changed" exit 0 fi +cat >"$stage/prepare.yml" <<'YAML' +services: + parsar-server: + volumes: + - type: bind + source: ${PARSAR_CORE_CONFIG_DIR:?Core configuration directory is required} + target: /run/secrets/core + read_only: false +YAML log "Pulling images" -compose pull parsar-server -log "Preparing server data directory" +compose pull || die "image pull failed; existing configuration and containers were not changed" +cat >"$stage/local-images.yml" <<'YAML' +services: + postgres: + pull_policy: never + parsar-server: + pull_policy: never +YAML +image_override=(-f "$stage/local-images.yml") +if [ "$generated_secrets" = true ]; then verify_empty_data; fi + +committing=true +if [ "$downloaded" = true ]; then mv "$compose_file" "$final_compose"; fi +mv "$env_file" "$final_env" +compose_file="$final_compose" +env_file="$final_env" +# Once preparation can write mounted data, keep the matching configuration on failure. +starting=true +log "Preparing server data and Core configuration" prepare_data_directory +image_override=() log "Starting Parsar" -compose up -d --remove-orphans - -if ! wait_for_health; then +if ! compose up -d --pull never || ! wait_for_health; then compose ps >&2 || true - compose logs --tail 120 parsar-server >&2 || true - die "parsar-server did not become healthy" + compose logs --tail 80 postgres parsar-server >&2 || true + die "services did not become ready; configuration and data were retained. Inspect the logs and rerun the same command." fi -cat <&2; fi; rm -rf "$test_root"' EXIT mkdir -p "$test_root/bin" # No Docker daemon is used. Record the lifecycle and simulate image identity @@ -12,14 +12,26 @@ mkdir -p "$test_root/bin" cat > "$test_root/bin/docker" <<'MOCK' #!/usr/bin/env bash set -euo pipefail -printf '%s\n' "$*" >> "$PARSAR_INSTALL_TEST_LOG" +printf '%s\n' "${*//$'\n'/ }" >> "$PARSAR_INSTALL_TEST_LOG" if [[ "$1" == compose && "$*" == *' run '* ]]; then # Compose v2.15 accepts pull_policy in YAML but has no run --pull option. - [[ "$*" != *'--pull '* && "${PARSAR_IMAGE_PULL_POLICY:-}" == never ]] + [[ "$*" != *'--pull '* && "$*" == *local-images.yml* ]] # The installer closes stdin, so Compose must not request an interactive TTY. [[ "$*" == *' -T '* ]] fi -if [[ "$*" == *'id -u'* ]]; then +if [[ "$*" == 'compose version --short' ]]; then + printf '2.15.1\n' +elif [[ "$1" == info ]]; then + printf 'linux\n' +elif [[ "$*" == *'config --services'* ]]; then + printf 'postgres\nparsar-server\n' +elif [[ "$*" == *' ps -aq '* ]]; then + printf 'test-container\n' +elif [[ "$1" == inspect ]]; then + printf 'running healthy\n' +elif [[ "$*" == *' port parsar-server '* ]]; then + printf '127.0.0.1:18080\n' +elif [[ "$*" == *'id -u'* ]]; then printf '12001:14001' elif [[ "$*" == *'chown -Rh'* && "${PARSAR_INSTALL_TEST_FAIL:-}" == ownership ]]; then exit 1 @@ -43,14 +55,13 @@ run_case() { run_case normal "" normal_log="$test_root/normal/docker.log" -[[ "$(cat "$test_root/normal/home/core/workspaces.json")" == "[]" ]] -printf '[{"workspace_id":"preserved"}]\n' > "$test_root/normal/home/core/workspaces.json" +cp "$test_root/normal/home/.env" "$test_root/normal/before.env" run_case normal "" -[[ "$(cat "$test_root/normal/home/core/workspaces.json")" == '[{"workspace_id":"preserved"}]' ]] +cmp "$test_root/normal/before.env" "$test_root/normal/home/.env" grep -q 'chown -Rh.*12001:14001' "$normal_log" grep 'chown -Rh' "$normal_log" | grep -q -- '--user 0:0' grep 'test -w' "$normal_log" | grep -vq -- '--user' -pull_line="$(grep -n ' pull ' "$normal_log" | head -1 | cut -d: -f1)" +pull_line="$(grep -n ' pull$' "$normal_log" | head -1 | cut -d: -f1)" owner_line="$(grep -n 'chown -Rh' "$normal_log" | cut -d: -f1)" write_line="$(grep -n 'test -w' "$normal_log" | cut -d: -f1)" up_line="$(grep -n ' up ' "$normal_log" | cut -d: -f1)" @@ -73,3 +84,5 @@ if grep -Eq ' (run|pull|up) ' "$test_root/dry/docker.log"; then exit 1 fi echo 'Installer data preparation and Core configuration checks passed.' + +python3 "$repo_root/scripts/check-installer-cases.py"