#!/bin/sh set -eu KARLDNS_BASE_URL="${KARLDNS_BASE_URL:-https://karldns.de}" KARLDNS_INTERVAL_MINUTES="${KARLDNS_INTERVAL_MINUTES:-5}" KARLDNS_INSTALL_MODE="${KARLDNS_INSTALL_MODE:-auto}" KARLDNS_UPDATE_URL="${KARLDNS_UPDATE_URL:-}" KARLDNS_HOSTNAME="${KARLDNS_HOSTNAME:-}" KARLDNS_MANAGEMENT_URL="${KARLDNS_MANAGEMENT_URL:-}" KARLDNS_RAW_BASE_URL="${KARLDNS_RAW_BASE_URL:-https://raw.githubusercontent.com/StasonJatham/karldns.de/main/scripts}" KARLDNS_IPV4="${KARLDNS_IPV4:-auto}" KARLDNS_IPV6="${KARLDNS_IPV6:-auto}" KARLDNS_SKIP_CRON="${KARLDNS_SKIP_CRON:-0}" KARLDNS_SKIP_FIRST_UPDATE="${KARLDNS_SKIP_FIRST_UPDATE:-0}" usage() { cat <<'EOF' KarlDNS Linux installer Usage: install-linux.sh [options] Options: --base-url URL KarlDNS server URL. Default: https://karldns.de --raw-base-url URL Updater script download URL --every MINUTES Update interval, 1-60. Default: 5 --mode MODE auto, system, or user. Default: auto --update-url URL Existing private update URL --hostname NAME Hostname to show after install --dashboard-url URL Private dashboard URL to show after install --ipv4 VALUE auto, off, or a static IPv4. Default: auto --ipv6 VALUE auto, off, or a static IPv6. Default: auto --no-cron Install files only, do not configure cron --no-run Do not run the first update after install -h, --help Show this help Examples: curl -fsSL https://raw.githubusercontent.com/StasonJatham/karldns.de/main/scripts/install-linux.sh | sudo sh curl -fsSL https://raw.githubusercontent.com/StasonJatham/karldns.de/main/scripts/install-linux.sh | sh -s -- --mode user EOF } while [ "$#" -gt 0 ]; do case "$1" in --base-url) KARLDNS_BASE_URL="${2:?missing value for --base-url}"; shift 2 ;; --raw-base-url) KARLDNS_RAW_BASE_URL="${2:?missing value for --raw-base-url}"; shift 2 ;; --every) KARLDNS_INTERVAL_MINUTES="${2:?missing value for --every}"; shift 2 ;; --mode) KARLDNS_INSTALL_MODE="${2:?missing value for --mode}"; shift 2 ;; --update-url) KARLDNS_UPDATE_URL="${2:?missing value for --update-url}"; shift 2 ;; --hostname) KARLDNS_HOSTNAME="${2:?missing value for --hostname}"; shift 2 ;; --dashboard-url|--management-url) KARLDNS_MANAGEMENT_URL="${2:?missing value for --dashboard-url}"; shift 2 ;; --ipv4) KARLDNS_IPV4="${2:?missing value for --ipv4}"; shift 2 ;; --ipv6) KARLDNS_IPV6="${2:?missing value for --ipv6}"; shift 2 ;; --no-cron) KARLDNS_SKIP_CRON=1; shift ;; --no-run) KARLDNS_SKIP_FIRST_UPDATE=1; shift ;; -h|--help) usage; exit 0 ;; *) echo "KarlDNS installer: unknown option: $1" >&2; usage >&2; exit 1 ;; esac done need() { if ! command -v "$1" >/dev/null 2>&1; then echo "KarlDNS installer: missing required command: $1" >&2 exit 1 fi } json_field() { key="$1" if command -v python3 >/dev/null 2>&1; then python3 -c 'import json,sys; print(json.load(sys.stdin).get(sys.argv[1], ""))' "$key" else sed -n "s/.*\"$key\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p" | head -n 1 fi } quote_sh() { printf "%s" "$1" | sed "s/'/'\\\\''/g; 1s/^/'/; \$s/\$/'/" } download_updater() { target_path="$1" tmp_path="$(mktemp)" if ! "$curl_bin" -fsS "$KARLDNS_RAW_BASE_URL/karldns-update.sh" -o "$tmp_path"; then rm -f "$tmp_path" exit 1 fi chmod 755 "$tmp_path" mv "$tmp_path" "$target_path" } cron_schedule() { if [ "$KARLDNS_INTERVAL_MINUTES" = "60" ]; then printf '0 * * * *' else printf '*/%s * * * *' "$KARLDNS_INTERVAL_MINUTES" fi } update_crontab() { cron_file="$1" schedule="$2" command="$3" marker_start="# karldns-update start" marker_end="# karldns-update end" awk -v start="$marker_start" -v end="$marker_end" ' $0 == start {skip = 1; next} $0 == end {skip = 0; next} skip != 1 {print} ' "$cron_file" 2>/dev/null printf '%s\n' "$marker_start" printf '%s %s\n' "$schedule" "$command" printf '%s\n' "$marker_end" } write_config() { config_file="$1" cache_file="$2" lock_file="$3" umask 077 { printf 'KARLDNS_BASE_URL=%s\n' "$(quote_sh "$KARLDNS_BASE_URL")" printf 'KARLDNS_UPDATE_URL=%s\n' "$(quote_sh "$KARLDNS_UPDATE_URL")" printf 'KARLDNS_HOSTNAME=%s\n' "$(quote_sh "$KARLDNS_HOSTNAME")" printf 'KARLDNS_MANAGEMENT_URL=%s\n' "$(quote_sh "$KARLDNS_MANAGEMENT_URL")" printf 'KARLDNS_CACHE_FILE=%s\n' "$(quote_sh "$cache_file")" printf 'KARLDNS_LOCK_FILE=%s\n' "$(quote_sh "$lock_file")" printf 'KARLDNS_IPV4=%s\n' "$(quote_sh "$KARLDNS_IPV4")" printf 'KARLDNS_IPV6=%s\n' "$(quote_sh "$KARLDNS_IPV6")" } > "$config_file" chmod 600 "$config_file" } install_user() { config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/karldns" cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/karldns" bin_dir="${HOME}/.local/bin" config_file="${config_dir}/updater.env" updater_path="${bin_dir}/karldns-update" cron_file="$(mktemp)" mkdir -p "$config_dir" "$cache_dir" "$bin_dir" download_updater "$updater_path" chmod 700 "$updater_path" write_config "$config_file" "$cache_dir/last-ip" "$cache_dir/updater.lock" if [ "$KARLDNS_SKIP_CRON" = "0" ]; then need crontab crontab -l > "$cron_file" 2>/dev/null || true update_crontab "$cron_file" "$(cron_schedule)" \ "KARLDNS_CONFIG=$(quote_sh "$config_file") $(quote_sh "$updater_path") >/dev/null 2>&1" > "$cron_file.new" mv "$cron_file.new" "$cron_file" crontab "$cron_file" rm -f "$cron_file" fi } install_system() { config_dir="/etc/karldns" cache_dir="/var/cache/karldns" config_file="${config_dir}/updater.env" updater_path="/usr/local/bin/karldns-update" cron_file="/etc/cron.d/karldns-updater" mkdir -p "$config_dir" "$cache_dir" download_updater "$updater_path" write_config "$config_file" "$cache_dir/last-ip" "/run/karldns-updater.lock" if [ "$KARLDNS_SKIP_CRON" = "0" ]; then cat > "$cron_file" </dev/null 2>&1 EOF chmod 644 "$cron_file" fi } need curl curl_bin="$(command -v curl)" case "$KARLDNS_INTERVAL_MINUTES" in ''|*[!0-9]*) echo "KARLDNS_INTERVAL_MINUTES must be a positive integer" >&2; exit 1 ;; esac if [ "$KARLDNS_INTERVAL_MINUTES" -lt 1 ] || [ "$KARLDNS_INTERVAL_MINUTES" -gt 60 ]; then echo "KARLDNS_INTERVAL_MINUTES must be between 1 and 60" >&2 exit 1 fi case "$KARLDNS_INSTALL_MODE" in auto) if [ "$(id -u)" -eq 0 ]; then KARLDNS_INSTALL_MODE="system" else KARLDNS_INSTALL_MODE="user" fi ;; user|system) ;; *) echo "KARLDNS_INSTALL_MODE must be auto, user, or system" >&2; exit 1 ;; esac if [ "$KARLDNS_INSTALL_MODE" = "system" ] && [ "$(id -u)" -ne 0 ]; then echo "System install needs root. Re-run with sudo or use --mode user." >&2 exit 1 fi if [ -z "$KARLDNS_UPDATE_URL" ]; then echo "KarlDNS: creating a new hostname at $KARLDNS_BASE_URL ..." response="$("$curl_bin" -fsS --max-time 30 -H 'content-type: application/json' -d '{}' "$KARLDNS_BASE_URL/api/v1/hostnames/magic")" KARLDNS_UPDATE_URL="$(printf '%s' "$response" | json_field update_url)" KARLDNS_HOSTNAME="$(printf '%s' "$response" | json_field hostname)" KARLDNS_MANAGEMENT_URL="$(printf '%s' "$response" | json_field management_url)" fi if [ -z "$KARLDNS_UPDATE_URL" ]; then echo "KarlDNS installer: could not create an update URL" >&2 exit 1 fi case "$KARLDNS_UPDATE_URL" in *""*|*""*) KARLDNS_UPDATE_URL="${KARLDNS_UPDATE_URL%%\?*}" ;; esac if [ "$KARLDNS_INSTALL_MODE" = "system" ]; then install_system else install_user fi echo "KarlDNS installed." echo "Hostname: ${KARLDNS_HOSTNAME:-already configured}" echo "Dashboard: ${KARLDNS_MANAGEMENT_URL:-use your existing private dashboard}" echo "Mode: $KARLDNS_INSTALL_MODE" if [ "$KARLDNS_SKIP_CRON" = "0" ]; then echo "Interval: every $KARLDNS_INTERVAL_MINUTES minutes" else echo "Cron: skipped" fi if [ "$KARLDNS_SKIP_FIRST_UPDATE" = "0" ]; then echo "Running first update now..." if [ "$KARLDNS_INSTALL_MODE" = "system" ]; then KARLDNS_CONFIG=/etc/karldns/updater.env /usr/local/bin/karldns-update else KARLDNS_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/karldns/updater.env" "$HOME/.local/bin/karldns-update" fi fi