;;; 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 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 .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)))