The upstream and rebuild syncs push with GITHUB_TOKEN, so GitHub holds
their build and test runs for approval. Their approve job only released
those runs once a maintainer had applied build-approved, and never ran
for the push that opened the PR, so every sync PR sat waiting.
The sync now labels its own PR build-approved, and the approve job runs
for created PRs as well as updated ones.
The dropbox watch failed with "no matching releases" on some runs
(e.g. actions run 37236931512). It is not the network: Dropbox's download
redirect sends a share of requests to a newer build the pattern rejects
on purpose. On 2026-10-04, 96 of 100 HEADs ended at
dropbox-lnx.x86_64-272.4.3798 and 4 at 274.3.4801, which is not an x.4.y
stable build. The redirect provider made one probe, so one unlucky
answer failed the whole watch.
It now probes up to five times and keeps the first final URL that
matches, and curl gets --retry 2 as Fetcher.file already has. Live,
100 discovers against Dropbox all found 272.4.3798 in 103 probes. Two
tests cover a rollout answer before the stable one and every probe
missing.
* Apply the Intel IPU7 camera's ISP tuning and fix its gain, range and exposure
The XPS 14 / 16 webcam looked soft and grainy because almost none of the
image pipeline was tuned:
- 0011: the graph asks for ISP tuning mode 4, which the OV08X40 tuning
does not carry, so the generic AIC found no tunings and noise
reduction, TNR and sharpening ran on library defaults. Fall back to
the tuning's default ISP container.
- 0010: AIQ emits the raw analog gain register code, which the in-tree
ov08x40 driver halves, so the sensor ran at twice the gain AE and the
ISP noise model assumed.
- 0008: the HAL ignored the requested YUV range and always produced
full-range frames that consumers decode as limited range.
- 0009 + relay config: icamerasrc pinned auto exposure to 1/30 s; a new
fps-range property lets AE lengthen frames in dim light, with gain
capped at 27 dB.
* Guard fps-range against NULL and correct two descriptions
Setting icamerasrc's new fps-range property to NULL, its own default, handed NULL to gst_camerasrc_parse_range, which crashed in strlen(). NULL now leaves the last range in effect, because the HAL has no way to drop a range once set. The value the relay sets goes through unchanged.
The relay comment said AE raises gain past 27 dB once frames reach 15 fps, but gain-range becomes a hard ISO ceiling (manual_iso_max), so gain never goes past it. The 0008 message credited the sensor JSON's yuvColorRangeMode, which only the mock HAL reads.
Co-Authored-By: Codex XHigh <noreply@openai.com>
* Free fps-range when icamerasrc is destroyed
finalize never freed the string the fps-range setter allocates, so every icamerasrc element that had the property set leaked it. The other string properties leak the same way upstream and are left to an upstream fix for all of them.
Co-Authored-By: Codex XHigh <noreply@openai.com>
* Harden IPU7 PSYS userptr pinning
(cherry picked from commit 5f1d0013b0e37d9229dfb906806dee0e98199035)
* Avoid unverified IPU7 permission assurances
(cherry picked from commit 9ce97d9788f2be508743fe785f8434ccfec7842e)
* Check IPU7 against Omarchy headers without a runtime dependency
Use linux-omarchy-headers only for check(), avoiding Arch headers on installed Omarchy systems and matching the supported kernel build configuration.
Signed-off-by: Afonso Oliveira <afonso.oliveira707@gmail.com>
(cherry picked from commit d0d382c07ea99eca5bb5c52240841db779d24b14)
* Test IPU7 build-only headers and kernel-tree selection
Signed-off-by: Afonso Oliveira <afonso.oliveira707@gmail.com>
(cherry picked from commit eb37cc828fac201a360c35aa1e87a1daf2d5fed1)
* Restrict the IPU7 PSYS node to root
Intel's PSYS driver has two buffer-lifecycle bugs that this package does not fix: a GETBUF that is never mapped leaves the buffer owned by both the PSYS handle and the exported dma-buf, so closing the two is a use-after-free and a double free, and UNMAPBUF drops its mapping reference before clearing the attachment, racing a concurrent dma-buf release. Any account that can open /dev/ipu7-psys0 can reach both.
Nothing but v4l2-relayd@ipu7, which runs as root, opens the node; applications use the v4l2loopback device. With the node at 0600 root:root the camera streams the same, so the video group and the seat user lose nothing and the bugs need root. GROUP and MODE are explicit so the upgrade's change event also tightens nodes on running machines.
Co-Authored-By: Codex XHigh <noreply@openai.com>
* Drop the DKMS Intel CVS driver now that linux-omarchy ships it
linux-omarchy and linux-omarchy-bore build drivers/media/i2c/cvs in-tree (CONFIG_VIDEO_INTEL_CVS=m) and carry the same wake-IRQ fix as our 0007 (0542), plus the Nova Lake ACPI ID (0541) that our copy lacks. The DKMS module has the same name and installs under updates/, so on every 7.2+ kernel it displaced the kernel's signed module with an unsigned, older copy: on Nova Lake that copy cannot bind the CVS device and the camera fails, and any later fix in the kernel's driver would be masked. Krzysztof Wilczyński reported the conflict on #723.
Skipping the build only on kernels that carry the driver (BUILD_EXCLUSIVE_CONFIG) would have kept it for stock Arch kernels, but the pacman dkms hook reports every such skip as "exited 77", so every Omarchy kernel update would print a warning. A stock Arch kernel left installed as the fallback boot entry now has no camera.
On upgrade the dkms hook removes intel-cvs and restores the kernel's original module. vision-drivers still covers kernels before 7.2.
* intel-ipu7-camera: build the Lunar Lake HAL plugin and stop rotating its frames
Two Panther Lake assumptions in this package break Lunar Lake boards, where it
is installed by the same hardware detection.
Build both HAL platforms. libcamhal picks its plugin by platform, and a Lunar
Lake machine asks for one that was never built:
CamHAL[ERR] HalAdaptor: load_camera_hal_library, failed to open library:
/usr/lib/libcamhal/plugins/ipu7x.so: No such file or directory
CamHAL[WAR] CameraParserInvoker: parseSensors: No sensors available
so the camera cannot work at all. The proprietary side of it is already
shipped -- libia_aic-ipu7x.so and the rest of that set come from
ipu7-camera-bins today; only the plugin the HAL loads was missing. Upstream
builds both platforms from one configure run and `make install` lays down
/etc/camera/ipu7x/ alongside ipu75xa, including the tuning this hardware
wants (OV08X40_BBG802N3_LNL.aiqb, gcss/OV08X40_BBG802N3_LNL.IPU7X.bin), so the
change is the IPU_VERSIONS list plus 0008, the ipu7x twin of 0006: the
"Intel CVS" pad formats that 1.0.6 added to the ipu75xa sensor config are
needed in the ipu7x one for the same reason, or link validation fails at
stream-on behind the Linux 7.2 bridge entity.
Pick the relay pipeline per board. v4l2-relayd-ipu7.conf rotates every frame
180 degrees, which is right where the sensor is mounted inverted and wrong
here: the sensor reports camera_sensor_rotation = 0 and camera_orientation =
Front, and the picture arrives upside down in every application. camera-init
now writes VIDEOSRC to /run based on the bridge ACPI id and the relay drop-in
reads it. Only INTC10DE takes the new path; INTC10E1, INTC10CF, INTC10E0 and
anything unrecognised keep the packaged pipeline byte for byte, and a boot
where camera-init did not run falls back to it as well.
Verified on a Dell Pro 14 Premium PA14250 (Lunar Lake, INTC10DE, OV08X40 +
HM1092) against intel-ipu7-camera 1.0.6-2 on linux-omarchy 7.2.5-3: with the
package's own intel-cvs DKMS the sensor joins the graph behind "Intel CVS",
the ipu7x plugin built from the pinned commit with 0005 and 0008 resolves
ov08x40-uf on CSI port 0, v4l2-relayd streams 30 fps to /dev/video50 and the
image is upright in a browser.
Note for reviewers: necessary but not sufficient on Lunar Lake. Three more
things this board needs are not in this PR: the sensor probe races the
bridge's runtime suspend (camera-init's `sleep 2` lands while the I2C bus is
still owned by the bridge firmware and ov08x40 reads its chip id as -110),
the in-tree cvs driver's quirk for the Synaptics SVP7500 (06cb:0701) hands
the privacy LED to the host so it never lights, and ipu-bridge before 7.3
does not know the HM1092 IR sensor. Details in #366.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 9be51b47104da9d115a7d79e04bc2ae5445809fc)
* Derive the Lunar Lake relay pipeline from the packaged one
camera-videosrc-init wrote VIDEOSRC for every board, with a copy of the pipeline the relay config carried when it was written, and its file is read after /etc/v4l2-relayd.d/ipu7.conf. On Panther Lake that replaced this branch's fps-range, gain-range and color-range settings with the old sharpness=80 ev=-1 saturation=10 pipeline. It now writes nothing unless the board is Lunar Lake, and there it takes the packaged pipeline and drops only the rotation, so a later change to the relay config reaches both platforms. It also removes a file left by an earlier run, which camera-init's restart on resume would otherwise keep.
The ipu7x CVS format patch becomes 0012: 0008 is already the YUV range patch.
* Regenerate the Lunar Lake pipeline on every relay start
camera-videosrc-init ran from camera-init.service and sourced /etc/v4l2-relayd.d/ipu7.conf as bash. A config that is valid for systemd but not for bash, such as an unquoted VIDEOSRC, made it fail and Lunar Lake fell back to the rotated pipeline; and because camera-init stays active, editing the config and restarting only the relay kept the override cached since boot.
It now runs as the relay's ExecStartPre, where systemd hands it VIDEOSRC already parsed from the relay's own environment files, and writes the override quoted for systemd's parser. ExecStart re-reads the drop-in's EnvironmentFile and ExecStopPost removes it, so each start sees the current config.
Co-Authored-By: Codex XHigh <noreply@openai.com>
* Scale the Lunar Lake ov08x40's analog gain codes too
The Lunar Lake tuning (OV08X40_BBG802N3_LNL.aiqb) carries the same CMC gain table as the Panther Lake one, 1x = code 256, and both platforms use the same in-tree ov08x40 driver, which takes 1x = 128. Without the shift a 4x request ran the Lunar Lake sensor at 8x, the mismatch 0010 fixes on Panther Lake. 0010 now sets analogGainCodeShift in the ipu7x sensor config as well.
Co-Authored-By: Codex XHigh <noreply@openai.com>
* Remove the Lunar Lake override before the generator runs
systemd passes ExecStartPre the unit's environment files as they stand when
that command starts, so the generator saw a /run override left behind by a
crash or SIGKILL (anything that skipped ExecStopPost) and took its VIDEOSRC
for the configured one: an edited /etc/v4l2-relayd.d/ipu7.conf would lose to
the stale file. Removing the file in its own ExecStartPre first means the
generator's environment is re-read without it.
Co-Authored-By: Codex XHigh <noreply@openai.com>
* Author the HAL and icamerasrc patches from the Omarchy address
---------
Signed-off-by: Afonso Oliveira <afonso.oliveira707@gmail.com>
Co-authored-by: Codex XHigh <noreply@openai.com>
Co-authored-by: Afonso Oliveira <afonso.oliveira707@gmail.com>
Co-authored-by: Kolbas <pkolbas@pm.me>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
omacom/omarchy#13362 (d0b94d98c) dropped the omarchy-hw-platform copy
omarchy-settings shipped beside its platform guard. Its 00-omarchy-hooks.conf
now asks the runtime's own detector on PATH, and keeps the busybox line where
there is none. The aarch64 settings recipes still refused any source whose
baseline names omarchy-hw-platform without that copy, so every aarch64 build
of quattro would fail once #13362 merges.
Both recipes drop that check. The copy is still installed when a source ships
the guard, as before. The test fixture follows #13362's current baseline, and
the split-layout test checks the package builds from it and ships no copy.
#638 gave omarchy-settings this and -dev was never ported, so edge, the only
channel aarch64 machines are qualified for, still stripped zram, oomd, zswap,
the sysctl tuning and USB autosuspend there: on Snapdragon, migration
1790328426 installs zram-generator, finds no dev-zram0.swap and completes
without zram. A source with default/settings-runtime-profile now gets the same
files, backup and optdepends on aarch64 as on x86_64, Thunderbolt drop-in
included, and its HOOKS files ship as they are. Older sources, including the
current quattro pin, keep today's aarch64 package. The platform guard and its
detector check stay aarch64-only (#691, #694). The keyboard backlight unit
first-run enables ships whenever the source has it, as in omarchy-settings.
tests/settings-runtime-profile.sh now runs both recipes. pkgrel 5 so the
recipe change builds; the payload from the current pin is unchanged.
* voxtype-bin: update to 1.1.0
Ship the signed 1.1.0 release assets, baseline and ARM binaries, and the complete Quickshell and OSD style trees. Add OpenVINO optional dependencies from #526 and select the baseline binary automatically on pre-AVX2 hosts.
* voxtype-bin: bump pkgrel past the published 1.1.0-1
voxtype-bin 1.1.0-1 is already published to edge, rc and stable from master's upstream sync. This branch changes that package's contents (baseline binary, OSD styles and recipes, the install hook, OpenVINO optdepends) without changing its version, so publish.yml would call it already published and skip it, and publish-artifact refuses different bytes under an existing filename. pacman would not offer it as an upgrade either.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* voxtype-bin: list accelerator optdepends for x86_64 only
makepkg appends optdepends_$CARCH to optdepends rather than replacing it, so optdepends_aarch64 did not drop the Vulkan, CUDA, ROCm and MIGraphX entries on aarch64: it listed all sixteen shared ones and then eleven of them a second time. The accelerator runtimes move to optdepends_x86_64 beside the OpenVINO ones, and the aarch64 array goes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* voxtype-bin: move pre-AVX2 hosts to the baseline build on upgrade
The baseline pick in _set_default_backend only runs when no backend was saved, which is a fresh install. Every release before 1.1.0 gave a CPU without AVX2 voxtype-avx2, and pre_upgrade saves that path, so post_upgrade restored the build the host cannot run and the machines the baseline binary exists for never reached it. A saved AVX2 or AVX-512 build whose instruction set the CPU lacks is now picked again; GPU and ONNX choices are left alone.
tests/voxtype-bin-install.sh covers the fresh-install pick and the upgrade cases, and runs with the other self-tests. It borrows the hook's fixed /tmp/.voxtype-backend-upgrade, so it refuses to start when anything is already there, a dangling symlink included: CI runs it as root, and writing through a planted link would land outside the test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
* tests: keep voxtype-bin-install's upgrade state out of /tmp
The test borrowed the hook's /tmp/.voxtype-backend-upgrade and checked it was free only once, so a real voxtype-bin upgrade writing its state during the run could have that state overwritten or deleted, and the hook would then lose the user's backend. The test now redefines the sourced _preserve_or_set_backend with the path moved into its own directory, and fails outright if the hook stops using that path rather than passing without reading it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
* tests: never parse voxtype-bin-install's temp path as code
The redefined _preserve_or_set_backend went through eval with the mktemp path pasted in, so a TMPDIR holding shell syntax -- a directory named $HOME, or $(...) -- was expanded or run when the function was called. It now carries a reference to $SAVED, which expands to the path only at run time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
---------
Co-authored-by: Spencer Bull <spencer@omarchy.org>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Codex XHigh <noreply@openai.com>
No platform package is built for x86_64, so an x86 machine has nothing to
guard. The check that 00-omarchy-hooks.conf has its detector copy follows it:
without the copy, x86_64 keeps the busybox line it would have anyway, and
leaving the check unscoped would fail every x86_64 build from a source that
asks omarchy-hw-platform. Taken from #691, which keeps the -dev recipe.
A source with default/settings-runtime-profile decides at runtime which
platform-specific files apply, so its aarch64 package now gets the same files,
backup and optdepends as x86_64: the Thunderbolt request and the memory stack
stay, and its HOOKS files ship as they are. The check that a Mac's asahi line
survives still runs on every aarch64 build. Older sources, including the pinned
v4.0.4, keep #380's aarch64 package: Thunderbolt removed, the HOOKS line guarded
for asahi, the memory stack stripped. The keyboard backlight unit first-run
enables ships whenever the source has it.
pkgrel 4 so the recipe change builds: edge already holds 4.0.4-3 on both
architectures. The payload built from v4.0.4 is unchanged.
omacom/omarchy#13362 sets HOOKS per platform inside 00-omarchy-hooks.conf,
with no column-0 HOOKS= line, so the aarch64 build failed on it; settings-dev
tracks quattro automatically and would break as soon as it lands. Wrap only a
single-line HOOKS=(...), refuse any other column-0 HOOKS=, then source the
shipped files onto a Mac line and a stock line: the Mac line must come through
unchanged and the stock one must not. Back up 00-omarchy-hooks.conf when it
ships, and refuse it without the omarchy-hw-platform copy it asks.
The test now uses the real v4.0.4 hooks file, #13362's two files and
omarchy-mac-boot's 90-94 fragments, and covers upgrades over a hand-restored
file.
Since publishing moved to CI on merge, a package whose PKGBUILD never
changes while its source moves was never rebuilt: omarchy-dev and
omarchy-settings-dev followed quattro through "#branch=" and a pkgver()
function, and nothing in this repository changed when quattro did. The
host timers that used to notice are off, so edge fell days behind.
The rule now: no git source without a commit or tag pin
(tests/pinned-sources.sh, run in CI). A package that has to follow a
branch declares a git_branch upstream watch, and the pin moves through
the same PR/build/publish path as every other version bump.
Watch (helpers/upstream-watch.py)
git_branch gains tag_pattern: the newest release tag in the pinned
commit's own history, exposed as {tag}/{version}/{distance}, so a
branch build is versioned <tag>.r<n>.g<sha>, above the release it
follows and below the next one. One blobless clone per branch per
run, shared by every package on it. min_release_age selects the
newest commit older than the window, so a push burst builds once.
Lane (helpers/package-metadata.sh, bin/sync-upstream --lane)
"auto_merge": true moves a package from the reviewed 6-hourly sync
PR to the unattended lane. Packages pinned from the same branch move
together: a failure on one restores the others and fails the group,
so the dev pair can never ship from two quattro commits.
Tracker (.github/workflows/track-branches.yml)
Every two hours: pin, open one PR with a GitHub App token, enable
auto-merge. Branch protection still gates the merge on result,
self-tests and build-isolation. A tip that fails to build stays an
open red PR until the next tick supersedes it. The App is required:
a PR opened with GITHUB_TOKEN has its checks held for approval and
its auto-merge would not fire publish.yml.
The reviewed workflows (sync-upstream, sync-rebuilds) open their PRs
with the same App so their builds start without a maintainer clicking
"Approve workflows to run"; without the App they fall back to
GITHUB_TOKEN and behave as before.
Recipes
The dev pair pins _commit and a real sha256sum, keeps the OMARCHY_SRC
override, and drops pkgver(). Its r-number stays the branch's total
commit count because the published history used it and pacman must
never see the version go down. omasnap-git is new: omacom/omasnap
main, versioned <tag>.r<distance>.g<sha>, provides/conflicts omasnap.
Apple Silicon Macs install the aarch64 package too, and an unconditional
HOOKS= line would replace their asahi hooks. Apply Omarchy's HOOKS line only
when the incoming hooks lack asahi, in whichever drop-in carries it, and
rebuild omarchy-settings (4.0.4-3) so existing installs get the fix.
Three things kept the upstream sync PR (#589) from ever finishing a build:
Scoped dispatches wiped the shared PR. A workflow_dispatch with `packages`
regenerates only those packages from master, and pushing that to
auto/sync-upstream replaced 38 pending updates with one. Scoped runs now
push to their own auto/sync-{upstream,rebuilds}-<packages> branch and PR;
scheduled runs keep the shared branch.
build-approved stopped working after the first bot push. A GITHUB_TOKEN
push creates pull_request runs held for approval but no pull_request_target
run, so approve-pr.yml never saw it: its last run on the branch was the
label itself (2026-09-25T19:26), and each of the next four syncs sat at
action_required. The sync workflows now release the held runs for the
commit they just pushed, from a separate job holding actions: write, and
only for their own bot-authored, same-repo PR while build-approved is on
it.
Each approved push cancelled the in-flight build. Approving the 21:43
sync's build cancelled the label-triggered one still queued on strata and
schist-bin. On auto/sync-* branches a new build now waits for the running
one instead, then reuses its artifacts. The approval script no longer
waits for a lone approved build to start before releasing tests, which a
queued build would have turned into a timeout.
On aarch64 the Limine hooks and the mkinitcpio wrapper run through
limine-apple-gate. Only an Apple Silicon Mac (device tree "apple,")
behaves differently: Limine's kernel and removal hooks wait until
Omarchy activates Limine (/var/lib/omarchy/limine.enabled and
/etc/default/limine), mkinitcpio's own kernel hook keeps /boot current
before and after activation, Limine's EFI deploy hook is left to
omarchy-mac-boot, and the wrapper is plain mkinitcpio. Skipped hooks
drain the socket pacman streams targets over.
x86_64 packages exactly what 1.39.0-1 did; generic aarch64 and
Snapdragon keep upstream behaviour.
`restore=$(shopt -p nullglob)` exits 1 when nullglob is off, which it is
in the workflow shell. Under the `bash -e` that GitHub runs steps with,
that ended "Pack artifact" before tar ran: every job on #512 and #550
built fine and then failed with nothing in the log but the command.
The self-test never saw it because `pack_packages ... || fail` suppresses
errexit. Enumerate package files with a loop instead of toggling shell
options, and add a test that calls both helpers under `bash -e` exactly
as the workflows do; it fails against the old helper.
actions/upload-artifact rejects any path containing ':', and makepkg names
a package with an epoch `name-1:ver-rel-arch.pkg.tar.zst`. Every PR that
built such a package (cursor-cli in the sync PRs, omasnap once it gained an
epoch) failed at "Upload artifact" after a successful build, and publish
then rebuilt from scratch on merge.
The files now ride inside packages.tar for the artifact hop and come back
out with makepkg's names untouched: pacman clients and bin/publish-artifact
both require the filename to match PKGINFO, and the channels already carry
these names. publish.yml still accepts bare pre-packing artifacts until the
7-day retention drains them.
helpers/artifact-helpers.sh holds both halves; tests/artifact-helpers.sh
covers the round trip and runs with the other self-tests.
* Package Omawake 0.0.3 and Omaspeak 0.0.2
Bump both -bin packages to the model-support roadmap delivery:
Omawake 0.0.3:
- W02-W05 setup/activation/cache gates audited and closed
- W07 pinned catalog URL health checks and import diagnostics
- W08 Moonshine Small/Medium benchmarked; both deferred (Tiny default)
- W09 Spanish wake profile (multilingual Whisper Base INT8, es)
- W10 connection-owned playback pauses (HoldPause)
Omaspeak 0.0.2:
- S09 Kokoro 82M: Kokoro-capable packaged provider (supertonic;kokoro_tts)
with espeak-ng-data.bin shipped beside the executable, 54 named voices
- S10 catalog URL checks, Spanish speech profile, consistent status shape
- S07 streaming deferred at the current pin
Upstream: omawake v0.0.3, omaspeak v0.0.2 (aarch64 + x86_64 verified on
promaxgb10-d666 CUDA and local NPU installs).
* omaspeak-bin: install espeak-ng-data.bin beside the packaged library
* omaspeak-bin: bump to 0.0.3-rc.1 (catalog-managed eSpeak data)
- The tarball no longer ships espeak-ng-data.bin: the Kokoro catalog row
pins the data package as a model asset (downloaded/verified/installed
into the model directory with the GGUF), so the core package ships no
model data at all.
- Both arch checksums taken from the v0.0.3-rc.1 SHA256SUMS.txt.
* omaspeak-bin: finalize at 0.0.3
* Stop setup-created Oma services before pacman removes their binaries
* Drop stale release-verification fixtures from the branch
These were swept in by git add -A during the version bumps: packaged
copies of old releases (0.0.1 tarballs and extracted trees, ~80 MB)
belong to the local verification workflow, not to the package repo.
The consolidated upstream PR should carry only the package changes,
hooks and the removal regression suite.
* Update removal-test fixture versions to the packaged finals
* Ask systemd to reset only an Oma unit that actually failed
The removal helper reset the failed state of every unit it stopped, but
systemd accepts ResetFailed for a unit that is in the failed state alone.
For any other state it answers that the unit is not loaded and exits
non-zero, and because the helper runs under errexit while the hook aborts
on failure, a healthy unit then aborted the whole transaction:
(2/2) Stop and remove omaspeak user services before package removal
Failed to reset failed state of unit omaspeak.service: Unit omaspeak.service not loaded.
:: Could not clean up omaspeak for jacob; removal aborted.
That is the ordinary case, as the packaged service ships disabled and an
enabled one is commonly stopped rather than failed. Read the active state
after the stop and ask for the reset only where it applies, so a failed
unit still loses its failed state along with its rate and restart counters
while a clean unit no longer fails the removal. A reset that a reachable
manager still refuses stays fatal.
Model the rule in the removal suite, where reset-failed now follows the
active state the way a real manager does, and cover both outcomes: an
inactive unit must not be asked for, a failed one must be reset between
the stop and the disable, and a refused reset must still fail the hook.
* Keep removal cleanup faithful to how systemd reads configuration
Both defects from the review of e025111 sat in the shared package-remove
helper, so both packages were affected the same way.
An offline user's drop-in was recognised by grep '^ExecStart=', while the
configuration parser throws away the whitespace around an assignment
(parse_line() strips the line and both halves of the assignment). A drop-in
naming a development build as
ExecStart =
ExecStart = /home/alice/build/omawake daemon
therefore went unmatched, and the helper cleared away the generated base unit
beside with its enablement links, right behind a service that was never meant
to be the package's. Match an assignment the way the parser accepts one. The
gate that decides whether a unit file is the generated one stays strict on
purpose: only the exact generated shape is ever deleted.
systemctl show-environment also prints every value the way a shell would read
it, through shell_maybe_quote(SHELL_ESCAPE_POSIX), so an XDG_CONFIG_HOME with a
space arrives as $'/home/alice/custom config'. The XDG_CONFIG_HOME=/* case saw
neither form and kept the home's .config directory quietly, leaving the unit in
the directory the manager really reads pointing at the removed binary. Decode
that quoting character by character, without letting the text become shell
syntax, and refuse a value that is neither a plain path nor a closed $'...'
quote rather than delete what would have to be guessed at. A value that is not
an absolute path stays the fallback it is in systemd itself.
The fixtures now hand the helper the very text a manager prints, quoted by a
mirror of that printer, and cover a quoted path with a space, an apostrophe and
a backslash, an unreadable quoted value, a relative one, and each spacing of an
offline override. Verified with the removal suite, 15 tests; the eight new
assertions fail against the helper as it was. The other suites were not run
here, as they reach for the network.
pkgrel 3 -> 4 and the helper's checksum, in both recipes.
Reported-by: spencerbull
* Decode systemd control escapes during service removal
systemctl C-escapes control bytes in show-environment output. Rejecting those valid values aborted package removal for every user, even when the affected account had no Oma service. Decode the printer’s named and octal escapes without evaluating shell syntax or stripping trailing newlines, and cover the real printer format in the fixtures.
Co-Authored-By: GPT-6 XHigh <noreply@openai.com>
---------
Co-authored-by: Spencer Bull <spencer@omarchy.org>
Co-authored-by: GPT-6 XHigh <noreply@openai.com>
Every pull request now builds the package directories it touches on
ephemeral DigitalOcean droplets, and every merge to master publishes the
resulting artifacts into the channels each package belongs to. The
repository host's timers become the fallback rather than the pipeline.
Build (.github/workflows/build-pr.yml)
One job per package per architecture, always against edge. The artifact
is labelled with the package directory's git tree hash. Tooling (bin/,
helpers/, build/) is checked out from the base branch; the PR supplies
only pkgbuilds/, so a PR can change what is built, never how. Builds
run only for trusted authors: collaborators, .github/VOUCHED.td, or a
PR carrying the build-approved label. A single required check, result,
aggregates the matrix.
Publish (.github/workflows/publish.yml, bin/publish-artifact)
One job per merge. It collects the PR artifacts for the merged tree,
builds anything that has none, then walks each channel/architecture
slot once: pull that database, repo-add every package that belongs in
it, upload packages, signatures, then the database. A published
filename is immutable; identical bytes under an existing name only
gain a database entry, different bytes are refused. Fast-ring packages
reach edge, rc and stable in the same run from the same file.
Matrix (bin/build-matrix)
Package x architecture, with the channels the artifact ships to,
decided by package_builds_for_mirror so CI and the host agree.
arch=any packages build once and land in every architecture database.
Builder (build/build.sh, bin/build, build/Dockerfile)
With no local published tree, plan against and resolve from the public
channel over HTTPS. PACKAGER is set to Omarchy. rclone is in the image.
Runners (ci/)
A controller droplet polls GitHub with curl and creates one g5 droplet
per queued job from cloud-init, deleting them when off or over-age.
Builders carry QEMU with credential support for aarch64. Operator SSH
keys come from GitHub. tests/controller.sh and tests/publish-artifact.sh
cover the decisions against fixtures and real makepkg output.
Tests run on pull requests only; branch protection requires result,
self-tests and build-isolation with up-to-date branches.
The root-run package hook changed ownership of paths below a
user-controlled home directory. A config symlink could redirect chown to
an arbitrary root-owned file during installation or upgrade.
Run the config writer as the target desktop user and remove the privileged
ownership changes. This also prevents the missing-config path from writing
through a user-controlled pathname as root. Add regression coverage and
bump the package release.
Reported-by: piratemoo (Esther) <22439214+piratemoo@users.noreply.github.com>
Link: https://github.com/piratemoo/Arbitrary-File-Ownership-Change-via-Symlink-LPE