commit e3b11be0cb5f58ffdf99a3a3fbcf9c4991a45d40 equwal <13551856+equwal@users.noreply.github.com> 2026-10-04 03:27:43 -0700 Initial version
9pstore.service.in | 17 ++++++++++++++ LICENSE | 21 +++++++++++++++++ docs/USAGE.md | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ install.sh | 27 ++++++++++++++++++++++ 4 files changed, 131 insertions(+)
diff --git a/9pstore.service.in b/9pstore.service.in new file mode 100644 index 0000000..b9ca2a7 --- /dev/null +++ b/9pstore.service.in @@ -0,0 +1,17 @@ +# 9P file store served by diod. install.sh fills in the @...@ values. +# No 9P auth: the private-address bind and the firewall are the gate. +# Every client user maps to @SQUASH_USER@. +[Unit] +Description=9P file store (diod) +After=network-online.target +Wants=network-online.target + +[Service] +ExecStart=/usr/sbin/diod -f -n -S -U @SQUASH_USER@ -e @EXPORT_DIR@ -l @LISTEN_ADDR@:@PORT@ -l 127.0.0.1:@PORT@ --nwthreads 2 -L stderr +Restart=on-failure +RestartSec=5 +MemoryMax=64M +MemoryHigh=48M + +[Install] +WantedBy=multi-user.target diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..777c6b0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 equwal + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/docs/USAGE.md b/docs/USAGE.md new file mode 100644 index 0000000..07a9a82 --- /dev/null +++ b/docs/USAGE.md @@ -0,0 +1,66 @@ +# 9p-store + +A small, disk-backed 9P file server for a private network. It uses diod +(Debian package) because plan9port has no persistent disk server: its ramfs +keeps files in RAM only. diod idles at under 1 MB of memory. + +## Server + +```sh +LISTEN_ADDR=10.0.0.1 ./install.sh +``` + +The unit listens on `$LISTEN_ADDR` and on 127.0.0.1 (for SSH tunnels), with a +64 MB memory cap and restart on failure. + +### Security + +diod runs with no 9P authentication (`-n`). Access control is the network: + +- Bind only to a private address, for example a WireGuard interface. +- Drop traffic to that address unless it arrives on the private interface: + + ```sh + nft add rule inet filter input ip daddr 10.0.0.1 iifname != "wg0" drop + ``` + +- Check that nothing listens on a public address: `ss -ltnp | grep :564`. +- Never store secrets in the export. + +Hosts outside the private network can reach the loopback listener through an +SSH forward limited with `permitopen="127.0.0.1:564"` in `authorized_keys`. + +## Clients + +### Linux kernel (v9fs) + +Needs `CONFIG_9P_FS` and `CONFIG_NET_9P` (`modprobe 9p`). + +```sh +mount -t 9p -o trans=tcp,port=564,aname=/srv/9pstore,version=9p2000.L,access=any 10.0.0.1 /mnt/9pstore +``` + +Through an SSH tunnel: + +```sh +ssh -fN -L 127.0.0.1:5640:127.0.0.1:564 tunnel@server +mount -t 9p -o trans=tcp,port=5640,aname=/srv/9pstore,version=9p2000.L,access=any 127.0.0.1 /mnt/9pstore +``` + +### plan9port + +```sh +9p -a tcp!10.0.0.1!564 -A /srv/9pstore ls / +9p -a tcp!10.0.0.1!564 -A /srv/9pstore read /file.txt +echo hi | 9p -a tcp!10.0.0.1!564 -A /srv/9pstore write /file.txt +9pfuse -A /srv/9pstore 'tcp!10.0.0.1!564' /mnt/9pstore +``` + +### diod tools + +```sh +diodls -s 127.0.0.1:564 -a /srv/9pstore +diodcat -s 127.0.0.1:564 -a /srv/9pstore file.txt +``` + +Windows has no built-in 9P TCP client; use a Linux host or plan9port. diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..9a1d3f1 --- /dev/null +++ b/install.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# Install a disk-backed 9P file server (diod) on Debian/Ubuntu. +# Usage: LISTEN_ADDR=<private-ip> ./install.sh +# LISTEN_ADDR private address to serve on (for example a WireGuard address). Required. +# EXPORT_DIR directory to serve (default /srv/9pstore) +# SQUASH_USER account that owns every file (default 9pstore) +# PORT TCP port (default 564) +set -eu +: "${LISTEN_ADDR:?set LISTEN_ADDR to a private address, never a public one}" +EXPORT_DIR=${EXPORT_DIR:-/srv/9pstore} +SQUASH_USER=${SQUASH_USER:-9pstore} +PORT=${PORT:-564} + +DEBIAN_FRONTEND=noninteractive apt-get install -y diod +systemctl disable --now diod 2>/dev/null || true # Debian ships a sysv unit; use ours. +id "$SQUASH_USER" >/dev/null 2>&1 || + useradd -r -s /usr/sbin/nologin -d "$EXPORT_DIR" "$SQUASH_USER" +mkdir -p "$EXPORT_DIR" +chown "$SQUASH_USER": "$EXPORT_DIR" +chmod 770 "$EXPORT_DIR" + +sed -e "s|@LISTEN_ADDR@|$LISTEN_ADDR|" -e "s|@EXPORT_DIR@|$EXPORT_DIR|" \ + -e "s|@SQUASH_USER@|$SQUASH_USER|" -e "s|@PORT@|$PORT|" \ + "$(dirname "$0")/9pstore.service.in" > /etc/systemd/system/9pstore.service +systemctl daemon-reload +systemctl enable --now 9pstore +ss -ltn "sport = :$PORT"