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)"` |
|
||||
| [`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)"` |
|
||||
| [`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
|
||||
|
||||
- Fresh Debian 12/13 installation (proxy, netlab) or Proxmox VE host (gitea-runner, openbao, ferretdb)
|
||||
- User with sudo privileges (do not run as root) — except gitea-runner, openbao and ferretdb which run as root on Proxmox
|
||||
- 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, ferretdb, and komodo which run as root
|
||||
- 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