This commit is contained in:
2026-06-13 17:23:18 +03:30
parent 53b8a91843
commit 2c8a134d41
5 changed files with 1549 additions and 2 deletions

332
setup.sh Executable file
View File

@@ -0,0 +1,332 @@
#!/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 </dev/tty || fail "could not read input from /dev/tty"
if [ -z "$answer" ]; then
answer=$default_value
fi
printf '%s' "$answer"
}
validate_absolute_path() {
value=$1
label=$2
case "$value" in
/*) ;;
*) fail "$label must be an absolute path" ;;
esac
case "$value" in
*'
'*) fail "$label must not contain newlines" ;;
esac
}
nginx_quote() {
value=$1
case "$value" in
*'"'*|*'\'*|*'$'*) fail "paths containing quotes, backslashes, or dollar signs are not supported" ;;
esac
printf '"%s"' "$value"
}
run_root() {
if [ "$(id -u)" -eq 0 ]; then
"$@"
else
sudo "$@"
fi
}
cleanup() {
status=$?
trap - EXIT HUP INT TERM
if [ "$status" -ne 0 ] && [ "${TRANSACTION_ACTIVE:-0}" -eq 1 ]; then
restore_previous
fi
if [ -n "${WORK_DIR:-}" ]; then
rm -rf "$WORK_DIR"
fi
exit "$status"
}
restore_previous() {
say "Setup failed; restoring the previous nginx configuration and assets."
set +e
if [ "$SITE_EXISTED" -eq 1 ]; then
run_root cp "$SITE_BACKUP" "$SITE_CONFIG"
else
run_root rm -f "$SITE_CONFIG"
fi
if [ "$MAIN_CHANGED" -eq 1 ]; then
run_root cp "$MAIN_BACKUP" "$NGINX_CONF"
fi
for asset in autoindex.css autoindex.js favicon.ico; do
if [ -f "$WORK_DIR/assets-backup/$asset" ]; then
run_root cp "$WORK_DIR/assets-backup/$asset" "$ASSETS_PATH/$asset"
else
run_root rm -f "$ASSETS_PATH/$asset"
fi
done
if ! run_root "$NGINX_BIN" -t; then
printf '%s\n' "nais setup: rollback completed, but the restored nginx configuration is invalid" >&2
fi
}
trap cleanup EXIT
trap 'exit 1' HUP INT TERM
if ! ( : </dev/tty && : >/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" <<EOF
# Managed by NAIS setup.sh. Re-running setup replaces this file.
server {
listen 443 ssl;
server_name $SERVER_NAME;
ssl_certificate $CERT_QUOTED;
ssl_certificate_key $KEY_QUOTED;
root $ROOT_QUOTED;
location / {
autoindex on;
autoindex_exact_size off;
autoindex_localtime on;
sub_filter '</head>' '<link rel="stylesheet" href="/autoindex.css">';
sub_filter '</body>' '<script src="/autoindex.js"></script></body>';
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" <<EOF
# Managed by NAIS setup.sh. Re-running setup replaces this file.
server {
listen 80;
server_name $SERVER_NAME;
root $ROOT_QUOTED;
location / {
autoindex on;
autoindex_exact_size off;
autoindex_localtime on;
sub_filter '</head>' '<link rel="stylesheet" href="/autoindex.css">';
sub_filter '</body>' '<script src="/autoindex.js"></script></body>';
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"