diff --git a/.gitignore b/.gitignore index 587e1aa..0b7cd0b 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,7 @@ spark-warehouse/ derby.log k8s_metrics.json dashboard_token.txt + +# local python virtualenv (not part of the repo) +.venv/ +port-forward.log diff --git a/README.md b/README.md index 5804d4a..a6ed031 100644 --- a/README.md +++ b/README.md @@ -354,9 +354,11 @@ The assistant is context-aware -- it sees the current route, selected entities, | kubectl | 1.25+ | | Kind | 0.20+ | | Node.js | 18+ | -| Python | 3.10+ | +| Python | 3.9+ | | Make | any | +> **Running on a Linux server?** See the [Ubuntu Server install guide](docs/INSTALL_UBUNTU.md) for a one-command installer (`scripts/install-ubuntu.sh`), SSH-tunnel remote access, and reboot-safe port-forwards. + ### Full Reset (Recommended) The single command to build everything from scratch -- creates a Kind cluster, builds all Docker images, deploys all Kubernetes resources, initializes the database, ingests test data, and launches port-forwarding in separate terminal tabs: @@ -365,7 +367,7 @@ The single command to build everything from scratch -- creates a Kind cluster, b make reset-dev ``` -This is the go-to command for development. It tears down any existing cluster and stands up a clean environment end-to-end. Once complete, three terminal tabs will open automatically: +This is the go-to command for development. It tears down any existing cluster and stands up a clean environment end-to-end. On macOS, once complete, three terminal tabs will open automatically (on Linux the port-forwards run in the background, logged to `port-forward.log`): | Tab | Purpose | URL | | --- | --- | --- | diff --git a/docs/INSTALL.md b/docs/INSTALL.md index d0e010b..50de754 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -1,5 +1,10 @@ # Installation +> **On a Linux server?** This guide targets a macOS/Linux development laptop. +> For headless **Ubuntu Server** — including a one-command installer, remote +> access, and reboot-safe port-forwards — see +> [INSTALL_UBUNTU.md](INSTALL_UBUNTU.md). + ## Prerequisites | Tool | Version | Purpose | diff --git a/docs/INSTALL_UBUNTU.md b/docs/INSTALL_UBUNTU.md new file mode 100644 index 0000000..99bb6bf --- /dev/null +++ b/docs/INSTALL_UBUNTU.md @@ -0,0 +1,255 @@ +# Installing OpenUBA on Ubuntu Server + +This guide covers running OpenUBA on a headless **Ubuntu 22.04 / 24.04 Server**. +The [main install guide](INSTALL.md) targets a macOS development laptop; this +one documents the Linux-server specifics — remote access, the ports Kind binds, +and the handful of issues you can hit on a fresh box. + +> Originally contributed by the community in +> [issue #116](https://github.com/GACWR/OpenUBA/issues/116) (thanks to +> **@rock0ne**) and folded into the repo as the official Linux path. + +There are two ways to install: the **automated script** (recommended) or the +**manual steps** if you'd rather understand each one. + +--- + +## Prerequisites + +| Requirement | Minimum | Notes | +|---|---|---| +| OS | Ubuntu 22.04 / 24.04 LTS Server | Desktop works too | +| RAM | 8 GB | 16 GB recommended (Kind runs a full K8s cluster) | +| CPU | 4 cores | | +| Disk | 40 GB free | Docker images are large | +| Network | Internet access | For pulling images | +| Access | root / sudo | The installer manages system packages | + +--- + +## Architecture (what you're installing) + +OpenUBA runs entirely inside a **Kind** (Kubernetes-in-Docker) cluster on the +server: + +``` +Your workstation browser + │ SSH tunnel (ports 3000 + 8000) + ▼ +Ubuntu server () + │ systemd port-forwards (127.0.0.1 only) + ▼ +Kind cluster (Kubernetes-in-Docker) + ├── frontend (Next.js) :3000 + ├── backend (FastAPI) :8000 + ├── postgres (PostgreSQL) :5432 + ├── postgraphile (GraphQL API) :5000 + ├── elasticsearch (optional) :9200 + └── spark (ML jobs) +``` + +A few facts worth knowing up front: + +1. The frontend is built with its API URL baked in at image-build time, so + remote access is via an **SSH tunnel** (your workstation's `localhost` + forwards to the server) — see [Remote access](#remote-access). +2. The default admin (`openuba` / `password`) is **seeded by the backend on + first startup** — there is no separate setup wizard and no manual DB step. +3. Kind binds host ports **80 and 443** for its ingress, so anything already + on those ports (e.g. nginx) must be stopped before the cluster is created. + The installer handles this for you. + +--- + +## Option A — Automated install (recommended) + +From the server, as root: + +```bash +git clone https://github.com/GACWR/OpenUBA.git /opt/openuba +cd /opt/openuba +sudo bash scripts/install-ubuntu.sh +``` + +The script installs Docker, kubectl, Kind and Node.js; brings up the cluster +with `make reset-dev`; discovers the service names; and wires up reboot-safe +systemd port-forwards (plus an optional nginx proxy). It's safe to re-run from +a clean snapshot. + +It's configurable via environment variables (see the header of +`scripts/install-ubuntu.sh`) — for example: + +```bash +# skip the nginx proxy and install to a custom location +sudo OPENUBA_DIR=/srv/openuba SETUP_NGINX=false bash scripts/install-ubuntu.sh +``` + +> Run it against the **system** Python, not inside a `venv` — it manages its +> own dependencies with `--break-system-packages --ignore-installed`. + +Then jump to [Remote access](#remote-access). + +--- + +## Option B — Manual install + +### 1. System packages + +```bash +apt-get update +apt-get install -y curl wget git make jq python3 python3-pip \ + nginx ca-certificates gnupg postgresql-client conntrack socat +``` + +### 2. Docker + +```bash +curl -fsSL https://get.docker.com | sh +systemctl enable --now docker +``` + +### 3. kubectl + +```bash +KUBE_VER=$(curl -sL https://dl.k8s.io/release/stable.txt) +curl -sLo /usr/local/bin/kubectl "https://dl.k8s.io/release/${KUBE_VER}/bin/linux/amd64/kubectl" +chmod +x /usr/local/bin/kubectl +``` + +### 4. Kind + +```bash +curl -sLo /usr/local/bin/kind \ + "https://github.com/kubernetes-sigs/kind/releases/download/v0.24.0/kind-linux-amd64" +chmod +x /usr/local/bin/kind +``` + +### 5. Node.js LTS + +```bash +curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - +apt-get install -y nodejs +``` + +### 6. Clone and install dependencies + +```bash +git clone https://github.com/GACWR/OpenUBA.git /opt/openuba +cd /opt/openuba + +# --ignore-installed avoids the urllib3 RECORD-file error on Ubuntu 24.04 +pip3 install -r requirements.txt --break-system-packages --ignore-installed + +make dev-install-frontend # a "pnpm not found" warning is fine — it falls back to npm +``` + +### 7. Bring up the cluster + +```bash +# Kind needs ports 80/443 — free them first +systemctl stop nginx + +# Creates the cluster, builds images, deploys manifests, seeds the admin user. +# First run takes 10-20 minutes. +make reset-dev +``` + +`make reset-dev` detects Linux and runs the port-forwards in the background +(logged to `port-forward.log`) — no macOS Terminal is involved. + +### 8. Reboot-safe port-forwards + +The cluster is only reachable from inside the server until you expose it. +Create a systemd unit per service (discover the real names first with +`kubectl get svc -n openuba`): + +```bash +cat > /etc/systemd/system/openuba-pf-frontend.service <<'EOF' +[Unit] +Description=OpenUBA frontend port-forward +After=network.target +[Service] +Type=simple +ExecStart=/usr/local/bin/kubectl port-forward --namespace=openuba --address=127.0.0.1 svc/frontend 3000:3000 +Restart=on-failure +RestartSec=5s +Environment=KUBECONFIG=/root/.kube/config +[Install] +WantedBy=multi-user.target +EOF +# repeat for backend (8000) and postgres (5432) + +systemctl daemon-reload +systemctl enable --now openuba-pf-frontend openuba-pf-backend openuba-pf-postgres +systemctl start nginx # optional convenience proxy +``` + +--- + +## Remote access + +Because the frontend was compiled with a `localhost` API URL, use an SSH +tunnel from your workstation — direct browser access to `http://:3000` +loads the page but its API calls fail. + +**Linux / macOS:** + +```bash +ssh -N -L 3000:127.0.0.1:3000 -L 8000:127.0.0.1:8000 user@ +``` + +**Windows (PowerShell):** + +```powershell +ssh -N ` + -L 3000:127.0.0.1:3000 ` + -L 8000:127.0.0.1:8000 ` + user@ +``` + +Keep the terminal open, then browse to . + +--- + +## Default credentials + +``` +Username: openuba +Password: password +``` + +> ⚠️ Change this immediately after first login (**Settings → Users**). These +> are seeded by the backend on first startup — don't try to create a user via +> `psql` beforehand. + +--- + +## Known issues & fixes + +| Symptom | Cause | Fix | +|---|---|---| +| `Cannot uninstall urllib3 … RECORD file not found` | Ubuntu ships `urllib3` as a Debian package with no pip RECORD file | Install with `pip3 install --ignore-installed` (the script does this) | +| `failed to bind host port 0.0.0.0:80/tcp: address already in use` | Kind's ingress binds 80/443; nginx is already there | Stop nginx before `make reset-dev` (the script does this) | +| Login shows **"Failed to fetch"** | The browser is trying to reach `localhost:8000` on your workstation | SSH-tunnel **both** 3000 and 8000 (see [Remote access](#remote-access)) | +| `elastic_transport.ConnectionError` in backend logs | Elasticsearch is slower to become ready than the backend expects | Non-critical — login, alerts and rules work without it | +| `osascript` / "Terminal.app" error | Older releases launched port-forwards via a macOS-only command | Fixed — `scripts/start-dev.sh` now detects Linux and backgrounds them | + +--- + +## Verification + +```bash +kubectl get pods -n openuba # all pods Running +systemctl status openuba-pf-frontend --no-pager # active +curl -I http://127.0.0.1:3000 # HTTP 200 +curl -I http://127.0.0.1:8000/docs # HTTP 200 +``` + +## Diagnostics + +| Problem | Command | +|---|---| +| Pod not starting | `kubectl describe pod -n openuba ` | +| Backend errors | `kubectl logs -n openuba deploy/backend -f` | +| Port-forward died | `systemctl restart openuba-pf-frontend` | +| Full cluster rebuild (destructive) | `cd /opt/openuba && make reset-dev` | diff --git a/scripts/install-ubuntu.sh b/scripts/install-ubuntu.sh new file mode 100755 index 0000000..01f50ff --- /dev/null +++ b/scripts/install-ubuntu.sh @@ -0,0 +1,249 @@ +#!/usr/bin/env bash +# ============================================================================= +# OpenUBA — automated install for Ubuntu Server (22.04 / 24.04) +# +# Installs Docker, kubectl, Kind, and Node.js, then brings up the full OpenUBA +# stack in a local Kind cluster and exposes it via persistent (reboot-safe) +# port-forwards. Idempotent enough to re-run from a clean snapshot. +# +# Based on the community install guide contributed in issue #116 (thanks to +# @rock0ne). See docs/INSTALL_UBUNTU.md for the manual walkthrough, the known +# issues behind each step, and remote-access instructions. +# +# Usage: +# sudo bash scripts/install-ubuntu.sh # run from a fresh clone, or +# curl -fsSL /scripts/install-ubuntu.sh | sudo bash +# +# Configuration (override via environment): +# OPENUBA_DIR install location (default: /opt/openuba) +# OPENUBA_REPO git remote to clone (default: upstream GACWR/OpenUBA) +# OPENUBA_REF branch/tag to check out (default: master) +# K8S_NS kubernetes namespace (default: openuba) +# KIND_VER pinned Kind release (default: v0.24.0) +# SETUP_NGINX install convenience proxy (default: true) +# ============================================================================= +set -euo pipefail + +OPENUBA_DIR="${OPENUBA_DIR:-/opt/openuba}" +OPENUBA_REPO="${OPENUBA_REPO:-https://github.com/GACWR/OpenUBA.git}" +OPENUBA_REF="${OPENUBA_REF:-master}" +K8S_NS="${K8S_NS:-openuba}" +KIND_VER="${KIND_VER:-v0.24.0}" +SETUP_NGINX="${SETUP_NGINX:-true}" + +CYAN='\033[0;36m'; GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'; NC='\033[0m' +info() { echo -e "${CYAN}[INFO]${NC} $*"; } +ok() { echo -e "${GREEN}[OK]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +die() { echo -e "${RED}[ERR]${NC} $*" >&2; exit 1; } + +# ─── 0. Preflight ──────────────────────────────────────────────────────────── +[[ $EUID -ne 0 ]] && die "Run as root: sudo bash $0" +HOST_IP="$(hostname -I 2>/dev/null | awk '{print $1}')" +[[ -z "$HOST_IP" ]] && HOST_IP="" + +# ─── 1. System packages ────────────────────────────────────────────────────── +info "Installing system packages..." +apt-get update -qq +apt-get install -y -qq \ + curl wget git make jq python3 python3-pip \ + ca-certificates gnupg lsb-release \ + postgresql-client conntrack socat +[[ "$SETUP_NGINX" == "true" ]] && apt-get install -y -qq nginx + +# ─── 2. Docker ─────────────────────────────────────────────────────────────── +if ! command -v docker &>/dev/null; then + info "Installing Docker..." + curl -fsSL https://get.docker.com | sh + systemctl enable --now docker + [[ -n "${SUDO_USER:-}" ]] && usermod -aG docker "$SUDO_USER" +fi +ok "Docker: $(docker --version)" + +# ─── 3. kubectl ────────────────────────────────────────────────────────────── +if ! command -v kubectl &>/dev/null; then + info "Installing kubectl..." + KUBE_VER="$(curl -sL --max-time 10 https://dl.k8s.io/release/stable.txt 2>/dev/null || echo "v1.31.0")" + [[ -z "$KUBE_VER" ]] && KUBE_VER="v1.31.0" + curl -sLo /usr/local/bin/kubectl "https://dl.k8s.io/release/${KUBE_VER}/bin/linux/amd64/kubectl" + chmod +x /usr/local/bin/kubectl +fi +ok "kubectl: $(kubectl version --client 2>/dev/null | head -1)" + +# ─── 4. Kind ───────────────────────────────────────────────────────────────── +if ! command -v kind &>/dev/null; then + info "Installing Kind ${KIND_VER} (pinned to avoid GitHub API rate limits)..." + curl -sLo /usr/local/bin/kind "https://github.com/kubernetes-sigs/kind/releases/download/${KIND_VER}/kind-linux-amd64" + chmod +x /usr/local/bin/kind +fi +ok "Kind: $(kind version)" + +# ─── 5. Node.js LTS ────────────────────────────────────────────────────────── +if ! command -v node &>/dev/null; then + info "Installing Node.js LTS..." + curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - + apt-get install -y nodejs +fi +ok "Node: $(node --version) / npm: $(npm --version)" + +# ─── 6. Clone / update the repo ────────────────────────────────────────────── +if [[ -d "$OPENUBA_DIR/.git" ]]; then + info "Updating existing checkout in $OPENUBA_DIR..." + git -C "$OPENUBA_DIR" fetch --depth 1 origin "$OPENUBA_REF" + git -C "$OPENUBA_DIR" checkout "$OPENUBA_REF" + git -C "$OPENUBA_DIR" reset --hard "origin/$OPENUBA_REF" 2>/dev/null || true +else + info "Cloning OpenUBA into $OPENUBA_DIR..." + git clone --branch "$OPENUBA_REF" "$OPENUBA_REPO" "$OPENUBA_DIR" +fi +cd "$OPENUBA_DIR" +ok "Repo ready at $OPENUBA_DIR ($OPENUBA_REF)." + +# ─── 7. Python dependencies ────────────────────────────────────────────────── +# --ignore-installed avoids pip aborting on Debian-managed packages (e.g. +# urllib3) that ship without a RECORD file and cannot be cleanly uninstalled. +info "Installing Python dependencies..." +pip3 install -r requirements.txt --break-system-packages --ignore-installed -q +ok "Python deps installed." + +# ─── 8. Frontend dependencies ──────────────────────────────────────────────── +info "Installing frontend dependencies..." +make dev-install-frontend +ok "Frontend deps installed." + +# ─── 9. Free ports 80/443 for the Kind ingress ─────────────────────────────── +# configs/local.yaml binds 80/443 on the host; stop nginx first so cluster +# creation doesn't fail with "address already in use". We restart it later. +if [[ "$SETUP_NGINX" == "true" ]]; then + info "Stopping nginx to free ports 80/443 for cluster creation..." + systemctl stop nginx 2>/dev/null || true +fi + +# ─── 10. Build & deploy the cluster ────────────────────────────────────────── +# start-dev.sh detects Linux and runs port-forwards in the background (no +# macOS Terminal.app dependency), so no source patching is needed here. +info "Running make reset-dev (10-20 min on first run)..." +make reset-dev +ok "Cluster deployed. The backend seeds the default admin (openuba / password) on first startup." + +# ─── 11. Discover service + deployment names (no hardcoding) ────────────────── +info "Discovering services in namespace $K8S_NS..." +kubectl get svc -n "$K8S_NS" || true + +svc_by_port() { kubectl get svc -n "$K8S_NS" -o json | jq -r --argjson p "$1" '.items[] | select(.spec.ports[]?.port == $p) | .metadata.name' | head -1; } +svc_by_name() { kubectl get svc -n "$K8S_NS" --no-headers -o custom-columns=NAME:.metadata.name | grep -i "$1" | grep -iv graphile | head -1; } +deploy_by_name() { kubectl get deploy -n "$K8S_NS" --no-headers -o custom-columns=NAME:.metadata.name | grep -i "$1" | head -1; } + +FRONTEND_SVC="$(svc_by_port 3000)"; [[ -z "$FRONTEND_SVC" ]] && FRONTEND_SVC="$(svc_by_name front)" +BACKEND_SVC="$(svc_by_port 8000)"; [[ -z "$BACKEND_SVC" ]] && BACKEND_SVC="$(svc_by_name back)" +POSTGRES_SVC="$(svc_by_port 5432)"; [[ -z "$POSTGRES_SVC" ]] && POSTGRES_SVC="$(svc_by_name post)" +BACKEND_DEPLOY="$(deploy_by_name back)" +FRONTEND_DEPLOY="$(deploy_by_name front)" + +[[ -z "$FRONTEND_SVC" ]] && die "Cannot find frontend service. Check: kubectl get svc -n $K8S_NS" +[[ -z "$BACKEND_SVC" ]] && die "Cannot find backend service. Check: kubectl get svc -n $K8S_NS" +[[ -z "$BACKEND_DEPLOY" ]] && die "Cannot find backend deployment." +[[ -z "$FRONTEND_DEPLOY" ]] && die "Cannot find frontend deployment." +ok "Frontend svc=$FRONTEND_SVC deploy=$FRONTEND_DEPLOY | Backend svc=$BACKEND_SVC deploy=$BACKEND_DEPLOY" + +# ─── 12. Wait for rollouts ─────────────────────────────────────────────────── +info "Waiting for backend + frontend rollouts..." +kubectl rollout status -n "$K8S_NS" "deploy/$BACKEND_DEPLOY" --timeout=600s +kubectl rollout status -n "$K8S_NS" "deploy/$FRONTEND_DEPLOY" --timeout=600s +kubectl get pods -n "$K8S_NS" + +# ─── 13. Persistent systemd port-forwards (127.0.0.1, survive reboot) ───────── +info "Creating systemd port-forward services..." +write_pf_unit() { + local name="$1" svc="$2" local_port="$3" remote_port="$4" + cat > "/etc/systemd/system/openuba-pf-${name}.service" < /etc/nginx/sites-available/openuba <<'NGINX' +upstream openuba_frontend { server 127.0.0.1:3000; } +upstream openuba_backend { server 127.0.0.1:8000; } + +server { + listen 80; + server_name _; + + location / { + proxy_pass http://openuba_frontend; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 3600s; + } + + location /api/ { + proxy_pass http://openuba_backend/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} +NGINX + rm -f /etc/nginx/sites-enabled/default + ln -sf /etc/nginx/sites-available/openuba /etc/nginx/sites-enabled/openuba + nginx -t && systemctl enable --now nginx && systemctl reload nginx + ok "nginx running." +fi + +# ─── 15. Smoke test ────────────────────────────────────────────────────────── +info "Running smoke tests..." +sleep 3 +FRONTEND_HTTP="$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3000 || echo 000)" +BACKEND_HTTP="$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8000/docs || echo 000)" +[[ "$FRONTEND_HTTP" =~ ^(200|301|302|304)$ ]] && ok "Frontend responding: HTTP $FRONTEND_HTTP" || warn "Frontend not responding yet (HTTP $FRONTEND_HTTP) — may still be starting" +[[ "$BACKEND_HTTP" =~ ^(200|301|302|304)$ ]] && ok "Backend responding: HTTP $BACKEND_HTTP" || warn "Backend not responding yet (HTTP $BACKEND_HTTP) — may still be starting" + +# ─── 16. Summary ───────────────────────────────────────────────────────────── +cat </dev/null || true sleep 1 -open -a Terminal.app "$PROJECT_ROOT/scripts/port-forward.sh" +if [ "$(uname)" = "Darwin" ]; then + open -a Terminal.app "$PROJECT_ROOT/scripts/port-forward.sh" + PF_LOCATION="separate Terminal window" +else + nohup bash "$PROJECT_ROOT/scripts/port-forward.sh" \ + > "$PROJECT_ROOT/port-forward.log" 2>&1 & + PF_LOCATION="background (logs: port-forward.log)" +fi # Wait for port-forwards to establish echo " Waiting for services..." @@ -71,5 +80,5 @@ echo " Backend: http://localhost:8000" echo " PostGraphile: http://localhost:5001/graphql" echo " Spark UI: http://localhost:8080" echo "" -echo " Port-forwards running in separate Terminal window." -echo " Close that window or run: pkill -f 'kubectl.*port-forward.*openuba'" +echo " Port-forwards running in ${PF_LOCATION}." +echo " Stop them with: pkill -f 'kubectl.*port-forward.*openuba'"