From a60fa03d7fc7c9d2fece99b7b3298637de6fac4c Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 22 Apr 2026 16:18:16 +0100 Subject: [PATCH] Route operator updates through the local signed helper --- README.md | 15 ++++- docs/AUTO_UPDATE.md | 10 ++-- docs/DOCKER.md | 10 ++-- docs/FAQ.md | 12 +--- docs/INSTALL.md | 32 +++++++--- docs/UPGRADE_v5.md | 9 ++- docs/UPGRADE_v6.md | 9 ++- docs/operations/AUTO_UPDATE.md | 5 +- .../subsystems/deployment-installability.md | 6 ++ install.sh | 58 ++++++++++--------- scripts/installtests/install_sh_test.go | 38 ++++++++++-- scripts/installtests/root_install_sh_test.go | 37 +++++++++++- .../release_promotion_policy_test.py | 2 + 13 files changed, 167 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 9aa7b1505..b36cd6125 100644 --- a/README.md +++ b/README.md @@ -67,10 +67,19 @@ Power-user shortcuts: ## ⚡ Quick Start ### Option 1: Proxmox LXC (Recommended) -Run this one-liner on your Proxmox host to create a lightweight LXC container: +Replace `vX.Y.Z` with the exact release tag you want, verify the signed installer, then run it on your Proxmox host: ```bash -curl -fsSL https://github.com/rcourtman/Pulse/releases/latest/download/install.sh | bash +export PULSE_VERSION=vX.Y.Z +curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh" +curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh.sshsig" +ssh-keygen -Y verify \ + -f <(printf '%s\n' 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDs21c5oPk2khrdHlsw1aZ9EJKoTsyalGzhb0hdwJrkV pulse-installer') \ + -I pulse-installer \ + -n pulse-install \ + -s install.sh.sshsig < install.sh +bash install.sh --version "${PULSE_VERSION}" +rm -f install.sh install.sh.sshsig ``` Note: this installs the Pulse **server**. Agent installs use the command generated in **Settings → Unified Agents → Installation commands** (served from `/install.sh` on your Pulse server). @@ -82,7 +91,7 @@ docker run -d \ -p 7655:7655 \ -v pulse_data:/data \ --restart unless-stopped \ - rcourtman/pulse:latest + rcourtman/pulse:vX.Y.Z ``` Access the dashboard at `http://:7655`. diff --git a/docs/AUTO_UPDATE.md b/docs/AUTO_UPDATE.md index 403dd16e0..09ada248e 100644 --- a/docs/AUTO_UPDATE.md +++ b/docs/AUTO_UPDATE.md @@ -74,7 +74,7 @@ Auto-update preferences are stored in `system.json` and edited via the UI. ```bash # Pull latest image -docker pull rcourtman/pulse:latest +docker pull rcourtman/pulse:vX.Y.Z # Restart container docker compose down && docker compose up -d @@ -85,18 +85,18 @@ If you use the legacy `docker-compose` binary, replace `docker compose` with `do ### ProxmoxVE LXC (Manual) ```bash -curl -fsSL https://github.com/rcourtman/Pulse/releases/latest/download/install.sh | bash +sudo /bin/update ``` -This script installs/updates the **Pulse server**. Agent updates use the `/install.sh` command generated in **Settings → Infrastructure → Install on a host**. +`/bin/update` is installed by the supported Pulse server installer and preserves the signed-installer trust chain. If your host does not have it yet, use the signed server-installer flow in [INSTALL.md](INSTALL.md). Agent updates still use the `/install.sh` command generated in **Settings → Infrastructure → Install on a host**. ### Systemd Service (Manual) ```bash -curl -fsSL https://github.com/rcourtman/Pulse/releases/latest/download/install.sh | bash +sudo /bin/update ``` -This script installs/updates the **Pulse server**. Agent updates use the `/install.sh` command generated in **Settings → Infrastructure → Install on a host**. +`/bin/update` is installed by the supported Pulse server installer and preserves the signed-installer trust chain. If your host does not have it yet, use the signed server-installer flow in [INSTALL.md](INSTALL.md). Agent updates still use the `/install.sh` command generated in **Settings → Infrastructure → Install on a host**. ### Source Build diff --git a/docs/DOCKER.md b/docs/DOCKER.md index 500c633b5..a5a23c413 100644 --- a/docs/DOCKER.md +++ b/docs/DOCKER.md @@ -10,7 +10,7 @@ docker run -d \ -p 7655:7655 \ -v pulse_data:/data \ --restart unless-stopped \ - rcourtman/pulse:latest + rcourtman/pulse:vX.Y.Z ``` Access at `http://:7655`. @@ -24,7 +24,7 @@ Create a `docker-compose.yml` file: ```yaml services: pulse: - image: rcourtman/pulse:latest + image: rcourtman/pulse:vX.Y.Z container_name: pulse restart: unless-stopped ports: @@ -86,10 +86,10 @@ services: ## 🔄 Updates -To update Pulse to the latest version: +To update Pulse to a specific release tag: ```bash -docker pull rcourtman/pulse:latest +docker pull rcourtman/pulse:vX.Y.Z docker stop pulse docker rm pulse # Re-run your docker run command @@ -168,7 +168,7 @@ Pulse provides granular control over update features via environment variables o ```yaml services: pulse: - image: rcourtman/pulse:latest + image: rcourtman/pulse:vX.Y.Z environment: - PULSE_DISABLE_DOCKER_UPDATE_ACTIONS=true ``` diff --git a/docs/FAQ.md b/docs/FAQ.md index ba7d77079..8c57ff730 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -3,19 +3,11 @@ ## 🛠️ Installation & Setup ### What's the easiest way to install? -If you run Proxmox VE, use the official LXC installer (recommended): - -```bash -curl -fsSL https://github.com/rcourtman/Pulse/releases/latest/download/install.sh | bash -``` - -Note: this installs the Pulse **server**. Agent installs use the command from **Settings → Infrastructure → Install on a host** (served from `/install.sh` on your Pulse server). +If you run Proxmox VE, use the signed LXC installer flow in [INSTALL.md](INSTALL.md) and replace `vX.Y.Z` with the exact release tag you want. If you prefer Docker: -```bash -docker run -d --name pulse -p 7655:7655 -v pulse_data:/data rcourtman/pulse:latest -``` +Use a pinned image tag such as `rcourtman/pulse:vX.Y.Z` instead of `:latest`. See [INSTALL.md](INSTALL.md) for all options (Docker Compose, Kubernetes, systemd). diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 88a970d93..26dae8dc9 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -7,10 +7,19 @@ Pulse offers flexible installation options from Docker to enterprise-ready Kuber ### Proxmox VE (LXC installer) If you run Proxmox VE, the easiest and most “Pulse-native” deployment is the official installer which creates and configures a lightweight LXC container. -Run this on your Proxmox host: +Replace `vX.Y.Z` with the exact release tag you want, then run this on your Proxmox host: ```bash -curl -fsSL https://github.com/rcourtman/Pulse/releases/latest/download/install.sh | bash +export PULSE_VERSION=vX.Y.Z +curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh" +curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh.sshsig" +ssh-keygen -Y verify \ + -f <(printf '%s\n' 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDs21c5oPk2khrdHlsw1aZ9EJKoTsyalGzhb0hdwJrkV pulse-installer') \ + -I pulse-installer \ + -n pulse-install \ + -s install.sh.sshsig < install.sh +bash install.sh --version "${PULSE_VERSION}" +rm -f install.sh install.sh.sshsig ``` > **Note**: The GitHub `install.sh` is the **server** installer. The agent installer is served from your Pulse server at `/install.sh` (see **Settings → Infrastructure → Install on a host**). @@ -24,7 +33,7 @@ docker run -d \ -p 7655:7655 \ -v pulse_data:/data \ --restart unless-stopped \ - rcourtman/pulse:latest + rcourtman/pulse:vX.Y.Z ``` ### Docker Compose @@ -33,7 +42,7 @@ Create a `docker-compose.yml` file: ```yaml services: pulse: - image: rcourtman/pulse:latest + image: rcourtman/pulse:vX.Y.Z container_name: pulse restart: unless-stopped ports: @@ -71,7 +80,16 @@ See [KUBERNETES.md](KUBERNETES.md) for ingress and persistence configuration. For Linux servers (VM or bare metal), use the official installer: ```bash -curl -fsSL https://github.com/rcourtman/Pulse/releases/latest/download/install.sh | sudo bash +export PULSE_VERSION=vX.Y.Z +curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh" +curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh.sshsig" +ssh-keygen -Y verify \ + -f <(printf '%s\n' 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDs21c5oPk2khrdHlsw1aZ9EJKoTsyalGzhb0hdwJrkV pulse-installer') \ + -I pulse-installer \ + -n pulse-install \ + -s install.sh.sshsig < install.sh +sudo bash install.sh --version "${PULSE_VERSION}" +rm -f install.sh install.sh.sshsig ``` > **Note**: This installs the Pulse server. Use the `/install.sh` endpoint from **Settings → Infrastructure → Install on a host** for installing `pulse-agent` on monitored hosts. @@ -149,9 +167,9 @@ Pulse can self-update to the latest stable version. | Platform | Command | |----------|---------| -| **Docker** | `docker pull rcourtman/pulse:latest && docker restart pulse` | +| **Docker** | `docker pull rcourtman/pulse:vX.Y.Z && docker restart pulse` | | **Kubernetes** | `helm repo update && helm upgrade pulse pulse/pulse -n pulse` | -| **Systemd** | Re-download binary and restart service | +| **Systemd / Proxmox LXC** | `sudo /bin/update` | ### Rollback If an update causes issues on systemd installations, backups are created automatically during the update process. diff --git a/docs/UPGRADE_v5.md b/docs/UPGRADE_v5.md index cc831b8ff..7695c4102 100644 --- a/docs/UPGRADE_v5.md +++ b/docs/UPGRADE_v5.md @@ -16,19 +16,18 @@ Preferred path: - **Settings → System → Updates** -If you prefer CLI, use the official installer for the target version: +If you prefer CLI, use the installed update helper for the target version: ```bash -curl -fsSL https://github.com/rcourtman/Pulse/releases/latest/download/install.sh | \ - sudo bash -s -- --version vX.Y.Z +sudo /bin/update --version vX.Y.Z ``` -This installer updates the **Pulse server**. Agent updates use the `/install.sh` command generated in **Settings → Infrastructure → Install on a host**. +`/bin/update` is installed by the supported systemd and Proxmox LXC server installer. If your host does not have it yet, follow the signed server-installer flow in [INSTALL.md](INSTALL.md). Agent updates still use the `/install.sh` command generated in **Settings → Infrastructure → Install on a host**. ### Docker ```bash -docker pull rcourtman/pulse:latest +docker pull rcourtman/pulse:vX.Y.Z docker compose up -d ``` diff --git a/docs/UPGRADE_v6.md b/docs/UPGRADE_v6.md index 157d81034..0114d5964 100644 --- a/docs/UPGRADE_v6.md +++ b/docs/UPGRADE_v6.md @@ -21,19 +21,18 @@ Preferred path: - **Settings → System → Updates** -If you prefer CLI, use the official installer for the target version: +If you prefer CLI, use the installed update helper for the target version: ```bash -curl -fsSL https://github.com/rcourtman/Pulse/releases/latest/download/install.sh | \ - sudo bash -s -- --version vX.Y.Z +sudo /bin/update --version vX.Y.Z ``` -This installer updates the **Pulse server**. Agent updates use the `/install.sh` command generated in **Settings → Infrastructure → Install on a host**. +`/bin/update` is installed by the supported systemd and Proxmox LXC server installer. If your host does not have it yet, follow the signed server-installer flow in [INSTALL.md](INSTALL.md). Agent updates still use the `/install.sh` command generated in **Settings → Infrastructure → Install on a host**. ### Docker ```bash -docker pull rcourtman/pulse:latest +docker pull rcourtman/pulse:vX.Y.Z docker compose up -d ``` diff --git a/docs/operations/AUTO_UPDATE.md b/docs/operations/AUTO_UPDATE.md index 5f4d76f67..778db69b6 100644 --- a/docs/operations/AUTO_UPDATE.md +++ b/docs/operations/AUTO_UPDATE.md @@ -46,7 +46,6 @@ If an update fails: 2. The timer script keeps a temporary backup under `/tmp/pulse-backup-` during the update; failures auto-restore from that backup and then clean it up. 3. If you need to pin a specific version, re-run the installer with a version: ```bash - curl -fsSL https://github.com/rcourtman/Pulse/releases/latest/download/install.sh | \ - sudo bash -s -- --version vX.Y.Z + sudo /bin/update --version vX.Y.Z ``` - This installer updates the **Pulse server**. Agent updates use the `/install.sh` command generated in **Settings → Infrastructure → Install on a host**. + `/bin/update` is installed by the supported Pulse server installer. If your host does not have it yet, use the signed server-installer flow in [INSTALL.md](../INSTALL.md). Agent updates still use the `/install.sh` command generated in **Settings → Infrastructure → Install on a host**. diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index 871f516c0..bf8b25ab2 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -513,6 +513,12 @@ root `install.sh`, its generated update helper, and installer scripts against the pinned release `.sshsig` sidecars before execution, rather than treating same-origin checksum files as a sufficient trust anchor. +That same boundary also owns operator-facing management entry points for +existing self-hosted installs: the installer's printed update/reset/uninstall +commands and the active install or upgrade docs must route supported +systemd/LXC servers through the installed local update helper (`/bin/update` +or the service-scoped equivalent), rather than telling operators to pipe a +freshly downloaded installer into `bash`. The local dev-runtime launcher and dependency manifest floor now sit on that same installability boundary. `scripts/hot-dev.sh` and `scripts/hot-dev-bg.sh` are the canonical owned entry diff --git a/install.sh b/install.sh index 8112435d6..4c87054e8 100755 --- a/install.sh +++ b/install.sh @@ -3347,17 +3347,37 @@ installer_env=( "PULSE_UPDATE_SERVICE_PATH=\$PULSE_UPDATE_SERVICE_PATH" "PULSE_UPDATE_TIMER_PATH=\$PULSE_UPDATE_TIMER_PATH" ) -if [[ -f "\$MARKER_FILE" ]]; then - branch=\$(tr -d '\r\n' <"\$MARKER_FILE" 2>/dev/null || true) - if [[ -n "\$branch" ]]; then - extra_args+=(--source "\$branch") - fi -elif [[ -f "\${CONFIG_DIR}/system.json" ]]; then - configured_channel=\$(grep -o '"updateChannel"[[:space:]]*:[[:space:]]*"[^"]*"' "\${CONFIG_DIR}/system.json" 2>/dev/null | sed 's/.*"\([^"]*\)"$/\1/' || true) - if [[ "\$configured_channel" == "rc" ]]; then - extra_args+=(--rc) +helper_args=() +if [[ \$# -gt 0 ]]; then + helper_args=("\$@") +fi +auto_selector_allowed=true +if [[ \${#helper_args[@]} -gt 0 ]]; then + for helper_arg in "\${helper_args[@]}"; do + case "\$helper_arg" in + -h|--help|--uninstall|--version|--rc|--pre|--stable|--source|--from-source|--branch|--archive|--archive=*) + auto_selector_allowed=false + break + ;; + esac + done +fi +if [[ "\$auto_selector_allowed" == "true" ]]; then + if [[ -f "\$MARKER_FILE" ]]; then + branch=\$(tr -d '\r\n' <"\$MARKER_FILE" 2>/dev/null || true) + if [[ -n "\$branch" ]]; then + extra_args+=(--source "\$branch") + fi + elif [[ -f "\${CONFIG_DIR}/system.json" ]]; then + configured_channel=\$(grep -o '"updateChannel"[[:space:]]*:[[:space:]]*"[^"]*"' "\${CONFIG_DIR}/system.json" 2>/dev/null | sed 's/.*"\([^"]*\)"$/\1/' || true) + if [[ "\$configured_channel" == "rc" ]]; then + extra_args+=(--rc) + fi fi fi +if [[ \${#helper_args[@]} -gt 0 ]]; then + extra_args+=("\${helper_args[@]}") +fi echo "Updating Pulse..." tmp_installer=\$(mktemp /tmp/pulse-update-installer.XXXXXX) @@ -3786,9 +3806,8 @@ print_completion() { build_printed_management_command() { local action=$1 - local download_cmd="curl -sSL https://github.com/$GITHUB_REPO/releases/latest/download/install.sh |" + local update_helper_path="${UPDATE_HELPER_PATH:-${PULSE_UPDATE_HELPER_PATH:-/bin/update}}" local -a args=() - local -a env_vars=() case "$action" in update) @@ -3810,28 +3829,13 @@ build_printed_management_command() { args=(--rc "${args[@]}") fi - if [[ "$SERVICE_NAME_EXPLICIT" == "true" ]]; then - env_vars+=("PULSE_SERVICE_NAME=$SERVICE_NAME") - fi - - printf '%s' "$download_cmd" - local env_var - if [[ ${#env_vars[@]} -gt 0 ]]; then - printf ' env' - for env_var in "${env_vars[@]}"; do - printf ' %q' "$env_var" - done - printf ' bash' - else - printf ' bash' - fi + printf '%q' "$update_helper_path" if [[ ${#args[@]} -eq 0 ]]; then printf '\n' return 0 fi - printf ' -s --' local arg for arg in "${args[@]}"; do printf ' %q' "$arg" diff --git a/scripts/installtests/install_sh_test.go b/scripts/installtests/install_sh_test.go index b71888660..daa417067 100644 --- a/scripts/installtests/install_sh_test.go +++ b/scripts/installtests/install_sh_test.go @@ -1138,6 +1138,15 @@ func TestSetupUpdateCommandHonorsRCChannelAndCustomPaths(t *testing.T) { if !strings.Contains(got, `CONFIG_DIR=/etc/pulse`) { t.Fatalf("update helper missing config dir logic:\n%s", got) } + if !strings.Contains(got, `helper_args=()`) || !strings.Contains(got, `helper_args=("$@")`) { + t.Fatalf("update helper missing passthrough helper args:\n%s", got) + } + if !strings.Contains(got, `-h|--help|--uninstall|--version|--rc|--pre|--stable|--source|--from-source|--branch|--archive|--archive=*)`) { + t.Fatalf("update helper missing auto-selector guard for explicit flags:\n%s", got) + } + if !strings.Contains(got, `extra_args+=("${helper_args[@]}")`) { + t.Fatalf("update helper missing forwarded helper args:\n%s", got) + } if !strings.Contains(got, `extra_args+=(--rc)`) { t.Fatalf("update helper missing rc channel forwarding:\n%s", got) } @@ -1925,10 +1934,10 @@ func TestBuildPrintedManagementCommandPreservesRCChannel(t *testing.T) { if len(lines) != 3 { t.Fatalf("expected 3 commands, got %d:\n%s", len(lines), out) } - if !strings.Contains(lines[0], "| bash -s -- --rc") { + if got := lines[0]; got != "/bin/update --rc" { t.Fatalf("update command missing rc flag: %s", lines[0]) } - if !strings.Contains(lines[1], "| bash -s -- --rc --reset") { + if got := lines[1]; got != "/bin/update --rc --reset" { t.Fatalf("reset command missing rc flag: %s", lines[1]) } if strings.Contains(lines[2], "--rc") { @@ -1956,14 +1965,35 @@ func TestBuildPrintedManagementCommandPreservesForcedVersion(t *testing.T) { if len(lines) != 2 { t.Fatalf("expected 2 commands, got %d:\n%s", len(lines), out) } - if !strings.Contains(lines[0], "| bash -s -- --version v1.2.3") { + if got := lines[0]; got != "/bin/update --version v1.2.3" { t.Fatalf("update command missing version pin: %s", lines[0]) } - if !strings.Contains(lines[1], "| bash -s -- --version v1.2.3 --reset") { + if got := lines[1]; got != "/bin/update --version v1.2.3 --reset" { t.Fatalf("reset command missing version pin: %s", lines[1]) } } +func TestBuildPrintedManagementCommandUsesConfiguredHelperPath(t *testing.T) { + script := ` + GITHUB_REPO="rcourtman/Pulse" + FORCE_VERSION="" + FORCE_CHANNEL="" + UPDATE_CHANNEL="" + UPDATE_HELPER_PATH="/usr/local/bin/update-pulse-preview" +` + extractRootInstallShellFunction(t, "build_printed_management_command") + ` + build_printed_management_command update + ` + + out, err := exec.Command("bash", "-c", script).CombinedOutput() + if err != nil { + t.Fatalf("bash: %v\n%s", err, out) + } + + if got := strings.TrimSpace(string(out)); got != "/usr/local/bin/update-pulse-preview" { + t.Fatalf("printed command = %q, want configured helper path", got) + } +} + func TestSelectedUpdateChannelTreatsPrereleaseVersionAsRC(t *testing.T) { script := ` FORCE_CHANNEL="" diff --git a/scripts/installtests/root_install_sh_test.go b/scripts/installtests/root_install_sh_test.go index 7a9efa0e8..4451d6645 100644 --- a/scripts/installtests/root_install_sh_test.go +++ b/scripts/installtests/root_install_sh_test.go @@ -241,8 +241,8 @@ func TestRootInstallScriptSupportsInstanceScopedServerInstalls(t *testing.T) { `Environment="PULSE_INSTALL_DIR=$install_dir"`, `Environment="PULSE_CONFIG_DIR=$config_dir"`, `Environment="PULSE_UPDATE_TIMER_UNIT=$update_timer_unit"`, - `printf ' env'`, - `env_vars+=("PULSE_SERVICE_NAME=$SERVICE_NAME")`, + `local update_helper_path="${UPDATE_HELPER_PATH:-${PULSE_UPDATE_HELPER_PATH:-/bin/update}}"`, + `printf '%q' "$update_helper_path"`, } for _, needle := range required { if !strings.Contains(script, needle) { @@ -324,3 +324,36 @@ func TestPulseAutoUpdateScriptRequiresSignedInstallerDownloads(t *testing.T) { } } } + +func TestOperatorInstallDocsAvoidUnverifiedBootstrapAndFloatingImageTags(t *testing.T) { + files := []string{ + filepath.Join("..", "..", "README.md"), + filepath.Join("..", "..", "docs", "INSTALL.md"), + filepath.Join("..", "..", "docs", "UPGRADE_v6.md"), + filepath.Join("..", "..", "docs", "UPGRADE_v5.md"), + filepath.Join("..", "..", "docs", "DOCKER.md"), + filepath.Join("..", "..", "docs", "AUTO_UPDATE.md"), + filepath.Join("..", "..", "docs", "operations", "AUTO_UPDATE.md"), + filepath.Join("..", "..", "docs", "FAQ.md"), + } + forbidden := []string{ + `curl -fsSL https://github.com/rcourtman/Pulse/releases/latest/download/install.sh |`, + `curl -sL https://github.com/rcourtman/Pulse/releases/latest/download/install.sh |`, + `rcourtman/pulse:latest`, + `docker pull rcourtman/pulse:latest`, + `image: rcourtman/pulse:latest`, + } + + for _, path := range files { + content, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read %s: %v", path, err) + } + text := string(content) + for _, needle := range forbidden { + if strings.Contains(text, needle) { + t.Fatalf("%s preserved insecure operator guidance: %s", path, needle) + } + } + } +} diff --git a/scripts/release_control/release_promotion_policy_test.py b/scripts/release_control/release_promotion_policy_test.py index e4cdb6ad7..15cb7ba97 100644 --- a/scripts/release_control/release_promotion_policy_test.py +++ b/scripts/release_control/release_promotion_policy_test.py @@ -157,6 +157,8 @@ class ReleasePromotionPolicyTest(unittest.TestCase): def test_upgrade_guide_points_at_current_rc_support_pack(self) -> None: upgrade_guide = read("docs/UPGRADE_v6.md") current_version = read("VERSION").strip() + self.assertIn("sudo /bin/update --version vX.Y.Z", upgrade_guide) + self.assertIn("follow the signed server-installer flow in [INSTALL.md](INSTALL.md)", upgrade_guide) if current_version == "6.0.0": self.assertIn("docs/releases/RELEASE_NOTES_v6.md", upgrade_guide) self.assertIn("docs/releases/V6_CHANGELOG.md", upgrade_guide)