Skip to content

Clean up three build-log warnings #35

Clean up three build-log warnings

Clean up three build-log warnings #35

Workflow file for this run

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> &mdash; the pkg repo descriptor</li>
<li><a href="os-netboot.pub">os-netboot.pub</a> &mdash; the repo signing public key</li>
<li><a href="pkg/">pkg/</a> &mdash; 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="../">&larr; 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="../">&larr; 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