#!/usr/bin/env bash set -Eeuo pipefail export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin if [[ -d /opt/debtik/go/bin ]]; then export PATH=/opt/debtik/go/bin:$PATH; fi export HOME=${HOME:-/root} export GOPATH=${GOPATH:-/var/cache/debtik/go} export GOCACHE=${GOCACHE:-/var/cache/debtik/go-build} src=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." 2>/dev/null && pwd || echo /usr/local) mode=${1:-install} [[ $# -le 1 && ( $mode == install || $mode == --check ) ]] || { echo 'Usage: install-dependencies.sh [--check]' >&2; exit 2; } packages=( bash coreutils grep gawk sed findutils util-linux systemd procps kmod ca-certificates curl git openssh-client tar python3 openssl tzdata iproute2 bridge-utils iptables ipset conntrack wireguard-tools keepalived bird2 dnsmasq isc-dhcp-client ppp pppoe pptp-linux pptpd xl2tpd charon-systemd strongswan-swanctl libstrongswan-standard-plugins libcharon-extra-plugins libcharon-extauth-plugins iputils-ping iputils-tracepath traceroute bind9-dnsutils mtr-tiny ethtool tcpdump ) installed() { [[ $(dpkg-query -W -f='${db:Status-Status}' "$1" 2>/dev/null || :) == installed ]]; } go_mod="" if [[ -f "$src/go.mod" ]]; then go_mod="$src/go.mod" elif [[ -f /var/lib/debtik/update/go.mod ]]; then go_mod=/var/lib/debtik/update/go.mod fi required="" if [[ -n $go_mod ]]; then required=$(awk '$1=="toolchain" {sub(/^go/,"",$2); print $2}' "$go_mod" 2>/dev/null || :) if [[ -z $required ]]; then required=$(awk '$1=="go" {print $2}' "$go_mod" 2>/dev/null || :); fi fi if [[ -z $required || ! $required =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then required="1.26.10" fi go_version_ok() { local actual actual=$(GOTOOLCHAIN=local "$1" version 2>/dev/null | awk '{sub(/^go/,"",$3);print $3}') || return 1 [[ -n $actual && $(printf '%s\n%s\n' "$required" "$actual" | sort -V | head -n1) == "$required" ]] } check_dependencies() { local missing=0 package binary go_cmd for package in "${packages[@]}"; do if ! installed "$package"; then echo "Missing package: $package" >&2; missing=1; fi done if ! installed ifupdown && ! installed ifupdown2; then echo 'Missing ifupdown or ifupdown2' >&2; missing=1; fi for binary in ssh ip bridge brctl iptables ip6tables iptables-save ip6tables-save iptables-restore ip6tables-restore ipset conntrack wg wg-quick keepalived bird birdc dnsmasq dhclient pppd pon poff pppoe-server pptp pptpd xl2tpd swanctl ping tracepath traceroute dig mtr mstpd mstpctl systemctl systemd-run hostnamectl timedatectl pkill flock python3 modprobe ethtool tcpdump; do if ! command -v "$binary" >/dev/null; then echo "Missing command: $binary" >&2; missing=1; fi done go_cmd=$(command -v go || :) if [[ -x /opt/debtik/go/bin/go ]]; then go_cmd=/opt/debtik/go/bin/go; fi if [[ -z $go_cmd ]] || ! go_version_ok "$go_cmd"; then echo "Missing Go >= $required" >&2; missing=1; fi if (( missing )); then return 1; fi echo "All DebTik dependencies available (Go >= $required, including RSTP)." } if [[ $mode == --check ]]; then check_dependencies; exit; fi [[ $EUID -eq 0 ]] || { echo 'Run as root' >&2; exit 1; } command -v apt-get >/dev/null || { echo 'Debian apt-get required' >&2; exit 1; } install -d -m 0700 /var/lib/debtik/update # Reuse the installer's inherited lock, or acquire it for a standalone repair. if [[ $(readlink /proc/self/fd/9 2>/dev/null || :) != /var/lib/debtik/update/lock ]]; then exec 9>/var/lib/debtik/update/lock; fi flock -n 9 || { echo 'An update or installation is already running' >&2; exit 1; } policy_created=false work='' cleanup() { if [[ -n $work ]]; then rm -rf "$work"; fi if [[ $policy_created == true ]]; then rm -f /usr/sbin/policy-rc.d; fi } trap cleanup EXIT if [[ ! -e /usr/sbin/policy-rc.d && ! -L /usr/sbin/policy-rc.d ]]; then printf '#!/bin/sh\nexit 101\n' > /usr/sbin/policy-rc.d chmod 0755 /usr/sbin/policy-rc.d policy_created=true fi export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true NEEDRESTART_MODE=l if ! apt-get update; then if check_dependencies >/dev/null 2>&1; then echo "Notice: apt-get update failed, but all dependencies are already satisfied locally." >&2 else echo "Error: apt-get update failed and required dependencies are missing." >&2 exit 1 fi fi # Proxmox normally uses ifupdown2; retain that implementation. if ! installed ifupdown && ! installed ifupdown2; then packages+=(ifupdown); fi apt-get install -y --no-install-recommends -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" "${packages[@]}" work=$(mktemp -d) if ! command -v mstpd >/dev/null || ! command -v mstpctl >/dev/null; then candidate=$(apt-cache policy mstpd | awk '/Candidate:/ {print $2}') if [[ -n $candidate && $candidate != '(none)' ]]; then apt-get install -y --no-install-recommends -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" mstpd else apt-get install -y --no-install-recommends -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" gcc make libc6-dev autoconf automake libtool pkg-config # Upstream release 0.2.0, pinned to a commit and verified archive digest. mstpd_commit=8995e5d11ee89f2251734e34cea711bb5a5bb911 curl --fail --location --retry 3 "https://codeload.github.com/mstpd/mstpd/tar.gz/$mstpd_commit" -o "$work/mstpd.tar.gz" printf '%s %s\n' f965e715c82640484f89fcf11cd85dbd86c325dcf42ef85bd15173d9a9aa45b3 "$work/mstpd.tar.gz" | sha256sum -c - mkdir "$work/mstpd" tar --strip-components=1 -C "$work/mstpd" -xzf "$work/mstpd.tar.gz" ( cd "$work/mstpd" # The archive has no .git metadata; supply its verified revision label. sed -i 's/^PACKAGE_BUILD=.*/PACKAGE_BUILD=8995e5d/' configure.ac ./autogen.sh ./configure --prefix=/usr/local --exec-prefix=/usr/local --sysconfdir=/etc --localstatedir=/var make -j2 mstpd mstpctl ) install -m 0755 "$work/mstpd/mstpd" /usr/local/sbin/mstpd.next install -m 0755 "$work/mstpd/mstpctl" /usr/local/sbin/mstpctl.next mv -f /usr/local/sbin/mstpd.next /usr/local/sbin/mstpd mv -f /usr/local/sbin/mstpctl.next /usr/local/sbin/mstpctl install -d -m 0755 /usr/local/share/doc/debtik-mstpd install -m 0644 "$work/mstpd/LICENSE" /usr/local/share/doc/debtik-mstpd/LICENSE printf '%s\n' "$mstpd_commit" > /usr/local/share/doc/debtik-mstpd/commit fi fi # Start only the daemon. DebTik explicitly attaches its configured RSTP bridges; # upstream global bridge-stp/ifupdown hooks are not installed by the source build. if ! systemctl cat mstpd.service >/dev/null 2>&1; then mstpd_binary=$(command -v mstpd) cat > /etc/systemd/system/mstpd.service < /etc/systemd/system/proxmox-router.service.d/mstpd.conf <<'UNIT' [Unit] Wants=mstpd.service After=mstpd.service UNIT systemctl daemon-reload systemctl enable --now mstpd.service systemctl is-active --quiet mstpd.service go_cmd=$(command -v go || :) if [[ -x /opt/debtik/go/bin/go ]]; then go_cmd=/opt/debtik/go/bin/go; fi if [[ -z $go_cmd ]] || ! go_version_ok "$go_cmd"; then case $(uname -m) in x86_64) arch=amd64;; aarch64) arch=arm64;; *) echo 'Unsupported CPU' >&2; exit 1;; esac archive="go${required}.linux-${arch}.tar.gz" curl --fail --location --retry 3 "https://go.dev/dl/$archive" -o "$work/$archive" curl --fail --location --retry 3 'https://go.dev/dl/?mode=json&include=all' -o "$work/go-releases.json" digest=$(python3 - "$work/go-releases.json" "$archive" <<'PYTHON' import json, sys with open(sys.argv[1]) as f: releases = json.load(f) for release in releases: for item in release['files']: if item['filename'] == sys.argv[2]: print(item['sha256']) raise SystemExit(0) raise SystemExit('Go archive is absent from the official release manifest') PYTHON ) [[ $digest =~ ^[a-f0-9]{64}$ ]] || { echo 'Invalid Go archive checksum' >&2; exit 1; } printf '%s %s\n' "$digest" "$work/$archive" | sha256sum -c - mkdir "$work/go-stage" tar -C "$work/go-stage" -xzf "$work/$archive" install -d -m 0755 /opt/debtik rm -rf /opt/debtik/go.previous if [[ -d /opt/debtik/go ]]; then mv /opt/debtik/go /opt/debtik/go.previous; fi if ! mv "$work/go-stage/go" /opt/debtik/go; then if [[ -d /opt/debtik/go.previous ]]; then mv /opt/debtik/go.previous /opt/debtik/go; fi exit 1 fi fi check_dependencies