Files
ssmithxandClaude Opus 5.5 519ccdb5b2 functions.sh: ipcalc.sh by store path; INSTALL.md: the Pi has one AP
netifd's dhcp.script calls functions.sh's ipcalc without base-files' bin/
on PATH ("ipcalc.sh: not found" at every lease).  The Pi's brcmfmac
allows one AP: TollGate's open one, so its private network stays down.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 18:46:12 -04:00

1745 lines
82 KiB
Scheme

;;; OpenWrt userland for Guix (Phase 3).
;;;
;;; Pinned to the commits OpenWrt main builds (package/*/*/Makefile,
;;; PKG_SOURCE_VERSION), so the set is one OpenWrt has tested together.
;;; Update: read those Makefiles, check out the commit, `guix hash -x -r .'.
;;; Lua bindings and examples are off: nothing here uses Lua (LuCI is ucode).
(define-module (openwrt)
;; Same cross toolchain as rpi4.scm, so `guix build --target' of these
;; packages gives the derivations the system uses (PITFALLS #35).
#:use-module (cross-libc-fix)
#:use-module (guix packages)
#:use-module (guix git-download)
#:use-module (guix gexp)
#:use-module (guix utils)
#:use-module ((guix licenses) #:prefix license:)
#:use-module (guix build-system cmake)
#:use-module (guix build-system copy)
#:use-module (guix build-system gnu)
#:use-module (gnu packages busybox)
#:use-module (srfi srfi-13)
#:use-module (gnu packages compression)
#:use-module (gnu packages crypto)
#:use-module (gnu packages linux)
#:use-module (gnu packages pkg-config)
#:use-module (gnu packages tls)
#:use-module ((gnu packages gnunet) #:select (libmicrohttpd))
#:use-module (guix download)
#:use-module (gnu packages web))
;; CMake looks for `pkg-config', but cross builds only have
;; TARGET-pkg-config. FindPkgConfig honours $PKG_CONFIG, which must be an
;; absolute file name; PATH is only set up once the phases run. A procedure,
;; called from `arguments', so pkg-config-for-target sees the target (a
;; top-level gexp would capture the native name).
(define (openwrt-phases)
#~(modify-phases %standard-phases
(add-before 'configure 'set-pkg-config
(lambda _
(setenv "PKG_CONFIG"
(or (which #$(pkg-config-for-target))
(error "pkg-config not found"
#$(pkg-config-for-target))))))))
;; copy-build-system ignores --target (Guix 1.5.0 lowers it with #:target
;; #f), so a store reference in what it installs -- an input or a gexp
;; `file-append' -- names an x86_64 build even in a cross-built system
;; (PITFALLS #39). For script packages that refer to other packages: GNU
;; build system, which does cross-build, running only copy-build-system's
;; install phase. Use as #:phases (modify-phases (cross-copy-phases ...) ...)
;; with #:modules %cross-copy-modules.
(define %cross-copy-modules
'((guix build gnu-build-system)
((guix build copy-build-system) #:prefix copy:)
(guix build utils)))
(define (cross-copy-phases install-plan)
#~(modify-phases %standard-phases
(delete 'bootstrap)
(delete 'configure)
(delete 'build)
(delete 'check)
(replace 'install
(lambda args
;; Its `install' isn't exported; take it from its phases.
(apply (assoc-ref copy:%standard-phases 'install)
#:install-plan #$install-plan args)))))
(define-public libubox
(let ((commit "e7608b69283d919d031d13cc8e21692503f5dbea")
(revision "0"))
(package
(name "libubox")
(version (git-version "2026.07.21" revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url "https://git.openwrt.org/project/libubox.git")
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32 "11qzh7p7qjn8d4qlfv4aclx2y7bs873hhsdaqx2x5qfd61frq7kl"))))
(build-system cmake-build-system)
(arguments
(list #:configure-flags #~'("-DBUILD_LUA=OFF" "-DBUILD_EXAMPLES=OFF")
#:tests? #f ;tests need cmocka + UNIT_TESTING
#:phases
#~(modify-phases #$(openwrt-phases)
(add-after 'unpack 'absolute-jshn
;; jshn.sh runs `jshn' from PATH; netifd's protocol
;; scripts source it with a minimal environment.
(lambda _
(substitute* "sh/jshn.sh"
(("`jshn ")
(string-append "`" #$output "/bin/jshn "))
(("^\tjshn ")
(string-append "\t" #$output "/bin/jshn "))))))))
(inputs (list json-c))
(native-inputs (list pkg-config))
(home-page "https://git.openwrt.org/?p=project/libubox.git")
(synopsis "OpenWrt base utility library")
(description
"libubox is the collection of helpers used throughout the OpenWrt
userland: lists and AVL trees, the blob/blobmsg binary message format, the
uloop event loop, ustream I/O, JSON conversion (@code{blobmsg_json}),
@code{json_script}, and the @command{jshn} shell JSON helper used by netifd
protocol scripts.")
;; Mostly ISC; avl.c/avl.h and friends (from olsrd) are BSD-3.
(license (list license:isc license:bsd-3)))))
(define-public ubus
(let ((commit "414c60a2e29e72c2c4f573bc50ad54aef076d169")
(revision "0"))
(package
(name "ubus")
(version (git-version "2026.09.25" revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url "https://git.openwrt.org/project/ubus.git")
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32 "0mmlyw3ad0fzfa6rghmbi6bssznnjvjhm3p1r7lwhs3fvscqsjg5"))))
(build-system cmake-build-system)
(arguments
;; The socket path (/var/run/ubus/ubus.sock) is compiled in; ubusd
;; doesn't create /var/run/ubus, so the service must.
(list #:configure-flags #~'("-DBUILD_LUA=OFF" "-DBUILD_EXAMPLES=OFF")
#:tests? #f
#:phases (openwrt-phases)))
(inputs (list libubox json-c))
(native-inputs (list pkg-config))
(home-page "https://git.openwrt.org/?p=project/ubus.git")
(synopsis "OpenWrt system message bus")
(description
"ubus is the RPC and event bus of the OpenWrt userland: the
@command{ubusd} daemon, the @command{ubus} command-line client, and the
@code{libubus} client library.")
(license license:lgpl2.1))))
(define-public uci
(let ((commit "66127cd76c5d0bd46d5a90302cc6110f53a4e2f8")
(revision "0"))
(package
(name "uci")
(version (git-version "2025.12.02" revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url "https://git.openwrt.org/project/uci.git")
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32 "16rp60i2ypp84higqsnckypm674rc45jmhmnm5bds8m08nqh73ny"))))
(build-system cmake-build-system)
(arguments
(list #:configure-flags #~'("-DBUILD_LUA=OFF")
#:tests? #f
#:phases (openwrt-phases)))
(inputs (list libubox))
(native-inputs (list pkg-config))
(home-page "https://git.openwrt.org/?p=project/uci.git")
(synopsis "OpenWrt Unified Configuration Interface")
(description
"UCI is the configuration framework of OpenWrt: the @code{libuci}
library and @command{uci} tool for reading and changing the
@file{/etc/config/*} files that all OpenWrt services are configured from.")
;; libuci is LGPL 2.1; the uci command (cli.c) is GPL 2.
(license (list license:lgpl2.1 license:gpl2)))))
(define-public libnl-tiny
(let ((commit "40493a655d8caa2ccf5206dde1e733abe2920432")
(revision "0"))
(package
(name "libnl-tiny")
(version (git-version "2025.12.02" revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url "https://git.openwrt.org/project/libnl-tiny.git")
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32 "1031dq1h8ls6vl23cah2bcpaldmi2nh6jq53ndz07mcm5ali9v2k"))))
(build-system cmake-build-system)
(arguments (list #:tests? #f)) ;no tests
(home-page "https://git.openwrt.org/?p=project/libnl-tiny.git")
(synopsis "Small netlink library from OpenWrt")
(description
"libnl-tiny is a stripped-down version of libnl with the generic
netlink parts used by OpenWrt's netifd, ucode and wireless tools. Its headers
install under @file{include/libnl-tiny} so it can coexist with libnl.")
(license license:lgpl2.1))))
(define-public jsonfilter
(let ((commit "b9034210bd331749673416c6bf389cccd4e23610")
(revision "0"))
(package
(name "jsonfilter")
(version (git-version "2026.03.16" revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url "https://git.openwrt.org/project/jsonpath.git")
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32 "144jhmb06x8xsgx0x3dsral3lkjr32yhg4nad2cjgcw3pwfg0pmw"))))
(build-system cmake-build-system)
(arguments
(list #:tests? #f ;no tests
;; The lemon rules use paths relative to the source tree.
#:out-of-source? #f
#:phases
#~(modify-phases #$(openwrt-phases)
(add-after 'install 'install-jsonfilter
;; Built as `jsonpath'; OpenWrt and its scripts call it
;; `jsonfilter'.
(lambda _
(symlink "jsonpath"
(string-append #$output "/bin/jsonfilter")))))))
;; The build compiles and runs the lemon parser generator with the
;; native `gcc', which cross builds also have.
(inputs (list libubox json-c))
(native-inputs (list pkg-config))
(home-page "https://git.openwrt.org/?p=project/jsonpath.git")
(synopsis "Query JSON with JSONPath expressions from the shell")
(description
"jsonfilter extracts values from JSON documents with JSONPath
expressions; OpenWrt's shell scripts use it to parse @command{ubus}
output.")
(license license:isc))))
(define-public ucode
(let ((commit "b885dd0fe1e974551fb1233ca5f3aa74d80b74d9")
(revision "0"))
(package
(name "ucode")
(version (git-version "0.0.20250529" revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url "https://github.com/jow-/ucode")
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32 "00gdsxz0ic5jf5masf5bdana2nnz80fbp1if2k2hhw0rqwv6arjg"))))
(build-system cmake-build-system)
(arguments
(list
#:configure-flags
;; The module search path is compiled in and has no run-time
;; override (only `ucode -L'). Add the system profile, so modules
;; from other packages (firewall4's fw4.uc, LuCI) are found.
#~(list (string-append
"-DLIB_SEARCH_PATH="
#$output "/lib/ucode/*.so:" #$output "/share/ucode/*.uc:"
"/run/current-system/profile/lib/ucode/*.so:"
"/run/current-system/profile/share/ucode/*.uc:"
"./*.so:./*.uc"))
#:tests? #f ;tests need a full test harness
#:phases (openwrt-phases)))
;; All modules: fs, io, math, debug, struct, socket, log, resolv,
;; plus ubus, uci, uloop (libubox), rtnl and nl80211 (libnl-tiny),
;; zlib and digest (libmd).
(inputs (list json-c libubox ubus uci libnl-tiny zlib libmd))
(native-inputs (list pkg-config))
(home-page "https://ucode.mein.io/")
(synopsis "Small ECMAScript-like scripting language from OpenWrt")
(description
"ucode is a tiny scripting language with ECMAScript-like syntax and
template support, used by OpenWrt for firewall4, netifd's wireless handling,
rpcd and LuCI. This package provides the @command{ucode} interpreter,
@code{libucode} and its native modules.")
(license license:isc))))
;;; Files that live in OpenWrt's main tree, not in the projects' own repos
;;; (netifd's main.uc and ifup/ifdown, base-files' shell library, ...).
;;; Same commit as the PKG_SOURCE_VERSION pins above; trimmed to the
;;; directories packages here take files from.
(define %openwrt-tree-commit "6c8798939303bbe8e19bd50eb810170cfadd0daf")
(define-public %openwrt-tree
(origin
(method git-fetch)
(uri (git-reference
(url "https://github.com/openwrt/openwrt")
(commit %openwrt-tree-commit)))
(file-name (git-file-name "openwrt-tree" (string-take %openwrt-tree-commit 7)))
(sha256
(base32 "1adbjb400afhl183c4inca0qrq8q39b0qybfvfq3qb142a9190q7"))
(modules '((guix build utils) (ice-9 ftw)))
(snippet
'(let ((keep '("package/base-files/files"
"package/network/config/netifd/files"
"package/system/uci/files")))
(mkdir "keep")
(for-each (lambda (dir)
(mkdir-p (string-append "keep/" (dirname dir)))
(rename-file dir (string-append "keep/" dir)))
keep)
(for-each (lambda (f)
(unless (member f '("." ".." "keep"))
(delete-file-recursively f)))
(scandir "."))
(for-each (lambda (f)
(unless (member f '("." ".."))
(rename-file (string-append "keep/" f) f)))
(scandir "keep"))
(rmdir "keep")))))
(define-public openwrt-base-files
(package
(name "openwrt-base-files")
(version (string-append "0-" (string-take %openwrt-tree-commit 7)))
(source %openwrt-tree)
(build-system gnu-build-system) ;not copy: cross refs (PITFALLS #39)
(arguments
(list
#:modules %cross-copy-modules
#:imported-modules %copy-build-system-modules
#:phases
#~(modify-phases
;; The shell library OpenWrt scripts source (netifd protocols,
;; hotplug and init scripts): /lib/functions.sh,
;; /lib/functions/*.sh, and /lib/config/uci.sh from OpenWrt's uci
;; package. Not the flash, LED, preinit or sysupgrade helpers.
#$(cross-copy-phases
#~'(("package/base-files/files/lib/functions.sh" "lib/")
("package/base-files/files/lib/functions/" "lib/functions/"
#:include ("network.sh" "ipv4.sh" "system.sh"
"uci-defaults.sh"))
("package/system/uci/files/lib/config/uci.sh"
"lib/config/")
;; functions.sh's ipcalc() runs it by name.
("package/base-files/files/bin/ipcalc.sh" "bin/")))
(add-after 'install 'store-paths
;; Absolute paths into this package, uci and libubox; plain
;; command names are found via PATH (set by the services).
(lambda _
(substitute* (string-append #$output "/bin/ipcalc.sh")
;; Not the native bash patch-source-shebangs put there.
(("^#!.*")
(string-append "#!" #$(file-append busybox "/bin/sh") "\n")))
(substitute* (append (find-files (string-append #$output "/lib")
"\\.sh$")
(list (string-append #$output
"/bin/ipcalc.sh")))
(("/lib/functions\\.sh")
(string-append #$output "/lib/functions.sh"))
(("/lib/functions/")
(string-append #$output "/lib/functions/"))
(("/lib/config/uci\\.sh")
(string-append #$output "/lib/config/uci.sh"))
(("/sbin/uci")
#$(file-append uci "/bin/uci"))
(("/usr/share/libubox/jshn\\.sh")
#$(file-append libubox "/share/libubox/jshn.sh")))
;; Not every caller has this package's bin/ on PATH: netifd's
;; dhcp.script ("ipcalc.sh: not found", no subnet check).
(substitute* (string-append #$output "/lib/functions.sh")
(("\\$\\(ipcalc\\.sh ")
(string-append "$(" #$output "/bin/ipcalc.sh "))))))))
(home-page "https://openwrt.org/")
(synopsis "OpenWrt shell function library")
(description
"The subset of OpenWrt's base-files that its shell scripts source:
@file{functions.sh} (config loading, @code{append}, @code{ipcalc} with
@file{bin/ipcalc.sh}, ...),
@file{functions/network.sh} (@code{network_get_ipaddr} and friends, via
ubus), and @file{config/uci.sh} (UCI wrappers).")
(license license:gpl2)))
(define-public udebug
(let ((commit "6726e98ad69402e79b6d0b305012ace857ca0786")
(revision "0"))
(package
(name "udebug")
(version (git-version "2026.09.25" revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url "https://git.openwrt.org/project/udebug.git")
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32 "0lqz2hqi0wm212q58ijmlnxyffdcm4c00pci7m5r94cbb310n99w"))))
(build-system cmake-build-system)
(arguments (list #:tests? #f #:phases (openwrt-phases)))
(inputs (list libubox ubus ucode json-c))
(native-inputs (list pkg-config))
(home-page "https://git.openwrt.org/?p=project/udebug.git")
(synopsis "OpenWrt debug ring buffers")
(description
"udebug provides shared-memory ring buffers that OpenWrt daemons such
as netifd log debug data into: @code{libudebug}, the @command{udebugd}
daemon, the @command{udebug-cli} tool and a ucode module.")
(license license:gpl2))))
(define-public netifd
(let ((commit "06d06c86d757196e56bb13a1a8d6d69df7cad04d")
(revision "0"))
(package
(name "netifd")
(version (git-version "2026.09.11" revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url "https://git.openwrt.org/project/netifd.git")
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32 "19hryqjfz254cv7blygwvmz0r3rbj92gg8c7y54vwvcfwh5rqyh5"))
;; glibc's popen() is unusable in our cross-built systems
;; (PITFALLS #35); run /bin/sh directly for handler discovery.
(patches (list (local-file "patches/netifd-popen-bin-sh.patch")))))
(build-system cmake-build-system)
(arguments
(list
#:tests? #f ;no tests
;; make_ethtool_modes_h.sh is run relative to the source tree.
#:out-of-source? #f
#:phases
#~(modify-phases #$(openwrt-phases)
(add-after 'unpack 'set-main-path
;; Scripts and ucode files live in OUT/lib/netifd. `-p'
;; would only cover the shell side: ucode.c loads
;; DEFAULT_MAIN_PATH "/main.uc" directly.
(lambda _
(substitute* "netifd.h"
(("#define DEFAULT_MAIN_PATH\t\"/lib/netifd\"")
(string-append "#define DEFAULT_MAIN_PATH\t\""
#$output "/lib/netifd\"")))))
(add-after 'install 'install-runtime-files
;; What OpenWrt's package installs to /lib/netifd. proto/
;; (dhcp.sh) comes with udhcpc and base-files.
(lambda _
(let ((lib (string-append #$output "/lib/netifd"))
(files (string-append
#$%openwrt-tree
"/package/network/config/netifd/files")))
(for-each (lambda (f) (install-file f lib))
(append
(list "scripts/utils.sh"
"scripts/netifd-proto.sh")
(find-files (string-append files "/lib/netifd")
"\\.uc$")))
;; The dhcp protocol: udhcpc driven by proto/dhcp.sh, with
;; dhcp.script reporting leases back over ubus.
(install-file (string-append files "/lib/netifd/dhcp.script")
lib)
(install-file (string-append files
"/lib/netifd/proto/dhcp.sh")
(string-append lib "/proto"))
;; Wireless (wifi-scripts): main.uc loads wireless.uc
;; from its own directory, handlers from wireless/.
(copy-recursively
#$(file-append (this-package-input "wifi-scripts")
"/lib/netifd")
lib)
;; ifup/ifdown (one script, by $0), ifstatus, devstatus:
;; ubus calls to netifd.
(let ((sbin (string-append #$output "/sbin")))
(for-each (lambda (f)
(install-file (string-append files "/sbin/" f)
sbin))
'("ifup" "ifstatus" "devstatus"))
(symlink "ifup" (string-append sbin "/ifdown"))
(substitute* (list (string-append sbin "/ifstatus")
(string-append sbin "/devstatus"))
(("/usr/share/libubox/jshn.sh")
#$(file-append libubox "/share/libubox/jshn.sh"))))
(for-each (lambda (f) (chmod f #o755))
(list (string-append lib "/dhcp.script")
(string-append lib "/proto/dhcp.sh")
(string-append #$output "/sbin/ifup")
(string-append #$output "/sbin/ifstatus")
(string-append #$output "/sbin/devstatus")))
;; Sourced by proto scripts: find netifd and jshn.sh in
;; the store, not /lib/netifd and /usr/share/libubox.
(substitute* (string-append lib "/netifd-proto.sh")
(("/lib/netifd\\}") (string-append lib "}"))
(("/usr/share/libubox/jshn.sh")
#$(file-append libubox "/share/libubox/jshn.sh")))
(let ((base #$(this-package-input "openwrt-base-files"))
(udhcpc #$(file-append (this-package-input "busybox")
"/sbin/udhcpc")))
(substitute* (list (string-append lib "/dhcp.script")
(string-append lib "/proto/dhcp.sh")
(string-append #$output "/sbin/ifup")
(string-append #$output "/sbin/ifstatus")
(string-append #$output "/sbin/devstatus"))
(("/lib/functions\\.sh")
(string-append base "/lib/functions.sh"))
(("/lib/functions/")
(string-append base "/lib/functions/"))
(("/lib/config/uci\\.sh")
(string-append base "/lib/config/uci.sh"))
(("/lib/netifd/") (string-append lib "/"))
(("/sbin/udhcpc") udhcpc)
;; Written for busybox ash (`export -n', ...): run
;; them with it, not whatever /bin/sh is.
(("^#!/bin/sh")
(string-append "#!" #$(file-append
(this-package-input "busybox")
"/bin/sh")))
;; Run by name through netifd: use the store path.
(("proto_run_command \"\\$config\" udhcpc")
(string-append "proto_run_command \"$config\" "
udhcpc))))))))))
(inputs (list libubox ubus uci ucode json-c libnl-tiny udebug
openwrt-base-files busybox wifi-scripts))
(native-inputs (list pkg-config))
(home-page "https://git.openwrt.org/?p=project/netifd.git")
(synopsis "OpenWrt network interface configuration daemon")
(description
"netifd configures network devices, interfaces, bridges, VLANs and
routes from UCI (@file{/etc/config/network}) and exposes their state on
ubus (@code{network.interface.*}); protocol handlers are built in (static)
or shell/ucode scripts (dhcp, ...).")
(license license:gpl2))))
(define-public odhcpd
(let ((commit "68f382690bfaec56d5b1f31c3c31c48bcb642e3a")
(revision "0"))
(package
(name "odhcpd")
(version (git-version "2026.06.28" revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url "https://git.openwrt.org/project/odhcpd.git")
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32 "0pfj7rid49hsf564az5mjv2q3hlv1kjdy2qvq5q6hfvlb57bywfv"))))
(build-system cmake-build-system)
(arguments
(list
;; OpenWrt's "odhcpd" (full) variant. DHCPv4 is built in but off
;; in our UCI seed: dnsmasq serves DHCPv4 and DNS, as on OpenWrt.
#:configure-flags #~'("-DUBUS=ON" "-DDHCPV4_SUPPORT=ON")
#:tests? #f ;no tests
#:phases
#~(modify-phases #$(openwrt-phases)
(add-after 'install 'install-odhcpd-update
;; The `leasetrigger' run (posix_spawn, absolute file name)
;; when leases change: make dnsmasq reread the hosts files
;; odhcpd writes to `hostsdir'. OpenWrt's version signals
;; through procd; here dnsmasq-service-type's `reload' action
;; sends the SIGHUP. (Not `pkill -x dnsmasq': busybox
;; matches the whole /gnu/store/.../dnsmasq argv[0].)
(lambda _
(let ((script (string-append #$output "/sbin/odhcpd-update"))
(busybox #$(this-package-input "busybox")))
(call-with-output-file script
(lambda (port)
(format port "#!~a/bin/sh
# Make dnsmasq reread odhcpd's hosts files.
exec /run/current-system/profile/bin/herd reload dnsmasq >/dev/null~%"
busybox)))
(chmod script #o755)))))))
(inputs (list libubox ubus uci json-c libnl-tiny busybox))
(native-inputs (list pkg-config))
(home-page "https://git.openwrt.org/?p=project/odhcpd.git")
(synopsis "OpenWrt DHCPv6, router advertisement and NDP server")
(description
"odhcpd serves and relays the IPv6 configuration protocols of a home
router (RFC 6204): router advertisements, DHCPv6 (stateful and stateless,
with prefix delegation) and NDP proxying, plus optionally DHCPv4. It is
configured from UCI (@file{/etc/config/dhcp}) and follows netifd's
interfaces over ubus.")
(license license:gpl2))))
(define-public firewall4
(let ((commit "b6e5157527d361f99ad52eaa6da273cb0f2dfd59")
(revision "0"))
(package
(name "firewall4")
(version (git-version "2025.03.17" revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url "https://git.openwrt.org/project/firewall4.git")
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32 "113sjgwrnlk451b7d3hjl11f44rr9j8vvhy4qych636mvcg29g1m"))))
(build-system gnu-build-system) ;not copy: cross refs (PITFALLS #39)
(arguments
(list
#:modules %cross-copy-modules
#:imported-modules %copy-build-system-modules
#:phases
#~(modify-phases
;; OpenWrt's layout, minus /usr. etc/ holds the stock
;; /etc/config/firewall and the hotplug script, for the
;; service to use; nothing reads them from the store directly.
#$(cross-copy-phases
#~'(("root/sbin/fw4" "sbin/")
("root/usr/share/firewall4" "share/firewall4")
("root/usr/share/ucode/fw4.uc" "share/ucode/")
("root/usr/share/nftables.d" "share/nftables.d")
;; Not init.d/firewall: a procd script.
("root/etc" "etc" #:exclude-regexp ("init\\.d"))))
(add-after 'install 'store-paths
(lambda _
(let ((busybox #$(this-package-input "busybox"))
(nft #$(file-append (this-package-input "nftables")
"/sbin/nft"))
(utpl #$(file-append (this-package-input "ucode")
"/bin/utpl"))
(share (string-append #$output "/share")))
(substitute* (string-append share "/ucode/fw4.uc")
(("/usr/sbin/nft") nft)
(("/usr/share/firewall4/") (string-append share "/firewall4/"))
(("/usr/share/nftables.d/")
(string-append share "/nftables.d/")))
;; fw4 runs utpl, nft and flock by name, and main.uc runs
;; firewall includes with `sh': give it its own PATH.
;; -L: load this fw4.uc, not whatever the profile has.
(substitute* (string-append #$output "/sbin/fw4")
;; (patch-source-shebangs has already run.)
(("^#!.*/bin/sh")
(string-append "#!" busybox "/bin/sh\n"
"PATH=" busybox "/bin:" busybox "/sbin:"
(dirname nft) ":" (dirname utpl)
":$PATH"))
(("^MAIN=/usr/share/firewall4/main.uc")
(string-append "MAIN=" share "/firewall4/main.uc"))
(("utpl -S")
(string-append "utpl -L " share "/ucode -S")))
;; netifd's hotplug: there is no /etc/init.d/firewall;
;; reload only when fw4 has loaded a ruleset (state file),
;; i.e. the firewall service runs.
(substitute* (string-append #$output
"/etc/hotplug.d/iface/20-firewall")
;; Not the native bash patch-source-shebangs put there.
(("^#!.*/bin/sh") (string-append "#!" busybox "/bin/sh"))
(("^/etc/init.d/firewall enabled \\|\\| exit 0")
"[ -f /var/run/fw4.state ] || exit 0"))
;; As OpenWrt's package: LuCI's firewall pages look for
;; fw3.
(symlink "fw4" (string-append #$output "/sbin/fw3"))))))))
(inputs (list busybox nftables ucode))
(home-page "https://git.openwrt.org/?p=project/firewall4.git")
(synopsis "OpenWrt nftables firewall configured from UCI")
(description
"firewall4 (fw4) generates an nftables ruleset from the UCI firewall
configuration (@file{/etc/config/firewall}: zones, forwardings, rules,
redirects, NAT, sets) and loads it into the @code{inet fw4} table. It
follows netifd's interfaces over ubus and is written in ucode.")
(license license:isc))))
;; One package's files/ directory from the OpenWrt tree (init scripts, stock
;; configs): same checkout as %openwrt-tree, another subset.
(define (openwrt-tree-files dir)
(origin
(inherit %openwrt-tree)
(snippet
#~(begin
(use-modules (guix build utils) (ice-9 ftw))
(rename-file #$dir "../files")
(for-each (lambda (f)
(unless (member f '("." ".."))
(delete-file-recursively f)))
(scandir "."))
(copy-recursively "../files" ".")
(delete-file-recursively "../files")))))
;; OpenWrt's dnsmasq init script, which renders /etc/config/dhcp into
;; dnsmasq's configuration -- what LuCI's DNS and DHCP pages edit.
(define %openwrt-dnsmasq-files
(openwrt-tree-files "package/network/services/dnsmasq/files"))
(define-public openwrt-dnsmasq
(package
(name "openwrt-dnsmasq")
(version (string-append "0-" (string-take %openwrt-tree-commit 7)))
(source %openwrt-dnsmasq-files)
(build-system gnu-build-system) ;not copy: cross refs (PITFALLS #39)
(arguments
(list
#:modules %cross-copy-modules
#:imported-modules %copy-build-system-modules
#:phases
#~(modify-phases
#$(cross-copy-phases
#~'(("dnsmasq.init" "share/openwrt-dnsmasq/")
("dhcpbogushostname.conf" "share/dnsmasq/")
("rfc6761.conf" "share/dnsmasq/")
("dhcp-script.sh" "lib/dnsmasq/")))
(add-after 'install 'store-paths
(lambda _
(let* ((busybox #$(this-package-input "busybox"))
(base #$(this-package-input "openwrt-base-files"))
(profile "/run/current-system/profile")
(init (string-append #$output
"/share/openwrt-dnsmasq/dnsmasq.init")))
(substitute* init
;; The service sets DNSMASQ (rpi4.scm's dnsmasq build).
(("^PROG=/usr/sbin/dnsmasq") "PROG=\"${DNSMASQ:?}\"")
(("/usr/share/dnsmasq/")
(string-append #$output "/share/dnsmasq/"))
(("/usr/lib/dnsmasq/")
(string-append #$output "/lib/dnsmasq/"))
(("/lib/functions/network\\.sh")
(string-append base "/lib/functions/network.sh"))
;; odhcpd "installed and enabled": a Shepherd service.
(("\\[ -x /usr/sbin/odhcpd \\] && \\[ -x /etc/init.d/odhcpd \\]")
(string-append "[ -x " profile "/sbin/odhcpd ]"))
;; Not `herd status': the renderer must never talk to
;; Shepherd, which may be waiting on it (PITFALLS #43).
(("/etc/init.d/odhcpd enabled") "true")
;; --enable-ubus only if this dnsmasq has it (Guix's
;; doesn't: "no-UBus").
(("\\[ \\$ubus -gt 0 \\] && xappend")
"[ $ubus -gt 0 ] && ! $PROG --version | grep -q no-UBus && xappend"))
(substitute* (string-append #$output "/lib/dnsmasq/dhcp-script.sh")
(("^#!.*") (string-append "#!" busybox "/bin/sh\n")))))))))
(inputs (list busybox openwrt-base-files))
(home-page "https://openwrt.org/")
(synopsis "OpenWrt's dnsmasq configuration from UCI")
(description
"OpenWrt's dnsmasq init script, which turns @file{/etc/config/dhcp}
(DNS and DHCPv4 settings, static leases, domains) into a dnsmasq
configuration; @command{openwrt-rc} (procd-compat) runs it.")
(license license:gpl2)))
;; OpenWrt's uhttpd init script and stock /etc/config/uhttpd: uhttpd
;; configured from UCI (LuCI, TollGate's setup edit it).
(define-public openwrt-uhttpd
(package
(name "openwrt-uhttpd")
(version (string-append "0-" (string-take %openwrt-tree-commit 7)))
(source (openwrt-tree-files "package/network/services/uhttpd/files"))
(build-system gnu-build-system) ;not copy: cross refs (PITFALLS #39)
(arguments
(list
#:modules %cross-copy-modules
#:imported-modules %copy-build-system-modules
#:phases
#~(modify-phases
#$(cross-copy-phases
#~'(("uhttpd.init" "share/openwrt-uhttpd/")
("uhttpd.config" "share/openwrt-uhttpd/")
;; OpenWrt installs it as uci-defaults/00_uhttpd_ubus.
("ubus.default" "share/openwrt-uhttpd/")))
(add-after 'install 'store-paths
(lambda _
(let ((uhttpd #$(this-package-input "uhttpd"))
(ustream #$(this-package-input "ustream-ssl")))
(substitute* (string-append #$output
"/share/openwrt-uhttpd/uhttpd.init")
(("^UHTTPD_BIN=.*")
(string-append "UHTTPD_BIN=\"" uhttpd "/bin/uhttpd\"\n"))
;; Certificate generation (no px5g here).
(("^OPENSSL_BIN=.*")
(string-append "OPENSSL_BIN=\""
#$(this-package-input "openssl")
"/bin/openssl\"\n"))
;; Plugin and TLS checks.
(("/usr/lib/uhttpd_(ubus|ucode|lua)\\.so" _ plugin)
(string-append uhttpd "/lib/uhttpd_" plugin ".so"))
(("/lib/libustream-ssl\\.so")
(string-append ustream "/lib/libustream-ssl.so")))))))))
(inputs (list uhttpd ustream-ssl openssl))
(home-page "https://openwrt.org/")
(synopsis "OpenWrt's uhttpd configuration from UCI")
(description
"OpenWrt's uhttpd init script, which starts uhttpd as
@file{/etc/config/uhttpd} says (listeners, TLS certificate generation,
ubus and ucode handlers), its stock configuration and uci-defaults script.")
(license license:gpl2)))
;; Guix's iptables-nft (iptables, ip6tables, ... -> xtables-nft-multi) uses
;; copy-build-system, so in a cross-built system its links point at an
;; x86_64 iptables (PITFALLS #39). Same links, with the GNU build system.
(define-public iptables-nft/cross
(package
(inherit iptables-nft)
(build-system gnu-build-system)
(arguments
(list
#:tests? #f
#:phases
#~(modify-phases %standard-phases
(delete 'unpack)
(delete 'configure)
(delete 'build)
(replace 'install
(lambda _
(let ((multi #$(file-append (this-package-input "iptables")
"/sbin/xtables-nft-multi")))
(for-each
(lambda (command)
(let ((link (string-append #$output command)))
(mkdir-p (dirname link))
(symlink multi link)))
(cons "/bin/iptables-xml"
(apply append
(map (lambda (xtables)
(map (lambda (suffix)
(string-append "/sbin/" xtables
suffix))
'("" "-restore" "-save")))
'("arptables" "ebtables" "iptables"
"ip6tables")))))))))))))
;; As OpenWrt's libmicrohttpd-no-ssl: nodogsplash serves plain HTTP, and
;; Guix's TLS inputs (gnutls/dane, curl, ...) are a long cross build.
(define libmicrohttpd/no-tls
(package
(inherit libmicrohttpd)
(name "libmicrohttpd-no-tls")
(arguments
(list #:configure-flags
#~(list "--disable-static" "--disable-https"
;; Their test programs use curl.
"--disable-curl")
#:tests? #f))
(inputs '())))
;; Captive portal (TollGate's gate: `ndsctl auth'). iptables rules, through
;; iptables-nft beside fw4's nftables table, as on OpenWrt.
(define-public nodogsplash
(package
(name "nodogsplash")
(version "5.0.2")
(source
(origin
(method url-fetch)
(uri (string-append "https://codeload.github.com/nodogsplash/"
"nodogsplash/tar.gz/v" version))
(file-name (string-append name "-" version ".tar.gz"))
(sha256
(base32 "15vp85mkyzm5h5bl4ilxvwsk8xa5qw2nffsczg7gs9ipx5s3d3ch"))))
(build-system gnu-build-system)
(arguments
(list
#:tests? #f ;no tests
#:make-flags #~(list (string-append "CC=" #$(cc-for-target)))
#:phases
#~(modify-phases %standard-phases
(delete 'configure)
(replace 'install
;; Not its install target (strip, /etc). htdocs are a template
;; for /etc/nodogsplash/htdocs, which TollGate rewrites.
(lambda _
(let ((share (string-append #$output "/share/nodogsplash")))
(for-each (lambda (f)
(install-file f (string-append #$output "/bin")))
'("nodogsplash" "ndsctl"))
(install-file "resources/nodogsplash.conf" share)
(for-each (lambda (f)
(install-file (string-append "resources/" f)
(string-append share "/htdocs")))
'("splash.html" "splash.css" "status.html"))
(install-file "resources/splash.jpg"
(string-append share "/htdocs/images"))))))))
(inputs (list libmicrohttpd/no-tls))
(home-page "https://github.com/nodogsplash/nodogsplash")
(synopsis "Captive portal for wireless and wired networks")
(description
"Nodogsplash gates a network's clients behind a splash page: until a
client is authenticated (from the page, or with @command{ndsctl auth}), its
traffic is limited by iptables rules to what the configuration allows.")
(license license:gpl2+)))
;; OpenWrt's nodogsplash files (packages feed, net/nodogsplash/files).
(define %openwrt-packages-commit "5ff48e80f3c582b71c40d072e04341516690592a")
(define (openwrt-packages-file path hash)
(origin
(method url-fetch)
(uri (string-append "https://raw.githubusercontent.com/openwrt/packages/"
%openwrt-packages-commit "/" path))
(file-name (string-append "openwrt-packages-"
(string-take %openwrt-packages-commit 7) "-"
(basename path)))
(sha256 (base32 hash))))
(define-public openwrt-nodogsplash
(package
(name "openwrt-nodogsplash")
(version (string-append "0-" (string-take %openwrt-packages-commit 7)))
(source #f)
(build-system gnu-build-system) ;not copy: cross refs (PITFALLS #39)
(arguments
(list
#:tests? #f
#:phases
#~(modify-phases %standard-phases
(delete 'unpack)
(delete 'configure)
(delete 'build)
(replace 'install
(lambda _
(let* ((share (string-append #$output "/share/openwrt-nodogsplash"))
(busybox #$(this-package-input "busybox"))
(init (string-append share "/nodogsplash.init"))
(restart (string-append share "/restart.sh"))
(defaults (string-append share "/40_nodogsplash"))
(config (string-append share "/nodogsplash.config")))
(mkdir-p share)
(copy-file #$(openwrt-packages-file
"net/nodogsplash/files/etc/init.d/nodogsplash"
"0hs5i8j9xv2cny5s4cww24l57aj9q7xizn6bfzfn5n27jqphl478")
init)
(copy-file #$(openwrt-packages-file
"net/nodogsplash/files/etc/config/nodogsplash"
"1m2k2yxw40m5z6l0as7pkmr4d5mz0s8qwmib8n3swv0g8mpdfc43")
config)
(copy-file #$(openwrt-packages-file
"net/nodogsplash/files/etc/uci-defaults/40_nodogsplash"
"17l9wkjrc5vr2n47w9hg6v5i3acb7k58c7sm5rzmpd0d18l5336r")
defaults)
(copy-file #$(openwrt-packages-file
"net/nodogsplash/files/usr/lib/nodogsplash/restart.sh"
"0rddk9wb04krmr6zyhn65dj1mi81rc072nidmyac5jc6mimnc2aj")
restart)
(for-each make-file-writable (list init config defaults restart))
(substitute* init
(("/usr/bin/nodogsplash")
#$(file-append (this-package-input "nodogsplash")
"/bin/nodogsplash"))
(("include /lib/functions")
(string-append "include "
#$(this-package-input "openwrt-base-files")
"/lib/functions")))
(substitute* defaults
;; Written into /etc/config/firewall: a path that
;; survives upgrades (this package is in the profile).
(("/usr/lib/nodogsplash/restart.sh")
"/run/current-system/profile/share/openwrt-nodogsplash/restart.sh"))
(substitute* restart
(("^#!.*") (string-append "#!" busybox "/bin/sh\n"))
;; Run by fw4 (firewall include) on every reload: detach,
;; so the firewall reload never waits for herd (PITFALLS
;; #43).
(("^ /etc/init.d/nodogsplash restart")
" ( /etc/init.d/nodogsplash restart ) </dev/null >/dev/null 2>&1 &"))
(chmod restart #o755)))))))
(inputs (list busybox nodogsplash openwrt-base-files))
(home-page "https://github.com/openwrt/packages")
(synopsis "OpenWrt's nodogsplash configuration from UCI")
(description
"OpenWrt's nodogsplash init script (which renders
@file{/etc/config/nodogsplash}), stock configuration, uci-defaults script and
firewall include.")
(license license:gpl2+)))
;;; OpenWrt's wireless stack: hostapd/wpa_supplicant with OpenWrt's ubus and
;;; ucode control (wpad), driven by netifd's ucode wireless scripts.
(define %openwrt-hostapd-files
(openwrt-tree-files "package/network/services/hostapd"))
;; hostapd + wpa_supplicant as one multicall binary, OpenWrt's
;; wpad-basic-openssl: upstream hostap at OpenWrt's commit, plus OpenWrt's
;; source overlay (ubus and ucode control) and patches.
(define-public wpad
(let ((commit "831364bf02710ad09c2f27d3efa92abeeb5634c0")
(revision "0"))
(package
(name "wpad")
(version (git-version "2026.08.07" revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url "https://w1.fi/hostap.git")
(commit commit)))
(file-name (git-file-name "hostap" version))
(sha256
(base32 "1cvndbkg5g5sh9brzvj2fr5q8n2mjk8rfpsqvkh5hyrbrkfh2blg"))))
(build-system gnu-build-system)
(arguments
(list
#:tests? #f
#:phases
#~(modify-phases %standard-phases
(add-after 'unpack 'openwrt-overlay
;; As OpenWrt's Build/Prepare: src/ over the tree, then the
;; patches in order.
(lambda _
(let ((owrt #$%openwrt-hostapd-files))
(copy-recursively (string-append owrt "/src") ".")
(for-each (lambda (p)
(invoke "patch" "-p1" "--no-backup-if-mismatch"
"-i" p))
(sort (find-files (string-append owrt "/patches")
"\\.patch$")
string<?))
;; Its ucode scripts (hostapd.uc, wifi-scripts' wdev.uc,
;; ...) from the profile.
(substitute* "src/utils/ucode.h"
(("\"/usr/share/hostap/\"")
"\"/run/current-system/profile/share/hostap/\""))
(copy-file (string-append owrt "/files/hostapd-basic.config")
"hostapd/.config")
(copy-file (string-append owrt
"/files/wpa_supplicant-basic.config")
"wpa_supplicant/.config"))))
(delete 'configure)
(replace 'build
;; OpenWrt's Build/Compile/wpad: both halves with the same
;; CFLAGS (dump_cflags of each), as multicall archives, linked
;; with multicall.c.
(lambda _
(setenv "CC" #$(cc-for-target))
(setenv "AR" #$(ar-for-target))
(setenv "BCHECK" "")
(setenv "CFLAGS"
(string-append
"-O2 -I" #$(this-package-input "libnl-tiny")
"/include/libnl-tiny -I" (getcwd) "/src/crypto"
" -DCONFIG_LIBNL20 -D_GNU_SOURCE"))
(setenv "LIBS" (string-append
"-lubox -lubus -lblobmsg_json -lucode -lm"
" -lnl-tiny -ludebug -lcrypto -lssl"))
(let ((opts '("CONFIG_ACS=y" "CONFIG_DRIVER_NL80211=y"
"CONFIG_IEEE80211AC=y" "CONFIG_IEEE80211AX=y"
"CONFIG_UCODE=y" "CONFIG_APUP=y"
"CONFIG_TLS=openssl" "CONFIG_SAE=y"
"CONFIG_OWE=y" "MULTICALL=1")))
(invoke "sh" "-c"
(string-append
"echo `make -C hostapd -s " (string-join opts)
" dump_cflags; make -C wpa_supplicant -s "
(string-join opts) " dump_cflags | sed -e 's,-n ,,g'`"
" > .cflags"))
(let ((cflags (call-with-input-file ".cflags"
(lambda (port)
(string-trim-both
((@ (ice-9 rdelim) read-line) port))))))
(apply invoke "make" "-C" "hostapd"
(string-append "CFLAGS=" cflags)
(append opts '("hostapd_cli" "hostapd_multi.a")))
(apply invoke "make" "-C" "wpa_supplicant"
(string-append "CFLAGS=" cflags)
(append opts '("wpa_cli" "wpa_supplicant_multi.a")))
(apply invoke #$(cc-for-target) "-O2" "-o" "wpad"
(string-append #$%openwrt-hostapd-files
"/files/multicall.c")
"hostapd/hostapd_multi.a"
"wpa_supplicant/wpa_supplicant_multi.a"
(string-split (getenv "LIBS") #\space))))))
(replace 'install
(lambda _
(let ((sbin (string-append #$output "/sbin"))
(share (string-append #$output "/share/hostap"))
(owrt #$%openwrt-hostapd-files))
(install-file "wpad" sbin)
(for-each (lambda (name)
(symlink "wpad" (string-append sbin "/" name)))
'("hostapd" "wpa_supplicant"))
(install-file "hostapd/hostapd_cli" sbin)
(install-file "wpa_supplicant/wpa_cli" sbin)
(for-each (lambda (f)
(install-file (string-append owrt "/files/" f)
share))
'("hostapd.uc" "wpa_supplicant.uc"))
;; FILS DHCP server lookup (hostapd.sh), by profile path.
(install-file (string-append owrt "/files/dhcp-get-server.sh")
(string-append #$output "/lib/netifd"))
(substitute* (string-append #$output
"/lib/netifd/dhcp-get-server.sh")
(("^#!.*")
(string-append "#!" #$(file-append
(this-package-input "busybox")
"/bin/sh")
"\n")))))))))
(inputs (list libnl-tiny libubox ubus ucode udebug openssl json-c busybox))
(home-page "https://w1.fi/hostapd/")
(synopsis "hostapd and wpa_supplicant with OpenWrt's ubus/ucode control")
(description
"OpenWrt's wpad: hostapd (access point) and wpa_supplicant (client)
in one binary, with OpenWrt's patches, which add the ubus objects and ucode
scripts netifd's wireless handling configures them through.")
(license license:bsd-3))))
;; netifd's wireless handling and `wifi' (OpenWrt's wifi-scripts, ucode
;; variant): /etc/config/wireless -> hostapd/wpa_supplicant over ubus. The
;; lib/netifd part must sit next to netifd's main.uc (netifd copies it in);
;; the ucode modules are found in the profile.
(define-public wifi-scripts
(package
(name "wifi-scripts")
(version (string-append "0-" (string-take %openwrt-tree-commit 7)))
(source (openwrt-tree-files "package/network/config/wifi-scripts"))
(build-system gnu-build-system) ;not copy: cross refs (PITFALLS #39)
(arguments
(list
#:modules %cross-copy-modules
#:imported-modules %copy-build-system-modules
#:phases
#~(modify-phases
;; files/, then files-ucode/ over it, as the Makefile; minus
;; WPS buttons, the ath12k trigger, procd hotplug and the ucode
;; iwinfo CLI (the iwinfo package has the C one).
#$(cross-copy-phases
#~'(("files/lib/netifd" "lib/netifd")
("files/lib/wifi" "lib/wifi")
("files/sbin/wifi" "sbin/")
("files/usr/share" "share")
("files-ucode/lib/netifd" "lib/netifd")
("files-ucode/usr/share" "share")))
(add-after 'install 'store-paths
(lambda _
(let* ((out #$output)
(profile "/run/current-system/profile")
(busybox #$(this-package-input "busybox"))
(ucode #$(file-append (this-package-input "ucode")
"/bin/ucode"))
(base #$(this-package-input "openwrt-base-files"))
(jshn #$(file-append (this-package-input "libubox")
"/share/libubox/jshn.sh"))
(scripts (append (find-files (string-append out "/lib"))
(find-files (string-append out "/share"))
(list (string-append out "/sbin/wifi")))))
(substitute* scripts
(("/usr/share/libubox/jshn\\.sh") jshn)
(("/lib/functions/network\\.sh")
(string-append base "/lib/functions/network.sh"))
(("([ .])/lib/functions\\.sh" _ pre)
(string-append pre base "/lib/functions.sh"))
;; hostapd/wpa_supplicant: wpad, in the profile.
(("/usr/sbin/(hostapd|wpa_supplicant)" _ prog)
(string-append profile "/sbin/" prog))
;; hostap's ucode helpers: here, in the profile (wpad has
;; its hostapd.uc there too).
(("/usr/share/hostap/")
(string-append profile "/share/hostap/"))
(("/usr/share/(schema/|wifi_devices\\.json|iso3166\\.json)"
_ rest)
(string-append out "/share/" rest))
(("/lib/wifi/mac80211\\.uc")
(string-append out "/lib/wifi/mac80211.uc"))
(("include /lib/wifi")
(string-append "include " out "/lib/wifi"))
(("/lib/netifd/dhcp-get-server\\.sh")
(string-append profile "/lib/netifd/dhcp-get-server.sh"))
(("^#!/usr/bin/ucode") (string-append "#!" ucode)))
(substitute* (string-append out "/sbin/wifi")
(("^#!.*/bin/sh")
(string-append "#!" busybox "/bin/sh\n"
"PATH=" #$(this-package-input "uci") "/bin:"
#$(this-package-input "ubus") "/bin:"
(dirname ucode) ":" busybox "/bin:$PATH")))
(for-each (lambda (f) (chmod f #o755))
(list (string-append out "/sbin/wifi")
(string-append out
"/lib/netifd/wireless/mac80211.sh")))))))))
(inputs (list busybox ucode uci ubus libubox openwrt-base-files))
(home-page "https://openwrt.org/")
(synopsis "OpenWrt's netifd wireless scripts and wifi command")
(description
"OpenWrt's wifi-scripts (ucode variant): netifd's wireless handling,
which configures hostapd and wpa_supplicant (wpad) over ubus from
@file{/etc/config/wireless}, radio detection and the @command{wifi}
command.")
(license license:gpl2)))
;; LuCI's backend: ubus sessions with ACLs, UCI over ubus, plugins.
(define-public rpcd
(let ((commit "d99f703e4035d03fbcfbada3cb697db346c2b984")
(revision "0"))
(package
(name "rpcd")
(version (git-version "2026.10.04" revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url "https://git.openwrt.org/project/rpcd.git")
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32 "08km7ihigw4pxxwskjw6483hy0h7km0cclav33qhjxw3z4znfb75"))))
(build-system cmake-build-system)
(arguments
(list
#:tests? #f ;no tests
#:phases
#~(modify-phases #$(openwrt-phases)
(add-after 'unpack 'fix-paths
(lambda _
;; INSTALL_PREFIX roots the ACL (share/rpcd/acl.d), plugin
;; (lib/rpcd, libexec/rpcd) and ucode script
;; (share/rpcd/ucode) directories, which LuCI packages add
;; to: the system profile, not this package. rpcd is in the
;; profile, so its own plugins and ACL are found there too.
(substitute* "CMakeLists.txt"
(("-DINSTALL_PREFIX=\"\\$\\{CMAKE_INSTALL_PREFIX\\}\"")
"-DINSTALL_PREFIX=\"/run/current-system/profile\""))
(substitute* "uci.c"
;; `uci apply': procd-compat's reload_config.
(("\"/sbin/reload_config\"")
"\"/run/current-system/profile/sbin/reload_config\""))
(substitute* "sys.c"
;; LuCI's password change pipes the password twice into
;; passwd's stdin: busybox's, which reads it without a tty.
(("\"/bin/passwd\"")
(string-append "\"" #$(this-package-input "busybox")
"/bin/passwd\"")))))
(add-after 'install 'install-acl-and-headers
(lambda _
(install-file "../source/unauthenticated.json"
(string-append #$output
"/share/rpcd/acl.d"))
;; For out-of-tree plugins (rpcd-mod-luci), as OpenWrt's
;; InstallDev.
(copy-recursively "../source/include/rpcd"
(string-append #$output
"/include/rpcd")))))))
(inputs (list libubox ubus uci ucode json-c libxcrypt busybox iwinfo))
(native-inputs (list pkg-config))
(home-page "https://git.openwrt.org/?p=project/rpcd.git")
(synopsis "OpenWrt ubus RPC daemon for LuCI")
(description
"rpcd provides ubus objects for web front ends: @code{session}
(login against @file{/etc/config/rpcd} and the shadow file, ACLs from
@file{share/rpcd/acl.d}), @code{uci} (staged changes, commit, apply with
rollback), @code{rc}, plus plugins: @code{file}, @code{rpc-sys}, @code{iwinfo} and
@code{ucode} (scripts in @file{share/rpcd/ucode}, as LuCI's backend is).")
(license license:isc))))
;; TLS for uloop streams (uhttpd's HTTPS), on OpenSSL.
(define-public ustream-ssl
(let ((commit "99f1c0db5a729f615bc5114b3b52fd8ac8083f34")
(revision "0"))
(package
(name "ustream-ssl")
(version (git-version "2026.03.01" revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url "https://git.openwrt.org/project/ustream-ssl.git")
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32 "16gy5fd0n2676w7j4ybpjixp6j5wxr3h2186jk3n1327lkzgfx6x"))))
(build-system cmake-build-system)
(arguments
(list #:tests? #f ;no tests
#:phases (openwrt-phases)))
(inputs (list libubox openssl))
(native-inputs (list pkg-config))
(home-page "https://git.openwrt.org/?p=project/ustream-ssl.git")
(synopsis "TLS wrapper for libubox streams")
(description
"ustream-ssl adds TLS to libubox's @code{ustream} (client and server),
on OpenSSL here. uhttpd loads it for HTTPS.")
(license license:isc))))
;; Web server for LuCI: static files, ubus over HTTP (JSON-RPC, rpcd
;; sessions), ucode handlers (LuCI's dispatcher), HTTPS.
(define-public uhttpd
(let ((commit "373145f72c884c36a2b16f7f47e74ffae06bd754")
(revision "0"))
(package
(name "uhttpd")
(version (git-version "2026.08.24" revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url "https://git.openwrt.org/project/uhttpd.git")
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32 "1n3kbv9pcsccf2j4l8yh71hxyxv29q91ihfrm5c1msri8r9bmdgl"))))
(build-system cmake-build-system)
(arguments
(list
#:configure-flags #~'("-DLUA_SUPPORT=OFF")
#:tests? #f ;no tests
#:phases
#~(modify-phases #$(openwrt-phases)
(add-after 'unpack 'fix-paths
;; Plugins and libustream-ssl are dlopen()ed by bare name;
;; uhttpd's RUNPATH doesn't cover them.
(lambda _
(substitute* "main.c"
(("\"(uhttpd_(ucode|ubus)\\.so)\"" _ so)
(string-append "\"" #$output "/lib/" so "\"")))
;; CGI programs get a fixed PATH: the profile first.
(substitute* "main.c"
(("conf\\.cgi_path = \"/sbin:/usr/sbin:/bin:/usr/bin\"")
"conf.cgi_path = \"/run/current-system/profile/bin:\
/run/current-system/profile/sbin:/sbin:/usr/sbin:/bin:/usr/bin\""))
(substitute* "tls.c"
(("\"libustream-ssl\\.\" LIB_EXT")
(string-append "\"" #$(this-package-input "ustream-ssl")
"/lib/libustream-ssl.\" LIB_EXT"))))))))
(inputs (list libubox ubus ucode json-c libxcrypt ustream-ssl))
(native-inputs (list pkg-config))
(home-page "https://git.openwrt.org/?p=project/uhttpd.git")
(synopsis "OpenWrt's small HTTP server")
(description
"uhttpd is the web server OpenWrt's LuCI runs on: static files, CGI,
ubus over HTTP (JSON-RPC with rpcd sessions), ucode request handlers, and
HTTPS through ustream-ssl.")
(license license:isc))))
;; Wireless information library and CLI (nl80211 backend), for LuCI's
;; rpcd plugins.
(define-public iwinfo
(let ((commit "66bdd1a071895d91babc9b9228bb84626bbce226")
(revision "0"))
(package
(name "iwinfo")
(version (git-version "2026.05.26" revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url "https://git.openwrt.org/project/iwinfo.git")
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32 "1fppvnp74ps15hfwr9zvqc9hjhk1yy7a04f7r26k6257iw9r4pd6"))))
(build-system gnu-build-system)
(arguments
(list
#:tests? #f ;no tests
#:parallel-build? #f ;iwinfo may link before libiwinfo.so
#:make-flags
#~(list (string-append "CC=" #$(cc-for-target))
;; _GNU_SOURCE: struct ucred, FNM_CASEFOLD (musl has them
;; without it).
(string-append "CFLAGS=-O2 -D_GNU_SOURCE -I"
#$(this-package-input "libnl-tiny")
"/include/libnl-tiny")
(string-append "LDFLAGS=-Wl,-rpath=" #$output "/lib")
"FPIC=-fPIC" "BACKENDS=nl80211"
;; OpenWrt's IWINFO_ABI_VERSION.
"SOVERSION=20230701"
;; Not `compile': that builds the Lua binding too.
"libiwinfo.so" "iwinfo")
#:phases
#~(modify-phases %standard-phases
(delete 'configure)
(add-after 'unpack 'fix-paths
(lambda _
(substitute* "include/iwinfo.h"
(("\"/usr/share/libiwinfo/devices.txt\"")
(string-append "\"" #$output
"/share/libiwinfo/devices.txt\"")))))
(replace 'install
(lambda _
(let ((lib (string-append #$output "/lib"))
(include (string-append #$output "/include")))
(install-file "iwinfo" (string-append #$output "/bin"))
(install-file "libiwinfo.so.20230701" lib)
(symlink "libiwinfo.so.20230701"
(string-append lib "/libiwinfo.so"))
(install-file "include/iwinfo.h" include)
(copy-recursively "include/iwinfo"
(string-append include "/iwinfo"))
(install-file "devices.txt"
(string-append #$output
"/share/libiwinfo"))))))))
(inputs (list libnl-tiny libubox ubus uci))
(home-page "https://git.openwrt.org/?p=project/iwinfo.git")
(synopsis "OpenWrt wireless information library")
(description
"libiwinfo reports wireless interface details (mode, channel,
signal, associated stations, scan results, hardware) through nl80211;
@command{iwinfo} is its command-line front end. rpcd and LuCI use it for
their wireless pages.")
(license license:gpl2))))
;; HTTP utility library (URL decoding, multipart/urlencoded parsers), with
;; the ucode binding LuCI's request handling uses.
(define-public lucihttp
(let ((commit "9b5b683fbb2bb0010aa031d620d0ab8105516757")
(revision "0"))
(package
(name "lucihttp")
(version (git-version "2023.03.15" revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url "https://github.com/jow-/lucihttp")
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32 "16z7zww6nc724w0b6fzn9rkrlj8cjzc5mkhm1yawb805dsxdrj38"))))
(build-system cmake-build-system)
(arguments
(list
#:configure-flags #~'("-DBUILD_LUA=OFF" "-DBUILD_TESTS=OFF")
#:tests? #f
#:phases (openwrt-phases)))
(inputs (list ucode json-c)) ;ucode's headers include json-c's
(native-inputs (list pkg-config))
(home-page "https://github.com/jow-/lucihttp")
(synopsis "HTTP utility library for LuCI")
(description
"liblucihttp provides URL encoding and decoding and streaming parsers
for urlencoded and multipart HTTP bodies, plus the @code{lucihttp} ucode
module that LuCI's request handling uses.")
(license license:isc))))
;; CGI helper for LuCI: file upload/download and command output streams
;; (cgi-exec: log and nftables pages), checked against rpcd session ACLs.
(define-public cgi-io
(let ((commit "31cb3c89f02d918d7f17bf62a80c852fc38a1ca1")
(revision "0"))
(package
(name "cgi-io")
(version (git-version "2026.07.21" revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url "https://git.openwrt.org/project/cgi-io.git")
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32 "0v7ygn2cii0clxlgfa5zyp5w74pn7545h03l8msmrds8lnpg9nma"))))
(build-system cmake-build-system)
(arguments
(list
#:tests? #f
#:phases
#~(modify-phases #$(openwrt-phases)
(add-after 'unpack 'fix-paths
(lambda _
;; Upload checksums: busybox md5sum/sha256sum applets.
(substitute* "main.c"
(("\"/bin/busybox\"")
(string-append "\"" #$(this-package-input "busybox")
"/bin/busybox\"")))))
(add-after 'install 'install-cgi
;; OpenWrt's layout: libexec/cgi-io, run by uhttpd as
;; www/cgi-bin/cgi-{upload,download,backup,exec} (mode by
;; name).
(lambda _
(let ((cgi-bin (string-append #$output "/www/cgi-bin")))
(mkdir-p (string-append #$output "/libexec"))
(rename-file (string-append #$output "/sbin/cgi-io")
(string-append #$output "/libexec/cgi-io"))
(rmdir (string-append #$output "/sbin"))
(mkdir-p cgi-bin)
(for-each (lambda (name)
(symlink "../../libexec/cgi-io"
(string-append cgi-bin "/cgi-" name)))
'("upload" "download" "backup" "exec"))))))))
(inputs (list libubox ubus json-c busybox))
(native-inputs (list pkg-config))
(home-page "https://git.openwrt.org/?p=project/cgi-io.git")
(synopsis "CGI file transfer and command helper for LuCI")
(description
"cgi-io handles what LuCI can't do over JSON-RPC: file upload and
download, configuration backups and streaming command output, each checked
against the caller's rpcd session ACLs.")
(license license:gpl2+))))
;;; LuCI. One source; rpcd-mod-luci (C, CMake) is its own package, the
;;; rest -- ucode runtime, JavaScript views, theme, the small C modules --
;;; is `luci', built from a chosen set of LuCI's packages.
(define %luci-commit "e14ffa434a908ac6f084eae24b5556def663b55a")
(define %luci-source
(origin
(method git-fetch)
(uri (git-reference
(url "https://git.openwrt.org/project/luci.git")
(commit %luci-commit)))
(file-name (git-file-name "luci" (string-take %luci-commit 7)))
(sha256
(base32 "1lx3cdxgn4v1falakg287p3vhr44ffd4phplj7hmkgrh0sdqa5rv"))
;; Process list: parse Guix's busybox `top' too (other columns).
(patches (list (local-file "patches/luci-busybox-top.patch")))))
(define %luci-version (git-version "26.288" "0" %luci-commit))
;; rpcd's `luci-rpc' object: network devices, DHCP leases, board info for
;; LuCI's network and status pages.
(define-public rpcd-mod-luci
(package
(name "rpcd-mod-luci")
(version %luci-version)
(source %luci-source)
(build-system cmake-build-system)
(arguments
(list
#:configure-flags
#~(list "-DLIBNL_LIBS=-lnl-tiny"
(string-append "-DCMAKE_C_FLAGS=-I"
#$(this-package-input "libnl-tiny")
"/include/libnl-tiny"))
#:tests? #f
#:phases
#~(modify-phases #$(openwrt-phases)
(add-after 'unpack 'chdir
(lambda _
(chdir "libs/rpcd-mod-luci/src")
;; libiwinfo is dlopen()ed when present.
(substitute* "luci.c"
(("\"/usr/lib/libiwinfo\\.so\\*\"")
(string-append "\"" #$(this-package-input "iwinfo")
"/lib/libiwinfo.so*\"")))))
(add-after 'install 'move-to-rpcd
;; rpcd loads plugins from lib/rpcd.
(lambda _
(mkdir-p (string-append #$output "/lib/rpcd"))
(rename-file (string-append #$output "/lib/luci.so")
(string-append #$output "/lib/rpcd/luci.so")))))))
(inputs (list libubox ubus uci libnl-tiny iwinfo rpcd json-c))
(native-inputs (list pkg-config))
(home-page "https://github.com/openwrt/luci")
(synopsis "rpcd plugin with LuCI's network and system queries")
(description
"This rpcd plugin publishes the @code{luci-rpc} ubus object: network
devices and their state, DHCP leases, host hints, board information.")
(license license:asl2.0)))
;; LuCI packages in the build, LuCI repository paths.
(define %luci-components
'("modules/luci-base"
"modules/luci-mod-status"
"modules/luci-mod-system"
"modules/luci-mod-network"
"libs/luci-lib-uqr"
"themes/luci-theme-bootstrap"
"applications/luci-app-firewall"))
(define-public luci
(package
(name "luci")
(version %luci-version)
(source %luci-source)
(build-system gnu-build-system)
(arguments
(list
#:tests? #f
#:modules '((guix build gnu-build-system)
(guix build utils)
(ice-9 match))
#:phases
#~(modify-phases %standard-phases
(delete 'configure)
(replace 'build
(lambda _
(let ((cc #$(cc-for-target)))
;; luci.core (core.so: lmo translations, crypt, sysinfo)
;; and luci.version. lemon is a build tool: native gcc.
(with-directory-excursion "modules/luci-base/src"
(substitute* "Makefile" (("\tcc -std") "\tgcc -std"))
(invoke "make" (string-append "CC=" cc) "FPIC=-fPIC"
"CFLAGS=-O2"
(string-append "LUCI_VERSION=" #$%luci-version)
"LUCI_GITBRANCH=master"
"core.so" "version.uc"))
;; ucode-mod-html (contrib/package): the `html' module.
(invoke cc "-O2" "-fPIC" "-shared" "-Wall"
"-o" "html.so"
"contrib/package/ucode-mod-html/src/html.c")
;; luci-bwc: realtime graphs' bandwidth/load collector.
(with-directory-excursion "modules/luci-mod-status/src"
(invoke "make" (string-append "CC=" cc) "FPIC=-fPIC"
"CFLAGS=-O2" "luci-bwc")))))
(replace 'install
;; luci.mk's install, minus /usr: htdocs -> www, ucode ->
;; share/ucode/luci, root/usr/X -> X, root/{www,etc} as is.
;; Not etc/init.d (procd scripts: ucitrack; procd-compat
;; triggers do its job).
(lambda _
(define (copy-tree from to)
(when (file-exists? from)
(copy-recursively from to #:keep-mtime? #t)))
(for-each
(lambda (component)
(let ((root (string-append component "/root")))
(copy-tree (string-append component "/htdocs")
(string-append #$output "/www"))
(copy-tree (string-append component "/ucode")
(string-append #$output "/share/ucode/luci"))
(when (file-exists? root)
(for-each
(match-lambda
((from . to)
(copy-tree (string-append root "/" from)
(string-append #$output "/" to))))
'(("usr/share" . "share")
("usr/libexec" . "libexec")
("usr/bin" . "bin")
("www" . "www")
("etc/config" . "etc/config")
("etc/uci-defaults" . "etc/uci-defaults"))))))
'#$%luci-components)
(install-file "modules/luci-base/src/core.so"
(string-append #$output "/lib/ucode/luci"))
(install-file "modules/luci-base/src/version.uc"
(string-append #$output "/share/ucode/luci"))
(install-file "html.so" (string-append #$output "/lib/ucode"))
(install-file "modules/luci-mod-status/src/luci-bwc"
(string-append #$output "/bin"))))
(add-after 'install 'fix-paths
;; Server side: the system profile instead of /usr, where the
;; profile has it. Client-side paths (fs.exec('/sbin/ip') +
;; ACLs) are left alone.
(lambda _
(let ((profile "/run/current-system/profile")
(busybox #$(this-package-input "busybox")))
(substitute* (append
(find-files (string-append #$output
"/share/ucode")
"\\.(uc|ut)$")
(find-files (string-append #$output
"/share/rpcd/ucode")))
(("/usr/share/(ucode|luci|rpcd|firewall4)" _ dir)
(string-append profile "/share/" dir))
(("/usr/lib/lua/luci/i18n")
(string-append profile "/share/luci/i18n"))
(("/usr/sbin/uhttpd") (string-append profile "/bin/uhttpd"))
;; Feature checks and commands (getFeatures: /sbin/fw4,
;; /usr/sbin/wpa_supplicant, ...).
(("(['\"`])/(usr/)?sbin/" _ quote)
(string-append quote profile "/sbin/"))
(("(['\"`])/usr/bin/" _ quote)
(string-append quote profile "/bin/"))
(("/bin/busybox") (string-append busybox "/bin/busybox"))
(("(['\"`])/www/" _ quote)
(string-append quote profile "/www/")))
;; Menu entries depending on files ("fs"): check the
;; profile. (/usr/sbin/uhttpd stays: its page edits a UCI
;; config uhttpd-service-type doesn't read.)
(substitute* (find-files (string-append #$output
"/share/luci/menu.d"))
(("\"/(usr/)?sbin/(fw3|nft|dnsmasq|odhcpd)\"" _ _ cmd)
(string-append "\"" profile "/sbin/" cmd "\"")))
;; Helper scripts rpcd/LuCI run.
(for-each (lambda (script)
(substitute* script
(("^#!.*/bin/sh")
(string-append "#!" busybox "/bin/sh"))
;; syslog-wrapper: no logread (ubox); Guix's
;; syslogd writes /var/log/messages.
(("/usr/sbin/logread -e \\^")
"cat /var/log/messages")
(("^\\. /lib/functions\\.sh")
(string-append
". " #$(this-package-input "openwrt-base-files")
"/lib/functions.sh"))))
(find-files (string-append #$output "/libexec")))
(substitute* (string-append #$output "/www/cgi-bin/luci")
(("^#!.*")
(string-append "#!" #$(this-package-input "ucode")
"/bin/ucode\n")))))))))
(inputs (list ucode json-c libxcrypt busybox openwrt-base-files
uci libubox ubus)) ;iwinfo.h includes theirs
(propagated-inputs
;; What luci-base and the modules depend on (LUCI_DEPENDS), so the
;; system profile has their files: ucode modules, rpcd plugins, ACLs.
(list rpcd rpcd-mod-luci lucihttp iwinfo cgi-io))
(home-page "https://github.com/openwrt/luci")
(synopsis "OpenWrt's web interface")
(description
"LuCI is OpenWrt's web interface: a ucode request dispatcher run by
uhttpd, JavaScript views talking to rpcd over ubus, and the Bootstrap theme.
This package has luci-base, the status, system and network modules, and the
firewall application.")
(license license:asl2.0)))
;;; Not an OpenWrt project: the parts of procd's ubus API that OpenWrt tools
;;; call, for a system where Shepherd is init. Lives in procd-compat/ next
;;; to this file. openwrt-services.scm runs it.
(define-public procd-compat
(package
(name "procd-compat")
(version "0.1")
(source (local-file "procd-compat" #:recursive? #t))
(build-system gnu-build-system) ;not copy: cross refs (PITFALLS #39)
(arguments
(list
#:modules %cross-copy-modules
#:imported-modules %copy-build-system-modules
#:tests? #f
#:phases
#~(modify-phases
#$(cross-copy-phases
#~'(("procd-compat.uc" "share/procd-compat/")
("reload_config" "sbin/")
("openwrt-rc" "sbin/")
("logread" "sbin/")))
(add-after 'install 'store-paths
(lambda _
(let ((busybox #$(this-package-input "busybox")))
(substitute* (string-append #$output "/sbin/reload_config")
;; (patch-source-shebangs has already run.)
(("^#!.*/bin/sh")
(string-append "#!" busybox "/bin/sh\n"
"PATH=" #$(this-package-input "uci") "/bin:"
#$(this-package-input "ubus") "/bin:"
busybox "/bin:$PATH")))
(substitute* (string-append #$output "/sbin/openwrt-rc")
(("^#!.*/bin/sh")
(string-append
"#!" busybox "/bin/sh\n"
;; What init scripts run by name: base-files' ipcalc.sh,
;; uci, ubus, jsonfilter, busybox; then the profile.
"PATH=" #$(this-package-input "openwrt-base-files") "/bin:"
#$(this-package-input "uci") "/bin:"
#$(this-package-input "ubus") "/bin:"
#$(this-package-input "jsonfilter") "/bin:"
busybox "/bin:" busybox "/sbin:"
"/run/current-system/profile/bin:"
"/run/current-system/profile/sbin:$PATH"))
(("@base@") #$(this-package-input "openwrt-base-files")))
(substitute* (string-append #$output "/sbin/logread")
(("^#!.*/bin/sh")
(string-append "#!" busybox "/bin/sh\n"
"PATH=" busybox "/bin:$PATH")))))))))
(inputs (list busybox ubus uci jsonfilter openwrt-base-files))
(home-page "https://source.archipelago-foundation.org/ssmithx/guix-router")
(synopsis "procd's ubus service and system objects, for Shepherd")
(description
"A ucode daemon that publishes the ubus @code{service} object (method
@code{event}: on @code{config.change} it reloads the Shepherd services
registered for that UCI config, as procd's reload triggers do; method
@code{get_data}) and the @code{system} object (@code{board}, @code{info},
@code{reboot}), plus @command{reload_config}, which reports the UCI configs
changed since its last run, @command{openwrt-rc}, which runs an OpenWrt
procd init script's service without procd, and @command{logread} over the
syslog file.")
(license license:isc)))