Integrate G15 submodules with Debian 13 packaging and devcontainer

This commit is contained in:
Mikhail Yevchenko committed 2026-09-07 15:44:03 +03:00
commit 795db7924b
28 files changed
+714

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
FROM debian:trixie-slim
ENV DEBIAN_FRONTEND=noninteractive
RUN printf 'deb-src http://deb.debian.org/debian trixie main\n' > /etc/apt/sources.list.d/g15-source.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends build-essential autoconf automake libtool pkg-config debhelper dpkg-dev libusb-1.0-0-dev git ca-certificates fakeroot lintian devscripts dh-sequence-movetousr libfreetype-dev libxtst-dev quilt shellcheck \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /work
CMD ["bash", "packaging/container-build.sh"]
+106
View File
@@ -0,0 +1,106 @@
# Debian 13 packaging
## Running
Use a regular, initialized Git clone on Linux with Docker Engine. Create an
empty output directory with `mktemp -d`, then run
`bash packaging/run.sh /absolute/path/to/output`. No host package installation,
API keys or `.env` are needed. Build scripts install packages and require a
disposable root container; do not run them in the hardware-enabled devcontainer.
`G15_BUILD_IMAGE` optionally changes the shared image tag.
`run.sh` builds `packaging/Dockerfile`, mounts only Git metadata, the four
submodules and `packaging/` read-only, then runs build and lifecycle tests.
A second fresh container mounts only artifacts and `rebuild.sh`, proving that
the source packages do not need this repository. All working sources are in
container temporary directories. Outputs and logs should remain outside the
checkout. Linked Git worktrees with external gitdirs are not supported by the
allowlisted mount layout. No container receives USB, uinput or a Docker socket.
## Sources, patches and versions
Order: `libg15`, `libg15render`, `g15daemon`, `g15macro`. Each stage installs
its own runtime and development packages before the next stage resolves build
dependencies. The base image does not supply distro G15 development packages.
Each of these four build submodules must match its indexed gitlink and be clean.
The additional `g15stats` submodule is not built, packaged or recorded in package
provenance. Git
archives export immutable HEADs, not working-tree contents. No checkout,
patching, committing, ref updates or pushing occurs in submodules.
Orig tarballs contain the complete committed upstream tree without `debian/`;
tracked generated Autotools inputs are preserved rather than guessed away.
No Git metadata, untracked objects or local build state is exported. Debian
quilt patches stay unapplied in orig tarballs and are applied by dpkg-source.
The existing libusb-1.0 port and daemon buffer-overflow fix are retained.
`overlays/v1/` is the explicit local compatibility layer, applied only in
temporary copies. It backports daemon -14.1's t64 transition from -14, adds
G110 systemd hotplug activation, and replaces macro's historical compat-5 CDBS
packaging with debhelper 13. Macro receives only two upstream build fixes:
remove bare `-I` and make an implicit `int` parameter explicit. All changes are
included in the Debian tarballs; macro C/build changes are quilt patches.
The source packages carry `debian/README.local-build` and original attribution.
The daemon control file and SONAME override are compared against the official
`g15daemon=1.9.5.3-14.1` source obtained through authenticated APT indexes, before
changing the local Maintainer. This preserves architecture-dependent
`${t64:Provides}`, `Replaces: libg15daemon-client1` and versioned `Breaks`.
Runtime package filenames/manpages are renamed, without changing SONAME.
The inherited `dh_installsystemd --no-start` remains in force.
Versions use `<upstream>+g15.git<12-character-commit>-1`. Macro's upstream
version is 1.0.5 (configure.ac), not the historical Debian changelog's 1.0.3.
Tests require versions above every available trixie candidate, including binNMUs.
There is no trixie macro candidate: macro is tested as a fresh installation,
not falsely reported as a distro upgrade. Increment the local Debian revision
when changing overlays for an already-published source commit. Git hashes are
identifiers, not monotonically increasing versions between local releases.
Orig tar ordering, ownership, timestamps and changelog dates are deterministic
from the source commit; `SOURCE_DATE_EPOCH` is set. `.buildinfo` records installed
dependencies. Debian mirrors and the trixie image intentionally float, so this
is repeatable source export and standalone rebuilding, NOT a claim of bit-for-bit
binary reproducibility. Keep complete released artifacts. No signing is performed.
## Outputs and gates
Eight packages: `libg15-1`, `libg15-dev`, `libg15render1`, `libg15render-dev`,
`g15daemon`, `libg15daemon-client1t64`, `libg15daemon-client-dev`, `g15macro`;
plus five debug-symbol packages on amd64. Each of the four sources has a `.dsc`,
orig tarball, Debian tarball, `.buildinfo` and `.changes`. `SUBMODULES` records
pins and `SHA256SUMS` covers all artifacts. The outer Git ZIP is not a substitute.
Tests check exact package names, candidate ordering, checksums, Lintian errors,
clean installs, distro upgrades, all three libraries' exported symbols and
SONAMEs, executable linking, development headers/linking, t64 fields, service
and udev installation, macro help/version and purge cleanup. Source rebuilds
install newly rebuilt dependencies, not binaries from the first build.
CI uses recursive checkout on relevant PRs, manual dispatch and published
releases. Build permissions are read-only; a separate write-permission job
downloads the verified artifact and checks checksums before uploading release
assets. All actions are commit-pinned. Existing assets are not overwritten.
Lint failures, build failures and source rebuild failures block publication.
## Safety and validation limits
Development-container configuration is checked separately with
`bash .devcontainer/test.sh` using local Docker, the Dev Containers CLI and
host Node.js (see `README.md`). It starts the real container, inspects exact
USB mappings against host sysfs and checks isolation, then exercises CLI exec
without keyboard I/O or driver changes. Package CI syntax-checks this script
but does not run it; package gates do not imply devcontainer or keyboard
functional validation.
Risk: high (legacy native code and distro replacement). Macro has preexisting
memory initialization, recording bounds, configuration-index and string-copy
defects. These are explicitly outside this build-compatibility change; compiler
warnings are not suppressed. Do not run macro as root or load untrusted config.
Its historical Debian archive import did not verify the descriptor signature;
that provenance limitation remains in `debian/README.source`.
Hardware operation, X11 recording/playback, hotplug under booted systemd,
32-bit t64 ABI behavior and actual GitHub publishing are not covered by local
package tests. No runtime-safety or security certification is claimed. Passing
packaging gates does not resolve those upstream security concerns.
+63
View File
@@ -0,0 +1,63 @@
#!/bin/bash
# Installs dependencies and outputs: disposable Debian 13 containers ONLY.
set -euo pipefail
export LC_ALL=C
root=$(cd -- "$(dirname -- "$0")/.." && pwd)
[[ -f /.dockerenv && $EUID == 0 ]] || { echo 'Disposable root Docker container required' >&2; exit 1; }
# shellcheck source=/dev/null
. /etc/os-release
[[ $ID == debian && $VERSION_ID == 13 ]] || exit 1
out=$(realpath "${1:?empty artifact directory required}")
[[ -d $out && -z $(find "$out" -mindepth 1 -print -quit) ]] || exit 1
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT
export DEB_BUILD_OPTIONS=${DEB_BUILD_OPTIONS:-parallel=2}
export DEBFULLNAME='G15 packaging contributors' DEBEMAIL='noreply@example.invalid'
git config --global --add safe.directory "$root"
for source in libg15 libg15render g15daemon g15macro; do
git config --global --add safe.directory "$root/$source"
pin=$(git -C "$root" ls-files --stage -- "$source" | awk '$1 == 160000 {print $2}')
[[ -n $pin && $(git -C "$root/$source" rev-parse HEAD) == "$pin" ]]
[[ -z $(git -C "$root/$source" status --porcelain --untracked-files=all) ]]
done
git -C "$root" submodule status --recursive -- libg15 libg15render g15daemon g15macro > "$out/SUBMODULES"
if grep -q '^[+U-]' "$out/SUBMODULES"; then echo 'Submodule pin mismatch or missing module' >&2; exit 1; fi
for source in libg15 libg15render g15daemon g15macro; do
mkdir "$work/$source"
cd "$work/$source"
sha=$(git -C "$root/$source" rev-parse HEAD)
export SOURCE_DATE_EPOCH
SOURCE_DATE_EPOCH=$(git -C "$root/$source" show -s --format=%ct HEAD)
mkdir tree
git -C "$root/$source" archive HEAD | tar -x -C tree
[[ ! -d tree/.pc ]] || { echo 'Expected unapplied quilt checkout' >&2; exit 1; }
base=$(dpkg-parsechangelog -l tree/debian/changelog -S Version)
upstream=${base%-*}
[[ $source != g15macro ]] || upstream=1.0.5
upstream+=+g15.git${sha:0:12}
version=$upstream-1
mv tree "$source-$upstream"
tree=$PWD/$source-$upstream
tar --sort=name --mtime="@$SOURCE_DATE_EPOCH" --owner=0 --group=0 --numeric-owner \
--exclude="$source-$upstream/debian" -cJf "${source}_${upstream}.orig.tar.xz" "$source-$upstream"
(cd "$tree" && bash "$root/packaging/overlays/v1/apply.sh" "$source" "$root")
if [[ $source == g15daemon ]]; then
mkdir official
(cd official && apt-get source --download-only g15daemon=1.9.5.3-14.1 && dpkg-source --skip-patches -x ./*.dsc source)
diff -u official/source/debian/control "$tree/debian/control"
diff -u official/source/debian/libg15daemon-client1t64.lintian-overrides "$tree/debian/libg15daemon-client1t64.lintian-overrides"
fi
cd "$tree"
sed -i "s/^Maintainer:.*/Maintainer: $DEBFULLNAME <$DEBEMAIL>/" debian/control
dch --newversion "$version" --distribution trixie --force-distribution \
"Export $source commit $sha; apply documented outer packaging overlay v1."
sed -i "0,/^ -- /s/^ -- .*/ -- $DEBFULLNAME <$DEBEMAIL> $(date -u -R -d "@$SOURCE_DATE_EPOCH")/" debian/changelog
cp "$root/packaging/overlays/v1/README.md" debian/README.local-build
apt-get build-dep -y .
dpkg-buildpackage -us -uc -sa
cd ..
cp ./*.deb ./*.dsc ./*.tar.* ./*.buildinfo ./*.changes "$out/"
apt-get install -y ./*.deb
done
cd "$out"
sha256sum -- *.deb *.dsc *.tar.* *.buildinfo *.changes SUBMODULES > SHA256SUMS
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
set -euo pipefail
# This entry point installs packages: use only in a disposable container.
[[ -f /.dockerenv && $EUID == 0 ]] || { echo 'Disposable root Docker container required' >&2; exit 1; }
apt-get update
bash packaging/build.sh /out
bash packaging/test.sh /out
+3
View File
@@ -0,0 +1,3 @@
# Repository G110 backend (extra keys, no LCD).
SUBSYSTEM=="usb", ATTR{idVendor}=="046d", ATTR{idProduct}=="c22b", TAG+="systemd", ENV{SYSTEMD_WANTS}+="g15daemon.service"
+25
View File
@@ -0,0 +1,25 @@
# Local build overlay v1 (2026-09-07)
Applied only to temporary git archives. Original submodules are never patched.
All upstream Debian quilt series, including the daemon client buffer-overflow
fix and libg15 libusb-1.0 conversion, remain intact. Orig tarballs contain the
unpatched committed source; local upstream changes are additional quilt patches.
Debian metadata changes are carried in each emitted Debian tarball.
The daemon overlay backports the Debian 1.9.5.3-14.1 t64 control fields and
file renames; the builder compares control and the SONAME override against the
authenticated trixie source archive. G110 hotplug activation is added separately.
Macro: replace historical compat-5 CDBS with debhelper 13, remove obsolete
configure switches, obsolete watch file and redundant install manifest. Keep
copyright, historical provenance, manual and changelog. Autoreconf regenerates
the stale generated build system. The build patch removes an invalid bare -I
and makes map_gkey's historical implicit int parameter explicit (GCC 14).
Package upstream version is 1.0.5, matching configure.ac, not historical 1.0.3.
This is build compatibility, NOT a macro-engine security repair. Preexisting
unchecked configuration indices, string copies, recording bounds and incorrect
memory initialization remain. Do not run macro as root or consume untrusted
configuration. Hardware, X11 playback and booted systemd require separate tests.
The imported historical macro archive signature was not verified; its existing
debian/README.source preserves that limitation. No security certification is made.
+27
View File
@@ -0,0 +1,27 @@
#!/bin/bash
# Called with cwd in a disposable git archive, NEVER a submodule.
set -euo pipefail
source=${1:?source} root=${2:?repository}
[[ ! -e .git && -f debian/control ]] || exit 1
case $source in
libg15|libg15render) ;;
g15daemon)
# shellcheck disable=SC2016 # Debian substvars must remain literal.
sed -i 's/^Build-Depends:$/Build-Depends: dpkg-dev (>= 1.22.5),/; s/libg15daemon-client1/libg15daemon-client1t64/g; /Package: libg15daemon-client1t64/a Provides: ${t64:Provides}\nReplaces: libg15daemon-client1\nBreaks: libg15daemon-client1 (<< ${source:Version})' debian/control
mv debian/libg15daemon-client1.install debian/libg15daemon-client1t64.install
mv debian/libg15daemon-client1.manpages debian/libg15daemon-client1t64.manpages
printf '%s\n' 'libg15daemon-client1t64: package-name-doesnt-match-sonames libg15daemon-client1' > debian/libg15daemon-client1t64.lintian-overrides
cat "$root/packaging/g110.udev" >> debian/g15daemon.udev
;;
g15macro)
cp "$root/packaging/overlays/v1/g15macro.control" debian/control
cp "$root/packaging/overlays/v1/g15macro.rules" debian/rules
chmod +x debian/rules
rm debian/compat debian/g15macro.install debian/watch
mkdir -p debian/source debian/patches
printf '3.0 (quilt)\n' > debian/source/format
cp "$root/packaging/overlays/v1/g15macro-build.patch" debian/patches/local-build.patch
printf 'local-build.patch\n' > debian/patches/series
;;
*) exit 1 ;;
esac
@@ -0,0 +1,19 @@
Description: Fix legacy build include switch and implicit-int declaration
Author: G15 packaging contributors
Forwarded: not-needed
Last-Update: 2026-09-07
--- a/Makefile.am
+++ b/Makefile.am
@@ -11,7 +11,6 @@
image_DATA = ${top_builddir}/g15macro.wbmp
LIBS = -Wl,--no-add-needed -lg15daemon_client -lg15render -lX11 -lXtst -lpthread
-IMCLUDES = -I
g15macro_SOURCES = g15macro.c
--- a/g15macro.c
+++ b/g15macro.c
@@ -191,1 +191,1 @@
-int map_gkey(keystate){
+int map_gkey(int keystate){
+15
View File
@@ -0,0 +1,15 @@
Source: g15macro
Section: utils
Priority: optional
Maintainer: G15 packaging contributors <noreply@example.invalid>
Build-Depends: debhelper-compat (= 13), libxtst-dev, libg15-dev, libg15render-dev, libg15daemon-client-dev
Standards-Version: 4.7.0
Homepage: https://sourceforge.net/projects/g15tools/
Rules-Requires-Root: no
Package: g15macro
Architecture: any
Depends: g15daemon, ${shlibs:Depends}, ${misc:Depends}
Description: Macro recording/playback application for G15Daemon
G15macro records and plays back keyboard macros using the M and G keys
on Logitech G15 keyboards in an X11 session.
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/make -f
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
%:
dh $@
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
# Standalone source QA: mount artifacts and this script, NOT the repository.
set -euo pipefail
export LC_ALL=C
[[ -f /.dockerenv && $EUID == 0 ]] || exit 1
out=$(realpath "${1:?artifact directory}")
(cd "$out" && sha256sum -c SHA256SUMS)
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT
export DEB_BUILD_OPTIONS=parallel=2
for source in libg15 libg15render g15daemon g15macro; do
mkdir "$work/$source"
cd "$work/$source"
dsc=("$out/${source}_"*.dsc)
[[ ${#dsc[@]} == 1 ]]
dpkg-source -x "${dsc[0]}" tree
cd tree
apt-get build-dep -y .
dpkg-buildpackage -us -uc -b
cd ..
apt-get install -y ./*.deb
done
echo 'PASS: all four source artifacts rebuilt standalone in dependency order.'
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
# Host orchestration: no package installation or generated source in workspace.
set -euo pipefail
root=$(cd -- "$(dirname -- "$0")/.." && pwd)
out=$(realpath "${1:?create an empty output directory outside the checkout}")
[[ -d $out && -z $(find "$out" -mindepth 1 -print -quit) ]] || exit 1
case $out in "$root"|"$root"/*) echo 'Output must be outside the checkout' >&2; exit 1 ;; esac
[[ -d $root/.git ]] || { echo 'Regular clone required (external gitdirs unsupported)' >&2; exit 1; }
image=${G15_BUILD_IMAGE:-g15-packaging}
docker build -t "$image" -f "$root/packaging/Dockerfile" "$root/packaging"
# Allowlist mounts: .vscode and other unrelated local files are not exposed.
mounts=()
for path in .git .gitmodules packaging libg15 libg15render g15daemon g15macro; do
mounts+=(--mount "type=bind,src=$root/$path,dst=/work/$path,readonly")
done
docker run --rm "${mounts[@]}" --mount "type=bind,src=$out,dst=/out" "$image"
docker run --rm --mount "type=bind,src=$out,dst=/out,readonly" \
--mount "type=bind,src=$root/packaging/rebuild.sh,dst=/rebuild.sh,readonly" \
"$image" bash -c 'apt-get update && bash /rebuild.sh /out'
+86
View File
@@ -0,0 +1,86 @@
#!/bin/bash
# Destructive package lifecycle checks, only in a disposable container.
set -euo pipefail
export LC_ALL=C
[[ -f /.dockerenv && $EUID == 0 ]] || exit 1
out=$(realpath "${1:?artifact directory required}")
cd "$out"
sha256sum -c SHA256SUMS
packages=(libg15-1 libg15-dev libg15render1 libg15render-dev g15daemon libg15daemon-client1t64 libg15daemon-client-dev g15macro)
distro=()
for package in "${packages[@]}"; do
files=("${package}"_*.deb)
[[ ${#files[@]} == 1 && -f ${files[0]} ]]
[[ $(dpkg-deb -f "${files[0]}" Package) == "$package" ]]
candidate=$(apt-cache madison "$package" | awk 'NR == 1 {print $3}')
if [[ -n $candidate ]]; then
dpkg --compare-versions "$(dpkg-deb -f "${files[0]}" Version)" gt "$candidate"
distro+=("$package=$candidate")
else
[[ $package == g15macro ]] || exit 1
echo 'g15macro has no trixie candidate; fresh installation only.'
fi
done
lintian --fail-on error ./*.changes
scratch=$(mktemp -d)
trap 'rm -rf "$scratch"' EXIT
multiarch=$(dpkg-architecture -qDEB_HOST_MULTIARCH)
libraries=(libg15 libg15render libg15daemon_client)
mapfile -t allpackages < <(for deb in ./*.deb; do dpkg-deb -f "$deb" Package; done)
purge() {
local package
local -a installed=()
for package in "${allpackages[@]}"; do
if dpkg-query -W -f='${db:Status-Status}' "$package" 2>/dev/null | grep -qx installed; then
installed+=("$package")
fi
done
if ((${#installed[@]})); then apt-get purge -y "${installed[@]}"; fi
}
smoke() {
local package files
for package in "${packages[@]}"; do
files=("${package}"_*.deb)
[[ $(dpkg-query -W -f='${Version}' "$package") == "$(dpkg-deb -f "${files[0]}" Version)" ]]
done
for binary in /usr/sbin/g15daemon /usr/bin/g15macro; do
ldd "$binary" > "$scratch/ldd"
cat "$scratch/ldd"
if grep -q 'not found' "$scratch/ldd"; then exit 1; fi
done
for library in "${libraries[@]}"; do
readelf -d "/usr/lib/$multiarch/$library.so.1" | grep "SONAME.*$library.so.1"
done
test -f /usr/lib/systemd/system/g15daemon.service
grep -q c22b /usr/lib/udev/rules.d/*g15daemon.rules
printf '#include <libg15.h>\n#include <libg15render.h>\n#include <g15daemon_client.h>\nint main(void) { g15canvas c; g15r_initCanvas(&c); return g15daemon_version() == 0; }\n' | \
cc -x c - -Wl,--no-as-needed -lg15 -lg15render -lg15daemon_client -o "$scratch/client-smoke"
"$scratch/client-smoke"
/usr/bin/g15macro --version
/usr/bin/g15macro --help
}
purge
apt-get install -y ./*.deb
smoke
purge
apt-get install -y "${distro[@]}"
for library in "${libraries[@]}"; do
nm -D --defined-only "/usr/lib/$multiarch/$library.so.1" | awk '{print $3}' | sort > "$scratch/$library.before"
done
apt-get install -y ./*.deb
for library in "${libraries[@]}"; do
nm -D --defined-only "/usr/lib/$multiarch/$library.so.1" | awk '{print $3}' | sort > "$scratch/$library.after"
comm -23 "$scratch/$library.before" "$scratch/$library.after" > "$scratch/$library.missing"
test ! -s "$scratch/$library.missing" || { cat "$scratch/$library.missing"; exit 1; }
done
version=$(dpkg-query -W -f='${Version}' libg15daemon-client1t64)
[[ $(dpkg-query -W -f='${Replaces}' libg15daemon-client1t64) == libg15daemon-client1 ]]
[[ $(dpkg-query -W -f='${Breaks}' libg15daemon-client1t64) == "libg15daemon-client1 (<< $version)" ]]
# amd64 retains the old ABI; dpkg generates architecture-dependent t64 Provides.
if [[ $(dpkg --print-architecture) == amd64 ]]; then
[[ $(dpkg-query -W -f='${Provides}' libg15daemon-client1t64) == "libg15daemon-client1 (= $version)" ]]
fi
smoke
purge
test ! -e /usr/lib/systemd/system/g15daemon.service
echo 'PASS: fresh install, distro upgrade, symbols, linking, t64 metadata and purge; no hardware I/O.'