Sfoglia il codice sorgente

feat(deps): integrate dependency installation and verification into installer and updater

Proxmox Router 1 giorno fa
parent
commit
2de7dd923e

BIN
bin/debtik-cli


BIN
bin/proxmox-router


+ 15 - 8
docs/dependencies.md

@@ -1,15 +1,15 @@
-# Installation dependencies (1.2.2)
+# Installation and update dependencies
 
 `install.sh` invokes `scripts/install-dependencies.sh` as the single dependency
-manifest. Both the full installation and `--dependencies-only` use it;
-`--check-dependencies` verifies package status, executable availability and Go
-version without installing anything. Debian 13, systemd and root are required.
+manifest. Full installation, `--dependencies-only`, and Git upgrades (`debtik-update`)
+all use it; `--check-dependencies` verifies package status, executable availability and Go
+version without installing anything. Debian 13 (or Proxmox VE), systemd and root are required.
 
 | Feature | Debian packages |
 | --- | --- |
 | Shell and system management | bash, coreutils, grep, gawk, sed, findutils, util-linux, systemd, procps, kmod, tzdata |
 | Downloads, Git, SSH repositories and helpers | ca-certificates, curl, git, openssh-client, tar, python3, openssl |
-| Interfaces, bridges and firewall | iproute2, bridge-utils, iptables, ipset, conntrack |
+| Interfaces, bridges, diagnostics and firewall | iproute2, bridge-utils, iptables, ipset, conntrack, ethtool, tcpdump |
 | WireGuard | wireguard-tools |
 | VRRP and dynamic routing | keepalived, bird2 |
 | DHCP/DNS | dnsmasq, isc-dhcp-client |
@@ -52,9 +52,16 @@ ACME issuance and renewal are implemented in Go; external certbot is not a
 runtime dependency. Kernel capabilities such as WireGuard, bridge networking and
 network administration permissions must be provided by the host/container.
 
-Git application updates currently replace binaries and updater helpers. Use the
-latest checkout's `./install.sh --dependencies-only` to synchronize dependencies
-on an existing installation; the updater does not run apt automatically.
+Git application updates (`debtik-update`, triggered from the Web UI or CLI)
+automatically synchronize and install dependencies prior to compiling and activating
+the new release. The updater runs `scripts/install-dependencies.sh` from the target
+release, ensuring newly required Debian packages, mstpd bridge routing tools, and
+matching Go toolchains are fully installed and verified before building. If dependency
+synchronization fails, the update aborts cleanly before stopping or modifying the
+active DebTik service.
+The dependency installer helper is also installed to `/usr/local/libexec/debtik-install-dependencies`
+and refreshed automatically on startup. Standalone checks can be performed via
+`install.sh --check-dependencies` or `/usr/local/libexec/debtik-install-dependencies --check`.
 
 ## Verification
 

+ 10 - 3
install.sh

@@ -16,19 +16,25 @@ if [[ $mode == --dependencies-only ]]; then exit; fi
 export HOME=${HOME:-/root}
 export GOPATH=${GOPATH:-/var/cache/debtik/go}
 export GOCACHE=${GOCACHE:-/var/cache/debtik/go-build}
-if [[ -x /opt/debtik/go/bin/go ]]; then export PATH=/opt/debtik/go/bin:$PATH; fi
+go_cmd=$(command -v go || :)
+if [[ -x /opt/debtik/go/bin/go ]]; then
+  go_cmd=/opt/debtik/go/bin/go
+  export PATH=/opt/debtik/go/bin:$PATH
+fi
+[[ -n $go_cmd ]] || { echo 'Go compiler not found' >&2; exit 1; }
 cd "$src"
 build=$(mktemp -d)
 commit=$(git rev-parse HEAD 2>/dev/null || printf unknown)
 echo "Building DebTik server for $(uname -m); the first build may take several minutes..."
-CGO_ENABLED=0 go build -trimpath -ldflags "-X proxmox-router/internal/api.BuildCommit=$commit" -o "$build/proxmox-router" ./cmd/server
+CGO_ENABLED=0 "$go_cmd" build -trimpath -ldflags "-X proxmox-router/internal/api.BuildCommit=$commit" -o "$build/proxmox-router" ./cmd/server
 echo "Building DebTik CLI..."
-CGO_ENABLED=0 go build -trimpath -o "$build/debtik-cli" ./cmd/debtik-cli
+CGO_ENABLED=0 "$go_cmd" build -trimpath -o "$build/debtik-cli" ./cmd/debtik-cli
 install -d -m 0700 /etc/proxmox-router
 install -d -m 0700 /var/lib/debtik/update
 install -d -m 0755 /usr/local/libexec
 install -m 0750 scripts/debtik-update /usr/local/libexec/debtik-update
 install -m 0750 scripts/release-version.py /usr/local/libexec/debtik-release-version
+install -m 0750 scripts/install-dependencies.sh /usr/local/libexec/debtik-install-dependencies
 install -d -m 0755 /etc/network/interfaces.d
 for name in proxmox-router debtik-cli; do
   if [[ -f /usr/local/bin/$name ]]; then cp -p "/usr/local/bin/$name" "/usr/local/bin/$name.previous"; fi
@@ -43,4 +49,5 @@ systemctl restart proxmox-router.service
 systemctl is-active --quiet proxmox-router.service
 if [[ $commit =~ ^[a-f0-9]{40}$ ]]; then printf '%s\n' "$commit" > /var/lib/debtik/update/installed_commit; fi
 install -m 0600 VERSION /var/lib/debtik/update/installed_version
+install -m 0644 go.mod /var/lib/debtik/update/go.mod
 echo 'DebTik installed: http://<host>/ and debtik-cli'

+ 15 - 2
scripts/debtik-update

@@ -1,6 +1,8 @@
 #!/usr/bin/env bash
 set -Eeuo pipefail
 umask 077
+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
 # Transient systemd units may omit HOME; Go needs explicit cache locations.
 export HOME=${HOME:-/root}
 export GOPATH=${GOPATH:-/var/cache/debtik/go}
@@ -39,11 +41,12 @@ restore() {
     cp -p "$base/previous/$name" "/usr/local/bin/$name.rollback"
     mv -f "/usr/local/bin/$name.rollback" "/usr/local/bin/$name"
   done
-  for name in debtik-update debtik-release-version; do
+  for name in debtik-update debtik-release-version debtik-install-dependencies; do
     if [[ -f "$base/previous/$name" ]]; then install -m 0750 "$base/previous/$name" "/usr/local/libexec/$name.rollback"; mv -f "/usr/local/libexec/$name.rollback" "/usr/local/libexec/$name"; fi
   done
   if [[ -f "$base/previous_commit" ]]; then cp -p "$base/previous_commit" "$base/installed_commit"; fi
   if [[ -f "$base/previous_version" ]]; then cp -p "$base/previous_version" "$base/installed_version"; fi
+  if [[ -f "$base/previous/go.mod" ]]; then cp -p "$base/previous/go.mod" "$base/go.mod"; fi
   systemctl start proxmox-router.service
   systemctl is-active --quiet proxmox-router.service
   if [[ -f "$base/previous/iptables.save" ]]; then iptables-restore -c < "$base/previous/iptables.save"; fi
@@ -77,7 +80,11 @@ cd "$work/source"
 commit=$(git rev-parse HEAD)
 [[ $(cat VERSION) == "$version" ]] || { state failed; echo 'Release tag and VERSION do not match'; exit 1; }
 bash -n scripts/debtik-update
+bash -n scripts/install-dependencies.sh
+bash -n install.sh
 python3 -c 'import pathlib; compile(pathlib.Path("scripts/release-version.py").read_text(),"release-version.py","exec")'
+state dependencies
+bash scripts/install-dependencies.sh
 state building
 go_cmd=$(command -v go || true)
 if [[ -x /opt/debtik/go/bin/go ]]; then go_cmd=/opt/debtik/go/bin/go; fi
@@ -86,9 +93,13 @@ CGO_ENABLED=0 "$go_cmd" build -trimpath -ldflags "-X proxmox-router/internal/api
 CGO_ENABLED=0 "$go_cmd" build -trimpath -o "$work/debtik-cli" ./cmd/debtik-cli
 install -d -m 0700 "$base/previous"
 for name in proxmox-router debtik-cli; do cp -p "/usr/local/bin/$name" "$base/previous/$name"; done
-for name in debtik-update debtik-release-version; do cp -p "/usr/local/libexec/$name" "$base/previous/$name"; done
+for name in debtik-update debtik-release-version debtik-install-dependencies; do
+  if [[ -f "/usr/local/libexec/$name" ]]; then cp -p "/usr/local/libexec/$name" "$base/previous/$name"; fi
+done
 cp -p "$base/installed_version" "$base/previous_version"
 if [[ -f "$base/installed_commit" ]]; then cp -p "$base/installed_commit" "$base/previous_commit"; fi
+if [[ -f "$base/go.mod" ]]; then cp -p "$base/go.mod" "$base/previous/go.mod"; fi
+install -m 0644 go.mod "$base/go.mod"
 printf '%s\n' "$commit" > "$base/candidate_commit"
 iptables-save -c > "$base/previous/iptables.save"
 ip6tables-save -c > "$base/previous/ip6tables.save"
@@ -103,8 +114,10 @@ for name in proxmox-router debtik-cli; do
 done
 install -m 0750 scripts/debtik-update /usr/local/libexec/debtik-update.next
 install -m 0750 scripts/release-version.py /usr/local/libexec/debtik-release-version.next
+install -m 0750 scripts/install-dependencies.sh /usr/local/libexec/debtik-install-dependencies.next
 mv -f /usr/local/libexec/debtik-update.next /usr/local/libexec/debtik-update
 mv -f /usr/local/libexec/debtik-release-version.next /usr/local/libexec/debtik-release-version
+mv -f /usr/local/libexec/debtik-install-dependencies.next /usr/local/libexec/debtik-install-dependencies
 systemctl start proxmox-router.service
 sleep 3
 systemctl is-active --quiet proxmox-router.service

+ 30 - 10
scripts/install-dependencies.sh

@@ -1,10 +1,11 @@
 #!/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]}")/.." && pwd)
+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=(
@@ -16,11 +17,23 @@ packages=(
   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 ]]; }
-required=$(awk '$1=="toolchain" {sub(/^go/,"",$2); print $2}' "$src/go.mod")
-if [[ -z $required ]]; then required=$(awk '$1=="go" {print $2}' "$src/go.mod"); fi
-[[ $required =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]] || { echo 'Invalid Go version in go.mod' >&2; exit 1; }
+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
@@ -32,7 +45,7 @@ check_dependencies() {
     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; do
+  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 || :)
@@ -60,18 +73,25 @@ if [[ ! -e /usr/sbin/policy-rc.d && ! -L /usr/sbin/policy-rc.d ]]; then
   chmod 0755 /usr/sbin/policy-rc.d
   policy_created=true
 fi
-export DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=l
-apt-get update
+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 "${packages[@]}"
+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 mstpd
+    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 gcc make libc6-dev autoconf automake libtool pkg-config
+    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"

+ 8 - 1
update_helpers.go

@@ -16,6 +16,9 @@ var updateHelper []byte
 //go:embed scripts/release-version.py
 var versionHelper []byte
 
+//go:embed scripts/install-dependencies.sh
+var dependenciesHelper []byte
+
 // RefreshUpdateHelpers refreshes an existing installation, without creating one
 // when the server is run from a development checkout.
 func RefreshUpdateHelpers(dir string) error {
@@ -27,7 +30,11 @@ func RefreshUpdateHelpers(dir string) error {
 	for _, helper := range []struct {
 		name string
 		data []byte
-	}{{"debtik-update", updateHelper}, {"debtik-release-version", versionHelper}} {
+	}{
+		{"debtik-update", updateHelper},
+		{"debtik-release-version", versionHelper},
+		{"debtik-install-dependencies", dependenciesHelper},
+	} {
 		if err := writeHelper(dir, helper.name, helper.data); err != nil {
 			return err
 		}

+ 5 - 1
update_helpers_test.go

@@ -27,7 +27,11 @@ func TestRefreshLegacyUpdater(t *testing.T) {
 	if err = RefreshUpdateHelpers(dir); err != nil {
 		t.Fatal(err)
 	}
-	for name, want := range map[string]string{"debtik-update": string(updateHelper), "debtik-release-version": string(versionHelper)} {
+	for name, want := range map[string]string{
+		"debtik-update":               string(updateHelper),
+		"debtik-release-version":      string(versionHelper),
+		"debtik-install-dependencies": string(dependenciesHelper),
+	} {
 		got, err := os.ReadFile(filepath.Join(dir, name))
 		if err != nil || string(got) != want {
 			t.Fatalf("%s was not refreshed: %v", name, err)