#!/bin/sh # Install Movim as a systemd service (not enabled or started). # # From a release tarball (see build-release.sh), installs the prebuilt binary: # tar xzf movim-*.tar.gz && cd movim-* && ./install.sh # From the repository, compiles first (as a normal user, not root): # ./install.sh # Privileged steps go through sudo when not run as root. # # Overridable: MOVIM_USER (default movim, created if missing), PREFIX # (default /opt/movim), DATA_DIR (default /var/lib/movim), CONF_DIR # (default /etc/movim), XMPP_DOMAIN, WEB_HOST and MOVIM_PORT (asked when run # in a terminal and the configuration does not exist yet). set -eu MOVIM_USER=${MOVIM_USER:-movim} PREFIX=${PREFIX:-/opt/movim} DATA_DIR=${DATA_DIR:-/var/lib/movim} CONF_DIR=${CONF_DIR:-/etc/movim} XMPP_DOMAIN=${XMPP_DOMAIN:-} WEB_HOST=${WEB_HOST:-} MOVIM_PORT=${MOVIM_PORT:-} UNIT=/etc/systemd/system/movim.service MIN_RUSTC=1.94 die() { echo "error: $*" >&2; exit 1; } # Validators print why a value is refused. valid_host() { printf '%s' "$1" | grep -Eq '^([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?\.)+[A-Za-z]{2,}$' && return 0 echo " not a valid domain name: $1" return 1 } port_in_use() { command -v ss >/dev/null && [ -n "$(ss -Htln "sport = :$1")" ] } # The service has no capabilities, so no privileged ports. valid_port() { case $1 in ''|*[!0-9]*) echo " not a port number: $1"; return 1 ;; esac if [ "$1" -lt 1024 ] || [ "$1" -gt 65535 ]; then echo " port must be between 1024 and 65535" return 1 fi if port_in_use "$1"; then echo " port $1 is already used by another service (ss -tlnp)" return 1 fi } free_port() { p=$1 while port_in_use "$p"; do p=$((p + 1)); done echo "$p" } # ask VAR "question" default [validator]: prompt on the terminal until valid. ask() { check=${4:-valid_host} eval "current=\${$1}" [ -z "$current" ] || { "$check" "$current" >&2 || die "invalid $1"; return 0; } if [ ! -t 0 ]; then echo " no terminal, $1 defaults to $3 (set $1=... to choose)" eval "$1=\$3" return 0 fi while :; do printf '%s [%s]: ' "$2" "$3" read -r answer || die "aborted" answer=${answer:-$3} if "$check" "$answer"; then eval "$1=\$answer" return 0 fi done } cd "$(dirname "$0")" [ -d frontend ] && [ -d templates ] || die "run this from the movim-rs repository or an extracted release" command -v systemctl >/dev/null || die "systemd is required" if [ "$(id -u)" -eq 0 ]; then sudo() { "$@"; } else command -v sudo >/dev/null || die "sudo is required (or run as root)" fi if [ -f Cargo.toml ]; then [ "$(id -u)" -ne 0 ] || die "do not compile as root; run as a normal user, privileged steps use sudo" BIN=target/release/movim elif [ -x movim ]; then BIN=./movim echo "==> Prebuilt binary: $(file -b movim 2>/dev/null || echo movim)" else die "no Cargo.toml and no prebuilt ./movim" fi # --- Questions (before the build, so nobody has to wait for it) ----------- if [ ! -e "$CONF_DIR/movim.env" ]; then ask XMPP_DOMAIN "XMPP domain users log in with (MOVIM_DOMAIN)" example.org ask MOVIM_PORT "Local port for Movim (behind nginx)" "$(free_port 8080)" valid_port fi if [ -d /etc/nginx/sites-available ] && [ ! -e /etc/nginx/sites-available/movim ]; then default_web=movim.${XMPP_DOMAIN:-example.org} ask WEB_HOST "Web hostname serving Movim (nginx server_name)" "$default_web" fi # --- Build ----------------------------------------------------------------- build() { rustup_help="Distribution packages (e.g. Debian's rustc) are too old. Install a current toolchain for your user with rustup, it does not conflict with apt's rustc: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh then run this script again." # Prefer a rustup toolchain over the distribution's cargo, even if # ~/.cargo/bin is not in PATH (non-login shell, Debian's cargo first). [ ! -x "$HOME/.cargo/bin/cargo" ] || PATH="$HOME/.cargo/bin:$PATH" command -v cargo >/dev/null || die "cargo not found, need Rust >= $MIN_RUSTC. $rustup_help" rustc_version=$(rustc --version | cut -d' ' -f2) lowest=$(printf '%s\n%s\n' "$MIN_RUSTC" "$rustc_version" | sort -V | head -n1) if [ "$lowest" != "$MIN_RUSTC" ]; then if command -v rustup >/dev/null; then die "rustc $rustc_version is too old, need >= $MIN_RUSTC: rustup update stable && rustup default stable" fi die "rustc $rustc_version ($(command -v rustc)) is too old, need >= $MIN_RUSTC. $rustup_help" fi # Linker and C compiler (bundled SQLite, crypto libraries). command -v cc >/dev/null || die "no C compiler; on Debian: sudo apt install build-essential cmake" echo "==> Building (rustc $rustc_version)" cargo build --release --locked } [ ! -f Cargo.toml ] || build # --- Install --------------------------------------------------------------- echo "==> Installing to $PREFIX (sudo)" if ! id "$MOVIM_USER" >/dev/null 2>&1; then nologin=$(command -v nologin || echo /usr/sbin/nologin) echo " creating system user $MOVIM_USER" sudo useradd --system --user-group --home-dir "$DATA_DIR" --no-create-home \ --shell "$nologin" "$MOVIM_USER" fi group=$(id -gn "$MOVIM_USER") # Code and assets are owned by root and read-only for the service. sudo install -d -m 0755 -o root -g root "$PREFIX" sudo install -m 0755 -o root -g root "$BIN" "$PREFIX/movim.new" for d in frontend templates; do sudo rm -rf "$PREFIX/$d.new" sudo cp -R "$d" "$PREFIX/$d.new" sudo chown -R root:root "$PREFIX/$d.new" sudo chmod -R u=rwX,go=rX "$PREFIX/$d.new" done # Swap in the new files only once everything is copied. sudo mv -f "$PREFIX/movim.new" "$PREFIX/movim" for d in frontend templates; do sudo rm -rf "$PREFIX/$d.old" [ ! -e "$PREFIX/$d" ] || sudo mv "$PREFIX/$d" "$PREFIX/$d.old" sudo mv "$PREFIX/$d.new" "$PREFIX/$d" sudo rm -rf "$PREFIX/$d.old" done # Database, VAPID key: only the service user can read them. sudo install -d -m 0750 -o "$MOVIM_USER" -g "$group" "$DATA_DIR" # Configuration: never overwrite an existing one. sudo install -d -m 0755 -o root -g root "$CONF_DIR" if [ ! -e "$CONF_DIR/movim.env" ]; then echo " writing $CONF_DIR/movim.env" sudo tee "$CONF_DIR/movim.env" >/dev/null </dev/null </dev/null <