#!/bin/sh
# Copyright (c) Tailscale Inc & contributors
# SPDX-License-Identifier: BSD-3-Clause
#
# Larktun Linux installer.
#
# Usage:
#   curl -fsSL https://download.larktun.com/install.sh | sh
#
# Environment variables:
#   LARKTUN_DOWNLOAD_URL: Override the download base URL
#                         (default: https://download.larktun.com)

set -eu

main() {
	BASE_URL="${LARKTUN_DOWNLOAD_URL:-https://download2.larktun.com}"
	BASE_URL="${BASE_URL%/}"

	require_linux
	detect_arch
	detect_downloader
	detect_root
	make_tempdir

	echo "Installing Larktun for Linux/$ARCH"

	CLI_TMP="$TMPDIR_CREATED/larktun"
	DAEMON_TMP="$TMPDIR_CREATED/larktund"
	download_file "$BASE_URL/linux-larktun-$ARCH" "$CLI_TMP"
	download_file "$BASE_URL/linux-larktund-$ARCH" "$DAEMON_TMP"

	stop_existing_larktun
	install_binaries "$CLI_TMP" "$DAEMON_TMP"
	install_service

	echo
	echo "Installation complete! Log in to start using Larktun by running:"
	echo
	if [ -z "$SUDO" ]; then
		echo "larktun up --accept-routes=false --accept-dns=false"
	else
		echo "$SUDO larktun up --accept-routes=false --accept-dns=false"
	fi
	echo
	echo "This keeps the server's existing default route and DNS. Enable Larktun routes or DNS later only if you need them."
}

fail() {
	echo "install.sh: $*" >&2
	exit 1
}

require_linux() {
	if ! command -v uname >/dev/null 2>&1; then
		fail "cannot detect operating system: uname not found"
	fi

	if [ "$(uname -s)" != "Linux" ]; then
		fail "this installer only supports Linux"
	fi
}

detect_arch() {
	case "$(uname -m)" in
		x86_64|amd64)
			ARCH="amd64"
			;;
		aarch64|arm64)
			ARCH="arm64"
			;;
		*)
			fail "unsupported CPU architecture: $(uname -m). Supported architectures are amd64 and arm64."
			;;
	esac
}

detect_downloader() {
	if command -v curl >/dev/null 2>&1; then
		DOWNLOADER="curl"
	elif command -v wget >/dev/null 2>&1; then
		DOWNLOADER="wget"
	else
		fail "curl or wget is required to download Larktun"
	fi
}

detect_root() {
	CAN_ROOT=
	SUDO=
	if [ "$(id -u)" = 0 ]; then
		CAN_ROOT=1
		SUDO=""
	elif command -v sudo >/dev/null 2>&1; then
		CAN_ROOT=1
		SUDO="sudo"
	elif command -v doas >/dev/null 2>&1; then
		CAN_ROOT=1
		SUDO="doas"
	fi

	if [ "$CAN_ROOT" != "1" ]; then
		fail "this installer needs root, sudo, or doas"
	fi
}

make_tempdir() {
	TMPDIR_CREATED="$(mktemp -d "${TMPDIR:-/tmp}/larktun-install.XXXXXX")" || fail "mktemp failed"
	trap 'rm -rf "$TMPDIR_CREATED"' EXIT INT TERM
}

download_file() {
	url="$1"
	dest="$2"

	echo "Downloading $url"
	case "$DOWNLOADER" in
		curl)
			curl -fL --retry 3 -o "$dest" "$url"
			;;
		wget)
			wget -q -O "$dest" "$url"
			;;
		*)
			fail "unknown downloader: $DOWNLOADER"
			;;
	esac

	if [ ! -s "$dest" ]; then
		fail "downloaded file is empty: $url"
	fi
	chmod 0755 "$dest"
}

stop_existing_larktun() {
	if is_systemd; then
		$SUDO systemctl stop larktund >/dev/null 2>&1 || true
	fi

	if command -v rc-service >/dev/null 2>&1; then
		$SUDO rc-service larktund stop >/dev/null 2>&1 || true
	fi

	if [ -x /etc/init.d/larktund ]; then
		$SUDO /etc/init.d/larktund stop >/dev/null 2>&1 || true
	fi
}

install_binaries() {
	cli_tmp="$1"
	daemon_tmp="$2"

	$SUDO install -d -m 0755 /usr/bin
	$SUDO install -d -m 0755 /usr/sbin
	$SUDO install -m 0755 "$cli_tmp" /usr/bin/larktun
	$SUDO install -m 0755 "$daemon_tmp" /usr/sbin/larktund
}

install_service() {
	ensure_runtime_dirs
	ensure_default_file
	install_underlay_route_helper

	if is_systemd; then
		install_systemd_service
	elif is_openrc; then
		install_openrc_service
	elif [ -d /etc/init.d ]; then
		install_sysv_service
	else
		echo
		echo "No supported init system detected. Installed binaries only."
		echo "Start the daemon manually with:"
		if [ -z "$SUDO" ]; then
			echo "/usr/sbin/larktund --state=/var/lib/larktun/tailscaled.state --socket=/run/larktun/larktund.sock --port=41641"
		else
			echo "$SUDO /usr/sbin/larktund --state=/var/lib/larktun/tailscaled.state --socket=/run/larktun/larktund.sock --port=41641"
		fi
	fi
}

install_underlay_route_helper() {
	$SUDO install -d -m 0755 /usr/lib/larktun
	write_root_file /usr/lib/larktun/preserve-underlay-routes.sh 0755 <<'EOF'
#!/bin/sh
# Preserve underlay DNS routes that overlap with Larktun's 100.64.0.0/10 range.
# Some cloud providers, notably Alibaba Cloud, use DNS servers such as
# 100.100.2.136 and 100.100.2.138. After Larktun comes up, the broader
# 100.64.0.0/10 route can otherwise capture those DNS queries.

PATH=/sbin:/usr/sbin:/bin:/usr/bin

command -v ip >/dev/null 2>&1 || exit 0

default_route="$(ip -4 route show default 2>/dev/null | awk 'NR == 1 { print; exit }')"
[ -n "$default_route" ] || exit 0

gateway="$(printf '%s\n' "$default_route" | awk '{ for (i = 1; i <= NF; i++) if ($i == "via") { print $(i + 1); exit } }')"
device="$(printf '%s\n' "$default_route" | awk '{ for (i = 1; i <= NF; i++) if ($i == "dev") { print $(i + 1); exit } }')"
[ -n "$device" ] || exit 0

is_cgnat_ipv4() {
	printf '%s\n' "$1" | awk -F. '
		NF == 4 &&
		$1 == 100 &&
		$2 >= 64 && $2 <= 127 &&
		$3 >= 0 && $3 <= 255 &&
		$4 >= 0 && $4 <= 255 { ok = 1 }
		END { exit ok ? 0 : 1 }
	'
}

add_route() {
	ns="$1"
	table="$2"

	if [ -n "$gateway" ]; then
		ip -4 route replace "$ns/32" via "$gateway" dev "$device" table "$table" >/dev/null 2>&1 ||
			ip -4 route replace "$ns/32" via "$gateway" dev "$device" onlink table "$table" >/dev/null 2>&1 ||
			return 0
	else
		ip -4 route replace "$ns/32" dev "$device" table "$table" >/dev/null 2>&1 || return 0
	fi
}

list_nameservers() {
	for resolv_conf in /etc/resolv.conf /run/systemd/resolve/resolv.conf; do
		if [ -f "$resolv_conf" ]; then
			awk '$1 == "nameserver" { print $2 }' "$resolv_conf"
		fi
	done
	if command -v resolvectl >/dev/null 2>&1; then
		resolvectl dns 2>/dev/null | tr ' ' '\n' | awk -F: '
			$NF ~ /^[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$/ { print $NF }
		'
	elif command -v systemd-resolve >/dev/null 2>&1; then
		systemd-resolve --status 2>/dev/null | awk '
			$0 ~ /DNS Servers:/ { for (i = 3; i <= NF; i++) print $i; next }
			$1 ~ /^[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$/ { print $1 }
		'
	fi
}

list_nameservers | sort -u |
while IFS= read -r ns; do
	if is_cgnat_ipv4 "$ns"; then
		add_route "$ns" main
		add_route "$ns" 52
	fi
done

if command -v resolvectl >/dev/null 2>&1; then
	resolvectl flush-caches >/dev/null 2>&1 || true
	resolvectl reset-server-features >/dev/null 2>&1 || true
fi
EOF

	$SUDO /usr/lib/larktun/preserve-underlay-routes.sh || true
}

ensure_runtime_dirs() {
	$SUDO install -d -m 0700 /var/lib/larktun
	$SUDO install -d -m 0755 /run/larktun
}

ensure_default_file() {
	$SUDO install -d -m 0755 /etc/default

	if [ -f /etc/default/larktund ]; then
		return
	fi

	if [ -f /etc/default/tailscaled ]; then
		echo "Copying existing /etc/default/tailscaled to /etc/default/larktund"
		$SUDO cp /etc/default/tailscaled /etc/default/larktund
		$SUDO chmod 0644 /etc/default/larktund
		return
	fi

	write_root_file /etc/default/larktund 0644 <<'EOF'
# Set the port to listen on for incoming VPN packets.
PORT="41641"

# Extra flags to pass to larktund.
FLAGS=""
EOF
}

write_root_file() {
	path="$1"
	mode="$2"
	tmp="$TMPDIR_CREATED/root-file"

	cat > "$tmp"
	$SUDO install -m "$mode" "$tmp" "$path"
}

is_systemd() {
	[ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1
}

is_openrc() {
	command -v rc-service >/dev/null 2>&1 && command -v rc-update >/dev/null 2>&1
}

install_systemd_service() {
	echo "Installing systemd service larktund.service"
	$SUDO install -d -m 0755 /etc/systemd/system
	write_root_file /etc/systemd/system/larktund.service 0644 <<'EOF'
[Unit]
Description=Larktun node agent
Documentation=https://download.larktun.com/
Wants=network-pre.target
After=network-pre.target NetworkManager.service systemd-resolved.service

[Service]
EnvironmentFile=-/etc/default/larktund
ExecStart=/usr/sbin/larktund --state=/var/lib/larktun/tailscaled.state --socket=/run/larktun/larktund.sock --port=${PORT} $FLAGS
ExecStartPost=-/usr/lib/larktun/preserve-underlay-routes.sh
ExecStopPost=/usr/sbin/larktund --cleanup
Restart=on-failure
RuntimeDirectory=larktun
RuntimeDirectoryMode=0755
StateDirectory=larktun
StateDirectoryMode=0700
CacheDirectory=larktun
CacheDirectoryMode=0750
Type=notify

[Install]
WantedBy=multi-user.target
EOF

	$SUDO systemctl daemon-reload
	$SUDO systemctl enable larktund >/dev/null
	$SUDO systemctl start larktund
}

install_openrc_service() {
	echo "Installing OpenRC service larktund"
	$SUDO install -d -m 0755 /etc/init.d
	write_root_file /etc/init.d/larktund 0755 <<'EOF'
#!/sbin/openrc-run

set -a
[ -f /etc/default/larktund ] && . /etc/default/larktund
set +a

name="larktund"
description="Larktun node agent"
command="/usr/sbin/larktund"
command_args="--state=/var/lib/larktun/tailscaled.state --socket=/run/larktun/larktund.sock --port=${PORT:-41641} ${FLAGS:-}"
command_background=true
pidfile="/run/larktund.pid"
output_log="/var/log/larktun.log"
error_log="/var/log/larktun.log"

depend() {
	need net
}

start_pre() {
	checkpath -d -m 0755 /run/larktun
	checkpath -d -m 0700 /var/lib/larktun
	/usr/lib/larktun/preserve-underlay-routes.sh >/dev/null 2>&1 || true
	$command --cleanup >/dev/null 2>&1 || true
}

stop_post() {
	$command --cleanup >/dev/null 2>&1 || true
}
EOF

	$SUDO rc-update add larktund default >/dev/null
	$SUDO rc-service larktund start
}

install_sysv_service() {
	echo "Installing SysV init service larktund"
	$SUDO install -d -m 0755 /etc/init.d
	write_root_file /etc/init.d/larktund 0755 <<'EOF'
#!/bin/sh
### BEGIN INIT INFO
# Provides:          larktund
# Required-Start:    $network
# Required-Stop:     $network
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Larktun node agent
### END INIT INFO

set -e

PATH=/sbin:/usr/sbin:/bin:/usr/bin
NAME=larktund
DAEMON=/usr/sbin/larktund
PIDFILE=/run/larktund.pid
STATE=/var/lib/larktun/tailscaled.state
SOCKET=/run/larktun/larktund.sock
PORT=41641
FLAGS=

[ -f /etc/default/larktund ] && . /etc/default/larktund

mkdir -p /run/larktun /var/lib/larktun
chmod 0755 /run/larktun
chmod 0700 /var/lib/larktun

do_start() {
	/usr/lib/larktun/preserve-underlay-routes.sh >/dev/null 2>&1 || true
	if command -v start-stop-daemon >/dev/null 2>&1; then
		start-stop-daemon --start --quiet --oknodo --background --make-pidfile --pidfile "$PIDFILE" --exec "$DAEMON" -- \
			--state="$STATE" \
			--socket="$SOCKET" \
			--port="$PORT" \
			$FLAGS
	else
		if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" >/dev/null 2>&1; then
			return 0
		fi
		"$DAEMON" --state="$STATE" --socket="$SOCKET" --port="$PORT" $FLAGS >/var/log/larktun.log 2>&1 &
		echo $! > "$PIDFILE"
	fi
}

do_stop() {
	if command -v start-stop-daemon >/dev/null 2>&1; then
		start-stop-daemon --stop --quiet --oknodo --remove-pidfile --pidfile "$PIDFILE" --exec "$DAEMON"
	elif [ -f "$PIDFILE" ]; then
		kill "$(cat "$PIDFILE")" >/dev/null 2>&1 || true
		rm -f "$PIDFILE"
	fi
	"$DAEMON" --cleanup >/dev/null 2>&1 || true
}

case "$1" in
	start)
		echo "Starting $NAME"
		do_start
		;;
	stop)
		echo "Stopping $NAME"
		do_stop
		;;
	restart)
		do_stop
		do_start
		;;
	status)
		if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" >/dev/null 2>&1; then
			echo "$NAME is running"
			exit 0
		fi
		echo "$NAME is not running"
		exit 3
		;;
	*)
		echo "Usage: /etc/init.d/$NAME {start|stop|restart|status}" >&2
		exit 2
		;;
esac

exit 0
EOF

	if command -v update-rc.d >/dev/null 2>&1; then
		$SUDO update-rc.d larktund defaults >/dev/null
	elif command -v chkconfig >/dev/null 2>&1; then
		$SUDO chkconfig --add larktund
		$SUDO chkconfig larktund on
	fi

	$SUDO /etc/init.d/larktund start
}

main "$@"
