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