Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions maintainers/maintainer-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -29457,6 +29457,12 @@
githubId = 69384921;
name = "ZeStig";
};
zsuper = {
email = "piyushkumbhare10@gmail.com";
github = "zSuperx";
githubId = 130249145;
name = "Piyush Kumbhare";
};
ztzg = {
email = "dd@crosstwine.com";
github = "ztzg";
Expand Down
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,7 @@
./services/networking/bitlbee.nix
./services/networking/blockbook-frontend.nix
./services/networking/blocky.nix
./services/networking/bore/bore.nix
./services/networking/byedpi.nix
./services/networking/cato-client.nix
./services/networking/centrifugo.nix
Expand Down
162 changes: 162 additions & 0 deletions nixos/modules/services/networking/bore/bore.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
{
config,
pkgs,
lib,
...
}:
let
cfg = config.services.bore;
inherit (lib)
mkOption
mkPackageOption
mkEnableOption
types
optionalString
;
inherit (types) attrsOf submodule;
in
{
options = {
services.bore = {
package = mkPackageOption pkgs "bore-cli" { };

local = mkOption {
type = attrsOf (submodule ./local-options.nix);
description = "Definition of bore local proxies.";
default = { };
};

server = mkOption {
type = attrsOf (submodule ./server-options.nix);
description = "Definition of bore remote proxy servers.";
default = { };
};

enableLocalPortCheck = mkEnableOption "local port check" // {
default = true;
example = false;
};

enableServerPortCheck = mkEnableOption "server port check" // {
default = true;
example = false;
};
};
};

config =
let
getEnabled = s: builtins.filter (value: value.enable) (lib.attrValues s);
mkBoreService =
Comment thread
MattSturgeon marked this conversation as resolved.
kind: v:
let
isServer = (kind == "server");
in
{
name = "bore-${kind}-${v.name}";
value = {
description = "bore remote proxy service for ${v.name}";
enable = true;
after = [
"network-online.target"
"nss-lookup.target"
];

requires = [
"network-online.target"
"nss-lookup.target"
];

wantedBy = [ "multi-user.target" ];

environment =
if isServer then
{
BORE_MIN_PORT = toString v.min-port;
BORE_MAX_PORT = toString v.max-port;
}
else
{
BORE_SERVER = v.to;
BORE_LOCAL_PORT = toString v.local-port;
};

script = ''
${optionalString (v.secretFile != null) ''export BORE_SECRET="$(<${v.secretFile})"''}

${lib.getExe' cfg.package "bore"} ${kind} \
''
+ (
if isServer then
''--bind-addr="${v.bind-addr}" --bind-tunnels="${v.bind-tunnels}"''
else
''--local-host="${v.local-host}" --port=${toString v.remote-port}''
);

serviceConfig = {
Restart = "on-failure";
RestartSec = 10;
};
};
};
in
{
systemd.services =
(lib.genAttrs' (getEnabled cfg.server) (mkBoreService "server"))
// (lib.genAttrs' (getEnabled cfg.local) (mkBoreService "local"));

assertions =
let
localTuples = map (v: "${v.local-host}:${toString v.local-port}") (getEnabled cfg.local);

# For remote tuples, filter out entries where remote-port == 0, since that's a legal default
remoteTuples = map (v: "${v.to}:${toString v.remote-port}") (
builtins.filter (v: v.remote-port != 0) (getEnabled cfg.local)
);

bindAddrs = map (v: v.bind-addr) (getEnabled cfg.server);

findDuplicates =
list:
lib.pipe list [
(builtins.groupBy toString)
(builtins.mapAttrs (_: builtins.length))
(lib.filterAttrs (_: len: len > 1))
builtins.attrNames
];
in
[
(lib.mkIf (cfg.enableLocalPortCheck) {
assertion = lib.allUnique localTuples;
message = ''
Detected duplicate values of the following `local-host:local-port` tuples:
${lib.concatStringsSep ", " (findDuplicates localTuples)}
Ensure these tuples are unique across attributes in `services.bore.local`!
(To turn off this assertion, set `services.bore.enableLocalPortCheck = false;`)
'';
})

(lib.mkIf (cfg.enableLocalPortCheck) {
assertion = lib.allUnique remoteTuples;
message = ''
Detected duplicate values for `to:remote-port` tuples:
${lib.concatStringsSep ", " (findDuplicates remoteTuples)}
Ensure these tuples are unique across attributes in `services.bore.local`!
(To turn off this assertion, set `services.bore.enableLocalPortCheck = false;`)
'';
})

(lib.mkIf (cfg.enableServerPortCheck) {
assertion = lib.allUnique bindAddrs;
message = ''
Detected duplicate values for bore remote `bind-addr`s:
${lib.concatStringsSep ", " (findDuplicates bindAddrs)}
Ensure `bind-addr` is unique across attributes in `services.bore.server`!
(To turn off this assertion, set `services.bore.enableServerPortCheck = false;`)
'';
})
];
};

meta.maintainers = with lib.maintainers; [ zsuper ];
}
28 changes: 28 additions & 0 deletions nixos/modules/services/networking/bore/common-options.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
name,
lib,
...
}:
let
inherit (lib) mkOption types literalMD;
inherit (types) nullOr str path;
in
{
options = {

name = mkOption {
type = str;
default = name;
defaultText = literalMD "the attribute name";
description = "name of the service";
};

secretFile = mkOption {
type = nullOr path;
default = null;
description = ''
Optional path to file containing secret for authentication.
'';
};
};
}
49 changes: 49 additions & 0 deletions nixos/modules/services/networking/bore/local-options.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
lib,
...
}:
let
inherit (lib) mkOption mkEnableOption types;
inherit (types) port str;
in
{
imports = [
./common-options.nix
];

options = {
enable = mkEnableOption "Bore TCP tunnel (local proxy to the remote server) for `<name>`";

local-host = mkOption {
type = str;
default = "localhost";
description = ''
The local host to expose.
'';
};

to = mkOption {
type = str;
default = "bore.pub";
description = ''
Address of the remote server to expose local ports to.
'';
};

remote-port = mkOption {
type = port;
default = 0;
description = ''
Optional port on the remote server to select.
(If set to 0, a random available port will be assigned by the remote server.)
'';
};

local-port = mkOption {
type = port;
description = ''
The local port to expose.
'';
};
};
}
55 changes: 55 additions & 0 deletions nixos/modules/services/networking/bore/server-options.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
lib,
config,
options,
...
}:
let
inherit (lib) mkOption mkEnableOption types;
inherit (types)
port
str
;
in
{
imports = [
./common-options.nix
];

options = {
enable = mkEnableOption "Bore TCP tunnel (remote proxy server) for `<name>`";

min-port = mkOption {
type = port;
default = 1024;
description = ''
Minimum accepted TCP port number.
'';
};

max-port = mkOption {
type = port;
default = 65535;
description = ''
Maximum accepted TCP port number.
'';
};

bind-addr = mkOption {
type = str;
default = "0.0.0.0";
description = ''
IP address to bind to, clients must reach this.
'';
};

bind-tunnels = mkOption {
type = str;
default = config.bind-addr;
defaultText = lib.literalExpression "config.${options.bind-addr}";
description = ''
IP address where tunnels will listen on.
'';
};
};
}
1 change: 1 addition & 0 deletions nixos/tests/all-tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ in
boot-stage1 = runTest ./boot-stage1.nix;
boot-stage2 = runTest ./boot-stage2.nix;
bootspec = handleTestOn [ "x86_64-linux" ] ./bootspec.nix { };
bore = runTest ./bore.nix;
borgbackup = runTest ./borgbackup.nix;
borgmatic = runTest ./borgmatic.nix;
botamusique = runTest ./botamusique.nix;
Expand Down
65 changes: 65 additions & 0 deletions nixos/tests/bore.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
name = "bore";

nodes = {
serverStandalone = {
services.bore = {
server = {
example.enable = true;
};
};
};

bothStandard = {
services.bore = {
server.example.enable = true;

local.example = {
enable = true;
to = "0.0.0.0";
local-port = 3000;
remote-port = 4000;
};
};
};

bothWithSecret =
{ pkgs, ... }:
{
services.bore = {
server.example = {
secretFile = pkgs.writeText "server-secret" "super-secret-message!";
enable = true;
};

local.example = {
enable = true;
to = "0.0.0.0";
local-port = 3000;
remote-port = 4000;
secretFile = pkgs.writeText "local-secret" "super-secret-message!";
};
};
};
};

testScript = ''
with subtest("Standalone server"):
serverStandalone.wait_for_unit("bore-server-example.service")
serverStandalone.wait_for_open_port(7835, "0.0.0.0")

with subtest("Server & Proxy running together"):
bothStandard.wait_for_unit("bore-server-example.service")
bothStandard.wait_for_open_port(7835, "0.0.0.0")

bothStandard.wait_for_unit("bore-local-example.service")
bothStandard.wait_for_open_port(4000, "0.0.0.0")

with subtest("Server & Proxy + authentication secret"):
bothWithSecret.wait_for_unit("bore-server-example.service")
bothWithSecret.wait_for_open_port(7835, "0.0.0.0")

bothWithSecret.wait_for_unit("bore-local-example.service")
bothWithSecret.wait_for_open_port(4000, "0.0.0.0")
'';
}
Loading