add juicity service module and juicity nixos module

This commit is contained in:
ulic-youthlic 2025-02-03 21:07:31 +08:00
parent 63a8871eaa
commit 1a6d282046
Signed by: youthlic
GPG key ID: 63E86C3C14A0D721
9 changed files with 447 additions and 104 deletions

View file

@ -31,6 +31,7 @@
enable = true;
baseDomain = "youthlic.fun";
};
juicity.server.enable = true;
};
};

View file

@ -39,6 +39,7 @@
open-webui.enable = true;
transmission.enable = true;
nix-ld.enable = true;
juicity.client.enable = true;
};
gui.enabled = "niri";
};

View file

@ -1,5 +1,6 @@
include {
proxy.d/*.dae
local.d/*.dae
}
global {
@ -43,10 +44,12 @@ dns {
group {
proxy {
filter: subtag(wget)
filter: name(local)
policy: min_moving_avg
}
us {
filter: subtag(wget) && name(keyword: "美国")
filter: name(local)
policy: min_moving_avg
}
hk {
@ -59,6 +62,7 @@ group {
routing {
pname(hickory-dns) && dport(53) -> must_direct
pname(mihomo) -> must_direct
pname(juicity-client) -> must_direct
# pname(systemd-resolve) -> must_direct
dip(107.174.145.140) -> must_direct

View file

@ -14,114 +14,126 @@ in
enable = lib.mkEnableOption "dae";
};
};
config = lib.mkIf cfg.enable {
services.dae = {
enable = true;
openFirewall = {
config = lib.mkMerge [
(lib.mkIf cfg.enable {
services.dae = {
enable = true;
port = 12345;
openFirewall = {
enable = true;
port = 12345;
};
disableTxChecksumIpGeneric = false;
config = builtins.readFile ./config.dae;
};
disableTxChecksumIpGeneric = false;
config = builtins.readFile ./config.dae;
};
sops.secrets.url = {
mode = "0444";
sopsFile = rootPath + "/secrets/general.yaml";
};
systemd.services =
let
update = ''
head="user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36"
new_proxy=/etc/dae/proxy.d.new
num=0
check=1
urls="$(cat ${config.sops.secrets.url.path})"
mkdir -p ''${new_proxy}
for url in ''${urls}; do
txt=''${new_proxy}/''${num}.txt
config="''${new_proxy}/''${num}.dae"
echo \'curl -LH \""''${head}"\" \""''${url}"\" -o \""''${txt}"\"\'
curl -LH "''${head}" "''${url}" -o "''${txt}"
echo End curl
echo "" > ''${config}
{
echo 'subscription {'
echo \ \ wget:\ \"file://proxy.d/''${num}.txt\"
echo "}"
} >> ''${config}
if [[ ! -s ''${txt} ]]; then
check=0
fi
chmod 0640 ''${txt}
chmod 0640 ''${config}
num=$((num+1))
sops.secrets.url = {
mode = "0444";
sopsFile = rootPath + "/secrets/general.yaml";
};
systemd.services =
let
update = ''
head="user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36"
new_proxy=/etc/dae/proxy.d.new
num=0
check=1
urls="$(cat ${config.sops.secrets.url.path})"
mkdir -p ''${new_proxy}
for url in ''${urls}; do
txt=''${new_proxy}/''${num}.txt
config="''${new_proxy}/''${num}.dae"
echo \'curl -LH \""''${head}"\" \""''${url}"\" -o \""''${txt}"\"\'
curl -LH "''${head}" "''${url}" -o "''${txt}"
echo End curl
echo "" > ''${config}
{
echo 'subscription {'
echo \ \ wget:\ \"file://proxy.d/''${num}.txt\"
echo "}"
} >> ''${config}
if [[ ! -s ''${txt} ]]; then
check=0
fi
chmod 0640 ''${txt}
chmod 0640 ''${config}
num=$((num+1))
if [[ ''${check} -eq 0 ]]; then
echo "''${txt}" is empty
exit 103
if [[ ''${check} -eq 0 ]]; then
echo "''${txt}" is empty
exit 103
fi
done
if [[ -d /etc/dae/proxy.d ]]; then
mv /etc/dae/proxy.d /etc/dae/proxy.d.old
fi
done
if [[ -d /etc/dae/proxy.d ]]; then
mv /etc/dae/proxy.d /etc/dae/proxy.d.old
fi
mv ''${new_proxy} /etc/dae/proxy.d
'';
updateScript = pkgs.writeShellApplication {
name = "update.sh";
runtimeInputs = with pkgs; [
coreutils
curl
];
text = ''
mkdir -p /etc/proxy.d
if [ -z "$(ls -A /etc/dae/proxy.d 2>/dev/null)" ]; then
echo "No subscription file found in /etc/dae/proxy.d. Update now..."
mv ''${new_proxy} /etc/dae/proxy.d
'';
updateScript = pkgs.writeShellApplication {
name = "update.sh";
runtimeInputs = with pkgs; [
coreutils
curl
];
text = ''
mkdir -p /etc/proxy.d
if [ -z "$(ls -A /etc/dae/proxy.d 2>/dev/null)" ]; then
echo "No subscription file found in /etc/dae/proxy.d. Update now..."
${update}
else
echo "Found existing subscription files. Skipping immediate update."
fi
'';
};
updateForceScript = pkgs.writeShellApplication {
name = "update-force.sh";
runtimeInputs = with pkgs; [
coreutils
curl
];
text = ''
${update}
else
echo "Found existing subscription files. Skipping immediate update."
fi
'';
};
updateForceScript = pkgs.writeShellApplication {
name = "update-force.sh";
runtimeInputs = with pkgs; [
coreutils
curl
];
text = ''
${update}
'';
};
in
{
"update-dae-subscription-immediate" = {
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
before = [ "dae.service" ];
serviceConfig = {
Type = "oneshot";
User = "root";
ExecStart = [
"${updateScript}/bin/update.sh"
];
'';
};
wantedBy = [ "multi-user.target" ];
};
"update-dae-subscription-force" = {
serviceConfig = {
Type = "oneshot";
User = "root";
ExecStartPre = [
"-${pkgs.systemd}/bin/systemctl stop dae.service"
];
ExecStartPost = [
"-${pkgs.systemd}/bin/systemctl start dae.service"
];
ExecStart = [
"${updateForceScript}/bin/update-force.sh"
];
in
{
"update-dae-subscription-immediate" = {
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
before = [ "dae.service" ];
serviceConfig = {
Type = "oneshot";
User = "root";
ExecStart = [
"${updateScript}/bin/update.sh"
];
};
wantedBy = [ "multi-user.target" ];
};
"update-dae-subscription-force" = {
serviceConfig = {
Type = "oneshot";
User = "root";
ExecStartPre = [
"-${pkgs.systemd}/bin/systemctl stop dae.service"
];
ExecStartPost = [
"-${pkgs.systemd}/bin/systemctl start dae.service"
];
ExecStart = [
"${updateForceScript}/bin/update-force.sh"
];
};
};
};
})
(lib.mkIf (cfg.enable && config.youthlic.programs.juicity.client.enable) {
environment.etc."dae/local.d/0.dae" = {
text = ''
node {
local: 'socks5://127.0.0.1:7890/'
}
'';
mode = "0440";
};
};
})
];
}

View file

@ -16,5 +16,6 @@
./transmission.nix
./conduwuit.nix
./nix-ld.nix
./juicity
];
}

View file

@ -0,0 +1,106 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.youthlic.programs.juicity;
in
{
imports = [
./template.nix
];
options = {
youthlic.programs.juicity = {
client = {
enable = lib.mkEnableOption "juicity-client";
};
server = {
enable = lib.mkEnableOption "juicity-server";
};
};
};
config = lib.mkMerge [
(lib.mkIf cfg.client.enable {
users.groups.juicity.members = [ "root" ];
sops = {
secrets = {
"juicity/serverIp" = { };
"juicity/sni" = { };
"juicity/certchainSha256" = { };
};
templates."juicity-client-config.json" = {
group = "juicity";
mode = "0440";
content = ''
{
"listen": ":7890",
"server": "${config.sops.placeholder."juicity/serverIp"}:23182",
"uuid": "${config.sops.placeholder."juicity/uuid"}",
"password": "${config.sops.placeholder."juicity/password"}",
"sni": "${config.sops.placeholder."juicity/sni"}",
"allow_insecure": false,
"pinned_certchain_sha256": "${config.sops.placeholder."juicity/certchainSha256"}",
"log_level": "info"
}
'';
};
};
services.juicity.client = {
enable = true;
package = pkgs.juicity;
configFile = "${config.sops.templates."juicity-client-config.json".path}";
allowedOpenFirewallPorts = [
7890
];
group = "juicity";
};
})
(lib.mkIf cfg.server.enable {
users.groups.juicity.members = [ "root" ];
sops = {
secrets = {
"juicity/certificate" = {
group = "juicity";
mode = "0440";
};
"juicity/private_key" = {
group = "juicity";
mode = "0440";
};
};
templates."juicity-server-config.json" = {
group = "juicity";
mode = "0440";
content = ''
{
"listen": ":23182",
"users": {
"${config.sops.placeholder."juicity/uuid"}": "${config.sops.placeholder."juicity/password"}"
},
"certificate": "${config.sops.secrets."juicity/certificate".path}",
"private_key": "${config.sops.secrets."juicity/private_key".path}",
"log_level": "info"
}
'';
};
};
services.juicity.server = {
enable = true;
package = pkgs.juicity;
configFile = "${config.sops.templates."juicity-server-config.json".path}";
allowedOpenFirewallPorts = [
23182
];
group = "juicity";
};
})
(lib.mkIf (cfg.server.enable || cfg.client.enable) {
sops.secrets = {
"juicity/uuid" = { };
"juicity/password" = { };
};
})
];
}

View file

@ -0,0 +1,206 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.juicity;
settingsFormat = pkgs.formats.json { };
clientConfigFile =
if (cfg.client.configFile != null) then
cfg.client.configFile
else
settingsFormat cfg.client.settings;
serverConfigFile =
if (cfg.server.configFile != null) then
cfg.server.configFile
else
settingsFormat cfg.server.settings;
in
{
options = {
services.juicity = {
client = {
enable = lib.mkEnableOption "juicity-client";
package = lib.mkPackageOption pkgs "juicity" { };
group = lib.mkOption {
type = lib.types.nullOr lib.types.str;
example = "juicity";
default = null;
};
settings = lib.mkOption {
type = settingsFormat.type;
default = { };
example = {
listen = ":1000";
server = "112.32.62.11:23182";
uuid = "00000000-0000-0000-0000-000000000000";
password = "my_password";
sni = "www.example.com";
allow_insecure = false;
congestion_control = "bbr";
log_level = "info";
};
description = ''
Juicity client configuration, for configuration options
see example of [client](https://github.com/juicity/juicity/blob/main/install/example-client.json) on github.
'';
};
configFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
example = "/run/juicity/config.json";
default = null;
description = ''
A file which JSON configurations for juicity client. See the {option}`settings` option for more information.
Note: this file will override {options}`settings` option, which is recommanded.
'';
};
allowedOpenFirewallPorts = lib.mkOption {
type = lib.types.nullOr (lib.types.listOf lib.types.port);
example = [ 23182 ];
default = null;
description = ''
the ports should be open
'';
};
};
server = {
enable = lib.mkEnableOption "juicity-server";
package = lib.mkPackageOption pkgs "juicity" { };
group = lib.mkOption {
type = lib.types.nullOr lib.types.str;
example = "juicity";
default = null;
};
settings = lib.mkOption {
type = settingsFormat.type;
default = { };
description = ''
Juicity server configuration, for configuration options
see example of [server](https://github.com/juicity/juicity/blob/main/install/example-server.json) on github.
'';
example = {
listen = ":23182";
users = {
"00000000-0000-0000-0000-000000000000" = "my_password";
};
certificate = "/path/to/fullchain.cer";
private_key = "/path/to/private.key";
congestion_control = "bbr";
log_level = "info";
};
};
configFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
example = "/run/juicity/config.json";
default = null;
description = ''
A file which JSON configurations for juicity server. See the {option}`settings` option for more information.
Note: this file will override {options}`settings` option, which is recommanded.
'';
};
allowedOpenFirewallPorts = lib.mkOption {
type = lib.types.nullOr (lib.types.listOf lib.types.port);
example = [ 23182 ];
default = null;
description = ''
the ports should be open
'';
};
};
};
};
config = lib.mkMerge [
(lib.mkIf (cfg.client.enable && (cfg.client.allowedOpenFirewallPorts != null)) {
networking.firewall.allowedTCPPorts = cfg.client.allowedOpenFirewallPorts;
networking.firewall.allowedUDPPorts = cfg.client.allowedOpenFirewallPorts;
})
(lib.mkIf (cfg.server.enable && (cfg.server.allowedOpenFirewallPorts != null)) {
networking.firewall.allowedTCPPorts = cfg.server.allowedOpenFirewallPorts;
networking.firewall.allowedUDPPorts = cfg.server.allowedOpenFirewallPorts;
})
(lib.mkIf (cfg.server.enable && (cfg.server.group != null)) {
systemd.services."juicity-server".serviceConfig = {
Group = cfg.server.group;
};
})
(lib.mkIf (cfg.client.enable && (cfg.client.group != null)) {
systemd.services."juicity-client".serviceConfig = {
Group = cfg.client.group;
};
})
(lib.mkIf cfg.client.enable {
environment.systemPackages = [
cfg.client.package
];
systemd.services.juicity-client = {
description = ''
juicity-client Service
'';
documentation = [
"https://github.com/juicity/juicity"
];
after = [
"network.target"
"nss-lookup.target"
];
wantedBy = [
"multi-user.target"
];
serviceConfig = {
Type = "simple";
DynamicUser = true;
CapabilityBoundingSet = [
"CAP_NET_ADMIN"
"CAP_NET_BIND_SERVICE"
"CAP_NET_RAW"
];
AmbientCapabilities = [
"CAP_NET_ADMIN"
"CAP_NET_BIND_SERVICE"
"CAP_NET_RAW"
];
ExecStart = ''
${lib.getExe' cfg.client.package "juicity-client"} run --disable-timestamp --config ${clientConfigFile}
'';
Restart = "on-failure";
LimitNPROC = 512;
LimitNOFILE = "infinity";
};
};
})
(lib.mkIf cfg.server.enable {
environment.systemPackages = [
cfg.server.package
];
systemd.services.juicity-server = {
description = ''
juicity-server Service
'';
documentation = [
"https://github.com/juicity/juicity"
];
after = [
"network.target"
"nss-lookup.target"
];
wantedBy = [
"multi-user.target"
];
serviceConfig = {
Type = "simple";
DynamicUser = true;
ExecStart = ''
${lib.getExe' cfg.server.package "juicity-server"} run --disable-timestamp --config ${serverConfigFile}
'';
Restart = "on-failure";
LimitNPROC = 512;
LimitNOFILE = "infinity";
};
};
})
];
}