1
1
Fork 1
mirror of https://github.com/oddlama/nix-config.git synced 2025-10-10 14:50:40 +02:00

chore: remove firezone again (waiting for upstream PR)

This commit is contained in:
oddlama 2025-02-18 14:32:17 +01:00
parent a3f74254e7
commit 2406dddd8e
No known key found for this signature in database
GPG key ID: 14EFE510775FE39A
21 changed files with 0 additions and 4208 deletions

View file

@ -43,6 +43,5 @@
unifi = uidGid 968;
plugdev.gid = 967;
tss = uidGid 966;
firezone-client = uidGid 965;
};
}

View file

@ -1,5 +1,4 @@
{
globals,
inputs,
lib,
minimal,
@ -89,28 +88,6 @@
# };
#};
# FIXME: the ui is not directly accessible via environment.systemPackages
# FIXME: to control it as a user (and to allow SSO) we need to be in the netbird-home group
services.netbird.ui.enable = true;
services.netbird.clients.home = {
port = 51820;
name = "netbird-home";
interface = "wt-home";
autoStart = false;
openFirewall = true;
config.ServerSSHAllowed = false;
environment = rec {
NB_MANAGEMENT_URL = "https://${globals.services.netbird.domain}";
NB_ADMIN_URL = NB_MANAGEMENT_URL;
};
};
environment.persistence."/persist".directories = [
{
directory = "/var/lib/netbird-home";
mode = "0700";
}
];
programs.nix-ld.enable = true;
topology.self.icon = "devices.desktop";
@ -122,9 +99,4 @@
dockerCompat = true;
defaultNetwork.settings.dns_enabled = true;
};
services.firezone.server.domain.enable = true;
services.firezone.server.domain.enableLocalDB = true;
services.firezone.server.web.enable = true;
services.firezone.server.api.enable = true;
}

View file

@ -8,9 +8,6 @@
./backups.nix
./deterministic-ids.nix
./distributed-config.nix
./firezone-relay.nix
./firezone-gateways.nix
./firezone-server.nix
./globals.nix
./meta.nix
./netbird-client.nix

View file

@ -1,170 +0,0 @@
{
lib,
pkgs,
config,
...
}:
let
inherit (lib)
boolToString
concatMapAttrs
flip
getExe
mkEnableOption
mkOption
mkPackageOption
types
;
in
{
options = {
services.firezone.gateways = mkOption {
description = ''
A set of gateway clients to deploy on this machine. Each gateway can
connect to exactly one firezone server.
'';
default = { };
type = types.attrsOf (
types.submodule (gatewaysSubmod: {
options = {
package = mkPackageOption pkgs "firezone-gateway" { };
name = mkOption {
type = types.str;
default = gatewaysSubmod.config._module.args.name;
description = "The name of this gateway as shown in firezone";
};
user = mkOption {
type = types.strMatching "^[a-zA-Z0-9_-]{1,32}$";
default = "firezone-gw-${gatewaysSubmod.config._module.args.name}";
description = "The DynamicUser name under which the gateway will run. Cannot exceed 32 characters.";
};
interface = mkOption {
type = types.strMatching "^[a-zA-Z0-9_-]{1,15}$";
default = "tun-${gatewaysSubmod.config._module.args.name}";
description = "The name of the TUN interface which will be created by this gateway";
};
apiUrl = mkOption {
type = types.str;
example = "wss://firezone.example.com/api";
description = ''
The URL of your firezone server's API. This should be the same
as your server's setting for {option}`services.firezone.server.settings.api.externalUrl`,
but with `wss://` instead of `https://`.
'';
};
tokenFile = mkOption {
type = types.path;
example = "/run/secrets/firezone-gateway-token";
description = ''
A file containing the firezone gateway token. Do not use a nix-store path here
as it will make the token publicly readable!
This file will be passed via systemd credentials, it should only be accessible
by the root user.
'';
};
logLevel = mkOption {
type = types.str;
default = "info";
description = ''
The log level for the firezone application. See
[RUST_LOG](https://docs.rs/env_logger/latest/env_logger/#enabling-logging)
for the format.
'';
};
enableTelemetry = mkEnableOption "telemetry";
};
})
);
};
};
config = {
systemd.services = flip concatMapAttrs config.services.firezone.gateways (
gatewayName: gatewayCfg: {
"firezone-gateway-${gatewayName}" = {
description = "Gateway service for the Firezone zero-trust access platform";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.util-linux ];
script = ''
# If FIREZONE_ID is not given by the user, use a persisted (or newly generated) uuid.
if [[ -z "''${FIREZONE_ID:-}" ]]; then
if [[ ! -e gateway_id ]]; then
uuidgen -r > gateway_id
fi
export FIREZONE_ID=$(< gateway_id)
fi
export FIREZONE_TOKEN=$(< "$CREDENTIALS_DIRECTORY/firezone-token")
exec ${getExe gatewayCfg.package}
'';
environment = {
FIREZONE_API_URL = gatewayCfg.apiUrl;
FIREZONE_NAME = gatewayCfg.name;
FIREZONE_NO_TELEMETRY = boolToString gatewayCfg.enableTelemetry;
FIREZONE_TUN_INTERFACE = gatewayCfg.interface;
RUST_LOG = gatewayCfg.logLevel;
};
serviceConfig = {
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateMounts = true;
PrivateTmp = true;
PrivateUsers = false;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_NETLINK"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = "@system-service";
UMask = "077";
Type = "exec";
DynamicUser = true;
User = gatewayCfg.user;
LoadCredential = [ "firezone-token:${gatewayCfg.tokenFile}" ];
DeviceAllow = "/dev/net/tun";
AmbientCapabilities = [ "CAP_NET_ADMIN" ];
CapabilityBoundingSet = [ "CAP_NET_ADMIN" ];
StateDirectory = "firezone-gateways/${gatewayName}";
WorkingDirectory = "/var/lib/firezone-gateways/${gatewayName}";
};
};
}
);
};
meta.maintainers = with lib.maintainers; [
oddlama
patrickdag
];
}

View file

@ -1,177 +0,0 @@
{
lib,
pkgs,
config,
...
}:
let
inherit (lib)
boolToString
getExe
mkEnableOption
mkIf
mkOption
mkPackageOption
types
;
cfg = config.services.firezone.relay;
in
{
options = {
services.firezone.relay = {
enable = mkEnableOption "the firezone relay server";
package = mkPackageOption pkgs "firezone-relay" { };
publicIpv4 = mkOption {
type = types.str;
description = "The public ipv4 address of this relay";
};
publicIpv6 = mkOption {
type = types.str;
description = "The public ipv6 address of this relay";
};
openFirewall = mkOption {
type = types.bool;
default = true;
description = "Opens up the main STUN port and the TURN allocation range.";
};
port = mkOption {
type = types.port;
default = 3478;
description = "The port to listen on for STUN messages";
};
lowestPort = mkOption {
type = types.port;
default = 49152;
description = "The lowest port to use in TURN allocation";
};
highestPort = mkOption {
type = types.port;
default = 65535;
description = "The highest port to use in TURN allocation";
};
apiUrl = mkOption {
type = types.str;
example = "wss://firezone.example.com/api";
description = ''
The URL of your firezone server's API. This should be the same
as your server's setting for {option}`services.firezone.server.settings.api.externalUrl`,
but with `wss://` instead of `https://`.
'';
};
tokenFile = mkOption {
type = types.path;
example = "/run/secrets/firezone-relay-token";
description = ''
A file containing the firezone relay token. Do not use a nix-store path here
as it will make the token publicly readable!
This file will be passed via systemd credentials, it should only be accessible
by the root user.
'';
};
logLevel = mkOption {
type = types.str;
default = "firezone_relay=info,firezone_tunnel=info,connlib_shared=info,tunnel_state=info,phoenix_channel=info,snownet=info,str0m=info,warn";
description = ''
The log level for the firezone application. See
[RUST_LOG](https://docs.rs/env_logger/latest/env_logger/#enabling-logging)
for the format.
'';
};
enableTelemetry = mkEnableOption "telemetry";
};
};
config = mkIf cfg.enable {
systemd.services."firezone-relay" = {
description = "relay service for the Firezone zero-trust access platform";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.util-linux ];
script = ''
# If FIREZONE_ID is not given by the user, use a persisted (or newly generated) uuid.
if [[ -z "''${FIREZONE_ID:-}" ]]; then
if [[ ! -e relay_id ]]; then
uuidgen -r > relay_id
fi
export FIREZONE_ID=$(< relay_id)
fi
export FIREZONE_TOKEN=$(< "$CREDENTIALS_DIRECTORY/firezone-token")
exec ${getExe cfg.package}
'';
environment = {
FIREZONE_API_URL = cfg.apiUrl;
FIREZONE_NAME = cfg.name;
FIREZONE_TELEMETRY = boolToString cfg.enableTelemetry;
PUBLIC_IPV4_ADDRESS = cfg.publicIpv4;
PUBLIC_IPV6_ADDRESS = cfg.publicIpv6;
LISTEN_PORT = toString cfg.port;
LOWEST_PORT = toString cfg.lowestPort;
HIGHEST_PORT = toString cfg.highestPort;
RUST_LOG = cfg.logLevel;
LOG_FORMAT = "human";
};
serviceConfig = {
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateMounts = true;
PrivateTmp = true;
PrivateUsers = false;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_NETLINK"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = "@system-service";
UMask = "077";
Type = "exec";
DynamicUser = true;
User = "firezone-relay";
LoadCredential = [ "firezone-token:${cfg.tokenFile}" ];
StateDirectory = "firezone-relay";
WorkingDirectory = "/var/lib/firezone-relay";
};
};
};
meta.maintainers = with lib.maintainers; [
oddlama
patrickdag
];
}

View file

@ -1,948 +0,0 @@
{
lib,
pkgs,
config,
...
}:
let
inherit (lib)
attrNames
boolToString
concatLines
concatLists
concatMapAttrs
concatStringsSep
filterAttrs
filterAttrsRecursive
flip
forEach
getExe
isBool
mapAttrs
mapAttrsToList
mkDefault
mkEnableOption
mkIf
mkMerge
mkOption
mkPackageOption
optionalAttrs
recursiveUpdate
subtractLists
toUpper
types
;
cfg = config.services.firezone.server;
jsonFormat = pkgs.formats.json { };
availableAuthAdapters = [
"email"
"openid_connect"
"userpass"
"token"
"google_workspace"
"microsoft_entra"
"okta"
"jumpcloud"
];
# All non-secret environment variables or the given component
collectEnvironment =
component:
mapAttrs (_: v: if isBool v then boolToString v else toString v) (
cfg.settings // cfg.${component}.settings
);
# All mandatory secrets which were not explicitly provided by the user will
# have to be generated, if they do not yet exist.
generateSecrets =
let
requiredSecrets = filterAttrs (_: v: v == null) cfg.settingsSecret;
in
''
mkdir -p secrets
chmod 700 secrets
''
+ concatLines (
forEach (attrNames requiredSecrets) (secret: ''
if [[ ! -e secrets/${secret} ]]; then
echo "Generating ${secret}"
# Some secrets like TOKENS_KEY_BASE require a value >=64 bytes.
head -c 64 /dev/urandom | base64 -w 0 > secrets/${secret}
chmod 600 secrets/${secret}
fi
'')
);
# All secrets given in `cfg.settingsSecret` must be loaded from a file and
# exported into the environment. Also exclude any variables that were
# overwritten by the local component settings.
loadSecretEnvironment =
component:
let
relevantSecrets = subtractLists (attrNames cfg.${component}.settings) (
attrNames cfg.settingsSecret
);
in
concatLines (
forEach relevantSecrets (
secret:
''export ${secret}=$(< ${
if cfg.settingsSecret.${secret} == null then
"secrets/${secret}"
else
"\"$CREDENTIALS_DIRECTORY/${secret}\""
})''
)
);
provisionStateJson =
let
# Convert clientSecretFile options into the real counterpart
augmentedAccounts = flip mapAttrs cfg.provision.accounts (
accountName: account:
account
// {
auth = flip mapAttrs account.auth (
authName: auth:
recursiveUpdate auth (
optionalAttrs (auth.adapter_config.clientSecretFile != null) {
adapter_config.client_secret = "{env:AUTH_CLIENT_SECRET_${toUpper accountName}_${toUpper authName}}";
}
)
);
}
);
in
jsonFormat.generate "provision-state.json" {
# Do not include any clientSecretFile attributes in the resulting json
accounts = filterAttrsRecursive (k: _: k != "clientSecretFile") augmentedAccounts;
};
commonServiceConfig = {
AmbientCapablities = [ ];
CapabilityBoundingSet = [ ];
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateMounts = true;
PrivateTmp = true;
PrivateUsers = false;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_NETLINK"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = "@system-service";
UMask = "077";
DynamicUser = true;
User = "firezone";
Slice = "system-firezone.slice";
StateDirectory = "firezone";
WorkingDirectory = "/var/lib/firezone";
LoadCredential = mapAttrsToList (secretName: secretFile: "${secretName}:${secretFile}") (
filterAttrs (_: v: v != null) cfg.settingsSecret
);
Type = "exec";
Restart = "on-failure";
RestartSec = 10;
};
componentOptions = component: {
enable = mkEnableOption "the Firezone ${component} server";
package = mkPackageOption pkgs "firezone-server-${component}" { };
settings = mkOption {
description = ''
Environment variables for this component of the Firezone server. For a
list of available variables, please refer to the [upstream definitions](https://github.com/firezone/firezone/blob/main/elixir/apps/domain/lib/domain/config/definitions.ex).
Some variables like `OUTBOUND_EMAIL_ADAPTER_OPTS` require json values
for which you can use `VAR = builtins.toJSON { /* ... */ }`.
This component will automatically inherit all variables defined via
{option}`services.firezone.server.settings` and
{option}`services.firezone.server.settingsSecret`, but which can be
overwritten by this option.
'';
default = { };
type = types.submodule {
freeformType = types.attrsOf (
types.oneOf [
types.bool
types.float
types.int
types.str
types.path
types.package
]
);
};
};
};
in
{
options.services.firezone.server = {
enable = mkEnableOption "all Firezone components";
enableLocalDB = mkEnableOption "a local postgresql database for Firezone";
nginx.enable = mkEnableOption "nginx virtualhost definition";
openClusterFirewall = mkOption {
type = types.bool;
default = false;
description = ''
Opens up the erlang distribution port of all enabled components to
allow reaching the server cluster from the internet. You only need to
set this if you are actually distributing your cluster across multiple
machines.
'';
};
clusterHosts = mkOption {
type = types.listOf types.str;
default = [
"api@localhost.localdomain"
"web@localhost.localdomain"
"domain@localhost.localdomain"
];
description = ''
A list of components and their hosts that are part of this cluster. For
a single-machine setup, the default value will be sufficient. This
value will automatically set `ERLANG_CLUSTER_ADAPTER_CONFIG`.
The format is `<COMPONENT_NAME>@<HOSTNAME>`.
'';
};
settingsSecret = mkOption {
default = { };
description = ''
This is a convenience option which allows you to set secret values for
environment variables by specifying a file which will contain the value
at runtime. Before starting the server, the content of each file will
be loaded into the respective environment variable.
Otherwise, this option is equivalent to
{option}`services.firezone.server.settings`. Refer to the settings
option for more information regarding the actual variables and how
filtering rules are applied for each component.
'';
type = types.submodule {
freeformType = types.attrsOf types.path;
options = {
RELEASE_COOKIE = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
A file containing a unique secret identifier for the Erlang
cluster. All Firezone components in your cluster must use the
same value.
If this is `null`, a shared value will automatically be generated
on startup and used for all components on this machine. You do
not need to set this except when you spread your cluster over
multiple hosts.
'';
};
TOKENS_KEY_BASE = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
A file containing a unique base64 encoded secret for the
`TOKENS_KEY_BASE`. All Firezone components in your cluster must
use the same value.
If this is `null`, a shared value will automatically be generated
on startup and used for all components on this machine. You do
not need to set this except when you spread your cluster over
multiple hosts.
'';
};
SECRET_KEY_BASE = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
A file containing a unique base64 encoded secret for the
`SECRET_KEY_BASE`. All Firezone components in your cluster must
use the same value.
If this is `null`, a shared value will automatically be generated
on startup and used for all components on this machine. You do
not need to set this except when you spread your cluster over
multiple hosts.
'';
};
TOKENS_SALT = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
A file containing a unique base64 encoded secret for the
`TOKENS_SALT`. All Firezone components in your cluster must
use the same value.
If this is `null`, a shared value will automatically be generated
on startup and used for all components on this machine. You do
not need to set this except when you spread your cluster over
multiple hosts.
'';
};
LIVE_VIEW_SIGNING_SALT = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
A file containing a unique base64 encoded secret for the
`LIVE_VIEW_SIGNING_SALT`. All Firezone components in your cluster must
use the same value.
If this is `null`, a shared value will automatically be generated
on startup and used for all components on this machine. You do
not need to set this except when you spread your cluster over
multiple hosts.
'';
};
COOKIE_SIGNING_SALT = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
A file containing a unique base64 encoded secret for the
`COOKIE_SIGNING_SALT`. All Firezone components in your cluster must
use the same value.
If this is `null`, a shared value will automatically be generated
on startup and used for all components on this machine. You do
not need to set this except when you spread your cluster over
multiple hosts.
'';
};
COOKIE_ENCRYPTION_SALT = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
A file containing a unique base64 encoded secret for the
`COOKIE_ENCRYPTION_SALT`. All Firezone components in your cluster must
use the same value.
If this is `null`, a shared value will automatically be generated
on startup and used for all components on this machine. You do
not need to set this except when you spread your cluster over
multiple hosts.
'';
};
};
};
};
settings = mkOption {
description = ''
Environment variables for the Firezone server. For a list of available
variables, please refer to the [upstream definitions](https://github.com/firezone/firezone/blob/main/elixir/apps/domain/lib/domain/config/definitions.ex).
Some variables like `OUTBOUND_EMAIL_ADAPTER_OPTS` require json values
for which you can use `VAR = builtins.toJSON { /* ... */ }`.
Each component has an additional `settings` option which allows you to
override specific variables passed to that component.
'';
default = { };
type = types.submodule {
freeformType = types.attrsOf (
types.oneOf [
types.bool
types.float
types.int
types.str
types.path
types.package
]
);
};
};
smtp = {
configureManually = mkOption {
type = types.bool;
default = false;
description = ''
Outbound email configuration is mandatory for Firezone and supports
many different delivery adapters. Yet, most users will only need an
SMTP relay to send emails, so this configuration enforced by default.
If you want to utilize an alternative way to send emails (e.g. via a
supportd API-based service), enable this option and define
`OUTBOUND_EMAIL_FROM`, `OUTBOUND_EMAIL_ADAPTER` and
`OUTBOUND_EMAIL_ADAPTER_OPTS` manually via
{option}`services.firezone.server.settings` and/or
{option}`services.firezone.server.settingsSecret`.
The Firezone documentation holds [a list of supported Swoosh adapters](https://github.com/firezone/firezone/blob/main/website/src/app/docs/reference/env-vars/readme.mdx#outbound-emails).
'';
};
from = mkOption {
type = types.str;
example = "firezone@example.com";
description = "Outbound SMTP FROM address";
};
host = mkOption {
type = types.str;
example = "mail.example.com";
description = "Outbound SMTP host";
};
port = mkOption {
type = types.port;
example = 465;
description = "Outbound SMTP port";
};
implicitTls = mkOption {
type = types.bool;
default = false;
description = "Whether to use implicit TLS instead of STARTTLS (usually port 465)";
};
username = mkOption {
type = types.str;
example = "firezone@example.com";
description = "Username to authenticate against the SMTP relay";
};
passwordFile = mkOption {
type = types.path;
example = "/run/secrets/smtp-password";
description = "File containing the password for the given username. Beware that a file in the nix store will be world readable.";
};
};
domain = componentOptions "domain";
web = componentOptions "web" // {
externalUrl = mkOption {
type = types.strMatching "^https://.+/$";
example = "https://firezone.example.com/";
description = ''
The external URL under which you will serve the web interface. You
need to setup a reverse proxy for TLS termination, either with
{option}`services.firezone.server.nginx.enable` or manually.
'';
};
address = mkOption {
type = types.str;
default = "127.0.0.1";
description = "The address to listen on";
};
port = mkOption {
type = types.port;
default = 8080;
description = "The port under which the web interface will be served locally";
};
trustedProxies = mkOption {
type = types.listOf types.str;
default = [ ];
description = "A list of trusted proxies";
};
};
api = componentOptions "api" // {
externalUrl = mkOption {
type = types.strMatching "^https://.+/$";
example = "https://firezone.example.com/api/";
description = ''
The external URL under which you will serve the api. You need to
setup a reverse proxy for TLS termination, either with
{option}`services.firezone.server.nginx.enable` or manually.
'';
};
address = mkOption {
type = types.str;
default = "127.0.0.1";
description = "The address to listen on";
};
port = mkOption {
type = types.port;
default = 8081;
description = "The port under which the api will be served locally";
};
trustedProxies = mkOption {
type = types.listOf types.str;
default = [ ];
description = "A list of trusted proxies";
};
};
provision = {
enable = mkEnableOption "provisioning of the Firezone domain server";
accounts = mkOption {
type = types.attrsOf (
types.submodule {
freeformType = jsonFormat.type;
options = {
name = mkOption {
type = types.str;
description = "The account name";
example = "My Organization";
};
features =
let
mkFeatureOption =
name: default:
mkOption {
type = types.bool;
inherit default;
description = "Whether to enable the `${name}` feature for this account.";
};
in
{
flow_activities = mkFeatureOption "flow_activities" true;
policy_conditions = mkFeatureOption "policy_conditions" true;
multi_site_resources = mkFeatureOption "multi_site_resources" true;
traffic_filters = mkFeatureOption "traffic_filters" true;
self_hosted_relays = mkFeatureOption "self_hosted_relays" true;
idp_sync = mkFeatureOption "idp_sync" true;
rest_api = mkFeatureOption "rest_api" true;
internet_resource = mkFeatureOption "internet_resource" true;
};
actors = mkOption {
type = types.attrsOf (
types.submodule {
freeformType = jsonFormat.type;
options = {
type = mkOption {
type = types.enum [
"account_admin_user"
"account_user"
"service_account"
"api_client"
];
description = "The account type";
};
email = mkOption {
type = types.str;
description = "The email address used to authenticate as this account";
};
};
}
);
default = { };
example = {
admin = {
type = "account_admin_user";
email = "admin@myorg.example.com";
};
};
description = "All actors (users) to provision.";
};
auth = mkOption {
type = types.attrsOf (
types.submodule {
freeformType = jsonFormat.type;
options = {
adapter = mkOption {
type = types.enum availableAuthAdapters;
description = "The auth adapter type";
};
adapter_config.clientSecretFile = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
A file containing a the client secret for an openid_connect adapter.
You only need to set this if this is an openid_connect provider.
'';
};
};
}
);
default = { };
example = {
myoidcprovider = {
adapter = "openid_connect";
adapter_config = {
client_id = "clientid";
clientSecretFile = "/run/secrets/oidc-client-secret";
response_type = "code";
scope = "openid email name";
discorvery_document_uri = "https://auth.example.com/.well-known/openid-configuration";
};
};
};
description = "All authentication providers to provision.";
};
};
}
);
default = { };
example = {
main = {
name = "My Account / Organization";
metadata.stripe.billing_email = "org@myorg.example.com";
features.rest_api = false;
};
};
description = ''
All accounts to provision. The key specified here will become the
account slug. By using `"{file:/path/to/file}"` as a string value
anywhere in these settings, the provisioning script will replace that
value with the content of the given file at runtime.
Please refer to the [Firezone source code](https://github.com/firezone/firezone/blob/main/elixir/apps/domain/lib/domain/accounts/account.ex)
for all available properties.
'';
};
};
};
config = mkMerge [
{
assertions =
[
{
assertion = cfg.provision.enable -> cfg.domain.enable;
message = "Provisioning must be done on a machine running the firezone domain server";
}
]
++ concatLists (
flip mapAttrsToList cfg.provision.accounts (
accountName: accountCfg:
[
{
assertion = (builtins.match "^[[:lower:]_-]+$" accountName) != null;
message = "An account name must contain only lowercase characters and underscores, as it will be used as the URL slug for this account.";
}
]
++ flip mapAttrsToList accountCfg.auth (
authName: _: {
assertion = (builtins.match "^[[:alnum:]_-]+$" authName) != null;
message = "An authentication provider name must contain only letters, numbers, underscores or dashes.";
}
)
)
);
}
# Enable all components if the main server is enabled
(mkIf cfg.enable {
services.firezone.server.domain.enable = true;
services.firezone.server.web.enable = true;
services.firezone.server.api.enable = true;
})
# Create (and configure) a local database if desired
(mkIf cfg.enableLocalDB {
services.postgresql = {
enable = true;
ensureUsers = [
{
name = "firezone";
ensureDBOwnership = true;
}
];
ensureDatabases = [ "firezone" ];
};
services.firezone.server.settings = {
DATABASE_SOCKET_DIR = "/run/postgresql";
DATABASE_PORT = "5432";
DATABASE_NAME = "firezone";
DATABASE_USER = "firezone";
};
})
# Create a local nginx reverse proxy
(mkIf cfg.nginx.enable {
services.nginx = mkMerge [
{
enable = true;
}
(
let
urlComponents = builtins.elemAt (builtins.split "https://([^/]*)(/?.*)" cfg.web.externalUrl) 1;
domain = builtins.elemAt urlComponents 0;
location = builtins.elemAt urlComponents 1;
in
{
virtualHosts.${domain} = {
forceSSL = mkDefault true;
locations.${location} = {
# The trailing slash is important to strip the location prefix from the request
proxyPass = "http://${cfg.web.address}:${toString cfg.web.port}/";
proxyWebsockets = true;
};
};
}
)
(
let
urlComponents = builtins.elemAt (builtins.split "https://([^/]*)(/?.*)" cfg.api.externalUrl) 1;
domain = builtins.elemAt urlComponents 0;
location = builtins.elemAt urlComponents 1;
in
{
virtualHosts.${domain} = {
forceSSL = mkDefault true;
locations.${location} = {
# The trailing slash is important to strip the location prefix from the request
proxyPass = "http://${cfg.api.address}:${toString cfg.api.port}/";
proxyWebsockets = true;
};
};
}
)
];
})
# Specify sensible defaults
{
services.firezone.server = {
settings = {
LOG_LEVEL = mkDefault "info";
RELEASE_HOSTNAME = mkDefault "localhost.localdomain";
ERLANG_CLUSTER_ADAPTER = mkDefault "Elixir.Cluster.Strategy.Epmd";
ERLANG_CLUSTER_ADAPTER_CONFIG = mkDefault (
builtins.toJSON {
hosts = cfg.clusterHosts;
}
);
TZDATA_DIR = mkDefault "/var/lib/firezone/tzdata";
TELEMETRY_ENABLED = mkDefault false;
# By default this will open nproc * 2 connections for each component,
# which can exceeds the (default) maximum of 100 connections for
# postgresql on a 12 core +SMT machine. 16 connections will be
# sufficient for small to medium deployments
DATABASE_POOL_SIZE = "16";
AUTH_PROVIDER_ADAPTERS = mkDefault (concatStringsSep "," availableAuthAdapters);
FEATURE_FLOW_ACTIVITIES_ENABLED = mkDefault true;
FEATURE_POLICY_CONDITIONS_ENABLED = mkDefault true;
FEATURE_MULTI_SITE_RESOURCES_ENABLED = mkDefault true;
FEATURE_SELF_HOSTED_RELAYS_ENABLED = mkDefault true;
FEATURE_IDP_SYNC_ENABLED = mkDefault true;
FEATURE_REST_API_ENABLED = mkDefault true;
FEATURE_INTERNET_RESOURCE_ENABLED = mkDefault true;
FEATURE_TRAFFIC_FILTERS_ENABLED = mkDefault true;
FEATURE_SIGN_UP_ENABLED = mkDefault true;
};
domain.settings = {
ERLANG_DISTRIBUTION_PORT = mkDefault 9000;
HEALTHZ_PORT = mkDefault 4000;
BACKGROUND_JOBS_ENABLED = mkDefault true;
};
web.settings = {
ERLANG_DISTRIBUTION_PORT = mkDefault 9001;
HEALTHZ_PORT = mkDefault 4001;
BACKGROUND_JOBS_ENABLED = mkDefault false;
PHOENIX_LISTEN_ADDRESS = mkDefault cfg.web.address;
PHOENIX_EXTERNAL_TRUSTED_PROXIES = mkDefault (builtins.toJSON cfg.web.trustedProxies);
PHOENIX_HTTP_WEB_PORT = mkDefault cfg.web.port;
PHOENIX_HTTP_API_PORT = mkDefault cfg.api.port;
PHOENIX_SECURE_COOKIES = mkDefault true; # enforce HTTPS on cookies
WEB_EXTERNAL_URL = mkDefault cfg.web.externalUrl;
API_EXTERNAL_URL = mkDefault cfg.api.externalUrl;
};
api.settings = {
ERLANG_DISTRIBUTION_PORT = mkDefault 9002;
HEALTHZ_PORT = mkDefault 4002;
BACKGROUND_JOBS_ENABLED = mkDefault false;
PHOENIX_LISTEN_ADDRESS = mkDefault cfg.api.address;
PHOENIX_EXTERNAL_TRUSTED_PROXIES = mkDefault (builtins.toJSON cfg.api.trustedProxies);
PHOENIX_HTTP_WEB_PORT = mkDefault cfg.web.port;
PHOENIX_HTTP_API_PORT = mkDefault cfg.api.port;
PHOENIX_SECURE_COOKIES = mkDefault true; # enforce HTTPS on cookies
WEB_EXTERNAL_URL = mkDefault cfg.web.externalUrl;
API_EXTERNAL_URL = mkDefault cfg.api.externalUrl;
};
};
}
(mkIf (!cfg.smtp.configureManually) {
services.firezone.server.settings = {
OUTBOUND_EMAIL_ADAPTER = "Elixir.Swoosh.Adapters.Mua";
OUTBOUND_EMAIL_ADAPTER_OPTS = builtins.toJSON { };
OUTBOUND_EMAIL_FROM = cfg.smtp.from;
OUTBOUND_EMAIL_SMTP_HOST = cfg.smtp.host;
OUTBOUND_EMAIL_SMTP_PORT = toString cfg.smtp.port;
OUTBOUND_EMAIL_SMTP_PROTOCOL = if cfg.smtp.implicitTls then "ssl" else "tcp";
OUTBOUND_EMAIL_SMTP_USERNAME = cfg.smtp.username;
};
services.firezone.server.settingsSecret = {
OUTBOUND_EMAIL_SMTP_PASSWORD = cfg.smtp.passwordFile;
};
})
(mkIf cfg.provision.enable {
# Load client secrets from authentication providers
services.firezone.server.settingsSecret = flip concatMapAttrs cfg.provision.accounts (
accountName: accountCfg:
flip concatMapAttrs accountCfg.auth (
authName: authCfg:
optionalAttrs (authCfg.adapter_config.clientSecretFile != null) {
"AUTH_CLIENT_SECRET_${toUpper accountName}_${toUpper authName}" =
authCfg.adapter_config.clientSecretFile;
}
)
);
})
(mkIf (cfg.openClusterFirewall && cfg.domain.enable) {
networking.firewall.allowedTCPPorts = [
cfg.domain.settings.ERLANG_DISTRIBUTION_PORT
];
})
(mkIf (cfg.openClusterFirewall && cfg.web.enable) {
networking.firewall.allowedTCPPorts = [
cfg.web.settings.ERLANG_DISTRIBUTION_PORT
];
})
(mkIf (cfg.openClusterFirewall && cfg.api.enable) {
networking.firewall.allowedTCPPorts = [
cfg.api.settings.ERLANG_DISTRIBUTION_PORT
];
})
(mkIf (cfg.domain.enable || cfg.web.enable || cfg.api.enable) {
systemd.slices.system-firezone = {
description = "Firezone Slice";
};
systemd.targets.firezone = {
description = "Common target for all Firezone services.";
wantedBy = [ "multi-user.target" ];
};
systemd.services.firezone-initialize = {
description = "Backend initialization service for the Firezone zero-trust access platform";
after = mkIf cfg.enableLocalDB [ "postgresql.service" ];
requires = mkIf cfg.enableLocalDB [ "postgresql.service" ];
wantedBy = [ "firezone.target" ];
partOf = [ "firezone.target" ];
script = ''
mkdir -p "$TZDATA_DIR"
# Generate and load secrets
${generateSecrets}
${loadSecretEnvironment "domain"}
echo "Running migrations"
${getExe cfg.domain.package} eval Domain.Release.migrate
'';
# We use the domain environment to be able to run migrations
environment = collectEnvironment "domain";
serviceConfig = commonServiceConfig // {
Type = "oneshot";
RemainAfterExit = true;
};
};
systemd.services.firezone-server-domain = mkIf cfg.domain.enable {
description = "Backend domain server for the Firezone zero-trust access platform";
after = [ "firezone-initialize.service" ];
bindsTo = [ "firezone-initialize.service" ];
wantedBy = [ "firezone.target" ];
partOf = [ "firezone.target" ];
script = ''
${loadSecretEnvironment "domain"}
exec ${getExe cfg.domain.package} start;
'';
path = [ pkgs.curl ];
postStart = mkIf cfg.provision.enable ''
${loadSecretEnvironment "domain"}
# Wait for the firezone server to come online
count=0
while [[ "$(curl -s "http://localhost:${toString cfg.domain.settings.HEALTHZ_PORT}" 2>/dev/null || echo)" != '{"status":"ok"}' ]]
do
sleep 1
if [[ "$count" -eq 30 ]]; then
echo "Tried for at least 30 seconds, giving up..."
exit 1
fi
count=$((count++))
done
sleep 1 # Wait for server to fully come up. Not ideal to use sleep, but at least it works.
ln -sTf ${provisionStateJson} provision-state.json
${getExe cfg.domain.package} rpc 'Code.eval_file("${./provision.exs}")'
'';
environment = collectEnvironment "domain";
serviceConfig = commonServiceConfig;
};
systemd.services.firezone-server-web = mkIf cfg.web.enable {
description = "Backend web server for the Firezone zero-trust access platform";
after = [ "firezone-initialize.service" ];
bindsTo = [ "firezone-initialize.service" ];
wantedBy = [ "firezone.target" ];
partOf = [ "firezone.target" ];
script = ''
${loadSecretEnvironment "web"}
exec ${getExe cfg.web.package} start;
'';
environment = collectEnvironment "web";
serviceConfig = commonServiceConfig;
};
systemd.services.firezone-server-api = mkIf cfg.api.enable {
description = "Backend api server for the Firezone zero-trust access platform";
after = [ "firezone-initialize.service" ];
bindsTo = [ "firezone-initialize.service" ];
wantedBy = [ "firezone.target" ];
partOf = [ "firezone.target" ];
script = ''
${loadSecretEnvironment "api"}
exec ${getExe cfg.api.package} start;
'';
environment = collectEnvironment "api";
serviceConfig = commonServiceConfig;
};
})
];
meta.maintainers = with lib.maintainers; [
oddlama
patrickdag
];
}

View file

@ -1,155 +0,0 @@
defmodule Provision do
alias Domain.{Repo, Accounts, Auth, Actors}
require Logger
defp resolve_references(value) when is_map(value) do
Enum.into(value, %{}, fn {k, v} -> {k, resolve_references(v)} end)
end
defp resolve_references(value) when is_list(value) do
Enum.map(value, &resolve_references/1)
end
defp resolve_references(value) when is_binary(value) do
Regex.replace(~r/\{env:([^}]+)\}/, value, fn _, var ->
System.get_env(var) || raise "Environment variable #{var} not set"
end)
end
defp resolve_references(value), do: value
defp atomize_keys(map) when is_map(map) do
Enum.into(map, %{}, fn {k, v} ->
{
if(is_binary(k), do: String.to_atom(k), else: k),
if(is_map(v), do: atomize_keys(v), else: v)
}
end)
end
def provision() do
IO.inspect("Starting provisioning", label: "INFO")
json_file = "provision-state.json"
{:ok, raw_json} = File.read(json_file)
{:ok, %{"accounts" => accounts}} = Jason.decode(raw_json)
accounts = resolve_references(accounts)
multi = Enum.reduce(accounts, Ecto.Multi.new(), fn {slug, account_data}, multi ->
account_attrs = atomize_keys(%{
name: account_data["name"],
slug: slug,
features: Map.get(account_data, "features", %{}),
metadata: Map.get(account_data, "metadata", %{}),
limits: Map.get(account_data, "limits", %{}),
})
multi = multi
|> Ecto.Multi.run({:account, slug}, fn repo, _changes ->
case Accounts.fetch_account_by_id_or_slug(slug) do
{:ok, acc} ->
IO.inspect("Updating existing account #{slug}", label: "INFO")
updated_acc = acc |> Ecto.Changeset.change(account_attrs) |> repo.update!()
{:ok, {:existing, updated_acc}}
_ ->
IO.inspect("Creating new account #{slug}", label: "INFO")
{:ok, account} = Accounts.create_account(account_attrs)
{:ok, {:new, account}}
end
end)
|> Ecto.Multi.run({:everyone_group, slug}, fn _repo, changes ->
case Map.get(changes, {:account, slug}) do
{:new, account} ->
IO.inspect("Creating Everyone group for new account", label: "INFO")
Actors.create_managed_group(account, %{name: "Everyone", membership_rules: [%{operator: true}]})
{:existing, _account} ->
{:ok, :skipped}
end
end)
|> Ecto.Multi.run({:provider, slug}, fn _repo, changes ->
case Map.get(changes, {:account, slug}) do
{:new, account} ->
IO.inspect("Creating default email provider for new account", label: "INFO")
Auth.create_provider(account, %{name: "Email", adapter: :email, adapter_config: %{}})
{:existing, account} ->
Auth.Provider.Query.not_disabled()
|> Auth.Provider.Query.by_adapter(:email)
|> Auth.Provider.Query.by_account_id(account.id)
|> Repo.fetch(Auth.Provider.Query, [])
end
end)
multi = Enum.reduce(account_data["actors"] || %{}, multi, fn {name, actor_data}, multi ->
actor_attrs = atomize_keys(%{
name: name,
type: String.to_atom(actor_data["type"]),
})
Ecto.Multi.run(multi, {:actor, slug, name}, fn repo, changes ->
{_, account} = changes[{:account, slug}]
case Repo.get_by(Actors.Actor, account_id: account.id, name: name) do
nil ->
IO.inspect("Creating new actor #{name}", label: "INFO")
{:ok, actor} = Actors.create_actor(account, actor_attrs)
{:ok, {:new, actor}}
act ->
IO.inspect("Updating existing actor #{name}", label: "INFO")
updated_act = act |> Ecto.Changeset.change(actor_attrs) |> repo.update!()
{:ok, {:existing, updated_act}}
end
end)
|> Ecto.Multi.run({:actor_identity, slug, name}, fn repo, changes ->
email_provider = changes[{:provider, slug}]
case Map.get(changes, {:actor, slug, name}) do
{:new, actor} ->
IO.inspect("Creating actor email identity", label: "INFO")
Auth.create_identity(actor, email_provider, %{
provider_identifier: actor_data["email"],
provider_identifier_confirmation: actor_data["email"]
})
{:existing, actor} ->
IO.inspect("Updating actor email identity", label: "INFO")
{:ok, identity} = Auth.Identity.Query.not_deleted()
|> Auth.Identity.Query.by_actor_id(actor.id)
|> Auth.Identity.Query.by_provider_id(email_provider.id)
|> Repo.fetch(Auth.Identity.Query, [])
{:ok, identity |> Ecto.Changeset.change(%{
provider_identifier: actor_data["email"],
}) |> repo.update!()}
end
end)
end)
multi = Enum.reduce(account_data["auth"] || %{}, multi, fn {name, provider_data}, multi ->
provider_attrs = %{
name: name,
adapter: String.to_atom(provider_data["adapter"]),
adapter_config: provider_data["adapter_config"],
}
Ecto.Multi.run(multi, {:provider, slug, name}, fn repo, changes ->
{_, account} = changes[{:account, slug}]
case Repo.get_by(Auth.Provider, account_id: account.id, name: name) do
nil ->
IO.inspect("Creating new provider #{name}", label: "INFO")
Auth.create_provider(account, provider_attrs)
existing ->
IO.inspect("Updating existing provider #{name}", label: "INFO")
{:ok, existing |> Ecto.Changeset.change(provider_attrs) |> repo.update!()}
end
end)
end)
multi
end)
case Repo.transaction(multi) do
{:ok, _result} ->
Logger.info("Provisioning completed successfully.")
{:error, step, reason, _changes} ->
Logger.error("Provisioning failed at step #{inspect(step)}: #{inspect(reason)}")
end
end
end
Provision.provision()

View file

@ -19,13 +19,6 @@ _inputs: [
# (_pythonFinal: pythonPrev: {
# })
# ];
firezone-server-domain = prev.callPackage ./firezone-server-domain/package.nix { };
firezone-server-web = prev.callPackage ./firezone-server-web/package.nix { };
firezone-server-api = prev.callPackage ./firezone-server-api/package.nix { };
firezone-gateway = prev.callPackage ./firezone-gateway/package.nix { };
firezone-relay = prev.callPackage ./firezone-relay/package.nix { };
firezone-gui-client = prev.callPackage ./firezone-gui-client/package.nix { };
firezone-headless-client = prev.callPackage ./firezone-headless-client/package.nix { };
mdns-repeater = prev.callPackage ./mdns-repeater.nix { };

View file

@ -1,39 +0,0 @@
{
lib,
rustPlatform,
fetchFromGitHub,
nix-update-script,
}:
rustPlatform.buildRustPackage rec {
pname = "firezone-gateway";
version = "1.4.2";
src = fetchFromGitHub {
owner = "oddlama";
repo = "firezone";
rev = "feat/configurable-gateway-interface";
hash = "sha256-dsGQubRs/ZHdAd6MHpa+3K4vGm4OnCdf1yMlGG1AIJk=";
};
useFetchCargoVendor = true;
cargoHash = "sha256-odrexu5mBGmvHNF4cUu9PDIEdxZC451lWV7dsLYT/Sg=";
sourceRoot = "${src.name}/rust";
buildAndTestSubdir = "gateway";
# Required to remove profiling arguments which conflict with this builder
postPatch = ''
rm .cargo/config.toml
'';
passthru.updateScript = nix-update-script { };
meta = {
description = "WireGuard tunnel server for the Firezone zero-trust access platform";
homepage = "https://github.com/firezone/firezone";
license = lib.licenses.asl20;
maintainers = with lib.maintainers; [
oddlama
patrickdag
];
mainProgram = "firezone-gateway";
};
}

View file

@ -1,281 +0,0 @@
diff --git a/rust/Cargo.lock b/rust/Cargo.lock
index 9645ee069..558339b1f 100644
--- a/rust/Cargo.lock
+++ b/rust/Cargo.lock
@@ -951,7 +951,7 @@ dependencies = [
"cocoa-foundation 0.1.2",
"core-foundation 0.9.4",
"core-graphics 0.23.2",
- "foreign-types",
+ "foreign-types 0.5.0",
"libc",
"objc",
]
@@ -967,7 +967,7 @@ dependencies = [
"cocoa-foundation 0.2.0",
"core-foundation 0.10.0",
"core-graphics 0.24.0",
- "foreign-types",
+ "foreign-types 0.5.0",
"libc",
"objc",
]
@@ -1173,7 +1173,7 @@ dependencies = [
"bitflags 1.3.2",
"core-foundation 0.9.4",
"core-graphics-types 0.1.3",
- "foreign-types",
+ "foreign-types 0.5.0",
"libc",
]
@@ -1186,7 +1186,7 @@ dependencies = [
"bitflags 2.6.0",
"core-foundation 0.10.0",
"core-graphics-types 0.2.0",
- "foreign-types",
+ "foreign-types 0.5.0",
"libc",
]
@@ -1999,6 +1999,7 @@ dependencies = [
"firezone-logging",
"firezone-telemetry",
"futures",
+ "hyper-tls",
"native-dialog",
"nix 0.29.0",
"rand 0.8.5",
@@ -2281,6 +2282,15 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2"
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared 0.1.1",
+]
+
[[package]]
name = "foreign-types"
version = "0.5.0"
@@ -2288,7 +2298,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
dependencies = [
"foreign-types-macros",
- "foreign-types-shared",
+ "foreign-types-shared 0.3.1",
]
[[package]]
@@ -2302,6 +2312,12 @@ dependencies = [
"syn 2.0.87",
]
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
[[package]]
name = "foreign-types-shared"
version = "0.3.1"
@@ -3035,6 +3051,22 @@ dependencies = [
"tower-service",
]
+[[package]]
+name = "hyper-tls"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
+dependencies = [
+ "bytes",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "native-tls",
+ "tokio",
+ "tokio-native-tls",
+ "tower-service",
+]
+
[[package]]
name = "hyper-util"
version = "0.1.9"
@@ -3723,6 +3755,23 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "native-tls"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
+dependencies = [
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
[[package]]
name = "ndk"
version = "0.9.0"
@@ -4286,6 +4335,50 @@ dependencies = [
"pathdiff",
]
+[[package]]
+name = "openssl"
+version = "0.10.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
+dependencies = [
+ "bitflags 2.6.0",
+ "cfg-if",
+ "foreign-types 0.3.2",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
[[package]]
name = "opentelemetry"
version = "0.26.0"
@@ -5385,6 +5478,15 @@ dependencies = [
"winapi-util",
]
+[[package]]
+name = "schannel"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
[[package]]
name = "schemars"
version = "0.8.21"
@@ -5474,6 +5576,29 @@ dependencies = [
"zbus",
]
+[[package]]
+name = "security-framework"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
+dependencies = [
+ "bitflags 2.6.0",
+ "core-foundation 0.9.4",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
[[package]]
name = "selectors"
version = "0.22.0"
@@ -5947,7 +6072,7 @@ dependencies = [
"bytemuck",
"cfg_aliases",
"core-graphics 0.24.0",
- "foreign-types",
+ "foreign-types 0.5.0",
"js-sys",
"log",
"objc2",
@@ -6786,6 +6911,16 @@ dependencies = [
"syn 2.0.87",
]
+[[package]]
+name = "tokio-native-tls"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
+dependencies = [
+ "native-tls",
+ "tokio",
+]
+
[[package]]
name = "tokio-rustls"
version = "0.26.0"
@@ -7395,6 +7530,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
[[package]]
name = "version-compare"
version = "0.2.0"
diff --git a/rust/gui-client/src-tauri/Cargo.toml b/rust/gui-client/src-tauri/Cargo.toml
index 32ca56197..f0475e6bf 100644
--- a/rust/gui-client/src-tauri/Cargo.toml
+++ b/rust/gui-client/src-tauri/Cargo.toml
@@ -25,6 +25,7 @@ firezone-headless-client = { workspace = true }
firezone-logging = { workspace = true }
firezone-telemetry = { workspace = true }
futures = { workspace = true }
+hyper-tls = "0.6.0"
native-dialog = { workspace = true }
rand = { workspace = true }
rustls = { workspace = true }

View file

@ -1,153 +0,0 @@
{
lib,
rustPlatform,
fetchFromGitHub,
nix-update-script,
pkg-config,
dbus,
openssl,
zenity,
cargo-tauri,
gdk-pixbuf,
glib,
gobject-introspection,
gtk3,
kdialog,
libsoup_3,
libayatana-appindicator,
webkitgtk_4_1,
wrapGAppsHook3,
stdenvNoCC,
git,
pnpm_9,
nodejs,
}:
let
version = "1.4.0";
src = fetchFromGitHub {
owner = "firezone";
repo = "firezone";
tag = "gui-client-${version}";
hash = "sha256-gwlkHTFVd1gHwOOIar+5+LEP2ovoyEIMmLfPxYb/OCs=";
# FIXME: remove once https://github.com/firezone/firezone/pull/7808 is released
# FIXME: somehow still required...
postFetch = ''
${lib.getExe git} -C $out apply ${./0000-add-hyper-tls-dependency.patch}
'';
};
frontend = stdenvNoCC.mkDerivation rec {
pname = "firezone-gui-client-frontend";
inherit version src;
pnpmDeps = pnpm_9.fetchDeps {
inherit pname version;
src = "${src}/rust/gui-client";
hash = "sha256-Gc+X4mbpHa8b/Ol6Fee7VFpTPG25LksoAEjFww1WH5I=";
};
pnpmRoot = "rust/gui-client";
nativeBuildInputs = [
pnpm_9.configHook
nodejs
];
buildPhase = ''
runHook preBuild
cd $pnpmRoot
cp node_modules/flowbite/dist/flowbite.min.js src/
pnpm tailwindcss -i src/input.css -o src/output.css
node --max_old_space_size=1024000 ./node_modules/vite/bin/vite.js build
runHook postBuild
'';
installPhase = ''
runHook preInstall
cp -r dist $out
runHook postInstall
'';
};
in
rustPlatform.buildRustPackage rec {
pname = "firezone-gui-client";
inherit version src;
useFetchCargoVendor = true;
cargoHash = "sha256-eqkYwP14kca7Q/P9wc+CvTBmKETTmbd06EBNx3MKLro=";
sourceRoot = "${src.name}/rust";
buildAndTestSubdir = "gui-client";
nativeBuildInputs = [
cargo-tauri.hook
pkg-config
wrapGAppsHook3
];
buildInputs = [
openssl
dbus
gdk-pixbuf
glib
gobject-introspection
gtk3
libsoup_3
libayatana-appindicator
webkitgtk_4_1
];
# Required to remove profiling arguments which conflict with this builder
postPatch = ''
rm .cargo/config.toml
ln -s ${frontend} gui-client/dist
'';
# Required to run tests
preCheck = ''
export XDG_RUNTIME_DIR=$(mktemp -d)
'';
checkFlags = [
# Requires network access
"--skip=notifiers"
];
preFixup = ''
gappsWrapperArgs+=(
# Otherwise blank screen, see https://github.com/tauri-apps/tauri/issues/9304
--set WEBKIT_DISABLE_DMABUF_RENDERER 1
--prefix PATH ":" ${
lib.makeBinPath [
zenity
kdialog
]
}
--prefix LD_LIBRARY_PATH ":" ${
lib.makeLibraryPath [
libayatana-appindicator
]
}
)
'';
passthru = {
inherit frontend;
updateScript = nix-update-script { };
};
meta = {
description = "GUI client for the Firezone zero-trust access platform";
homepage = "https://github.com/firezone/firezone";
license = lib.licenses.asl20;
maintainers = with lib.maintainers; [
oddlama
patrickdag
];
mainProgram = "firezone-gui-client";
};
}

View file

@ -1,44 +0,0 @@
{
lib,
rustPlatform,
fetchFromGitHub,
nix-update-script,
}:
rustPlatform.buildRustPackage rec {
pname = "firezone-headless-client";
version = "1.4.0";
src = fetchFromGitHub {
owner = "oddlama";
repo = "firezone";
rev = "feat/configurable-gateway-interface";
hash = "sha256-dsGQubRs/ZHdAd6MHpa+3K4vGm4OnCdf1yMlGG1AIJk=";
};
useFetchCargoVendor = true;
cargoHash = "sha256-odrexu5mBGmvHNF4cUu9PDIEdxZC451lWV7dsLYT/Sg=";
sourceRoot = "${src.name}/rust";
buildAndTestSubdir = "headless-client";
# Required to remove profiling arguments which conflict with this builder
postPatch = ''
rm .cargo/config.toml
'';
# Required to run tests
preCheck = ''
export XDG_RUNTIME_DIR=$(mktemp -d)
'';
passthru.updateScript = nix-update-script { };
meta = {
description = "CLI client for the Firezone zero-trust access platform";
homepage = "https://github.com/firezone/firezone";
license = lib.licenses.asl20;
maintainers = with lib.maintainers; [
oddlama
patrickdag
];
mainProgram = "firezone-headless-client";
};
}

View file

@ -1,39 +0,0 @@
{
lib,
rustPlatform,
fetchFromGitHub,
nix-update-script,
}:
rustPlatform.buildRustPackage rec {
pname = "firezone-relay";
version = "unstable-2025-01-19";
src = fetchFromGitHub {
owner = "firezone";
repo = "firezone";
rev = "8c9427b7b133e5050be34c2ac0e831c12c08f02c";
hash = "sha256-yccplADHRJQQiKrmHcJ5rvouswHrbx4K6ysnIAoZJR0=";
};
useFetchCargoVendor = true;
cargoHash = "sha256-WpJL5ALFMvyYv3QI5gMazAj6BVr4oyGq+zOo40rxqOE=";
sourceRoot = "${src.name}/rust";
buildAndTestSubdir = "relay";
# Required to remove profiling arguments which conflict with this builder
postPatch = ''
rm .cargo/config.toml
'';
passthru.updateScript = nix-update-script { };
meta = {
description = "STUN/TURN server for the Firezone zero-trust access platform";
homepage = "https://github.com/firezone/firezone";
license = lib.licenses.asl20;
maintainers = with lib.maintainers; [
oddlama
patrickdag
];
mainProgram = "firezone-relay";
};
}

View file

@ -1,3 +0,0 @@
import ../firezone-server-domain/generic.nix {
mixReleaseName = "api";
}

View file

@ -1,60 +0,0 @@
diff --git a/apps/domain/lib/domain/config/definitions.ex b/apps/domain/lib/domain/config/definitions.ex
index 8cd2e8d0f..de55c8e8f 100644
--- a/apps/domain/lib/domain/config/definitions.ex
+++ b/apps/domain/lib/domain/config/definitions.ex
@@ -120,6 +120,7 @@ defmodule Domain.Config.Definitions do
]},
{"Instrumentation",
[
+ :healthz_port,
:instrumentation_client_logs_enabled,
:instrumentation_client_logs_bucket,
:telemetry_metrics_reporter,
@@ -474,6 +475,19 @@ defmodule Domain.Config.Definitions do
## Telemetry
##############################################
+ @doc """
+ The port for the internal healthz endpoint.
+ """
+ defconfig(:healthz_port, :integer,
+ default: 4000,
+ changeset: fn changeset, key ->
+ Ecto.Changeset.validate_number(changeset, key,
+ greater_than: 0,
+ less_than_or_equal_to: 65_535
+ )
+ end
+ )
+
@doc """
Enable or disable the Firezone telemetry collection.
diff --git a/apps/domain/lib/domain/telemetry.ex b/apps/domain/lib/domain/telemetry.ex
index af430358d..d154282b4 100644
--- a/apps/domain/lib/domain/telemetry.ex
+++ b/apps/domain/lib/domain/telemetry.ex
@@ -13,7 +13,7 @@ defmodule Domain.Telemetry do
children = [
# We start a /healthz endpoint that is used for liveness probes
- {Bandit, plug: Telemetry.HealthzPlug, scheme: :http, port: 4000},
+ {Bandit, plug: Telemetry.HealthzPlug, scheme: :http, port: Keyword.get(config, :healthz_port)},
# Telemetry poller will execute the given period measurements
# every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics
diff --git a/config/runtime.exs b/config/runtime.exs
index 7817942fa..c6dfe9c31 100644
--- a/config/runtime.exs
+++ b/config/runtime.exs
@@ -211,7 +211,9 @@ if config_env() == :prod do
otlp_endpoint: System.get_env("OTLP_ENDPOINT")
end
- config :domain, Domain.Telemetry, metrics_reporter: compile_config!(:telemetry_metrics_reporter)
+ config :domain, Domain.Telemetry,
+ healthz_port: compile_config!(:healthz_port),
+ metrics_reporter: compile_config!(:telemetry_metrics_reporter)
if telemetry_metrics_reporter = compile_config!(:telemetry_metrics_reporter) do
config :domain, telemetry_metrics_reporter, compile_config!(:telemetry_metrics_reporter_opts)

View file

@ -1,100 +0,0 @@
diff --git a/apps/domain/lib/domain/config.ex b/apps/domain/lib/domain/config.ex
index 11fdcc3a1..9254f210d 100644
--- a/apps/domain/lib/domain/config.ex
+++ b/apps/domain/lib/domain/config.ex
@@ -42,6 +42,24 @@ defmodule Domain.Config do
end
end
+ @doc """
+ Similar to `compile_config/2` but returns nil if the configuration is invalid.
+
+ This function does not resolve values from the database because it's intended use is during
+ compilation and before application boot (in `config/runtime.exs`).
+
+ If you need to resolve values from the database, use `fetch_config/1` or `fetch_config!/1`.
+ """
+ def compile_config(module \\ Definitions, key, env_config \\ System.get_env()) do
+ case Fetcher.fetch_source_and_config(module, key, %{}, env_config) do
+ {:ok, _source, value} ->
+ value
+
+ {:error, reason} ->
+ nil
+ end
+ end
+
def config_changeset(changeset, schema_key, config_key \\ nil) do
config_key = config_key || schema_key
diff --git a/apps/domain/lib/domain/config/definitions.ex b/apps/domain/lib/domain/config/definitions.ex
index 8cd2e8d0f..f27d67c69 100644
--- a/apps/domain/lib/domain/config/definitions.ex
+++ b/apps/domain/lib/domain/config/definitions.ex
@@ -61,6 +61,7 @@ defmodule Domain.Config.Definitions do
{"Database",
[
:database_host,
+ :database_socket_dir,
:database_port,
:database_name,
:database_user,
@@ -255,6 +256,11 @@ defmodule Domain.Config.Definitions do
"""
defconfig(:database_host, :string, default: "postgres")
+ @doc """
+ PostgreSQL socket directory (takes precedence over hostname).
+ """
+ defconfig(:database_socket_dir, :string, default: nil)
+
@doc """
PostgreSQL port.
"""
diff --git a/config/runtime.exs b/config/runtime.exs
index 7817942fa..14cbe182f 100644
--- a/config/runtime.exs
+++ b/config/runtime.exs
@@ -1,22 +1,31 @@
import Config
if config_env() == :prod do
- import Domain.Config, only: [compile_config!: 1]
+ import Domain.Config, only: [compile_config!: 1, compile_config: 1]
###############################
##### Domain ##################
###############################
- config :domain, Domain.Repo,
- database: compile_config!(:database_name),
- username: compile_config!(:database_user),
- hostname: compile_config!(:database_host),
- port: compile_config!(:database_port),
- password: compile_config!(:database_password),
- pool_size: compile_config!(:database_pool_size),
- ssl: compile_config!(:database_ssl_enabled),
- ssl_opts: compile_config!(:database_ssl_opts),
- parameters: compile_config!(:database_parameters)
+ config :domain,
+ Domain.Repo,
+ [
+ {:database, compile_config!(:database_name)},
+ {:username, compile_config!(:database_user)},
+ {:port, compile_config!(:database_port)},
+ {:pool_size, compile_config!(:database_pool_size)},
+ {:ssl, compile_config!(:database_ssl_enabled)},
+ {:ssl_opts, compile_config!(:database_ssl_opts)},
+ {:parameters, compile_config!(:database_parameters)}
+ ] ++
+ if(compile_config(:database_password),
+ do: [{:password, compile_config!(:database_password)}],
+ else: []
+ ) ++
+ if(compile_config(:database_socket_dir),
+ do: [{:socket_dir, compile_config!(:database_socket_dir)}],
+ else: [{:hostname, compile_config!(:database_host)}]
+ )
config :domain, Domain.Tokens,
key_base: compile_config!(:tokens_key_base),

View file

@ -1,94 +0,0 @@
diff --git a/apps/domain/lib/domain/config/definitions.ex b/apps/domain/lib/domain/config/definitions.ex
index 8cd2e8d0f..2e6ca054b 100644
--- a/apps/domain/lib/domain/config/definitions.ex
+++ b/apps/domain/lib/domain/config/definitions.ex
@@ -584,6 +584,7 @@ defmodule Domain.Config.Definitions do
Swoosh.Adapters.Mailgun,
Swoosh.Adapters.Mailjet,
Swoosh.Adapters.Mandrill,
+ Swoosh.Adapters.Mua,
Swoosh.Adapters.Postmark,
Swoosh.Adapters.ProtonBridge,
Swoosh.Adapters.SMTP,
diff --git a/apps/web/lib/web/live/sign_up.ex b/apps/web/lib/web/live/sign_up.ex
index dfbc94d9b..638e3b373 100644
--- a/apps/web/lib/web/live/sign_up.ex
+++ b/apps/web/lib/web/live/sign_up.ex
@@ -1,5 +1,6 @@
defmodule Web.SignUp do
use Web, {:live_view, layout: {Web.Layouts, :public}}
+ require Logger
alias Domain.{Auth, Accounts, Actors, Config}
alias Web.Registration
@@ -358,7 +359,9 @@ defmodule Web.SignUp do
{:noreply, assign(socket, form: to_form(changeset))}
- {:error, :send_email, _reason, _effects_so_far} ->
+ {:error, :send_email, reason, _effects_so_far} ->
+ Logger.warning("Failed to send sign_up email", reason: inspect(reason))
+
changeset =
Ecto.Changeset.add_error(
changeset,
diff --git a/config/runtime.exs b/config/runtime.exs
index 7817942fa..8704ccde5 100644
--- a/config/runtime.exs
+++ b/config/runtime.exs
@@ -228,7 +228,21 @@ if config_env() == :prod do
[
adapter: compile_config!(:outbound_email_adapter),
from_email: compile_config!(:outbound_email_from)
- ] ++ compile_config!(:outbound_email_adapter_opts)
+ ] ++
+ (if System.get_env("OUTBOUND_EMAIL_ADAPTER") == "Elixir.Swoosh.Adapters.Mua" do
+ [
+ protocol: String.to_atom(System.get_env("OUTBOUND_EMAIL_SMTP_PROTOCOL")),
+ relay: System.get_env("OUTBOUND_EMAIL_SMTP_HOST"),
+ port: String.to_integer(System.get_env("OUTBOUND_EMAIL_SMTP_PORT")),
+ auth: [
+ username: System.get_env("OUTBOUND_EMAIL_SMTP_USERNAME"),
+ password: System.get_env("OUTBOUND_EMAIL_SMTP_PASSWORD")
+ ]
+ ]
+ else
+ []
+ end) ++
+ compile_config!(:outbound_email_adapter_opts)
config :workos, WorkOS.Client,
api_key: compile_config!(:workos_api_key),
diff --git a/mix.exs b/mix.exs
index 12782d631..dee1245d2 100644
--- a/mix.exs
+++ b/mix.exs
@@ -47,7 +47,9 @@ defmodule Firezone.MixProject do
# Formatter doesn't track dependencies of children applications
{:phoenix, "~> 1.7.0"},
{:phoenix_live_view, "~> 1.0.0-rc.0"},
- {:floki, "~> 0.37.0"}
+ {:floki, "~> 0.37.0"},
+ {:mua, "~> 0.2.0"},
+ {:mail, "~> 0.3.0"}
]
end
diff --git a/mix.lock b/mix.lock
index 757829003..47775677d 100644
--- a/mix.lock
+++ b/mix.lock
@@ -50,11 +50,13 @@
"junit_formatter": {:hex, :junit_formatter, "3.4.0", "d0e8db6c34dab6d3c4154c3b46b21540db1109ae709d6cf99ba7e7a2ce4b1ac2", [:mix], [], "hexpm", "bb36e2ae83f1ced6ab931c4ce51dd3dbef1ef61bb4932412e173b0cfa259dacd"},
"libcluster": {:hex, :libcluster, "3.3.3", "a4f17721a19004cfc4467268e17cff8b1f951befe428975dd4f6f7b84d927fe0", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7c0a2275a0bb83c07acd17dab3c3bfb4897b145106750eeccc62d302e3bdfee5"},
"logger_json": {:hex, :logger_json, "6.2.0", "13e2e9f5f7b195865c5c3ef3d296c3ad50e7ecb038d899433702a79e979b91d7", [:mix], [{:ecto, "~> 3.11", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "98366d02bedbb56e41b25a6d248d566d4f4bc224bae2b1e982df00ed04ba9219"},
+ "mail": {:hex, :mail, "0.3.1", "cb0a14e4ed8904e4e5a08214e686ccf6f9099346885db17d8c309381f865cc5c", [:mix], [], "hexpm", "1db701e89865c1d5fa296b2b57b1cd587587cca8d8a1a22892b35ef5a8e352a6"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
"mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"},
"mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"},
"mix_audit": {:hex, :mix_audit, "2.1.4", "0a23d5b07350cdd69001c13882a4f5fb9f90fbd4cbf2ebc190a2ee0d187ea3e9", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "fd807653cc8c1cada2911129c7eb9e985e3cc76ebf26f4dd628bb25bbcaa7099"},
+ "mua": {:hex, :mua, "0.2.4", "a9172ab0a1ac8732cf2699d739ceac3febcb9b4ffc540260ad2e32c0b6632af9", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "e7e4dacd5ad65f13e3542772e74a159c00bd2d5579e729e9bb72d2c73a266fb7"},
"multipart": {:hex, :multipart, "0.4.0", "634880a2148d4555d050963373d0e3bbb44a55b2badd87fa8623166172e9cda0", [:mix], [{:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm", "3c5604bc2fb17b3137e5d2abdf5dacc2647e60c5cc6634b102cf1aef75a06f0a"},
"nimble_csv": {:hex, :nimble_csv, "1.2.0", "4e26385d260c61eba9d4412c71cea34421f296d5353f914afe3f2e71cce97722", [:mix], [], "hexpm", "d0628117fcc2148178b034044c55359b26966c6eaa8e2ce15777be3bbc91b12a"},
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},

View file

@ -1,135 +0,0 @@
{
mixReleaseName, # "domain" "web" or "api"
}:
{
lib,
nixosTests,
fetchFromGitHub,
beamPackages,
pnpm_9,
nodejs,
tailwindcss,
esbuild,
}:
beamPackages.mixRelease rec {
pname = "firezone-server-${mixReleaseName}";
version = "unstable-2025-01-19";
src = "${
fetchFromGitHub {
owner = "firezone";
repo = "firezone";
rev = "8c9427b7b133e5050be34c2ac0e831c12c08f02c";
hash = "sha256-yccplADHRJQQiKrmHcJ5rvouswHrbx4K6ysnIAoZJR0=";
}
}/elixir";
patches = [
./0000-add-healthz-port.patch
./0001-postgres-socket.patch
./0002-add-mua.patch
];
pnpmDeps = pnpm_9.fetchDeps {
inherit pname version;
src = "${src}/apps/web/assets";
hash = "sha256-6rhhGv3jQY5MkOMNe1GEtNyrzJYXCSzvo8RLlKelP10=";
};
pnpmRoot = "apps/web/assets";
preBuild = ''
cat >> config/config.exs <<EOF
config :tailwind, path: "${lib.getExe tailwindcss}"
config :esbuild, path: "${lib.getExe esbuild}"
EOF
cat >> config/runtime.exs <<EOF
config :tzdata, :data_dir, System.get_env("TZDATA_DIR")
EOF
'';
postBuild = ''
pushd apps/web
# for external task you need a workaround for the no deps check flag
# https://github.com/phoenixframework/phoenix/issues/2690
mix do deps.loadpaths --no-deps-check, assets.deploy
mix do deps.loadpaths --no-deps-check, phx.digest priv/static
popd
'';
nativeBuildInputs = [
pnpm_9
pnpm_9.configHook
nodejs
];
inherit mixReleaseName;
# https://github.com/elixir-cldr/cldr_numbers/pull/52
mixNixDeps = import ./mix.nix {
inherit lib beamPackages;
overrides =
final: prev:
(lib.mapAttrs (
_: value:
value.override {
appConfigPath = src + "/config";
}
) prev)
// {
ex_cldr_numbers = beamPackages.buildMix rec {
name = "ex_cldr_numbers";
version = "2.33.4";
src = beamPackages.fetchHex {
pkg = "ex_cldr_numbers";
version = "${version}";
sha256 = "sha256-0Vt+IX6eYMMo5zBF5R3GfXrF0plyR7gz76ssabLtBvU=";
};
beamDeps = [
final.decimal
final.digital_token
final.ex_cldr
final.ex_cldr_currencies
final.jason
];
};
# mix2nix does not support git dependencies yet,
# so we need to add them manually
openid_connect = beamPackages.buildMix {
name = "openid_connect";
version = "2024-06-15-unstable";
src = fetchFromGitHub {
owner = "firezone";
repo = "openid_connect";
rev = "e4d9dca8ae43c765c00a7d3dfa12d6f24f5b3418";
hash = "sha256-LMmG+WWs83Hw/jcrersUMpk2tdXxkOU0CTe7qVbk6GQ=";
};
beamDeps = [
final.jason
final.finch
final.jose
];
};
};
};
passthru.tests = {
inherit (nixosTests) firezone;
};
meta = {
description = "Backend server for the Firezone zero-trust access platform";
homepage = "https://github.com/firezone/firezone";
license = lib.licenses.asl20;
maintainers = with lib.maintainers; [
oddlama
patrickdag
];
mainProgram = mixReleaseName;
platforms = lib.platforms.linux;
};
}

File diff suppressed because it is too large Load diff

View file

@ -1,3 +0,0 @@
import ./generic.nix {
mixReleaseName = "domain";
}

View file

@ -1,3 +0,0 @@
import ../firezone-server-domain/generic.nix {
mixReleaseName = "web";
}