Compare commits

..

No commits in common. "master" and "master" have entirely different histories.

26 changed files with 532 additions and 524 deletions

4
.gitignore vendored
View File

@ -1,5 +1,3 @@
userdata/userdata.json
userdata/tokens.json
hardware-configuration.nix
networking.nix
/result
networking.nix

View File

@ -12,12 +12,49 @@ in
{
options.services.selfprivacy-api = {
enable = mkOption {
default = true;
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 {
@ -27,67 +64,19 @@ in
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.rclone
pkgs.restic
pkgs.mkpasswd
pkgs.util-linux
pkgs.e2fsprogs
pkgs.iproute2
pkgs.fuse-overlayfs
pkgs.fuse
];
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-graphql-api}/bin/app.py";
Restart = "always";
RestartSec = "5";
};
};
systemd.services.selfprivacy-api-worker = {
description = "Task worker for SelfPrivacy API";
environment = config.nix.envVars // {
inherit (config.environment.sessionVariables) NIX_PATH;
HOME = "/root";
PYTHONUNBUFFERED = "1";
PYTHONPATH = pkgs.selfprivacy-graphql-api.pythonPath + ":${pkgs.selfprivacy-graphql-api}/lib/python3.10/site-packages/";
} // 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.rclone
pkgs.restic
pkgs.mkpasswd
pkgs.util-linux
pkgs.e2fsprogs
pkgs.iproute2
pkgs.fuse-overlayfs
pkgs.fuse
];
after = [ "network-online.target" ];
wantedBy = [ "network-online.target" ];
serviceConfig = {
User = "root";
ExecStart = "${pkgs.python310Packages.huey}/bin/huey_consumer.py selfprivacy_api.task_registry.huey";
ExecStart = "${pkgs.selfprivacy-api}/bin/app.py";
Restart = "always";
RestartSec = "5";
};
@ -103,8 +92,6 @@ in
serviceConfig = {
User = "root";
ExecStart = "${pkgs.nixos-rebuild}/bin/nixos-rebuild switch";
KillMode = "none";
SendSIGKILL = "no";
};
};
# One shot systemd service to upgrade NixOS using nixos-rebuild
@ -118,8 +105,6 @@ in
serviceConfig = {
User = "root";
ExecStart = "${pkgs.nixos-rebuild}/bin/nixos-rebuild switch --upgrade";
KillMode = "none";
SendSIGKILL = "no";
};
};
# One shot systemd service to rollback NixOS using nixos-rebuild
@ -133,8 +118,6 @@ in
serviceConfig = {
User = "root";
ExecStart = "${pkgs.nixos-rebuild}/bin/nixos-rebuild switch --rollback";
KillMode = "none";
SendSIGKILL = "no";
};
};
};

View File

@ -2,13 +2,18 @@
{
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" ];
group = "selfprivacy-api";
};
users.groups."selfprivacy-api" = {
members = [ "selfprivacy-api" ];

35
backup/restic.nix Normal file
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

@ -1,23 +1,25 @@
{ config, pkgs, lib, ... }:
let
url-overlay = "https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nix-repo/archive/test-migration.tar.gz";
url-overlay = "https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nix-repo/archive/master.tar.gz";
nix-overlay = (import (builtins.fetchTarball url-overlay));
in
{
imports = [
./hardware-configuration.nix
./variables-module.nix
./variables.nix
./files.nix
./volumes.nix
./users.nix
./mailserver/system/mailserver.nix
./mailserver/system/alps.nix
./vpn/ocserv.nix
./api/api.nix
./api/api-module.nix
./social/pleroma.nix
./letsencrypt/acme.nix
./letsencrypt/resolve.nix
./backup/restic.nix
./passmgr/bitwarden.nix
./webserver/nginx.nix
./webserver/memcached.nix
@ -29,37 +31,12 @@ in
nixpkgs.overlays = [ (nix-overlay) ];
services.redis.servers.sp-api = {
enable = true;
save = [
[
30
1
]
[
10
10
]
];
port = 0;
settings = {
notify-keyspace-events = "KEA";
};
};
services.do-agent.enable = if config.services.userdata.server.provider == "DIGITALOCEAN" then true else false;
boot.cleanTmpDir = true;
networking = {
hostName = config.services.userdata.hostname;
usePredictableInterfaceNames = false;
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" ];
};
@ -73,33 +50,25 @@ in
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
];
environment.variables = {
DOMAIN = config.services.userdata.domain;
};
system.autoUpgrade = {
enable = config.services.userdata.autoUpgrade.enable;
allowReboot = config.services.userdata.autoUpgrade.allowReboot;
channel = "https://channel.selfprivacy.org/nixos-selfpricacy";
};
system.stateVersion = config.services.userdata.stateVersion;
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 = {
optimise.automatic = true;
gc = {
automatic = true;
options = "--delete-older-than 7d";
};
extraOptions = ''
experimental-features = nix-command flakes repl-flake
'';
};
services.journald.extraConfig = "SystemMaxUse=500M";
boot.kernel.sysctl = {
"net.ipv4.ip_forward" = 1;
};

100
files.nix
View File

@ -1,89 +1,37 @@
{ config, pkgs, ... }:
let
cfg = config.services.userdata;
dnsCredentialsTemplates = {
DIGITALOCEAN = "DO_AUTH_TOKEN=REPLACEME";
CLOUDFLARE = ''
CF_API_KEY=REPLACEME
CLOUDFLARE_DNS_API_TOKEN=REPLACEME
CLOUDFLARE_ZONE_API_TOKEN=REPLACEME
'';
DESEC = "DESEC_TOKEN=REPLACEME";
};
dnsCredentialsTemplate = dnsCredentialsTemplates.${cfg.dns.provider};
in
{
systemd.tmpfiles.rules =
let
domain = builtins.replaceStrings [ "\n" "\"" "\\" "%" ] [ "\\n" "\\\"" "\\\\" "%%" ] cfg.domain;
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 0770 vaultwarden vaultwarden -" else "")
(if cfg.bitwarden.enable then "d /var/lib/bitwarden/backup 0770 vaultwarden vaultwarden -" else "")
(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 "")
(if cfg.pleroma.enable then "f /var/lib/pleroma/secrets.exs 0750 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.bitwarden.enable then "f /var/lib/bitwarden/.env 0640 vaultwarden vaultwarden - -" else "")
"d /var/sieve 0770 virtualMail virtualMail - -"
"d /var/www/root 0750 nginx nginx - -"
(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}"
];
system.activationScripts =
let
jq = "${pkgs.jq}/bin/jq";
sed = "${pkgs.gnused}/bin/sed";
in
{
nextcloudSecrets =
if cfg.nextcloud.enable then ''
mkdir -p /var/lib/nextcloud
cat /etc/nixos/userdata/userdata.json | ${jq} -r '.nextcloud.databasePassword' > /var/lib/nextcloud/db-pass
chmod 0440 /var/lib/nextcloud/db-pass
chown nextcloud:nextcloud /var/lib/nextcloud/db-pass
cat /etc/nixos/userdata/userdata.json | ${jq} -r '.nextcloud.adminPassword' > /var/lib/nextcloud/admin-pass
chmod 0440 /var/lib/nextcloud/admin-pass
chown nextcloud:nextcloud /var/lib/nextcloud/admin-pass
''
else ''
rm -f /var/lib/nextcloud/db-pass
rm -f /var/lib/nextcloud/admin-pass
'';
cloudflareCredentials = ''
mkdir -p /var/lib/cloudflare
chmod 0440 /var/lib/cloudflare
chown nginx:acmerecievers /var/lib/cloudflare
echo '${dnsCredentialsTemplate}' > /var/lib/cloudflare/Credentials.ini
${sed} -i "s/REPLACEME/$(cat /etc/nixos/userdata/userdata.json | ${jq} -r '.dns.apiKey')/g" /var/lib/cloudflare/Credentials.ini
chmod 0440 /var/lib/cloudflare/Credentials.ini
chown nginx:acmerecievers /var/lib/cloudflare/Credentials.ini
'';
pleromaCredentials =
if cfg.pleroma.enable then ''
echo 'import Config' > /var/lib/pleroma/secrets.exs
echo 'config :pleroma, Pleroma.Repo,' >> /var/lib/pleroma/secrets.exs
echo ' password: "REPLACEME"' >> /var/lib/pleroma/secrets.exs
${sed} -i "s/REPLACEME/$(cat /etc/nixos/userdata/userdata.json | ${jq} -r '.databasePassword')/g" /var/lib/pleroma/secrets.exs
chmod 0750 /var/lib/pleroma/secrets.exs
chown pleroma:pleroma /var/lib/pleroma/secrets.exs
'' else ''
rm -f /var/lib/pleroma/secrets.exs
'';
bitwardenCredentials =
if cfg.bitwarden.enable then ''
mkdir -p /var/lib/bitwarden
token=$(cat /etc/nixos/userdata/userdata.json | ${jq} -r '.bitwarden.adminToken')
if [ "$token" == "null" ]; then
# If it's null, delete the contents of the file
> /var/lib/bitwarden/.env
else
echo "ADMIN_TOKEN=$token" > /var/lib/bitwarden/.env
fi
chmod 0640 /var/lib/bitwarden/.env
chown vaultwarden:vaultwarden /var/lib/bitwarden/.env
'' else ''
rm -f /var/lib/bitwarden/.env
'';
};
}

View File

@ -1,22 +1,16 @@
{ config, lib, pkgs, ... }:
{ config, pkgs, ... }:
let
cfg = config.services.userdata;
in
{
fileSystems = lib.mkIf cfg.useBinds {
"/var/lib/gitea" = {
device = "/volumes/${cfg.gitea.location}/gitea";
options = [ "bind" ];
};
};
services = {
gitea = {
enable = cfg.gitea.enable;
stateDir = "/var/lib/gitea";
# log = {
# rootPath = "/var/lib/gitea/log";
# level = "Warn";
# };
log = {
rootPath = "/var/lib/gitea/log";
level = "Warn";
};
user = "gitea";
database = {
type = "sqlite3";
@ -26,10 +20,10 @@ in
path = "/var/lib/gitea/data/gitea.db";
createDatabase = true;
};
# ssh = {
# enable = true;
# clonePort = 22;
# };
ssh = {
enable = true;
clonePort = 22;
};
lfs = {
enable = true;
contentDir = "/var/lib/gitea/lfs";
@ -37,17 +31,16 @@ in
appName = "SelfPrivacy git Service";
repositoryRoot = "/var/lib/gitea/repositories";
domain = "git.${cfg.domain}";
rootUrl = "https://git.${cfg.domain}/";
rootUrl = "https://${cfg.domain}/";
httpAddress = "0.0.0.0";
httpPort = 3000;
# cookieSecure = true;
cookieSecure = true;
settings = {
mailer = {
ENABLED = false;
};
ui = {
DEFAULT_THEME = "arc-green";
SHOW_USER_EMAIL = false;
};
picture = {
DISABLE_GRAVATAR = true;
@ -58,13 +51,6 @@ in
repository = {
FORCE_PRIVATE = false;
};
session = {
COOKIE_SECURE = true;
};
log = {
ROOT_PATH = "/var/lib/gitea/log";
LEVEL = "Warn";
};
};
};
};

View File

@ -1,7 +1,6 @@
{ config, pkgs, lib, ... }:
{ config, pkgs, ... }:
let
cfg = config.services.userdata;
dnsPropagationCheckExceptions = [ "DIGITALOCEAN" ];
in
{
users.groups.acmerecievers = {
@ -9,23 +8,14 @@ in
};
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";
dnsPropagationCheck = if lib.elem cfg.dns.provider dnsPropagationCheckExceptions then false else true;
reloadServices = [ "nginx" ];
};
certs = lib.mkForce {
"wildcard-${cfg.domain}" = {
domain = "*.${cfg.domain}";
group = "acmerecievers";
dnsProvider = lib.strings.toLower cfg.dns.provider;
credentialsFile = "/var/lib/cloudflare/Credentials.ini";
};
email = "${cfg.username}@${cfg.domain}";
certs = {
"${cfg.domain}" = {
domain = cfg.domain;
domain = "*.${cfg.domain}";
extraDomainNames = [ "${cfg.domain}" ];
group = "acmerecievers";
webroot = "/var/lib/acme/acme-challenge";
dnsProvider = "cloudflare";
credentialsFile = "/var/lib/cloudflare/Credentials.ini";
};
};
};

View File

@ -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

@ -6,24 +6,13 @@ in
imports = [
(builtins.fetchTarball {
# Pick a commit from the branch you are interested in
url = "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/archive/6d0d9fb9/nixos-mailserver-6d0d9fb9.tar.gz";
url = "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/archive/5675b122/nixos-mailserver-5675b122.tar.gz";
# And set its hash
sha256 = "sha256:0h35al73p15z9v8zb6hi5nq987sfl5wp4rm5c8947nlzlnsjl61x";
sha256 = "1fwhb7a5v9c98nzhf3dyqf3a5ianqh7k50zizj8v5nmj3blxw4pi";
})
];
fileSystems = lib.mkIf cfg.useBinds {
"/var/vmail" = {
device = "/volumes/${cfg.email.location}/vmail";
options = [ "bind" ];
};
"/var/sieve" = {
device = "/volumes/${cfg.email.location}/sieve";
options = [ "bind" ];
};
};
users.users = {
virtualMail = {
isNormalUser = false;
@ -43,7 +32,7 @@ in
sieveScript = ''
require ["fileinto", "mailbox"];
if header :contains "Chat-Version" "1.0"
{
{
fileinto :create "DeltaChat";
stop;
}
@ -57,7 +46,7 @@ in
sieveScript = ''
require ["fileinto", "mailbox"];
if header :contains "Chat-Version" "1.0"
{
{
fileinto :create "DeltaChat";
stop;
}

View File

@ -1,17 +1,11 @@
{ pkgs, lib, config, ... }:
{ pkgs, config, ... }:
let
cfg = config.services.userdata;
in
{
fileSystems = lib.mkIf cfg.useBinds {
"/var/lib/nextcloud" = {
device = "/volumes/${cfg.nextcloud.location}/nextcloud";
options = [ "bind" ];
};
};
services.nextcloud = {
enable = cfg.nextcloud.enable;
package = pkgs.nextcloud25;
package = pkgs.nextcloud22;
hostName = "cloud.${cfg.domain}";
# Use HTTPS for links

View File

@ -1,23 +1,12 @@
{ pkgs, lib, config, ... }:
{ pkgs, config, ... }:
let
cfg = config.services.userdata;
in
{
fileSystems = lib.mkIf cfg.useBinds {
"/var/lib/bitwarden" = {
device = "/volumes/${cfg.bitwarden.location}/bitwarden";
options = [ "bind" ];
};
"/var/lib/bitwarden_rs" = {
device = "/volumes/${cfg.bitwarden.location}/bitwarden_rs";
options = [ "bind" ];
};
};
services.vaultwarden = {
services.bitwarden_rs = {
enable = cfg.bitwarden.enable;
dbBackend = "sqlite";
backupDir = "/var/lib/bitwarden/backup";
environmentFile = "/var/lib/bitwarden/.env";
config = {
domain = "https://password.${cfg.domain}/";
signupsAllowed = true;

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

133
social/pleroma-module.nix Normal file
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

@ -1,18 +1,13 @@
{ pkgs, lib, config, ... }:
{ pkgs, config, ... }:
let
cfg = config.services.userdata;
in
{
fileSystems = lib.mkIf cfg.useBinds {
"/var/lib/pleroma" = {
device = "/volumes/${cfg.pleroma.location}/pleroma";
options = [ "bind" ];
};
"/var/lib/postgresql" = {
device = "/volumes/${cfg.pleroma.location}/postgresql";
options = [ "bind" ];
};
};
nixpkgs.overlays = [
(self: super: {
pleroma-otp = self.callPackage ./pleroma-package.nix { };
})
];
services = {
pleroma = {
enable = cfg.pleroma.enable;
@ -20,8 +15,8 @@ in
group = "pleroma";
configs = [
(builtins.replaceStrings
[ "$DOMAIN" "$LUSER" ]
[ cfg.domain cfg.username ]
[ "$DOMAIN" "$LUSER" "$DB_PASSWORD" ]
[ cfg.domain cfg.username cfg.databasePassword ]
(builtins.readFile ./config.exs))
];
};
@ -29,21 +24,10 @@ in
enable = true;
package = pkgs.postgresql_12;
initialScript = "/etc/setup.psql";
ensureDatabases = [
"pleroma"
];
ensureUsers = [
{
name = "pleroma";
ensurePermissions = {
"DATABASE pleroma" = "ALL PRIVILEGES";
};
}
];
};
};
environment.etc."setup.psql".text = ''
CREATE USER pleroma;
CREATE USER pleroma WITH ENCRYPTED PASSWORD '${cfg.databasePassword}';
CREATE DATABASE pleroma OWNER pleroma;
\c pleroma;
--Extensions made by ecto.migrate that need superuser access
@ -55,6 +39,5 @@ in
extraGroups = [ "postgres" ];
isNormalUser = false;
isSystemUser = true;
group = "pleroma";
};
}

View File

@ -1,72 +0,0 @@
{
"$schema": "http://json-schema.org/schema#",
"$id": "https://git.selfprivacy.org/inex/selfprivacy-nixos-config/raw/branch/master/userdata/tokens_schema.json",
"type": "object",
"properties": {
"tokens": {
"type": "array",
"items": {
"type": "object",
"properties": {
"token": {
"type": "string"
},
"name": {
"type": "string"
},
"date": {
"type": "string"
}
},
"required": [
"token",
"name",
"date"
]
}
},
"recovery_token": {
"type": "object",
"properties": {
"token": {
"type": "string"
},
"date": {
"type": "string"
},
"expiration": {
"type": "string"
},
"uses_left": {
"type": "integer"
}
},
"required": [
"token",
"date"
]
},
"new_device": {
"type": "object",
"properties": {
"token": {
"type": "string"
},
"date": {
"type": "string"
},
"expiration": {
"type": "string"
}
},
"required": [
"token",
"date",
"expiration"
]
}
},
"required": [
"tokens"
]
}

View File

@ -17,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

@ -11,6 +11,10 @@ let
in
{
options.services.userdata = {
enable = mkOption {
default = true;
type = types.nullOr types.bool;
};
# General server options
hostname = mkOption {
description = "The hostname of the server.";
@ -41,13 +45,6 @@ in
type = types.nullOr types.bool;
};
};
stateVersion = mkOption {
description = ''
State version of the server
'';
type = types.str;
default = "22.11";
};
########################
# Server admin options #
########################
@ -74,6 +71,19 @@ in
# API options #
###############
api = {
token = mkOption {
description = ''
API token used to authenticate with the server
'';
type = types.nullOr types.str;
};
enableSwagger = mkOption {
default = true;
description = ''
Enable Swagger UI
'';
type = types.bool;
};
skippedMigrations = mkOption {
default = [ ];
description = ''
@ -85,64 +95,62 @@ in
#############
# Secrets #
#############
dns = {
provider = mkOption {
description = "DNS provider that was defined at the initial setup process. Default is ClOUDFLARE";
type = types.nullOr types.str;
};
useStagingACME = mkOption {
description = "Use staging ACME server. Default is false";
type = types.nullOr types.bool;
};
};
backup = {
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;
};
};
server = {
provider = mkOption {
description = "Server provider that was defined at the initial setup process. Default is HETZNER";
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;
};
location = mkOption {
default = "sda1";
type = types.nullOr types.str;
};
};
email = {
location = mkOption {
default = "sda1";
type = types.nullOr types.str;
};
};
gitea = {
enable = mkOption {
default = false;
type = types.nullOr types.bool;
};
location = mkOption {
default = "sda1";
type = types.nullOr types.str;
};
};
nextcloud = {
enable = mkOption {
default = true;
type = types.nullOr types.bool;
};
location = mkOption {
default = "sda1";
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;
};
};
@ -151,10 +159,6 @@ in
default = false;
type = types.nullOr types.bool;
};
location = mkOption {
default = "sda1";
type = types.nullOr types.str;
};
};
jitsi = {
enable = mkOption {
@ -168,6 +172,15 @@ in
type = types.nullOr types.bool;
};
};
#############
# Backups #
#############
resticPassword = mkOption {
description = ''
Password for the restic
'';
type = types.nullOr types.str;
};
#########
# SSH #
#########
@ -187,7 +200,7 @@ in
description = ''
Password authentication for SSH
'';
default = false;
default = true;
type = types.nullOr types.bool;
};
};
@ -201,19 +214,5 @@ in
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;
};
};
}

View File

@ -1,65 +1,6 @@
{ pkgs, lib, ... }:
let
jsonData = builtins.fromJSON (builtins.readFile ./userdata/userdata.json);
in
{ pkgs, ... }:
{
services.userdata = {
hostname = lib.attrsets.attrByPath [ "hostname" ] null jsonData;
domain = lib.attrsets.attrByPath [ "domain" ] null jsonData;
timezone = lib.attrsets.attrByPath [ "timezone" ] "Europe/Uzhgorod" jsonData;
stateVersion = lib.attrsets.attrByPath [ "stateVersion" ] "22.05" jsonData;
autoUpgrade = {
enable = lib.attrsets.attrByPath [ "autoUpgrade" "enable" ] true jsonData;
allowReboot = lib.attrsets.attrByPath [ "autoUpgrade" "allowReboot" ] true jsonData;
};
username = lib.attrsets.attrByPath [ "username" ] null jsonData;
hashedMasterPassword = lib.attrsets.attrByPath [ "hashedMasterPassword" ] null jsonData;
sshKeys = lib.attrsets.attrByPath [ "sshKeys" ] [ ] jsonData;
api = {
skippedMigrations = lib.attrsets.attrByPath [ "api" "skippedMigrations" ] [ ] jsonData;
};
dns = {
provider = lib.attrsets.attrByPath [ "dns" "provider" ] "CLOUDFLARE" jsonData;
useStagingACME = lib.attrsets.attrByPath [ "dns" "useStagingACME" ] false jsonData;
};
backup = {
bucket = lib.attrsets.attrByPath [ "backup" "bucket" ] (lib.attrsets.attrByPath [ "backblaze" "bucket" ] "" jsonData) jsonData;
};
server = {
provider = lib.attrsets.attrByPath [ "server" "provider" ] "HETZNER" jsonData;
};
bitwarden = {
enable = lib.attrsets.attrByPath [ "bitwarden" "enable" ] false jsonData;
location = lib.attrsets.attrByPath [ "bitwarden" "location" ] "sda1" jsonData;
};
gitea = {
enable = lib.attrsets.attrByPath [ "gitea" "enable" ] false jsonData;
location = lib.attrsets.attrByPath [ "gitea" "location" ] "sda1" jsonData;
};
nextcloud = {
enable = lib.attrsets.attrByPath [ "nextcloud" "enable" ] false jsonData;
location = lib.attrsets.attrByPath [ "nextcloud" "location" ] "sda1" jsonData;
};
pleroma = {
enable = lib.attrsets.attrByPath [ "pleroma" "enable" ] false jsonData;
location = lib.attrsets.attrByPath [ "pleroma" "location" ] "sda1" jsonData;
};
jitsi = {
enable = lib.attrsets.attrByPath [ "jitsi" "enable" ] false jsonData;
};
ocserv = {
enable = lib.attrsets.attrByPath [ "ocserv" "enable" ] false jsonData;
};
ssh = {
enable = lib.attrsets.attrByPath [ "ssh" "enable" ] true jsonData;
rootKeys = lib.attrsets.attrByPath [ "ssh" "rootKeys" ] [ "" ] jsonData;
passwordAuthentication = lib.attrsets.attrByPath [ "ssh" "passwordAuthentication" ] true jsonData;
};
email = {
location = lib.attrsets.attrByPath [ "email" "location" ] "sda1" jsonData;
};
users = lib.attrsets.attrByPath [ "users" ] [ ] jsonData;
volumes = lib.attrsets.attrByPath [ "volumes" ] [ ] jsonData;
useBinds = lib.attrsets.attrByPath [ "useBinds" ] false jsonData;
services = {
userdata = builtins.fromJSON (builtins.readFile ./userdata/userdata.json);
};
}

View File

@ -6,7 +6,7 @@ in
services.jitsi-meet = {
enable = config.services.userdata.jitsi.enable;
hostName = "meet.${domain}";
nginx.enable = true;
nginx.enable = false;
interfaceConfig = {
SHOW_JITSI_WATERMARK = false;
SHOW_WATERMARK_FOR_GUESTS = false;

View File

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

View File

@ -10,7 +10,6 @@ in
isNormalUser = false;
isSystemUser = true;
extraGroups = [ "ocserv" "acmerecievers" ];
group = "ocserv";
};
services.ocserv = {
enable = config.services.userdata.ocserv.enable;

View File

@ -20,7 +20,8 @@ in
virtualHosts = {
"${domain}" = {
enableACME = true;
sslCertificate = "/var/lib/acme/${domain}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/${domain}/key.pem";
forceSSL = true;
extraConfig = ''
add_header Strict-Transport-Security $hsts_header;
@ -32,15 +33,10 @@ in
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
expires 10m;
'';
locations = {
"/" = {
root = "/var/www/root";
};
};
};
"vpn.${domain}" = {
sslCertificate = "/var/lib/acme/wildcard-${domain}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/wildcard-${domain}/key.pem";
sslCertificate = "/var/lib/acme/${domain}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/${domain}/key.pem";
forceSSL = true;
extraConfig = ''
add_header Strict-Transport-Security $hsts_header;
@ -54,8 +50,8 @@ in
'';
};
"git.${domain}" = {
sslCertificate = "/var/lib/acme/wildcard-${domain}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/wildcard-${domain}/key.pem";
sslCertificate = "/var/lib/acme/${domain}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/${domain}/key.pem";
forceSSL = true;
extraConfig = ''
add_header Strict-Transport-Security $hsts_header;
@ -74,8 +70,8 @@ in
};
};
"cloud.${domain}" = {
sslCertificate = "/var/lib/acme/wildcard-${domain}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/wildcard-${domain}/key.pem";
sslCertificate = "/var/lib/acme/${domain}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/${domain}/key.pem";
forceSSL = true;
extraConfig = ''
add_header Strict-Transport-Security $hsts_header;
@ -93,9 +89,52 @@ in
};
};
};
"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;
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_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/wildcard-${domain}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/wildcard-${domain}/key.pem";
sslCertificate = "/var/lib/acme/${domain}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/${domain}/key.pem";
forceSSL = true;
extraConfig = ''
add_header Strict-Transport-Security $hsts_header;
@ -114,8 +153,8 @@ in
};
};
"api.${domain}" = {
sslCertificate = "/var/lib/acme/wildcard-${domain}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/wildcard-${domain}/key.pem";
sslCertificate = "/var/lib/acme/${domain}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/${domain}/key.pem";
forceSSL = true;
extraConfig = ''
add_header Strict-Transport-Security $hsts_header;
@ -130,13 +169,12 @@ in
locations = {
"/" = {
proxyPass = "http://127.0.0.1:5050";
proxyWebsockets = true;
};
};
};
"social.${domain}" = {
sslCertificate = "/var/lib/acme/wildcard-${domain}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/wildcard-${domain}/key.pem";
sslCertificate = "/var/lib/acme/${domain}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/${domain}/key.pem";
root = "/var/www/social.${domain}";
forceSSL = true;
extraConfig = ''
@ -155,13 +193,6 @@ in
};
};
};
"meet.${domain}" = {
sslCertificate = "/var/lib/acme/wildcard-${domain}/fullchain.pem";
sslCertificateKey = "/var/lib/acme/wildcard-${domain}/key.pem";
forceSSL = true;
useACMEHost = "wildcard-${domain}";
enableACME = false;
};
};
};
}