Add Komodo installation script and docs
Add Komodo (Docker + MongoDB) installation script for Alpine VM Update README with Komodo requirements and installation command
This commit is contained in:
@@ -24,9 +24,10 @@ These scripts automate the deployment of personal infrastructure components. The
|
|||||||
| [`gitea-runner/install.sh`](gitea-runner/) | Gitea Act Runner on Alpine LXC (Proxmox) | `bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/gitea-runner/install.sh)"` |
|
| [`gitea-runner/install.sh`](gitea-runner/) | Gitea Act Runner on Alpine LXC (Proxmox) | `bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/gitea-runner/install.sh)"` |
|
||||||
| [`openbao/install.sh`](openbao/) | OpenBao secrets manager on Alpine LXC (Proxmox) | `bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/openbao/install.sh)"` |
|
| [`openbao/install.sh`](openbao/) | OpenBao secrets manager on Alpine LXC (Proxmox) | `bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/openbao/install.sh)"` |
|
||||||
| [`ferretdb/install.sh`](ferretdb/) | FerretDB (MongoDB-compatible) on Debian LXC (Proxmox) | `bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/ferretdb/install.sh)"` |
|
| [`ferretdb/install.sh`](ferretdb/) | FerretDB (MongoDB-compatible) on Debian LXC (Proxmox) | `bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/ferretdb/install.sh)"` |
|
||||||
|
| [`komodo/install.sh`](komodo/) | Komodo (Docker + MongoDB) on Alpine VM | `bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/komodo/install.sh)"` |
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
- Fresh Debian 12/13 installation (proxy, netlab) or Proxmox VE host (gitea-runner, openbao, ferretdb)
|
- Fresh Debian 12/13 installation (proxy, netlab) or Proxmox VE host (gitea-runner, openbao, ferretdb) or Alpine VM (komodo)
|
||||||
- User with sudo privileges (do not run as root) — except gitea-runner, openbao and ferretdb which run as root on Proxmox
|
- User with sudo privileges (do not run as root) — except gitea-runner, openbao, ferretdb, and komodo which run as root
|
||||||
- Internet access
|
- Internet access
|
||||||
|
|||||||
94
komodo/README.md
Normal file
94
komodo/README.md
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
# Komodo
|
||||||
|
|
||||||
|
Automated installation script for [Komodo](https://komo.do) — Docker + a MongoDB-backed Core + Periphery stack — running inside an **Alpine Linux VM**.
|
||||||
|
|
||||||
|
Follows the upstream [MongoDB quick start](https://komo.do/docs/setup/mongo): MongoDB stores all
|
||||||
|
resource configuration, audit logs, users, and system state. The Core exposes the API/UI on
|
||||||
|
loopback only; [Tailscale](https://tailscale.com) (running on the VM) acts as the reverse
|
||||||
|
proxy and terminates TLS via tailnet certificates.
|
||||||
|
|
||||||
|
### Why a VM, not an LXC?
|
||||||
|
|
||||||
|
Komodo Periphery binds `/var/run/docker.sock` and `/proc` from the host into a container so it
|
||||||
|
can manage other containers and report system stats. In an unprivileged LXC this clashes with
|
||||||
|
the cgroup / docker-in-docker constraints Proxmox imposes. A regular VM keeps it boring — the
|
||||||
|
script can just run `apk add docker` and let dockerd own the kernel namespace.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
| Context | Action |
|
||||||
|
| ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| Fresh Alpine VM (no `/opt/komodo/compose.env`) | Enables the `community` repo, installs Docker + Tailscale, generates secrets, writes compose files, pulls + ups |
|
||||||
|
| Re-run on an existing install (`/opt/komodo/compose.env` already there) | Preserves the generated secrets, re-pulls the latest images, and recreates the stack |
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
- Alpine Linux **VM** (`/etc/alpine-release` must exist), not an LXC
|
||||||
|
- Internet access from the VM
|
||||||
|
- Run as **root** (enforced)
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
#### Install / update
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/komodo/install.sh)"
|
||||||
|
```
|
||||||
|
|
||||||
|
The script prints the generated `KOMODO_DATABASE_PASSWORD`, `KOMODO_WEBHOOK_SECRET`, and
|
||||||
|
`KOMODO_JWT_SECRET` at the end. They are also written to `/opt/komodo/compose.env`
|
||||||
|
(`chmod 600`, root-only).
|
||||||
|
|
||||||
|
The initial admin user is `admin` / `changeme` — change it from the UI immediately after first
|
||||||
|
login.
|
||||||
|
|
||||||
|
#### Customisation
|
||||||
|
|
||||||
|
Every parameter is exposed as an environment variable:
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
| -------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `KOMODO_DIR` | `/opt/komodo` | Where the compose files and env file live |
|
||||||
|
| `KOMODO_HOSTNAME` | `komodo` | Hostname used when running `tailscale up` |
|
||||||
|
| `KOMODO_LISTEN_ADDR` | `127.0.0.1:9120` | Host-side bind for Core's HTTP listener. Loopback by default — Tailscale fronts it. |
|
||||||
|
| `KOMODO_HOST` | `https://komodo.taila5ad8.ts.net` | Written verbatim into `compose.env`; used by Komodo for OAuth / webhook URL suggestions. Set this to whatever your tailnet (or external reverse proxy) is. |
|
||||||
|
| `TS_AUTHKEY` | _(unset)_ | Pre-auth key (generate at <https://login.tailscale.com/admin/settings/keys>). If unset, finish `tailscale up` manually inside the VM. |
|
||||||
|
|
||||||
|
All other Komodo / Periphery variables are written with the upstream defaults — edit
|
||||||
|
`/opt/komodo/compose.env` if you need to tune them, then:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/komodo
|
||||||
|
docker compose --env-file compose.env -f mongo.compose.yaml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Tailscale reverse proxy
|
||||||
|
|
||||||
|
Komodo Core binds to `127.0.0.1:9120` only — Tailscale (running on the VM, **not** in a
|
||||||
|
container) is the reverse proxy and terminates TLS via tailnet certificates.
|
||||||
|
|
||||||
|
If `TS_AUTHKEY` was supplied at install time, the script runs `tailscale up` and
|
||||||
|
`tailscale serve --bg --https=443 http://127.0.0.1:9120` automatically. Komodo then becomes
|
||||||
|
reachable at `https://<hostname>.<tailnet>.ts.net` (matching `KOMODO_HOST`).
|
||||||
|
|
||||||
|
Otherwise, finish setup manually inside the VM:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tailscale up --ssh --hostname komodo
|
||||||
|
tailscale serve --bg --https=443 http://127.0.0.1:9120
|
||||||
|
tailscale status # confirms the tailnet FQDN
|
||||||
|
```
|
||||||
|
|
||||||
|
> HTTPS in `tailscale serve` requires HTTPS to be enabled on your tailnet
|
||||||
|
> (Admin console → DNS → HTTPS Certificates).
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
|
||||||
|
- **OS**: Alpine VM with the `community` apk repo enabled (required for `docker`)
|
||||||
|
- **Engine**: `docker` + `docker-cli-compose` (the `docker compose` v2 plugin), OpenRC service `docker` enabled at boot
|
||||||
|
- **Stack**: `mongo`, `ghcr.io/moghtech/komodo-core:2`, `ghcr.io/moghtech/komodo-periphery:2` — see [upstream `mongo.compose.yaml`](https://komo.do/docs/setup/mongo)
|
||||||
|
- **Compose files**: `/opt/komodo/mongo.compose.yaml` + `/opt/komodo/compose.env` (`chmod 600`)
|
||||||
|
- **Network**: Core's `ports:` mapping is rewritten to `${KOMODO_LISTEN_ADDR}:9120` so only the loopback (and Tailscale serve) can reach it; Periphery has no host port
|
||||||
|
- **Reverse proxy**: Tailscale runs natively on the VM (not in a container) and publishes the loopback listener via `tailscale serve --https=443`
|
||||||
|
- **Secrets**: `KOMODO_DATABASE_PASSWORD`, `KOMODO_WEBHOOK_SECRET`, `KOMODO_JWT_SECRET` are auto-generated (`openssl rand -hex …`) on first install and preserved across re-runs
|
||||||
|
- **Logs**: Docker `json-file` driver capped at `max-size=10m`, `max-file=3` per container (`/etc/docker/daemon.json`)
|
||||||
463
komodo/install.sh
Executable file
463
komodo/install.sh
Executable file
@@ -0,0 +1,463 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# install.sh - Komodo (Docker + MongoDB) on an Alpine VM
|
||||||
|
# Usage (from inside the Alpine VM, as root):
|
||||||
|
# bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/komodo/install.sh)"
|
||||||
|
#
|
||||||
|
# Unlike the openbao/ferretdb scripts, this is targeted at an Alpine *VM*, not
|
||||||
|
# an LXC: there is no Proxmox-host orchestration mode. The script just installs
|
||||||
|
# Docker, pulls Komodo (Core + Periphery + MongoDB) via docker compose, and
|
||||||
|
# fronts it on the tailnet via `tailscale serve --https=443`.
|
||||||
|
#
|
||||||
|
# Re-running the script is safe: existing /opt/komodo/compose.env secrets are
|
||||||
|
# preserved, and `docker compose up -d --pull always` upgrades the images.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# --- Config (override via environment) ---
|
||||||
|
KOMODO_DIR="${KOMODO_DIR:-/opt/komodo}"
|
||||||
|
KOMODO_HOSTNAME="${KOMODO_HOSTNAME:-komodo}"
|
||||||
|
# Komodo Core publishes its HTTP listener on this address — kept on loopback
|
||||||
|
# only; the tailnet reaches it through `tailscale serve`.
|
||||||
|
KOMODO_LISTEN_ADDR="${KOMODO_LISTEN_ADDR:-127.0.0.1:9120}"
|
||||||
|
# `KOMODO_HOST` ends up in the compose.env verbatim. Default is the tailnet
|
||||||
|
# FQDN you asked for; override to point Komodo at a different reverse proxy.
|
||||||
|
KOMODO_HOST="${KOMODO_HOST:-https://komodo.taila5ad8.ts.net}"
|
||||||
|
# Optional: pre-authorise the VM's Tailscale non-interactively.
|
||||||
|
# Generate at https://login.tailscale.com/admin/settings/keys
|
||||||
|
TS_AUTHKEY="${TS_AUTHKEY:-}"
|
||||||
|
|
||||||
|
# --- Colors ---
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
# Logs go to stderr so callers can safely use $(fn) without capturing log noise.
|
||||||
|
log_info() { echo -e "${GREEN}[INFO]${NC} $1" >&2; }
|
||||||
|
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" >&2; }
|
||||||
|
log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
|
||||||
|
|
||||||
|
require_root() {
|
||||||
|
if [[ "$(id -u)" -ne 0 ]]; then
|
||||||
|
log_error "This script must be run as root (current uid: $(id -u))."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
require_alpine() {
|
||||||
|
if [[ ! -f /etc/alpine-release ]]; then
|
||||||
|
log_error "This script targets Alpine Linux (no /etc/alpine-release found)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Hex secret of the requested byte length.
|
||||||
|
gen_secret() {
|
||||||
|
local bytes="${1:-32}"
|
||||||
|
if command -v openssl >/dev/null 2>&1; then
|
||||||
|
openssl rand -hex "$bytes"
|
||||||
|
else
|
||||||
|
head -c "$bytes" /dev/urandom | od -An -tx1 | tr -d ' \n'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Docker — Alpine install per https://wiki.alpinelinux.org/wiki/Docker
|
||||||
|
# ============================================================
|
||||||
|
install_docker() {
|
||||||
|
log_info "Enabling the Alpine 'community' repository..."
|
||||||
|
# The community repo holds the docker package. The default repositories file
|
||||||
|
# carries a commented `community` line; uncomment it (idempotent).
|
||||||
|
sed -i -E 's|^#(.*/community)$|\1|' /etc/apk/repositories
|
||||||
|
|
||||||
|
log_info "Updating package index..."
|
||||||
|
apk update >/dev/null
|
||||||
|
|
||||||
|
log_info "Installing Docker + Compose plugin..."
|
||||||
|
# docker-cli-compose provides `docker compose` (v2 plugin), which is what the
|
||||||
|
# Komodo upstream docs assume. openrc is normally already on the VM but
|
||||||
|
# listing it explicitly keeps this script self-contained.
|
||||||
|
apk add --no-cache docker docker-cli-compose openrc >/dev/null
|
||||||
|
|
||||||
|
log_info "Enabling dockerd at boot..."
|
||||||
|
rc-update add docker default >/dev/null
|
||||||
|
|
||||||
|
if ! rc-service docker status >/dev/null 2>&1; then
|
||||||
|
log_info "Starting dockerd..."
|
||||||
|
rc-service docker start >/dev/null
|
||||||
|
else
|
||||||
|
log_info "dockerd already running."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Sanity check — fail fast if the daemon never came up.
|
||||||
|
local tries=0
|
||||||
|
until docker info >/dev/null 2>&1; do
|
||||||
|
tries=$((tries + 1))
|
||||||
|
if (( tries > 15 )); then
|
||||||
|
log_error "dockerd did not become ready after 15s — check /var/log/docker.log"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Tailscale — install and front Komodo with `tailscale serve`
|
||||||
|
# ============================================================
|
||||||
|
install_tailscale() {
|
||||||
|
if command -v tailscale >/dev/null 2>&1; then
|
||||||
|
log_info "Tailscale already installed."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
log_info "Installing Tailscale from the Alpine community repo..."
|
||||||
|
apk add --no-cache tailscale >/dev/null
|
||||||
|
rc-update add tailscale default >/dev/null 2>&1 || true
|
||||||
|
rc-service tailscale start >/dev/null 2>&1 \
|
||||||
|
|| log_warn "tailscaled failed to start — check /var/log/tailscaled.log"
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_tailscale_proxy() {
|
||||||
|
if ! command -v tailscale >/dev/null 2>&1; then
|
||||||
|
log_warn "tailscale CLI not found, skipping reverse-proxy setup."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local backend_state
|
||||||
|
backend_state=$(tailscale status --json 2>/dev/null | jq -r '.BackendState // "unknown"')
|
||||||
|
if [[ "$backend_state" != "Running" ]]; then
|
||||||
|
if [[ -n "$TS_AUTHKEY" ]]; then
|
||||||
|
log_info "Bringing Tailscale up with provided auth key..."
|
||||||
|
tailscale up --authkey "$TS_AUTHKEY" --ssh --hostname "$KOMODO_HOSTNAME" \
|
||||||
|
|| log_warn "tailscale up failed — run it manually inside the VM."
|
||||||
|
else
|
||||||
|
log_warn "Tailscale not authenticated and TS_AUTHKEY was not supplied."
|
||||||
|
log_warn "Finish setup with: tailscale up --ssh --hostname ${KOMODO_HOSTNAME}"
|
||||||
|
log_warn "Then publish Komodo with: tailscale serve --bg --https=443 http://${KOMODO_LISTEN_ADDR}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if tailscale serve status 2>/dev/null | grep -q "${KOMODO_LISTEN_ADDR}"; then
|
||||||
|
log_info "Tailscale serve already publishes http://${KOMODO_LISTEN_ADDR}."
|
||||||
|
else
|
||||||
|
log_info "Publishing Komodo on the tailnet via 'tailscale serve' (HTTPS:443)..."
|
||||||
|
tailscale serve --bg --https=443 "http://${KOMODO_LISTEN_ADDR}" \
|
||||||
|
|| log_warn "tailscale serve failed — enable HTTPS on your tailnet and retry."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Komodo — compose files in $KOMODO_DIR
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
# Pulls the existing value of $1 out of compose.env (if any). Used so re-runs
|
||||||
|
# preserve the password and secrets generated the first time around.
|
||||||
|
read_env_var() {
|
||||||
|
local key="$1"
|
||||||
|
local env_file="${KOMODO_DIR}/compose.env"
|
||||||
|
[[ -f "$env_file" ]] || return 0
|
||||||
|
awk -F= -v k="$key" '$1 == k { sub(/^[^=]*=/, ""); gsub(/^"|"$/, ""); print; exit }' "$env_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_compose_files() {
|
||||||
|
log_info "Provisioning ${KOMODO_DIR}..."
|
||||||
|
mkdir -p "$KOMODO_DIR"
|
||||||
|
chmod 750 "$KOMODO_DIR"
|
||||||
|
|
||||||
|
# Reuse existing secrets if compose.env was written by a previous run.
|
||||||
|
local db_password webhook_secret jwt_secret
|
||||||
|
db_password="$(read_env_var KOMODO_DATABASE_PASSWORD || true)"
|
||||||
|
webhook_secret="$(read_env_var KOMODO_WEBHOOK_SECRET || true)"
|
||||||
|
jwt_secret="$(read_env_var KOMODO_JWT_SECRET || true)"
|
||||||
|
|
||||||
|
if [[ -z "$db_password" ]]; then
|
||||||
|
db_password="$(gen_secret 24)"
|
||||||
|
log_info "Generated KOMODO_DATABASE_PASSWORD."
|
||||||
|
else
|
||||||
|
log_info "Preserving existing KOMODO_DATABASE_PASSWORD from compose.env."
|
||||||
|
fi
|
||||||
|
if [[ -z "$webhook_secret" ]]; then
|
||||||
|
webhook_secret="$(gen_secret 32)"
|
||||||
|
log_info "Generated KOMODO_WEBHOOK_SECRET."
|
||||||
|
else
|
||||||
|
log_info "Preserving existing KOMODO_WEBHOOK_SECRET from compose.env."
|
||||||
|
fi
|
||||||
|
if [[ -z "$jwt_secret" ]]; then
|
||||||
|
jwt_secret="$(gen_secret 32)"
|
||||||
|
log_info "Generated KOMODO_JWT_SECRET."
|
||||||
|
else
|
||||||
|
log_info "Preserving existing KOMODO_JWT_SECRET from compose.env."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stash the resolved values so the install summary can print them.
|
||||||
|
KOMODO_DB_PASSWORD_RESOLVED="$db_password"
|
||||||
|
KOMODO_WEBHOOK_SECRET_RESOLVED="$webhook_secret"
|
||||||
|
KOMODO_JWT_SECRET_RESOLVED="$jwt_secret"
|
||||||
|
|
||||||
|
# mongo.compose.yaml is upstream's reference (https://komo.do/docs/setup/mongo)
|
||||||
|
# with one change: Core's host port is bound to ${KOMODO_LISTEN_ADDR} instead
|
||||||
|
# of every interface, so only the loopback (and Tailscale serve) can reach it.
|
||||||
|
cat > "${KOMODO_DIR}/mongo.compose.yaml" <<EOF
|
||||||
|
services:
|
||||||
|
mongo:
|
||||||
|
image: mongo
|
||||||
|
labels:
|
||||||
|
komodo.skip:
|
||||||
|
command: --quiet --wiredTigerCacheSizeGB 0.25
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- mongo-data:/data/db
|
||||||
|
- mongo-config:/data/configdb
|
||||||
|
environment:
|
||||||
|
MONGO_INITDB_ROOT_USERNAME: \${KOMODO_DATABASE_USERNAME}
|
||||||
|
MONGO_INITDB_ROOT_PASSWORD: \${KOMODO_DATABASE_PASSWORD}
|
||||||
|
|
||||||
|
core:
|
||||||
|
image: ghcr.io/moghtech/komodo-core:\${COMPOSE_KOMODO_IMAGE_TAG:-2}
|
||||||
|
init: true
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- mongo
|
||||||
|
ports:
|
||||||
|
- ${KOMODO_LISTEN_ADDR}:9120
|
||||||
|
env_file: ./compose.env
|
||||||
|
environment:
|
||||||
|
KOMODO_DATABASE_ADDRESS: mongo:27017
|
||||||
|
volumes:
|
||||||
|
- keys:/config/keys
|
||||||
|
- \${COMPOSE_KOMODO_BACKUPS_PATH}:/backups
|
||||||
|
|
||||||
|
periphery:
|
||||||
|
image: ghcr.io/moghtech/komodo-periphery:\${COMPOSE_KOMODO_IMAGE_TAG:-2}
|
||||||
|
init: true
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- core
|
||||||
|
env_file: ./compose.env
|
||||||
|
volumes:
|
||||||
|
- keys:/config/keys
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- /proc:/proc
|
||||||
|
- \${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}:\${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mongo-data:
|
||||||
|
mongo-config:
|
||||||
|
keys:
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# compose.env is upstream's reference file with the three secrets injected
|
||||||
|
# and KOMODO_HOST set to the tailnet FQDN. Every other variable is left at
|
||||||
|
# the upstream default — adjust by hand later if needed.
|
||||||
|
cat > "${KOMODO_DIR}/compose.env" <<EOF
|
||||||
|
####################################
|
||||||
|
# Generated by infra-scripts/komodo #
|
||||||
|
####################################
|
||||||
|
|
||||||
|
COMPOSE_KOMODO_IMAGE_TAG="2"
|
||||||
|
COMPOSE_KOMODO_BACKUPS_PATH=/etc/komodo/backups
|
||||||
|
|
||||||
|
## DB credentials — password generated at install time, do not commit anywhere
|
||||||
|
KOMODO_DATABASE_USERNAME=admin
|
||||||
|
KOMODO_DATABASE_PASSWORD=${db_password}
|
||||||
|
|
||||||
|
TZ=Etc/UTC
|
||||||
|
|
||||||
|
#=-------------------------=#
|
||||||
|
#= Komodo Core Environment =#
|
||||||
|
#=-------------------------=#
|
||||||
|
|
||||||
|
## Used for Oauth / Webhook url suggestion — served via 'tailscale serve'.
|
||||||
|
KOMODO_HOST=${KOMODO_HOST}
|
||||||
|
KOMODO_TITLE=Komodo
|
||||||
|
|
||||||
|
KOMODO_PERIPHERY_PUBLIC_KEY=file:/config/keys/periphery.pub
|
||||||
|
|
||||||
|
KOMODO_LOCAL_AUTH=true
|
||||||
|
KOMODO_INIT_ADMIN_USERNAME=admin
|
||||||
|
KOMODO_INIT_ADMIN_PASSWORD=changeme
|
||||||
|
|
||||||
|
KOMODO_FIRST_SERVER_NAME=Local
|
||||||
|
|
||||||
|
KOMODO_DISABLE_CONFIRM_DIALOG=false
|
||||||
|
KOMODO_DISABLE_INIT_RESOURCES=false
|
||||||
|
|
||||||
|
## Secrets — generated at install time
|
||||||
|
KOMODO_WEBHOOK_SECRET=${webhook_secret}
|
||||||
|
KOMODO_JWT_SECRET=${jwt_secret}
|
||||||
|
KOMODO_JWT_TTL="1-day"
|
||||||
|
|
||||||
|
KOMODO_MONITORING_INTERVAL="15-sec"
|
||||||
|
KOMODO_RESOURCE_POLL_INTERVAL="1-hr"
|
||||||
|
|
||||||
|
KOMODO_DISABLE_USER_REGISTRATION=false
|
||||||
|
KOMODO_ENABLE_NEW_USERS=false
|
||||||
|
KOMODO_DISABLE_NON_ADMIN_CREATE=false
|
||||||
|
KOMODO_TRANSPARENT_MODE=false
|
||||||
|
|
||||||
|
KOMODO_OIDC_ENABLED=false
|
||||||
|
KOMODO_GITHUB_OAUTH_ENABLED=false
|
||||||
|
KOMODO_GOOGLE_OAUTH_ENABLED=false
|
||||||
|
|
||||||
|
KOMODO_AWS_ACCESS_KEY_ID=
|
||||||
|
KOMODO_AWS_SECRET_ACCESS_KEY=
|
||||||
|
|
||||||
|
KOMODO_LOGGING_PRETTY=false
|
||||||
|
KOMODO_PRETTY_STARTUP_CONFIG=false
|
||||||
|
|
||||||
|
#=------------------------------=#
|
||||||
|
#= Komodo Periphery Environment =#
|
||||||
|
#=------------------------------=#
|
||||||
|
|
||||||
|
PERIPHERY_CORE_ADDRESS=ws://core:9120
|
||||||
|
PERIPHERY_CONNECT_AS=\${KOMODO_FIRST_SERVER_NAME}
|
||||||
|
PERIPHERY_CORE_PUBLIC_KEYS=file:/config/keys/core.pub
|
||||||
|
|
||||||
|
PERIPHERY_ROOT_DIRECTORY=/etc/komodo
|
||||||
|
|
||||||
|
PERIPHERY_DISABLE_TERMINALS=false
|
||||||
|
PERIPHERY_DISABLE_CONTAINER_TERMINALS=false
|
||||||
|
|
||||||
|
PERIPHERY_INCLUDE_DISK_MOUNTS=/etc/hostname
|
||||||
|
|
||||||
|
PERIPHERY_LOGGING_PRETTY=false
|
||||||
|
PERIPHERY_PRETTY_STARTUP_CONFIG=false
|
||||||
|
EOF
|
||||||
|
# Compose env contains the DB password + JWT/webhook secrets — keep readable
|
||||||
|
# only by root.
|
||||||
|
chmod 600 "${KOMODO_DIR}/compose.env"
|
||||||
|
}
|
||||||
|
|
||||||
|
bring_up_stack() {
|
||||||
|
log_info "Pulling Komodo + MongoDB images..."
|
||||||
|
(cd "$KOMODO_DIR" && docker compose --env-file ./compose.env -f mongo.compose.yaml pull) >/dev/null
|
||||||
|
|
||||||
|
log_info "Starting the Komodo stack..."
|
||||||
|
(cd "$KOMODO_DIR" && docker compose --env-file ./compose.env -f mongo.compose.yaml up -d) >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Log hygiene + console auto-login (repo conventions)
|
||||||
|
# ============================================================
|
||||||
|
configure_log_hygiene() {
|
||||||
|
# Bound the journal-equivalent — Alpine uses OpenRC, so we cap Docker's
|
||||||
|
# per-container logs via daemon.json (json-file driver, default).
|
||||||
|
log_info "Capping per-container Docker log size..."
|
||||||
|
mkdir -p /etc/docker
|
||||||
|
cat > /etc/docker/daemon.json <<'EOF'
|
||||||
|
{
|
||||||
|
"log-driver": "json-file",
|
||||||
|
"log-opts": {
|
||||||
|
"max-size": "10m",
|
||||||
|
"max-file": "3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
# Reload so the cap applies to subsequent container starts. Existing
|
||||||
|
# containers keep their previous log driver until recreated.
|
||||||
|
rc-service docker reload >/dev/null 2>&1 \
|
||||||
|
|| rc-service docker restart >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_console_autologin() {
|
||||||
|
log_info "Enabling console auto-login on tty1..."
|
||||||
|
# Alpine's busybox getty does not support --autologin; use util-linux's agetty.
|
||||||
|
apk add --no-cache agetty >/dev/null 2>&1 || apk add --no-cache util-linux >/dev/null
|
||||||
|
sed -i '/^tty1::/d' /etc/inittab
|
||||||
|
echo 'tty1::respawn:/sbin/agetty --autologin root --noclear 38400 tty1' >> /etc/inittab
|
||||||
|
kill -HUP 1 2>/dev/null || true
|
||||||
|
pkill -KILL -f '(getty|agetty).*tty1' 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
write_motd() {
|
||||||
|
log_info "Configuring MOTD..."
|
||||||
|
# Quoted heredoc: every variable in the body is resolved at login time, not
|
||||||
|
# install time.
|
||||||
|
cat > /etc/profile.d/00-komodo.sh <<'MOTD'
|
||||||
|
KOMODO_DIR="${KOMODO_DIR:-/opt/komodo}"
|
||||||
|
KOMODO_HOST=$(awk -F= '$1 == "KOMODO_HOST" { print $2; exit }' "${KOMODO_DIR}/compose.env" 2>/dev/null)
|
||||||
|
[ -z "$KOMODO_HOST" ] && KOMODO_HOST="https://<not-set>"
|
||||||
|
|
||||||
|
TS_FQDN=$(tailscale status --json 2>/dev/null | awk -F'"' '
|
||||||
|
/"Self"/ { in_self=1 }
|
||||||
|
in_self && /"DNSName"/ { gsub(/\.$/, "", $4); print $4; exit }
|
||||||
|
')
|
||||||
|
[ -z "$TS_FQDN" ] && TS_FQDN="$(hostname).ts.net"
|
||||||
|
|
||||||
|
CORE_STATE=$(docker inspect -f '{{.State.Status}}' komodo-core-1 2>/dev/null || echo "missing")
|
||||||
|
PERI_STATE=$(docker inspect -f '{{.State.Status}}' komodo-periphery-1 2>/dev/null || echo "missing")
|
||||||
|
MONGO_STATE=$(docker inspect -f '{{.State.Status}}' komodo-mongo-1 2>/dev/null || echo "missing")
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo " _ __ _ "
|
||||||
|
echo "| |/ /___ _ __ ___ __| | ___ "
|
||||||
|
echo "| ' // _ \\| '_ \` _ \\ / _\` |/ _ \\ "
|
||||||
|
echo "| . \\ (_) | | | | | | (_| | (_) |"
|
||||||
|
echo "|_|\\_\\___/|_| |_| |_|\\__,_|\\___/ "
|
||||||
|
echo ""
|
||||||
|
echo "Komodo (Docker + MongoDB)"
|
||||||
|
echo "─────────────────────────────────────────"
|
||||||
|
echo "Access:"
|
||||||
|
echo " • Local : http://127.0.0.1:9120"
|
||||||
|
echo " • Tailnet : ${KOMODO_HOST}"
|
||||||
|
echo ""
|
||||||
|
echo "Containers:"
|
||||||
|
echo " • core : ${CORE_STATE}"
|
||||||
|
echo " • periphery : ${PERI_STATE}"
|
||||||
|
echo " • mongo : ${MONGO_STATE}"
|
||||||
|
echo ""
|
||||||
|
echo "Useful commands:"
|
||||||
|
echo " cd ${KOMODO_DIR}"
|
||||||
|
echo " docker compose --env-file compose.env -f mongo.compose.yaml ps"
|
||||||
|
echo " docker compose --env-file compose.env -f mongo.compose.yaml logs -f core"
|
||||||
|
echo " docker compose --env-file compose.env -f mongo.compose.yaml pull && \\"
|
||||||
|
echo " docker compose --env-file compose.env -f mongo.compose.yaml up -d"
|
||||||
|
echo "─────────────────────────────────────────"
|
||||||
|
echo ""
|
||||||
|
MOTD
|
||||||
|
chmod +x /etc/profile.d/00-komodo.sh
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Main
|
||||||
|
# ============================================================
|
||||||
|
main() {
|
||||||
|
require_root
|
||||||
|
require_alpine
|
||||||
|
|
||||||
|
log_info "=== Komodo — installation ==="
|
||||||
|
|
||||||
|
# Base tooling for the rest of the script (jq is used to read tailscale status,
|
||||||
|
# curl/ca-certificates for image pulls / future tailscale install).
|
||||||
|
apk add --no-cache bash curl jq ca-certificates openssl >/dev/null
|
||||||
|
|
||||||
|
install_docker
|
||||||
|
install_tailscale
|
||||||
|
write_compose_files
|
||||||
|
bring_up_stack
|
||||||
|
configure_log_hygiene
|
||||||
|
configure_console_autologin
|
||||||
|
configure_tailscale_proxy
|
||||||
|
write_motd
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
log_info "========================================="
|
||||||
|
log_info "Komodo installation complete!"
|
||||||
|
log_info "========================================="
|
||||||
|
echo ""
|
||||||
|
echo " Local URL : http://${KOMODO_LISTEN_ADDR}"
|
||||||
|
echo " Tailnet URL : ${KOMODO_HOST}"
|
||||||
|
echo " Compose dir : ${KOMODO_DIR}"
|
||||||
|
echo ""
|
||||||
|
echo "Initial admin (change immediately via the UI):"
|
||||||
|
echo " username : admin"
|
||||||
|
echo " password : changeme"
|
||||||
|
echo ""
|
||||||
|
echo "Generated secrets (also in ${KOMODO_DIR}/compose.env, root-only):"
|
||||||
|
echo " KOMODO_DATABASE_PASSWORD : ${KOMODO_DB_PASSWORD_RESOLVED}"
|
||||||
|
echo " KOMODO_WEBHOOK_SECRET : ${KOMODO_WEBHOOK_SECRET_RESOLVED}"
|
||||||
|
echo " KOMODO_JWT_SECRET : ${KOMODO_JWT_SECRET_RESOLVED}"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Reference in New Issue
Block a user