Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ on:
reuse_run_id:
description: Exact prior run supplying unchanged boot and userspace artifacts
required: false
extra_devices:
description: Devices built and published beside J71b, such as j71t; the hardware tag must ship their components
required: false
permissions:
contents: read
actions: read
Expand All @@ -31,6 +34,8 @@ env:
CLI_TAG: ${{ inputs.cli_tag || 'v0.1.8' }}
CLI_ARTIFACT_DIR: ${{ inputs.cli_run_id != '' && 'out/dependency-wheels' || '' }}
HARDWARE_ARTIFACT_DIR: ${{ inputs.hardware_run_id != '' && 'out/downloads/hardware' || '' }}
# Add j71t to the default with the first hardware release carrying its components.
EXTRA_DEVICES: ${{ inputs.extra_devices || '' }}
jobs:
test:
runs-on: ubuntu-24.04
Expand Down Expand Up @@ -154,6 +159,10 @@ jobs:
if: inputs.reuse_run_id == ''
run: |
python boot/build-loader --logo-header out/boot/omarchy-logo.h --output out/boot/m1n1.bin
for device in $EXTRA_DEVICES; do
python boot/build-loader --device "$device" --logo-header out/boot/omarchy-logo.h \
--output "out/boot/$device-m1n1.bin"
done
tar -C out -czf out/distro-boot.tar.gz boot
- uses: actions/upload-artifact@v4
with:
Expand Down Expand Up @@ -228,7 +237,12 @@ jobs:
VERSION="preview-$RUN_ID"
if [[ "$REF_TYPE" == tag ]]; then VERSION="${REF_NAME#v}"; fi
bash system/build-rootfs j71b --desktop out/desktop --release "$VERSION" --output out/base
tar -C out -czf out/omarchy-userspace.tar.gz base cursor bluetooth labwc
bases=()
for device in $EXTRA_DEVICES; do
bash system/build-rootfs "$device" --desktop out/desktop --release "$VERSION" --output "out/base-$device"
bases+=("base-$device")
done
tar -C out -czf out/omarchy-userspace.tar.gz base "${bases[@]}" cursor bluetooth labwc
- uses: actions/upload-artifact@v4
with:
name: omarchy-userspace
Expand Down Expand Up @@ -308,9 +322,30 @@ jobs:
--cursor-tools out/cursor --bluetooth-tools out/bluetooth --labwc-tools out/labwc \
--output out/release
python -m release.j71b.audit out/release/j71b-checkm8-pongo.toml
for device in $EXTRA_DEVICES; do
[[ "$device" =~ ^[a-z0-9][a-z0-9_-]*$ && "$device" != j71b ]]
DEVICE="$device" bash ci/dependencies --components-only
python ci/normalize-recovery-tools.py --archive "out/base-$device/rootfs.tar.gz" \
--receipt "out/base-$device/rootfs.tar.gz.json"
parts="out/$device/components"
python -m "release.$device.build" --release "$VERSION" \
--base "out/base-$device/rootfs.tar.gz" --base-receipt "out/base-$device/rootfs.tar.gz.json" \
--modules-dir "$parts/kernel/lib/modules" --loader "out/boot/$device-m1n1.bin" \
--kernel "$parts/kernel/Image.gz" --dtb "$parts/kernel/$device.dtb" \
--pongo out/boot/pongo.bin --busybox "$parts/boot/busybox" \
--dropbear "$parts/boot/dropbear" --dropbearkey "$parts/boot/dropbearkey" \
--cursor-tools out/cursor --bluetooth-tools out/bluetooth --labwc-tools out/labwc \
--output "out/release-$device"
python -m "release.$device.audit" "out/release-$device/$device-checkm8-pongo.toml"
python -m release.merge --release-dir out/release --add "out/release-$device"
done
orchard resolve out/release/index.toml
for device in $EXTRA_DEVICES; do orchard resolve out/release/index.toml --profile "$device"; done
python -m release.publish --tag "$TAG" --release-dir out/release --output out/publication
cp out/release/rootfs.json out/release/build-receipt.json out/publication/
for device in $EXTRA_DEVICES; do
cp "out/release/$device-rootfs.json" "out/release/$device-build-receipt.json" out/publication/
done
cp out/downloads/pongo-source.tar.gz out/publication/
git archive --format=tar.gz -o out/publication/omarchy-linux-source.tar.gz HEAD
(cd out/publication && sha256sum ./* > ../SHA256SUMS && mv ../SHA256SUMS .)
Expand Down
20 changes: 18 additions & 2 deletions BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ Use `orchard-hardware>=0.1.9,<0.2`. To test unpublished packages, install compat
## Produce components

- `desktop/build-sources` and `desktop/build`: desktop applications and payload.
- `system/build-rootfs`: signed Arch Linux ARM base and package receipt; see [system/j71b/README.md](system/j71b/README.md).
- `system/build-rootfs DEVICE`: signed Arch Linux ARM base and package receipt; see [system/j71b/README.md](system/j71b/README.md) and [system/j71t/README.md](system/j71t/README.md).
- Installed hardware producers: kernel, DTB, modules and static BusyBox/Dropbear tools; see [hardware build instructions](https://github.com/OrchardLinux/hardware/blob/main/BUILD.md).
- `python boot/build-loader`: distro-branded loader. Pongo sources and build instructions are under `boot/pongo/`.
- `python boot/build-loader [--device DEVICE]`: distro-branded loader (default J71b). Pongo sources and build instructions are under `boot/pongo/`.

Retain each component's receipt. Kernel, modules and DTB must be compatible. Do not change hardware activation or timing as part of presentation changes.

Expand Down Expand Up @@ -59,6 +59,22 @@ python -m release.j71b.build \

Choose a new version and output directory for each build. Root builds first; recovery embeds its exact stream receipt, and the index is written last. Installed wheel resources supply hardware code and scripts. `image/j71b/image_policy.py` and `permissions.py` supply desktop readiness, systemd and privilege policy.

### More devices

Each device is assembled separately from its own components, then merged into one release. J71t (iPad 5 Wi-Fi) uses `system/build-rootfs j71t`, `python boot/build-loader --device j71t`, the hardware's `j71t-components.tar.gz` and `image/j71t/`:

```sh
python -m release.j71t.build --release 0.1.0-orchard.3 \
--dtb out/j71t/components/kernel/j71t.dtb --loader out/j71t/m1n1.bin ... \
--output out/releases/0.1.0-orchard.3-j71t
python -m release.j71t.audit out/releases/0.1.0-orchard.3-j71t/j71t-checkm8-pongo.toml
python -m release.merge --release-dir out/releases/0.1.0-orchard.3 \
--add out/releases/0.1.0-orchard.3-j71t
orchard resolve out/releases/0.1.0-orchard.3/index.toml --profile j71t
```

The remaining arguments match J71b's. Device-specific files are named `j71t-rootfs.tar.zst`, `j71t-recovery.bin`, `j71t-rootfs.json` and `j71t-build-receipt.json`; J71b keeps its original names and both share the branded `pongo.bin`. `release.merge` refuses any differing file and rewrites one `index.toml` pinning each device.

## Validate without a device

```sh
Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ The current J71b port is the reference implementation:
- [Device protocol](https://github.com/OrchardLinux/cli/blob/main/docs/device-protocol.md): operations exposed by the runtime to the host.
- Omarchy A's `image/j71b/` and `release/j71b/`: installed-system policy and release assembly.

J71t (iPad 5 Wi-Fi) shows what a further device adds here: `image/j71t/` (its platform helpers), `system/j71t/` (package list and device overlay), thin `release/j71t/` entry points, and an entry in `release/devices.py`. Record feature status in [docs/devices.md](docs/devices.md).

Keep device-specific hardware procedures in the hardware implementation and desktop policy in Omarchy A. The host coordinates operations and presents confirmations; the device runtime owns storage validation, hardware activation, extraction, and handoff.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ Includes a device-aware CLI and release format for installation, diagnostics, an
| Device | Identifier | Status |
| --- | --- | --- |
| iPad 6 Wi-Fi | `iPad7,5` / J71b | Experimental; no GPU acceleration |
| iPad 5 Wi-Fi | `iPad6,11` / J71t | Experimental; no GPU acceleration; drivers tested on one unit; Orchard install not yet tested |

See [supported devices and features](docs/devices.md) for per-feature status.

This image uses **tethered boot**: starting Linux after a cold restart requires a host computer running `orchard launch`.

Expand Down
9 changes: 7 additions & 2 deletions boot/build-loader
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ import sys
import tempfile
import orchard_hardware

# The splash only draws into m1n1's boot framebuffer. J71t applies the J71b
# patch unchanged on top of its own loader patches (m1n1 d5a10ac52a).
SPLASH = {'j71b': 'j71b', 'j71t': 'j71b'}

parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--output', required=True, type=Path)
parser.add_argument('--device', default='j71b', choices=sorted(SPLASH))
parser.add_argument('--resume', action='store_true')
parser.add_argument('--logo-header', type=Path, help='pre-generated matching artwork header (CI)')
args = parser.parse_args()
Expand All @@ -19,7 +24,7 @@ with tempfile.TemporaryDirectory(prefix='omarchy-loader-') as temporary:
subprocess.run([sys.executable, str(here / 'pongo/generate-logo.py'),
'--size', '480', '--output', str(header)], check=True)
subprocess.run([str(Path(orchard_hardware.__file__).parent / 'boot/m1n1/build'),
'j71b', '--output', str(args.output),
'--extra-patch', str(here / 'm1n1/patches/j71b/0002-omarchy-splash.patch'),
args.device, '--output', str(args.output),
'--extra-patch', str(here / 'm1n1/patches' / SPLASH[args.device] / '0002-omarchy-splash.patch'),
'--include-header', str(header), '--extra-cflags', '-DOMARCHY_SPLASH',
*(['--resume'] if args.resume else [])], check=True)
48 changes: 30 additions & 18 deletions ci/dependencies
Original file line number Diff line number Diff line change
@@ -1,36 +1,48 @@
#!/usr/bin/env bash
# Install a published, hashed wheel pair and verified public component bundle.
# DEVICE (default j71b) selects the bundle; other devices extract to out/DEVICE.
set -euo pipefail
: "${HARDWARE_TAG:?Set HARDWARE_TAG to an immutable hardware release}"
: "${CLI_TAG:?Set CLI_TAG to an immutable CLI release}"
[[ "$HARDWARE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ && "$CLI_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || {
echo 'Hardware and CLI tags must be vMAJOR.MINOR.PATCH' >&2; exit 2;
}
python3 -m venv .venv
if [[ -n "${CLI_ARTIFACT_DIR:-}" ]]; then
.venv/bin/python -m pip install --only-binary=:all: "$CLI_ARTIFACT_DIR"/*.whl
else
.venv/bin/python -m pip install --only-binary=:all: -r \
"https://github.com/OrchardLinux/cli/releases/download/$CLI_TAG/requirements.txt"
fi
.venv/bin/python - <<'PY'
mode=${1:-}
case "$#:$mode" in
0:|1:--wheels-only|1:--components-only) ;;
*) echo 'Usage: ci/dependencies [--wheels-only|--components-only]' >&2; exit 2;;
esac
device=${DEVICE:-j71b}
[[ "$device" =~ ^[a-z0-9][a-z0-9_-]*$ ]] || { echo 'Invalid DEVICE' >&2; exit 2; }
if [[ "$mode" != --components-only ]]; then
python3 -m venv .venv
if [[ -n "${CLI_ARTIFACT_DIR:-}" ]]; then
.venv/bin/python -m pip install --only-binary=:all: "$CLI_ARTIFACT_DIR"/*.whl
else
.venv/bin/python -m pip install --only-binary=:all: -r \
"https://github.com/OrchardLinux/cli/releases/download/$CLI_TAG/requirements.txt"
fi
.venv/bin/python - <<'PY'
import importlib.metadata as m, os
for package, variable in (('orchard-hardware', 'HARDWARE_TAG'), ('orchard-cli', 'CLI_TAG')):
if 'v' + m.version(package) != os.environ[variable]:
raise SystemExit('Published wheel pair does not match ' + variable)
PY
.venv/bin/python -m pip check
if [[ "${1:-}" == --wheels-only ]]; then exit 0; fi
[[ $# == 0 ]] || { echo 'Usage: ci/dependencies [--wheels-only]' >&2; exit 2; }
mkdir -p out/downloads
.venv/bin/python -m pip check
if [[ "$mode" == --wheels-only ]]; then exit 0; fi
fi
bundle="$device-components.tar.gz"
destination=out
[[ $device == j71b ]] || destination="out/$device"
mkdir -p out/downloads "$destination"
base="https://github.com/OrchardLinux/hardware/releases/download/$HARDWARE_TAG"
if [[ -n "${HARDWARE_ARTIFACT_DIR:-}" ]]; then
cp "$HARDWARE_ARTIFACT_DIR/j71b-components.tar.gz" "$HARDWARE_ARTIFACT_DIR/SHA256SUMS" out/downloads/
cp "$HARDWARE_ARTIFACT_DIR/$bundle" "$HARDWARE_ARTIFACT_DIR/SHA256SUMS" out/downloads/
else
curl -fL --retry 3 "$base/j71b-components.tar.gz" -o out/downloads/j71b-components.tar.gz
curl -fL --retry 3 "$base/$bundle" -o "out/downloads/$bundle"
curl -fL --retry 3 "$base/SHA256SUMS" -o out/downloads/SHA256SUMS
fi
grep -Eq ' (\./)?j71b-components.tar.gz$' out/downloads/SHA256SUMS
(cd out/downloads && grep -E ' (\./)?j71b-components.tar.gz$' SHA256SUMS | sha256sum --check --strict)
tar -xzf out/downloads/j71b-components.tar.gz -C out
.venv/bin/python -m pip check
grep -Eq " (\./)?$bundle\$" out/downloads/SHA256SUMS
(cd out/downloads && grep -E " (\./)?$bundle\$" SHA256SUMS | sha256sum --check --strict)
tar -xzf "out/downloads/$bundle" -C "$destination"
if [[ "$mode" != --components-only ]]; then .venv/bin/python -m pip check; fi
38 changes: 38 additions & 0 deletions docs/devices.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Supported devices and features

Omarchy A is experimental on every device. Boot is tethered and there is no GPU acceleration. "Works" means verified on hardware, "Partial" means it works with limits or only parts are verified, and "Not yet" means not available.

| Device | Identifier | Chip / board | Tested |
| --- | --- | --- | --- |
| iPad 6 Wi-Fi | `iPad7,5` / J71b | T8010 (A10), board `0x18` | See the J71b column note |
| iPad 5 Wi-Fi | `iPad6,11` / J71t | S8003 (A9, TSMC), board `0x10` | One unit, iPadOS 16.7.10 |

`iPad6,11` is also sold as J71s (Samsung S8000, chip `0x8000`). J71s and the cellular iPad 5 (`iPad6,12`, J72s/J72t) are not supported.

## Features

| Feature | J71b¹ | J71t² |
| --- | --- | --- |
| Wi-Fi | Supported (a) | **Works**: BCM4355 C0 over PCIe; MAC and calibration come from the iPad's own device tree; 5 GHz verified |
| Bluetooth | Supported (a) | **Works**: scan and keyboard pairing |
| Touch | Supported (a) | **Works**: N1 multitouch; firmware from the IPSW, per-unit calibration from the device tree |
| Display | Supported, no GPU acceleration (b) | **Works**: native KMS at 1536×2048 on the display pipe iBoot leaves running; the panel is `eDP-1`. Software rendering only. Booting with `apple_adbe_drm.enable=0` falls back to the boot framebuffer (simpledrm) |
| Brightness / ambient light | Backlight supported (c); ALS — | **Works** for brightness: the brightness slider and power-key screen toggle. The CT819 light sensor is exposed through IIO only, with no automatic brightness |
| Audio out | — | **Works**: speakers (CS42L81) through PipeWire's default ALSA profile. Headphone jack: routing code present, not validated |
| Microphones | — | **Works**: built-in stereo microphones (48 kHz capture verified) |
| Battery | Supported (d) | **Works**: bq27540 gauge, read-only telemetry; the status reads Discharging whenever current flows out, also at 100 % |
| Charging (detection, up to 2.4 A) | Partial: guarded 425 mA lease (e) | **Works**: computers (USB-A, and a Mac USB-C port through Apple's USB-C to Lightning cable), USB-C wall chargers and a Mac USB-C power adapter charge at up to 2.4 A, the board maximum (verified on the device on 2026-10-27). 5 V only: the iPad 5 has no USB Power Delivery. Charging policy runs in the kernel |
| NVMe install | Supported (f) | **Partial**: untested through Orchard. Installing to a Linux partition beside a shrunk iOS works with the omarchy-ipad5 port's own installer and kernel write fence |
| Suspend | Disabled by image policy (g) | **Not yet**: disabled by image policy, as on J71b; s2idle is only partially validated |
| USB gadget | Supported (h) | **Works**: USB networking and serial console to the host |
| Lightning camera adapter | — | **Partial**: switches to host mode, but no hub or device enumerates yet and host-mode input is limited to 100 mA |
| Accelerometer / auto-rotate | — | **Not yet** in this port: the AOP driver can only be built in, needs a boot-time release step from the omarchy-ipad5 port's own initramfs, and Orchard cannot yet generate its calibration from the device tree. Works on cold boot in omarchy-ipad5 |
| GPU | Not supported (b) | **Not yet**: software rendering only; GPU research code is excluded from this port |
| Video decode | — | **Partial**: V4L2 stateless H.264 decoder, bit-exact on a test stream. Its firmware comes from the IPSW and starts only on request, through debugfs; no media player is set up to use it |
| Cameras | — | **Not yet**: no driver |

Also not in the J71t port: Touch ID / Secure Enclave (communication only, no fingerprint support), deeper CPU idle and overclocking profiles, and the shared cold-restart launcher (`orchard launch`), which needs J71t launcher assets first. Resize APFS support for iPad 5 has not been checked.

¹ J71b entries state only what Orchard's own code and documentation show; "—" means not stated there (maintainer to confirm). Sources: (a) hardware `runtime/j71b/phase2-hardware` (Wi-Fi, touch and Bluetooth steps) and `devices/j71b/device.toml`; (b) [README](../README.md); (c) `system/j71b/overlay/usr/lib/udev/rules.d/70-j71b-backlight.rules` and the power-key screen toggle; (d) hardware `kernel/devicetree/j71b/battery-gauge.dtso` and `system/j71b/overlay/usr/lib/modprobe.d/30-j71b-battery.conf`; (e) `system/j71b/overlay/usr/lib/systemd/system/j71b-charge.service`; (f) [How it works](how-it-works.md); (g) `system/j71b/overlay/etc/systemd/sleep.conf.d/60-omarchy-a-no-suspend.conf`; (h) hardware `boot/initramfs/j71b/recovery/usb-gadget` and `omarchy-diagnostics.service`.

² J71t statuses come from device tests of the same drivers in the release 2 kernel of the [omarchy-ipad5](https://github.com/brkosebay/omarchy-ipad5) port, which passed 13 of 13 device checks through that port's own boot path on one iPad6,11 J71t (S8003, board `0x10`) running iPadOS 16.7.10. Kernel source: [`a9/orchard-j71t`](https://github.com/brkosebay/linux/tree/a9/orchard-j71t). Charging was verified on the same unit on 2026-10-27 with the charger code of kernel patch 0013. The Orchard recovery, installation and handoff path has not yet run on a J71t.
Loading