#!/usr/bin/env bash # Install LLeMbas as a system service behind nginx with a self-signed cert. # # Creates a dedicated service user, a virtualenv, a systemd unit and an nginx # vhost. Idempotent: safe to re-run. To deploy new code afterwards use # update.sh, which is what a `git push` should be followed by. # # Everything is configurable from the environment: # # SITE_HOST=chat.example ./deploy/install.sh # vhost name # APP_PORT=8080 # loopback port # PREFIX=/srv/lembas # install root # HOME_DIR=/home/lembas # service user's home # REPO_URL=... # defaults to this checkout's origin # # PREFIX defaults to a bind mount of HOME_DIR rather than living directly under # /srv, because on many machines the root filesystem is small and the venv plus # database belong on the larger /home volume. Set PREFIX=HOME_DIR to skip that. set -euo pipefail HERE="$(dirname "$(readlink -f "$0")")" SITE_HOST="${SITE_HOST:-lembas.local}" APP_PORT="${APP_PORT:-8080}" SERVICE_USER="${SERVICE_USER:-lembas}" HOME_DIR="${HOME_DIR:-/home/lembas}" PREFIX="${PREFIX:-/srv/lembas}" BRANCH="${LEMBAS_BRANCH:-main}" # Default to wherever this checkout came from, so a fork deploys itself. REPO_URL="${REPO_URL:-$(git -C "$HERE" remote get-url origin 2>/dev/null || true)}" APP="$PREFIX/app" VENV="$PREFIX/venv" ENV_FILE="$PREFIX/lembas.env" if [[ -z "$REPO_URL" ]]; then echo "Could not determine REPO_URL. Set it explicitly." >&2 exit 1 fi echo "== plan ==" echo " host : https://$SITE_HOST -> 127.0.0.1:$APP_PORT" echo " user : $SERVICE_USER ($HOME_DIR)" echo " prefix : $PREFIX" echo " repo : $REPO_URL ($BRANCH)" echo "== service user ==" # --system: no ageing, no mail spool. Home under /home, not /var/lib, so the # venv and database sit on the larger volume. if ! getent passwd "$SERVICE_USER" >/dev/null; then sudo useradd --system --create-home --home-dir "$HOME_DIR" \ --shell /usr/bin/nologin --comment "LLeMbas" "$SERVICE_USER" else echo " user $SERVICE_USER already exists" fi sudo chmod 755 "$HOME_DIR" if [[ "$PREFIX" != "$HOME_DIR" ]]; then echo "== $PREFIX bind-mount onto $HOME_DIR ==" sudo mkdir -p "$PREFIX" grep -q "^$HOME_DIR[[:space:]]" /etc/fstab \ || echo "$HOME_DIR $PREFIX none bind 0 0" | sudo tee -a /etc/fstab >/dev/null sudo systemctl daemon-reload mountpoint -q "$PREFIX" || sudo mount "$PREFIX" fi echo "== checkout ==" if [[ ! -d "$APP/.git" ]]; then sudo -u "$SERVICE_USER" git clone --branch "$BRANCH" "$REPO_URL" "$APP" else echo " already cloned; use update.sh to pull" fi echo "== virtualenv ==" if [[ ! -x "$VENV/bin/python" ]]; then sudo -u "$SERVICE_USER" python -m venv "$VENV" fi sudo -u "$SERVICE_USER" "$VENV/bin/pip" install --quiet --upgrade pip # With the `search` extra: DuckDuckGo is the default web search provider and is # meant to work with no setup at all. sudo -u "$SERVICE_USER" "$VENV/bin/pip" install --quiet -e "$APP[search]" echo "== environment ==" # Generated once and never regenerated: rotating LEMBAS_SECRET_KEY signs every # user out AND makes the stored upstream API keys unreadable. if [[ ! -f "$ENV_FILE" ]]; then KEY=$("$VENV/bin/python" -c "import secrets; print(secrets.token_urlsafe(48))") sudo tee "$ENV_FILE" >/dev/null </dev/null sudo systemctl daemon-reload echo "== self-signed cert for $SITE_HOST ==" sudo mkdir -p /etc/nginx/ssl if [[ ! -f "/etc/nginx/ssl/$SITE_HOST.crt" ]]; then sudo openssl req -x509 -newkey rsa:2048 -nodes \ -keyout "/etc/nginx/ssl/$SITE_HOST.key" -out "/etc/nginx/ssl/$SITE_HOST.crt" \ -days 3650 -subj "/CN=$SITE_HOST" -addext "subjectAltName=DNS:$SITE_HOST" sudo chmod 600 "/etc/nginx/ssl/$SITE_HOST.key" sudo chmod 644 "/etc/nginx/ssl/$SITE_HOST.crt" fi echo "== nginx vhost ==" sed -e "s|__SITE_HOST__|$SITE_HOST|g" -e "s|__APP_PORT__|$APP_PORT|g" \ "$HERE/nginx-vhost.conf" | sudo tee "/etc/nginx/conf.d/$SITE_HOST.conf" >/dev/null sudo nginx -t sudo systemctl reload nginx echo "== local name resolution ==" # Only useful when the LAN's DNS does not already answer for this name. if ! getent hosts "$SITE_HOST" >/dev/null; then printf '127.0.0.1\t%s\n::1\t\t%s\n' "$SITE_HOST" "$SITE_HOST" | sudo tee -a /etc/hosts >/dev/null echo " added $SITE_HOST to /etc/hosts" else echo " $SITE_HOST already resolves" fi echo "== enable service ==" sudo systemctl enable --now lembas sleep 2 sudo systemctl --no-pager --lines=0 status lembas || true echo echo "LLeMbas is up at https://$SITE_HOST (self-signed cert; accept the warning)" echo "Create the first account -- it becomes the administrator."