#!/bin/sh set -eu BASE_URL=${NAIS_BASE_URL:-https://meghdadfadaee.github.io/nais} DEFAULT_ASSETS_PATH=/var/www/nais-assets MANAGED_INCLUDE='include nais-sites/*.conf;' say() { printf '%s\n' "$*" } fail() { printf 'nais setup: %s\n' "$*" >&2 exit 1 } need_command() { command -v "$1" >/dev/null 2>&1 || fail "required command not found: $1" } prompt() { prompt_text=$1 default_value=${2-} if [ -n "$default_value" ]; then printf '%s [%s]: ' "$prompt_text" "$default_value" >/dev/tty else printf '%s: ' "$prompt_text" >/dev/tty fi IFS= read -r answer &2 fi } trap cleanup EXIT trap 'exit 1' HUP INT TERM if ! ( : /dev/tty ) 2>/dev/null; then fail "/dev/tty is required for interactive setup" fi need_command curl need_command nginx need_command awk need_command sed need_command mktemp if [ "$(id -u)" -ne 0 ]; then need_command sudo sudo -v || fail "root privileges are required to configure nginx" fi SERVER_NAME=$(prompt "Server name") [ -n "$SERVER_NAME" ] || fail "server_name is required" case "$SERVER_NAME" in *[!A-Za-z0-9._:-]*) fail "server_name must be one hostname or IP address without whitespace" ;; esac ROOT_PATH=$(prompt "Root path") [ -n "$ROOT_PATH" ] || fail "root_path is required" validate_absolute_path "$ROOT_PATH" "root_path" ASSETS_PATH=$(prompt "Assets path" "$DEFAULT_ASSETS_PATH") validate_absolute_path "$ASSETS_PATH" "assets_path" CERTIFICATES_PATH=$(prompt "Certificates path (optional)") if [ -n "$CERTIFICATES_PATH" ]; then validate_absolute_path "$CERTIFICATES_PATH" "certificates_path" run_root test -f "$CERTIFICATES_PATH/fullchain.pem" || fail "certificate not found: $CERTIFICATES_PATH/fullchain.pem" run_root test -f "$CERTIFICATES_PATH/privkey.pem" || fail "certificate key not found: $CERTIFICATES_PATH/privkey.pem" fi NGINX_BIN=$(command -v nginx) NGINX_VERSION=$("$NGINX_BIN" -V 2>&1) NGINX_PREFIX=$(printf '%s\n' "$NGINX_VERSION" | sed -n 's/.*--prefix=\([^ ]*\).*/\1/p') NGINX_CONF=$(printf '%s\n' "$NGINX_VERSION" | sed -n 's/.*--conf-path=\([^ ]*\).*/\1/p') [ -n "$NGINX_PREFIX" ] || NGINX_PREFIX=/etc/nginx [ -n "$NGINX_CONF" ] || NGINX_CONF=$NGINX_PREFIX/conf/nginx.conf case "$NGINX_CONF" in /*) ;; *) NGINX_CONF=$NGINX_PREFIX/$NGINX_CONF ;; esac run_root test -f "$NGINX_CONF" || fail "nginx configuration not found: $NGINX_CONF" WORK_DIR=$(mktemp -d) SITE_BACKUP=$WORK_DIR/site.conf.backup MAIN_BACKUP=$WORK_DIR/nginx.conf.backup GENERATED_CONFIG=$WORK_DIR/nais.conf MAIN_CURRENT=$WORK_DIR/nginx.conf.current MAIN_CHANGED=0 MAIN_NEEDS_CHANGE=0 SITE_EXISTED=0 TRANSACTION_ACTIVE=0 run_root cp "$NGINX_CONF" "$MAIN_CURRENT" run_root chmod a+r "$MAIN_CURRENT" say "Downloading NAIS assets..." for asset in autoindex.css autoindex.js favicon.ico; do curl -fsSL "$BASE_URL/$asset" -o "$WORK_DIR/$asset" || fail "failed to download $BASE_URL/$asset" done ROOT_QUOTED=$(nginx_quote "$ROOT_PATH") ASSETS_QUOTED=$(nginx_quote "$ASSETS_PATH") if [ -n "$CERTIFICATES_PATH" ]; then CERT_QUOTED=$(nginx_quote "$CERTIFICATES_PATH/fullchain.pem") KEY_QUOTED=$(nginx_quote "$CERTIFICATES_PATH/privkey.pem") cat >"$GENERATED_CONFIG" <' ''; sub_filter '' ''; sub_filter_once on; } location = /autoindex.css { root $ASSETS_QUOTED; } location = /autoindex.js { root $ASSETS_QUOTED; } location = /favicon.ico { root $ASSETS_QUOTED; } } server { listen 80; server_name $SERVER_NAME; return 301 https://$SERVER_NAME\$request_uri; } EOF else cat >"$GENERATED_CONFIG" <' ''; sub_filter '' ''; sub_filter_once on; } location = /autoindex.css { root $ASSETS_QUOTED; } location = /autoindex.js { root $ASSETS_QUOTED; } location = /favicon.ico { root $ASSETS_QUOTED; } } EOF fi ACTIVE_INCLUDE=$( sed 's/[[:space:]]*#.*$//' "$MAIN_CURRENT" | awk ' $1 == "include" { path = $2 sub(/;$/, "", path) gsub(/^"|"$/, "", path) if (path ~ /sites-enabled\/\*$/) { print path; exit } if (path ~ /conf\.d\/\*\.conf$/) { print path; exit } if (path ~ /http\.d\/\*\.conf$/) { print path; exit } if (path ~ /nais-sites\/\*\.conf$/) { print path; exit } } ' ) if [ -n "$ACTIVE_INCLUDE" ]; then case "$ACTIVE_INCLUDE" in /*) INCLUDE_PATTERN=$ACTIVE_INCLUDE ;; *) INCLUDE_PATTERN=$NGINX_PREFIX/$ACTIVE_INCLUDE ;; esac SITE_DIR=${INCLUDE_PATTERN%/*} else SITE_DIR=$NGINX_PREFIX/nais-sites cp "$MAIN_CURRENT" "$MAIN_BACKUP" awk -v include_line=" $MANAGED_INCLUDE" ' BEGIN { added = 0 } !added && $0 ~ /^[[:space:]]*http[[:space:]]*\{/ { print print include_line added = 1 next } { print } END { if (!added) exit 42 } ' "$MAIN_CURRENT" >"$WORK_DIR/nginx.conf" || fail "could not find the nginx http block in $NGINX_CONF" MAIN_NEEDS_CHANGE=1 fi SITE_CONFIG=$SITE_DIR/nais.conf run_root mkdir -p "$ROOT_PATH" "$ASSETS_PATH" "$SITE_DIR" mkdir -p "$WORK_DIR/assets-backup" for asset in autoindex.css autoindex.js favicon.ico; do if run_root test -e "$ASSETS_PATH/$asset" && ! run_root test -f "$ASSETS_PATH/$asset"; then fail "asset destination is not a regular file: $ASSETS_PATH/$asset" fi if run_root test -f "$ASSETS_PATH/$asset"; then run_root cp "$ASSETS_PATH/$asset" "$WORK_DIR/assets-backup/$asset" fi done if run_root test -e "$SITE_CONFIG"; then run_root test -f "$SITE_CONFIG" || fail "site config destination is not a regular file: $SITE_CONFIG" run_root cp "$SITE_CONFIG" "$SITE_BACKUP" SITE_EXISTED=1 fi TRANSACTION_ACTIVE=1 run_root chmod a+rx "$ASSETS_PATH" for asset in autoindex.css autoindex.js favicon.ico; do run_root cp "$WORK_DIR/$asset" "$ASSETS_PATH/$asset" run_root chmod a+r "$ASSETS_PATH/$asset" done run_root cp "$GENERATED_CONFIG" "$SITE_CONFIG" if [ "$MAIN_NEEDS_CHANGE" -eq 1 ]; then MAIN_CHANGED=1 run_root cp "$WORK_DIR/nginx.conf" "$NGINX_CONF" fi say "Validating nginx configuration..." if ! run_root "$NGINX_BIN" -t; then fail "nginx validation failed" fi if command -v systemctl >/dev/null 2>&1; then if run_root systemctl is-active --quiet nginx; then run_root systemctl reload nginx else run_root systemctl enable --now nginx fi elif command -v service >/dev/null 2>&1; then run_root service nginx restart else fail "nginx configuration is installed, but no supported service manager was found" fi TRANSACTION_ACTIVE=0 say "NAIS configured successfully." say "Site config: $SITE_CONFIG" say "Assets: $ASSETS_PATH"