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
15 changes: 15 additions & 0 deletions BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,21 @@ Choose new evidence directories. `wlrctl` is a test-only native AArch64 helper,

`tools/with-source-denied --deny PATH [--allow INPUT_DIRECTORY] -- COMMAND...` optionally verifies that a command does not read a source checkout.

## N61 development

N61 development uses the shared AArch64 desktop/base producers with its own NFS
root composition. See [the N61 build and staging instructions](system/n61/README.md).
The generated private root receipt feeds the hardware package's native NFS RAM
experiment. Its image policy preserves the USB network root and selects phone
scaling and memory defaults.

The recorded N61 desktop build reached Arch Linux ARM and Labwc through USB/NFS
on two boots with distinct identities. Both reported zero failed system units.
The owner confirmed touch on each; the first covered the vertical edges and
on-screen keyboard entry. The exact tested artifacts and source revisions are
listed in the N61 instructions. USB/NFS tethering remains necessary, and
installation on the phone's storage and untethered startup are future work.

## Publish

Follow [Publishing Omarchy A](docs/releasing.md). Build/audit success is not physical-device qualification: test a fresh installation and a cold-restart `orchard launch` on supported hardware before announcing the release. The experimental desktop auto-logs in and grants passwordless sudo; this is disclosed in the README and must not be described as a secure daily-use system.
71 changes: 71 additions & 0 deletions examples/n61-ram/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# iPhone 6 RAM boot

This experiment prepares an Orchard USB shell for `iPhone7,2` / N61AP, A8
T7000, with CPID `0x7000` and board ID `0x06`. Its local `orchard.toml`
selects the `n61` hardware descriptor and `apple-4k` kernel platform.

The first physical boot passed on 28 October 2026. The USB root shell
reported `7.3.0-rc1-orchard-apple-4k` with 4 KiB pages and both Typhoon CPUs
online. Linux text was visible on the 750 × 1334 LCD through simpledrm.

That boot used PongoOS `2.6.3-e98323f8` bundled in official palera1n v2.4.
Its 238,104-byte image has SHA-256
`5475c5f701423858b34e92176c966a9d4b12950f38acb8d1c347f14d5b272655`.
The default source-built Pongo's A8 entry remains under investigation.
Use the verified bundled image to reproduce the recorded boot.

From the Omarchy A checkout, build before entering DFU, supplying that
Pongo image through the existing component override:

```sh
./orchard build examples/n61-ram --pongo /path/to/Pongo-2.6.3-e98323f8.bin
```

The hardware and CLI sources must contain N61 support. On macOS, place Linux
source and compiler scratch on a case-sensitive filesystem. The shared
producer uses the pinned Linux revision and patch series, then assembles
BusyBox and this directory's `/init` through the usual RAM builder.

Keep the iPhone 6 as the only connected 64-bit iOS device. The recorded
test preserved a connected 32-bit iPad mini in normal iOS. Enter a fresh
DFU session using palera1n's guided countdown, then run the upload:

```sh
./orchard run examples/n61-ram
```

Inspect the displayed model and identity before confirming. Orchard verifies
the payload and uploads once through the shared Pongo transport. On an
Apple Silicon Mac using USB-C, watch `examples/n61-ram/out/palera1n.log` live and reconnect
the phone's Lightning plug immediately after `Checkmate!`. The recorded
successful attempt reached download mode three seconds after that cue.

After upload, run `./orchard devices` and find the `Orchard n61-ram`
product. macOS IOKit can present its name as `Orchard n61_ram`. Use that
device's `serial_port` value to connect to the USB shell.

The experiment mounts pseudo-filesystems, checks `apple,n61` and exposes an
ACM root shell. Its USB descriptor carries the current boot ID. Automatic
module loading stays disabled, and the image leaves internal storage inactive.

At the shell, record the kernel and page size:

```sh
uname -a
grep -m 1 KernelPageSize /proc/self/smaps
cat /sys/devices/system/cpu/online
tr '\000' '\n' </proc/device-tree/compatible
cat /proc/mtd
ls -l /sys/class/block
```

Capture the serial log and the build receipt together. Page size should be
4 KiB, with both Typhoon CPUs online. Classify each reported MTD or block
device by its backing driver before interpreting the inventory. A RAM region
used for the Apple Device Tree can appear separately from flash storage.

One successful RAM boot is recorded. Further qualification requires repeated
boots and a recorded return to iOS after a successful Linux session.
The build receipt starts with `hardware_tested=false`; USB upload completion
leaves Linux readiness for the serial check. Installation remains disabled
in the N61 descriptor while storage and peripheral work continues.
40 changes: 40 additions & 0 deletions examples/n61-ram/init
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/bin/busybox sh
# N61 RAM experiment: expose a physical USB shell after checking the board.
set -eu
export PATH=/bin:/sbin:/usr/bin:/usr/sbin
/bin/busybox --install -s /bin
mount -t proc -o nosuid,nodev,noexec proc /proc
mount -t sysfs -o nosuid,nodev,noexec sysfs /sys
mount -t devtmpfs -o nosuid devtmpfs /dev
mount -t tmpfs -o nosuid,nodev tmpfs /run
mkdir -p /dev/pts
mount -t devpts devpts /dev/pts
exec </dev/console >/dev/console 2>&1
echo /bin/false > /proc/sys/kernel/modprobe
if ! tr '\000' '\n' </proc/device-tree/compatible | grep -qx 'apple,n61'; then
echo 'This experiment requires the Apple iPhone 6 N61 device tree.'
exec sh -i
fi
mount -t configfs -o nosuid,nodev,noexec configfs /sys/kernel/config
name=$(cat /etc/hostname)
g=/sys/kernel/config/usb_gadget/$name
mkdir "$g"
echo 0x05ac > "$g/idVendor"
# The prototype PID matches the generic RAM example.
echo 0x4144 > "$g/idProduct"
mkdir -p "$g/strings/0x409" "$g/configs/c.1/strings/0x409"
echo "Orchard $name" > "$g/strings/0x409/product"
cat /proc/sys/kernel/random/boot_id > "$g/strings/0x409/serialnumber"
echo 'N61 physical USB diagnostic shell' > "$g/configs/c.1/strings/0x409/configuration"
# USB descriptor value; charger control requires a separate driver.
echo 100 > "$g/configs/c.1/MaxPower"
mkdir "$g/functions/acm.usb0"
ln -s "$g/functions/acm.usb0" "$g/configs/c.1/acm.usb0"
set -- /sys/class/udc/*
[ "$#" -eq 1 ] && [ -e "$1" ]
printf '%s\n' "${1##*/}" > "$g/UDC"
echo "$name: USB root shell for a trusted host."
while :; do
setsid sh -i </dev/ttyGS0 >/dev/ttyGS0 2>&1 || true
sleep 1
done
1 change: 1 addition & 0 deletions examples/n61-ram/orchard.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
device = "n61"
5 changes: 5 additions & 0 deletions examples/n61-ram/rootfs/etc/motd
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Orchard iPhone 6 RAM experiment

This image exposes a root shell over USB ACM. Use a trusted host.
The startup script mounts RAM interfaces and leaves device storage inactive.
Display and USB operation await a physical N61 test. Touch support needs work.
2 changes: 1 addition & 1 deletion hardware
34 changes: 34 additions & 0 deletions image/n61/image_policy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""Omarchy A's N61 desktop and NFS image contract."""
import json
import os
from pathlib import Path

PROFILE = 'n61'
ARCHITECTURE = 'aarch64'
KERNEL_RELEASE = '7.3.0-rc1-orchard-apple-4k'
INIT = '/usr/lib/systemd/systemd'
PANEL = (750, 1334)
SCALE = 1.5


def ready():
path = Path('/run/user/1000/omarchy-desktop-ready.json')
try:
if path.is_symlink() or path.stat().st_uid != 1000:
return False
record = json.loads(path.read_text())
if record['boot_id'] != Path('/proc/sys/kernel/random/boot_id').read_text().strip():
return False
process = Path('/proc') / str(int(record['shell_pid']))
socket = Path(record['wayland_socket'])
return (process.stat().st_uid == 1000 and
(process / 'exe').readlink().name in ('qs', 'quickshell') and
socket.is_socket() and socket.stat().st_uid == 1000)
except (OSError, ValueError, KeyError):
return False


def validate_root(root):
for helper in ('usr/lib/systemd/systemd', 'usr/libexec/omarchy-desktop-ready'):
if not os.access(root / helper, os.X_OK):
raise ValueError('N61 root lacks executable ' + helper)
83 changes: 83 additions & 0 deletions release/desktop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""Receipt-checked desktop components shared by AArch64 images."""
import hashlib
import json
from pathlib import Path, PurePosixPath


def sha(path):
return hashlib.sha256(Path(path).read_bytes()).hexdigest()


def safe_name(name):
path = PurePosixPath(name)
if path.is_absolute() or ".." in path.parts or not path.parts:
raise ValueError("Unsafe desktop component path")
return str(path)


def cursor_files(directory, project):
record = json.loads((directory / 'manifest.json').read_text())
pin = json.loads((project / 'desktop/cursor/wtype.json').read_text())
if record.get('schema') != 1 or record.get('architecture') != 'aarch64' or record.get('source') != pin:
raise ValueError('Incompatible startup cursor helper receipt')
files = {}
for name, destination, mode in (
('wtype', 'usr/libexec/omarchy-wtype', 0o755),
('LICENSE', 'usr/share/licenses/omarchy-wtype/LICENSE', 0o644)):
path = directory / name
if path.is_symlink() or not path.is_file():
raise ValueError('Invalid startup cursor helper file: ' + name)
data = path.read_bytes()
if hashlib.sha256(data).hexdigest() != record['files'].get(name):
raise ValueError('Startup cursor helper differs from receipt: ' + name)
files[destination] = (data, mode)
files['etc/omarchy/cursor-helper.json'] = (json.dumps(record, sort_keys=True).encode(), 0o644)
return files


def bluetooth_files(directory, project):
record = json.loads((directory / 'manifest.json').read_text())
pin = json.loads((project / 'desktop/bluetooth/dbus-next.json').read_text())
if record.get('schema') != 1 or record.get('source') != pin:
raise ValueError('Incompatible Bluetooth dependency receipt')
files = {}
if not {'lib/dbus_next/__init__.py', 'LICENSE'} <= record['files'].keys():
raise ValueError('Incomplete Bluetooth dependency receipt')
for name, digest in record['files'].items():
if name != 'LICENSE' and not (name.startswith('lib/dbus_next/') and name.endswith('.py')):
raise ValueError('Unexpected Bluetooth dependency file')
if safe_name(name) != name:
raise ValueError('Unsafe Bluetooth dependency path')
path = directory / name
if path.is_symlink() or not path.is_file():
raise ValueError('Invalid Bluetooth dependency file')
data = path.read_bytes()
if hashlib.sha256(data).hexdigest() != digest:
raise ValueError('Bluetooth dependency differs from receipt')
destination = ('usr/share/licenses/omarchy-bluetooth-dbus-next/LICENSE' if name == 'LICENSE'
else 'usr/lib/omarchy-bluetooth/' + name.removeprefix('lib/'))
files[destination] = (data, 0o644)
files['etc/omarchy/bluetooth-helper.json'] = (json.dumps(record, sort_keys=True).encode(), 0o644)
return files


def labwc_files(directory, project):
record = json.loads((directory / 'manifest.json').read_text())
pin = json.loads((project / 'desktop/labwc/source.json').read_text())
if record.get('schema') != 1 or record.get('source') != pin:
raise ValueError('Incompatible Labwc component receipt')
expected = {'labwc', 'LICENSE', 'source.tar.gz', '0001-cancel-super-tap-on-pointer.patch',
'packages.lock', 'compiler.txt', 'version.txt'}
if record.get('files', {}).keys() != expected:
raise ValueError('Incomplete Labwc component')
files = {}
for name, digest in record['files'].items():
path = directory / name
if path.is_symlink() or not path.is_file() or sha(path) != digest:
raise ValueError('Labwc component differs from receipt')
destination = 'usr/bin/labwc' if name == 'labwc' else 'usr/share/licenses/omarchy-labwc/' + name
files[destination] = (path.read_bytes(), 0o755 if name == 'labwc' else 0o644)
if record['files']['source.tar.gz'] != pin['sha256'] or record['files']['0001-cancel-super-tap-on-pointer.patch'] != pin['patch_sha256']:
raise ValueError('Labwc corresponding source differs from pin')
files['etc/omarchy/labwc-helper.json'] = (json.dumps(record, sort_keys=True).encode(), 0o644)
return files
64 changes: 5 additions & 59 deletions release/j71b/public_root.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from .common import group_membership, SYSTEM, NETWORK_RULE
from .runtime import PROJECT, files as runtime_files
from release import desktop as desktop_components
from release.package_permissions import privileged_entries, apply as apply_package_permissions


Expand Down Expand Up @@ -43,71 +44,15 @@ def desktop_replacements():


def cursor_files(directory):
record = json.loads((directory / 'manifest.json').read_text())
pin = json.loads((PROJECT / 'desktop/cursor/wtype.json').read_text())
if record.get('schema') != 1 or record.get('architecture') != 'aarch64' or record.get('source') != pin:
raise ValueError('Incompatible startup cursor helper receipt')
files = {}
for name, destination, mode in (
('wtype', 'usr/libexec/omarchy-wtype', 0o755),
('LICENSE', 'usr/share/licenses/omarchy-wtype/LICENSE', 0o644)):
path = directory / name
if path.is_symlink() or not path.is_file():
raise ValueError('Invalid startup cursor helper file: ' + name)
data = path.read_bytes()
if hashlib.sha256(data).hexdigest() != record['files'].get(name):
raise ValueError('Startup cursor helper differs from receipt: ' + name)
files[destination] = (data, mode)
files['etc/omarchy/cursor-helper.json'] = (json.dumps(record, sort_keys=True).encode(), 0o644)
return files
return desktop_components.cursor_files(directory, PROJECT)


def bluetooth_files(directory):
record = json.loads((directory / 'manifest.json').read_text())
pin = json.loads((PROJECT / 'desktop/bluetooth/dbus-next.json').read_text())
if record.get('schema') != 1 or record.get('source') != pin:
raise ValueError('Incompatible Bluetooth dependency receipt')
files = {}
if not {'lib/dbus_next/__init__.py', 'LICENSE'} <= record['files'].keys():
raise ValueError('Incomplete Bluetooth dependency receipt')
for name, digest in record['files'].items():
if name != 'LICENSE' and not (name.startswith('lib/dbus_next/') and name.endswith('.py')):
raise ValueError('Unexpected Bluetooth dependency file')
if safe_name(name) != name:
raise ValueError('Unsafe Bluetooth dependency path')
path = directory / name
if path.is_symlink() or not path.is_file():
raise ValueError('Invalid Bluetooth dependency file')
data = path.read_bytes()
if hashlib.sha256(data).hexdigest() != digest:
raise ValueError('Bluetooth dependency differs from receipt')
destination = ('usr/share/licenses/omarchy-bluetooth-dbus-next/LICENSE' if name == 'LICENSE'
else 'usr/lib/omarchy-bluetooth/' + name.removeprefix('lib/'))
files[destination] = (data, 0o644)
files['etc/omarchy/bluetooth-helper.json'] = (json.dumps(record, sort_keys=True).encode(), 0o644)
return files
return desktop_components.bluetooth_files(directory, PROJECT)


def labwc_files(directory):
record = json.loads((directory / 'manifest.json').read_text())
pin = json.loads((PROJECT / 'desktop/labwc/source.json').read_text())
if record.get('schema') != 1 or record.get('source') != pin:
raise ValueError('Incompatible Labwc component receipt')
expected = {'labwc', 'LICENSE', 'source.tar.gz', '0001-cancel-super-tap-on-pointer.patch',
'packages.lock', 'compiler.txt', 'version.txt'}
if record.get('files', {}).keys() != expected:
raise ValueError('Incomplete Labwc component')
files = {}
for name, digest in record['files'].items():
path = directory / name
if path.is_symlink() or not path.is_file() or sha(path) != digest:
raise ValueError('Labwc component differs from receipt')
destination = 'usr/bin/labwc' if name == 'labwc' else 'usr/share/licenses/omarchy-labwc/' + name
files[destination] = (path.read_bytes(), 0o755 if name == 'labwc' else 0o644)
if record['files']['source.tar.gz'] != pin['sha256'] or record['files']['0001-cancel-super-tap-on-pointer.patch'] != pin['patch_sha256']:
raise ValueError('Labwc corresponding source differs from pin')
files['etc/omarchy/labwc-helper.json'] = (json.dumps(record, sort_keys=True).encode(), 0o644)
return files
return desktop_components.labwc_files(directory, PROJECT)


def desktop_additions():
Expand All @@ -124,6 +69,7 @@ def system_files(kernel_release):
mode = 0o440 if name.startswith('etc/sudoers.d/') else (
0o755 if name.startswith('usr/libexec/') else 0o644)
additions[name] = (data, mode)
additions['usr/libexec/omarchy-desktop-ready'] = ((PROJECT / 'desktop/configuration/desktop-ready').read_bytes(), 0o755)
return additions


Expand Down
Loading