1
0

Compare commits

...

5 Commits

Author SHA1 Message Date
dc4b7ebffd module docs: print examples 2023-08-11 21:12:57 +01:00
bd8d00fe13 TODO for modules phase 2023-08-11 18:28:30 +01:00
b81604870b extract kernel config options from base module
we then "import" them straight back into base.nix - it's not
as though you can opt out of having a kernel. But this means
they'll appear separately in the documentation
2023-08-11 18:15:17 +01:00
3ea40f95dc convert pppoe to serviceDefn 2023-08-10 22:53:45 +01:00
2942c465b9 add ssh module 2023-08-10 22:53:21 +01:00
10 changed files with 260 additions and 136 deletions

View File

@ -2015,3 +2015,28 @@ everyone can use it.
- also the type_service type defn exists only locally in modules.nix,
and we would like to refer to it elsewhere
Thu Aug 10 21:46:36 BST 2023
to finish service/modules milestone
[done] there are some modules not using serviceDefn
- modules included by standard.nix should have all their options
grouped together in docs
how can we determine which they are? or maybe "modules that
don't contain services" is an acceptable criterion
maybe this is not actually an issue, if the modules are
reasonably coherent. It looks odd now because base.nix is a mess
- print the module pathname so people know what to import
- docs don't print the examples
- and seem to be getting the default wrong too
- decide what we deem to be "internal" (if anything)
is `filesystem` internal, for example? or `busybox`? they're
both mostly _used_ internally but may still be valuable to expose
- maybe document outputs separately or not at all?
- bridge to be one service instead of two?
[done] get rid of services/
- anything else in rotuer.nix that we should servicify
- services for liminix.networking
- a nice way to specify service dependencies
- do another video

5
ci.nix
View File

@ -34,12 +34,13 @@ let
];
src = ./doc;
buildPhase = ''
cat ${(import ./doc/extract-options.nix).doc} | fennel --correlate parse-options.fnl > modules.rst
cat ${(import ./doc/extract-options.nix).doc} > options.json
cat options.json | fennel --correlate parse-options.fnl > modules.rst
make html
'';
installPhase = ''
mkdir -p $out/nix-support $out/share/doc/
# (cd _build && tar cf $out/share/doc/liminix_manual.tar html)
cp modules.rst options.json $out
cp -a _build/html $out/share/doc/liminix
echo "file source-dist \"$out/share/doc/liminix\"" \
> $out/nix-support/hydra-build-products

View File

@ -20,38 +20,44 @@
(.. lines (string.gsub l "^## *" "") "\n"))))))
(fn strip-newlines [text]
(and text
(-> text
(string.gsub "\n([^\n])" " %1")
(string.gsub "\n\n+" "\n"))))
(-> text
(string.gsub "\n([^\n])" " %1")
(string.gsub "\n\n+" "\n")))
(fn indent [n text]
(let [margin (string.rep " " n)]
(.. margin (string.gsub text "\n +" (.. "\n" margin )))))
(.. margin (string.gsub (or text "") "\n +" (.. "\n" margin )))))
(fn indent-literal [n text]
(let [margin (string.rep " " n)]
(.. margin (string.gsub (or text "") "\n" (.. "\n" margin )))))
(fn extract-text [description]
(and description
; (do (print (view description)) true)
(-> (match description
{ :type "literalExpression" : text } text
{} nil
nil nil
t description)
strip-newlines)))
(match description
{ :_type "literalExpression" : text } text
(where s (= (type s) "string")) description
_ nil))
(fn print-option [o offset]
(let [i (or offset 0)]
(print (indent i (.. " * option ``" o.name "``")))
(print (indent (+ 4 i)
(or (extract-text o.description) "(no description)")))
(case (-?> o.description extract-text strip-newlines)
descr (print (indent (+ 4 i) descr)))
(print)
(print (indent (+ 4 i) (.. "**type** " o.type "\n")))
(print (indent (+ 4 i)
(.. "**default** "
(or (extract-text (?. o :default)) "(none)")
"\n"
)))
(print )))
(when o.example
(print (indent (+ 4 i) "**example**")) (print)
(print (indent (+ 4 i) ".. code-block:: nix"))
(print)
(print (indent-literal (+ 8 i) (extract-text o.example)) "\n")
(print))
(when (extract-text o.default)
(print (indent (+ 4 i) "**default**")) (print)
(print (indent (+ 4 i) ".. code-block:: nix"))
(print)
(print (indent-literal (+ 8 i) (extract-text o.default)) "\n")
(print))))
(fn print-service [o]
(print (.. " * service ``" o.name "``"))
@ -83,13 +89,3 @@
(if (= o.type "parametrisable s6-rc service definition")
(print-service o)
(print-option o)))))))
;; for each element el, add to table modules keyed on
;; el.declarations
;; for each value in modules
;; print title
;; elements = (sort elements on el.name)
;; for each el in elements
;; is option or service? print whichever
;;

View File

@ -50,6 +50,7 @@ in rec {
../modules/hostapd
../modules/bridge
../modules/ntp
../modules/ssh
];
rootfsType = "jffs2";
hostname = "rotuer";
@ -95,25 +96,7 @@ in rec {
makestep = { threshold = 1.0; limit = 3; };
};
services.sshd = longrun {
name = "sshd";
# env -i clears the environment so we don't pass anything weird to
# ssh sessions. Dropbear params are
# -e pass environment to child
# -E log to stderr
# -R create hostkeys if needed
# -P pid-file
# -F don't fork into background
run = ''
if test -d /persist; then
mkdir -p /persist/secrets/dropbear
ln -s /persist/secrets/dropbear /run
fi
PATH=${lib.makeBinPath config.defaultProfile.packages}:/bin
exec env -i ENV=/etc/ashrc PATH=$PATH ${dropbear}/bin/dropbear -e -E -R -P /run/dropbear.pid -F
'';
};
services.sshd = svc.ssh.build { };
users.root = secrets.root;
@ -129,7 +112,7 @@ in rec {
domain = "fake.liminix.org";
};
services.wan = svc.pppoe {
services.wan = svc.pppoe.build {
interface = config.hardware.networkInterfaces.wan;
ppp-options = [
"debug" "+ipv6" "noauth"

View File

@ -12,6 +12,9 @@ let
type_service = pkgs.liminix.lib.types.service;
in {
imports = [
./kernel.nix # this is a separate module for doc purposes
];
options = {
# analogous to nixos systemPackages, but we don't symlink into
# /run/current-system, we just add the paths in /etc/profile
@ -28,23 +31,6 @@ in {
default = "squashfs";
type = types.str;
};
kernel = {
src = mkOption { type = types.package; } ;
modular = mkOption {
type = types.bool;
default = true;
description = "support loadable kernel modules";
};
extraPatchPhase = mkOption {
default = "true";
type = types.lines;
} ;
config = mkOption {
# mostly the values are y n or m, but sometimes
# other strings are also used
type = types.attrsOf types.nonEmptyStr;
};
};
boot = {
commandLine = mkOption {
type = types.listOf types.nonEmptyStr;
@ -75,52 +61,6 @@ in {
};
};
kernel = rec {
modular = true; # disabling this is not yet supported
config = {
IKCONFIG = "y";
IKCONFIG_PROC = "y";
PROC_FS = "y";
KEXEC = "y";
MODULES = if modular then "y" else "n";
MODULE_SIG = if modular then "y" else "n";
DEBUG_FS = "y";
MIPS_BOOTLOADER_CMDLINE_REQUIRE_COOKIE = "y";
MIPS_BOOTLOADER_CMDLINE_COOKIE = "\"liminix\"";
MIPS_CMDLINE_DTB_EXTEND = "y";
# basic networking protocols
NET = "y";
UNIX = "y";
INET = "y";
IPV6 = "y";
PACKET = "y"; # for ppp, tcpdump ...
SYSVIPC= "y";
# disabling this option causes the kernel to use an "empty"
# initramfs instead: it has a /dev/console node and not much
# else. Note that pid 1 is started *before* the root
# filesystem is mounted and it expects /dev/console to be
# present already
BLK_DEV_INITRD = lib.mkDefault "n"; # overriden by initramfs module
# s6-linux-init mounts this on /dev
DEVTMPFS = "y";
# some or all of these may be fix for "tmpfs: Unknown parameter 'mode'" error
TMPFS = "y";
TMPFS_POSIX_ACL = "y";
TMPFS_XATTR = "y";
FW_LOADER = "y";
FW_LOADER_COMPRESS = "y";
# We don't have a user helper, so we get multiple 60s pauses
# at boot time unless we disable trying to call it.
# https://lkml.org/lkml/2013/8/5/175
FW_LOADER_USER_HELPER = "n";
};
};
boot.commandLine = [
"console=ttyS0,115200 panic=10 oops=panic init=/bin/init loglevel=8"
"root=${config.hardware.rootDevice}"

94
modules/kernel.nix Normal file
View File

@ -0,0 +1,94 @@
## Kernel-related options
## ======================
##
##
{ lib, pkgs, config, ...}:
let
inherit (lib) mkEnableOption mkOption types isDerivation hasAttr ;
inherit (pkgs.pseudofile) dir symlink;
inherit (pkgs.liminix.networking) address interface;
inherit (pkgs.liminix.services) bundle;
type_service = pkgs.liminix.lib.types.service;
in {
options = {
kernel = {
src = mkOption { type = types.package; } ;
modular = mkOption {
type = types.bool;
default = true;
description = "support loadable kernel modules";
};
extraPatchPhase = mkOption {
default = "true";
type = types.lines;
} ;
config = mkOption {
description = ''
Kernel config options, as listed in Kconfig* files in the
kernel source tree. Do not include the leading "CONFIG_"
prefix when defining these. Most values are "y", "n" or "m",
but sometimes other strings are also used.
'';
type = types.attrsOf types.nonEmptyStr;
example = lib.literalExpression ''
{
BRIDGE = "y";
TMPFS = "y";
FW_LOADER_USER_HELPER = "n";
};
'';
};
};
};
config = {
kernel = rec {
modular = true; # disabling this is not yet supported
config = {
IKCONFIG = "y";
IKCONFIG_PROC = "y";
PROC_FS = "y";
KEXEC = "y";
MODULES = if modular then "y" else "n";
MODULE_SIG = if modular then "y" else "n";
DEBUG_FS = "y";
MIPS_BOOTLOADER_CMDLINE_REQUIRE_COOKIE = "y";
MIPS_BOOTLOADER_CMDLINE_COOKIE = "\"liminix\"";
MIPS_CMDLINE_DTB_EXTEND = "y";
# basic networking protocols
NET = "y";
UNIX = "y";
INET = "y";
IPV6 = "y";
PACKET = "y"; # for ppp, tcpdump ...
SYSVIPC= "y";
# disabling this option causes the kernel to use an "empty"
# initramfs instead: it has a /dev/console node and not much
# else. Note that pid 1 is started *before* the root
# filesystem is mounted and it expects /dev/console to be
# present already
BLK_DEV_INITRD = lib.mkDefault "n"; # overriden by initramfs module
# s6-linux-init mounts this on /dev
DEVTMPFS = "y";
# some or all of these may be fix for "tmpfs: Unknown parameter 'mode'" error
TMPFS = "y";
TMPFS_POSIX_ACL = "y";
TMPFS_XATTR = "y";
FW_LOADER = "y";
FW_LOADER_COMPRESS = "y";
# We don't have a user helper, so we get multiple 60s pauses
# at boot time unless we disable trying to call it.
# https://lkml.org/lkml/2013/8/5/175
FW_LOADER_USER_HELPER = "n";
};
};
};
}

View File

@ -11,14 +11,24 @@
{ lib, pkgs, config, ...}:
let
inherit (lib) mkOption types;
inherit (pkgs) liminix;
in {
options = {
system.service.pppoe = mkOption {
type = types.functionTo types.package;
type = liminix.lib.types.serviceDefn;
};
};
config = {
system.service.pppoe = pkgs.callPackage ./pppoe.nix {};
system.service.pppoe = pkgs.liminix.callService ./pppoe.nix {
interface = mkOption {
type = liminix.lib.types.service;
description = "ethernet interface to run PPPoE over";
};
ppp-options = mkOption {
type = types.listOf types.str;
description = "options supplied on ppp command line";
};
};
kernel = {
config = {
PPP = "y";

View File

@ -6,25 +6,9 @@
, writeAshScript
, serviceFns
} :
{ interface, ppp-options }:
let
inherit (liminix.services) longrun;
inherit (liminix.lib) typeChecked;
inherit (lib) mkOption types;
t = {
interface = mkOption {
type = liminix.lib.types.service;
description = "ethernet interface to run PPPoE over";
};
ppp-options = mkOption {
type = types.listOf types.str;
description = "options supplied on ppp command line";
};
};
in
params:
let
inherit (typeChecked "pppoe.nix" t params) interface ppp-options;
name = "${interface.device}.pppoe";
ip-up = writeAshScript "ip-up" {} ''
. ${serviceFns}
@ -55,7 +39,6 @@ let
"usepeerdns"
"logfd" "2"
];
in
longrun {
inherit name;

50
modules/ssh/default.nix Normal file
View File

@ -0,0 +1,50 @@
## Secure Shell
## ============
##
## Provide SSH service using Dropbear
{ lib, pkgs, config, ...}:
let
inherit (lib) mkOption types;
inherit (pkgs) liminix;
mkBoolOption = description : mkOption {
type = types.bool;
inherit description;
default = true;
};
in {
options = {
system.service.ssh = mkOption {
type = liminix.lib.types.serviceDefn;
};
};
config.system.service = {
ssh = liminix.callService ./ssh.nix {
address = mkOption {
type = types.nullOr types.str;
default = null;
description = "Listen on specified address";
example = "127.0.0.1";
};
port = mkOption {
type = types.port;
default = 22;
description = "Listen on specified TCP port";
};
allowRoot = mkBoolOption "Allow root to login";
allowPasswordLogin = mkBoolOption "Allow login using password (disable for public key auth only)";
allowPasswordLoginForRoot = mkBoolOption "Allow root to login using password (disable for public key auth only)";
allowLocalPortForward = mkBoolOption "Enable local port forwarding";
allowRemotePortForward = mkBoolOption "Enable remote port forwarding";
allowRemoteConnectionToForwardedPorts = mkOption {
type = types.bool; default = false;
description = "Allow remote hosts to connect to local forwarded ports (by default they are bound to loopback)";
};
extraConfig = mkOption {
type = types.separatedString " ";
default = "";
};
};
};
}

42
modules/ssh/ssh.nix Normal file
View File

@ -0,0 +1,42 @@
{
liminix
, dropbear
, serviceFns
, lib
}:
p :
let
inherit (liminix.services) longrun;
inherit (lib) concatStringsSep;
options =
[
"-e" # pass environment to child
"-E" # log to stderr
"-R" # create hostkeys if needed
"-P /run/dropbear.pid"
"-F" # don't fork into background
] ++
(lib.optional (! p.allowRoot) "-w") ++
(lib.optional (! p.allowPasswordLogin) "-s") ++
(lib.optional (! p.allowPasswordLoginForRoot) "-g") ++
(lib.optional (! p.allowLocalPortForward) "-j") ++
(lib.optional (! p.allowRemotePortForward) "-k") ++
(lib.optional (! p.allowRemoteConnectionToForwardedPorts) "-a") ++
[(if p.address != null
then "-p ${p.address}:${p.port}"
else "-p ${builtins.toString p.port}")] ++
[p.extraConfig];
in
longrun {
name = "sshd";
# env -i clears the environment so we don't pass anything weird to
# ssh sessions
run = ''
if test -d /persist; then
mkdir -p /persist/secrets/dropbear
ln -s /persist/secrets/dropbear /run
fi
. /etc/profile # sets PATH but do we need this? it's the same file as ashrc
exec env -i ENV=/etc/ashrc PATH=$PATH ${dropbear}/bin/dropbear ${concatStringsSep " " options}
'';
}