# IRIX network install server `tools/irix_netinstall.py` is a single-file BOOTP + TFTP + rsh server for booting the SGI PROM and running the IRIX `inst` network installer. It is intended for a dedicated install network. rsh is unauthenticated by design. ## Requirements - Linux with Python 3.10 or newer. - `mksh` or `dash` (the server prefers `mksh`). - `bubblewrap` for the read-only shell sandbox (recommended; `--sandbox none` runs without it, but then absolute paths can reach the host filesystem). - Root, or the ability to bind UDP 67, UDP 69 and TCP 514. - No other BOOTP/TFTP/rsh service on those ports. On Debian/Ubuntu: ```sh sudo apt-get install mksh bubblewrap ``` ## Install tree The server serves an already-extracted IRIX distribution tree. It does not mount ISO images or unpack `tardist` files itself. The paths used by the PROM and `inst` are relative to `--root`: ``` /srv/irix/ 6.5.30/ stand/ fx.ARCS fx.64 sashARCS sash64 dist/ ... install.cmds ``` The `dist` directory is what `inst` opens as the remote distribution. The `stand` directory is what the PROM boots with `bootp()`. ## Run it ```sh sudo ./tools/irix_netinstall.py \ --root /srv/irix \ --server-ip 10.0.0.1 \ --client-ip 10.0.0.2 \ --client-mac 08:00:69:07:dc:e0 \ --bootfile /6.5.30/stand/fx.ARCS ``` Useful options: | Option | Meaning | | --- | --- | | `--l2-iface tap-mess-1000-0` | BOOTP over AF_PACKET for MAME/QEMU taps | | `--shell mksh` / `--shell dash` | force a shell | | `--sandbox bwrap` / `--sandbox none` | force a shell sandbox mode | | `--bootp-port`, `--tftp-port`, `--rsh-port` | non-default ports (testing) | | `--allow-high-ports` | accept rsh clients from non-reserved ports (tests only) | At the PROM: ``` setenv netaddr 10.0.0.2 boot -f bootp():/6.5.30/stand/fx.ARCS ``` For a 64-bit machine use `fx.64`; for the 32-bit IP22 Indy/Indigo2 use `fx.ARCS`. After partitioning, return to the maintenance menu, choose the software installation option, enter the server address and the distribution path without a trailing slash, for example: ``` /6.5.30/dist ``` Then load a command file at `Inst>`: ``` admin source 10.0.0.1:/install.cmds ``` ## What the services do ### BOOTP Answers RFC 951 requests on UDP 67. The reply carries: - `yiaddr` = `--client-ip` - `siaddr` = `--server-ip` - `file` = the file the PROM asked for, or `--bootfile` - the client's `chaddr`, transaction ID and flags echoed It can answer by MAC (`--client-mac`) and accepts plain UDP or AF_PACKET (`--l2-iface`). The L2 mode exists because some emulated clients send from `0.0.0.0` in a way the host IP stack never receives. ### TFTP Serves RFC 1350 RRQ in octet mode. It opens a new UDP socket per transfer and keeps that socket's source port **at or below 32767**, because the 5.0 B6 and 5.1.2 B4 PROMs compare the server port as a signed 16-bit value. It also supports the SGI boot-tape directory format: ``` bootp():/images/boottape(sash) ``` The first 512-byte block is checked for magic `0xACED1234` (or the byte-swapped `0x3412EDAC`), the checksum, and the 20-entry directory; only the requested member's bytes are transferred. ### rsh Serves the BSD `rcmd` protocol on TCP 514: - the client must connect from a reserved source port (512-1023); - the server reads the stderr port, dials back **before** reading the user and command fields, then answers with a zero byte; - `exec /bin/sh` is accepted and mapped to `mksh`, falling back to `dash`; - stdout is the primary connection, stderr is the callback connection; - callback bytes `0x02` are forwarded to the shell's process group as SIGINT. The host's `/bin/sh` is never changed. ## Why mksh/dash, not bash or csh `inst` runs `exec /bin/sh` and streams real POSIX shell lines: `dd`, `cat`, `ls`, `grep`, pipes, subshells, `trap`, `$?`, and the marker idiom: ```sh echo 'TOKEN\c' ``` Bash's builtin `echo` does not interpret `\c` by default (not even with `--posix`), so the marker gets a literal `\c` and a newline. csh is a different syntax family entirely and cannot parse the stream. `mksh` and `dash` handle `\c` correctly. The server prefers `mksh` because it is the shell used by the proven IRIX install guides. The shell is run in a read-only bubblewrap sandbox: - the install tree is bound at `/irix` and each top-level entry is symlinked to `/`, so absolute `inst` paths such as `/6.5.30/dist` work; - host `/bin`, `/usr`, `/lib`, `/lib64` and `/etc` are bind-mounted read-only; - a small wrapper directory provides an IRIX-compatible `dd` that accepts `iseek`/`oseek` and refuses writes; - `egrep`/`fgrep` wrappers map to GNU `grep -E`/`-F`. With `--sandbox none`, the shell runs with the install tree as its working directory but can still reach absolute host paths. Use that only on an isolated install network. ## Tested Protocol tests run without root on loopback: ```sh cd tools python3 -m unittest -v test_irix_netinstall ``` They cover BOOTP, TFTP file and TPD-member transfers, rsh shell sessions, the `\c` marker, and `dd` under both `none` and `bwrap`. The server has not yet been run against a physical Indy, so treat a real install as the acceptance test. ## Prior art - [instigator](https://github.com/jamesbraid/instigator) — Go server with BOOTP/TFTP/rsh and a constrained POSIX interpreter; the best reference for the `inst` shell vocabulary. - [irixboot](https://github.com/halfmanhalftaco/irixboot) — Vagrant VM using isc-dhcp, tftpd-hpa and rsh-server. - [Remote IRIX 6.5 install from Linux](http://techpubs.spinlocksolutions.com/irix/remote-irix-6.5-installation-from-linux.html) — the `mksh` + `in.rshd` + `bootp` + `tftpd` recipe.