From 7955d56ac268e48d5918de300691e02cd30467e0 Mon Sep 17 00:00:00 2001 From: Nawa Manusitthipol Date: Fri, 11 Sep 2026 18:08:54 -0400 Subject: [PATCH 1/3] Fix android-example platform mismatch and elixir-example OTP/arm64 gap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit android-example set common-args=[--platform, linux/amd64] to force the x86_64-only Android SDK to work under emulation, but that flag only ever reached `docker run` — buildLocalImage() never passed it to `docker build`, so the image built for the host's native arch and then failed to start under the forced platform ("does not provide the specified platform"). Extract --platform from common-args/run-args and apply it to the build too. elixir-example failed because ppa:rabbitmq/rabbitmq-erlang-28 doesn't publish arm64 builds of the individual erlang-base/erlang-dev/... packages erlang--setup.sh installs by name — only a few arch-independent metapackages. apt silently satisfied those names from Ubuntu's default archive instead (OTP 25 on arm64), and Elixir 1.19 dropped OTP 25 support, so the mismatch surfaced as a confusing 404 fetching elixir-otp-25.zip far from the actual cause. erlang--setup.sh now verifies the installed OTP major matches what was requested and fails immediately with a clear explanation instead of silently drifting. elixir-example itself is re-pinned to erlang 25 + elixir 1.18.4, a combination that installs identically on amd64 and arm64 without needing the incomplete PPA at all. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_0137yjw6oV573p5vEfCoqNpj --- cli/src/pkg/booth/ensure_docker_image.go | 34 ++ .../.booth/setups/affine-server--setup.sh | 411 ---------------- .../.booth/setups/postgresql--setup.sh | 137 ------ .../.booth/setups/anythingllm--setup.sh | 439 ------------------ .../.booth/setups/appwrite-cli--setup.sh | 96 ---- .../.booth/setups/appwrite-server--setup.sh | 429 ----------------- .../.booth/setups/deno-pkg--install.sh | 27 -- .../elixir-example/.booth/Boothfile | 12 +- .../.booth/setups/floci--setup.sh | 140 ------ .../.booth/setups/flutter--setup.sh | 222 --------- .../.booth/setups/flutter-android--setup.sh | 167 ------- .../setups/flutter-code-extension--setup.sh | 46 -- .../setups/flutter-linux-desktop--setup.sh | 103 ---- .../.booth/setups/cb-has-desktop.sh | 25 - .../.booth/setups/logo--setup.sh | 129 ----- .../.booth/setups/thonny--setup.sh | 109 ----- .../.booth/setups/wails--setup.sh | 164 ------- .../.booth/setups/wails-android--setup.sh | 100 ---- .../.booth/setups/wails--setup.sh | 164 ------- variants/base/setups/erlang--setup.sh | 26 ++ 20 files changed, 71 insertions(+), 2909 deletions(-) delete mode 100755 examples/workspaces/affine-example/.booth/setups/affine-server--setup.sh delete mode 100755 examples/workspaces/affine-example/.booth/setups/postgresql--setup.sh delete mode 100755 examples/workspaces/anythingllm-example/.booth/setups/anythingllm--setup.sh delete mode 100755 examples/workspaces/appwrite-example/.booth/setups/appwrite-cli--setup.sh delete mode 100755 examples/workspaces/appwrite-example/.booth/setups/appwrite-server--setup.sh delete mode 100755 examples/workspaces/deno-example/.booth/setups/deno-pkg--install.sh delete mode 100755 examples/workspaces/floci-example/.booth/setups/floci--setup.sh delete mode 100755 examples/workspaces/flutter-example/.booth/setups/flutter--setup.sh delete mode 100755 examples/workspaces/flutter-example/.booth/setups/flutter-android--setup.sh delete mode 100755 examples/workspaces/flutter-example/.booth/setups/flutter-code-extension--setup.sh delete mode 100755 examples/workspaces/flutter-example/.booth/setups/flutter-linux-desktop--setup.sh delete mode 100755 examples/workspaces/turtle-example/.booth/setups/cb-has-desktop.sh delete mode 100755 examples/workspaces/turtle-example/.booth/setups/logo--setup.sh delete mode 100755 examples/workspaces/turtle-example/.booth/setups/thonny--setup.sh delete mode 100755 examples/workspaces/wails-android-example/.booth/setups/wails--setup.sh delete mode 100755 examples/workspaces/wails-android-example/.booth/setups/wails-android--setup.sh delete mode 100755 examples/workspaces/wails-example/.booth/setups/wails--setup.sh diff --git a/cli/src/pkg/booth/ensure_docker_image.go b/cli/src/pkg/booth/ensure_docker_image.go index acc9d3dd..d7786bc8 100644 --- a/cli/src/pkg/booth/ensure_docker_image.go +++ b/cli/src/pkg/booth/ensure_docker_image.go @@ -8,6 +8,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "github.com/nawaman/codingbooth/src/pkg/appctx" "github.com/nawaman/codingbooth/src/pkg/boothfile" @@ -249,6 +250,18 @@ func buildLocalImage(ctx appctx.AppContext) { "--pull=false", ))) } + // A --platform in common-args/run-args exists to force a specific + // architecture for the whole booth (e.g. an x86_64-only SDK under + // emulation on arm64), but it was only ever threaded into `docker run`. + // Building without it produces an image for the host's native arch, and + // `docker run --platform ...` then refuses to start it: the local image + // has no manifest for the platform it was asked to run under. Apply the + // same platform to the build so the image it produces actually matches. + if platform, ok := extractPlatformFlag(ctx.CommonArgs(), ctx.RunArgs()); ok { + args = args.ExtendByLists(ilist.NewList(ilist.NewList( + "--platform", platform, + ))) + } args = args.ExtendByLists(ilist.NewList(ilist.NewList( "--build-arg", fmt.Sprintf("BOOTH_VARIANT_TAG=%s", ctx.Variant()), ))) @@ -278,6 +291,27 @@ func buildLocalImage(ctx appctx.AppContext) { } } +// extractPlatformFlag scans the given argument groups (in order) for a +// "--platform " or "--platform=" pair and returns the first +// value found. Groups are flat docker CLI argument lists, e.g. common-args +// or run-args from config.toml. +func extractPlatformFlag(groupLists ...ilist.List[ilist.List[string]]) (string, bool) { + for _, groups := range groupLists { + for _, group := range groups.Slice() { + items := group.Slice() + for i, item := range items { + if item == "--platform" && i+1 < len(items) { + return items[i+1], true + } + if value, ok := strings.CutPrefix(item, "--platform="); ok { + return value, true + } + } + } + } + return "", false +} + // pullImageIfNeeded pulls the Docker image if needed. func pullImageIfNeeded(ctx appctx.AppContext) { imageName := ctx.Image() diff --git a/examples/workspaces/affine-example/.booth/setups/affine-server--setup.sh b/examples/workspaces/affine-example/.booth/setups/affine-server--setup.sh deleted file mode 100755 index 242adc3a..00000000 --- a/examples/workspaces/affine-example/.booth/setups/affine-server--setup.sh +++ /dev/null @@ -1,411 +0,0 @@ -#!/bin/bash -# Copyright 2025-2026 : Nawa Manusitthipol -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. - -set -Eeuo pipefail -trap 'echo "❌ Error on line $LINENO"; exit 1' ERR - -usage() { - cat <] [--data clean|seed|persist] - $0 [PORT] - -Arguments: - --port PORT AFFiNE web port (default: 13010) - --data MODE clean (default): empty every booth - seed: restore ~/.affine (home-seed), do not dump back - persist: dump/restore ~/.affine (.booth/cache bind) - -Examples: - $0 - $0 --port 3010 --data persist - -Prerequisites: -- AFFiNE must be pre-installed at /opt/affine - (typically via COPY --from=ghcr.io/toeverything/affine: /app /opt/affine) -- Node.js 22, PostgreSQL, and Redis at runtime (the template requires them) - -Notes: -- Creates start-affine-server / stop-affine-server -- The server is NOT started during build; add the autostart extension -- persist requires +persist (cache-dirs bind). seed requires +seed (home-seed). -USAGE -} - -# ---- root check ---- -[[ $EUID -eq 0 ]] || { echo "❌ Run as root (sudo)"; exit 1; } - -# This script will always be installed by root. -HOME=/root - -if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then - usage - exit 0 -fi - -# ---- defaults / args ---- -AFFINE_SERVER_PORT="13010" -DATA_MODE="clean" -while [[ $# -gt 0 ]]; do - case "$1" in - --port) shift; AFFINE_SERVER_PORT="${1:-13010}"; shift ;; - --data) shift; DATA_MODE="${1:-clean}"; shift ;; - -h|--help) usage; exit 0 ;; - *) - if [[ "$1" =~ ^[0-9]+$ ]]; then - AFFINE_SERVER_PORT="$1"; shift - else - echo "❌ Unknown arg: $1" >&2; usage; exit 2 - fi - ;; - esac -done -DATA_MODE="$(echo "$DATA_MODE" | tr '[:upper:]' '[:lower:]')" -case "$DATA_MODE" in - clean|seed|persist) ;; - *) - echo "⚠️ Unknown --data ${DATA_MODE}; using clean." >&2 - DATA_MODE="clean" - ;; -esac - -AFFINE_DIR="/opt/affine" -PROFILE_FILE="/etc/profile.d/70-cb-affine-server--profile.sh" -STARTER_FILE="/usr/local/bin/start-affine-server" -STOPPER_FILE="/usr/local/bin/stop-affine-server" -LIB_FILE="/usr/local/lib/cb-affine-data.sh" - -# ---- verify AFFiNE is present ---- -if [[ ! -f "$AFFINE_DIR/dist/main.js" ]]; then - echo "❌ AFFiNE Server not found at $AFFINE_DIR" - echo " Use COPY --from=ghcr.io/toeverything/affine: /app /opt/affine" - exit 1 -fi - -# ---- Node.js 22 (native addons in the official image are built for it) ---- -need_node=1 -if command -v node >/dev/null 2>&1; then - NODE_MAJOR="$(node -v | sed 's/^v//' | cut -d. -f1)" - # Official image native addons are built for Node 22. Catalog nodejs now - # defaults to 24 (current LTS); a different major is an ABI mismatch. - if [[ "${NODE_MAJOR:-0}" -eq 22 ]]; then - echo "• Node.js already installed: $(node --version)" - need_node=0 - else - echo "• Node.js $(node --version) is not 22 (Affine native addons need 22)" - fi -fi -if [[ "$need_node" -eq 1 ]]; then - SETUPS_DIR="/opt/codingbooth/setups" - SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - if [[ -x "$SETUPS_DIR/nodejs--setup.sh" ]]; then - echo "• Installing Node.js 22 ..." - "$SETUPS_DIR/nodejs--setup.sh" 22 - elif [[ -x "$SCRIPT_DIR/nodejs--setup.sh" ]]; then - echo "• Installing Node.js 22 ..." - "$SCRIPT_DIR/nodejs--setup.sh" 22 - else - echo "❌ nodejs--setup.sh not found (need Node.js 22)" - exit 1 - fi -fi - -# ---- runtime libraries (Prisma / official image) ---- -export DEBIAN_FRONTEND=noninteractive -echo "• Installing OpenSSL (Prisma) ..." -apt-get update -apt-get install -y --no-install-recommends openssl ca-certificates - -# pgvector: official compose uses pgvector/pgvector. Indexer is off by default, -# but CREATE EXTENSION vector still succeeds when the package is present. -if command -v pg_config >/dev/null 2>&1; then - PG_VER="$(pg_config --version | grep -oE '[0-9]+' | head -1 || true)" - if [[ -n "$PG_VER" ]]; then - echo "• Installing postgresql-${PG_VER}-pgvector ..." - apt-get install -y --no-install-recommends "postgresql-${PG_VER}-pgvector" || \ - echo "⚠️ postgresql-${PG_VER}-pgvector is not in this distro — continuing without it." - fi -else - echo "⚠️ PostgreSQL not installed yet; pgvector skipped (select postgresql)." -fi -rm -rf /var/lib/apt/lists/* - -# Readable by the booth user; predeploy may write under /opt/affine. -chmod -R a+rX "$AFFINE_DIR" -if id coder >/dev/null 2>&1; then - chown -R coder:coder "$AFFINE_DIR" || true -fi - -mkdir -p /usr/local/lib -export BAKED_PORT="$AFFINE_SERVER_PORT" -export BAKED_DATA="$DATA_MODE" -envsubst '$BAKED_PORT $BAKED_DATA' <<'EOF' > "$LIB_FILE" -# shellcheck shell=bash -# Sourced by start-affine-server / stop-affine-server. - -cb_affine_init() { - PORT="${1:-${AFFINE_SERVER_PORT:-$BAKED_PORT}}" - AFFINE_DIR="/opt/affine" - DATA_DIR="${AFFINE_DATA_DIR:-$HOME/.affine}" - DUMP_FILE="${DATA_DIR}/dumps/affine.dump" - MODE="$(echo "${AFFINE_SERVER_DATA:-$BAKED_DATA}" | tr '[:upper:]' '[:lower:]')" - case "$MODE" in - clean|seed|persist) ;; - *) MODE="clean" ;; - esac - export NODE_ENV="${NODE_ENV:-production}" - export DEPLOYMENT_TYPE="${DEPLOYMENT_TYPE:-selfhosted}" - export AFFINE_INDEXER_ENABLED="${AFFINE_INDEXER_ENABLED:-false}" - export PORT - export AFFINE_SERVER_PORT="$PORT" - export REDIS_SERVER_HOST="${REDIS_SERVER_HOST:-127.0.0.1}" - export REDIS_SERVER_PORT="${REDIS_SERVER_PORT:-6379}" - export PATH="/opt/affine/node_modules/.bin:${PATH}" - CUSER="${USER:-coder}" - export DATABASE_URL="${DATABASE_URL:-postgresql://${CUSER}@127.0.0.1:5432/affine}" -} - -cb_affine_data_is_bind() { - local p="$DATA_DIR" - [[ -e "$p" ]] || return 1 - awk -v p="$p" '$5 == p { found=1 } END { exit !found }' /proc/self/mountinfo 2>/dev/null -} - -cb_affine_resolve_mode() { - if [[ "$MODE" == persist ]] && ! cb_affine_data_is_bind; then - echo "⚠️ AFFINE_SERVER_DATA=persist but ${DATA_DIR} is not a cache bind." >&2 - echo " Select affine-server+persist so cache-dirs mounts it. Using clean." >&2 - MODE="clean" - fi -} - -cb_affine_has_dump() { - [[ -s "$DUMP_FILE" ]] -} - -cb_affine_ensure_db() { - createdb affine 2>/dev/null || true - psql -d affine -c "CREATE EXTENSION IF NOT EXISTS vector;" >/dev/null 2>&1 || \ - echo "⚠️ pgvector extension not available — indexer stays disabled." -} - -cb_affine_wipe_db() { - dropdb --if-exists affine >/dev/null 2>&1 || true - createdb affine 2>/dev/null || true - psql -d affine -c "CREATE EXTENSION IF NOT EXISTS vector;" >/dev/null 2>&1 || true -} - -cb_affine_wipe_files() { - rm -rf "${DATA_DIR}/config" "${DATA_DIR}/storage" "${DATA_DIR}/dumps" -} - -cb_affine_dump() { - mkdir -p "${DATA_DIR}/dumps" - if ! command -v pg_dump >/dev/null 2>&1; then - echo "⚠️ pg_dump not available; skip persist dump." >&2 - return 0 - fi - if psql -d affine -c 'SELECT 1' >/dev/null 2>&1; then - echo "Dumping AFFiNE database to ${DUMP_FILE} ..." - pg_dump -Fc affine > "$DUMP_FILE" - fi -} - -cb_affine_restore() { - [[ -s "$DUMP_FILE" ]] || return 1 - echo "Restoring AFFiNE database from ${DUMP_FILE} ..." - cb_affine_wipe_db - pg_restore -d affine --no-owner --role="${USER:-coder}" "$DUMP_FILE" >/dev/null 2>&1 || \ - pg_restore -d affine "$DUMP_FILE" >/dev/null 2>&1 || true -} - -cb_affine_wait_deps() { - if command -v redis-server >/dev/null 2>&1 && ! redis-cli ping >/dev/null 2>&1; then - mkdir -p "${HOME}/.redis/data" "${HOME}/.redis/log" - echo "• Starting Redis ..." - redis-server --dir "${HOME}/.redis/data" --daemonize yes \ - --logfile "${HOME}/.redis/log/redis.log" - fi - echo "• Waiting for PostgreSQL and Redis ..." - local i pg_ok rd_ok - for i in $(seq 1 60); do - pg_ok=0; rd_ok=0 - if command -v pg_isready >/dev/null 2>&1 && pg_isready -q; then pg_ok=1; fi - if command -v redis-cli >/dev/null 2>&1 && redis-cli ping >/dev/null 2>&1; then rd_ok=1; fi - if [[ "$pg_ok" -eq 1 && "$rd_ok" -eq 1 ]]; then return 0; fi - sleep 1 - done - echo "❌ PostgreSQL and Redis must be running." >&2 - pg_isready || true - redis-cli ping || true - return 1 -} - -cb_affine_prepare_files() { - mkdir -p "${DATA_DIR}/config" "${DATA_DIR}/storage" - local cfg="${DATA_DIR}/config/config.json" - if [[ ! -f "$cfg" ]]; then - cat > "$cfg" </dev/null || true - sleep 1 - kill -9 $pid 2>/dev/null || true - fi -} -EOF -chmod 644 "$LIB_FILE" - -cat > "${PROFILE_FILE}" < "${STARTER_FILE}" <<'STARTER' -#!/usr/bin/env bash -set -euo pipefail -# shellcheck disable=SC1091 -source /usr/local/lib/cb-affine-data.sh -cb_affine_init "${1:-}" -cb_affine_resolve_mode - -if [[ ! -f "$AFFINE_DIR/dist/main.js" ]]; then - echo "❌ AFFiNE Server is not installed at $AFFINE_DIR" >&2 - exit 1 -fi - -cb_affine_wait_deps - -echo "Starting AFFiNE on http://localhost:${PORT} (data=${MODE})" -mkdir -p "$DATA_DIR" - -case "$MODE" in - clean) - cb_affine_wipe_files - cb_affine_wipe_db - redis-cli FLUSHDB >/dev/null 2>&1 || true - cb_affine_ensure_db - cb_affine_prepare_files - cb_affine_migrate - ;; - seed) - cb_affine_wipe_db - if cb_affine_has_dump; then - cb_affine_restore - else - cb_affine_ensure_db - fi - cb_affine_prepare_files - cb_affine_migrate - ;; - persist) - if cb_affine_has_dump; then - cb_affine_restore - else - cb_affine_ensure_db - fi - cb_affine_prepare_files - cb_affine_migrate - ;; -esac - -echo " Admin setup (first visit): http://localhost:${PORT}/admin/setup" -echo " Host browser: http://localhost:${PORT}/ (globe pane cannot load /admin/js/)" - -node ./dist/main.js & -affine_pid=$! -trap 'kill "$affine_pid" 2>/dev/null || true; [[ "$MODE" == persist ]] && cb_affine_dump; exit 0' TERM INT -wait "$affine_pid" || true -[[ "$MODE" == persist ]] && cb_affine_dump -STARTER -chmod 755 "${STARTER_FILE}" - -cat > "${STOPPER_FILE}" <<'STOPPER' -#!/usr/bin/env bash -set -euo pipefail -# shellcheck disable=SC1091 -source /usr/local/lib/cb-affine-data.sh -cb_affine_init "${1:-}" -cb_affine_resolve_mode -cb_affine_stop_server -if [[ "$MODE" == persist ]]; then - echo "Dumping AFFiNE data to ${DATA_DIR} ..." - cb_affine_dump - echo "AFFiNE data saved (persist)." -fi -echo "AFFiNE Server stopped." -STOPPER -chmod 755 "${STOPPER_FILE}" - -# ---- summary ---- -echo "" -# Register a desktop icon that opens AFFiNE in a browser (desktop variants only). -ICON="applications-internet" -for candidate in favicon.svg favicon.ico icon.png logo.png; do - found="$(find "$AFFINE_DIR/static" -maxdepth 3 -name "$candidate" -print -quit 2>/dev/null || true)" - if [ -n "$found" ]; then ICON="$found"; break; fi -done -cb-web-icon.sh --id affine-server --name "AFFiNE" --icon "$ICON" \ - --port-env AFFINE_SERVER_PORT --port "${AFFINE_SERVER_PORT}" \ - --path / --start start-affine-server - -echo "✅ AFFiNE Server installed." -echo " Location: ${AFFINE_DIR}" -echo " Port: ${AFFINE_SERVER_PORT}" -echo " Data: ${DATA_MODE}" -echo " Starter: ${STARTER_FILE}" -echo " Stopper: ${STOPPER_FILE}" -echo "" -echo "ℹ️ Ready to use:" -echo " start-affine-server [PORT]" -echo " stop-affine-server" -echo " Access: http://localhost:${AFFINE_SERVER_PORT}/admin/setup" -echo " Host browser (not the globe pane): http://localhost:${AFFINE_SERVER_PORT}/" -echo " Data: ${DATA_MODE} (clean | +seed | +persist)" -echo " Add +autostart to run it on boot, +expose to reach it from the host." diff --git a/examples/workspaces/affine-example/.booth/setups/postgresql--setup.sh b/examples/workspaces/affine-example/.booth/setups/postgresql--setup.sh deleted file mode 100755 index 6c7f8412..00000000 --- a/examples/workspaces/affine-example/.booth/setups/postgresql--setup.sh +++ /dev/null @@ -1,137 +0,0 @@ -#!/bin/bash -# Copyright 2025-2026 : Nawa Manusitthipol -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. - -set -Eeuo pipefail - -usage() { - cat <] - -Examples: - $0 # install default PostgreSQL - $0 --version 16 # pin to PostgreSQL 16 - -Notes: -- Installs PostgreSQL server + client via apt -- The server is NOT started during build (Docker best practice) -- A startup script auto-initializes and starts the server on container start -USAGE -} - -[[ $EUID -eq 0 ]] || { echo "❌ Run as root (use sudo)"; exit 1; } - -PG_VERSION="" - -while [[ $# -gt 0 ]]; do - case "$1" in - --version) shift; PG_VERSION="${1:-}"; shift ;; - -h|--help) usage; exit 0 ;; - *) echo "❌ Unknown arg: $1"; usage; exit 2 ;; - esac -done - -# "latest" means "whatever the distro ships" — same as passing nothing. Without -# this it would reach apt as the literal package name "postgresql-latest". -[[ "$PG_VERSION" == "latest" ]] && PG_VERSION="" - -export DEBIAN_FRONTEND=noninteractive -echo "📦 Installing PostgreSQL ..." -apt-get update - -if [[ -n "$PG_VERSION" ]]; then - apt-get install -y --no-install-recommends \ - "postgresql-${PG_VERSION}" "postgresql-client-${PG_VERSION}" libpq-dev -else - apt-get install -y --no-install-recommends \ - postgresql postgresql-client libpq-dev -fi - -rm -rf /var/lib/apt/lists/* - -# --- build-time: create CodingBooth PostgreSQL config files --- -CB_PG_CONF_DIR="/opt/codingbooth/postgresql" -mkdir -p "$CB_PG_CONF_DIR" - -cat > "$CB_PG_CONF_DIR/pg_hba.conf" <<'PGHBA' -# CodingBooth: trust local connections (dev environment) -local all all trust -host all all 127.0.0.1/32 trust -host all all ::1/128 trust -PGHBA - -cat > "$CB_PG_CONF_DIR/postgresql-booth.conf" <<'PGCONF' -# CodingBooth: PostgreSQL overrides for dev environment -listen_addresses = 'localhost' -PGCONF - -# --- startup script: init DB cluster and start server --- -STARTUP_FILE="/usr/share/startup.d/50-cb-postgresql--startup.sh" -install -d "$(dirname "$STARTUP_FILE")" -cat >"$STARTUP_FILE" <<'STARTUP' -#!/usr/bin/env bash -set -euo pipefail - -CB_PG_CONF_DIR="/opt/codingbooth/postgresql" - -# Find the installed PG version -PG_VER=$(pg_config --version 2>/dev/null | grep -oP '\d+' | head -1) -PG_DATA="/var/lib/postgresql/${PG_VER}/main" - -# booth-pgdata is a named volume reused across booths. The postgres UID in -# the image can drift (this image is 100; an older one left the volume at -# 112). A 0700 data dir owned by the old UID makes `cp pg_hba.conf` fail -# with "Permission denied" and, under booth-entry's set -e, takes the -# booth down before anything else starts. -if [ -d /var/lib/postgresql ]; then - if [ -d "$PG_DATA" ]; then - sudo -u postgres test -r "$PG_DATA" 2>/dev/null || \ - sudo chown -R postgres:postgres /var/lib/postgresql - else - sudo -u postgres test -w /var/lib/postgresql 2>/dev/null || \ - sudo chown -R postgres:postgres /var/lib/postgresql - fi -fi - -# Initialize cluster if not exists -if [ ! -d "$PG_DATA" ] || [ ! -f "$PG_DATA/PG_VERSION" ]; then - sudo -u postgres /usr/lib/postgresql/${PG_VER}/bin/initdb -D "$PG_DATA" 2>/dev/null || true -fi - -# Stale pid from another container that last used this volume -if [ -f "$PG_DATA/postmaster.pid" ]; then - if ! sudo -u postgres /usr/lib/postgresql/${PG_VER}/bin/pg_ctl -D "$PG_DATA" status >/dev/null 2>&1; then - sudo rm -f "$PG_DATA/postmaster.pid" - fi -fi - -# Apply CodingBooth configs (idempotent) -if [ -f "$CB_PG_CONF_DIR/pg_hba.conf" ]; then - sudo -u postgres cp "$CB_PG_CONF_DIR/pg_hba.conf" "$PG_DATA/pg_hba.conf" -fi -if [ -f "$CB_PG_CONF_DIR/postgresql-booth.conf" ] && \ - ! grep -q "include 'postgresql-booth.conf'" "$PG_DATA/postgresql.conf" 2>/dev/null; then - sudo -u postgres cp "$CB_PG_CONF_DIR/postgresql-booth.conf" "$PG_DATA/postgresql-booth.conf" - echo "include 'postgresql-booth.conf'" | sudo -u postgres tee -a "$PG_DATA/postgresql.conf" > /dev/null -fi - -# Start server -sudo -u postgres /usr/lib/postgresql/${PG_VER}/bin/pg_ctl \ - -D "$PG_DATA" -l /var/log/postgresql/postgresql.log start 2>/dev/null || true - -# Create a user matching the container user (if not exists) -CUSER="${USER:-coder}" -sudo -u postgres psql -tc "SELECT 1 FROM pg_roles WHERE rolname='${CUSER}'" 2>/dev/null \ - | grep -q 1 \ - || sudo -u postgres createuser -s "${CUSER}" 2>/dev/null || true -STARTUP -chmod 755 "$STARTUP_FILE" - -INSTALLED_VERSION=$(pg_config --version 2>/dev/null || echo "unknown") -echo "" -echo "✅ ${INSTALLED_VERSION} installed." -echo " Server auto-starts on container boot via startup script." -echo "" -echo "ℹ️ Ready to use: psql, createdb, pg_dump" diff --git a/examples/workspaces/anythingllm-example/.booth/setups/anythingllm--setup.sh b/examples/workspaces/anythingllm-example/.booth/setups/anythingllm--setup.sh deleted file mode 100755 index 49c3d095..00000000 --- a/examples/workspaces/anythingllm-example/.booth/setups/anythingllm--setup.sh +++ /dev/null @@ -1,439 +0,0 @@ -#!/bin/bash -# Copyright 2025-2026 : Nawa Manusitthipol -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. - -set -Eeuo pipefail -trap 'echo "❌ Error on line $LINENO"; exit 1' ERR - -usage() { - cat <] [--port ] - $0 [PORT] - -Arguments: - --version TAG Image tag that was copied in (default: 1.16.1; display only) - --port PORT Port for the AnythingLLM web UI (default: 3001) - -Examples: - $0 - $0 --port 13001 - $0 --version 1.16.1 --port 3001 - -Prerequisites: -- AnythingLLM must be pre-installed at /opt/anythingllm - (typically via COPY --from=mintplexlabs/anythingllm: /app /opt/anythingllm) - -Notes: -- Creates a starter script at /usr/local/bin/start-anythingllm -- The server is NOT started during build; add the autostart extension -- Workspaces, documents, and the SQLite DB live in ~/.anythingllm - (STORAGE_DIR, default ~/.anythingllm; +persist bind-mounts it) -- AUTH_TOKEN is the single-user password. Empty/unset = no login - (+passwordless). JWT_SECRET is set when a password is enabled. -- start-anythingllm sets SERVER_PORT and ANYTHING_LLM_RUNTIME=docker - (filesystem agent is hidden unless RUNTIME is docker) -- Pair with ollama+autostart and point the UI at http://127.0.0.1:11434 -- Host LM Studio: http://host.docker.internal:1234/v1 (not localhost) -- AnythingLLM web UI is accessible at http://localhost: -USAGE -} - -if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then - usage - exit 0 -fi - -# ---- root check ---- -[[ $EUID -eq 0 ]] || { echo "❌ Run as root (sudo)"; exit 1; } - -# This script will always be installed by root. -HOME=/root - -# ---- defaults / args ---- -VERSION="1.16.1" -ANYTHINGLLM_PORT="3001" -while [[ $# -gt 0 ]]; do - case "$1" in - --version) shift; VERSION="${1:-1.16.1}"; shift ;; - --port) shift; ANYTHINGLLM_PORT="${1:-3001}"; shift ;; - -h|--help) usage; exit 0 ;; - *) - if [[ "$1" =~ ^[0-9]+$ ]]; then - ANYTHINGLLM_PORT="$1"; shift - else - echo "❌ Unknown arg: $1" >&2; usage; exit 2 - fi - ;; - esac -done -VERSION="${VERSION#v}" - -ANYTHINGLLM_DIR="/opt/anythingllm" -PROFILE_FILE="/etc/profile.d/70-cb-anythingllm--profile.sh" -STARTUP_FILE="/usr/share/startup.d/70-cb-anythingllm--startup.sh" -STARTER_FILE="/usr/local/bin/start-anythingllm" - -# ---- verify AnythingLLM is present ---- -if [[ ! -f "$ANYTHINGLLM_DIR/server/index.js" ]]; then - echo "❌ AnythingLLM not found at $ANYTHINGLLM_DIR" - echo " Use COPY --from=mintplexlabs/anythingllm: /app /opt/anythingllm" - exit 1 -fi - -# ---- Node.js 18 (native addons in the official image are built for it) ---- -need_node=1 -if command -v node >/dev/null 2>&1; then - NODE_MAJOR="$(node -v | sed 's/^v//' | cut -d. -f1)" - # Official image native addons are built for Node 18. Catalog nodejs now - # defaults to 24 (current LTS); a different major is an ABI mismatch. - if [[ "${NODE_MAJOR:-0}" -eq 18 ]]; then - echo "• Node.js already installed: $(node --version)" - need_node=0 - else - echo "• Node.js $(node --version) is not 18 (AnythingLLM native addons need 18)" - fi -fi -if [[ "$need_node" -eq 1 ]]; then - SETUPS_DIR="/opt/codingbooth/setups" - SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - if [[ -x "$SETUPS_DIR/nodejs--setup.sh" ]]; then - echo "• Installing Node.js 18 ..." - "$SETUPS_DIR/nodejs--setup.sh" 18 - elif [[ -x "$SCRIPT_DIR/nodejs--setup.sh" ]]; then - echo "• Installing Node.js 18 ..." - "$SCRIPT_DIR/nodejs--setup.sh" 18 - else - echo "❌ nodejs--setup.sh not found (need Node.js 18)" - exit 1 - fi -fi - -# ---- runtime libraries ---- -export DEBIAN_FRONTEND=noninteractive -echo "• Installing OpenSSL (Prisma) ..." -apt-get update -apt-get install -y --no-install-recommends openssl ca-certificates - -# Puppeteer / ffmpeg only pay off on a real image (prisma schema present). -# A fixture used by tests has server/index.js and nothing else. -if [[ -f "$ANYTHINGLLM_DIR/server/prisma/schema.prisma" ]]; then - echo "• Installing ffmpeg and Chromium runtime libraries ..." - apt-get install -y --no-install-recommends \ - ffmpeg \ - fonts-liberation \ - libasound2t64 \ - libatk1.0-0 \ - libcups2 \ - libgbm1 \ - libnss3 \ - libpango-1.0-0 \ - libcairo2 \ - libxcomposite1 \ - libxdamage1 \ - libxfixes3 \ - libxrandr2 \ - libxkbcommon0 || \ - echo "⚠️ Some Chromium/ffmpeg packages were unavailable — URL scraping may fail." -fi -rm -rf /var/lib/apt/lists/* - -# Readable by the booth user; prisma generate may write under /opt/anythingllm. -chmod -R a+rX "$ANYTHINGLLM_DIR" -if id coder >/dev/null 2>&1; then - chown -R coder:coder "$ANYTHINGLLM_DIR" || true -fi - -# ---- SPA basename: host `/` and globe pane `/proxy/` both work ---- -# AnythingLLM's createBrowserRouter defaults to basename "/". The booth globe -# pane loads the app at /proxy//, which is not a route, so React 404s. -# Detect that prefix at runtime and pass it as basename. Host tabs stay "/". -if [[ -f "$ANYTHINGLLM_DIR/server/public/index.js" ]]; then - echo "• Patching AnythingLLM SPA basename for the booth globe pane ..." - node - "$ANYTHINGLLM_DIR" <<'PATCH' -const fs = require("fs"); -const path = require("path"); -const root = process.argv[2]; -const snippet = - '\n'; -const scriptTag = ''; -const snippetRe = - /