Clean up three build-log warnings #35
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: build-release | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| # We need contents:write to attach assets to a release and to push the | |
| # gh-pages branch that hosts the pkg repo catalog. | |
| permissions: | |
| contents: write | |
| # Opt the @v4 JS actions (actions/checkout, actions/upload-artifact, | |
| # peaceiris/actions-gh-pages) onto Node.js 24 now, ahead of the June 2, | |
| # 2026 mandatory cutover. The runner emits a deprecation warning for any | |
| # action that ships with a Node 20 runtime; this env var flips them over. | |
| # Catches forward-compat issues while we still have time to react. | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' | |
| jobs: | |
| build: | |
| name: Build, sign, and publish OPNsense plugin pkg | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check out this plugin | |
| uses: actions/checkout@v4 | |
| with: | |
| path: os-netboot | |
| fetch-depth: 0 | |
| - name: Check out opnsense/plugins for the Mk/ build infrastructure | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: opnsense/plugins | |
| path: opnsense-plugins | |
| ref: master | |
| - name: Check out opnsense/core as a sibling (needed by make lint) | |
| # The plugin's Mk/lint.mk pulls in core/Mk/lint.mk via: | |
| # .-include "${PLUGINSDIR}/../core/Mk/lint.mk" | |
| # Several lint targets (lint-class, lint-import, lint-acl, lint-php | |
| # via parallel-lint) reference scripts under opnsense/core. Without | |
| # a sibling core checkout these targets soft-disable and the strict | |
| # OPNsense conventions go unenforced. | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: opnsense/core | |
| path: core | |
| ref: master | |
| - name: Inject this plugin into the opnsense/plugins tree | |
| run: | | |
| set -eux | |
| mkdir -p opnsense-plugins/ftp/netboot | |
| tar -C os-netboot \ | |
| --exclude='.git' \ | |
| --exclude='.github' \ | |
| --exclude='dist' \ | |
| --exclude='.research' \ | |
| -cf - . | tar -C opnsense-plugins/ftp/netboot -xf - | |
| - name: Lint, test, and build the .pkg inside a FreeBSD VM | |
| uses: vmactions/freebsd-vm@v1 | |
| # Make the signing secret available to THIS STEP as an env var. | |
| # The 'envs:' field below tells vmactions/freebsd-vm to forward | |
| # variables INTO the VM, but only if they already exist on the | |
| # runner -- so we must materialize it from secrets here first. | |
| env: | |
| PKG_SIGNING_KEY: ${{ secrets.PKG_SIGNING_KEY }} | |
| # Per-build monotonic counter. Lands as the pkg revision suffix | |
| # so every CI run produces a distinct pkg version (0.1.0_NN), | |
| # which means `pkg upgrade` actually picks up new builds without | |
| # the user having to delete-and-reinstall or clear the pkg cache. | |
| # github.run_number increments on every workflow invocation | |
| # including re-runs of the same commit. | |
| GH_RUN_NUMBER: ${{ github.run_number }} | |
| with: | |
| release: "14.2" | |
| usesh: true | |
| prepare: | | |
| # bsdmake comes with FreeBSD base. The php83 port installs the | |
| # binary directly as /usr/local/bin/php; no aliasing needed. | |
| # | |
| # We pkg-upgrade first because the VM image's libpcre2-8 was | |
| # built against a stale ABI; a fresh php83 from the current pkg | |
| # repo wants PCRE2_10.47 and fails to dlopen with the stale lib. | |
| # Upgrading aligns libpcre2 (and anything else the new php | |
| # transitively depends on) before the php install lands. | |
| pkg upgrade -y | |
| # python3 is needed only to silence opnsense-plugins' | |
| # defaults.mk warning "Cannot build without PLUGIN_PYTHON set" | |
| # -- the Mk machinery probes /usr/local/bin/python3 to derive | |
| # a PLUGIN_PYTHON suffix for replacing %%PYTHON_VER%% in any | |
| # plugin files. We don't use that replacement (no Python in | |
| # this plugin) but installing python3 lets defaults.mk compute | |
| # the value cleanly instead of falling through to the warning. | |
| pkg install -y git php83 php83-tokenizer php83-xml php83-dom python3 | |
| php -v | |
| python3 -V | |
| envs: 'PKG_SIGNING_KEY GH_RUN_NUMBER' | |
| run: | | |
| # SECURITY: capture the signing secret to a tmpfile and drop | |
| # it from the env BEFORE running anything else. Otherwise | |
| # every subprocess (make, phpunit, pkg, ...) inherits an env | |
| # that contains the PEM, and a future bug or third-party | |
| # rule that dumps env would leak it. Once captured to disk, | |
| # the secret only travels by file path; commands trace the | |
| # path, not the bytes. | |
| # | |
| # set -e is on, set -x is off until the secret leaves the env. | |
| set -eu | |
| if [ -z "${PKG_SIGNING_KEY:-}" ]; then | |
| echo "ERROR: signing key env var is empty inside the FreeBSD VM." | |
| echo " Expected: a non-empty value forwarded from the runner." | |
| echo " Check the repo secret at" | |
| echo " github.com/johnwbyrd/os-netboot/settings/secrets/actions" | |
| echo " and the env: + envs: lines on the FreeBSD VM step in" | |
| echo " .github/workflows/build-release.yml." | |
| exit 1 | |
| fi | |
| KEYFILE=$(mktemp) | |
| chmod 600 "${KEYFILE}" | |
| # printf's stderr piped away to belt-and-suspenders against any | |
| # diagnostic that might quote the value. | |
| printf '%s\n' "${PKG_SIGNING_KEY}" > "${KEYFILE}" 2>/dev/null | |
| unset PKG_SIGNING_KEY | |
| # Make sure the keyfile is shredded even if a later step fails. | |
| trap 'rm -f "${KEYFILE}"' EXIT INT TERM | |
| # OK -- the env is now clean. From here on, trace is fine. | |
| set -x | |
| WORKDIR=$(pwd) | |
| # vmactions/freebsd-vm mounts the host workspace into the VM | |
| # but the host workspace is owned by uid 1001 on the runner | |
| # while the VM commands run as root. Modern git refuses to | |
| # operate on a repo owned by a different uid and prints | |
| # "fatal: detected dubious ownership in repository". That | |
| # makes opnsense/plugins' Scripts/version.sh (which calls | |
| # `git describe` to embed an upstream-version hash in the | |
| # generated pkg-plist annotation) return non-zero, leaving | |
| # product_hash empty in the +MANIFEST metadata. Mark every | |
| # checkout under WORKDIR as safe to defuse this. | |
| git config --global --add safe.directory "${WORKDIR}/opnsense-plugins" | |
| git config --global --add safe.directory "${WORKDIR}/os-netboot" | |
| git config --global --add safe.directory "${WORKDIR}/core" | |
| cd "${WORKDIR}/opnsense-plugins/ftp/netboot" | |
| # Strict OPNsense lint. Surfaces model-XML convention violations, | |
| # missing shebangs / +x bits, raw shell-outs from PHP, parallel- | |
| # lint PHP syntax errors, and pkg-plist drift. Fails the build on | |
| # any finding. | |
| make lint | |
| # PHPUnit runs in ci.yml on the Linux runner, NOT here. The | |
| # OPNsense 'make test' target wants TESTDIR at src/opnsense/ | |
| # mvc/tests/, but that path is owned by the opnsense-core | |
| # package's pkg-plist -- any file we put there collides with | |
| # core at install time. So we keep tests at <repo>/tests/ | |
| # and run them via ci.yml's plain phpunit invocation. | |
| # | |
| # Phalcon-bound tests (Layer 4 in doc/tdd.md) will eventually | |
| # need to run in this VM since Phalcon isn't on the Linux | |
| # runner. That's a future addition; for now no VM-side tests. | |
| # PLUGIN_REVISION forces every CI run to produce a distinct | |
| # pkg version. Without it, two builds at the same PLUGIN_VERSION | |
| # produce identical pkg metadata and `pkg upgrade` won't see | |
| # them as upgrades. Result: users have to delete-and-reinstall | |
| # to pick up template / controller fixes. With it, each run | |
| # gets a fresh suffix like 0.1.0_47 and pkg-upgrade DTRT. | |
| make package PLUGIN_REVISION="${GH_RUN_NUMBER}" | |
| ls -la work/pkg | |
| REPODIR="${WORKDIR}/pkg-repo" | |
| ABI=$(pkg config ABI) | |
| REPODIR_ABI="${REPODIR}/${ABI}" | |
| mkdir -p "${REPODIR_ABI}" | |
| cp work/pkg/*.pkg "${REPODIR_ABI}/" 2>/dev/null || cp work/pkg/*.txz "${REPODIR_ABI}/" | |
| # Sign the freshly-built repo. $KEYFILE was set up at the very | |
| # top of this script (before any other command ran) and the | |
| # original env var was already unset. | |
| # | |
| # We use FreeBSD pkg's BUILT-IN rsa: signing form rather than | |
| # the shell-out 'signing_command: openssl dgst -sha256 -sign' | |
| # form. Both syntaxes appear in pkg-repo(8)'s usage line, but | |
| # the signing_command: path forks an external process and | |
| # pipes the digest through stdin -- and in practice that | |
| # pipe handshake hangs indefinitely on FreeBSD 14.2 / pkg 1.x | |
| # for reasons that aren't clearly diagnosed. (Symptom: 'pkg | |
| # repo' prints 'Creating repository ... done' and then | |
| # hangs forever, with the openssl child stuck waiting on | |
| # stdin even though pkg appears to have already produced | |
| # the catalog data.) Four consecutive ~1-hour-stuck runs | |
| # before we caught it. | |
| # | |
| # rsa:<keypath> tells pkg to sign with libcrypto directly, | |
| # no subprocess, no stdin handshake. The keyfile is read | |
| # once at the start. Fast and reliable. | |
| pkg repo "${REPODIR_ABI}" "rsa:${KEYFILE}" | |
| # The trap from the top of the script will remove KEYFILE on | |
| # exit too; explicit rm here so it's gone immediately after | |
| # the last use, not just on script termination. | |
| rm -f "${KEYFILE}" | |
| trap - EXIT INT TERM | |
| # Sanity check: the rsa: signing form EMBEDS the signature | |
| # inside data.pkg / packagesite.pkg / meta -- it does NOT | |
| # produce a separate .sig file. (An earlier version of this | |
| # check looked for *.sig and falsely failed on every clean | |
| # build with rsa: signing.) | |
| # | |
| # The legitimate check is "is the data.pkg / packagesite.pkg | |
| # noticeably larger than an unsigned build would produce?" | |
| # Empirically, an unsigned packagesite.pkg for this plugin | |
| # is ~1020 bytes; the embedded RSA-2048 / RSA-4096 signature | |
| # adds ~300-700 bytes. Pick a conservative floor of 1200 | |
| # bytes for packagesite.pkg as the "this clearly has a | |
| # signature in it" threshold. If the catalog is smaller | |
| # than that, signing silently failed. | |
| psize=$(stat -f %z "${REPODIR_ABI}/packagesite.pkg") | |
| echo "packagesite.pkg size = ${psize} bytes (signed: >= 1200; unsigned: ~1020)" | |
| if [ "${psize}" -lt 1200 ]; then | |
| echo "ERROR: packagesite.pkg is ${psize} bytes -- looks unsigned." | |
| echo " Expected: at least 1200 bytes once the RSA signature is" | |
| echo " embedded by pkg-repo's rsa: signing form. A smaller size" | |
| echo " indicates pkg-repo did not actually sign, even though it" | |
| echo " exited cleanly. Check the keyfile content (must be a" | |
| echo " valid unencrypted PEM RSA private key)." | |
| echo " Files actually present:" | |
| ls -la "${REPODIR_ABI}" | |
| exit 1 | |
| fi | |
| ls -la "${REPODIR_ABI}" | |
| - name: Stage pages tree (pkg/${ABI}/ + public key + install helpers) | |
| run: | | |
| set -eux | |
| mkdir -p pages/pkg | |
| cp -R pkg-repo/* pages/pkg/ | |
| # Public key and the .conf snippet go at the root for easy fetch by users. | |
| if [ -f os-netboot/dist/os-netboot.pub ]; then | |
| cp os-netboot/dist/os-netboot.pub pages/os-netboot.pub | |
| fi | |
| cp os-netboot/dist/os-netboot.conf pages/os-netboot.conf | |
| # ----- Index pages ----- | |
| # GitHub Pages does NOT serve directory listings. Without these | |
| # the pkg/ and pkg/<ABI>/ links 404 even though the files inside | |
| # them are reachable directly. Write small index.html files that | |
| # mirror the layout so the site is browseable. | |
| # Top-level landing page. | |
| cat > pages/index.html <<'EOF' | |
| <!doctype html> | |
| <html lang="en"><head> | |
| <meta charset="utf-8"> | |
| <title>os-netboot pkg repo</title> | |
| <style> | |
| body { font-family: system-ui, sans-serif; max-width: 50em; margin: 2em auto; padding: 0 1em; color: #222; } | |
| code { background: #f4f4f4; padding: 0 0.25em; border-radius: 3px; } | |
| ul { line-height: 1.7; } | |
| .small { color: #666; font-size: 0.9em; } | |
| </style> | |
| </head><body> | |
| <h1>os-netboot</h1> | |
| <p>Third-party OPNsense pkg repository for the | |
| <a href="https://github.com/johnwbyrd/os-netboot">os-netboot</a> plugin.</p> | |
| <p>For full install instructions see the | |
| <a href="https://github.com/johnwbyrd/os-netboot#install">README</a>. | |
| Short version:</p> | |
| <ol> | |
| <li>On your OPNsense shell: <code>fetch -o /usr/local/etc/ssl/os-netboot.pub https://johnwbyrd.github.io/os-netboot/os-netboot.pub</code></li> | |
| <li><code>fetch -o /usr/local/etc/pkg/repos/os-netboot.conf https://johnwbyrd.github.io/os-netboot/os-netboot.conf</code></li> | |
| <li><code>pkg update -f</code></li> | |
| <li>On your OPNsense shell: <code>configctl firmware install os-netboot</code></li> | |
| </ol> | |
| <h2>Files served from this site</h2> | |
| <ul> | |
| <li><a href="os-netboot.conf">os-netboot.conf</a> — the pkg repo descriptor</li> | |
| <li><a href="os-netboot.pub">os-netboot.pub</a> — the repo signing public key</li> | |
| <li><a href="pkg/">pkg/</a> — the signed pkg(8) catalog, one subdirectory per FreeBSD ABI</li> | |
| </ul> | |
| <p class="small">Generated by the build-release GitHub Actions workflow on every push to <code>main</code> and on tag pushes.</p> | |
| </body></html> | |
| EOF | |
| # pkg/ index lists each ABI subdirectory. | |
| { | |
| echo '<!doctype html><html lang="en"><head><meta charset="utf-8"><title>os-netboot pkg/</title>' | |
| echo '<style>body{font-family:system-ui,sans-serif;max-width:50em;margin:2em auto;padding:0 1em;color:#222} code{background:#f4f4f4;padding:0 .25em;border-radius:3px}</style>' | |
| echo '</head><body>' | |
| echo '<p><a href="../">← back to os-netboot</a></p>' | |
| echo '<h1>pkg/</h1>' | |
| echo '<p>One catalog per FreeBSD ABI. <code>pkg(8)</code> on the client expands <code>${ABI}</code> automatically; you only see this listing if you visit in a browser.</p>' | |
| echo '<ul>' | |
| for d in pages/pkg/*/; do | |
| [ -d "$d" ] || continue | |
| abi=$(basename "$d") | |
| # The colon characters in ABI strings need URL-escaping in the | |
| # href even though they're legal in filesystem paths. | |
| href=$(printf '%s' "$abi" | sed 's/:/%3A/g') | |
| echo " <li><a href=\"${href}/\"><code>${abi}/</code></a></li>" | |
| done | |
| echo '</ul></body></html>' | |
| } > pages/pkg/index.html | |
| # pkg/<ABI>/ index lists the actual files (with sizes). | |
| for d in pages/pkg/*/; do | |
| [ -d "$d" ] || continue | |
| abi=$(basename "$d") | |
| { | |
| echo '<!doctype html><html lang="en"><head><meta charset="utf-8">' | |
| echo "<title>os-netboot pkg/${abi}/</title>" | |
| echo '<style>body{font-family:system-ui,sans-serif;max-width:50em;margin:2em auto;padding:0 1em;color:#222} code{background:#f4f4f4;padding:0 .25em;border-radius:3px} table{border-collapse:collapse} td,th{padding:.2em 1em;text-align:left} .num{text-align:right;font-variant-numeric:tabular-nums}</style>' | |
| echo '</head><body>' | |
| echo '<p><a href="../">← back to pkg/</a></p>' | |
| echo "<h1>pkg/${abi}/</h1>" | |
| echo '<p>This directory is a pkg(8) repository catalog. The interesting files for a client are <code>packagesite.pkg</code> (the catalog), <code>packagesite.pkg.sig</code> (the signature, if present), and the individual <code>.pkg</code> packages.</p>' | |
| echo '<table><thead><tr><th>name</th><th class="num">size</th></tr></thead><tbody>' | |
| for f in "$d"*; do | |
| [ -f "$f" ] || continue | |
| name=$(basename "$f") | |
| # Skip the index.html we are writing in this loop. | |
| [ "$name" = "index.html" ] && continue | |
| size=$(stat -c %s "$f" 2>/dev/null || stat -f %z "$f") | |
| echo " <tr><td><a href=\"${name}\">${name}</a></td><td class=\"num\">${size}</td></tr>" | |
| done | |
| echo '</tbody></table></body></html>' | |
| } > "$d/index.html" | |
| done | |
| # Final layout for sanity-check in the build log. | |
| ls -laR pages | |
| - name: Publish to gh-pages | |
| if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') | |
| uses: peaceiris/actions-gh-pages@v4 | |
| with: | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| publish_dir: ./pages | |
| publish_branch: gh-pages | |
| keep_files: false | |
| force_orphan: true | |
| - name: Locate the built .txz/.pkg | |
| id: artifact | |
| run: | | |
| set -eux | |
| ARTIFACT=$(ls opnsense-plugins/ftp/netboot/work/pkg/*.pkg opnsense-plugins/ftp/netboot/work/pkg/*.txz 2>/dev/null | head -n1) | |
| echo "path=$ARTIFACT" >> "$GITHUB_OUTPUT" | |
| echo "name=$(basename $ARTIFACT)" >> "$GITHUB_OUTPUT" | |
| - name: Upload artifact (every build) | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ steps.artifact.outputs.name }} | |
| path: ${{ steps.artifact.outputs.path }} | |
| if-no-files-found: error | |
| - name: Attach to GitHub Release (tag pushes only) | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| files: ${{ steps.artifact.outputs.path }} | |
| fail_on_unmatched_files: true | |
| generate_release_notes: true |