Compare commits

..

11 Commits

Author SHA1 Message Date
Izorkin 412d1a9851 enable modsecurity to nextcloud 2021-12-19 17:08:31 +02:00
Izorkin 9700b9c08d build nginx with custom modules 2021-12-19 17:03:32 +02:00
Izorkin f775c42454 pkgs: init modsecurity-crs at v3.3.2 2021-12-18 22:14:42 +02:00
Izorkin ced421eff4 pkgs: init libmodsecurity at v3.0.6 2021-12-18 22:11:24 +02:00
Izorkin 36e7cb8c5e update gitignore 2021-12-16 19:42:10 +02:00
Izorkin 8a341bf253 move users to generic subfolder 2021-12-16 19:42:10 +02:00
Izorkin b557482517 move files to generic subfolder 2021-12-16 19:42:10 +02:00
Izorkin b2659618aa move variables to generic subfolder 2021-12-16 19:42:10 +02:00
Izorkin ac9860453b move api to generic subfolder 2021-12-16 19:42:10 +02:00
Izorkin 8b10998cae move limits to generic subfolder 2021-12-16 19:42:10 +02:00
Izorkin 4dcbc6cd4e move all services to subfolder 2021-12-16 19:42:08 +02:00
67 changed files with 1545 additions and 1497 deletions

6
.gitignore vendored
View File

@ -1,5 +1,7 @@
outputs
result
result-*
source
userdata/userdata.json
userdata/tokens.json
hardware-configuration.nix
networking.nix
/result

125
README.md
View File

@ -1,70 +1,67 @@
# SelfPrivacy NixOS configuration
This configuration is not self-contained, as it needs to be plugged as an input of a top-level NixOS configuration flake (i.e. https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-template/). This flake outputs the following function:
```nix
nixosConfigurations-fun =
{ hardware-configuration # hardware-configuration.nix file
, deployment # deployment.nix file
, userdata # nix attrset, obtained by fromJSON from userdata.json
, top-level-flake # `self`-reference of the top-level flake
, sp-modules # flake inputs of sp-modules flake
}:
```
which returns one or more attributes, containing NixOS configurations (created with `nixpkgs.lib.nixosSystem`). (As of 2024-01-10 there is only a single configuration named `default`.)
This is a NixOS config which builds a SelfPrivacy server distribution
based on data provided in `userdata/userdata.json`.
## updating flake inputs
JSON schema is provided in `userdata/schema.json` for reference.
We have 2 flake inputs:
- nixpkgs
- selfprivacy-api
**hardware-configuration.nix is not included.**
Both get updated the same ways.
Example JSON config:
There are 2 methods:
1. specify input name only in a command, relying on URL inside `flake.nix`
2. specify input name and URL in a command, **overriding** whatever URL is inside `flake.nix` for the input to update (override)
In any case a Nix flake input is specified using some special _references_ syntax, including URLs, revisions, etc, described in manual: https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-flake.html#examples. Such reference can be used inside `flake.nix` or as an argument to `nix flake` commands. When a new reference is encountered Nix downloads and extracts it to /nix/store.
Before and after running `nix flake lock` (or `nix flake update`) commands you would most likely want to list current inputs using `nix flake metadata`, which are read from `flake.lock` file. Although, Nix should also print a diff between changed references once changed.
`--commit-lock-file` option tells Nix commands to do `git commit flake.lock` automatically, creating a new commit for you.
### method 1: update specific input
Example:
```console
$ nix flake lock --update-input nixpkgs
$ nix flake lock --update-input selfprivacy-api
```
Depending on how "precise" the URL was speficied in `flake.nix`, with _unmodified_ `flake.nix` the result might be:
* URL with `rev` (sha1) parameter => nothing will update (as we're already at exact commit)
* URL with `ref` (branch) parameter => input will update to the latest commit of the specified branch
* URL without `rev` nor `ref` => input will update to the latest commit of a default branch!
---
Once Nix 2.19 stabilizes, a different command _must_ be used for updating a single input (recursively), like this:
```console
$ nix flake update nixpkgs
```
### method 2: override specific input
Overriding is more powerful (for non-nested flakes) as it allows to change a flake input reference to anything just in one command (not only update in the bounds of a branch or a repository).
Example:
```console
$ nix flake lock --override-input nixpkgs github:nixos/nixpkgs?ref=nixos-23.11
$ nix flake lock --override-input selfprivacy-api git+https://git.selfprivacy.org/SelfPrivacy/selfprivacy-rest-api.git?ref=flakes
```
Similarly to update mechanism (described above), depending on the "precision" of an URL, its update scope varies accordingly.
Note, that subsequent calls of `nix flake lock --update-input <INPUT>` or `nix flake update` (or `nix flake update INPUT` by Nix 2.19+) will update the input regardless of the prior override. The information about override is stored only in `flake.lock` (`flake.nix` is not altered by Nix).
---
Note, that override does not update flake inputs recursively (say, you have a flake nested inside your flake input). For recursive updates only `nix flake lock --update-input` and `nix flake update` mechanisms are suitable. However, as of 2024-01-10 none of the SP NixOS configuration inputs contain other flakes, hence override mechanism is fine (don't confuse with top-level flake which has nested inputs).
```json
{
"backblaze": {
"accountId": "BACKBLAZE_KEY_ID",
"accountKey": "BACKBLAZE_ACCOUNT_KEY",
"bucket": "BACKBLAZE_BUCKET_NAME"
},
"api": {
"token": "API_TOKEN",
"enableSwagger": false
},
"bitwarden": {
"enable": true
},
"cloudflare": {
"apiKey": "CF_TOKEN"
},
"databasePassword": "DB_PASSWORD",
"domain": "DOMAIN",
"hashedMasterPassword": "HASHED_PASSWORD",
"hostname": "DOMAIN",
"nextcloud": {
"enable": true,
"adminPassword": "PASSWORD",
"databasePassword": "PASSWORD"
},
"gitea": {
"enable": true
},
"jitsi": {
"enable": true
},
"ocserv": {
"enable": true
},
"pleroma": {
"enable": true
},
"timezone": "Europe/Moscow",
"resticPassword": "PASSWORD",
"ssh": {
"enable": true,
"rootSshKeys": [
"ssh-ed25519 KEY user@host"
],
"passwordAuthentication": true
},
"username": "LUSER",
"users": [
{
"hashedPassword": "OTHER_USER_HASHED_PASSWORD",
"username": "OTHER_USER"
}
]
}
```

View File

@ -1,155 +1,78 @@
{ config, pkgs, lib, ... }:
let
redis-sp-api-srv-name = "sp-api";
sp-print-api-token = pkgs.writeShellApplication {
name = "sp-print-api-token";
runtimeInputs = with pkgs; [ redis ];
text = ''
hash_token="$(redis-cli -s /run/redis-${redis-sp-api-srv-name}/redis.sock keys "token_repo:tokens:*" | head -n 1)"
hash_token="''${hash_token#"token_repo:tokens:"}"
token="$(redis-cli -s /run/redis-${redis-sp-api-srv-name}/redis.sock HGETALL "token_repo:tokens:$hash_token")"
token="$(echo "$token" | sed -n '2p')"
echo "$token"
'';
};
url-overlay = "https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nix-repo/archive/master.tar.gz";
nix-overlay = (import (builtins.fetchTarball url-overlay));
in
{
imports = [
./selfprivacy-module.nix
./volumes.nix
./users.nix
./letsencrypt/acme.nix
./letsencrypt/resolve.nix
./webserver/nginx.nix
./webserver/memcached.nix
# ./resources/limits.nix
./hardware-configuration.nix
./generic/api/api.nix
./generic/modules/api.nix
./generic/modules/userdata.nix
./generic/overlays/default.nix
./generic/services/backup/restic.nix
./generic/services/cloud/nextcloud.nix
./generic/services/git/gitea.nix
./generic/services/letsencrypt/acme.nix
./generic/services/letsencrypt/resolve.nix
./generic/services/mail/alps.nix
./generic/services/mail/mailserver.nix
./generic/services/passmgr/bitwarden.nix
./generic/services/social/pleroma.nix
./generic/services/videomeet/jitsi.nix
./generic/services/vpn/ocserv.nix
./generic/services/webserver/modsecurity/default.nix
./generic/services/webserver/memcached.nix
./generic/services/webserver/nginx.nix
./generic/system/limits.nix
./generic/system/tmpfiles.nix
./generic/system/userdata.nix
./generic/system/users.nix
];
fileSystems."/".options = [ "noatime" ];
nixpkgs.overlays = [ (nix-overlay) ];
services.selfprivacy-api.enable = true;
services.redis.servers.${redis-sp-api-srv-name} = {
enable = true;
save = [
[
30
1
]
[
10
10
]
];
port = 0;
settings = {
notify-keyspace-events = "KEA";
};
};
services.do-agent.enable = if config.selfprivacy.server.provider == "DIGITALOCEAN" then true else false;
boot.tmp.cleanOnBoot = true;
boot.cleanTmpDir = true;
networking = {
hostName = config.selfprivacy.hostname;
domain = config.selfprivacy.domain;
usePredictableInterfaceNames = false;
hostName = config.services.userdata.hostname;
firewall = {
allowedTCPPorts = lib.mkForce [ 22 25 80 143 443 465 587 993 4443 8443 ];
allowedUDPPorts = lib.mkForce [ 8443 10000 ];
extraCommands = ''
iptables --table nat --append POSTROUTING --out-interface eth0 -j MASQUERADE
iptables --append FORWARD --in-interface vpn00 -j ACCEPT
'';
allowedTCPPorts = lib.mkForce [ 22 25 80 143 443 465 587 993 8443 ];
allowedUDPPorts = lib.mkForce [ 8443 ];
};
nameservers = [ "1.1.1.1" "1.0.0.1" ];
};
time.timeZone = config.selfprivacy.timezone;
time.timeZone = config.services.userdata.timezone;
i18n.defaultLocale = "en_GB.UTF-8";
users.users.root.openssh.authorizedKeys.keys = config.selfprivacy.ssh.rootKeys;
users.users.root.openssh.authorizedKeys.keys = config.services.userdata.ssh.rootKeys;
services.openssh = {
enable = config.selfprivacy.ssh.enable;
settings = {
PasswordAuthentication = config.selfprivacy.ssh.passwordAuthentication;
PermitRootLogin = "yes";
};
enable = config.services.userdata.ssh.enable;
passwordAuthentication = config.services.userdata.ssh.passwordAuthentication;
permitRootLogin = "yes";
openFirewall = false;
};
programs.ssh = {
pubkeyAcceptedKeyTypes = [ "ssh-ed25519" "ssh-rsa" "ecdsa-sha2-nistp256" ];
pubkeyAcceptedKeyTypes = [ "ssh-ed25519" "ssh-rsa" ];
hostKeyAlgorithms = [ "ssh-ed25519" "ssh-rsa" ];
};
environment.systemPackages = with pkgs; [
git
jq
sp-print-api-token
];
# consider environment.defaultPackages = lib.mkForce [];
documentation.enable = false; # no {man,info}-pages & docs, etc to save space
# (or create a systemd service with `ConditionFirstBoot=yes`?)
systemd.tmpfiles.rules = [
"# Completely remove remnants of NIXOS_LUSTRATE."
"R! /old-root"
];
system.stateVersion =
lib.mkIf (config.selfprivacy.stateVersion != null)
config.selfprivacy.stateVersion;
system.autoUpgrade = {
enable = config.selfprivacy.autoUpgrade.enable;
allowReboot = config.selfprivacy.autoUpgrade.allowReboot;
# TODO get attribute name from selfprivacy options
flake = "/etc/nixos#default";
environment.variables = {
DOMAIN = config.services.userdata.domain;
};
systemd.services.nixos-upgrade.serviceConfig.WorkingDirectory = "/etc/nixos";
# TODO parameterize URL somehow; run nix flake update as non-root user
systemd.services.nixos-upgrade.serviceConfig.ExecCondition =
pkgs.writeShellScript "flake-update-script" ''
set -o xtrace
if ${config.nix.package.out}/bin/nix flake update \
--override-input selfprivacy-nixos-config git+https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-config.git?ref=flakes
then
if ${pkgs.diffutils}/bin/diff -u -r /etc/selfprivacy/nixos-config-source/ /etc/nixos/
then
set +o xtrace
echo "No configuration changes detected. Nothing to upgrade."
exit 1
fi
else
# ExecStart must not start after 255 exit code, service must fail.
exit 255
fi
'';
system.autoUpgrade.enable = config.services.userdata.autoUpgrade.enable;
system.autoUpgrade.allowReboot = config.services.userdata.autoUpgrade.allowReboot;
system.autoUpgrade.channel = https://nixos.org/channels/nixos-21.05-small;
nix = {
channel.enable = false;
# daemonCPUSchedPolicy = "idle";
# daemonIOSchedClass = "idle";
# daemonIOSchedPriority = 7;
# this is superseded by nix.settings.auto-optimise-store.
# optimise.automatic = true;
optimise.automatic = true;
gc = {
automatic = true; # TODO it's debatable, because of IO&CPU load
automatic = true;
options = "--delete-older-than 7d";
};
};
nix.settings = {
sandbox = true;
experimental-features = [ "nix-command" "flakes" "repl-flake" ];
# auto-optimise-store = true;
# evaluation restrictions:
# restrict-eval = true;
# allowed-uris = [];
allow-dirty = false;
};
services.journald.extraConfig = "SystemMaxUse=500M";
boot.kernel.sysctl = {
"net.ipv4.ip_forward" = 1; # TODO why is it here by default, for VPN only?
"net.ipv4.ip_forward" = 1;
};
# TODO must be configurable and determined at nixos-infect stage
swapDevices = [
{
device = "/swapfile";
@ -157,12 +80,9 @@ in
size = 2048;
}
];
# TODO why is sudo needed?
security = {
sudo = {
enable = true;
};
};
systemd.enableEmergencyMode = false;
systemd.coredump.enable = false;
}

View File

@ -1,47 +0,0 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1716361217,
"narHash": "sha256-mzZDr00WUiUXVm1ujBVv6A0qRd8okaITyUp4ezYRgc4=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "46397778ef1f73414b03ed553a3368f0e7e33c2f",
"type": "github"
},
"original": {
"owner": "nixos",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"selfprivacy-api": "selfprivacy-api"
}
},
"selfprivacy-api": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1716580438,
"narHash": "sha256-wWKhCiRmjrFgnHUgcxBb2mtBy9uTrY93yxpMYh/wpVY=",
"ref": "master",
"rev": "4f1d44ce74432e745f0be3b192f8cc4ae06fd169",
"revCount": 1304,
"type": "git",
"url": "https://git.selfprivacy.org/SelfPrivacy/selfprivacy-rest-api.git"
},
"original": {
"type": "git",
"url": "https://git.selfprivacy.org/SelfPrivacy/selfprivacy-rest-api.git"
}
}
},
"root": "root",
"version": 7
}

View File

@ -1,98 +0,0 @@
{
description = "SelfPrivacy NixOS configuration flake";
inputs = {
nixpkgs.url = github:nixos/nixpkgs;
selfprivacy-api.url =
git+https://git.selfprivacy.org/SelfPrivacy/selfprivacy-rest-api.git;
# make selfprivacy-api use the same shared nixpkgs
selfprivacy-api.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, selfprivacy-api }: {
nixosConfigurations-fun =
{ hardware-configuration
, deployment
, userdata
, top-level-flake
, sp-modules
}:
{
default = nixpkgs.lib.nixosSystem {
modules = [
hardware-configuration
deployment
./configuration.nix
selfprivacy-api.nixosModules.default
{
# pass userdata (parsed from JSON) options to selfprivacy module
selfprivacy = userdata;
# embed top-level flake source folder into the build
environment.etc."selfprivacy/nixos-config-source".source =
top-level-flake;
# for running "nix search nixpkgs", etc
nix.registry.nixpkgs.flake = nixpkgs;
# embed commit sha1 for `nixos-version --configuration-revision`
system.configurationRevision = self.rev
or "@${self.lastModifiedDate}"; # for development
# TODO assertion to forbid dirty builds caused by top-level-flake
# reset contents of /etc/nixos to match running NixOS generation
system.activationScripts.selfprivacy-nixos-config-source = ''
rm -rf /etc/nixos/{*,.[!.]*}
cp -r --no-preserve=all ${top-level-flake}/ -T /etc/nixos/
'';
}
]
++
# add SP modules, but contrain available config attributes for each
# (TODO revise evaluation performance of the code below)
nixpkgs.lib.attrsets.mapAttrsToList
(name: sp-module: args@{ config, pkgs, ... }:
let
lib = nixpkgs.lib;
configPathsNeeded = sp-module.configPathsNeeded or
(abort "allowed config paths not set for module \"${name}\"");
constrainConfigArgs = args'@{ pkgs, ... }: args' // {
config =
# TODO use lib.attrsets.mergeAttrsList from nixpkgs 23.05
(builtins.foldl' lib.attrsets.recursiveUpdate { }
(map
(p: lib.attrsets.setAttrByPath p
(lib.attrsets.getAttrFromPath p config))
configPathsNeeded
)
);
};
constrainImportsArgsRecursive = lib.attrsets.mapAttrsRecursive
(p: v:
# TODO traverse only imports and imports of imports, etc
# without traversing all attributes
if lib.lists.last p == "imports"
then
map
(m:
(args'@{ pkgs, ... }: constrainImportsArgsRecursive
(if builtins.isPath m
then import m (constrainConfigArgs args')
else
if builtins.isFunction m
then m (constrainConfigArgs args')
else m))
)
v
else v);
in
constrainImportsArgsRecursive
(sp-module.nixosModules.default (constrainConfigArgs args))
)
sp-modules;
};
};
formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixpkgs-fmt;
};
}

21
generic/api/api.nix Normal file
View File

@ -0,0 +1,21 @@
{ config, pkgs, ... }:
{
services.selfprivacy-api = {
enable = true;
token = config.services.userdata.api.token;
enableSwagger = config.services.userdata.api.enableSwagger;
b2AccountId = config.services.userdata.backblaze.accountId;
b2AccountKey = config.services.userdata.backblaze.accountKey;
b2Bucket = config.services.userdata.backblaze.bucket;
resticPassword = config.services.userdata.resticPassword;
};
users.users."selfprivacy-api" = {
isNormalUser = false;
isSystemUser = true;
extraGroups = [ "opendkim" ];
};
users.groups."selfprivacy-api" = {
members = [ "selfprivacy-api" ];
};
}

124
generic/modules/api.nix Normal file
View File

@ -0,0 +1,124 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.selfprivacy-api;
directionArg =
if cfg.direction == ""
then ""
else "--direction=${cfg.direction}";
in
{
options.services.selfprivacy-api = {
enable = mkOption {
default = false;
type = types.bool;
description = ''
Enable SelfPrivacy API service
'';
};
token = mkOption {
type = types.str;
description = ''
SelfPrivacy API token
'';
};
enableSwagger = mkOption {
default = false;
type = types.bool;
description = ''
Enable Swagger UI
'';
};
b2AccountId = mkOption {
type = types.str;
description = ''
B2 account ID
'';
};
b2AccountKey = mkOption {
type = types.str;
description = ''
B2 account key
'';
};
b2Bucket = mkOption {
type = types.str;
description = ''
B2 bucket
'';
};
resticPassword = mkOption {
type = types.str;
description = ''
Restic password
'';
};
};
config = lib.mkIf cfg.enable {
systemd.services.selfprivacy-api = {
description = "API Server used to control system from the mobile application";
environment = config.nix.envVars // {
inherit (config.environment.sessionVariables) NIX_PATH;
HOME = "/root";
PYTHONUNBUFFERED = "1";
AUTH_TOKEN = cfg.token;
ENABLE_SWAGGER = (if cfg.enableSwagger then "1" else "0");
B2_ACCOUNT_ID = cfg.b2AccountId;
B2_ACCOUNT_KEY = cfg.b2AccountKey;
B2_BUCKET = cfg.b2Bucket;
RESTIC_PASSWORD = cfg.resticPassword;
} // config.networking.proxy.envVars;
path = [ "/var/" "/var/dkim/" pkgs.coreutils pkgs.gnutar pkgs.xz.bin pkgs.gzip pkgs.gitMinimal config.nix.package.out pkgs.nixos-rebuild pkgs.restic pkgs.mkpasswd ];
after = [ "network-online.target" ];
wantedBy = [ "network-online.target" ];
serviceConfig = {
User = "root";
ExecStart = "${pkgs.selfprivacy-api}/bin/app.py";
Restart = "always";
RestartSec = "5";
};
};
# One shot systemd service to rebuild NixOS using nixos-rebuild
systemd.services.sp-nixos-rebuild = {
description = "Upgrade NixOS using nixos-rebuild";
environment = config.nix.envVars // {
inherit (config.environment.sessionVariables) NIX_PATH;
HOME = "/root";
} // config.networking.proxy.envVars;
path = [ pkgs.coreutils pkgs.gnutar pkgs.xz.bin pkgs.gzip pkgs.gitMinimal config.nix.package.out pkgs.nixos-rebuild ];
serviceConfig = {
User = "root";
ExecStart = "${pkgs.nixos-rebuild}/bin/nixos-rebuild switch";
};
};
# One shot systemd service to upgrade NixOS using nixos-rebuild
systemd.services.sp-nixos-upgrade = {
description = "Upgrade NixOS using nixos-rebuild";
environment = config.nix.envVars // {
inherit (config.environment.sessionVariables) NIX_PATH;
HOME = "/root";
} // config.networking.proxy.envVars;
path = [ pkgs.coreutils pkgs.gnutar pkgs.xz.bin pkgs.gzip pkgs.gitMinimal config.nix.package.out pkgs.nixos-rebuild ];
serviceConfig = {
User = "root";
ExecStart = "${pkgs.nixos-rebuild}/bin/nixos-rebuild switch --upgrade";
};
};
# One shot systemd service to rollback NixOS using nixos-rebuild
systemd.services.sp-nixos-rollback = {
description = "Rollback NixOS using nixos-rebuild";
environment = config.nix.envVars // {
inherit (config.environment.sessionVariables) NIX_PATH;
HOME = "/root";
} // config.networking.proxy.envVars;
path = [ pkgs.coreutils pkgs.gnutar pkgs.xz.bin pkgs.gzip pkgs.gitMinimal config.nix.package.out pkgs.nixos-rebuild ];
serviceConfig = {
User = "root";
ExecStart = "${pkgs.nixos-rebuild}/bin/nixos-rebuild switch --rollback";
};
};
};
}

View File

@ -1,8 +1,20 @@
{ lib, ... }:
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.userdata;
directionArg =
if cfg.direction == ""
then ""
else "--direction=${cfg.direction}";
in
{
options.selfprivacy = {
options.services.userdata = {
enable = mkOption {
default = true;
type = types.nullOr types.bool;
};
# General server options
hostname = mkOption {
description = "The hostname of the server.";
@ -12,20 +24,19 @@ with lib;
description = ''
Domain used by the server
'';
# see: https://regexr.com/7p7ep, https://stackoverflow.com/a/26987741
type = lib.types.strMatching ''^(xn--)?[a-z0-9][a-z0-9_-]{0,61}[a-z0-9]{0,1}\.(xn--)?([a-z0-9\-]{1,61}|[a-z0-9-]{1,30}\.[a-z]{2,})$'';
type = types.nullOr types.str;
};
timezone = mkOption {
description = ''
Timezone used by the server
'';
type = types.nullOr types.str;
default = "Etc/UTC";
default = "Europe/Uzhgorod";
};
autoUpgrade = {
enable = mkOption {
description = "Enable auto-upgrade of the server.";
default = false;
default = true;
type = types.nullOr types.bool;
};
allowReboot = mkOption {
@ -34,11 +45,6 @@ with lib;
type = types.nullOr types.bool;
};
};
stateVersion = mkOption {
description = "State version of the server";
type = types.nullOr types.str;
default = null;
};
########################
# Server admin options #
########################
@ -61,25 +67,112 @@ with lib;
type = types.nullOr (types.listOf types.str);
default = [ ];
};
#############
# DNS #
#############
dns = {
provider = mkOption {
description = "DNS provider that was defined at the initial setup process.";
###############
# API options #
###############
api = {
token = mkOption {
description = ''
API token used to authenticate with the server
'';
type = types.nullOr types.str;
};
useStagingACME = mkOption {
description = "Use staging ACME server. Default is false";
type = types.nullOr types.bool;
default = false;
enableSwagger = mkOption {
default = true;
description = ''
Enable Swagger UI
'';
type = types.bool;
};
};
server = {
provider = mkOption {
description = "Server provider that was defined at the initial setup process.";
type = types.str;
#############
# Secrets #
#############
backblaze = {
bucket = mkOption {
description = "Bucket name used for userdata backups";
type = types.nullOr types.str;
};
accountId = mkOption {
description = "Backblaze B2 Account ID";
type = types.nullOr types.str;
};
accountKey = mkOption {
description = "Backblaze B2 Account Key.";
type = types.nullOr types.str;
};
};
cloudflare = {
apiKey = mkOption {
description = "Cloudflare API Key.";
type = types.nullOr types.str;
};
};
##############
# Services #
##############
databasePassword = mkOption {
description = ''
Password for the database
'';
type = types.nullOr types.str;
};
bitwarden = {
enable = mkOption {
default = false;
type = types.nullOr types.bool;
};
};
gitea = {
enable = mkOption {
default = false;
type = types.nullOr types.bool;
};
};
nextcloud = {
enable = mkOption {
default = true;
type = types.nullOr types.bool;
};
databasePassword = mkOption {
description = ''
Password for the nextcloud database
'';
type = types.nullOr types.str;
};
adminPassword = mkOption {
description = ''
Password for the nextcloud admin user
'';
type = types.nullOr types.str;
};
};
pleroma = {
enable = mkOption {
default = false;
type = types.nullOr types.bool;
};
};
jitsi = {
enable = mkOption {
default = false;
type = types.nullOr types.bool;
};
};
ocserv = {
enable = mkOption {
default = true;
type = types.nullOr types.bool;
};
};
#############
# Backups #
#############
resticPassword = mkOption {
description = ''
Password for the restic
'';
type = types.nullOr types.str;
};
#########
# SSH #
@ -91,7 +184,7 @@ with lib;
};
rootKeys = mkOption {
description = ''
Root SSH authorized keys
Root SSH Keys
'';
type = types.nullOr (types.listOf types.str);
default = [ "" ];
@ -100,7 +193,7 @@ with lib;
description = ''
Password authentication for SSH
'';
default = false;
default = true;
type = types.nullOr types.bool;
};
};
@ -114,20 +207,5 @@ with lib;
type = types.nullOr (types.listOf (types.attrsOf types.anything));
default = [ ];
};
##############
# Volumes #
##############
volumes = mkOption {
description = ''
Volumes that will be created on the server
'';
type = types.nullOr (types.listOf (types.attrsOf types.anything));
default = [ ];
};
useBinds = mkOption {
type = types.nullOr types.bool;
default = false;
description = "Whether to bind-mount vmail and sieve folders";
};
};
}

View File

@ -0,0 +1,21 @@
{ lib, pkgs, ... }:
{
nixpkgs.config.packageOverrides = pkgs: rec {
spdev = import ./../pkgs { inherit pkgs; };
};
nixpkgs.overlays = [
(self: super: {
nginx = super.nginxMainline.override {
withDebug = false;
withStream = false;
modules = [
pkgs.nginxModules.rtmp
pkgs.nginxModules.dav
pkgs.nginxModules.moreheaders
pkgs.nginxModules.modsecurity-nginx
];
};
})
];
}

5
generic/pkgs/default.nix Normal file
View File

@ -0,0 +1,5 @@
{ pkgs ? import <nixpkgs> { } }:
rec {
libmodsecurity = pkgs.callPackage ./libmodsecurity { };
modsecurity-crs = pkgs.callPackage ./modsecurity-crs { };
}

View File

@ -0,0 +1,70 @@
{ lib, stdenv, fetchFromGitHub
, autoreconfHook, bison, flex, pkg-config
, curl, geoip, libmaxminddb, libxml2, lmdb, lua, pcre
, ssdeep, valgrind, yajl
}:
stdenv.mkDerivation rec {
pname = "libmodsecurity";
version = "3.0.6";
src = fetchFromGitHub {
owner = "SpiderLabs";
repo = "ModSecurity";
rev = "v${version}";
sha256 = "sha256-V+NBT2YN8qO3Px8zEzSA2ZsjSf1pv8+VlLxYlrpqfGg=";
fetchSubmodules = true;
};
nativeBuildInputs = [ autoreconfHook bison flex pkg-config ];
buildInputs = [ curl geoip libmaxminddb libxml2 lmdb lua pcre ssdeep valgrind yajl ];
outputs = [ "out" "dev" ];
configureFlags = [
"--enable-parser-generation"
"--with-curl=${curl.dev}"
"--with-libxml=${libxml2.dev}"
"--with-lmdb=${lmdb.out}"
"--with-maxmind=${libmaxminddb}"
"--with-pcre=${pcre.dev}"
"--with-ssdeep=${ssdeep}"
];
postPatch = ''
substituteInPlace build/lmdb.m4 \
--replace "\''${path}/include/lmdb.h" "${lmdb.dev}/include/lmdb.h" \
--replace "lmdb_inc_path=\"\''${path}/include\"" "lmdb_inc_path=\"${lmdb.dev}/include\""
substituteInPlace build/ssdeep.m4 \
--replace "/usr/local/libfuzzy" "${ssdeep}/lib" \
--replace "\''${path}/include/fuzzy.h" "${ssdeep}/include/fuzzy.h" \
--replace "ssdeep_inc_path=\"\''${path}/include\"" "ssdeep_inc_path=\"${ssdeep}/include\""
substituteInPlace modsecurity.conf-recommended \
--replace "SecUnicodeMapFile unicode.mapping 20127" "SecUnicodeMapFile $out/share/modsecurity/unicode.mapping 20127"
'';
postInstall = ''
mkdir -p $out/share/modsecurity
cp ${src}/{AUTHORS,CHANGES,LICENSE,README.md,modsecurity.conf-recommended,unicode.mapping} $out/share/modsecurity
'';
enableParallelBuilding = true;
meta = with lib; {
homepage = "https://github.com/SpiderLabs/ModSecurity";
description = ''
ModSecurity v3 library component.
'';
longDescription = ''
Libmodsecurity is one component of the ModSecurity v3 project. The
library codebase serves as an interface to ModSecurity Connectors taking
in web traffic and applying traditional ModSecurity processing. In
general, it provides the capability to load/interpret rules written in
the ModSecurity SecRules format and apply them to HTTP content provided
by your application via Connectors.
'';
license = licenses.asl20;
platforms = platforms.all;
maintainers = with maintainers; [ izorkin ];
};
}

View File

@ -0,0 +1,42 @@
{ lib, stdenv, fetchFromGitHub }:
stdenv.mkDerivation rec {
version = "3.3.2";
pname = "modsecurity-crs";
src = fetchFromGitHub {
owner = "coreruleset";
repo = "coreruleset";
rev = "v${version}";
sha256 = "sha256-m/iVLhk2y5BpYu8EwC2adrrDnbaVCQ0SE25ltvMokCw=";
};
installPhase = ''
install -D -m444 -t $out/rules ${src}/rules/*.conf
install -D -m444 -t $out/rules ${src}/rules/*.data
install -D -m444 -t $out/share/doc/modsecurity-crs ${src}/*.md
install -D -m444 -t $out/share/doc/modsecurity-crs ${src}/{CHANGES,INSTALL,LICENSE}
install -D -m444 -t $out/share/modsecurity-crs ${src}/rules/*.example
install -D -m444 -t $out/share/modsecurity-crs ${src}/crs-setup.conf.example
cat > $out/share/modsecurity-crs/modsecurity-crs.load.example <<EOF
##
## This is a sample file for loading OWASP CRS's rules.
##
Include /etc/modsecurity/crs/crs-setup.conf
IncludeOptional /etc/modsecurity/crs/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
Include $out/rules/*.conf
IncludeOptional /etc/modsecurity/crs/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf
EOF
'';
meta = with lib; {
homepage = "https://coreruleset.org";
description = ''
The OWASP ModSecurity Core Rule Set is a set of generic attack detection
rules for use with ModSecurity or compatible web application firewalls.
'';
license = licenses.asl20;
platforms = platforms.all;
maintainers = with maintainers; [ izorkin ];
};
}

View File

@ -0,0 +1,35 @@
{ config, pkgs, ... }:
let
cfg = config.services.userdata;
in
{
services.restic.backups = {
options = {
passwordFile = "/etc/restic/resticPasswd";
repository = "s3:s3.anazonaws.com/${cfg.backblaze.bucket}";
initialize = true;
paths = [
"/var/dkim"
"/var/vmail"
];
timerConfig = {
OnCalendar = [ "daily" ];
};
user = "restic";
pruneOpts = [
"--keep-daily 5"
];
};
};
users.users.restic = {
isNormalUser = false;
isSystemUser = true;
};
environment.etc."restic/resticPasswd".text = ''
${cfg.resticPassword}
'';
environment.etc."restic/s3Passwd".text = ''
AWS_ACCESS_KEY_ID=${cfg.backblaze.accountId}
AWS_SECRET_ACCESS_KEY=${cfg.backblaze.accountKey}
'';
}

View File

@ -0,0 +1,34 @@
{ pkgs, config, ... }:
let
cfg = config.services.userdata;
in
{
services.nextcloud = {
enable = cfg.nextcloud.enable;
package = pkgs.nextcloud22;
hostName = "cloud.${cfg.domain}";
# Use HTTPS for links
https = false;
# Auto-update Nextcloud Apps
autoUpdateApps.enable = true;
# Set what time makes sense for you
autoUpdateApps.startAt = "05:00:00";
config = {
# Further forces Nextcloud to use HTTPS
overwriteProtocol = "https";
# Nextcloud PostegreSQL database configuration, recommended over using SQLite
dbtype = "sqlite";
dbuser = "nextcloud";
dbhost = "/run/postgresql"; # nextcloud will add /.s.PGSQL.5432 by itself
dbname = "nextcloud";
dbpassFile = "/var/lib/nextcloud/db-pass";
adminpassFile = "/var/lib/nextcloud/admin-pass";
adminuser = "admin";
};
};
}

View File

@ -0,0 +1,57 @@
{ config, pkgs, ... }:
let
cfg = config.services.userdata;
in
{
services = {
gitea = {
enable = cfg.gitea.enable;
stateDir = "/var/lib/gitea";
log = {
rootPath = "/var/lib/gitea/log";
level = "Warn";
};
user = "gitea";
database = {
type = "sqlite3";
host = "127.0.0.1";
name = "gitea";
user = "gitea";
path = "/var/lib/gitea/data/gitea.db";
createDatabase = true;
};
ssh = {
enable = true;
clonePort = 22;
};
lfs = {
enable = true;
contentDir = "/var/lib/gitea/lfs";
};
appName = "SelfPrivacy git Service";
repositoryRoot = "/var/lib/gitea/repositories";
domain = "git.${cfg.domain}";
rootUrl = "https://${cfg.domain}/";
httpAddress = "0.0.0.0";
httpPort = 3000;
cookieSecure = true;
settings = {
mailer = {
ENABLED = false;
};
ui = {
DEFAULT_THEME = "arc-green";
};
picture = {
DISABLE_GRAVATAR = true;
};
admin = {
ENABLE_KANBAN_BOARD = true;
};
repository = {
FORCE_PRIVATE = false;
};
};
};
};
}

View File

@ -0,0 +1,22 @@
{ config, pkgs, ... }:
let
cfg = config.services.userdata;
in
{
users.groups.acmerecievers = {
members = [ "nginx" "dovecot2" "postfix" "virtualMail" "ocserv" ];
};
security.acme = {
acceptTerms = true;
email = "${cfg.username}@${cfg.domain}";
certs = {
"${cfg.domain}" = {
domain = "*.${cfg.domain}";
extraDomainNames = [ "${cfg.domain}" ];
group = "acmerecievers";
dnsProvider = "cloudflare";
credentialsFile = "/var/lib/cloudflare/Credentials.ini";
};
};
};
}

View File

@ -1,6 +1,6 @@
{ config, lib, ... }:
{ config, pkgs, ... }:
let
domain = config.selfprivacy.domain;
domain = config.services.userdata.domain;
in
{
systemd = {
@ -12,6 +12,11 @@ in
Restart = "on-failure";
};
};
"nginx-config-reload" = {
serviceConfig = {
After = [ "acme-${domain}.service" ];
};
};
};
};
}

View File

@ -0,0 +1,30 @@
{ lib, fetchgit, buildGoModule, ... }:
buildGoModule rec {
pname = "alps";
version = "v1.0.0"; # latest available tag at the moment
src = fetchGit {
url = "https://git.selfprivacy.org/ilchub/selfprivacy-alps";
rev = "dc2109ca2fdabfbda5d924faa4947f5694d5d758";
};
vendorSha256 = "0bqg0qjam4mvh07wfil6l5spz32mk5a7kfxxnwfyva805pzmn6dk";
deleteVendor = false;
runVend = true;
buildPhase = ''
go build ./cmd/alps
'';
installPhase = ''
mkdir -p $out/bin
cp -r * $out/bin
'';
meta = with lib; {
description = "Webmail application for the dovecot/postfix mailserver";
homepage = "https://git.selfprivacy.org/ilchub/selfprivacy-alps";
license = licenses.mit;
};
}

View File

@ -0,0 +1,18 @@
{ pkgs, config, lib, fetchgit, buildGoModule, ... }:
let domain = config.services.userdata.domain;
in
{
nixpkgs.overlays =
[ (self: super: { alps = self.callPackage ./alps-package.nix { }; }) ];
systemd.services = {
alps = {
path = [ pkgs.alps pkgs.coreutils ];
serviceConfig = {
ExecStart =
"${pkgs.alps}/bin/alps -theme sourcehut imaps://${domain}:993 smtps://${domain}:465";
WorkingDirectory = "${pkgs.alps}/bin";
};
};
};
}

View File

@ -1,30 +1,17 @@
{ config, lib, ... }:
{ config, pkgs, lib, ... }:
let
sp = config.selfprivacy;
cfg = config.services.userdata;
in
lib.mkIf sp.modules.simple-nixos-mailserver.enable
{
fileSystems = lib.mkIf sp.useBinds
{
"/var/vmail" = {
device =
"/volumes/${sp.modules.simple-nixos-mailserver.location}/vmail";
options = [
"bind"
"x-systemd.required-by=postfix.service"
"x-systemd.before=postfix.service"
];
};
"/var/sieve" = {
device =
"/volumes/${sp.modules.simple-nixos-mailserver.location}/sieve";
options = [
"bind"
"x-systemd.required-by=dovecot2.service"
"x-systemd.before=dovecot2.service"
];
};
};
imports = [
(builtins.fetchTarball {
# Pick a commit from the branch you are interested in
url = "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/archive/5675b122/nixos-mailserver-5675b122.tar.gz";
# And set its hash
sha256 = "1fwhb7a5v9c98nzhf3dyqf3a5ianqh7k50zizj8v5nmj3blxw4pi";
})
];
users.users = {
virtualMail = {
@ -32,23 +19,20 @@ lib.mkIf sp.modules.simple-nixos-mailserver.enable
};
};
users.groups.acmereceivers.members = [ "dovecot2" "postfix" "virtualMail" ];
mailserver = {
enable = true;
fqdn = sp.domain;
domains = [ sp.domain ];
localDnsResolver = false;
fqdn = cfg.domain;
domains = [ cfg.domain ];
# A list of all login accounts. To create the password hashes, use
# mkpasswd -m sha-512 "super secret password"
loginAccounts = {
"${sp.username}@${sp.domain}" = {
hashedPassword = sp.hashedMasterPassword;
"${cfg.username}@${cfg.domain}" = {
hashedPassword = cfg.hashedMasterPassword;
sieveScript = ''
require ["fileinto", "mailbox"];
if header :contains "Chat-Version" "1.0"
{
{
fileinto :create "DeltaChat";
stop;
}
@ -56,26 +40,28 @@ lib.mkIf sp.modules.simple-nixos-mailserver.enable
};
} // builtins.listToAttrs (builtins.map
(user: {
name = "${user.username}@${sp.domain}";
name = "${user.username}@${cfg.domain}";
value = {
hashedPassword = user.hashedPassword;
sieveScript = ''
require ["fileinto", "mailbox"];
if header :contains "Chat-Version" "1.0"
{
{
fileinto :create "DeltaChat";
stop;
}
'';
};
})
sp.users);
cfg.users);
extraVirtualAliases = {
"admin@${sp.domain}" = "${sp.username}@${sp.domain}";
"admin@${cfg.domain}" = "${cfg.username}@${cfg.domain}";
};
certificateScheme = "acme";
certificateScheme = 1;
certificateFile = "/var/lib/acme/${cfg.domain}/fullchain.pem";
keyFile = "/var/lib/acme/${cfg.domain}/key.pem";
# Enable IMAP and POP3
enableImap = true;

View File

@ -0,0 +1,16 @@
{ pkgs, config, ... }:
let
cfg = config.services.userdata;
in
{
services.bitwarden_rs = {
enable = cfg.bitwarden.enable;
dbBackend = "sqlite";
backupDir = "/var/lib/bitwarden/backup";
config = {
domain = "https://password.${cfg.domain}/";
signupsAllowed = true;
rocketPort = 8222;
};
};
}

View File

@ -22,8 +22,9 @@ config :pleroma, :media_proxy,
config :pleroma, Pleroma.Repo,
adapter: Ecto.Adapters.Postgres,
username: "pleroma",
password: "$DB_PASSWORD",
database: "pleroma",
socket_dir: "/run/postgresql",
hostname: "localhost",
pool_size: 10
#config :web_push_encryption, :vapid_details,
@ -40,4 +41,4 @@ config :pleroma, :http_security,
#config :joken, default_signer: ""
config :pleroma, configurable_from_database: true
config :pleroma, configurable_from_database: false

View File

@ -0,0 +1,133 @@
{ config, options, lib, pkgs, stdenv, ... }:
let
cfg = config.services.pleroma;
in
{
options = {
services.pleroma = with lib; {
enable = mkEnableOption "pleroma";
package = mkOption {
type = types.package;
default = pkgs.pleroma-otp;
description = "Pleroma package to use.";
};
user = mkOption {
type = types.str;
default = "pleroma";
description = "User account under which pleroma runs.";
};
group = mkOption {
type = types.str;
default = "pleroma";
description = "Group account under which pleroma runs.";
};
stateDir = mkOption {
type = types.str;
default = "/var/lib/pleroma";
readOnly = true;
description = "Directory where the pleroma service will save the uploads and static files.";
};
configs = mkOption {
type = with types; listOf str;
description = ''
Pleroma public configuration.
This list gets appended from left to
right into /etc/pleroma/config.exs. Elixir evaluates its
configuration imperatively, meaning you can override a
setting by appending a new str to this NixOS option list.
<emphasis>DO NOT STORE ANY PLEROMA SECRET
HERE</emphasis>, use
<link linkend="opt-services.pleroma.secretConfigFile">services.pleroma.secretConfigFile</link>
instead.
This setting is going to be stored in a file part of
the Nix store. The Nix store being world-readable, it's not
the right place to store any secret
Have a look to Pleroma section in the NixOS manual for more
informations.
'';
};
secretConfigFile = mkOption {
type = types.str;
default = "/var/lib/pleroma/secrets.exs";
description = ''
Path to the file containing your secret pleroma configuration.
<emphasis>DO NOT POINT THIS OPTION TO THE NIX
STORE</emphasis>, the store being world-readable, it'll
compromise all your secrets.
'';
};
};
};
config = lib.mkIf cfg.enable {
users = {
users."${cfg.user}" = {
description = "Pleroma user";
home = cfg.stateDir;
extraGroups = [ cfg.group ];
};
groups."${cfg.group}" = { };
};
environment.systemPackages = [ cfg.package ];
environment.etc."/pleroma/config.exs".text = ''
${lib.concatMapStrings (x: "${x}") cfg.configs}
# The lau/tzdata library is trying to download the latest
# timezone database in the OTP priv directory by default.
# This directory being in the store, it's read-only.
# Setting that up to a more appropriate location.
config :tzdata, :data_dir, "/var/lib/pleroma/elixir_tzdata_data"
import_config "${cfg.secretConfigFile}"
'';
systemd.services.pleroma = {
description = "Pleroma social network";
after = [ "network-online.target" "postgresql.service" ];
wantedBy = [ "multi-user.target" ];
restartTriggers = [ config.environment.etc."/pleroma/config.exs".source ];
serviceConfig = {
User = cfg.user;
Group = cfg.group;
Type = "exec";
WorkingDirectory = "~";
StateDirectory = "pleroma pleroma/static pleroma/uploads";
StateDirectoryMode = "700";
# Checking the conf file is there then running the database
# migration before each service start, just in case there are
# some pending ones.
#
# It's sub-optimal as we'll always run this, even if pleroma
# has not been updated. But the no-op process is pretty fast.
# Better be safe than sorry migration-wise.
ExecStartPre =
let preScript = pkgs.writers.writeBashBin "pleromaStartPre"
"${cfg.package}/bin/pleroma_ctl migrate";
in "${preScript}/bin/pleromaStartPre";
ExecStart = "${cfg.package}/bin/pleroma start";
ExecStop = "${cfg.package}/bin/pleroma stop";
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
# Systemd sandboxing directives.
# Taken from the upstream contrib systemd service at
# pleroma/installation/pleroma.service
PrivateTmp = true;
ProtectHome = true;
ProtectSystem = "full";
PrivateDevices = false;
NoNewPrivileges = true;
CapabilityBoundingSet = "~CAP_SYS_ADMIN";
};
};
};
meta.maintainers = with lib.maintainers; [ ninjatrappeur ];
}

View File

@ -0,0 +1,69 @@
{ lib
, stdenv
, autoPatchelfHook
, fetchurl
, file
, makeWrapper
, ncurses
, nixosTests
, openssl
, unzip
, zlib
}:
stdenv.mkDerivation {
pname = "pleroma-otp";
version = "2.3.0";
# To find the latest binary release stable link, have a look at
# the CI pipeline for the latest commit of the stable branch
# https://git.pleroma.social/pleroma/pleroma/-/tree/stable
src = {
aarch64-linux = fetchurl {
url = "https://git.pleroma.social/pleroma/pleroma/-/jobs/182392/artifacts/download";
sha256 = "1drpd6xh7m2damxi5impb8jwvjl6m3qv5yxynl12i8g66vi3rbwf";
};
x86_64-linux = fetchurl {
url = "https://git.pleroma.social/pleroma/pleroma/-/jobs/182388/artifacts/download";
sha256 = "1c6l04gga9iigm249ywwcrjg6wzy8iiid652mws3j9dnl71w2sim";
};
}."${stdenv.hostPlatform.system}";
nativeBuildInputs = [ unzip ];
buildInputs = [
autoPatchelfHook
file
makeWrapper
ncurses
openssl
zlib
];
# mkDerivation fails to detect the zip nature of $src due to the
# missing .zip extension.
# Let's unpack the archive explicitely.
unpackCmd = "unzip $curSrc";
installPhase = ''
mkdir $out
cp -r * $out'';
# Pleroma is using the project's root path (here the store path)
# as its TMPDIR.
# Patching it to move the tmp dir to the actual tmpdir
postFixup = ''
wrapProgram $out/bin/pleroma --set-default RELEASE_TMP "/tmp"
wrapProgram $out/bin/pleroma_ctl --set-default RELEASE_TMP "/tmp"'';
passthru.tests = {
pleroma = nixosTests.pleroma;
};
meta = with lib; {
description = "ActivityPub microblogging server";
homepage = https://git.pleroma.social/pleroma/pleroma;
license = licenses.agpl3;
maintainers = with maintainers; [ ninjatrappeur ];
platforms = [ "x86_64-linux" "aarch64-linux" ];
};
}

View File

@ -0,0 +1,43 @@
{ pkgs, config, ... }:
let
cfg = config.services.userdata;
in
{
nixpkgs.overlays = [
(self: super: {
pleroma-otp = self.callPackage ./pleroma-package.nix { };
})
];
services = {
pleroma = {
enable = cfg.pleroma.enable;
user = "pleroma";
group = "pleroma";
configs = [
(builtins.replaceStrings
[ "$DOMAIN" "$LUSER" "$DB_PASSWORD" ]
[ cfg.domain cfg.username cfg.databasePassword ]
(builtins.readFile ./config.exs))
];
};
postgresql = {
enable = true;
package = pkgs.postgresql_12;
initialScript = "/etc/setup.psql";
};
};
environment.etc."setup.psql".text = ''
CREATE USER pleroma WITH ENCRYPTED PASSWORD '${cfg.databasePassword}';
CREATE DATABASE pleroma OWNER pleroma;
\c pleroma;
--Extensions made by ecto.migrate that need superuser access
CREATE EXTENSION IF NOT EXISTS citext;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
'';
users.users.pleroma = {
extraGroups = [ "postgres" ];
isNormalUser = false;
isSystemUser = true;
};
}

View File

@ -0,0 +1,15 @@
{ pkgs, config, ... }:
let
domain = config.services.userdata.domain;
in
{
services.jitsi-meet = {
enable = config.services.userdata.jitsi.enable;
hostName = "meet.${domain}";
nginx.enable = false;
interfaceConfig = {
SHOW_JITSI_WATERMARK = false;
SHOW_WATERMARK_FOR_GUESTS = false;
};
};
}

View File

@ -0,0 +1,51 @@
{ pkgs, config, ... }:
let
domain = config.services.userdata.domain;
in
{
users.groups.ocserv = {
members = [ "ocserv" ];
};
users.users.ocserv = {
isNormalUser = false;
isSystemUser = true;
extraGroups = [ "ocserv" "acmerecievers" ];
};
services.ocserv = {
enable = config.services.userdata.ocserv.enable;
config = ''
socket-file = /var/run/ocserv-socket
auth = "pam"
tcp-port = 8443
udp-port = 8443
server-cert = /var/lib/acme/${domain}/fullchain.pem
server-key = /var/lib/acme/${domain}/key.pem
compression = true
max-clients = 0
max-same-clients = 6
try-mtu-discovery = true
idle-timeout=1200
mobile-idle-timeout=2400
default-domain = vpn.${domain}
device = vpn0
ipv4-network = 10.10.10.0
ipv4-netmask = 255.255.255.0
tunnel-all-dns = true
dns = 1.1.1.1
dns = 1.0.0.1
route = default
'';
};
}

View File

@ -0,0 +1,74 @@
{config, pkgs, ... }:
{
environment.etc."modsecurity/modsecurity_includes.conf".text = ''
Include /etc/modsecurity/modsecurity.conf
Include /etc/modsecurity/crs-setup.conf
Include ${pkgs.spdev.modsecurity-crs}/rules/*.conf
'';
environment.etc."modsecurity/modsecurity.conf".text = ''
SecRuleEngine On
SecRequestBodyAccess On
SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\+|/)|text/)xml" \
"id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML"
SecRule REQUEST_HEADERS:Content-Type "application/json" \
"id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"
SecRequestBodyLimit 13107200
SecRequestBodyNoFilesLimit 131072
SecRequestBodyLimitAction Reject
SecRule REQBODY_ERROR "!@eq 0" \
"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2"
SecRule MULTIPART_STRICT_ERROR "!@eq 0" \
"id:'200003',phase:2,t:none,log,deny,status:400, \
msg:'Multipart request body failed strict validation: \
PE %{REQBODY_PROCESSOR_ERROR}, \
BQ %{MULTIPART_BOUNDARY_QUOTED}, \
BW %{MULTIPART_BOUNDARY_WHITESPACE}, \
DB %{MULTIPART_DATA_BEFORE}, \
DA %{MULTIPART_DATA_AFTER}, \
HF %{MULTIPART_HEADER_FOLDING}, \
LF %{MULTIPART_LF_LINE}, \
SM %{MULTIPART_MISSING_SEMICOLON}, \
IQ %{MULTIPART_INVALID_QUOTING}, \
IP %{MULTIPART_INVALID_PART}, \
IH %{MULTIPART_INVALID_HEADER_FOLDING}, \
FL %{MULTIPART_FILE_LIMIT_EXCEEDED}'"
SecRule MULTIPART_UNMATCHED_BOUNDARY "@eq 1" \
"id:'200004',phase:2,t:none,log,deny,msg:'Multipart parser detected a possible unmatched boundary.'"
SecPcreMatchLimit 1000
SecPcreMatchLimitRecursion 1000
SecRule TX:/^MSC_/ "!@streq 0" \
"id:'200005',phase:2,t:none,deny,msg:'ModSecurity internal error flagged: %{MATCHED_VAR_NAME}'"
SecResponseBodyAccess On
SecResponseBodyMimeType text/plain text/html text/xml
SecResponseBodyLimit 524288
SecResponseBodyLimitAction ProcessPartial
SecTmpDir /tmp/
SecDataDir /tmp/
SecDebugLog /var/log/nginx/modsec_debug.log
SecDebugLogLevel 3
SecAuditEngine RelevantOnly
SecAuditLogRelevantStatus "^(?:5|4(?!04))"
SecAuditLogParts ABIJDEFHZ
SecAuditLogType Serial
SecAuditLog /var/log/nginx/modsec_audit.log
SecAuditLogFormat Json
SecArgumentSeparator &
SecCookieFormat 0
SecUnicodeMapFile ${pkgs.spdev.libmodsecurity}/share/modsecurity/unicode.mapping 20127
SecStatusEngine On
'';
environment.etc."modsecurity/crs-setup.conf".text = ''
SecDefaultAction "phase:1,log,auditlog,pass"
SecDefaultAction "phase:2,log,auditlog,pass"
SecCollectionTimeout 600
SecAction \
"id:900990,\
phase:1,\
nolog,\
pass,\
t:none,\
setvar:tx.crs_setup_version=332"
'';
}

View File

@ -0,0 +1,124 @@
{ lib, pkgs, config, ... }:
let
domain = config.services.userdata.domain;
in
{
systemd.services.nginx.serviceConfig.SystemCallFilter = lib.mkForce "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid";
services.nginx = {
enable = true;
enableReload = true;
package = pkgs.nginx;
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
clientMaxBodySize = "1024m";
virtualHosts = {
"${domain}" = {
sslCertificate = "/var/lib/acme/${domain}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/${domain}/key.pem";
forceSSL = true;
};
"vpn.${domain}" = {
sslCertificate = "/var/lib/acme/${domain}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/${domain}/key.pem";
forceSSL = true;
};
"git.${domain}" = {
sslCertificate = "/var/lib/acme/${domain}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/${domain}/key.pem";
forceSSL = true;
locations = {
"/" = {
proxyPass = "http://127.0.0.1:3000";
};
};
};
"cloud.${domain}" = {
sslCertificate = "/var/lib/acme/${domain}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/${domain}/key.pem";
forceSSL = true;
extraConfig = ''
modsecurity on;
modsecurity_rules_file /etc/modsecurity/modsecurity_includes.conf;
'';
locations = {
"/" = {
proxyPass = "http://127.0.0.1:80/";
};
};
};
"meet.${domain}" = {
forceSSL = true;
sslCertificate = "/var/lib/acme/${domain}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/${domain}/key.pem";
root = pkgs.jitsi-meet;
extraConfig = ''
ssi on;
'';
locations = {
"@root_path" = {
extraConfig = ''
rewrite ^/(.*)$ / break;
'';
};
"~ ^/([^/\\?&:'\"]+)$" = {
tryFiles = "$uri @root_path";
};
"=/http-bind" = {
proxyPass = "http://localhost:5280/http-bind";
extraConfig = ''
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
'';
};
"=/external_api.js" = {
alias = "${pkgs.jitsi-meet}/libs/external_api.min.js";
};
"=/config.js" = {
alias = "${pkgs.jitsi-meet}/config.js";
};
"=/interface_config.js" = {
alias = "${pkgs.jitsi-meet}/interface_config.js";
};
};
};
"password.${domain}" = {
sslCertificate = "/var/lib/acme/${domain}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/${domain}/key.pem";
forceSSL = true;
locations = {
"/" = {
proxyPass = "http://127.0.0.1:8222";
};
};
};
"api.${domain}" = {
sslCertificate = "/var/lib/acme/${domain}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/${domain}/key.pem";
forceSSL = true;
locations = {
"/" = {
proxyPass = "http://127.0.0.1:5050";
};
};
};
"social.${domain}" = {
sslCertificate = "/var/lib/acme/${domain}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/${domain}/key.pem";
root = "/var/www/social.${domain}";
forceSSL = true;
locations = {
"/" = {
proxyPass = "http://127.0.0.1:4000";
};
};
extraConfig = ''
client_max_body_size 1024m;
'';
};
};
};
}

48
generic/system/limits.nix Normal file
View File

@ -0,0 +1,48 @@
{ pkgs, ... }:
{
systemd.services = {
dovecot2 = {
serviceConfig = {
cpuAccounting = true;
cpuQuota = "20%";
memoryAccounting = true;
memoryMax = "256M";
startLimitIntervalSec = 500;
startLimitBurst = 5;
blockIOWeigth = 25;
};
};
postfix = {
serviceConfig = {
cpuAccounting = true;
cpuQuota = "20%";
memoryAccounting = true;
memoryMax = "256M";
startLimitIntervalSec = 500;
startLimitBurst = 5;
blockIOWeigth = 25;
};
};
ocserv = {
serviceConfig = {
cpuAccounting = true;
cpuQuota = "70%";
memoryAccounting = true;
memoryMax = "512M";
startLimitIntervalSec = 500;
startLimitBurst = 5;
};
};
nginx = {
serviceConfig = {
cpuAccounting = true;
cpuQuota = "70%";
memoryAccounting = true;
memoryMax = "768M";
startLimitIntervalSec = 500;
startLimitBurst = 5;
blockIOWeigth = 10;
};
};
};
}

View File

@ -0,0 +1,37 @@
{ config, pkgs, ... }:
let
cfg = config.services.userdata;
in
{
systemd.tmpfiles.rules =
let
nextcloudDBPass = builtins.replaceStrings [ "\n" "\"" "\\" ] [ "\\n" "\\\"" "\\\\" ] cfg.nextcloud.databasePassword;
nextcloudAdminPass = builtins.replaceStrings [ "\n" "\"" "\\" ] [ "\\n" "\\\"" "\\\\" ] cfg.nextcloud.adminPassword;
resticPass = builtins.replaceStrings [ "\n" "\"" "\\" ] [ "\\n" "\\\"" "\\\\" ] cfg.resticPassword;
domain = builtins.replaceStrings [ "\n" "\"" "\\" ] [ "\\n" "\\\"" "\\\\" ] cfg.domain;
cloudflareCredentials = builtins.replaceStrings [ "\n" "\"" "\\" ] [ "\\n" "\\\"" "\\\\" ] ''
CF_API_KEY=${cfg.cloudflare.apiKey}
CLOUDFLARE_DNS_API_TOKEN=${cfg.cloudflare.apiKey}
CLOUDFLARE_ZONE_API_TOKEN=${cfg.cloudflare.apiKey}
'';
rcloneConfig = builtins.replaceStrings [ "\n" "\"" "\\" ] [ "\\n" "\\\"" "\\\\" ] ''
[backblaze]
type = b2
account = ${cfg.backblaze.accountId}
key = ${cfg.backblaze.accountKey}
'';
in
[
(if cfg.bitwarden.enable then "d /var/lib/bitwarden 0777 bitwarden_rs bitwarden_rs -" else "")
(if cfg.bitwarden.enable then "d /var/lib/bitwarden/backup 0777 bitwarden_rs bitwarden_rs -" else "")
(if cfg.pleroma.enable then "d /var/lib/pleroma 0700 pleroma pleroma - -" else "")
"d /var/lib/restic 0600 restic - - -"
"f+ /var/lib/restic/pass 0400 restic - - ${resticPass}"
"f+ /root/.config/rclone/rclone.conf 0400 root root - ${rcloneConfig}"
(if cfg.pleroma.enable then "f /var/lib/pleroma/secrets.exs 0755 pleroma pleroma - -" else "")
"f+ /var/domain 0444 selfprivacy-api selfprivacy-api - ${domain}"
(if cfg.nextcloud.enable then "f+ /var/lib/nextcloud/db-pass 0440 nextcloud nextcloud - ${nextcloudDBPass}" else "")
(if cfg.nextcloud.enable then "f+ /var/lib/nextcloud/admin-pass 0440 nextcloud nextcloud - ${nextcloudAdminPass}" else "")
"f+ /var/lib/cloudflare/Credentials.ini 0440 nginx acmerecievers - ${cloudflareCredentials}"
];
}

View File

@ -0,0 +1,6 @@
{ pkgs, ... }:
{
services = {
userdata = builtins.fromJSON (builtins.readFile ./../../userdata/userdata.json);
};
}

View File

@ -1,11 +1,10 @@
{ config, ... }:
{ pkgs, config, ... }:
let
cfg = config.selfprivacy;
cfg = config.services.userdata;
in
{
users.mutableUsers = false;
users = {
mutableUsers = false;
allowNoPasswordLogin = true;
users = {
"${cfg.username}" = {
isNormalUser = true;
@ -18,7 +17,7 @@ in
value = {
isNormalUser = true;
hashedPassword = user.hashedPassword;
openssh.authorizedKeys.keys = (if user ? sshKeys then user.sshKeys else [ ]);
openssh.authorizedKeys.keys = (if user ? sshKeys then user.sshKeys else []);
};
})
cfg.users);

View File

@ -1,63 +0,0 @@
{ config, lib, pkgs, ... }:
let
cfg = config.selfprivacy;
dnsCredentialsTemplates = {
DIGITALOCEAN = "DO_AUTH_TOKEN=$TOKEN";
CLOUDFLARE = ''
CF_API_KEY=$TOKEN
CLOUDFLARE_DNS_API_TOKEN=$TOKEN
CLOUDFLARE_ZONE_API_TOKEN=$TOKEN
CLOUDFLARE_POLLING_INTERVAL=30
'';
DESEC = ''
DESEC_TOKEN=$TOKEN
DESEC_POLLING_INTERVAL=30
DESEC_PROPAGATION_TIMEOUT=180
DESEC_TTL=3600
'';
};
dnsCredentialsTemplate = dnsCredentialsTemplates.${cfg.dns.provider};
acme-env-filepath = "/var/lib/selfprivacy/acme-env";
secrets-filepath = "/etc/selfprivacy/secrets.json";
dnsPropagationCheckExceptions = [ "DIGITALOCEAN" "DESEC" ];
in
{
users.groups.acmereceivers.members = [ "nginx" ];
security.acme = {
acceptTerms = true;
defaults = {
email = "${cfg.username}@${cfg.domain}";
server = if cfg.dns.useStagingACME then "https://acme-staging-v02.api.letsencrypt.org/directory" else "https://acme-v02.api.letsencrypt.org/directory";
reloadServices = [ "nginx" ];
};
certs = {
"${cfg.domain}" = {
domain = "*.${cfg.domain}";
extraDomainNames = [ "${cfg.domain}" ];
group = "acmereceivers";
dnsProvider = lib.strings.toLower cfg.dns.provider;
credentialsFile = acme-env-filepath;
dnsPropagationCheck =
! (lib.elem cfg.dns.provider dnsPropagationCheckExceptions);
};
};
};
systemd.services.acme-secrets = {
before = [ "acme-${cfg.domain}.service" ];
requiredBy = [ "acme-${cfg.domain}.service" ];
serviceConfig.Type = "oneshot";
path = with pkgs; [ coreutils jq ];
script = ''
set -o nounset
TOKEN="$(jq -re '.dns.apiKey' ${secrets-filepath})"
filecontents=$(cat <<- EOF
${dnsCredentialsTemplate}
EOF
)
install -m 0440 -o root -g acmereceivers -DT \
<(printf "%s\n" "$filecontents") ${acme-env-filepath}
'';
};
}

View File

@ -1,47 +0,0 @@
{
systemd.services = {
dovecot2 = {
serviceConfig = {
CpuAccounting = true;
CpuQuota = "20%";
MemoryAccounting = true;
MemoryMax = "256M";
StartLimitIntervalSec = 500;
StartLimitBurst = 5;
BlockIOWeight = 25;
};
};
postfix = {
serviceConfig = {
CpuAccounting = true;
CpuQuota = "20%";
MemoryAccounting = true;
MemoryMax = "256M";
StartLimitIntervalSec = 500;
StartLimitBurst = 5;
BlockIOWeight = 25;
};
};
ocserv = {
serviceConfig = {
CpuAccounting = true;
CpuQuota = "70%";
MemoryAccounting = true;
MemoryMax = "512M";
StartLimitIntervalSec = 500;
StartLimitBurst = 5;
};
};
nginx = {
serviceConfig = {
CpuAccounting = true;
CpuQuota = "70%";
MemoryAccounting = true;
MemoryMax = "768M";
StartLimitIntervalSec = 500;
StartLimitBurst = 5;
BlockIOWeight = 10;
};
};
};
}

View File

@ -1,17 +0,0 @@
{ config, lib, ... }:
let
inherit (import ./common.nix config) bitwarden-env sp;
in
# FIXME do we really want to delete passwords on module deactivation!?
{
config = lib.mkIf (!sp.modules.bitwarden.enable) {
system.activationScripts.bitwarden =
lib.trivial.warn
(
"bitwarden service is disabled, ${bitwarden-env} will be removed!"
)
''
rm -f -v ${bitwarden-env}
'';
};
}

View File

@ -1,5 +0,0 @@
config:
{
sp = config.selfprivacy;
bitwarden-env = "/var/lib/bitwarden/.env";
}

View File

@ -1,5 +0,0 @@
[
[ "selfprivacy", "domain" ],
[ "selfprivacy", "useBinds" ],
[ "selfprivacy", "modules", "bitwarden" ]
]

View File

@ -1,10 +0,0 @@
{
description = "PoC SP module for Bitwarden password management solution";
outputs = { self }: {
nixosModules.default = _:
{ imports = [ ./module.nix ./cleanup-module.nix ]; };
configPathsNeeded =
builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
};
}

View File

@ -1,107 +0,0 @@
{ config, lib, pkgs, ... }:
let
secrets-filepath = "/etc/selfprivacy/secrets.json";
backup-dir = "/var/lib/bitwarden/backup";
cfg = sp.modules.bitwarden;
inherit (import ./common.nix config) bitwarden-env sp;
in
{
options.selfprivacy.modules.bitwarden = {
enable = lib.mkOption {
default = false;
type = lib.types.bool;
};
location = lib.mkOption {
type = lib.types.str;
};
subdomain = lib.mkOption {
default = "password";
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
};
};
config = lib.mkIf config.selfprivacy.modules.bitwarden.enable {
fileSystems = lib.mkIf sp.useBinds {
"/var/lib/bitwarden" = {
device = "/volumes/${cfg.location}/bitwarden";
options = [
"bind"
"x-systemd.required-by=bitwarden-secrets.service"
"x-systemd.required-by=backup-vaultwarden.service"
"x-systemd.required-by=vaultwarden.service"
"x-systemd.before=bitwarden-secrets.service"
"x-systemd.before=backup-vaultwarden.service"
"x-systemd.before=vaultwarden.service"
];
};
"/var/lib/bitwarden_rs" = {
device = "/volumes/${cfg.location}/bitwarden_rs";
options = [
"bind"
"x-systemd.required-by=bitwarden-secrets.service"
"x-systemd.required-by=backup-vaultwarden.service"
"x-systemd.required-by=vaultwarden.service"
"x-systemd.before=bitwarden-secrets.service"
"x-systemd.before=backup-vaultwarden.service"
"x-systemd.before=vaultwarden.service"
];
};
};
services.vaultwarden = {
enable = true;
dbBackend = "sqlite";
backupDir = backup-dir;
environmentFile = "${bitwarden-env}";
config = {
domain = "https://${cfg.subdomain}.${sp.domain}/";
signupsAllowed = true;
rocketPort = 8222;
};
};
systemd.services.bitwarden-secrets = {
before = [ "vaultwarden.service" ];
requiredBy = [ "vaultwarden.service" ];
serviceConfig.Type = "oneshot";
path = with pkgs; [ coreutils jq ];
script = ''
set -o nounset
token="$(jq -r '.bitwarden.adminToken' ${secrets-filepath})"
if [ "$token" == "null" ]; then
# If it's null, empty the contents of the file
bitwarden_env=""
else
bitwarden_env="ADMIN_TOKEN=$token"
fi
install -C -m 0700 -o vaultwarden -g vaultwarden \
-d /var/lib/bitwarden
install -C -m 0600 -o vaultwarden -g vaultwarden -DT \
<(printf "%s" "$bitwarden_env") ${bitwarden-env}
'';
};
services.nginx.virtualHosts."${cfg.subdomain}.${sp.domain}" = {
useACMEHost = sp.domain;
forceSSL = true;
extraConfig = ''
add_header Strict-Transport-Security $hsts_header;
#add_header Content-Security-Policy "script-src 'self'; object-src 'none'; base-uri 'none';" always;
add_header 'Referrer-Policy' 'origin-when-cross-origin';
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
expires 10m;
'';
locations = {
"/" = {
proxyPass = "http://127.0.0.1:8222";
};
};
};
# NixOS upstream bug? Otherwise, backup-vaultwarden cannot find sqlite DB.
systemd.services.backup-vaultwarden.unitConfig.ConditionPathExists =
"/var/lib/bitwarden_rs/db.sqlite3";
};
}

View File

@ -1,5 +0,0 @@
[
[ "selfprivacy", "domain" ],
[ "selfprivacy", "useBinds" ],
[ "selfprivacy", "modules", "gitea" ]
]

View File

@ -1,9 +0,0 @@
{
description = "PoC SP module for Gitea forge service";
outputs = { self }: {
nixosModules.default = import ./module.nix;
configPathsNeeded =
builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
};
}

View File

@ -1,113 +0,0 @@
{ config, lib, ... }:
let
sp = config.selfprivacy;
stateDir =
if sp.useBinds
then "/volumes/${cfg.location}/gitea"
else "/var/lib/gitea";
cfg = sp.modules.gitea;
in
{
options.selfprivacy.modules.gitea = {
enable = lib.mkOption {
default = false;
type = lib.types.bool;
};
location = lib.mkOption {
type = lib.types.str;
};
subdomain = lib.mkOption {
default = "git";
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
};
};
config = lib.mkIf cfg.enable {
fileSystems = lib.mkIf sp.useBinds {
"/var/lib/gitea" = {
device = "/volumes/${cfg.location}/gitea";
options = [ "bind" ];
};
};
services.gitea = {
enable = true;
inherit stateDir;
# log = {
# rootPath = "/var/lib/gitea/log";
# level = "Warn";
# };
user = "gitea";
database = {
type = "sqlite3";
host = "127.0.0.1";
name = "gitea";
user = "gitea";
path = "${stateDir}/data/gitea.db";
createDatabase = true;
};
# ssh = {
# enable = true;
# clonePort = 22;
# };
lfs = {
enable = true;
contentDir = "${stateDir}/lfs";
};
appName = "SelfPrivacy git Service";
repositoryRoot = "${stateDir}/repositories";
# cookieSecure = true;
settings = {
server = {
DOMAIN = "${cfg.subdomain}.${sp.domain}";
ROOT_URL = "https://${cfg.subdomain}.${sp.domain}/";
HTTP_ADDR = "0.0.0.0";
HTTP_PORT = 3000;
};
mailer = {
ENABLED = false;
};
ui = {
DEFAULT_THEME = "arc-green";
SHOW_USER_EMAIL = false;
};
picture = {
DISABLE_GRAVATAR = true;
};
admin = {
ENABLE_KANBAN_BOARD = true;
};
repository = {
FORCE_PRIVATE = false;
};
session = {
COOKIE_SECURE = true;
};
log = {
ROOT_PATH = "${stateDir}/log";
LEVEL = "Warn";
};
};
};
services.nginx.virtualHosts."${cfg.subdomain}.${sp.domain}" = {
useACMEHost = sp.domain;
forceSSL = true;
extraConfig = ''
add_header Strict-Transport-Security $hsts_header;
#add_header Content-Security-Policy "script-src 'self'; object-src 'none'; base-uri 'none';" always;
add_header 'Referrer-Policy' 'origin-when-cross-origin';
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
expires 10m;
'';
locations = {
"/" = {
proxyPass = "http://127.0.0.1:3000";
};
};
};
systemd.services.gitea.unitConfig.RequiresMountsFor =
lib.mkIf sp.useBinds "/volumes/${cfg.location}/gitea";
};
}

View File

@ -1,4 +0,0 @@
[
[ "selfprivacy", "domain" ],
[ "selfprivacy", "modules", "jitsi-meet" ]
]

View File

@ -1,9 +0,0 @@
{
description = "PoC SP module for Jitsi Meet video conferences server";
outputs = { self }: {
nixosModules.default = import ./module.nix;
configPathsNeeded =
builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
};
}

View File

@ -1,34 +0,0 @@
{ config, lib, ... }:
let
domain = config.selfprivacy.domain;
cfg = config.selfprivacy.modules.jitsi-meet;
in
{
options.selfprivacy.modules.jitsi-meet = {
enable = lib.mkOption {
default = false;
type = lib.types.bool;
};
subdomain = lib.mkOption {
default = "meet";
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
};
};
config = lib.mkIf cfg.enable {
services.jitsi-meet = {
enable = true;
hostName = "${cfg.subdomain}.${domain}";
nginx.enable = true;
interfaceConfig = {
SHOW_JITSI_WATERMARK = false;
SHOW_WATERMARK_FOR_GUESTS = false;
};
};
services.nginx.virtualHosts."${cfg.subdomain}.${domain}" = {
forceSSL = true;
useACMEHost = domain;
enableACME = false;
};
};
}

View File

@ -1,19 +0,0 @@
{ config, lib, ... }:
let
inherit (import ./common.nix config) sp db-pass-filepath admin-pass-filepath;
in
# FIXME do we really want to delete passwords on module deactivation!?
{
config = lib.mkIf (!sp.modules.nextcloud.enable) {
system.activationScripts.nextcloudSecrets =
lib.trivial.warn
(
"nextcloud service is disabled, " +
"${db-pass-filepath} and ${admin-pass-filepath} will be removed!"
)
''
rm -f -v ${db-pass-filepath}
rm -f -v ${admin-pass-filepath}
'';
};
}

View File

@ -1,6 +0,0 @@
config: rec {
sp = config.selfprivacy;
secrets-filepath = "/etc/selfprivacy/secrets.json";
db-pass-filepath = "/var/lib/nextcloud/db-pass";
admin-pass-filepath = "/var/lib/nextcloud/admin-pass";
}

View File

@ -1,5 +0,0 @@
[
[ "selfprivacy", "domain" ],
[ "selfprivacy", "useBinds" ],
[ "selfprivacy", "modules", "nextcloud" ]
]

View File

@ -1,10 +0,0 @@
{
description = "PoC SP module for nextcloud";
outputs = { self }: {
nixosModules.default = _:
{ imports = [ ./module.nix ./cleanup-module.nix ]; };
configPathsNeeded =
builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
};
}

View File

@ -1,85 +0,0 @@
{ config, lib, pkgs, ... }:
{
options.selfprivacy.modules.nextcloud = with lib; {
enable = mkOption {
type = types.bool;
default = false;
};
location = mkOption {
type = types.str;
};
subdomain = lib.mkOption {
default = "cloud";
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
};
};
config =
let
inherit (import ./common.nix config)
sp secrets-filepath db-pass-filepath admin-pass-filepath;
cfg = sp.modules.nextcloud;
hostName = "${cfg.subdomain}.${sp.domain}";
in
lib.mkIf sp.modules.nextcloud.enable {
fileSystems = lib.mkIf sp.useBinds {
"/var/lib/nextcloud" = {
device = "/volumes/${cfg.location}/nextcloud";
options = [
"bind"
"x-systemd.required-by=nextcloud-setup.service"
"x-systemd.required-by=nextcloud-secrets.service"
"x-systemd.before=nextcloud-setup.service"
"x-systemd.before=nextcloud-secrets.service"
];
};
};
systemd.services.nextcloud-secrets = {
before = [ "nextcloud-setup.service" ];
requiredBy = [ "nextcloud-setup.service" ];
serviceConfig.Type = "oneshot";
path = with pkgs; [ coreutils jq ];
script = ''
databasePassword=$(jq -re '.modules.nextcloud.databasePassword' ${secrets-filepath})
adminPassword=$(jq -re '.modules.nextcloud.adminPassword' ${secrets-filepath})
install -C -m 0440 -o nextcloud -g nextcloud -DT \
<(printf "%s\n" "$databasePassword") \
${db-pass-filepath}
install -C -m 0440 -o nextcloud -g nextcloud -DT \
<(printf "%s\n" "$adminPassword") \
${admin-pass-filepath}
'';
};
services.nextcloud = {
enable = true;
package = pkgs.nextcloud26;
inherit hostName;
# Use HTTPS for links
https = true;
# auto-update Nextcloud Apps
autoUpdateApps.enable = true;
# set what time makes sense for you
autoUpdateApps.startAt = "05:00:00";
config = {
# further forces Nextcloud to use HTTPS
overwriteProtocol = "https";
dbtype = "sqlite";
dbuser = "nextcloud";
dbname = "nextcloud";
dbpassFile = db-pass-filepath;
adminpassFile = admin-pass-filepath;
adminuser = "admin";
};
};
services.nginx.virtualHosts.${hostName} = {
useACMEHost = sp.domain;
forceSSL = true;
};
};
}

View File

@ -1,5 +0,0 @@
[
[ "security", "acme", "certs" ],
[ "selfprivacy", "domain" ],
[ "selfprivacy", "modules", "ocserv" ]
]

View File

@ -1,9 +0,0 @@
{
description = "PoC SP module for OpenConnect VPN server (ocserv)";
outputs = { self }: {
nixosModules.default = import ./module.nix;
configPathsNeeded =
builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
};
}

View File

@ -1,81 +0,0 @@
{ config, lib, ... }:
let
domain = config.selfprivacy.domain;
cert = "${config.security.acme.certs.${domain}.directory}/fullchain.pem";
key = "${config.security.acme.certs.${domain}.directory}/key.pem";
cfg = config.selfprivacy.modules.ocserv;
in
{
options.selfprivacy.modules.ocserv = {
enable = lib.mkOption {
default = false;
type = lib.types.bool;
};
subdomain = lib.mkOption {
default = "vpn";
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
};
};
config = lib.mkIf cfg.enable {
users.groups.ocserv.members = [ "ocserv" ];
users.users.ocserv = {
isNormalUser = false;
isSystemUser = true;
extraGroups = [ "acmereceivers" ];
group = "ocserv";
};
services.ocserv = {
enable = true;
config = ''
socket-file = /var/run/ocserv-socket
auth = "pam"
tcp-port = 8443
udp-port = 8443
server-cert = ${cert}
server-key = ${key}
compression = true
max-clients = 0
max-same-clients = 6
try-mtu-discovery = true
idle-timeout=1200
mobile-idle-timeout=2400
default-domain = ${cfg.subdomain}.${domain}
device = vpn0
ipv4-network = 10.10.10.0
ipv4-netmask = 255.255.255.0
tunnel-all-dns = true
dns = 1.1.1.1
dns = 1.0.0.1
route = default
'';
};
services.nginx.virtualHosts."${cfg.subdomain}.${domain}" = {
useACMEHost = domain;
forceSSL = true;
extraConfig = ''
add_header Strict-Transport-Security $hsts_header;
#add_header Content-Security-Policy "script-src 'self'; object-src 'none'; base-uri 'none';" always;
add_header 'Referrer-Policy' 'origin-when-cross-origin';
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
expires 10m;
'';
};
systemd.services.ocserv.unitConfig.ConditionPathExists = [ cert key ];
};
}

View File

@ -1,17 +0,0 @@
{ config, lib, ... }:
let
inherit (import ./common.nix config) secrets-exs sp;
in
# FIXME do we really want to delete passwords on module deactivation!?
{
config = lib.mkIf (!sp.modules.pleroma.enable) {
system.activationScripts.pleroma =
lib.trivial.warn
(
"pleroma service is disabled, ${secrets-exs} will be removed!"
)
''
rm -f -v ${secrets-exs}
'';
};
}

View File

@ -1,5 +0,0 @@
config:
{
sp = config.selfprivacy;
secrets-exs = "/var/lib/pleroma/secrets.exs";
}

View File

@ -1,6 +0,0 @@
[
[ "selfprivacy", "domain" ],
[ "selfprivacy", "username" ],
[ "selfprivacy", "useBinds" ],
[ "selfprivacy", "modules", "pleroma" ]
]

View File

@ -1,9 +0,0 @@
{
description = "PoC SP module for Pleroma lightweight fediverse server";
outputs = { self }: {
nixosModules.default = import ./module.nix;
configPathsNeeded =
builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
};
}

View File

@ -1,131 +0,0 @@
{ config, lib, pkgs, ... }:
let
secrets-filepath = "/etc/selfprivacy/secrets.json";
cfg = config.selfprivacy.modules.pleroma;
inherit (import ./common.nix config) secrets-exs sp;
in
{
options.selfprivacy.modules.pleroma = {
enable = lib.mkOption {
default = false;
type = lib.types.bool;
};
location = lib.mkOption {
type = lib.types.str;
};
subdomain = lib.mkOption {
default = "social";
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
};
};
config = lib.mkIf cfg.enable {
fileSystems = lib.mkIf sp.useBinds {
"/var/lib/pleroma" = {
device = "/volumes/${cfg.location}/pleroma";
options = [
"bind"
"x-systemd.required-by=pleroma-secrets.service"
"x-systemd.required-by=pleroma.service"
"x-systemd.before=pleroma.service"
"x-systemd.before=pleroma-secrets.service"
];
};
"/var/lib/postgresql" = {
device = "/volumes/${cfg.location}/postgresql";
options = [
"bind"
"x-systemd.required-by=pleroma-secrets.service"
"x-systemd.required-by=pleroma.service"
"x-systemd.before=pleroma-secrets.service"
"x-systemd.before=pleroma.service"
];
};
};
services = {
pleroma = {
enable = true;
user = "pleroma";
group = "pleroma";
configs = [
(builtins.replaceStrings
[ "$DOMAIN" "$LUSER" ]
[ sp.domain sp.username ]
(builtins.readFile ./config.exs.in))
];
};
postgresql = {
enable = true;
package = pkgs.postgresql_12;
initialScript = "/etc/setup.psql";
ensureDatabases = [
"pleroma"
];
ensureUsers = [
{
name = "pleroma";
ensureDBOwnership = true;
}
];
};
};
systemd.services.pleroma-secrets = {
before = [ "pleroma.service" ];
requiredBy = [ "pleroma.service" ];
serviceConfig.Type = "oneshot";
path = with pkgs; [ coreutils jq ];
script = ''
set -o nounset
password="$(jq -re '.databasePassword' ${secrets-filepath})"
filecontents=$(cat <<- EOF
import Config
config :pleroma, Pleroma.Repo,
password: "$password"
EOF
)
install -C -m 0700 -o pleroma -g pleroma -d /var/lib/pleroma
install -C -m 0600 -o pleroma -g pleroma -DT \
<(printf "%s" "$filecontents") ${secrets-exs}
'';
};
environment.etc."setup.psql".text = ''
CREATE USER pleroma;
CREATE DATABASE pleroma OWNER pleroma;
\c pleroma;
--Extensions made by ecto.migrate that need superuser access
CREATE EXTENSION IF NOT EXISTS citext;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
'';
users.users.pleroma = {
extraGroups = [ "postgres" ];
isNormalUser = false;
isSystemUser = true;
group = "pleroma";
};
# seems to be an upstream nixpkgs/nixos bug (missing hexdump)
systemd.services.pleroma.path = [ pkgs.util-linux ];
services.nginx.virtualHosts."${cfg.subdomain}.${sp.domain}" = {
useACMEHost = sp.domain;
root = "/var/www/${cfg.subdomain}.${sp.domain}";
forceSSL = true;
extraConfig = ''
add_header Strict-Transport-Security $hsts_header;
#add_header Content-Security-Policy "script-src 'self'; object-src 'none'; base-uri 'none';" always;
add_header 'Referrer-Policy' 'origin-when-cross-origin';
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
expires 10m;
'';
locations = {
"/" = {
proxyPass = "http://127.0.0.1:4000";
};
};
};
};
}

View File

@ -1,16 +0,0 @@
[
[ "mailserver" ],
[ "security", "acme", "certs" ],
[ "selfprivacy", "domain" ],
[ "selfprivacy", "hashedMasterPassword" ],
[ "selfprivacy", "useBinds" ],
[ "selfprivacy", "username" ],
[ "selfprivacy", "users" ],
[ "services", "dovecot2" ],
[ "services", "opendkim" ],
[ "services", "postfix", "group" ],
[ "services", "postfix", "user" ],
[ "services", "redis" ],
[ "services", "rspamd" ],
[ "selfprivacy", "modules", "simple-nixos-mailserver" ]
]

View File

@ -1,126 +0,0 @@
{
"nodes": {
"blobs": {
"flake": false,
"locked": {
"lastModified": 1604995301,
"narHash": "sha256-wcLzgLec6SGJA8fx1OEN1yV/Py5b+U5iyYpksUY/yLw=",
"owner": "simple-nixos-mailserver",
"repo": "blobs",
"rev": "2cccdf1ca48316f2cfd1c9a0017e8de5a7156265",
"type": "gitlab"
},
"original": {
"owner": "simple-nixos-mailserver",
"repo": "blobs",
"type": "gitlab"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1668681692,
"narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "009399224d5e398d03b22badca40a37ac85412a1",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"mailserver": {
"inputs": {
"blobs": "blobs",
"flake-compat": "flake-compat",
"nixpkgs": "nixpkgs",
"nixpkgs-22_11": "nixpkgs-22_11",
"nixpkgs-23_05": "nixpkgs-23_05",
"utils": "utils"
},
"locked": {
"lastModified": 1700085753,
"narHash": "sha256-qtib7f3eRwfaUF+VziJXiBcZFqpHCAXS4HlrFsnzzl4=",
"owner": "simple-nixos-mailserver",
"repo": "nixos-mailserver",
"rev": "008d78cc21959e33d0d31f375b88353a7d7121ae",
"type": "gitlab"
},
"original": {
"owner": "simple-nixos-mailserver",
"repo": "nixos-mailserver",
"type": "gitlab"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1670751203,
"narHash": "sha256-XdoH1v3shKDGlrwjgrNX/EN8s3c+kQV7xY6cLCE8vcI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "64e0bf055f9d25928c31fb12924e59ff8ce71e60",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-unstable",
"type": "indirect"
}
},
"nixpkgs-22_11": {
"locked": {
"lastModified": 1669558522,
"narHash": "sha256-yqxn+wOiPqe6cxzOo4leeJOp1bXE/fjPEi/3F/bBHv8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "ce5fe99df1f15a09a91a86be9738d68fadfbad82",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-22.11",
"type": "indirect"
}
},
"nixpkgs-23_05": {
"locked": {
"lastModified": 1684782344,
"narHash": "sha256-SHN8hPYYSX0thDrMLMWPWYulK3YFgASOrCsIL3AJ78g=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "8966c43feba2c701ed624302b6a935f97bcbdf88",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-23.05",
"type": "indirect"
}
},
"root": {
"inputs": {
"mailserver": "mailserver"
}
},
"utils": {
"locked": {
"lastModified": 1605370193,
"narHash": "sha256-YyMTf3URDL/otKdKgtoMChu4vfVL3vCMkRqpGifhUn0=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "5021eac20303a61fafe17224c087f5519baed54d",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@ -1,22 +0,0 @@
{
description = "PoC SP module for the simple-nixos-mailserver";
inputs.mailserver.url =
gitlab:simple-nixos-mailserver/nixos-mailserver;
outputs = { self, mailserver }: {
nixosModules.default = _: {
imports = [
mailserver.nixosModules.default
./options.nix
./config.nix
];
};
configPathsNeeded =
builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
# TODO generate json docs from module? something like:
# nix eval --impure --expr 'let flake = builtins.getFlake (builtins.toPath ./.); pkgs = flake.inputs.mailserver.inputs.nixpkgs.legacyPackages.x86_64-linux; in (pkgs.nixosOptionsDoc { inherit (pkgs.lib.evalModules { modules = [ flake.nixosModules.default ]; }) options; }).optionsJSON'
# (doesn't work because of `assertions`)
};
}

View File

@ -1,12 +0,0 @@
{ lib, ... }:
{
options.selfprivacy.modules.simple-nixos-mailserver = {
enable = lib.mkOption {
default = false;
type = lib.types.bool;
};
location = lib.mkOption {
type = lib.types.str;
};
};
}

184
userdata/schema.json Normal file
View File

@ -0,0 +1,184 @@
{
"$schema": "http://json-schema.org/schema#",
"$id": "https://git.selfprivacy.org/inex/selfprivacy-nixos-config/raw/branch/master/userdata/schema.json",
"type": "object",
"properties": {
"autoUpgrade": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
},
"allowReboot": {
"type": "boolean"
}
}
},
"hostname": {
"type": "string"
},
"domain": {
"type": "string"
},
"username": {
"type": "string"
},
"hashedMasterPassword": {
"type": "string"
},
"sshKeys": {
"type": "array",
"items": {
"type": "string"
}
},
"timezone": {
"type": "string"
},
"api": {
"type": "object",
"properties": {
"token": {
"type": "string"
},
"enableSwagger": {
"type": "boolean"
}
}
},
"backblaze": {
"type": "object",
"properties": {
"bucket": {
"type": "string"
},
"accountId": {
"type": "string"
},
"accountKey": {
"type": "string"
}
},
"required": ["bucket", "accountId", "accountKey"]
},
"cloudflare": {
"type": "object",
"properties": {
"apiKey": {
"type": "string"
}
},
"required": ["apiKey"]
},
"databasePassword": {
"type": "string"
},
"bitwarden": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
}
}
},
"gitea": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
}
}
},
"nextcloud": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
},
"databasePassword": {
"type": "string"
},
"adminPassword": {
"type": "string"
}
},
"required": ["databasePassword", "adminPassword"]
},
"pleroma": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
}
}
},
"jitsi": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
}
}
},
"ocserv": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
}
}
},
"resticPassword": {
"type": "string"
},
"ssh": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
},
"rootKeys": {
"type": "array",
"items": {
"type": "string"
}
},
"passwordAuthentication": {
"type": "boolean"
}
}
},
"users": {
"type": "array",
"items": {
"type": "object",
"properties": {
"username": {
"type": "string"
},
"hashedPassword": {
"type": "string"
},
"sshKeys": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": ["username", "hashedPassword"]
}
}
},
"required": [
"hostname",
"domain",
"username",
"hashedMasterPassword",
"backblaze",
"cloudflare",
"databasePassword",
"nextcloud",
"resticPassword"
]
}

View File

@ -1,15 +0,0 @@
{ config, ... }:
let
cfg = config.selfprivacy;
in
{
fileSystems = builtins.listToAttrs (builtins.map
(volume: {
name = "${volume.mountPoint}";
value = {
device = "${volume.device}";
fsType = "${volume.fsType}";
};
})
cfg.volumes);
}

View File

@ -1,67 +0,0 @@
{ config, lib, ... }:
let
domain = config.selfprivacy.domain;
in
{
services.nginx = {
enable = true;
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
sslProtocols = lib.mkForce "TLSv1.2 TLSv1.3";
sslCiphers = lib.mkForce "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:!SHA1:!SHA256:!SHA384:!DSS:!aNULL";
clientMaxBodySize = "1024m";
commonHttpConfig = ''
map $scheme $hsts_header {
https "max-age=31536000; includeSubdomains; preload";
}
proxy_headers_hash_bucket_size 128;
proxy_headers_hash_max_size 512;
'';
virtualHosts = {
"${domain}" = {
useACMEHost = domain;
forceSSL = true;
extraConfig = ''
add_header Strict-Transport-Security $hsts_header;
#add_header Content-Security-Policy "script-src 'self'; object-src 'none'; base-uri 'none';" always;
add_header 'Referrer-Policy' 'origin-when-cross-origin';
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
expires 10m;
'';
locations = {
"/" = {
root = "/var/www/root";
};
};
};
"api.${domain}" = {
useACMEHost = domain;
forceSSL = true;
extraConfig = ''
add_header Strict-Transport-Security $hsts_header;
#add_header Content-Security-Policy "script-src 'self'; object-src 'none'; base-uri 'none';" always;
add_header 'Referrer-Policy' 'origin-when-cross-origin';
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
expires 10m;
'';
locations = {
"/" = {
proxyPass = "http://127.0.0.1:5050";
proxyWebsockets = true;
};
};
};
};
};
systemd.tmpfiles.rules = [
"d /var/www/root 0750 nginx nginx - -"
];
}