debtik-update 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. #!/usr/bin/env bash
  2. set -Eeuo pipefail
  3. umask 077
  4. export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
  5. if [[ -d /opt/debtik/go/bin ]]; then export PATH=/opt/debtik/go/bin:$PATH; fi
  6. # Transient systemd units may omit HOME; Go needs explicit cache locations.
  7. export HOME=${HOME:-/root}
  8. export GOPATH=${GOPATH:-/var/cache/debtik/go}
  9. export GOCACHE=${GOCACHE:-/var/cache/debtik/go-build}
  10. mkdir -p "$GOPATH" "$GOCACHE"
  11. base=/var/lib/debtik/update
  12. mkdir -p "$base"
  13. exec 9>"$base/lock"
  14. flock -n 9 || { echo 'An update is already running' >&2; exit 1; }
  15. exec >>"$base/update.log" 2>&1
  16. state() { printf '%s\n' "$1" >"$base/status"; printf '[%s] %s\n' "$(date -u +%FT%TZ)" "$1"; }
  17. cleanup_veth() {
  18. python3 - <<'PY'
  19. import json,subprocess,pathlib
  20. p=pathlib.Path('/etc/debtik/vpn/aerotun_instances.json')
  21. if p.exists():
  22. for tunnel in json.loads(p.read_text()):
  23. name=tunnel['name']
  24. result=subprocess.run(['ip','-j','-d','link','show','dev',name],capture_output=True,text=True)
  25. if result.returncode:
  26. continue
  27. links=json.loads(result.stdout)
  28. if len(links)!=1:raise RuntimeError('Ambiguous AeroTUN interface')
  29. link=links[0]
  30. if link.get('linkinfo',{}).get('info_kind')=='veth':
  31. if link.get('link')!=name+'_ep':raise RuntimeError('Unexpected veth peer')
  32. subprocess.run(['ip','link','delete',name],check=True)
  33. PY
  34. }
  35. restore() {
  36. state rolling_back
  37. systemctl stop debtik.service || systemctl stop proxmox-router.service || true
  38. cleanup_veth || true
  39. for name in debtik debtik-cli; do
  40. test -f "$base/previous/$name"
  41. cp -p "$base/previous/$name" "/usr/local/bin/$name.rollback"
  42. mv -f "/usr/local/bin/$name.rollback" "/usr/local/bin/$name"
  43. done
  44. ln -sf /usr/local/bin/debtik /usr/local/bin/proxmox-router
  45. for name in debtik-update debtik-release-version debtik-install-dependencies; do
  46. 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
  47. done
  48. if [[ -f "$base/previous_commit" ]]; then cp -p "$base/previous_commit" "$base/installed_commit"; fi
  49. if [[ -f "$base/previous_version" ]]; then cp -p "$base/previous_version" "$base/installed_version"; fi
  50. if [[ -f "$base/previous/go.mod" ]]; then cp -p "$base/previous/go.mod" "$base/go.mod"; fi
  51. if [[ -f "$base/previous/debtik.service" ]]; then
  52. install -m 0644 "$base/previous/debtik.service" /etc/systemd/system/debtik.service
  53. systemctl daemon-reload
  54. fi
  55. systemctl start debtik.service
  56. systemctl is-active --quiet debtik.service
  57. if [[ -f "$base/previous/iptables.save" ]]; then iptables-restore -c < "$base/previous/iptables.save"; fi
  58. if [[ -s "$base/previous/ip6tables.save" ]]; then ip6tables-restore -c < "$base/previous/ip6tables.save"; fi
  59. state rolled_back
  60. }
  61. if [[ ${1:-} == rollback ]]; then restore; exit; fi
  62. [[ $# == 4 && $1 == install ]] || { echo 'Usage: debtik-update install URL BRANCH | rollback' >&2; exit 2; }
  63. repo=$2 branch=$3 pin=$4
  64. git_opts=()
  65. if [[ -n $pin ]]; then
  66. [[ $pin =~ ^sha256//[A-Za-z0-9+/]{43}=$ ]] || exit 2
  67. git_opts=(-c http.sslVerify=false -c "http.pinnedpubkey=$pin")
  68. fi
  69. [[ $repo == http://* || $repo == https://* || $repo == ssh://* ]] || exit 2
  70. [[ $branch =~ ^[A-Za-z0-9][A-Za-z0-9._/-]{0,127}$ && $branch != *..* ]] || exit 2
  71. work=$(mktemp -d "$base/work.XXXXXXXX")
  72. trap 'rm -rf "$work"' EXIT
  73. trap 'state failed' ERR
  74. state fetching
  75. version_tool=/usr/local/libexec/debtik-release-version
  76. version=$(GIT_TERMINAL_PROMPT=0 git "${git_opts[@]}" ls-remote --tags "$repo" 'refs/tags/v*' | "$version_tool" latest)
  77. installed_version=$(cat "$base/installed_version")
  78. if ! "$version_tool" greater "$version" "$installed_version"; then
  79. state failed
  80. echo "Update refused: available version $version must be greater than installed version $installed_version"
  81. exit 1
  82. fi
  83. GIT_TERMINAL_PROMPT=0 git -c protocol.file.allow=never "${git_opts[@]}" clone --single-branch --branch "v$version" -- "$repo" "$work/source"
  84. cd "$work/source"
  85. commit=$(git rev-parse HEAD)
  86. [[ $(cat VERSION) == "$version" ]] || { state failed; echo 'Release tag and VERSION do not match'; exit 1; }
  87. bash -n scripts/debtik-update
  88. bash -n scripts/install-dependencies.sh
  89. bash -n install.sh
  90. python3 -c 'import pathlib; compile(pathlib.Path("scripts/release-version.py").read_text(),"release-version.py","exec")'
  91. state dependencies
  92. bash scripts/install-dependencies.sh
  93. state building
  94. go_cmd=$(command -v go || true)
  95. if [[ -x /opt/debtik/go/bin/go ]]; then go_cmd=/opt/debtik/go/bin/go; fi
  96. [[ -n $go_cmd ]] || { state failed; echo 'Go is not installed'; exit 1; }
  97. CGO_ENABLED=0 "$go_cmd" build -trimpath -ldflags "-X debtik/internal/api.BuildCommit=$commit" -o "$work/debtik" ./cmd/server
  98. CGO_ENABLED=0 "$go_cmd" build -trimpath -o "$work/debtik-cli" ./cmd/debtik-cli
  99. install -d -m 0700 "$base/previous"
  100. rm -f "$base/previous/debtik.service"
  101. if [[ -f /etc/systemd/system/debtik.service ]]; then cp -p /etc/systemd/system/debtik.service "$base/previous/debtik.service"; fi
  102. for name in debtik debtik-cli; do cp -p "/usr/local/bin/$name" "$base/previous/$name"; done
  103. for name in debtik-update debtik-release-version debtik-install-dependencies debtik-ifnames; do
  104. if [[ -f "/usr/local/libexec/$name" ]]; then cp -p "/usr/local/libexec/$name" "$base/previous/$name"; fi
  105. done
  106. cp -p "$base/installed_version" "$base/previous_version"
  107. if [[ -f "$base/installed_commit" ]]; then cp -p "$base/installed_commit" "$base/previous_commit"; fi
  108. if [[ -f "$base/go.mod" ]]; then cp -p "$base/go.mod" "$base/previous/go.mod"; fi
  109. install -m 0644 go.mod "$base/go.mod"
  110. printf '%s\n' "$commit" > "$base/candidate_commit"
  111. iptables-save -c > "$base/previous/iptables.save"
  112. ip6tables-save -c > "$base/previous/ip6tables.save"
  113. state installing
  114. rollback_on_error() { trap - ERR; restore || state failed; exit 1; }
  115. trap rollback_on_error ERR
  116. if systemctl is-active --quiet proxmox-router.service; then
  117. systemctl stop proxmox-router.service || true
  118. systemctl disable proxmox-router.service || true
  119. fi
  120. systemctl stop debtik.service || true
  121. cleanup_veth
  122. for name in debtik debtik-cli; do
  123. install -m 0755 "$work/$name" "/usr/local/bin/$name.next"
  124. mv -f "/usr/local/bin/$name.next" "/usr/local/bin/$name"
  125. done
  126. ln -sf /usr/local/bin/debtik /usr/local/bin/proxmox-router
  127. install -m 0750 scripts/debtik-update /usr/local/libexec/debtik-update.next
  128. install -m 0750 scripts/release-version.py /usr/local/libexec/debtik-release-version.next
  129. install -m 0750 scripts/install-dependencies.sh /usr/local/libexec/debtik-install-dependencies.next
  130. install -m 0755 scripts/debtik-ifnames /usr/local/libexec/debtik-ifnames.next
  131. mv -f /usr/local/libexec/debtik-update.next /usr/local/libexec/debtik-update
  132. mv -f /usr/local/libexec/debtik-release-version.next /usr/local/libexec/debtik-release-version
  133. mv -f /usr/local/libexec/debtik-install-dependencies.next /usr/local/libexec/debtik-install-dependencies
  134. mv -f /usr/local/libexec/debtik-ifnames.next /usr/local/libexec/debtik-ifnames
  135. mkdir -p /etc/network/if-pre-up.d
  136. ln -sf /usr/local/libexec/debtik-ifnames /etc/network/if-pre-up.d/debtik-ifnames
  137. # Migrate legacy /etc/proxmox-router config directory to /etc/debtik
  138. if [[ -d /etc/proxmox-router && ! -L /etc/proxmox-router ]]; then
  139. install -d -m 0700 /etc/debtik
  140. cp -a /etc/proxmox-router/* /etc/debtik/ 2>/dev/null || true
  141. rm -rf /etc/proxmox-router
  142. ln -s /etc/debtik /etc/proxmox-router
  143. fi
  144. if [[ ! -e /etc/proxmox-router ]]; then
  145. install -d -m 0700 /etc/debtik
  146. ln -s /etc/debtik /etc/proxmox-router
  147. fi
  148. if [[ -f systemd/debtik.service ]]; then
  149. install -m 0644 systemd/debtik.service /etc/systemd/system/debtik.service
  150. if [[ -f systemd/debtik-ifnames.service ]]; then
  151. install -m 0644 systemd/debtik-ifnames.service /etc/systemd/system/debtik-ifnames.service
  152. fi
  153. if [[ -f /etc/systemd/system/proxmox-router.service.d/mstpd.conf && ! -f /etc/systemd/system/debtik.service.d/mstpd.conf ]]; then
  154. install -d -m 0755 /etc/systemd/system/debtik.service.d
  155. cp /etc/systemd/system/proxmox-router.service.d/mstpd.conf /etc/systemd/system/debtik.service.d/mstpd.conf
  156. fi
  157. systemctl daemon-reload
  158. systemctl enable debtik-ifnames.service || true
  159. systemctl enable debtik.service
  160. fi
  161. systemctl start debtik.service
  162. sleep 3
  163. systemctl is-active --quiet debtik.service
  164. printf '%s\n' "$commit" > "$base/installed_commit"
  165. printf '%s\n' "$version" > "$base/installed_version"
  166. state complete