diff --git a/scripts/openhop-update b/scripts/openhop-update index c2e6460..326e318 100755 --- a/scripts/openhop-update +++ b/scripts/openhop-update @@ -50,6 +50,10 @@ echo "==> Running openHop Repeater upgrade" cd "$REPO_DIR" OPENHOP_UPGRADE_REF="$BRANCH" TERM="${TERM:-xterm}" bash manage.sh upgrade +# Keep the documented management copy in /opt aligned with the source used for +# this successful upgrade. +install -m 0755 "$REPO_DIR/manage.sh" /opt/openhop_repeater/manage.sh + # Refresh the installed helper for the next run without replacing this script # until all update steps have completed successfully. install -m 0755 "$REPO_DIR/scripts/openhop-update" /usr/local/bin/openhop-update.new diff --git a/scripts/proxmox-install.sh b/scripts/proxmox-install.sh index 7826e65..20840dd 100644 --- a/scripts/proxmox-install.sh +++ b/scripts/proxmox-install.sh @@ -36,6 +36,16 @@ msg_ok() { echo -e " ${GN}✓${CL} ${1}"; } msg_warn() { echo -e " ${YW}⚠${CL} ${1}"; } msg_error() { echo -e " ${RD}✗${CL} ${1}"; } +is_safe_git_ref() { + local ref="${1:-}" + [[ "$ref" =~ ^[a-zA-Z0-9][a-zA-Z0-9._/-]{0,79}$ ]] \ + && [[ "$ref" != *..* ]] \ + && [[ "$ref" != *//* ]] \ + && [[ "$ref" != */ ]] \ + && [[ "$ref" != *. ]] \ + && [[ "$ref" != *.lock ]] +} + header() { clear echo -e "${BLD}" @@ -105,6 +115,10 @@ AVAILABLE_STORAGES=$(pvesm status -content rootdir 2>/dev/null | awk 'NR>1 {prin echo " Available storages: ${AVAILABLE_STORAGES}" read -p " Storage [${CT_STORAGE}]: " -r input; CT_STORAGE="${input:-$CT_STORAGE}" read -p " Git branch [${BRANCH}]: " -r input; BRANCH="${input:-$BRANCH}" +if ! is_safe_git_ref "$BRANCH"; then + msg_error "Invalid Git branch: ${BRANCH}" + exit 1 +fi read -p " Install host-side CH341 udev rule? [y/N]: " -r input [[ "${input:-n}" =~ ^[Yy]([Ee][Ss])?$ ]] && INSTALL_CH341_UDEV=true read -p " Install optional openHop Console WebUI? [y/N]: " -r input @@ -192,10 +206,18 @@ fi msg_info "Starting container..." pct start "$CTID" sleep 3 +NETWORK_READY=false for _ in $(seq 1 30); do - pct exec "$CTID" -- ping -c1 -W1 8.8.8.8 &>/dev/null && break + if pct exec "$CTID" -- ping -c1 -W1 8.8.8.8 &>/dev/null; then + NETWORK_READY=true + break + fi sleep 1 done +if [[ "$NETWORK_READY" != "true" ]]; then + msg_error "Container network did not become ready after 30 seconds" + exit 1 +fi msg_ok "Container running with network" # ── Bootstrap: install git, clone repo ──────────────────────────────────── @@ -246,7 +268,7 @@ MOTD msg_ok "curl/git installed, locale fixed, console auto-login enabled" msg_info "Cloning openhop-repeater (branch: ${BRANCH})..." -pct exec "$CTID" -- bash -c "git clone --branch ${BRANCH} ${REPO} /root/openhop-repeater" +pct exec "$CTID" -- git clone --branch "$BRANCH" "$REPO" /root/openhop-repeater msg_ok "Repository cloned" msg_info "Installing LXC update command..." diff --git a/tests/test_proxmox_installer.py b/tests/test_proxmox_installer.py index 968ed9e..a5d29a0 100644 --- a/tests/test_proxmox_installer.py +++ b/tests/test_proxmox_installer.py @@ -20,13 +20,30 @@ def test_ch341_host_rule_is_opt_in() -> None: prompt = "Install host-side CH341 udev rule? [y/N]" rule = "/etc/udev/rules.d/99-ch341.rules" - conditional = script.index('if [[ "$INSTALL_CH341_UDEV" == "true" ]]; then') + rule_section = script.split("# ── Host udev rule", 1)[1].split("# ── Start container", 1)[0] assert prompt in script - assert script.index(rule) > conditional + assert 'if [[ "$INSTALL_CH341_UDEV" == "true" ]]; then' in rule_section + assert rule in rule_section assert "CH341 host udev rule: ${CH341_UDEV_SUMMARY}" in script +def test_git_branch_is_validated_and_not_evaluated_by_a_shell() -> None: + script = INSTALLER.read_text() + + assert 'is_safe_git_ref "$BRANCH"' in script + assert 'pct exec "$CTID" -- git clone --branch "$BRANCH" "$REPO"' in script + assert 'bash -c "git clone --branch ${BRANCH}' not in script + + +def test_installer_fails_when_container_network_never_becomes_ready() -> None: + script = INSTALLER.read_text() + + assert "NETWORK_READY=false" in script + assert "NETWORK_READY=true" in script + assert "Container network did not become ready after 30 seconds" in script + + def test_console_is_opt_in_installed_after_repeater_and_not_enabled() -> None: script = INSTALLER.read_text() @@ -62,6 +79,7 @@ def test_update_command_updates_apt_and_current_repeater_branch() -> None: assert 'git -C "$REPO_DIR" pull --ff-only origin "$BRANCH"' in updater assert 'OPENHOP_UPGRADE_REF="$BRANCH"' in updater assert "bash manage.sh upgrade" in updater + assert 'install -m 0755 "$REPO_DIR/manage.sh" /opt/openhop_repeater/manage.sh' in updater assert 'install -m 0755 "$REPO_DIR/scripts/openhop-update"' in updater