From 85aaf52635c46b996a9c1ad834565d68a745c89c Mon Sep 17 00:00:00 2001 From: Inex Code Date: Mon, 15 Nov 2021 13:02:05 +0300 Subject: [PATCH] Initial commit --- .gitignore | 3 + api/api-module.nix | 43 ++++++++ api/api-package.nix | 24 +++++ api/api.nix | 15 +++ backup/restic.nix | 35 +++++++ configuration.nix | 82 ++++++++++++++++ files.nix | 30 ++++++ git/gitea.nix | 57 +++++++++++ letsencrypt/acme.nix | 22 +++++ letsencrypt/resolve.nix | 22 +++++ mailserver/system/alps-package.nix | 30 ++++++ mailserver/system/alps.nix | 18 ++++ mailserver/system/mailserver.nix | 85 ++++++++++++++++ nextcloud/nextcloud.nix | 34 +++++++ passmgr/bitwarden.nix | 16 +++ resources/limits.nix | 48 +++++++++ social/config.exs | 44 +++++++++ social/pleroma-module.nix | 133 +++++++++++++++++++++++++ social/pleroma-package.nix | 69 +++++++++++++ social/pleroma.nix | 41 ++++++++ userdata/schema.json | 142 ++++++++++++++++++++++++++ users.nix | 12 +++ variables-module.nix | 153 +++++++++++++++++++++++++++++ variables.nix | 6 ++ videomeet/jitsi.nix | 15 +++ vpn/ocserv.nix | 51 ++++++++++ webserver/memcached.nix | 13 +++ webserver/nginx.nix | 117 ++++++++++++++++++++++ 28 files changed, 1360 insertions(+) create mode 100644 .gitignore create mode 100644 api/api-module.nix create mode 100644 api/api-package.nix create mode 100644 api/api.nix create mode 100644 backup/restic.nix create mode 100644 configuration.nix create mode 100644 files.nix create mode 100644 git/gitea.nix create mode 100644 letsencrypt/acme.nix create mode 100644 letsencrypt/resolve.nix create mode 100644 mailserver/system/alps-package.nix create mode 100644 mailserver/system/alps.nix create mode 100644 mailserver/system/mailserver.nix create mode 100644 nextcloud/nextcloud.nix create mode 100644 passmgr/bitwarden.nix create mode 100644 resources/limits.nix create mode 100644 social/config.exs create mode 100644 social/pleroma-module.nix create mode 100644 social/pleroma-package.nix create mode 100644 social/pleroma.nix create mode 100644 userdata/schema.json create mode 100644 users.nix create mode 100644 variables-module.nix create mode 100644 variables.nix create mode 100644 videomeet/jitsi.nix create mode 100644 vpn/ocserv.nix create mode 100644 webserver/memcached.nix create mode 100644 webserver/nginx.nix diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d128ad9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +userdata/userdata.json +hardware-configuration.nix +networking.nix \ No newline at end of file diff --git a/api/api-module.nix b/api/api-module.nix new file mode 100644 index 0000000..5c9998a --- /dev/null +++ b/api/api-module.nix @@ -0,0 +1,43 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + selfprivacy-api = pkgs.callPackage ./api-package.nix { }; + 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 + ''; + }; + }; + 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"; + } // 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 = "${selfprivacy-api}/bin/app.py"; + Restart = "always"; + RestartSec = "5"; + }; + }; + }; +} diff --git a/api/api-package.nix b/api/api-package.nix new file mode 100644 index 0000000..29afe65 --- /dev/null +++ b/api/api-package.nix @@ -0,0 +1,24 @@ +{ nixpkgs ? import { }, pythonPkgs ? nixpkgs.pkgs.python39Packages }: + +let + inherit (nixpkgs) pkgs; + inherit pythonPkgs; + + selfprivacy-api = { buildPythonPackage, flask, flask-restful, setuptools }: + buildPythonPackage rec { + pname = "selfprivacy-api"; + version = "1.1"; + src = builtins.fetchGit { + url = "https://git.selfprivacy.org/ilchub/selfprivacy-rest-api.git"; + rev = "dbb4c1095654bba88d4f0c91b7b195d5262976b6"; + }; + propagatedBuildInputs = [ flask flask-restful setuptools ]; + meta = { + description = '' + SelfPrivacy Server Management API + ''; + }; + }; + drv = pythonPkgs.callPackage selfprivacy-api { }; +in +if pkgs.lib.inNixShell then drv.env else drv diff --git a/api/api.nix b/api/api.nix new file mode 100644 index 0000000..37875d4 --- /dev/null +++ b/api/api.nix @@ -0,0 +1,15 @@ +{ pkgs, ... }: +{ + services.selfprivacy-api = { + enable = true; + }; + + users.users."selfprivacy-api" = { + isNormalUser = false; + isSystemUser = true; + extraGroups = [ "opendkim" ]; + }; + users.groups."selfprivacy-api" = { + members = [ "selfprivacy-api" ]; + }; +} diff --git a/backup/restic.nix b/backup/restic.nix new file mode 100644 index 0000000..6c7d5bb --- /dev/null +++ b/backup/restic.nix @@ -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} + ''; +} diff --git a/configuration.nix b/configuration.nix new file mode 100644 index 0000000..ee84133 --- /dev/null +++ b/configuration.nix @@ -0,0 +1,82 @@ +{ config, pkgs, lib, ... }: +{ + imports = [ + ./hardware-configuration.nix + + ./variables-module.nix + ./variables.nix + ./vscode.nix + ./files.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 + ./nextcloud/nextcloud.nix + ./resources/limits.nix + ./videomeet/jitsi.nix + ./git/gitea.nix + ]; + + boot.cleanTmpDir = true; + networking = { + hostName = config.services.userdata.hostname; + firewall = { + 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 = "Europe/Uzhgorod"; + i18n.defaultLocale = "en_GB.UTF-8"; + users.users.root.openssh.authorizedKeys.keys = config.services.userdata.rootSshKeys; + services.openssh = { + enable = true; + passwordAuthentication = true; + permitRootLogin = "yes"; + openFirewall = false; + }; + programs.ssh = { + pubkeyAcceptedKeyTypes = [ "ssh-ed25519" ]; + hostKeyAlgorithms = [ "ssh-ed25519" ]; + }; + environment.systemPackages = with pkgs; [ + git + ]; + environment.variables = { + DOMAIN = config.services.userdata.domain; + }; + system.autoUpgrade.enable = true; + system.autoUpgrade.allowReboot = false; + system.autoUpgrade.channel = https://nixos.org/channels/nixos-21.05-small; + nix = { + optimise.automatic = true; + gc = { + automatic = true; + options = "--delete-older-than 7d"; + }; + }; + boot.kernel.sysctl = { + "net.ipv4.ip_forward" = 1; + }; + swapDevices = [ + { + device = "/swapfile"; + priority = 0; + size = 2048; + } + ]; + security = { + sudo = { + enable = true; + }; + }; +} diff --git a/files.nix b/files.nix new file mode 100644 index 0000000..818224e --- /dev/null +++ b/files.nix @@ -0,0 +1,30 @@ +{ 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} + ''; + in + [ + "d /var/restic 0660 restic - - -" + "d /var/bitwarden 0777 bitwarden_rs bitwarden_rs -" + "d /var/bitwarden/backup 0777 bitwarden_rs bitwarden_rs -" + "d /var/lib/pleroma 0600 pleroma pleroma - -" + "f /var/lib/pleroma/secrets.exs 0755 pleroma pleroma - -" + "f /var/domain 0444 selfprivacy-api selfprivacy-api - ${domain}" + "f /var/restic/restic-repo-password 0660 restic - - ${resticPass}" + "f /var/nextcloud-db-pass 0440 nextcloud nextcloud - ${nextcloudDBPass}" + "f /var/nextcloud-admin-pass 0440 nextcloud nextcloud - ${nextcloudAdminPass}" + "f /var/cloudflareCredentials.ini 0440 nginx acmerecievers - ${cloudflareCredentials}" + ]; +} diff --git a/git/gitea.nix b/git/gitea.nix new file mode 100644 index 0000000..56f938e --- /dev/null +++ b/git/gitea.nix @@ -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; + }; + }; + }; + }; +} diff --git a/letsencrypt/acme.nix b/letsencrypt/acme.nix new file mode 100644 index 0000000..d6a533d --- /dev/null +++ b/letsencrypt/acme.nix @@ -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/cloudflareCredentials.ini"; + }; + }; + }; +} diff --git a/letsencrypt/resolve.nix b/letsencrypt/resolve.nix new file mode 100644 index 0000000..d7196e4 --- /dev/null +++ b/letsencrypt/resolve.nix @@ -0,0 +1,22 @@ +{ config, pkgs, ... }: +let + domain = config.services.userdata.domain; +in +{ + systemd = { + services = { + "acme-${domain}" = { + serviceConfig = { + StartLimitBurst = 5; + StartLimitIntervalSec = 5; + Restart = "on-failure"; + }; + }; + "nginx-config-reload" = { + serviceConfig = { + After = [ "acme-${domain}.service" ]; + }; + }; + }; + }; +} diff --git a/mailserver/system/alps-package.nix b/mailserver/system/alps-package.nix new file mode 100644 index 0000000..fda9961 --- /dev/null +++ b/mailserver/system/alps-package.nix @@ -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; + }; +} diff --git a/mailserver/system/alps.nix b/mailserver/system/alps.nix new file mode 100644 index 0000000..b8802eb --- /dev/null +++ b/mailserver/system/alps.nix @@ -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"; + }; + }; + }; +} diff --git a/mailserver/system/mailserver.nix b/mailserver/system/mailserver.nix new file mode 100644 index 0000000..aafa677 --- /dev/null +++ b/mailserver/system/mailserver.nix @@ -0,0 +1,85 @@ +{ config, pkgs, lib, ... }: +let + cfg = config.services.userdata; +in +{ + 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"; + }) + ]; + + services.dovecot2 = { + enablePAM = lib.mkForce true; + showPAMFailure = lib.mkForce true; + }; + + users.users = { + virtualMail = { + isNormalUser = false; + }; + }; + + mailserver = { + enable = true; + 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 = { + "${cfg.username}@${cfg.domain}" = { + hashedPassword = cfg.hashedMasterPassword; + catchAll = [ cfg.domain ]; + sieveScript = '' + require ["fileinto", "mailbox"]; + if header :contains "Chat-Version" "1.0" + { + fileinto :create "DeltaChat"; + stop; + } + ''; + } // builtins.listToAttrs (builtins.map + (user: { + name = "${user.username}@${cfg.domain}"; + value = { + hashedPassword = user.hashedPassword; + catchAll = [ cfg.domain ]; + sieveScript = '' + require ["fileinto", "mailbox"]; + if header :contains "Chat-Version" "1.0" + { + fileinto :create "DeltaChat"; + stop; + } + ''; + }; + }) + cfg.users); + }; + + extraVirtualAliases = { + "admin@${cfg.domain}" = "${cfg.username}@${cfg.domain}"; + }; + + certificateScheme = 1; + certificateFile = "/var/lib/acme/${cfg.domain}/fullchain.pem"; + keyFile = "/var/lib/acme/${cfg.domain}/key.pem"; + + # Enable IMAP and POP3 + enableImap = true; + enableImapSsl = true; + enablePop3 = false; + enablePop3Ssl = false; + dkimSelector = "selector"; + + # Enable the ManageSieve protocol + enableManageSieve = true; + + virusScanning = false; + }; +} diff --git a/nextcloud/nextcloud.nix b/nextcloud/nextcloud.nix new file mode 100644 index 0000000..b53eed6 --- /dev/null +++ b/nextcloud/nextcloud.nix @@ -0,0 +1,34 @@ +{ pkgs, config, ... }: +let + cfg = config.services.userdata; +in +{ + services.nextcloud = { + enable = cfg.nextcloud.enable; + package = pkgs.nextcloud21; + 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 = "http"; + + # 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/nextcloud-db-pass"; + + adminpassFile = "/var/nextcloud-admin-pass"; + adminuser = "admin"; + }; + }; +} diff --git a/passmgr/bitwarden.nix b/passmgr/bitwarden.nix new file mode 100644 index 0000000..817b82a --- /dev/null +++ b/passmgr/bitwarden.nix @@ -0,0 +1,16 @@ +{ pkgs, config, ... }: +let + cfg = config.services.userdata; +in +{ + services.bitwarden_rs = { + enable = cfg.bitwarden.enable; + dbBackend = "sqlite"; + backupDir = "/var/bitwarden/backup"; + config = { + domain = "https://password.${cfg.domain}/"; + signupsAllowed = true; + rocketPort = 8222; + }; + }; +} diff --git a/resources/limits.nix b/resources/limits.nix new file mode 100644 index 0000000..5d341cb --- /dev/null +++ b/resources/limits.nix @@ -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; + }; + }; + }; +} diff --git a/social/config.exs b/social/config.exs new file mode 100644 index 0000000..8782dea --- /dev/null +++ b/social/config.exs @@ -0,0 +1,44 @@ +import Config + +config :pleroma, Pleroma.Web.Endpoint, + url: [host: "social.$DOMAIN", scheme: "https", port: 443], + http: [ip: {127, 0, 0, 1}, port: 4000] + #secret_key_base: "", + #signing_salt: "" + +config :pleroma, :instance, + name: "social.$DOMAIN", + email: "$LUSER@$DOMAIN", + notify_email: "$LUSER@$DOMAIN", + limit: 5000, + upload_limit: 1073741824, + registrations_open: true + +config :pleroma, :media_proxy, + enabled: false, + redirect_on_failure: true + #base_url: "https://cache.pleroma.social" + +config :pleroma, Pleroma.Repo, + adapter: Ecto.Adapters.Postgres, + username: "pleroma", + password: "$DB_PASSWORD", + database: "pleroma", + hostname: "localhost", + pool_size: 10 + +#config :web_push_encryption, :vapid_details, + #subject: "", + #public_key: "", + #private_key: "" + +config :pleroma, :database, rum_enabled: false +config :pleroma, :instance, static_dir: "/var/lib/pleroma/static" +config :pleroma, Pleroma.Uploaders.Local, uploads: "/var/lib/pleroma/uploads" + +config :pleroma, :http_security, + sts: true + +#config :joken, default_signer: "" + +config :pleroma, configurable_from_database: false diff --git a/social/pleroma-module.nix b/social/pleroma-module.nix new file mode 100644 index 0000000..45b4b9e --- /dev/null +++ b/social/pleroma-module.nix @@ -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. + DO NOT STORE ANY PLEROMA SECRET + HERE, use + services.pleroma.secretConfigFile + 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. + DO NOT POINT THIS OPTION TO THE NIX + STORE, 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 ]; +} diff --git a/social/pleroma-package.nix b/social/pleroma-package.nix new file mode 100644 index 0000000..38f41e9 --- /dev/null +++ b/social/pleroma-package.nix @@ -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" ]; + }; +} diff --git a/social/pleroma.nix b/social/pleroma.nix new file mode 100644 index 0000000..4850a36 --- /dev/null +++ b/social/pleroma.nix @@ -0,0 +1,41 @@ +{ 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" ]; + }; +} diff --git a/userdata/schema.json b/userdata/schema.json new file mode 100644 index 0000000..0d10a19 --- /dev/null +++ b/userdata/schema.json @@ -0,0 +1,142 @@ +{ + "$schema": "http://json-schema.org/schema#", + "$id": "https://git.selfprivacy.org/inex/selfprivacy-nixos-config/raw/branch/master/userdata/schema.json", + "type": "object", + "properties": { + "hostname": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "username": { + "type": "string" + }, + "hashedMasterPassword": { + "type": "string" + }, + "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" + }, + "rootSshKeys": { + "type": "array", + "items": { + "type": "string" + } + }, + "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" + ] +} \ No newline at end of file diff --git a/users.nix b/users.nix new file mode 100644 index 0000000..400a057 --- /dev/null +++ b/users.nix @@ -0,0 +1,12 @@ +{ pkgs, config, ... }: +{ + users.mutableUsers = false; + users = { + users = { + "${config.services.userdata.username}" = { + isNormalUser = true; + hashedPassword = config.services.userdata.hashedMasterPassword; + }; + }; + }; +} diff --git a/variables-module.nix b/variables-module.nix new file mode 100644 index 0000000..5031030 --- /dev/null +++ b/variables-module.nix @@ -0,0 +1,153 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.userdata; + directionArg = + if cfg.direction == "" + then "" + else "--direction=${cfg.direction}"; + userDef = { + options = { + username = mkOption { + type = types.nullOr types.string; + }; + hashedPassword = mkOption { + type = types.nullOr types.string; + }; + sshKeys = mkOption { + type = types.nullOr types.listOf types.string; + }; + }; + }; +in +{ + options.services.userdata = { + enable = mkOption { + default = true; + type = types.nullOr types.bool; + }; + hostname = mkOption { + description = "The hostname of the server."; + type = types.nullOr types.string; + }; + domain = mkOption { + description = '' + Domain used by the server + ''; + type = types.nullOr types.string; + }; + username = mkOption { + description = '' + Username that was defined at the initial setup process + ''; + type = types.nullOr types.string; + }; + hashedMasterPassword = mkOption { + description = '' + Hash of the password that was defined at the initial setup process + ''; + type = types.nullOr types.string; + }; + backblaze = { + bucket = mkOption { + description = "Bucket name used for userdata backups"; + type = types.nullOr types.string; + }; + accountId = mkOption { + description = "Backblaze B2 Account ID"; + type = types.nullOr types.string; + }; + accountKey = mkOption { + description = "Backblaze B2 Account Key."; + type = types.nullOr types.string; + }; + }; + cloudflare = { + apiKey = mkOption { + description = "Cloudflare API Key."; + type = types.nullOr types.string; + }; + }; + databasePassword = mkOption { + description = '' + Password for the database + ''; + type = types.nullOr types.string; + }; + 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.string; + }; + adminPassword = mkOption { + description = '' + Password for the nextcloud admin user + ''; + type = types.nullOr types.string; + }; + }; + 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; + }; + }; + resticPassword = mkOption { + description = '' + Password for the restic + ''; + type = types.nullOr types.string; + }; + rootSshKeys = mkOption { + description = '' + Root SSH Keys + ''; + type = types.nullOr types.listOf types.string; + }; + timezone = mkOption { + description = '' + Timezone used by the server + ''; + type = types.nullOr types.string; + default = "Europe/Uzhgorod"; + }; + users = mkOption { + description = '' + Users that will be created on the server + ''; + type = with types; nullOr listOf attrsOf (submodule userDef); + }; + }; +} diff --git a/variables.nix b/variables.nix new file mode 100644 index 0000000..5c5adbf --- /dev/null +++ b/variables.nix @@ -0,0 +1,6 @@ +{ pkgs, ... }: +{ + services = { + userdata = builtins.fromJSON (builtins.readFile "./userdata/userdata.json"); + }; +} diff --git a/videomeet/jitsi.nix b/videomeet/jitsi.nix new file mode 100644 index 0000000..27ed438 --- /dev/null +++ b/videomeet/jitsi.nix @@ -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; + }; + }; +} diff --git a/vpn/ocserv.nix b/vpn/ocserv.nix new file mode 100644 index 0000000..e47e500 --- /dev/null +++ b/vpn/ocserv.nix @@ -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 + ''; + }; +} diff --git a/webserver/memcached.nix b/webserver/memcached.nix new file mode 100644 index 0000000..2438f97 --- /dev/null +++ b/webserver/memcached.nix @@ -0,0 +1,13 @@ +{ pkgs, ... }: +{ + services = { + memcached = { + enable = true; + user = "memcached"; + listen = "127.0.0.1"; + port = 11211; + maxMemory = 64; + maxConnections = 1024; + }; + }; +} diff --git a/webserver/nginx.nix b/webserver/nginx.nix new file mode 100644 index 0000000..6e925ad --- /dev/null +++ b/webserver/nginx.nix @@ -0,0 +1,117 @@ +{ pkgs, config, ... }: +let + domain = config.services.userdata.domain; +in +{ + services.nginx = { + enable = true; + enableReload = true; + 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; + 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; + ''; + }; + }; + }; +}