Initialize GovOPlaN meta repository

This commit is contained in:
2026-07-11 17:16:58 +02:00
commit 70109afc78
70 changed files with 11641 additions and 0 deletions

217
tools/launch/launch-dev.sh Normal file
View File

@@ -0,0 +1,217 @@
#!/usr/bin/env bash
set -euo pipefail
META_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
ROOT="${GOVOPLAN_CORE_ROOT:-$META_ROOT/../govoplan-core}"
ROOT="$(cd "$ROOT" && pwd)"
WEBUI_ROOT="${GOVOPLAN_WEBUI_ROOT:-$ROOT/webui}"
VENV_ROOT="${GOVOPLAN_VENV_ROOT:-$META_ROOT/.venv}"
PYTHON="${PYTHON:-$VENV_ROOT/bin/python}"
NODE_BIN="${NODE_BIN:-/home/zemion/.nvm/versions/node/v22.22.3/bin}"
NPM="${NPM:-$NODE_BIN/npm}"
NPM_USERCONFIG="$(mktemp "${TMPDIR:-/tmp}/govoplan-npmrc.XXXXXXXX")"
BACKEND_HOST="${GOVOPLAN_BACKEND_HOST:-127.0.0.1}"
BACKEND_PORT="${GOVOPLAN_BACKEND_PORT:-8000}"
FRONTEND_HOST="${GOVOPLAN_FRONTEND_HOST:-127.0.0.1}"
FRONTEND_PORT="${GOVOPLAN_FRONTEND_PORT:-5173}"
OPEN_BROWSER="${OPEN_BROWSER:-1}"
FRONTEND_FORCE_RELOAD="${GOVOPLAN_FRONTEND_FORCE_RELOAD:-1}"
FRONTEND_CLEAR_VITE_CACHE="${GOVOPLAN_FRONTEND_CLEAR_VITE_CACHE:-1}"
FRONTEND_USE_POLLING="${GOVOPLAN_FRONTEND_USE_POLLING:-1}"
FRONTEND_POLLING_INTERVAL="${GOVOPLAN_FRONTEND_POLLING_INTERVAL:-500}"
DEV_DATABASE_BACKEND="${GOVOPLAN_DEV_DATABASE_BACKEND:-postgres}"
LOG_DIR="${GOVOPLAN_DEV_LOG_DIR:-$META_ROOT/runtime/dev-launcher}"
BACKEND_LOG="$LOG_DIR/backend.log"
FRONTEND_LOG="$LOG_DIR/frontend.log"
BACKEND_URL="http://$BACKEND_HOST:$BACKEND_PORT"
FRONTEND_URL="http://$FRONTEND_HOST:$FRONTEND_PORT"
backend_pid=""
frontend_pid=""
run_grouped_pid=""
fail() {
printf 'launch-dev: %s\n' "$*" >&2
exit 1
}
run_grouped() {
local cwd="$1"
local log_file="$2"
shift 2
if command -v setsid >/dev/null 2>&1; then
setsid sh -c 'cd "$1" || exit 127; shift; exec "$@"' sh "$cwd" "$@" >"$log_file" 2>&1 &
else
(
cd "$cwd"
exec "$@"
) >"$log_file" 2>&1 &
fi
run_grouped_pid="$!"
}
terminate_process() {
local pid="${1:-}"
local pgid=""
[ -n "$pid" ] || return 0
kill -0 "$pid" 2>/dev/null || return 0
pgid="$(ps -o pgid= -p "$pid" 2>/dev/null | tr -d ' ' || true)"
if [ "$pgid" = "$pid" ]; then
kill -TERM "-$pid" 2>/dev/null || true
else
kill "$pid" 2>/dev/null || true
fi
sleep 1
kill -0 "$pid" 2>/dev/null || return 0
if [ "$pgid" = "$pid" ]; then
kill -KILL "-$pid" 2>/dev/null || true
else
kill -KILL "$pid" 2>/dev/null || true
fi
}
case "$DEV_DATABASE_BACKEND" in
postgres)
export DATABASE_URL="${DATABASE_URL:-postgresql+psycopg://govoplan_dev@127.0.0.1:5432/govoplan_dev}"
export GOVOPLAN_DATABASE_URL_PGTOOLS="${GOVOPLAN_DATABASE_URL_PGTOOLS:-postgresql://govoplan_dev@127.0.0.1:5432/govoplan_dev}"
;;
sqlite)
mkdir -p "$META_ROOT/runtime"
export DATABASE_URL="${DATABASE_URL:-sqlite:///$META_ROOT/runtime/govoplan-dev.db}"
;;
*)
fail "Unsupported GOVOPLAN_DEV_DATABASE_BACKEND=$DEV_DATABASE_BACKEND. Use postgres or sqlite."
;;
esac
export DEV_BOOTSTRAP_ENABLED="${DEV_BOOTSTRAP_ENABLED:-true}"
port_is_free() {
"$PYTHON" - "$1" "$2" <<'PY'
import socket
import sys
host = sys.argv[1]
port = int(sys.argv[2])
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
sock.bind((host, port))
except OSError:
raise SystemExit(1)
PY
}
wait_for_url() {
"$PYTHON" - "$1" <<'PY'
import sys
import time
import urllib.request
url = sys.argv[1]
deadline = time.monotonic() + 60
last_error = None
while time.monotonic() < deadline:
try:
with urllib.request.urlopen(url, timeout=2) as response:
if 200 <= response.status < 500:
raise SystemExit(0)
except Exception as exc: # noqa: BLE001 - printed only on timeout.
last_error = exc
time.sleep(1)
print(f"Timed out waiting for {url}: {last_error}", file=sys.stderr)
raise SystemExit(1)
PY
}
cleanup() {
terminate_process "$frontend_pid"
terminate_process "$backend_pid"
rm -f "$NPM_USERCONFIG"
}
trap cleanup EXIT INT TERM
[ -x "$PYTHON" ] || fail "Python virtualenv not found at $PYTHON. Run: cd $META_ROOT && python3 -m venv .venv && ./.venv/bin/python -m pip install --upgrade pip && ./.venv/bin/python -m pip install -r requirements-dev.txt"
[ -x "$NPM" ] || fail "npm not found at $NPM. Set NODE_BIN or NPM to your Node installation."
[ -f "$WEBUI_ROOT/package.json" ] || fail "WebUI package.json not found at $WEBUI_ROOT/package.json"
[ -d "$WEBUI_ROOT/node_modules" ] || fail "WebUI node_modules missing. Run: cd $WEBUI_ROOT && PATH=$NODE_BIN:\$PATH $NPM install"
(
cd "$META_ROOT"
"$PYTHON" - <<'PY'
import importlib
importlib.import_module("govoplan_core.devserver")
PY
) || fail "Python environment at $PYTHON cannot import govoplan_core.devserver. Run: cd $META_ROOT && ./.venv/bin/python -m pip install -r requirements-dev.txt"
mkdir -p "$LOG_DIR"
: > "$BACKEND_LOG"
: > "$FRONTEND_LOG"
if [ "$FRONTEND_CLEAR_VITE_CACHE" = "1" ]; then
rm -rf "$WEBUI_ROOT/node_modules/.vite" "$WEBUI_ROOT/node_modules/.vite-temp"
fi
port_is_free "$BACKEND_HOST" "$BACKEND_PORT" || fail "$BACKEND_URL is already in use"
port_is_free "$FRONTEND_HOST" "$FRONTEND_PORT" || fail "$FRONTEND_URL is already in use"
printf 'Starting GovOPlaN backend at %s\n' "$BACKEND_URL"
run_grouped "$ROOT" "$BACKEND_LOG" "$PYTHON" -m govoplan_core.devserver --host "$BACKEND_HOST" --port "$BACKEND_PORT"
backend_pid="$run_grouped_pid"
printf 'Waiting for %s/health\n' "$BACKEND_URL"
wait_for_url "$BACKEND_URL/health" || {
tail -n 80 "$BACKEND_LOG" >&2 || true
fail "backend did not become healthy"
}
printf 'Starting GovOPlaN WebUI at %s\n' "$FRONTEND_URL"
frontend_args=(run dev -- --host "$FRONTEND_HOST" --port "$FRONTEND_PORT" --strictPort)
if [ "$FRONTEND_FORCE_RELOAD" = "1" ]; then
frontend_args+=(--force)
fi
run_grouped "$WEBUI_ROOT" "$FRONTEND_LOG" \
env \
-u npm_config_tmp \
-u NPM_CONFIG_TMP \
"PATH=$WEBUI_ROOT/node_modules/.bin:$NODE_BIN:$PATH" \
"NPM_CONFIG_USERCONFIG=$NPM_USERCONFIG" \
"CHOKIDAR_USEPOLLING=$FRONTEND_USE_POLLING" \
"CHOKIDAR_INTERVAL=$FRONTEND_POLLING_INTERVAL" \
"VITE_API_PROXY_TARGET=$BACKEND_URL" \
"$NPM" "${frontend_args[@]}"
frontend_pid="$run_grouped_pid"
printf 'Waiting for %s\n' "$FRONTEND_URL"
wait_for_url "$FRONTEND_URL" || {
tail -n 80 "$FRONTEND_LOG" >&2 || true
fail "frontend did not become reachable"
}
if [ "$OPEN_BROWSER" = "1" ] && command -v xdg-open >/dev/null 2>&1; then
xdg-open "$FRONTEND_URL" >/dev/null 2>&1 || true
fi
cat <<EOF
GovOPlaN is running.
Web UI: $FRONTEND_URL
API: $BACKEND_URL/api/v1
Health: $BACKEND_URL/health
DB: $DATABASE_URL
Logs:
Backend: $BACKEND_LOG
WebUI: $FRONTEND_LOG
Frontend reload:
Vite cache cleared: $FRONTEND_CLEAR_VITE_CACHE
Vite --force: $FRONTEND_FORCE_RELOAD
Watch polling: $FRONTEND_USE_POLLING (${FRONTEND_POLLING_INTERVAL}ms)
Press Ctrl+C to stop both processes.
EOF
wait