install-dependencies.sh 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. #!/usr/bin/env bash
  2. set -Eeuo pipefail
  3. export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
  4. if [[ -d /opt/debtik/go/bin ]]; then export PATH=/opt/debtik/go/bin:$PATH; fi
  5. export HOME=${HOME:-/root}
  6. export GOPATH=${GOPATH:-/var/cache/debtik/go}
  7. export GOCACHE=${GOCACHE:-/var/cache/debtik/go-build}
  8. src=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." 2>/dev/null && pwd || echo /usr/local)
  9. mode=${1:-install}
  10. [[ $# -le 1 && ( $mode == install || $mode == --check ) ]] || { echo 'Usage: install-dependencies.sh [--check]' >&2; exit 2; }
  11. packages=(
  12. bash coreutils grep gawk sed findutils util-linux systemd procps kmod
  13. ca-certificates curl git openssh-client tar python3 openssl tzdata
  14. iproute2 bridge-utils iptables ipset conntrack wireguard-tools
  15. keepalived bird2 dnsmasq isc-dhcp-client
  16. ppp pppoe pptp-linux pptpd xl2tpd
  17. charon-systemd strongswan-swanctl libstrongswan-standard-plugins
  18. libcharon-extra-plugins libcharon-extauth-plugins
  19. iputils-ping iputils-tracepath traceroute bind9-dnsutils mtr-tiny
  20. ethtool tcpdump
  21. )
  22. installed() { [[ $(dpkg-query -W -f='${db:Status-Status}' "$1" 2>/dev/null || :) == installed ]]; }
  23. go_mod=""
  24. if [[ -f "$src/go.mod" ]]; then
  25. go_mod="$src/go.mod"
  26. elif [[ -f /var/lib/debtik/update/go.mod ]]; then
  27. go_mod=/var/lib/debtik/update/go.mod
  28. fi
  29. required=""
  30. if [[ -n $go_mod ]]; then
  31. required=$(awk '$1=="toolchain" {sub(/^go/,"",$2); print $2}' "$go_mod" 2>/dev/null || :)
  32. if [[ -z $required ]]; then required=$(awk '$1=="go" {print $2}' "$go_mod" 2>/dev/null || :); fi
  33. fi
  34. if [[ -z $required || ! $required =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then
  35. required="1.26.10"
  36. fi
  37. go_version_ok() {
  38. local actual
  39. actual=$(GOTOOLCHAIN=local "$1" version 2>/dev/null | awk '{sub(/^go/,"",$3);print $3}') || return 1
  40. [[ -n $actual && $(printf '%s\n%s\n' "$required" "$actual" | sort -V | head -n1) == "$required" ]]
  41. }
  42. check_dependencies() {
  43. local missing=0 package binary go_cmd
  44. for package in "${packages[@]}"; do
  45. if ! installed "$package"; then echo "Missing package: $package" >&2; missing=1; fi
  46. done
  47. if ! installed ifupdown && ! installed ifupdown2; then echo 'Missing ifupdown or ifupdown2' >&2; missing=1; fi
  48. 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
  49. if ! command -v "$binary" >/dev/null; then echo "Missing command: $binary" >&2; missing=1; fi
  50. done
  51. go_cmd=$(command -v go || :)
  52. if [[ -x /opt/debtik/go/bin/go ]]; then go_cmd=/opt/debtik/go/bin/go; fi
  53. if [[ -z $go_cmd ]] || ! go_version_ok "$go_cmd"; then echo "Missing Go >= $required" >&2; missing=1; fi
  54. if (( missing )); then return 1; fi
  55. echo "All DebTik dependencies available (Go >= $required, including RSTP)."
  56. }
  57. if [[ $mode == --check ]]; then check_dependencies; exit; fi
  58. [[ $EUID -eq 0 ]] || { echo 'Run as root' >&2; exit 1; }
  59. command -v apt-get >/dev/null || { echo 'Debian apt-get required' >&2; exit 1; }
  60. install -d -m 0700 /var/lib/debtik/update
  61. # Reuse the installer's inherited lock, or acquire it for a standalone repair.
  62. if [[ $(readlink /proc/self/fd/9 2>/dev/null || :) != /var/lib/debtik/update/lock ]]; then exec 9>/var/lib/debtik/update/lock; fi
  63. flock -n 9 || { echo 'An update or installation is already running' >&2; exit 1; }
  64. policy_created=false
  65. work=''
  66. cleanup() {
  67. if [[ -n $work ]]; then rm -rf "$work"; fi
  68. if [[ $policy_created == true ]]; then rm -f /usr/sbin/policy-rc.d; fi
  69. }
  70. trap cleanup EXIT
  71. if [[ ! -e /usr/sbin/policy-rc.d && ! -L /usr/sbin/policy-rc.d ]]; then
  72. printf '#!/bin/sh\nexit 101\n' > /usr/sbin/policy-rc.d
  73. chmod 0755 /usr/sbin/policy-rc.d
  74. policy_created=true
  75. fi
  76. export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true NEEDRESTART_MODE=l
  77. if ! apt-get update; then
  78. if check_dependencies >/dev/null 2>&1; then
  79. echo "Notice: apt-get update failed, but all dependencies are already satisfied locally." >&2
  80. else
  81. echo "Error: apt-get update failed and required dependencies are missing." >&2
  82. exit 1
  83. fi
  84. fi
  85. # Proxmox normally uses ifupdown2; retain that implementation.
  86. if ! installed ifupdown && ! installed ifupdown2; then packages+=(ifupdown); fi
  87. apt-get install -y --no-install-recommends -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" "${packages[@]}"
  88. work=$(mktemp -d)
  89. if ! command -v mstpd >/dev/null || ! command -v mstpctl >/dev/null; then
  90. candidate=$(apt-cache policy mstpd | awk '/Candidate:/ {print $2}')
  91. if [[ -n $candidate && $candidate != '(none)' ]]; then
  92. apt-get install -y --no-install-recommends -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" mstpd
  93. else
  94. 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
  95. # Upstream release 0.2.0, pinned to a commit and verified archive digest.
  96. mstpd_commit=8995e5d11ee89f2251734e34cea711bb5a5bb911
  97. curl --fail --location --retry 3 "https://codeload.github.com/mstpd/mstpd/tar.gz/$mstpd_commit" -o "$work/mstpd.tar.gz"
  98. printf '%s %s\n' f965e715c82640484f89fcf11cd85dbd86c325dcf42ef85bd15173d9a9aa45b3 "$work/mstpd.tar.gz" | sha256sum -c -
  99. mkdir "$work/mstpd"
  100. tar --strip-components=1 -C "$work/mstpd" -xzf "$work/mstpd.tar.gz"
  101. (
  102. cd "$work/mstpd"
  103. # The archive has no .git metadata; supply its verified revision label.
  104. sed -i 's/^PACKAGE_BUILD=.*/PACKAGE_BUILD=8995e5d/' configure.ac
  105. ./autogen.sh
  106. ./configure --prefix=/usr/local --exec-prefix=/usr/local --sysconfdir=/etc --localstatedir=/var
  107. make -j2 mstpd mstpctl
  108. )
  109. install -m 0755 "$work/mstpd/mstpd" /usr/local/sbin/mstpd.next
  110. install -m 0755 "$work/mstpd/mstpctl" /usr/local/sbin/mstpctl.next
  111. mv -f /usr/local/sbin/mstpd.next /usr/local/sbin/mstpd
  112. mv -f /usr/local/sbin/mstpctl.next /usr/local/sbin/mstpctl
  113. install -d -m 0755 /usr/local/share/doc/debtik-mstpd
  114. install -m 0644 "$work/mstpd/LICENSE" /usr/local/share/doc/debtik-mstpd/LICENSE
  115. printf '%s\n' "$mstpd_commit" > /usr/local/share/doc/debtik-mstpd/commit
  116. fi
  117. fi
  118. # Start only the daemon. DebTik explicitly attaches its configured RSTP bridges;
  119. # upstream global bridge-stp/ifupdown hooks are not installed by the source build.
  120. if ! systemctl cat mstpd.service >/dev/null 2>&1; then
  121. mstpd_binary=$(command -v mstpd)
  122. cat > /etc/systemd/system/mstpd.service <<UNIT
  123. [Unit]
  124. Description=Spanning Tree Protocol daemon for DebTik
  125. Before=debtik.service
  126. [Service]
  127. Type=simple
  128. ExecStart=$mstpd_binary -d
  129. Restart=on-failure
  130. RestartSec=2
  131. [Install]
  132. WantedBy=multi-user.target
  133. UNIT
  134. fi
  135. install -d -m 0755 /etc/systemd/system/debtik.service.d
  136. cat > /etc/systemd/system/debtik.service.d/mstpd.conf <<'UNIT'
  137. [Unit]
  138. Wants=mstpd.service
  139. After=mstpd.service
  140. UNIT
  141. install -d -m 0755 /etc/systemd/system/proxmox-router.service.d
  142. cat > /etc/systemd/system/proxmox-router.service.d/mstpd.conf <<'UNIT'
  143. [Unit]
  144. Wants=mstpd.service
  145. After=mstpd.service
  146. UNIT
  147. systemctl daemon-reload
  148. systemctl enable --now mstpd.service
  149. systemctl is-active --quiet mstpd.service
  150. go_cmd=$(command -v go || :)
  151. if [[ -x /opt/debtik/go/bin/go ]]; then go_cmd=/opt/debtik/go/bin/go; fi
  152. if [[ -z $go_cmd ]] || ! go_version_ok "$go_cmd"; then
  153. case $(uname -m) in x86_64) arch=amd64;; aarch64) arch=arm64;; *) echo 'Unsupported CPU' >&2; exit 1;; esac
  154. archive="go${required}.linux-${arch}.tar.gz"
  155. curl --fail --location --retry 3 "https://go.dev/dl/$archive" -o "$work/$archive"
  156. curl --fail --location --retry 3 'https://go.dev/dl/?mode=json&include=all' -o "$work/go-releases.json"
  157. digest=$(python3 - "$work/go-releases.json" "$archive" <<'PYTHON'
  158. import json, sys
  159. with open(sys.argv[1]) as f:
  160. releases = json.load(f)
  161. for release in releases:
  162. for item in release['files']:
  163. if item['filename'] == sys.argv[2]:
  164. print(item['sha256'])
  165. raise SystemExit(0)
  166. raise SystemExit('Go archive is absent from the official release manifest')
  167. PYTHON
  168. )
  169. [[ $digest =~ ^[a-f0-9]{64}$ ]] || { echo 'Invalid Go archive checksum' >&2; exit 1; }
  170. printf '%s %s\n' "$digest" "$work/$archive" | sha256sum -c -
  171. mkdir "$work/go-stage"
  172. tar -C "$work/go-stage" -xzf "$work/$archive"
  173. install -d -m 0755 /opt/debtik
  174. rm -rf /opt/debtik/go.previous
  175. if [[ -d /opt/debtik/go ]]; then mv /opt/debtik/go /opt/debtik/go.previous; fi
  176. if ! mv "$work/go-stage/go" /opt/debtik/go; then
  177. if [[ -d /opt/debtik/go.previous ]]; then mv /opt/debtik/go.previous /opt/debtik/go; fi
  178. exit 1
  179. fi
  180. fi
  181. check_dependencies