Integrate G15 submodules with Debian 13 packaging and devcontainer
This commit is contained in:
commit
795db7924b
28 files changed
+714
No files matched your search
@@ -0,0 +1,2 @@
|
||||
compose.usb.yaml
|
||||
compose.usb.yaml.tmp.*
|
||||
@@ -0,0 +1,9 @@
|
||||
services:
|
||||
dev:
|
||||
image: g15-packaging:latest
|
||||
build:
|
||||
context: ../packaging
|
||||
dockerfile: Dockerfile
|
||||
command: ["sleep", "infinity"]
|
||||
volumes:
|
||||
- ..:/work
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "G15 Debian 13",
|
||||
"dockerComposeFile": ["compose.yaml", "compose.usb.yaml"],
|
||||
"service": "dev",
|
||||
"workspaceFolder": "/work",
|
||||
"initializeCommand": ["bash", "${localWorkspaceFolder}/.devcontainer/initialize.sh"],
|
||||
"overrideCommand": false,
|
||||
"shutdownAction": "stopCompose"
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Require a local native Linux daemon, not SSH/TCP or Docker Desktop's VM.
|
||||
[[ $(uname -s) == Linux ]] || { echo 'Local Linux Docker required.' >&2; exit 1; }
|
||||
if [[ -n ${DOCKER_CONTEXT:-} ]]; then
|
||||
endpoint=$(docker context inspect "$DOCKER_CONTEXT" --format '{{.Endpoints.docker.Host}}')
|
||||
elif [[ -n ${DOCKER_HOST:-} ]]; then
|
||||
endpoint=$DOCKER_HOST
|
||||
else
|
||||
endpoint=$(docker context inspect --format '{{.Endpoints.docker.Host}}')
|
||||
fi
|
||||
[[ $endpoint == unix://* ]] || { echo 'Remote Docker endpoints are unsupported.' >&2; exit 1; }
|
||||
os=$(docker info --format '{{.OperatingSystem}}')
|
||||
[[ $os != *"Docker Desktop"* ]] || { echo 'Docker Desktop USB mapping is unsupported.' >&2; exit 1; }
|
||||
|
||||
# shellcheck source=.devcontainer/usb.sh
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/usb.sh"
|
||||
output="$(dirname "${BASH_SOURCE[0]}")/compose.usb.yaml"
|
||||
tmp=$(mktemp "$output.tmp.XXXXXX")
|
||||
trap 'rm -f "$tmp"' EXIT
|
||||
generate_usb /sys/bus/usb/devices /dev/bus/usb > "$tmp"
|
||||
mv "$tmp" "$output"
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
for command in devcontainer docker node; do
|
||||
command -v "$command" >/dev/null || { echo "Required command missing: $command" >&2; exit 1; }
|
||||
done
|
||||
workspace=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
devcontainer up --workspace-folder "$workspace" > "$tmp/up.json"
|
||||
container=$(node -e '
|
||||
const result = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
|
||||
if (result.outcome !== "success" || !result.containerId) throw Error("CLI startup failed");
|
||||
process.stdout.write(result.containerId);
|
||||
' "$tmp/up.json")
|
||||
docker inspect "$container" > "$tmp/inspect.json"
|
||||
node - "$tmp/inspect.json" "$workspace" <<'NODE'
|
||||
const fs = require('fs');
|
||||
const assert = require('assert/strict');
|
||||
const [container] = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
||||
const host = container.HostConfig;
|
||||
const expected = [];
|
||||
const products = new Set(['c222', 'c225', 'c227', 'c22b', 'c22d', 'c22e']);
|
||||
for (const entry of fs.readdirSync('/sys/bus/usb/devices')) {
|
||||
const base = `/sys/bus/usb/devices/${entry}`;
|
||||
const read = name => fs.readFileSync(`${base}/${name}`, 'utf8').trim();
|
||||
try {
|
||||
if (read('idVendor') !== '046d' || !products.has(read('idProduct'))) continue;
|
||||
const bus = read('busnum'), number = read('devnum');
|
||||
assert.match(bus, /^\d{1,3}$/);
|
||||
assert.match(number, /^\d{1,3}$/);
|
||||
const node = `/dev/bus/usb/${String(Number(bus)).padStart(3, '0')}/${String(Number(number)).padStart(3, '0')}`;
|
||||
if (fs.statSync(node).isCharacterDevice()) expected.push(node);
|
||||
} catch (error) {
|
||||
if (error.code !== 'ENOENT') throw error;
|
||||
}
|
||||
}
|
||||
assert.equal(container.State.Running, true, 'container must be running');
|
||||
assert.equal(host.Privileged, false, 'privileged mode forbidden');
|
||||
assert.notEqual(host.NetworkMode, 'host', 'host networking forbidden');
|
||||
assert.deepEqual(host.DeviceCgroupRules || [], [], 'device cgroup overrides forbidden');
|
||||
assert.deepEqual(host.DeviceRequests || [], [], 'additional device requests forbidden');
|
||||
const devices = host.Devices || [];
|
||||
assert.deepEqual(devices.map(device => device.PathOnHost).sort(), expected.sort(),
|
||||
'USB mappings must match current host allowlist; reconnects may require container recreation');
|
||||
for (const device of devices) {
|
||||
assert.equal(device.PathInContainer, device.PathOnHost, 'device remapping forbidden');
|
||||
assert.equal(device.CgroupPermissions, 'rw', 'only rw device access allowed');
|
||||
}
|
||||
// Only the checkout bind and the CLI's optional editor volume are permitted.
|
||||
assert(container.Mounts.some(mount => mount.Type === 'bind' &&
|
||||
mount.Source === process.argv[3] && mount.Destination === '/work'), 'workspace bind missing');
|
||||
for (const mount of container.Mounts) {
|
||||
assert((mount.Type === 'bind' && mount.Source === process.argv[3] && mount.Destination === '/work') ||
|
||||
(mount.Type === 'volume' && mount.Name === 'vscode' && mount.Destination === '/vscode'),
|
||||
`unexpected mount: ${mount.Source} -> ${mount.Destination}`);
|
||||
}
|
||||
console.log(`PASS: real Docker configuration; ${devices.length} exact allowed USB mapping(s)`);
|
||||
NODE
|
||||
# Expand PWD inside the container, not on the host.
|
||||
# shellcheck disable=SC2016
|
||||
devcontainer exec --workspace-folder "$workspace" bash -c \
|
||||
'set -euo pipefail; test "$PWD" = /work; test -f .devcontainer/devcontainer.json; test ! -e /dev/uinput; printf "PASS: CLI exec and no /dev/uinput; no keyboard I/O performed\n"'
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Initialization supplies the real host sysfs and USB device paths.
|
||||
generate_usb() {
|
||||
local sys=$1 dev=$2 entry vendor product bus number node
|
||||
local -a nodes=()
|
||||
for entry in "$sys"/*; do
|
||||
[[ -r $entry/idVendor && -r $entry/idProduct ]] || continue
|
||||
read -r vendor < "$entry/idVendor" || continue
|
||||
read -r product < "$entry/idProduct" || continue
|
||||
[[ $vendor == 046d ]] || continue
|
||||
case $product in c222|c225|c227|c22b|c22d|c22e) ;; *) continue ;; esac
|
||||
[[ -r $entry/busnum && -r $entry/devnum ]] || continue
|
||||
read -r bus < "$entry/busnum" || continue
|
||||
read -r number < "$entry/devnum" || continue
|
||||
[[ $bus =~ ^[0-9]{1,3}$ && $number =~ ^[0-9]{1,3}$ ]] || continue
|
||||
printf -v node '%s/%03d/%03d' "$dev" "$((10#$bus))" "$((10#$number))"
|
||||
[[ -c $node ]] || continue
|
||||
nodes+=("$node")
|
||||
done
|
||||
printf 'services:\n dev:\n'
|
||||
if ((${#nodes[@]})); then
|
||||
printf ' devices:\n'
|
||||
for node in "${nodes[@]}"; do
|
||||
printf ' - "%s:%s:rw"\n' "$node" "$node"
|
||||
done
|
||||
else
|
||||
printf ' devices: []\n'
|
||||
fi
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
name: Debian 13 packages
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'packaging/**'
|
||||
- '.github/**'
|
||||
- '.devcontainer/**'
|
||||
- '.gitmodules'
|
||||
- '.gitignore'
|
||||
- 'README.md'
|
||||
- 'libg15'
|
||||
- 'libg15render'
|
||||
- 'g15daemon'
|
||||
- 'g15macro'
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Build and validate in Debian 13
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for script in packaging/*.sh packaging/overlays/v1/*.sh .devcontainer/*.sh; do bash -n "$script"; done
|
||||
# Real devcontainer CLI validation is a separate local check; see README.md.
|
||||
docker run --rm -v "$GITHUB_WORKSPACE/.github:/repo/.github:ro" -w /repo \
|
||||
rhysd/actionlint@sha256:887a259a5a534f3c4f36cb02dca341673c6089431057242cdc931e9f133147e9 \
|
||||
.github/workflows/debian.yml
|
||||
mkdir -p "$RUNNER_TEMP/debs"
|
||||
bash packaging/run.sh "$RUNNER_TEMP/debs"
|
||||
docker run --rm -v "$GITHUB_WORKSPACE/packaging:/checks/packaging:ro" \
|
||||
-v "$GITHUB_WORKSPACE/.devcontainer:/checks/.devcontainer:ro" \
|
||||
-w /checks g15-packaging bash -c 'shellcheck packaging/*.sh packaging/overlays/v1/*.sh .devcontainer/*.sh'
|
||||
test -z "$(git submodule foreach --quiet --recursive 'git status --porcelain')"
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: debian13-amd64
|
||||
path: ${{ runner.temp }}/debs/*
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
if: github.event_name == 'release'
|
||||
needs: build
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
name: debian13-amd64
|
||||
path: artifacts
|
||||
- name: Attach verified packages and sources
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
||||
run: |
|
||||
cd artifacts
|
||||
sha256sum -c SHA256SUMS
|
||||
gh release upload "$RELEASE_TAG" -- ./*
|
||||
@@ -0,0 +1,3 @@
|
||||
/.vscode/
|
||||
/dist/
|
||||
/distro.zip
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
[submodule "libg15"]
|
||||
path = libg15
|
||||
url = https://gt.uo1.net/mikhail/libg15
|
||||
[submodule "g15daemon"]
|
||||
path = g15daemon
|
||||
url = https://gt.uo1.net/mikhail/g15daemon
|
||||
[submodule "g15macro"]
|
||||
path = g15macro
|
||||
url = https://gt.uo1.net/mikhail/g15macro
|
||||
[submodule "libg15render"]
|
||||
path = libg15render
|
||||
url = https://gt.uo1.net/mikhail/libg15render
|
||||
[submodule "g15stats"]
|
||||
path = g15stats
|
||||
url = https://gt.uo1.net/mikhail/g15stats
|
||||
@@ -0,0 +1,77 @@
|
||||
# G15 tools: Debian 13 development and packages
|
||||
|
||||
This outer repository builds and packages four pinned Git submodules: `libg15`,
|
||||
`libg15render`, `g15daemon`, and `g15macro`. A fifth submodule, `g15stats`, is
|
||||
present but is not integrated into the build or packaging pipeline.
|
||||
Initialize a fresh clone with
|
||||
`git submodule update --init --recursive`. Builds reject missing, dirty, or
|
||||
misaligned submodules; they export committed, unpatched Debian sources and
|
||||
never modify submodule working trees or refs.
|
||||
|
||||
## Build and verify release packages
|
||||
|
||||
Create an empty directory outside the checkout (for example with `mktemp -d`)
|
||||
and run `bash packaging/run.sh /absolute/path/to/that-directory`.
|
||||
The runner uses disposable Debian 13 Docker containers to build all four
|
||||
projects in dependency order, install their own development packages before
|
||||
downstream builds, test distro replacement, and rebuild all four source packages
|
||||
without the checkout mounted. It mounts only an allowlist of build inputs;
|
||||
`.vscode` is not exposed to release build containers.
|
||||
|
||||
Outputs include eight binary packages, debug symbols, four standalone Debian
|
||||
source packages, build metadata, submodule provenance and checksums. See
|
||||
`packaging/NOTES.md` for versions, overlays, QA and release behavior. The old
|
||||
outer `git archive` ZIP is not a source distribution: it omits submodule content.
|
||||
Use the verified Debian source artifacts instead.
|
||||
|
||||
**Safety limitation:** legacy macro memory/configuration handling has known
|
||||
preexisting defects. Compatibility overlays do not repair the macro engine.
|
||||
Do not run it as root or use untrusted configuration. Build and install tests
|
||||
are not hardware or security certification; see `packaging/overlays/v1/README.md`.
|
||||
|
||||
## Recommended: Debian 13 container and packages
|
||||
|
||||
Use local Linux Docker Engine with Compose and the Dev Containers CLI (or
|
||||
VS Code's Reopen in Container). From the repository root, run
|
||||
`devcontainer up --workspace-folder .`, then
|
||||
`devcontainer exec --workspace-folder . bash`.
|
||||
The container reuses `packaging/Dockerfile` (Debian 13/trixie), with GCC,
|
||||
Make, Autotools, Debian packaging tools, libusb-1.0, FreeType and XTest headers.
|
||||
The image does not preinstall distro G15 development libraries: package builds
|
||||
install the versions built from the submodules. Its normal packaging command
|
||||
is replaced with `sleep infinity`.
|
||||
No host package installation is needed beyond your existing container tooling.
|
||||
|
||||
For distributable packages, follow `packaging/NOTES.md`
|
||||
using its disposable build container, not the development shell: the packaging
|
||||
scripts install their outputs.
|
||||
|
||||
Initialization regenerates ignored `.devcontainer/compose.usb.yaml`, including
|
||||
only existing exact USB character nodes for Logitech `046d` keyboards
|
||||
`c222`, `c225`, `c227`, `c22b`, `c22d`, `c22e`. No keyboard is required for
|
||||
build-only use. Speakers and gamepanels are excluded. Remote SSH/TCP Docker
|
||||
endpoints and Docker Desktop are rejected; use a native local Linux daemon
|
||||
whose USB paths match this host (do not use a forwarded Unix socket).
|
||||
After reconnecting a keyboard, recreate the container with
|
||||
`devcontainer up --workspace-folder . --remove-existing-container` to refresh
|
||||
device mappings. A device disappearing during startup may require retrying.
|
||||
|
||||
There are no whole-USB mounts, wildcard device rules, privileged mode, Docker
|
||||
socket mounts, or host networking. `/dev/uinput` is deliberately omitted:
|
||||
host input injection does not work by default. If needed, manually configure
|
||||
only that device and host permissions after reviewing the security implications.
|
||||
Rootless Docker/device permissions may further limit hardware access. Building
|
||||
and container checks do not establish keyboard functionality; do not run a
|
||||
second daemon against a keyboard already owned by a host service.
|
||||
|
||||
Run `bash .devcontainer/test.sh` with local Linux Docker Engine, Compose,
|
||||
the installed Dev Containers CLI and host Node.js. It runs real CLI `up` and
|
||||
`exec` with the absolute checkout path, then checks `docker inspect` against
|
||||
the current host sysfs allowlist and rejects unexpected devices, mounts,
|
||||
device cgroup overrides, privileged mode and host networking. It also checks
|
||||
that `/dev/uinput` is absent. No keyboard is required; an empty allowlist must
|
||||
produce no device mappings. The container is left running for development.
|
||||
If USB devices changed, recreate the container as described above and rerun.
|
||||
These configuration checks perform no keyboard I/O, USB driver changes or
|
||||
keyboard functional validation. See `packaging/NOTES.md` for release test
|
||||
coverage and safety limitations.
|
||||
Submodule
+1
Submodule g15daemon added at 21349f7892.
Submodule
+1
Submodule g15macro added at b2f8cd2195.
Submodule
+1
Submodule g15stats added at 95de11a526.
Submodule
+1
Submodule libg15 added at 1cba4225b6.
Submodule
+1
Submodule libg15render added at b29a7d05ea.
@@ -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"]
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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.
|
||||
@@ -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){
|
||||
@@ -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.
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
|
||||
|
||||
%:
|
||||
dh $@
|
||||
@@ -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.'
|
||||
@@ -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'
|
||||
@@ -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.'
|
||||
Reference in new issue
Block a user