enable-ssh.sh turns a factory raw SD image (King Kong LE 0.97) into an SSH-enabled one: injects the static dropbear/dropbearkey/sftp-server binaries, the SysV init script (/etc/init.d/sshd), the S30sshd boot symlink and the caller-supplied public key into the ext3 rootfs partition with debugfs (no mounting). The input image is never modified, the output path must be explicit and distinct, and every byte outside p2 is verified identical afterwards. The public key is an explicit -k argument — nothing is baked in and private keys are rejected. --dry-run supported; --force for non-standard p2 geometry. bin/ is byte-identical to the deployed king_kong_le-0_97_0.ssh image. validate-image.sh functionally proves a result (boots the image's own init script under qemu, real ssh pubkey login, password auth refused, sftp round-trip); build-binaries.sh rebuilds bin/ in Docker. Co-Authored-By: Claude Code <noreply@anthropic.com>
ssh-image — key-only Dropbear SSH for a Stern Spike 2 raw SD image
Turns a factory Spike 2 raw SD image (King Kong LE 0.97.0, 16 GiB) into an
SSH-enabled image, reproducing the working setup documented in
/Users/matt/src/pinball-assets/README.md sections 1 and 3. The original image
is only ever read; the result is a new image file you choose.
enable-ssh.sh -i <factory>.raw -o <factory>.ssh.raw -k <your>.pub
No password is ever set or needed — Dropbear is compiled without password
auth. The public key is an explicit input (-k); no private key is read,
stored, or embedded anywhere by this tool.
What gets injected (into the ext3 rootfs partition, p2, only)
| Path on the machine | Source here | Mode | Owner |
|---|---|---|---|
/usr/local/bin/dropbear |
bin/dropbear |
0755 | 0:0 |
/usr/local/bin/dropbearkey |
bin/dropbearkey |
0755 | 0:0 |
/usr/libexec/sftp-server |
bin/sftp-server |
0755 | 0:0 |
/etc/init.d/sshd |
files/sshd |
0755 | 0:0 |
/etc/rc5.d/S30sshd |
symlink → ../init.d/sshd |
0777 | 0:0 |
/home/root/.ssh |
new directory | 0700 | 0:0 |
/home/root/.ssh/authorized_keys |
your -k file |
0600 | 0:0 |
Byte-identical to the deployed king_kong_le-0_97_0.ssh.16G.sdcard.raw
(verified against it with debugfs dump + sha256 for all three binaries).
- dropbear / dropbearkey — 2025.88, static musl armhf. Password auth
compiled out (
DROPBEAR_SVR_PASSWORD_AUTH 0),DROPBEAR_REEXEC=0(the Spike 2 kernel is 3.14 and predatesexecveat). - sftp-server — static Go binary (pkg/sftp). dropbear dropped its built-in
scp upstream; this makes
scp/rsyncwork. It lives at/usr/libexec/sftp-serverbecause that is dropbear's compile-timeSFTPSERVER_PATHdefault — no-sflag in the init script is needed. /etc/init.d/sshd— SysV init script: onstartit generates Dropbear host keys (rsa/ecdsa/ed25519) into/data/dropbearif absent (host keys are per-machine, so they are generated on first boot rather than baked in), then runs dropbear on port 22./etc/rc5.d/S30sshd— boot hook; S30 runs before the game'sS95game.
Everything else in the image — p1 (boot, FAT), the game partition, and every
data/dump byte — is untouched and verified byte-identical after the run
(cmp over both regions outside p2).
Requirements
- macOS (Homebrew). The image work uses
cp,dd,cmp,shasum— all stock. brew install e2fsprogs— providesdebugfsande2fsck(1.4x).python3(system) — for MBR parsing and path resolution.- For
validate-image.shonly: Docker (any linux container) — it runs privileged (bind mounts for /dev, /proc) and usesqemu-user-staticto boot the ARM rootfs.build-binaries.shalso needs Docker plus network access (musl.cc, GitHub, codeload).
Usage
# 1. make sure you have a public key (the private half never enters this repo)
ssh-keygen -t ed25519 -a 100 -f ~/.ssh/spike2 # if you don't have one
# use ~/.ssh/spike2.pub as the -k input
# 2. dry run: parses the MBR, inspects p2, prints the exact plan, writes nothing
./enable-ssh.sh -i ~/images/king_kong_le-0_97_0.Release.16G.sdcard.raw \
-o /tmp/king_kong_le-0_97_0.ssh.16G.sdcard.raw \
-k ~/.ssh/spike2.pub --dry-run
# 3. real run (APFS clone is instant; a full 16 GiB copy on other volumes takes a while)
./enable-ssh.sh -i ~/images/king_kong_le-0_97_0.Release.16G.sdcard.raw \
-o ~/images/king_kong_le-0_97_0.ssh.16G.sdcard.raw \
-k ~/.ssh/spike2.pub
# 4. optional deep check before flashing (Docker, qemu-arm, real ssh login)
./validate-image.sh -i ~/images/king_kong_le-0_97_0.ssh.16G.sdcard.raw \
-k ~/.ssh/spike2.pub
Options: -b DIR / -f DIR point at alternative binary / init-script
directories (defaults are ./bin and ./files beside the script);
--force accepts a p2 geometry other than the known King Kong layout
(12 MiB offset, 336 MiB size — the script parses the MBR, it does not assume);
--overwrite replaces an existing output file (refused by default);
--dry-run writes nothing.
The script refuses to run if: input/output are the same file, the output
already exists, the key file contains a private key or unrecognised lines,
the image has no MBR / no ext3 rootfs partition, or the rootfs fails a
read-only e2fsck -fn before or after injection.
Safety model
- The input image is opened read-only and is never written; its size is re-checked at the end.
- The output is created fresh (
cp -cAPFS clone when possible, else full copy) and must be a different path from the input. - Only one region is ever written back: p2, exactly
skip/countin 512-byte sectors. Afterwardscmpproves every byte before and after p2 equals the input — including wherever the data/dump partitions live. - Host keys are generated on the machine at first boot (
/data/dropbear), never baked into the image. - Before flashing over a card that is in use: back up its data partition
(scores/settings/NVM). The image's
/datais factory-empty; flashing it over a live card erases that data. (The original setup lost it once — restored from a pre-SSH backup via tar-over-ssh.) - Flash to a full-size SD card in the machine's
mmcblk0slot. Never flash to the machine's live SD while it is the boot device.
Validation
enable-ssh.sh self-verifies every run (filesystem health before/after,
presence + mode + owner of all injected objects, sha256 of the three
binaries inside the result, and the cmp preservation proof).
validate-image.sh goes further: it extracts the output image's p2 into a
scratch rootfs tree (debugfs rdump, no loop device needed) in a privileged
Docker container, starts dropbear via the image's own
/etc/init.d/sshd (first-boot path: the image's dropbearkey generates
the host keys into /data/dropbear), and then from the container
(a) logs in as root over ssh with a throwaway ed25519 key generated inside
the container (your private key is never read or stored), (b) confirms
password auth is refused, and (c) runs sftp put/get to prove the
sftp-server wiring. It also statically checks that the deployed
authorized_keys in the image equals the -k file you gave
enable-ssh.sh. The image file is only ever read; all test mutations happen
on a scratch copy of p2.
Building the binaries (provenance and rebuild)
bin/dropbear, bin/dropbearkey and bin/sftp-server were built once, on
2026-10-11, in the pinball-assets/.ssh-build/ tree:
| binary | build | notes |
|---|---|---|
dropbear, dropbearkey |
dropbear 2025.88, ./configure --host=arm-linux-musleabihf --disable-zlib --enable-static, localoptions.h: DROPBEAR_SVR_PASSWORD_AUTH 0, DROPBEAR_CLI_PASSWORD_AUTH 0, DROPBEAR_SVR_PUBKEY_AUTH 1, DROPBEAR_REEXEC 0 |
musl.cc arm-linux-musleabihf cross toolchain; Debian 12 container |
sftp-server |
Go 1.19+ in GOPATH mode from codeload tarballs (pkg/sftp v1.13.2, kr/fs v0.1.0, golang.org/x/crypto v0.7.0, x/sys v0.7.0), GOARCH=arm GOARM=7 CGO_ENABLED=0, go build -trimpath -ldflags="-s -w" |
static, stripped |
To rebuild them from this directory (requires Docker):
./build-binaries.sh # rewrites bin/ and prints file(1) + sha256 for each
The originals live on at pinball-assets/.ssh-build/out-musl/{dropbear,dropbearkey}
and pinball-assets/.ssh-build/out-sftp/sftp-server with the build scripts
build.sh (dropbear) and build-sftp-go.sh (sftp).
Files
| Path | What |
|---|---|
enable-ssh.sh |
the injection tool (input image + public key → output image) |
validate-image.sh |
Docker/qemu functional test of an output image (optional) |
build-binaries.sh |
rebuilds bin/ in a Docker container (optional) |
files/sshd |
the SysV init script injected to /etc/init.d/sshd |
bin/dropbear, bin/dropbearkey, bin/sftp-server |
the static armhf binaries injected into the image |