Skip to content

Add Arch packages and proper Windows installers to the packaging pipeline - #2118

Merged
nullPointerEnjoyer merged 4 commits into
masterfrom
feature/arch-windows-packaging
Sep 17, 2026
Merged

nullPointerEnjoyer merged 4 commits into
masterfrom
feature/arch-windows-packaging

Conversation

@nullPointerEnjoyer

Copy link
Copy Markdown
Contributor

Summary

Extends the native packaging pipeline (deb/rpm from #2115) with:

Arch Linux (.pkg.tar.zst)packaging/arch/

  • Builder runs in a pinned archlinux:base container; makepkg (as a dedicated build user) repackages the prebuilt release binaries from PKGBUILD templates; install scriptlet applies the preset policy (Arch convention: enable, don't start)
  • Dependencies resolved from the binaries via pacman -F on the arch-matched x86_64 leg; the aarch64 leg repackages cross-target (Arch publishes no arm64 images) with a static fallback map, unstripped binaries and stub man pages — no qemu needed
  • namcap gate + fresh-container install smoke tests (arm64 skips the install step: pacman refuses foreign-arch packages)
  • Artifacts: Mintlayer_Node{,_GUI}_linux_<version>_<arch>.pkg.tar.zstrelease.yml's Mintlayer*/* glob picks them up unchanged
  • Not published to the AUR (registration is currently paused); users install with pacman -U directly (documented in packaging/README.md)

Windows (NSIS)build-tools/win/

  • Replaces the GUI-only create-nsis-script.ps1 with renderable templates (nsi/*.nsi.in + shared common.nsh macros) and a generator producing two installers:
    • Mintlayer_Node_win_<v>_Setup.exe (new): all CLI tools, optional PATH entry, optional mainnet service (sc.exe) + TCP firewall rule
    • GUI setup: all-users shortcuts (SetShellVarContext all), upgrade handling, and a fix for the broken ${SMPROGRAMS} constant carried over from the old script (Start Menu shortcuts never worked)
  • smoke-install.ps1: silent install → verify files/registry/PATH/--help → silent uninstall → verify clean, on the CI runner
  • release_windows.yml: gains workflow_dispatch dry-run + git-describe version fallback (parity with the Linux workflow)

Shared helperspackaging/common/lib.sh + packaging/images.env

  • 7-binary list, version validation and man-page generation single-sourced across the three builders and smoke tests
  • Image pins single-sourced (also fixes the fedora:latest vs fedora:44 drift between test-local.sh and CI)

Drive-by fixes (found by the review agents + local testing)

  • Version charset check rejected any suffixed version (1.4.1-rc1) in deb/rpm/arch due to a bash glob range quirk (+-a parsed as a range)
  • namcap gate keyed on its E: output (namcap always exits 0, even on errors)
  • test-local.sh: run_step/summary block was only defined in the --skip-build branch — default (building) runs could never reach it
  • NSIS PATH rewrite: length-gated (stock NSIS strings cap at 1024 chars — a long machine PATH would be silently truncated), previous value backed up, no ;; residue on removal
  • Checkout hardening: persist-credentials: false + explicit read-only permissions on the packaging jobs

Testing

Local end-to-end (docker, real 1.4.x binaries from the deb-container build):

  • all six artifacts build + lint (lintian/rpmlint/namcap) + install-smoke + pass the artifact-name gate
  • Arch x86_64 native and aarch64 cross-target legs; both installers compile via the actual PowerShell generator + makensis; shellcheck/PowerShell-parse/YAML clean

CI plan: workflow_dispatch dry-runs of both release workflows on this branch will exercise the real matrix (arm64 binaries, Windows silent install) before the 1.4.1 tag.

…line

Arch Linux (.pkg.tar.zst), mirroring the deb/rpm builders:
- packaging/arch/build.sh runs in a pinned archlinux:base container; makepkg
  as a dedicated build user repackages the prebuilt release binaries
  (PKGBUILD templates, install scriptlet applies the preset policy)
- dependencies resolved from the binaries via pacman -F on the arch-matched
  x86_64 leg; the aarch64 leg repackages cross-target (Arch publishes no
  arm64 images) with a static fallback map, unstripped binaries and stub man
  pages
- namcap gate (keyed on its ' E: ' output: namcap always exits 0) and
  fresh-container install smoke tests; arm64 skips the install smoke since
  pacman refuses foreign-arch packages
- release_linux.yml builds + uploads both arches; test-local.sh replicates

Windows (NSIS), replacing the GUI-only setup.exe:
- renderable templates (build-tools/win/nsi/*.nsi.in + common.nsh macros)
  and create-nsis-installers.ps1 produce two installers: a new node Setup
  (all CLI tools, optional PATH entry with length-gated registry rewrite and
  backup, optional mainnet service + TCP firewall rule) and an improved GUI
  setup (all-users shortcuts, upgrade handling, fixed the broken
  \${SMPROGRAMS} constant carried over from the old script)
- smoke-install.ps1 silent-installs, verifies files/registry/PATH/--help and
  uninstalls on the CI runner; release_windows.yml gains a workflow_dispatch
  dry run and version fallback like the Linux workflow

Shared helpers (packaging/common/lib.sh): the 7-binary list, version
validation (the deb/rpm charset checks rejected any suffixed version - e.g.
1.4.1-rc1 - due to a glob range quirk) and man-page generation, now
single-sourced across the three builders and smoke tests; container image
pins moved to packaging/images.env (also fixes the fedora:latest drift in
test-local.sh); test-local.sh run_step/summary block moved out of the
--skip-build branch where a default (building) run could never reach it.

Checkout hardening: persist-credentials: false and read-only permissions on
the packaging jobs.
@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown

🔍 OpenCodeReview found 22 issue(s) in this PR.

  • ✅ Successfully posted inline: 9 comment(s)
  • 📋 Routed to summary by policy: 13 comment(s)

⚠️ 1 warning(s) occurred during review.


maintainability · low

📄 packaging/test-local.sh (L29-L30)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity low · category maintainability)

images.env is documented as the single source of truth for image pins and already sets ARCH_IMAGE, so this inline fallback hard-codes a second copy of the same pin ('archlinux:base-20260913.0.592969'). If images.env is bumped but this literal is forgotten, local runs would silently diverge from CI (and vice versa if images.env is ever renamed/removed). Consider failing loudly instead of falling back, e.g. : "${ARCH_IMAGE:?ARCH_IMAGE not set by $PKG_ROOT/images.env}".

💡 Suggested Change

Before:

. "$PKG_ROOT/images.env"
ARCH_IMAGE="${ARCH_IMAGE:-archlinux:base-20260913.0.592969}"

After:

. "$PKG_ROOT/images.env"
: "${ARCH_IMAGE:?ARCH_IMAGE not set by $PKG_ROOT/images.env}"

maintainability · low

📄 .github/workflows/release_linux.yml (L141-L142)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity low · category maintainability)

Debian image pins remain hardcoded inline as debian:12 in this workflow (Debian build and smoke steps) while Fedora and Arch moved to packaging/images.env — the file's comment says it is the single source of truth for CI and test-local.sh. Move the Debian pin there too so all container images stay consistent.

💡 Suggested Change

Before:

        docker run --rm --platform linux/$ARCH -v "$PWD":/work -w /work debian:12 \
          packaging/deb/build.sh \

After:

        source packaging/images.env
        docker run --rm --platform linux/$ARCH -v "$PWD":/work -w /work "$DEBIAN_IMAGE" \
          packaging/deb/build.sh \

maintainability · low

📄 packaging/common/lib.sh (L12-L20)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity low · category maintainability)

This duplicated binary list (Windows NSIS/PowerShell lists and workflow binary_list defaults) can silently drift from NODE_BINARIES — a new binary would ship in Linux packages but be missing from Windows installers, with no automated check failing the build. Consider adding a consistency check to packaging/checks/ (e.g. grep the .ps1/.yml lists and diff against printf '%s\n' "${NODE_BINARIES[@]}") so drift is caught in CI rather than at release time.


bug · low

📄 packaging/common/lib.sh (L37-L43)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity low · category bug)

The X.Y.Z pattern is slightly lax: * can span the literal dots and there is no anchor on the suffix, so inputs like 1...1 or 1.4.1- (trailing dash) pass validation and later flow into sed/changelog substitutions. Tighten it, e.g. match X.Y.Z exactly and require the suffix (if any) to be non-empty and start with alnum: case "$version" in [0-9].[0-9].[0-9]|...) ... — or use a regex [[ "$version" =~ ^[0-9]+.[0-9]+.[0-9]+(-[0-9A-Za-z.-]+)?$ ]], consistent with the stricter grammar already enforced in release_linux.yml and create-nsis-installers.ps1.

💡 Suggested Change

Before:

    case "$version" in
        [0-9]*.[0-9]*.[0-9]*) ;;  # e.g. 1.4.1, 1.4.1-rc1
        *)
            VERSION_FORMAT_ERROR="invalid version (expected X.Y.Z[-suffix]): $version"
            return 1
            ;;
    esac

After:

    if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
        VERSION_FORMAT_ERROR="invalid version (expected X.Y.Z[-suffix]): $version"
        return 1
    fi

maintainability · low

📄 packaging/arch/build.sh (L241-L241)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity low · category maintainability)

The node package derives its backup() list purely from the staged etc/ tree, with no non-emptiness assertion (unlike DEPENDS, which is guarded). If etc/ staging ever regresses, the PKGBUILD silently renders an empty backup list and conffile tracking breaks without any failure. Add a guard symmetric to the 'empty depends list' check for the node package.

💡 Suggested Change

Before:

while IFS= read -r f; do BACKUP="$BACKUP '$f'"; done < <(cd "$BR" && find etc -type f | sort)

After:

while IFS= read -r f; do BACKUP="$BACKUP '$f'"; done < <(cd "$BR" && find etc -type f | sort)
if [ "$PACKAGE" = node ] && [ -z "$BACKUP" ]; then
    echo "empty backup list for node package (etc/ not staged?)" >&2
    exit 1
fi

test · low

📄 packaging/checks/smoke-arch.sh (L39-L43)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity low · category test)

For foreign-architecture packages the binaries run under qemu binfmt emulation, but --help >/dev/null 2>&1 discards all diagnostics, and there is no upfront check that binfmt handlers exist. If the handler is missing, the failure mode is set -e aborting on 'cannot execute binary file' with no context; if a handler misbehaves, real output diagnostics are hidden. Consider capturing stderr and printing it on failure, and warning early when /proc/sys/fs/binfmt_misc has no qemu handler for the target.

💡 Suggested Change

Before:

    for bin in "${NODE_BINARIES[@]}"; do
        test -x "/usr/bin/mintlayer-$bin"
        "/usr/bin/mintlayer-$bin" --help >/dev/null 2>&1
        echo "  ok: mintlayer-$bin --help"
    done

After:

    for bin in "${NODE_BINARIES[@]}"; do
        test -x "/usr/bin/mintlayer-$bin"
        if ! "/usr/bin/mintlayer-$bin" --help >/dev/null 2>&1; then
            echo "ERROR: mintlayer-$bin --help failed (qemu/binfmt configured for $PKG_ARCH?)" >&2
            exit 1
        fi
        echo "  ok: mintlayer-$bin --help"
    done

maintainability · low

📄 packaging/rpm/build.sh (L147-L149)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity low · category maintainability)

This pattern works under set -e (failure of the non-final left side of && doesn't trigger errexit), but it's fragile: a future refactor that reorders this into a different construct (e.g. appending || true removal or moving it into a subshell/pipe context) could silently trip errexit. An explicit if is clearer about intent and immune to that.

💡 Suggested Change

Before:

RUNNABLE=0
[ "$RPMARCH" = "$HOST_ARCH" ] && RUNNABLE=1
gen_man "$BR" "$VERSION" "$RUNNABLE"

After:

RUNNABLE=0
if [ "$RPMARCH" = "$HOST_ARCH" ]; then
    RUNNABLE=1
fi
gen_man "$BR" "$VERSION" "$RUNNABLE"

bug · low

📄 packaging/common/lib.sh (L37-L38)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity low · category bug)

This validation accepts malformed versions that downstream consumers may not expect: the glob [0-9]*.[0-9]*.[0-9]* allows empty digit runs between dots (e.g. 1..4.0 matches) and an empty -suffix (e.g. 1.4.0-). The deb builder then appends a revision producing 1.4.0--1, and the rpm builder maps it to 1.4.0~~1, both of which may fail dpkg/rpm version parsing. Consider requiring at least one digit per component and a non-empty suffix when a dash is present.

💡 Suggested Change

Before:

    case "$version" in
        [0-9]*.[0-9]*.[0-9]*) ;;  # e.g. 1.4.1, 1.4.1-rc1

After:

    case "$version" in
        [0-9]*.[0-9]*.[0-9]*) ;;  # e.g. 1.4.1, 1.4.1-rc1
        *[!0-9]*|*-) ;;  # empty digit run or trailing dash: reject

bug · low

📄 packaging/common/lib.sh (L64-L66)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity low · category bug)

The glob over "$bin_dir"/usr/bin/* iterates every entry, not just executables. If any non-executable file or subdirectory ends up in staging/usr/bin (e.g. a stray build artifact or data file), it silently ships a stub man page named after that file instead of failing. Consider skipping non-regular/non-executable entries (e.g. [ -f "$binpath" ] && [ -x "$binpath" ], with a warning otherwise) so only real binaries get man pages.

💡 Suggested Change

Before:

    for binpath in "$bin_dir"/usr/bin/*; do
        binname="$(basename "$binpath")"
        if [ "$runnable" -eq 1 ] && "$binpath" --help >/dev/null 2>&1; then

After:

    for binpath in "$bin_dir"/usr/bin/*; do
        if [ ! -f "$binpath" ] || [ ! -x "$binpath" ]; then
            echo "warning: skipping non-executable entry in staging bin dir: $binpath" >&2
            continue
        fi
        binname="$(basename "$binpath")"
        if [ "$runnable" -eq 1 ] && "$binpath" --help >/dev/null 2>&1; then

bug · low

📄 build-tools/win/smoke-install.ps1 (L114-L117)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity low · category bug)

After the 60s timeout, $proc.Kill() is immediately followed by a throw; the child process tree (daemons may spawn children) is not terminated and the process is not given a chance to exit. Consider $proc.Kill($true) (with $proc.WaitForExit()) to kill the process tree, otherwise a partially started daemon could keep running and interfere with the subsequent uninstall assertion.

💡 Suggested Change

Before:

    if (-not $proc.WaitForExit(60000)) {
        $proc.Kill()
        throw "$bin --help timed out after 60s (possible daemon start)"
    }

After:

    if (-not $proc.WaitForExit(60000)) {
        $proc.Kill($true)
        $proc.WaitForExit()
        throw "$bin --help timed out after 60s (possible daemon start)"
    }

maintainability · low

📄 packaging/deb/build.sh (L135-L138)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity low · category maintainability)

The centralized validate_version is now stricter than the previous inline checks: '' and '+' were previously accepted for deb packages (both are legal characters in Debian version strings, e.g. '1.4.0rc1' or '1.4.0+dfsg'), but are now rejected to keep the RPM/Arch '-'→''/'_' mappings injective. This means tag inputs that previously built fine as .deb now fail. If intentional, consider a deb-specific entry point (e.g. validate_version allowing ''/'+' for deb, a stricter variant for rpm/arch) so the deb builder doesn't inherit restrictions imposed by other formats.

💡 Suggested Change

Before:

if ! validate_version "$VERSION"; then
    echo "$VERSION_FORMAT_ERROR" >&2
    exit 2
fi

After:

if ! validate_version "$VERSION"; then
    echo "$VERSION_FORMAT_ERROR" >&2
    exit 2
fi

maintainability · low

📄 packaging/common/lib.sh (L63-L64)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity low · category maintainability)

gen_man unconditionally runs shopt -u nullglob, which resets the caller's shell option even if the caller had nullglob enabled before sourcing/calling. Save and restore the previous state (e.g. capture with shopt -q nullglob and restore conditionally) to avoid surprising the calling builder scripts.

💡 Suggested Change

Before:

    shopt -s nullglob
    for binpath in "$bin_dir"/usr/bin/*; do

After:

    local nullglob_was_off=1
    shopt -q nullglob && nullglob_was_off=0
    shopt -s nullglob
    for binpath in "$bin_dir"/usr/bin/*; do

maintainability · low

📄 packaging/common/lib.sh (L73-L74)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity low · category maintainability)

The misleading warning text: when runnable=1 but the binary's --help exits non-zero, the else branch also prints "not runnable", which matches the original deb behavior, but for runnable=0 cross-target builds every binary gets this warning even though the binaries are fine — the actual reason is the foreign arch. Consider a distinct message (e.g. "cross-target build: shipping stub man page for $binname") so CI logs are not confused with genuine breakage.

💡 Suggested Change

Before:

        else
            echo "warning: $binname --help not runnable, shipping stub man page" >&2

After:

        elif [ "$runnable" -eq 0 ]; then
            echo "warning: $binname not runnable (cross-target build), shipping stub man page" >&2
        else
            echo "warning: $binname --help not runnable, shipping stub man page" >&2

⚠️ Warnings:

  • packaging/arch/build.sh (token_budget_reached): stopped group "packaging/arch/build.sh,packaging/checks/smoke-arch.sh" mid-review: used 589239 tokens exceeds budget 500000

Comment thread .github/workflows/release_linux.yml Outdated
Comment on lines +204 to +205
- name: Smoke test Arch packages
if: matrix.arch == 'x86_64'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · medium
Unlike the deb and rpm legs (whose smoke tests run under qemu via --platform linux/arm64 for both matrix archs), the Arch arm64 package is built and uploaded without any install/runtime verification: this smoke step is gated to x86_64 and the smoke-arch.sh arm64 leg never runs. A broken aarch64 payload (wrong binary, missing files, bad .PKGINFO) would ship silently. Consider adding a container-free verification (e.g. extract the .pkg.tar.zst with tar and check binaries/paths/.PKGINFO arch, similar to verify-artifacts.sh) for the aarch64 leg instead of relying only on the builder's self-check.

Comment thread .github/workflows/release_windows.yml Outdated
Comment on lines +90 to +91
.\build-tools\win\smoke-install.ps1 -Installer "Mintlayer_Node_win_${{ steps.get_version.outputs.VERSION }}_Setup.exe" -AppName "Mintlayer Node" -Kind node -Version "${{ steps.get_version.outputs.VERSION }}"
.\build-tools\win\smoke-install.ps1 -Installer "Mintlayer_Node_GUI_win_${{ steps.get_version.outputs.VERSION }}_Setup.exe" -AppName "Mintlayer Node GUI" -Kind gui -Version "${{ steps.get_version.outputs.VERSION }}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security · medium
The extracted VERSION is interpolated directly into this pwsh run: block via ${{ steps.get_version.outputs.VERSION }}. On a non-tag trigger the fallback uses git describe output, and a crafted tag/branch name (or ref) could break out of the quoted string and execute arbitrary code before the regex validation inside the .ps1 scripts ever runs. Pass the value through an env: block and reference $env:VERSION instead.

Suggestion:

Suggested change
.\build-tools\win\smoke-install.ps1 -Installer "Mintlayer_Node_win_${{ steps.get_version.outputs.VERSION }}_Setup.exe" -AppName "Mintlayer Node" -Kind node -Version "${{ steps.get_version.outputs.VERSION }}"
.\build-tools\win\smoke-install.ps1 -Installer "Mintlayer_Node_GUI_win_${{ steps.get_version.outputs.VERSION }}_Setup.exe" -AppName "Mintlayer Node GUI" -Kind gui -Version "${{ steps.get_version.outputs.VERSION }}"
env:
VERSION: ${{ steps.get_version.outputs.VERSION }}
run: |
.\build-tools\win\smoke-install.ps1 -Installer "Mintlayer_Node_win_${VERSION}_Setup.exe" -AppName "Mintlayer Node" -Kind node -Version "${VERSION}"

Comment on lines +22 to +24
if ($Version -notmatch '^[0-9][0-9A-Za-z.~+-]*$') {
throw "invalid version '$Version' (expected digits-first X.Y.Z[-suffix])"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maintainability · medium
The Windows version validation is looser than the Linux one in packaging/common/lib.sh (validate_version requires X.Y.Z with an optional -suffix). Here, strings like "1", "1.2" or "1.2.3-rc1-x-y" pass. Since both package families ship from the same tag, keeping a single consistent rule avoids producing a Windows installer filename that the Linux gates would reject (or vice versa). Consider requiring at least two dots, mirroring the X.Y.Z[-suffix] rule.

Suggestion:

Suggested change
if ($Version -notmatch '^[0-9][0-9A-Za-z.~+-]*$') {
throw "invalid version '$Version' (expected digits-first X.Y.Z[-suffix])"
}
if ($Version -notmatch '^[0-9]+\.[0-9]+\.[0-9]+([-+~][0-9A-Za-z.~+-]*)?$') {
throw "invalid version '$Version' (expected X.Y.Z[-suffix])"
}

Comment on lines +29 to +30
$InstallDir = Join-Path $env:ProgramFiles (Join-Path "Mintlayer" $AppName)
$UninstKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Mintlayer $AppName"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · medium
The smoke test assumes a 64-bit PowerShell: it reads $env:ProgramFiles and HKLM:\SOFTWARE...\Uninstall. The templates install to $PROGRAMFILES64 and write the uninstall key in the native registry view, so under 32-bit PowerShell (e.g. Windows PowerShell x86, or a CI step launched from a 32-bit shell) $InstallDir would resolve to 'Program Files (x86)' and Get-ItemProperty would be redirected to WOW6432Node, causing spurious failures. Add an explicit 64-bit check up front (or use $env:ProgramW6432 and the Sysnative path).

Suggestion:

Suggested change
$InstallDir = Join-Path $env:ProgramFiles (Join-Path "Mintlayer" $AppName)
$UninstKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Mintlayer $AppName"
if ([Environment]::Is64BitProcess -ne $true) {
throw "run this script under 64-bit PowerShell (installers use $PROGRAMFILES64 and the native HKLM view)"
}
$InstallDir = Join-Path $env:ProgramFiles (Join-Path "Mintlayer" $AppName)
$UninstKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Mintlayer $AppName"

Comment thread packaging/arch/build.sh Outdated
echo "$VERSION_FORMAT_ERROR" >&2
exit 2
fi
PKGVER="$(printf '%s' "$VERSION" | tr -- '-~' '__')"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · medium
validate_version permits '' but not '_', so mapping both '-' and '' to '_' makes distinct versions collide on the same pkgver (e.g. 1.4.1-rc1 and 1.4.1rc1 both become 1.4.1_rc1, which pacman treats as the same package version). The rpm builder maps only '-' to '', which is injective — do the same here.

Suggestion:

Suggested change
PKGVER="$(printf '%s' "$VERSION" | tr -- '-~' '__')"
PKGVER="$(printf '%s' "$VERSION" | tr -- '-' '_')"

Comment thread packaging/arch/build.sh
Comment on lines +146 to +148
for binpath in "$BR"/usr/bin/*; do
file "$binpath" | grep -q "not stripped" && strip --strip-unneeded "$binpath"
done

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · high
Under set -e, file ... | grep -q "not stripped" && strip ... aborts the whole build if a binary is already stripped (grep returns 1 and the && list's failure propagates). The deb builder deliberately uses an if wrapper for exactly this reason (see deb/build.sh comment about the "already stripped" match failure tripping errexit). Use the same form here.

Suggestion:

Suggested change
for binpath in "$BR"/usr/bin/*; do
file "$binpath" | grep -q "not stripped" && strip --strip-unneeded "$binpath"
done
for binpath in "$BR"/usr/bin/*; do
if file "$binpath" | grep -q "not stripped"; then
strip --strip-unneeded "$binpath"
fi
done

Comment thread packaging/test-local.sh Outdated
@@ -71,6 +78,7 @@ fi
echo "pulling container images..."
docker pull -q debian:12 >/dev/null
docker pull -q fedora:latest >/dev/null

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maintainability · medium
images.env is sourced above and its header claims it is the single source of truth shared with CI, but this script still pulls and runs fedora:latest throughout instead of the pinned $FEDORA_IMAGE. Local rpm builds therefore drift from CI (fedora:44) and the pin provides no reproducibility here. Use "$FEDORA_IMAGE" in the docker pull and all fedora docker run invocations (debian:12 could be similarly pinned or noted as intentionally unpinned).

Suggestion:

Suggested change
docker pull -q fedora:latest >/dev/null
docker pull -q "$FEDORA_IMAGE" >/dev/null

- release_linux.yml: smoke-test the Arch packages on both matrix legs; the
  aarch64 leg installs the foreign-arch package via IgnoreArch and runs the
  binaries through the qemu binfmt handlers, matching the deb/rpm legs
- release_windows.yml: pass the version and the workflow input through
  environment variables instead of interpolating them into the pwsh run
  blocks (script-injection hardening)
- create-nsis-installers.ps1: enforce the same strict version grammar as
  packaging/common/lib.sh (X.Y.Z with an optional -suffix)
- smoke-install.ps1: fail fast under 32-bit PowerShell, which would read
  the redirected WOW6432Node registry view and x86 Program Files
- packaging/common/lib.sh: reject '~' and '+' in versions so that the rpm
  ('-' -> '~') and Arch ('-' -> '_') version mappings stay injective;
  distinct versions can no longer collide on the same package version
- packaging/arch/build.sh, packaging/rpm/build.sh: guard the strip step
  with an if so an already-stripped binary cannot trip errexit
- packaging/test-local.sh: use the pinned $FEDORA_IMAGE everywhere
  instead of fedora:latest, so local runs cannot drift from CI
Comment thread .github/workflows/release_linux.yml Outdated
Comment on lines +189 to +191
packaging/arch/build.sh \
--package node --arch "$ARCH" \
--version "${{ steps.get_version.outputs.VERSION }}" \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security · medium
The version string, which is derived from the attacker-controllable tag/ref (see the 'Extract version from tag' step: GITHUB_REF tag or unvalidated git describe output), is interpolated directly into these run: blocks via ${{ steps.get_version.outputs.VERSION }}. A crafted tag value can therefore inject shell commands into the packaging/smoke-test steps. The Windows workflow in this same change explicitly fixed this exact pattern by passing the value through an env: variable (see release_windows.yml 'Package Mintlayer Node' comment); the Linux workflow should do the same for consistency and safety. Additionally, like the confirmed Windows finding, the git-describe fallback here is never validated against the version grammar before being passed to packaging/build.sh.

Suggestion:

Suggested change
packaging/arch/build.sh \
--package node --arch "$ARCH" \
--version "${{ steps.get_version.outputs.VERSION }}" \
env:
VERSION: ${{ steps.get_version.outputs.VERSION }}
run: |
source packaging/images.env
ARCH=${{ matrix.arch }}
docker run --rm -v "$PWD":/work -w /work $ARCH_IMAGE \
packaging/arch/build.sh \
--package node --arch "$ARCH" \
--version "$VERSION" \

Comment on lines +39 to +40
$VERSION = git describe --tags --abbrev=0 2>$null
$VERSION = $VERSION -replace '^v', ''

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · medium
The fallback version from git describe --tags --abbrev=0 is not validated against any grammar before being exported. A non-semver tag (e.g. v1.2 or a date-based tag) yields VERSION like 1.2, which makes create-nsis-installers.ps1 throw mid-release ("invalid version"), failing the workflow at the packaging step instead of being caught here. Validate or normalize the version at extraction time so failures surface early with a clear message.

Suggestion:

Suggested change
$VERSION = git describe --tags --abbrev=0 2>$null
$VERSION = $VERSION -replace '^v', ''
$VERSION = git describe --tags --abbrev=0 2>$null
$VERSION = $VERSION -replace '^v', ''
if ($VERSION -notmatch '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$') {
throw "derived version '$VERSION' is not X.Y.Z[-suffix]; tag a semver release"
}

Comment thread build-tools/win/smoke-install.ps1 Outdated
Comment on lines +99 to +102
& $exe --help *> $null
if ($LASTEXITCODE -ne 0) {
throw "$bin --help exited with $LASTEXITCODE"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · high
Each installed binary is executed with & $exe --help and its exit code checked, but there is no timeout around the invocation. If a binary ignores --help and starts serving (as daemons may), or blocks waiting for input, this loop hangs forever. The release_windows.yml job has no timeout-minutes either, so a hung smoke test consumes the runner indefinitely. Wrap the invocation in a bounded wait (e.g. Start-Process -PassThru + WaitForExit(timeout), kill on timeout) or add a job-level timeout-minutes in the workflow.

Suggestion:

Suggested change
& $exe --help *> $null
if ($LASTEXITCODE -ne 0) {
throw "$bin --help exited with $LASTEXITCODE"
}
$proc = Start-Process -FilePath $exe -ArgumentList "--help" -NoNewWindow -Wait $false -PassThru -RedirectStandardOutput NUL -RedirectStandardError NUL
if (-not $proc.WaitForExit(60000)) {
$proc.Kill()
throw "$bin --help timed out after 60s (possible daemon start)"
}
if ($proc.ExitCode -ne 0) {
throw "$bin --help exited with $($proc.ExitCode)"
}

Comment thread packaging/arch/build.sh Outdated
echo "$VERSION_FORMAT_ERROR" >&2
exit 2
fi
PKGVER="$(printf '%s' "$VERSION" | tr -- '-' '_')"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · medium
Mapping '-' to '_' makes pre-release versions sort incorrectly under Arch's vercmp: pacman considers '1.4.1_rc1' NEWER than '1.4.1' (for the segment '1_rc1' vs '1', vercmp prefers the longer segment after the equal numeric prefix), so systems that installed the RC package would refuse to upgrade to the final release. The rpm builder maps '-' to '' precisely to sort before the final release; Arch has no '', so this builder either needs a different scheme (e.g. keep the plain '1.4.1' pkgver with pkgrel distinguishing, or document/accept the ordering quirk explicitly) or a guard rejecting pre-release versions.

Suggestion:

Suggested change
PKGVER="$(printf '%s' "$VERSION" | tr -- '-' '_')"
# Note: Arch vercmp treats '1.4.1_rc1' as newer than '1.4.1', so pre-release
# versions do not sort before the final release. Reject them or handle ordering
# explicitly.
case "$VERSION" in
*-*) echo "pre-release versions are not supported for Arch packages" >&2; exit 2 ;;
esac
PKGVER="$(printf '%s' "$VERSION" | tr -- '-')"

Comment thread packaging/arch/build.sh
# installed, so provision them here (package names are x86_64 repo names,
# which is what the amd64 container's repos provide regardless of the target
# arch being packaged).
pacman -S --noconfirm --needed --asdeps $DEPENDS >/dev/null

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · medium
If the ldd/pacman -F resolution ever produces an empty DEPENDS (e.g. all binaries statically linked, or LIBS filtered to nothing), this runs pacman -S --noconfirm --needed --asdeps with no package arguments, which fails with pacman's usage error rather than a clear message. Also, $DEPENDS is intentionally word-split here, which breaks on any package name containing whitespace (unlikely for Arch, but worth a defensive check). Consider an explicit [ -n "$DEPENDS" ] || { echo ...; exit 1; } guard before invoking pacman.

Suggestion:

Suggested change
pacman -S --noconfirm --needed --asdeps $DEPENDS >/dev/null
[ -n "$DEPENDS" ] || { echo "empty depends list" >&2; exit 1; }
pacman -S --noconfirm --needed --asdeps $DEPENDS >/dev/null

Comment thread packaging/common/lib.sh
Comment on lines +61 to +62
for binpath in "$bin_dir"/usr/bin/*; do
binname="$(basename "$binpath")"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · medium
Without nullglob, if $bin_dir/usr/bin is empty (or the directory is missing), the glob expands to the literal string and this loop iterates once with binpath='.../usr/bin/'. The stub branch then writes a file literally named '.1' and gzip renames it to '*.1.gz', producing a garbage artifact instead of failing (or cleanly no-oping). Since gen_man is now shared by deb, rpm and arch builders, a builder whose copy step is skipped/failed early would surface this confusingly.

Suggestion:

Suggested change
for binpath in "$bin_dir"/usr/bin/*; do
binname="$(basename "$binpath")"
local binpath binname
shopt -s nullglob
for binpath in "$bin_dir"/usr/bin/*; do
binname="$(basename "$binpath")"

- release_linux.yml: the version and the workflow input are passed through
  environment variables instead of being interpolated into the run blocks
  (script-injection hardening, same as the Windows workflow), and the
  derived version is validated against X.Y.Z[-suffix] at extraction time
- release_windows.yml: the git-describe fallback version is validated up
  front with a clear error instead of failing mid-release in the NSIS
  step; job-level timeout-minutes bounds the whole build
- smoke-install.ps1: every installed binary is executed with a 60s
  timeout and killed on expiry, so a binary that ignores --help and starts
  serving cannot hang the smoke test forever
- packaging/arch/build.sh: pre-release versions are rejected explicitly,
  because Arch vercmp has no '~' equivalent ('1.4.1_rc1' would sort newer
  than '1.4.1' and refuse the upgrade to the final release); the depends
  provisioning guards against an empty dependency list
- packaging/common/lib.sh: gen_man sets nullglob, so an empty or missing
  bin dir fails loudly instead of producing a garbage '*.1.gz' man page
Comment on lines 27 to 28
jobs:
build:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security · medium
This job declares no permissions key, so it inherits the default (potentially broad) token permissions, while release_windows.yml was updated to set permissions: contents: read. Since this job only builds and uploads artifacts, restrict it the same way for least-privilege consistency.

Suggestion:

Suggested change
jobs:
build:
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read

Comment thread .github/workflows/release_linux.yml Outdated
Comment on lines 257 to 258
name: Mintlayer_Node_linux_${VERSION}_${{ matrix.arch }}_deb
path: dist/Mintlayer_Node_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch == 'x86_64' && 'amd64' || 'arm64' }}.deb

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · high
Shell-style ${VERSION} is not expanded in with: blocks — only ${{ }} expressions are. The artifact name will literally be Mintlayer_Node_linux_${VERSION}_${{ matrix.arch }}_deb. These upload steps don't have a VERSION env var, so they must keep ${{ steps.get_version.outputs.VERSION }}.

Suggestion:

Suggested change
name: Mintlayer_Node_linux_${VERSION}_${{ matrix.arch }}_deb
path: dist/Mintlayer_Node_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch == 'x86_64' && 'amd64' || 'arm64' }}.deb
name: Mintlayer_Node_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch }}_deb
path: dist/Mintlayer_Node_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch == 'x86_64' && 'amd64' || 'arm64' }}.deb

Comment thread .github/workflows/release_linux.yml Outdated
Comment on lines +281 to +282
name: Mintlayer_Node_linux_${VERSION}_${{ matrix.arch }}_pkg
path: dist/Mintlayer_Node_linux_${VERSION}_${{ matrix.arch }}.pkg.tar.zst

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · high
Same problem, and worse here: both name and path use ${VERSION}, so the path matches no file and the PKG artifact upload fails outright. Use the step output expression instead.

Suggestion:

Suggested change
name: Mintlayer_Node_linux_${VERSION}_${{ matrix.arch }}_pkg
path: dist/Mintlayer_Node_linux_${VERSION}_${{ matrix.arch }}.pkg.tar.zst
name: Mintlayer_Node_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch }}_pkg
path: dist/Mintlayer_Node_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch }}.pkg.tar.zst

Comment on lines +49 to +52
function Assert-PathEntry {
param ([bool]$ShouldExist)
$machinePath = [Environment]::GetEnvironmentVariable("Path", "Machine")
$present = ($machinePath -split ";" -contains $InstallDir)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · medium
Cross-file contract risk: node.nsi.in's AddToPath deliberately skips the PATH write when the machine PATH exceeds PATH_MAX_SAFE_LEN (900 chars) and, in a silent install, the warning MessageBox/DetailPrint is effectively invisible. On a CI runner or machine with a long PATH this makes Assert-PathEntry fail as a false positive even though the installer behaved as designed. Consider reading the same limit (or having the installer record a marker, e.g. a 'PathSkipped' registry value under the uninstall key) so the smoke test can distinguish 'installer broken' from 'PATH too long, skipped by design'.

Suggestion:

Suggested change
function Assert-PathEntry {
param ([bool]$ShouldExist)
$machinePath = [Environment]::GetEnvironmentVariable("Path", "Machine")
$present = ($machinePath -split ";" -contains $InstallDir)
function Assert-PathEntry {
param ([bool]$ShouldExist)
$machinePath = [Environment]::GetEnvironmentVariable("Path", "Machine")
$present = ($machinePath -split ";" -contains $InstallDir)
# Node: the installer skips the PATH write when the machine PATH exceeds
# its NSIS safe-length limit; treat that case explicitly to avoid false failures.

Comment thread packaging/arch/build.sh Outdated
Comment on lines +194 to +195
pkg="$(pacman -F --machinereadable "usr/lib/$lib" 2>/dev/null \
| tr '\0' '\t' | awk -F'\t' 'NR == 1 {print $2}')"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · medium
The comment says fallback names were "verified against pacman -F", but the native path here resolves each library with pacman -F and blindly takes the first result (NR == 1). A soname can be provided by several packages, and the first machinereadable row is not guaranteed to be the canonical runtime provider (ordering depends on repo/db order), so a wrong or overly-specific package can end up in depends= and wind up in the published PKGBUILD metadata. Consider collecting all provider rows and preferring a well-known provider (e.g. skip -impl/-devel style names, or cross-check that the chosen package actually owns the file in the synced repos).

Comment thread packaging/test-local.sh Outdated
Comment on lines +252 to +254
# pacman refuses foreign-architecture packages, so the arch pkg smoke
# test runs on the x86_64 leg only (like in release_linux.yml).
if [ "$arch" = x86_64 ]; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test · medium
This comment is inaccurate and the corresponding gap is real: release_linux.yml DOES smoke-test the Arch packages on both matrix legs (the aarch64 leg installs with IgnoreArch and runs the binaries through the qemu binfmt handlers registered by docker/setup-qemu-action), while test-local.sh only runs the Arch smoke tests on x86_64. Local testing is therefore weaker than CI for the aarch64 pkg artifacts (and the justification given in the comment does not hold). Since the host already registers binfmt handlers for the deb/rpm arm64 legs, consider passing the architecture as the fourth argument and running the aarch64 pkg smoke tests here too, or at least fixing the comment.

Suggestion:

Suggested change
# pacman refuses foreign-architecture packages, so the arch pkg smoke
# test runs on the x86_64 leg only (like in release_linux.yml).
if [ "$arch" = x86_64 ]; then
# aarch64 leg relies on qemu binfmt handlers (already required for the
# deb/rpm arm64 smoke tests) plus smoke-arch.sh's IgnoreArch handling.
run_step "smoke pkg node ($arch)" \
docker run --rm -v "$REPO_ROOT:/work" -w /work "$ARCH_IMAGE" \
packaging/checks/smoke-arch.sh packaging/dist/Mintlayer_Node_linux_${VERSION}_${arch}.pkg.tar.zst \
mintlayer-node node "$arch"

- release_linux.yml: restore the ${{ }} expressions in the artifact
  upload name/path fields, where shell-style ${VERSION} does not expand
  (regression introduced by the env-var change); declare
  'permissions: contents: read' like the Windows workflow
- smoke-install.ps1: treat the installer's PATH-too-long skip (PATH above
  its 900-char NSIS safe limit) as the designed behavior instead of a
  false failure
- packaging/arch/build.sh: collect all pacman -F providers per soname and
  pick the shortest package name deterministically (first row is not
  guaranteed to be the canonical runtime provider), logging when several
  packages provide the same library
- packaging/test-local.sh: run the aarch64 pkg smoke tests too, mirroring
  CI (IgnoreArch + the qemu binfmt handlers already required for the
  deb/rpm arm64 legs)
Comment on lines +31 to 34
permissions:
contents: read
permissions:
contents: read

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · high
This job declares permissions: twice. Duplicate YAML keys are invalid — GitHub's YAML parser may reject the workflow outright (breaking the whole release pipeline) or silently keep only one value depending on the parser version. Keep a single permissions: contents: read block.

Suggestion:

Suggested change
permissions:
contents: read
permissions:
contents: read
permissions:
contents: read

Comment on lines +204 to +207
source packaging/images.env
ARCH=${{ matrix.arch }}
docker run --rm -v "$PWD":/work -w /work $ARCH_IMAGE \
packaging/arch/build.sh \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · medium
On the arm64 matrix leg this step runs an amd64-only container ($ARCH_IMAGE) without --platform. That requires an amd64 binfmt handler on the arm64 runner, but the setup step above only documents/registers handlers so --platform linux/arm64 works on the x86_64 leg. If the setup action doesn't register the amd64 (qemu-x86_64) handler on arm64 runners, this step fails on the arm64 leg. Either register the needed handler explicitly or document that the setup action covers all architectures.

Comment on lines +41 to +44
if ($VERSION -eq $env:GITHUB_REF) {
$VERSION = git describe --tags --abbrev=0 2>$null
$VERSION = $VERSION -replace '^v', ''
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · medium
On workflow_dispatch there is no tag: the regex replace leaves GITHUB_REF (refs/heads/) untouched, so the fallback runs git describe --tags --abbrev=0, which returns the newest tag anywhere in the fetched history — not the version of the code being dispatched. A manual run from an older branch produces installers stamped with an unrelated, newer version (and the semver check then passes). Consider deriving the version from git describe --tags --exact-match first and falling back to a commit-based dev version (e.g. 0.0.0-<short-sha>) instead of the nearest tag.

Suggestion:

Suggested change
if ($VERSION -eq $env:GITHUB_REF) {
$VERSION = git describe --tags --abbrev=0 2>$null
$VERSION = $VERSION -replace '^v', ''
}
if ($VERSION -eq $env:GITHUB_REF) {
$VERSION = git describe --tags --exact-match 2>$null
$VERSION = $VERSION -replace '^v', ''
if ([string]::IsNullOrEmpty($VERSION)) {
$VERSION = "0.0.0-$(git rev-parse --short HEAD)"
}
}

Comment on lines +58 to +61
if (-not $present -and $machinePath.Length -gt 900) {
Write-Warning "machine PATH is longer than the installer's safe limit (900 chars); the PATH entry was skipped by design"
return
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · medium
When the machine PATH exceeds 900 chars, this skip also bypasses the negative assertion during uninstall: a stale PATH entry left behind by a failed cleanup would be silently accepted. On install this is fine (the installer refuses to write a long PATH), but on uninstall the entry can only exist via manual edits or an installer that did write before PATH grew — in both cases a leftover entry is exactly what the smoke test should report. Consider skipping only the ShouldExist $true branch and still asserting removal.

Suggestion:

Suggested change
if (-not $present -and $machinePath.Length -gt 900) {
Write-Warning "machine PATH is longer than the installer's safe limit (900 chars); the PATH entry was skipped by design"
return
}
if (-not $ShouldExist) {
if ($present) { throw "machine PATH still contains '$InstallDir'" }
return
}
if (-not $present -and $machinePath.Length -gt 900) {
Write-Warning "machine PATH is longer than the installer's safe limit (900 chars); the PATH entry was skipped by design"
return
}
if (-not $present) {
throw "machine PATH does not contain '$InstallDir'"
}

# '_?=' pins the uninstaller to the install dir (unquoted, as documented) so
# it does not copy itself to a temp location, which would make -Wait return
# before deletion finishes.
$p = Start-Process -FilePath $uninstaller -ArgumentList "/S", "_?=$InstallDir" -Wait -PassThru

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · medium
NSIS documents _?= as requiring an unquoted path; Start-Process -ArgumentList will quote this argument because $InstallDir ("C:\Program Files\Mintlayer\Mintlayer Node") contains spaces. A quoted _?= value makes the uninstaller fail to resolve the copy location, so it may copy itself to temp and return before deletion finishes (or leave residue), producing flaky smoke-test failures.

Suggestion:

Suggested change
$p = Start-Process -FilePath $uninstaller -ArgumentList "/S", "_?=$InstallDir" -Wait -PassThru
$p = Start-Process -FilePath $uninstaller -ArgumentList "/S _?=$InstallDir" -Wait -PassThru # or invoke via cmd /c to control quoting

Comment thread packaging/arch/build.sh
Comment on lines +206 to +207
pkg="$(printf '%s\n' "$providers" \
| awk '{ print length($0), $0 }' | sort -n -k1,1 -k2,2 | head -n1 | cut -d' ' -f2-)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maintainability · medium
The deterministic provider pick ('shortest package name, ties alphabetical') is only a heuristic: a soname can be provided by a meta/compat/-headers variant whose selection as a runtime dependency is wrong (and namcap errors are the only safety net, with warnings allowed). Consider cross-checking the chosen provider against a small allowlist/known-good set (e.g. asserting the result matches ^[a-z0-9@._+-]+$ and excluding obvious meta names), or at least extending the multi-provider note so CI output makes a suspicious choice easy to audit.

Suggestion:

Suggested change
pkg="$(printf '%s\n' "$providers" \
| awk '{ print length($0), $0 }' | sort -n -k1,1 -k2,2 | head -n1 | cut -d' ' -f2-)"
pkg="$(printf '%s\n' "$providers" \
| awk '{ print length($0), $0 }' | sort -n -k1,1 -k2,2 | head -n1 | cut -d' ' -f2-)"
case "$pkg" in
*-headers|*-docs|*-devel*)
echo "warning: suspicious provider for usr/lib/$lib: $pkg" >&2 ;;
esac

Comment on lines +17 to +20
PKG_FILE="$(readlink -f "$1")"
PKG_NAME="$2"
KIND="$3"
PKG_ARCH="${4:-$(uname -m)}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · medium
The /etc/pacman.conf is mutated (IgnoreArch injection) and the database synced before the package file is validated. If $1 does not point to an existing file, readlink -f silently yields a bogus path and the failure surfaces later from pacman -U, after the container state has already been changed. Add an explicit existence check right after resolving PKG_FILE so bad arguments fail fast and the diagnostic is unambiguous.

Suggestion:

Suggested change
PKG_FILE="$(readlink -f "$1")"
PKG_NAME="$2"
KIND="$3"
PKG_ARCH="${4:-$(uname -m)}"
PKG_FILE="$(readlink -f "$1")"
[ -f "$PKG_FILE" ] || { echo "package file not found: $1" >&2; exit 2; }
PKG_NAME="$2"
KIND="$3"
PKG_ARCH="${4:-$(uname -m)}"

shopt -u nullglob
[ ${#units[@]} -gt 0 ] || { echo "ERROR: no mintlayer units installed" >&2; exit 1; }
for unit in "${units[@]}"; do
systemd-analyze verify "$unit"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · medium
The node smoke test runs systemd-analyze verify, but the Arch node package declares systemd-libs as its dependency — the systemd-analyze binary ships with the full systemd package, which is typically not installed in the minimal archlinux:base container (and unlike desktop-file-utils in the GUI branch, nothing here installs it). If it's absent, set -e aborts the smoke test with a confusing 'command not found' error unrelated to the package under test. Either self-provision it (pacman -S --noconfirm --needed systemd or systemd-libs+tool availability check) before this block, or skip the verify step with a clear message when the tool is unavailable.

Suggestion:

Suggested change
systemd-analyze verify "$unit"
if command -v systemd-analyze >/dev/null 2>&1; then
systemd-analyze verify "$unit"
else
echo " warning: systemd-analyze not available, skipping unit verification" >&2
fi

Comment thread packaging/common/lib.sh
Comment on lines +57 to +58
export LC_ALL=C.UTF-8
local man_dir="$bin_dir/usr/share/man/man1"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maintainability · medium
gen_man mutates the caller's shell state without restoring it: export LC_ALL=C.UTF-8 persists in the caller's environment after the function returns (it is not scoped), and shopt -u nullglob unconditionally disables nullglob even if the caller had it enabled before calling. All current callers run under set -euo pipefail and source this lib, so the leaks are live. Save/restore the previous state: local _oldshopt="$(shopt -p nullglob)" ... eval "$_oldshopt", and prefer prefixing the help2man invocation with LC_ALL=C.UTF-8 instead of exporting.

Suggestion:

Suggested change
export LC_ALL=C.UTF-8
local man_dir="$bin_dir/usr/share/man/man1"
local _lc_all_was_set="${LC_ALL+set}"
local _old_lc_all="${LC_ALL-}"
export LC_ALL=C.UTF-8
...
# restore at end of function:
if [ -n "$_lc_all_was_set" ]; then export LC_ALL="$_old_lc_all"; else unset LC_ALL; fi

@nullPointerEnjoyer
nullPointerEnjoyer merged commit b317c4a into master Sep 17, 2026
21 checks passed
@nullPointerEnjoyer
nullPointerEnjoyer deleted the feature/arch-windows-packaging branch September 17, 2026 16:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants