mirror of https://github.com/wg-easy/wg-easy
25 changed files with 919 additions and 83 deletions
@ -0,0 +1,27 @@ |
|||||
|
{ |
||||
|
"nodes": { |
||||
|
"nixpkgs": { |
||||
|
"locked": { |
||||
|
"lastModified": 1779508470, |
||||
|
"narHash": "sha256-Ap9KJX+5xHIn3bPIpfNgT6MEXdAECECwo4/rmlQD74M=", |
||||
|
"owner": "NixOS", |
||||
|
"repo": "nixpkgs", |
||||
|
"rev": "29916453413845e54a65b8a1cf996842300cd299", |
||||
|
"type": "github" |
||||
|
}, |
||||
|
"original": { |
||||
|
"owner": "NixOS", |
||||
|
"ref": "nixos-unstable", |
||||
|
"repo": "nixpkgs", |
||||
|
"type": "github" |
||||
|
} |
||||
|
}, |
||||
|
"root": { |
||||
|
"inputs": { |
||||
|
"nixpkgs": "nixpkgs" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"root": "root", |
||||
|
"version": 7 |
||||
|
} |
||||
@ -0,0 +1,71 @@ |
|||||
|
{ |
||||
|
description = "Nix package and NixOS module for wg-easy"; |
||||
|
|
||||
|
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; |
||||
|
|
||||
|
outputs = |
||||
|
{ self, nixpkgs }: |
||||
|
let |
||||
|
linuxSystems = [ |
||||
|
"x86_64-linux" |
||||
|
"aarch64-linux" |
||||
|
]; |
||||
|
formatterSystems = linuxSystems ++ [ |
||||
|
"aarch64-darwin" |
||||
|
"x86_64-darwin" |
||||
|
]; |
||||
|
forAllSystems = nixpkgs.lib.genAttrs linuxSystems; |
||||
|
forFormatterSystems = nixpkgs.lib.genAttrs formatterSystems; |
||||
|
pkgsFor = system: import nixpkgs { inherit system; }; |
||||
|
in |
||||
|
{ |
||||
|
packages = forAllSystems (system: { |
||||
|
wg-easy = (pkgsFor system).callPackage ./nix/package.nix { }; |
||||
|
default = self.packages.${system}.wg-easy; |
||||
|
}); |
||||
|
|
||||
|
checks = forAllSystems (system: { |
||||
|
package = self.packages.${system}.wg-easy; |
||||
|
}); |
||||
|
|
||||
|
devShells = forAllSystems ( |
||||
|
system: |
||||
|
let |
||||
|
pkgs = pkgsFor system; |
||||
|
in |
||||
|
{ |
||||
|
default = pkgs.mkShell { |
||||
|
packages = with pkgs; [ |
||||
|
nodejs_24 |
||||
|
pnpm_11 |
||||
|
wireguard-tools |
||||
|
wireguard-go |
||||
|
iptables |
||||
|
nftables |
||||
|
]; |
||||
|
}; |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
formatter = forFormatterSystems ( |
||||
|
system: |
||||
|
let |
||||
|
pkgs = pkgsFor system; |
||||
|
in |
||||
|
pkgs.writeShellApplication { |
||||
|
name = "wg-easy-nix-fmt"; |
||||
|
runtimeInputs = [ pkgs.nixfmt ]; |
||||
|
text = '' |
||||
|
if [ "$#" -eq 0 ]; then |
||||
|
set -- flake.nix nix/*.nix |
||||
|
fi |
||||
|
|
||||
|
exec nixfmt "$@" |
||||
|
''; |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
nixosModules.default = import ./nix/module.nix; |
||||
|
nixosModules.wg-easy = self.nixosModules.default; |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,254 @@ |
|||||
|
{ |
||||
|
config, |
||||
|
lib, |
||||
|
pkgs, |
||||
|
... |
||||
|
}: |
||||
|
|
||||
|
let |
||||
|
cfg = config.services.wg-easy; |
||||
|
boolString = value: if value then "true" else "false"; |
||||
|
listString = values: lib.concatStringsSep "," values; |
||||
|
validListItems = |
||||
|
values: lib.all (value: value != "" && builtins.match ".*,.*" value == null) values; |
||||
|
interfaceNamePattern = "[A-Za-z0-9_.-]{1,15}"; |
||||
|
in |
||||
|
{ |
||||
|
options.services.wg-easy = { |
||||
|
enable = lib.mkEnableOption "wg-easy"; |
||||
|
|
||||
|
package = lib.mkOption { |
||||
|
type = lib.types.package; |
||||
|
default = pkgs.callPackage ./package.nix { }; |
||||
|
defaultText = lib.literalExpression "pkgs.callPackage ./package.nix { }"; |
||||
|
description = "wg-easy package to run."; |
||||
|
}; |
||||
|
|
||||
|
interfaceName = lib.mkOption { |
||||
|
type = lib.types.str; |
||||
|
default = "wg0"; |
||||
|
description = "WireGuard interface name managed by wg-easy."; |
||||
|
}; |
||||
|
|
||||
|
wireguardPort = lib.mkOption { |
||||
|
type = lib.types.port; |
||||
|
default = 51820; |
||||
|
description = "UDP port used by the WireGuard interface."; |
||||
|
}; |
||||
|
|
||||
|
uiPort = lib.mkOption { |
||||
|
type = lib.types.port; |
||||
|
default = 51821; |
||||
|
description = "TCP port used by the wg-easy web UI."; |
||||
|
}; |
||||
|
|
||||
|
stateDir = lib.mkOption { |
||||
|
type = lib.types.str; |
||||
|
default = "/var/lib/wg-easy"; |
||||
|
description = "Writable directory for the SQLite database and generated WireGuard config."; |
||||
|
}; |
||||
|
|
||||
|
externalInterface = lib.mkOption { |
||||
|
type = lib.types.nullOr lib.types.str; |
||||
|
default = null; |
||||
|
description = "Optional egress network interface used in wg-easy NAT hooks."; |
||||
|
}; |
||||
|
|
||||
|
enableIPv6 = lib.mkOption { |
||||
|
type = lib.types.bool; |
||||
|
default = false; |
||||
|
description = "Enable IPv6 addresses and IPv6 firewall hooks in wg-easy."; |
||||
|
}; |
||||
|
|
||||
|
defaultDns = lib.mkOption { |
||||
|
type = lib.types.nullOr (lib.types.listOf lib.types.str); |
||||
|
default = null; |
||||
|
description = "DNS servers to stamp onto new clients. Leave null to use wg-easy's global default."; |
||||
|
}; |
||||
|
|
||||
|
defaultAllowedIps = lib.mkOption { |
||||
|
type = lib.types.nullOr (lib.types.listOf lib.types.str); |
||||
|
default = null; |
||||
|
description = "Allowed IPs to stamp onto new client configs. Leave null to use wg-easy's global default."; |
||||
|
}; |
||||
|
|
||||
|
defaultServerAllowedIps = lib.mkOption { |
||||
|
type = lib.types.nullOr (lib.types.listOf lib.types.str); |
||||
|
default = null; |
||||
|
description = "Additional server-side peer AllowedIPs to stamp onto new clients."; |
||||
|
}; |
||||
|
|
||||
|
defaultFirewallAllowedIps = lib.mkOption { |
||||
|
type = lib.types.nullOr (lib.types.listOf lib.types.str); |
||||
|
default = null; |
||||
|
description = "Firewall-enforced allowed destinations to stamp onto new clients."; |
||||
|
}; |
||||
|
|
||||
|
defaultPersistentKeepalive = lib.mkOption { |
||||
|
type = lib.types.nullOr (lib.types.ints.between 0 65535); |
||||
|
default = 25; |
||||
|
description = "Persistent keepalive to stamp onto new clients. Set null to preserve wg-easy's configured default."; |
||||
|
}; |
||||
|
|
||||
|
firewallEnabled = lib.mkOption { |
||||
|
type = lib.types.nullOr lib.types.bool; |
||||
|
default = null; |
||||
|
description = "Enable or disable wg-easy per-client firewall filtering. Leave null to preserve wg-easy's configured value."; |
||||
|
}; |
||||
|
|
||||
|
forceUpdateClients = lib.mkOption { |
||||
|
type = lib.types.bool; |
||||
|
default = false; |
||||
|
description = "Overwrite existing clients with configured Nix defaults on service startup."; |
||||
|
}; |
||||
|
|
||||
|
insecure = lib.mkOption { |
||||
|
type = lib.types.bool; |
||||
|
default = false; |
||||
|
description = "Serve the UI as insecure HTTP. Keep false when using HTTPS through a reverse proxy."; |
||||
|
}; |
||||
|
|
||||
|
openFirewall = lib.mkOption { |
||||
|
type = lib.types.bool; |
||||
|
default = true; |
||||
|
description = "Open the WireGuard UDP port in the NixOS firewall."; |
||||
|
}; |
||||
|
|
||||
|
openWebUIFirewall = lib.mkOption { |
||||
|
type = lib.types.bool; |
||||
|
default = false; |
||||
|
description = "Open the wg-easy web UI TCP port in the NixOS firewall."; |
||||
|
}; |
||||
|
|
||||
|
extraEnvironment = lib.mkOption { |
||||
|
type = lib.types.attrsOf lib.types.str; |
||||
|
default = { }; |
||||
|
description = "Additional environment variables for the wg-easy service."; |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
config = lib.mkIf cfg.enable { |
||||
|
assertions = [ |
||||
|
{ |
||||
|
assertion = builtins.match interfaceNamePattern cfg.interfaceName != null; |
||||
|
message = "services.wg-easy.interfaceName must be 1-15 characters using only letters, numbers, '.', '_', or '-'."; |
||||
|
} |
||||
|
{ |
||||
|
assertion = |
||||
|
cfg.externalInterface == null || builtins.match interfaceNamePattern cfg.externalInterface != null; |
||||
|
message = "services.wg-easy.externalInterface must be 1-15 characters using only letters, numbers, '.', '_', or '-'."; |
||||
|
} |
||||
|
{ |
||||
|
assertion = lib.hasPrefix "/" cfg.stateDir; |
||||
|
message = "services.wg-easy.stateDir must be an absolute path."; |
||||
|
} |
||||
|
{ |
||||
|
assertion = cfg.defaultDns == null || validListItems cfg.defaultDns; |
||||
|
message = "services.wg-easy.defaultDns entries must be non-empty and must not contain commas."; |
||||
|
} |
||||
|
{ |
||||
|
assertion = |
||||
|
cfg.defaultAllowedIps == null |
||||
|
|| (cfg.defaultAllowedIps != [ ] && validListItems cfg.defaultAllowedIps); |
||||
|
message = "services.wg-easy.defaultAllowedIps must be null or a non-empty list without commas."; |
||||
|
} |
||||
|
{ |
||||
|
assertion = cfg.defaultServerAllowedIps == null || validListItems cfg.defaultServerAllowedIps; |
||||
|
message = "services.wg-easy.defaultServerAllowedIps entries must be non-empty and must not contain commas."; |
||||
|
} |
||||
|
{ |
||||
|
assertion = |
||||
|
cfg.defaultFirewallAllowedIps == null |
||||
|
|| (cfg.defaultFirewallAllowedIps != [ ] && validListItems cfg.defaultFirewallAllowedIps); |
||||
|
message = "services.wg-easy.defaultFirewallAllowedIps must be null or a non-empty list without commas."; |
||||
|
} |
||||
|
]; |
||||
|
|
||||
|
boot.kernel.sysctl = { |
||||
|
"net.ipv4.ip_forward" = lib.mkDefault 1; |
||||
|
"net.ipv4.conf.all.src_valid_mark" = lib.mkDefault 1; |
||||
|
} |
||||
|
// lib.optionalAttrs cfg.enableIPv6 { |
||||
|
"net.ipv6.conf.all.disable_ipv6" = lib.mkDefault 0; |
||||
|
"net.ipv6.conf.all.forwarding" = lib.mkDefault 1; |
||||
|
"net.ipv6.conf.default.forwarding" = lib.mkDefault 1; |
||||
|
}; |
||||
|
|
||||
|
networking.firewall.allowedUDPPorts = lib.mkIf cfg.openFirewall [ cfg.wireguardPort ]; |
||||
|
networking.firewall.allowedTCPPorts = lib.mkIf cfg.openWebUIFirewall [ cfg.uiPort ]; |
||||
|
|
||||
|
systemd.tmpfiles.rules = [ |
||||
|
"d ${cfg.stateDir} 0700 root root -" |
||||
|
]; |
||||
|
|
||||
|
systemd.services.wg-easy = { |
||||
|
description = "WireGuard Easy"; |
||||
|
documentation = [ "https://wg-easy.github.io/wg-easy/latest/" ]; |
||||
|
wantedBy = [ "multi-user.target" ]; |
||||
|
wants = [ "network-online.target" ]; |
||||
|
after = [ "network-online.target" ]; |
||||
|
|
||||
|
environment = { |
||||
|
DISABLE_IPV6 = boolString (!cfg.enableIPv6); |
||||
|
INSECURE = boolString cfg.insecure; |
||||
|
PORT = toString cfg.uiPort; |
||||
|
WG_FORCE_UPDATE_CLIENTS = boolString cfg.forceUpdateClients; |
||||
|
WG_INTERFACE_NAME = cfg.interfaceName; |
||||
|
WG_PORT = toString cfg.wireguardPort; |
||||
|
WG_STATE_DIR = cfg.stateDir; |
||||
|
} |
||||
|
// lib.optionalAttrs (cfg.defaultDns != null) { |
||||
|
WG_DEFAULT_DNS = listString cfg.defaultDns; |
||||
|
} |
||||
|
// lib.optionalAttrs (cfg.defaultAllowedIps != null) { |
||||
|
WG_DEFAULT_ALLOWED_IPS = listString cfg.defaultAllowedIps; |
||||
|
} |
||||
|
// lib.optionalAttrs (cfg.defaultServerAllowedIps != null) { |
||||
|
WG_DEFAULT_SERVER_ALLOWED_IPS = listString cfg.defaultServerAllowedIps; |
||||
|
} |
||||
|
// lib.optionalAttrs (cfg.defaultFirewallAllowedIps != null) { |
||||
|
WG_DEFAULT_FIREWALL_ALLOWED_IPS = listString cfg.defaultFirewallAllowedIps; |
||||
|
} |
||||
|
// lib.optionalAttrs (cfg.defaultPersistentKeepalive != null) { |
||||
|
WG_DEFAULT_PERSISTENT_KEEPALIVE = toString cfg.defaultPersistentKeepalive; |
||||
|
} |
||||
|
// lib.optionalAttrs (cfg.firewallEnabled != null) { |
||||
|
WG_FIREWALL_ENABLED = boolString cfg.firewallEnabled; |
||||
|
} |
||||
|
// lib.optionalAttrs (cfg.externalInterface != null) { |
||||
|
WG_DEVICE = cfg.externalInterface; |
||||
|
} |
||||
|
// cfg.extraEnvironment; |
||||
|
|
||||
|
path = with pkgs; [ |
||||
|
bash |
||||
|
coreutils |
||||
|
gawk |
||||
|
gnugrep |
||||
|
gnused |
||||
|
iproute2 |
||||
|
iptables |
||||
|
kmod |
||||
|
nftables |
||||
|
procps |
||||
|
wireguard-go |
||||
|
wireguard-tools |
||||
|
]; |
||||
|
|
||||
|
serviceConfig = { |
||||
|
ExecStart = lib.getExe cfg.package; |
||||
|
Restart = "on-failure"; |
||||
|
RestartSec = "5s"; |
||||
|
User = "root"; |
||||
|
AmbientCapabilities = [ |
||||
|
"CAP_NET_ADMIN" |
||||
|
"CAP_SYS_MODULE" |
||||
|
]; |
||||
|
CapabilityBoundingSet = [ |
||||
|
"CAP_NET_ADMIN" |
||||
|
"CAP_SYS_MODULE" |
||||
|
]; |
||||
|
}; |
||||
|
}; |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,105 @@ |
|||||
|
{ |
||||
|
lib, |
||||
|
stdenv, |
||||
|
nodejs_24, |
||||
|
pnpm_10, |
||||
|
pnpmConfigHook, |
||||
|
fetchPnpmDeps, |
||||
|
makeWrapper, |
||||
|
python3, |
||||
|
pkg-config, |
||||
|
bash, |
||||
|
coreutils, |
||||
|
gawk, |
||||
|
gnugrep, |
||||
|
gnused, |
||||
|
iproute2, |
||||
|
iptables, |
||||
|
kmod, |
||||
|
nftables, |
||||
|
procps, |
||||
|
wireguard-go, |
||||
|
wireguard-tools, |
||||
|
}: |
||||
|
|
||||
|
let |
||||
|
nodejs = nodejs_24; |
||||
|
runtimePath = lib.makeBinPath [ |
||||
|
bash |
||||
|
coreutils |
||||
|
gawk |
||||
|
gnugrep |
||||
|
gnused |
||||
|
iproute2 |
||||
|
iptables |
||||
|
kmod |
||||
|
nftables |
||||
|
procps |
||||
|
wireguard-go |
||||
|
wireguard-tools |
||||
|
]; |
||||
|
in |
||||
|
stdenv.mkDerivation (finalAttrs: { |
||||
|
pname = "wg-easy"; |
||||
|
version = "15.3.0"; |
||||
|
|
||||
|
src = ../src; |
||||
|
|
||||
|
CI = true; |
||||
|
|
||||
|
nativeBuildInputs = [ |
||||
|
nodejs |
||||
|
pnpm_10 |
||||
|
pnpmConfigHook |
||||
|
makeWrapper |
||||
|
python3 |
||||
|
pkg-config |
||||
|
]; |
||||
|
|
||||
|
pnpmDeps = fetchPnpmDeps { |
||||
|
inherit (finalAttrs) pname version src; |
||||
|
fetcherVersion = 3; |
||||
|
hash = "sha256-+fvK2wzd3AbOeUzeDhMXjW8rO2nkv1KziQrW6SNqm+g="; |
||||
|
}; |
||||
|
|
||||
|
buildPhase = '' |
||||
|
runHook preBuild |
||||
|
pnpm build |
||||
|
runHook postBuild |
||||
|
''; |
||||
|
|
||||
|
installPhase = '' |
||||
|
runHook preInstall |
||||
|
|
||||
|
mkdir -p $out/share/wg-easy $out/bin |
||||
|
cp -R .output/* $out/share/wg-easy/ |
||||
|
|
||||
|
mkdir -p $out/share/wg-easy/server/database |
||||
|
cp -R server/database/migrations $out/share/wg-easy/server/database/migrations |
||||
|
|
||||
|
if [ -e node_modules/libsql ]; then |
||||
|
mkdir -p $out/share/wg-easy/server/node_modules |
||||
|
cp -RL node_modules/libsql $out/share/wg-easy/server/node_modules/libsql |
||||
|
fi |
||||
|
|
||||
|
makeWrapper ${lib.getExe nodejs} $out/bin/wg-easy \ |
||||
|
--run "cd $out/share/wg-easy" \ |
||||
|
--add-flags "$out/share/wg-easy/server/index.mjs" \ |
||||
|
--prefix PATH : ${runtimePath} |
||||
|
|
||||
|
makeWrapper ${lib.getExe nodejs} $out/bin/wg-easy-cli \ |
||||
|
--run "cd $out/share/wg-easy" \ |
||||
|
--add-flags "$out/share/wg-easy/server/cli.mjs" \ |
||||
|
--prefix PATH : ${runtimePath} |
||||
|
|
||||
|
runHook postInstall |
||||
|
''; |
||||
|
|
||||
|
meta = { |
||||
|
description = "WireGuard VPN server with a web-based admin UI"; |
||||
|
homepage = "https://github.com/wg-easy/wg-easy"; |
||||
|
license = lib.licenses.agpl3Only; |
||||
|
platforms = lib.platforms.linux; |
||||
|
mainProgram = "wg-easy"; |
||||
|
}; |
||||
|
}) |
||||
@ -0,0 +1 @@ |
|||||
|
8PLg+fY2dl4JuoWZvsmmumA6rzFn8tvb5bg/juCuc0I= |
||||
@ -0,0 +1 @@ |
|||||
|
VjyJs31bpDkEqlSEERvjNCjIDj7fRY0Odc69Eo665Qo= |
||||
Loading…
Reference in new issue