From 6513755670892983db88a6633b8c1ea6019c03d1 Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Fri, 15 Nov 2024 14:55:37 -0500 Subject: Re-namespace some stuff to Omni I was getting confused about what is a product and what is internal infrastructure; I think it is good to keep those things separate. So I moved a bunch of stuff to an Omni namespace, actually most stuff went there. Only things that are explicitly external products are still in the Biz namespace. --- Omni/Cloud/Chat.nix | 94 ++++++++++++++ Omni/Cloud/Comms.nix | 5 + Omni/Cloud/Comms/Coturn.nix | 10 ++ Omni/Cloud/Comms/Jitsi.nix | 17 +++ Omni/Cloud/Comms/Mumble.nix | 25 ++++ Omni/Cloud/Comms/Xmpp.nix | 210 ++++++++++++++++++++++++++++++ Omni/Cloud/Git.nix | 119 +++++++++++++++++ Omni/Cloud/Gmnisrv.nix | 40 ++++++ Omni/Cloud/Grocy.nix | 17 +++ Omni/Cloud/Hardware.nix | 9 ++ Omni/Cloud/Hub.nix | 57 +++++++++ Omni/Cloud/Mail.nix | 54 ++++++++ Omni/Cloud/Networking.nix | 48 +++++++ Omni/Cloud/NostrRelay.nix | 39 ++++++ Omni/Cloud/Ports.nix | 46 +++++++ Omni/Cloud/Web.nix | 303 ++++++++++++++++++++++++++++++++++++++++++++ Omni/Cloud/Znc.nix | 76 +++++++++++ Omni/Cloud/post-receive.sh | 39 ++++++ 18 files changed, 1208 insertions(+) create mode 100644 Omni/Cloud/Chat.nix create mode 100644 Omni/Cloud/Comms.nix create mode 100644 Omni/Cloud/Comms/Coturn.nix create mode 100644 Omni/Cloud/Comms/Jitsi.nix create mode 100644 Omni/Cloud/Comms/Mumble.nix create mode 100644 Omni/Cloud/Comms/Xmpp.nix create mode 100644 Omni/Cloud/Git.nix create mode 100644 Omni/Cloud/Gmnisrv.nix create mode 100644 Omni/Cloud/Grocy.nix create mode 100644 Omni/Cloud/Hardware.nix create mode 100644 Omni/Cloud/Hub.nix create mode 100644 Omni/Cloud/Mail.nix create mode 100644 Omni/Cloud/Networking.nix create mode 100644 Omni/Cloud/NostrRelay.nix create mode 100644 Omni/Cloud/Ports.nix create mode 100644 Omni/Cloud/Web.nix create mode 100644 Omni/Cloud/Znc.nix create mode 100755 Omni/Cloud/post-receive.sh (limited to 'Omni/Cloud') diff --git a/Omni/Cloud/Chat.nix b/Omni/Cloud/Chat.nix new file mode 100644 index 0000000..7f86621 --- /dev/null +++ b/Omni/Cloud/Chat.nix @@ -0,0 +1,94 @@ +{ config, pkgs, ... }: +# +# a homeserver for matrix.org. +# +# this uses the config.networking.domain as the ACME host. be sure to add the +# fqdn and element subdomains to security.acme.certs..extraDomainNames +# +# - nixos manual: https://nixos.org/nixos/manual/index.html#module-services-matrix +# +# to create new users: +# +# nix run nixpkgs.matrix-synapse +# register_new_matrix_user -k http://localhost: +# +let + fqdn = "matrix.${config.networking.domain}"; + element = "chat.${config.networking.domain}"; + matrix_port = 8448; +in { + # matrix-synapse server. for what the settings mean, see: + # https://nixos.org/nixos/manual/index.html#module-services-matrix + # + services.matrix-synapse = { + enable = false; + settings.server_name = config.networking.domain; + #registration_shared_secret = "AkGRWSQLga3RoKRFnHhKoeCEIeZzu31y4TRzMRkMyRbBnETkVTSxilf24qySLzQn"; + settings.listeners = [{ + port = matrix_port; + bind_address = "::1"; + type = "http"; + tls = false; + x_forwarded = true; + resources = [{ + names = [ "client" "federation" ]; + compress = false; + }]; + }]; + }; + # matrix needs a database + # + services.postgresql.enable = true; + # web proxy for the matrix server + # + services.nginx = { + enable = true; + recommendedTlsSettings = true; + recommendedOptimisation = true; + recommendedGzipSettings = true; + recommendedProxySettings = true; + virtualHosts = { + # route to matrix-synapse + "${config.networking.domain}" = { + locations."= /.well-known/matrix/server".extraConfig = + let server = { "m.server" = "${fqdn}:443"; }; + in '' + add_header Content-Type application/json; + return 200 '${builtins.toJSON server}'; + ''; + locations."= /.well-known/matrix/client".extraConfig = let + client = { + "m.homeserver" = { "base_url" = "https://${fqdn}"; }; + "m.identity_server" = { "base_url" = "https://vector.im"; }; + }; + in '' + add_header Content-Type application/json; + add_header Access-Control-Allow-Origin *; + return 200 '${builtins.toJSON client}'; + ''; + }; + # reverse proxy for matrix client-server and server-server communication + "${fqdn}" = { + forceSSL = true; + useACMEHost = config.networking.domain; + locations."/".extraConfig = '' + return 404; + ''; + locations."/_matrix" = { + proxyPass = "http://[::1]:${toString matrix_port}"; + }; + }; + }; + }; + # matrix client, available at chat.simatime.com + # + # note that element and matrix-synapse must be on separate fqdn's to + # protect from XSS attacks: + # https://github.com/vector-im/element-web#important-security-note + # + services.nginx.virtualHosts."${element}" = { + useACMEHost = config.networking.domain; + forceSSL = true; + root = pkgs.element-web; + }; +} diff --git a/Omni/Cloud/Comms.nix b/Omni/Cloud/Comms.nix new file mode 100644 index 0000000..bf7a763 --- /dev/null +++ b/Omni/Cloud/Comms.nix @@ -0,0 +1,5 @@ +{ ... }: + +{ + imports = [ ./Comms/Xmpp.nix ./Comms/Mumble.nix ]; +} diff --git a/Omni/Cloud/Comms/Coturn.nix b/Omni/Cloud/Comms/Coturn.nix new file mode 100644 index 0000000..93093f0 --- /dev/null +++ b/Omni/Cloud/Comms/Coturn.nix @@ -0,0 +1,10 @@ +{ config, ... }: + +{ + services.coturn = { + enable = true; + cert = "/var/lib/acme/${config.networking.domain}/fullchain.pem"; + pkey = "/var/lib/acme/${config.networking.domain}/key.pem"; + cli-ip = "127.0.0.1"; + }; +} diff --git a/Omni/Cloud/Comms/Jitsi.nix b/Omni/Cloud/Comms/Jitsi.nix new file mode 100644 index 0000000..17aeced --- /dev/null +++ b/Omni/Cloud/Comms/Jitsi.nix @@ -0,0 +1,17 @@ +{ config, ... }: + +{ + services.jitsi-meet = { + enable = true; + config = { + enableWelcomePage = false; + defaulLang = "en"; + }; + + prosody.enable = true; + nginx.enable = true; + jibri.enable = false; + jicofo.enable = false; + videobridge.enable = false; + }; +} diff --git a/Omni/Cloud/Comms/Mumble.nix b/Omni/Cloud/Comms/Mumble.nix new file mode 100644 index 0000000..66d21a5 --- /dev/null +++ b/Omni/Cloud/Comms/Mumble.nix @@ -0,0 +1,25 @@ +{ config, ... }: + +# mumble and related services +let ports = import ../Ports.nix; +in { + services.murmur = { + enable = true; + openFirewall = true; + environmentFile = "/var/lib/murmur/murmurd.env"; + registerHostname = config.networking.domain; + registerName = config.networking.domain; + }; + + services.botamusique = { + enable = true; + settings = { + webinterface = { + enabled = true; + listening_addr = "127.0.0.1"; + listening_port = ports.botamusique; + }; + radio = { lofi = "https://live.hunter.fm/lofi_high"; }; + }; + }; +} diff --git a/Omni/Cloud/Comms/Xmpp.nix b/Omni/Cloud/Comms/Xmpp.nix new file mode 100644 index 0000000..ad8649b --- /dev/null +++ b/Omni/Cloud/Comms/Xmpp.nix @@ -0,0 +1,210 @@ +{ config, pkgs, ... }: +# +# xmpp chat service +# +let + rootDomain = config.networking.domain; # simatime.com + ssl = { + cert = "/var/lib/acme/${rootDomain}/fullchain.pem"; + key = "/var/lib/acme/${rootDomain}/key.pem"; + }; +in { + networking.firewall.allowedTCPPorts = [ + # https://prosody.im/doc/ports + 5000 # file transfer + 5222 # client connections + 5269 # server-to-server + 5280 # http + 5281 # https + 5347 # external components + 5582 # telnet console + ]; + + services.prosody = { + enable = true; + package = + pkgs.prosody.override { withCommunityModules = [ "conversejs" ]; }; + + # when i learn how to use security.acme better, and use separate certs, then i + # can fix this group + group = "nginx"; + admins = [ "bsima@${rootDomain}" ]; + allowRegistration = true; + inherit ssl; + uploadHttp = { + domain = "upload.${rootDomain}"; + uploadExpireAfter = toString (60 * 60 * 24 * 30); # 30 days, as seconds + }; + + modules = { + announce = true; + blocklist = true; + bookmarks = true; + bosh = true; + carbons = true; + cloud_notify = true; + csi = true; + dialback = true; + disco = true; + groups = true; + http_files = false; # hm, look into this + motd = true; + pep = true; + ping = true; + private = true; + proxy65 = true; + register = true; + roster = true; + server_contact_info = true; + smacks = true; + vcard = true; + watchregistrations = true; + websocket = true; + welcome = true; + }; + + extraConfig = '' + conversejs_options = { + allow_registration = true; + bosh_service_url = "https://${rootDomain}/http-bind"; + debug = true; + loglevel = "debug"; + -- default_domain = "${rootDomain}"; + -- domain_placeholder = "${rootDomain}"; + -- jid = "${rootDomain}"; + -- keepalive = true; + -- registration_domain = "${rootDomain}"; + websocket_url = "wss://${rootDomain}/xmpp-websocket"; + } + + cross_domain_websocket = { "https://${rootDomain}", "https://anon.${rootDomain}" } + cross_domain_bosh = false; -- handle this with nginx + consider_bosh_secure = true; + + -- this is a virtualhost that allows anonymous authentication. use this + -- for a public lobby. the nix module doesn't support 'authentication' + -- so i have to do this here. + VirtualHost "anon.${rootDomain}" + authentication = "anonymous" + ssl = { + cafile = "/etc/ssl/certs/ca-bundle.crt"; + key = "${ssl.key}"; + certificate = "${ssl.cert}"; + }; + ''; + + muc = [ + { + domain = "conference.${rootDomain}"; + maxHistoryMessages = 10000; + name = "Chat Rooms"; + restrictRoomCreation = "admin"; + roomDefaultHistoryLength = 20; + roomDefaultMembersOnly = true; + roomDefaultModerated = true; + roomDefaultPublic = false; + } + { + domain = "chat.${rootDomain}"; + maxHistoryMessages = 10000; + name = "Chat Rooms"; + restrictRoomCreation = false; + roomDefaultHistoryLength = 200; + roomDefaultMembersOnly = false; + roomDefaultModerated = false; + roomDefaultPublic = true; + roomDefaultPublicJids = true; + } + ]; + + virtualHosts = { + "${rootDomain}" = { + domain = "${rootDomain}"; + enabled = true; + inherit ssl; + }; + }; + }; + + services.prosody-filer = { enable = true; }; + + services.nginx.virtualHosts."${rootDomain}".locations = { + "/http-bind" = { + proxyPass = "https://${rootDomain}:5281/http-bind"; + extraConfig = '' + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + add_header Access-Control-Allow-Origin "*"; + ''; + }; + + "/xmpp-websocket" = { + proxyPass = "https://${rootDomain}:5281/xmpp-websocket"; + extraConfig = '' + proxy_http_version 1.1; + proxy_buffering off; + proxy_set_header Host $host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_read_timeout 86400; + add_header Access-Control-Allow-Origin "*"; + ''; + }; + + "/chat" = { + proxyPass = "https://${rootDomain}:5281/conversejs"; + extraConfig = '' + add_header Access-Control-Allow-Origin "*"; + ''; + }; + }; + + services.nginx.virtualHosts."anon.${rootDomain}" = { + useACMEHost = "${rootDomain}"; + forceSSL = true; + locations = { + "/http-bind" = { + proxyPass = "https://anon.${rootDomain}:5281/http-bind"; + extraConfig = '' + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + if ($request_method ~* "(GET|POST)") { + add_header Access-Control-Allow-Origin "*"; + } + if ($request_method = OPTIONS) { + add_header Access-Control-Allow-Origin "*"; + add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, HEAD"; + add_header Access-Control-Allow-Headers "Authorization, Origin, X-Requested-With, Content-Type, Accept"; + return 200; + } + ''; + }; + }; + }; + + users.users.nginx.extraGroups = [ "prosody" ]; + + security.acme.certs.${rootDomain}.extraDomainNames = [ + # these stopped working idk why + #"upload.${rootDomain}" + #"conference.${rootDomain}" + "anon.${rootDomain}" + "chat.${rootDomain}" + ]; + + #security.acme.certs.prosody = { + # domain = "${domain}"; + # group = "prosody"; + # dnsProvider = "rfc2136"; + # #credentialsFile = config.secrets.files.dns_creds.path; + # postRun = "systemctl restart prosody"; + # extraDomainNames = [ + # domain + # "upload.${domain}" + # ]; + #}; +} diff --git a/Omni/Cloud/Git.nix b/Omni/Cloud/Git.nix new file mode 100644 index 0000000..bc97d23 --- /dev/null +++ b/Omni/Cloud/Git.nix @@ -0,0 +1,119 @@ +{ lib, config, pkgs, ... }: + +let + inherit (config.networking) domain; + root = "/var/git"; + ports = import ./Ports.nix; +in { + services = { + cgit.cloud = { + enable = true; + nginx.location = "/git"; + nginx.virtualHost = "/git"; + scanPath = "/var/git/repositories"; + settings = { + strict-export = "git-daemon-export-ok"; + css = "/git/cgit.css"; + logo = "/git/cgit.png"; + root-title = "ben's git repos"; + root-desc = "building"; + enable-git-config = 1; + clone-url = lib.strings.concatStringsSep " " [ + "https://$HTTP_HOST/git/$CGIT_REPO_URL" + "git://$HTTP_HOST/$CGIT_REPO_URL" + "git@$HTTP_HOST:$CGIT_REPO_URL" + ]; + }; + }; + gitolite = { + enable = true; + enableGitAnnex = true; + dataDir = root; + user = "git"; + group = "git"; + # the umask is necessary to give the git group read permissions, otherwise + # git-daemon et al can't access the repos + extraGitoliteRc = '' + $RC{SITE_INFO} = 'a computer is a bicycle for the mind.'; + $RC{UMASK} = 0027; + $RC{GIT_CONFIG_KEYS} = '.*'; + ''; + adminPubkey = lib.trivial.pipe ../Keys/Ben.pub [ + builtins.readFile + (lib.strings.splitString "\n") + lib.lists.head + ]; + # commonHooks = [ ./git-hooks ]; + }; + gitDaemon = { + enable = true; + basePath = "${root}/repositories"; + listenAddress = domain; + user = "gitDaemon"; + group = "gitDaemon"; + }; + gerrit = { + enable = false; + builtinPlugins = [ + "commit-message-length-validator" + "delete-project" + "plugin-manager" + "singleusergroup" + "reviewnotes" + ]; + jvmOpts = [ + # https://stackoverflow.com/a/71817404 + "--add-opens" + "java.base/java.lang=ALL-UNNAMED" + "--add-opens" + "java.base/java.util=ALL-UNNAMED" + ]; + plugins = [ + (pkgs.fetchurl { + url = + "https://github.com/davido/gerrit-oauth-provider/releases/download/v3.5.1/gerrit-oauth-provider.jar"; + sha256 = "sha256-MS3ElMRUrBX4miiflepMETRK3SaASqpqO3nUn9kq3Gk="; + }) + ]; + listenAddress = "[::]:${toString ports.gerrit}"; + serverId = "cc6cca15-2a7e-4946-89b9-67f5d6d996ae"; + settings = { + auth.type = "OAUTH"; + auth.gitBasicAuthPolicy = "HTTP"; + download.command = [ "checkout" "cherry_pick" "pull" "format_patch" ]; + gerrit.canonicalWebUrl = "https://gerrit.${domain}"; + httpd.listenUrl = + "proxy-https://${config.services.gerrit.listenAddress}"; + plugin.gerrit-oauth-provider-github-oauth = { + root-url = "https://github.com"; + client-id = "e48084aa0eebe31a2b18"; + }; + sshd.advertisedAddress = + "gerrit.${domain}:${toString ports.gerrit-ssh}"; + sshd.listenAddress = "[::]:${toString ports.gerrit-ssh}"; + }; + }; + nginx.virtualHosts."gerrit.${domain}" = { + forceSSL = true; + useACMEHost = domain; + locations."/" = { + proxyPass = "http://localhost:${toString ports.gerrit}"; + extraConfig = '' + proxy_set_header X-Forwarded-For $remote_addr; + ''; + }; + }; + }; + # need to specify that these users can access git files by being part of the + # git group + users.users = { + gitDaemon = { + group = "gitDaemon"; + isSystemUser = true; + description = "Git daemon user"; + extraGroups = [ "git" ]; + }; + "nginx".extraGroups = [ "git" ]; + }; + users.groups = { gitDaemon = { }; }; +} diff --git a/Omni/Cloud/Gmnisrv.nix b/Omni/Cloud/Gmnisrv.nix new file mode 100644 index 0000000..e2a66f6 --- /dev/null +++ b/Omni/Cloud/Gmnisrv.nix @@ -0,0 +1,40 @@ +{ lib, config, pkgs, ... }: + +let cfg = config.services.gmnisrv; +in { + meta.maintainers = [ lib.maintainers.bsima ]; + options.services.gmnisrv = { + enable = lib.mkEnableOption "Enable the gmnisrv service"; + listen = lib.mkOption { + description = "Addresses and ports on which to listen."; + default = lib.mkDefault "0.0.0.0:1965 [::]:1965"; + }; + settings = lib.mkOption { + # type = cfgFormat.type; + description = '' + Configuration for gmnisrv. See gmnisrv.ini(5) for supported settings. + ''; + default = { + ":tls" = { "store" = lib.mkDefault "${cfg.dataDir}/certs"; }; + }; + }; + dataDir = lib.mkOption { + type = lib.types.str; + default = "/var/lib/gemini"; + description = "Where gmnisrv should store certs and other data."; + }; + }; + config = lib.mkIf cfg.enable { + systemd.services.gmnisrv = { + description = "gmnisrv service"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + script = let + ini = lib.generators.toINIWithGlobalSection { } { + globalSection = { "listen" = cfg.listen; }; + sections = cfg.settings; + }; + in "${pkgs.gmnisrv}/bin/gmnisrv -C ${ini}"; + }; + }; +} diff --git a/Omni/Cloud/Grocy.nix b/Omni/Cloud/Grocy.nix new file mode 100644 index 0000000..697c2f1 --- /dev/null +++ b/Omni/Cloud/Grocy.nix @@ -0,0 +1,17 @@ +{ ... }: + +{ + services.grocy = { + enable = true; + hostName = "grocy.simatime.com"; + nginx.enableSSL = false; # set in Web.nix + settings = { + calendar = { + firstDayOfWeek = 1; + showWeekNumber = true; + }; + currency = "USD"; + culture = "en"; + }; + }; +} diff --git a/Omni/Cloud/Hardware.nix b/Omni/Cloud/Hardware.nix new file mode 100644 index 0000000..8fdbd4e --- /dev/null +++ b/Omni/Cloud/Hardware.nix @@ -0,0 +1,9 @@ +{ modulesPath, ... }: { + imports = [ (modulesPath + "/profiles/qemu-guest.nix") ]; + boot.loader.grub.device = "/dev/vda"; + boot.initrd.kernelModules = [ "nvme" ]; + fileSystems."/" = { + device = "/dev/vda1"; + fsType = "ext4"; + }; +} diff --git a/Omni/Cloud/Hub.nix b/Omni/Cloud/Hub.nix new file mode 100644 index 0000000..39bbdd0 --- /dev/null +++ b/Omni/Cloud/Hub.nix @@ -0,0 +1,57 @@ +{ lib, pkgs, config, ... }: + +let + ssl = { + cert = "/var/lib/acme/${config.networking.domain}/fullchain.pem"; + key = "/var/lib/acme/${config.networking.domain}/key.pem"; + }; + ports = import ./Ports.nix; + mkPlugin = path: settings: { + plugin = "${pkgs.uhub}/plugins/${path}"; + inherit settings; + }; + motdFile = pkgs.writeText "motd" '' + Meshheads write code. + ''; + rulesFile = pkgs.writeText "rules" '' + 1. x + 2. (λx.M) + 3. (M N) + 4. Profit. + ''; +in { + networking.firewall.allowedUDPPorts = [ ports.meshhub ]; + networking.firewall.allowedTCPPorts = [ ports.meshhub ]; + + services.uhub = { + "meshhub" = { + enable = true; + enableTLS = false; + settings = { + hub_name = "meshhub"; + hub_description = "vaporware is dead. long live vaporware"; + server_port = toString ports.meshhub; + server_bind_addr = "any"; + max_users = 14; # we only have 14 users in meshheads chat + tls_private_key = ssl.key; + tls_certificate = ssl.cert; + tls_enable = "no"; + tls_require = "no"; + }; + plugins = lib.attrsets.mapAttrsToList mkPlugin { + #"mod_logging.so" = { + # file = "/var/log/uhub/meshhub.log"; + #}; + "mod_welcome.so" = { + motd = "${motdFile}"; + rules = "${rulesFile}"; + }; + "mod_chat_history.so" = { + history_max = "200"; + history_default = "10"; + history_connect = "5"; + }; + }; + }; + }; +} diff --git a/Omni/Cloud/Mail.nix b/Omni/Cloud/Mail.nix new file mode 100644 index 0000000..fa99cf3 --- /dev/null +++ b/Omni/Cloud/Mail.nix @@ -0,0 +1,54 @@ +{ ... }: + +/* Known issues: + + - when the acme cert gets refreshed, you need to manually restart dovecot + - when restarting dovecot, it might hang, in that case do: + systemctl --job-mode=ignore-dependencies restart dovecot2 postfix +*/ + +{ + mailserver = { + enable = true; + monitoring = { + enable = false; + alertAddress = "bsima@me.com"; + }; + fqdn = "simatime.com"; + domains = [ "simatime.com" "bsima.me" ]; + certificateScheme = 3; # let's encrypt + enableImap = true; + enablePop3 = true; + enableImapSsl = true; + enablePop3Ssl = true; + enableManageSieve = true; + virusScanning = false; # ur on ur own + localDnsResolver = true; + + loginAccounts = { + "ben@simatime.com" = { + hashedPasswordFile = "/home/ben/hashed-mail-password"; + aliases = [ + # my default email + "ben@bsima.me" + # admin stuff + "postmaster@simatime.com" + "abuse@simatime.com" + ]; + catchAll = [ "simatime.com" "bsima.me" ]; + quota = "10G"; + }; + "dev@simatime.com" = { + hashedPasswordFile = "/home/ben/hashed-mail-password"; + aliases = [ "dev@bsima.me" ]; + quota = "10G"; + }; + "nick@simatime.com" = { + hashedPassword = + "$6$31P/Mg8k8Pezy1e$Fn1tDyssf.1EgxmLYFsQpSq6RP4wbEvP/UlBlXQhyKA9FnmFtJteXsbJM1naa8Kyylo8vZM9zmeoSthHS1slA1"; + aliases = [ "nicolai@simatime.com" ]; + quota = "1G"; + }; + }; + }; +} diff --git a/Omni/Cloud/Networking.nix b/Omni/Cloud/Networking.nix new file mode 100644 index 0000000..1c1f832 --- /dev/null +++ b/Omni/Cloud/Networking.nix @@ -0,0 +1,48 @@ +{ lib, ... }: { + # This file was populated at runtime with the networking + # details gathered from the active system. + networking = { + nameservers = [ "8.8.8.8" ]; + defaultGateway = "143.198.112.1"; + defaultGateway6 = "2604:a880:400:d0::1"; + dhcpcd.enable = false; + usePredictableInterfaceNames = lib.mkForce false; + interfaces = { + eth0 = { + ipv4.addresses = [ + { + address = "143.198.118.179"; + prefixLength = 20; + } + { + address = "10.10.0.7"; + prefixLength = 16; + } + ]; + ipv6.addresses = [ + { + address = "2604:a880:400:d0::19f1:7001"; + prefixLength = 64; + } + { + address = "fe80::a06e:26ff:fee1:941"; + prefixLength = 64; + } + ]; + ipv4.routes = [{ + address = "143.198.112.1"; + prefixLength = 32; + }]; + ipv6.routes = [{ + address = "2604:a880:400:d0::1"; + prefixLength = 128; + }]; + }; + + }; + }; + services.udev.extraRules = '' + ATTR{address}=="a2:6e:26:e1:09:41", NAME="eth0" + ATTR{address}=="f2:4e:52:1a:72:ef", NAME="eth1" + ''; +} diff --git a/Omni/Cloud/NostrRelay.nix b/Omni/Cloud/NostrRelay.nix new file mode 100644 index 0000000..0be8a6f --- /dev/null +++ b/Omni/Cloud/NostrRelay.nix @@ -0,0 +1,39 @@ +{ config, pkgs, ... }: + +let + ports = import ./Ports.nix; + dataDir = "/var/lib/nostr-rs-relay"; + # https://git.sr.ht/~gheartsfield/nostr-rs-relay/tree/master/config.toml + cfg = pkgs.writeText "config.toml" '' + [info] + name = "simatime" + relay_url = "wss://nostr.simatime.com" + description = "yet another nostr relay" + + [database] + data_directory = "/var/lib/nostr-rs-relay" + + [network] + address = "0.0.0.0" + port = ${toString ports.nostr-relay} + ''; +in { + config.systemd.services.nostr-relay = { + path = [ pkgs.nostr-rs-relay ]; + wantedBy = [ "multi-user.target" ]; + preStart = '' + mkdir -p ${dataDir} + cat "${cfg}" > ${dataDir}/config.toml + ''; + script = "nostr-rs-relay --db ${dataDir}"; + serviceConfig = { + Environment = + [ "RUST_LOG=info,nostr_rs_relay=info" "APP_DATA=${dataDir}" ]; + WorkingDirectory = dataDir; + KillSignal = "INT"; + Type = "simple"; + Restart = "always"; + RestartSec = "1"; + }; + }; +} diff --git a/Omni/Cloud/Ports.nix b/Omni/Cloud/Ports.nix new file mode 100644 index 0000000..5b8446c --- /dev/null +++ b/Omni/Cloud/Ports.nix @@ -0,0 +1,46 @@ +{ + bensIp = "24.140.205.252"; # not a port, but it's convenient + barrier = 24800; + bitcoind = 8333; + bitcoind-rpc = 8332; + botamusique = 8181; + dandel-rovbur = 8080; + delugeWeb = 8112; + dns = 53; + dragons = 8095; + et = 2022; + gemini = 1965; + gerrit = 8081; + gerrit-ssh = 2222; + git = 9418; + headscale = 8844; + hoogle = 8008; + http = 80; + httpdev = { + from = 8000; + to = 8099; + }; + https = 443; + invidious = 8086; + jellyfin = 8096; + jupyter = 4000; + k3s = 6443; + libreddit = 8085; + meshhub = 1511; + mpd = 6600; + mpd-stream = 8097; + murmur = 64738; + nostr-relay = 8084; + radicale = 5232; + sabten = 8081; + ssh = 22; + stableDiffusion = 8501; + syncthing-gui = 8384; + tor = 144; + torrents = { + from = 3000; + to = 3099; + }; + wireguard = 51820; + znc = 5000; +} diff --git a/Omni/Cloud/Web.nix b/Omni/Cloud/Web.nix new file mode 100644 index 0000000..0e080f5 --- /dev/null +++ b/Omni/Cloud/Web.nix @@ -0,0 +1,303 @@ +{ config, ... }: + +let + rootDomain = config.networking.domain; + ports = import ./Ports.nix; +in { + imports = [ ./Gmnisrv.nix ]; + networking.firewall = { + allowedTCPPorts = [ + ports.ssh + ports.git + ports.http + ports.https + ports.sabten + ports.gemini + ports.radicale + ports.znc + ports.gerrit-ssh + ]; + }; + + services = { + + libreddit = { + enable = true; + address = "127.0.0.1"; + openFirewall = true; + port = ports.libreddit; + }; + + invidious = { + enable = true; + database.createLocally = true; + domain = "youtube.${rootDomain}"; + nginx.enable = false; # do this myself, below + port = ports.invidious; + }; + + radicale = { + enable = true; + rights = { + # Allow reading root collection for authenticated users + root = { + user = ".+"; + collection = ""; + permissions = "R"; + }; + # Allow reading and writing principal collection (same as username) + principal = { + user = ".+"; + collection = "{user}"; + permissions = "RW"; + }; + # Allow reading and writing calendars and address books that are direct + # children of the principal collection + calendars = { + user = ".+"; + collection = "{user}/[^/]+"; + permissions = "rw"; + }; + # Allow any authenticated user to modify the public collection + public = { + user = ".*"; + collection = "public/.*"; + permissions = "rw"; + }; + }; + settings = { + server = { + hosts = [ + "0.0.0.0:${toString ports.radicale}" + "[::]:${toString ports.radicale}" + ]; + }; + auth = { + type = "htpasswd"; + htpasswd_filename = "/etc/radicale/users"; + htpasswd_encryption = "plain"; + }; + }; + }; + + gmnisrv = { + enable = false; + listen = "0.0.0.0:${toString ports.gemini} [::]:${toString ports.gemini}"; + settings = { + ":tls" = { store = "/var/lib/gmnisrv"; }; + "bsima.me" = { "root" = "/var/web/ben"; }; + "${rootDomain}" = { + "root" = "/var/web/simatime.com"; + "cgi" = "on"; + }; + }; + }; + + nginx = { + enable = true; + recommendedGzipSettings = true; + recommendedOptimisation = true; + recommendedProxySettings = true; + recommendedTlsSettings = true; + statusPage = true; + + user = "nginx"; + group = "nginx"; + + virtualHosts = { + ${rootDomain} = { + forceSSL = true; + enableACME = true; + locations = { + # the nginx/cgit module puts a '/' at the end of 'location', so we need to + # redirect '/git' to '/git/' + "/git".return = "301 https://$host/git/"; + # nostr nip-5 verification + "/.well-known/nostr.json".return = "200 '${ + builtins.toJSON { + names.bensima = + "2fa4b9ba71b6dab17c4723745bb7850dfdafcb6ae1a8642f76f9c64fa5f43436"; + } + }'"; + # disabled for nixpert test + "/" = { + root = "/var/web/simatime.com"; + extraConfig = '' + autoindex on; + ''; + }; + # serve /~$USER paths + "~ ^/~(.+?)(/.*)?$" = { + alias = "/var/web/$1$2"; + index = "index.html index.htm"; + extraConfig = '' + autoindex on; + ''; + }; + }; + }; + + "bsima.me" = { + locations."/" = { + root = "/var/web/ben"; + index = "index.html index.htm"; + extraConfig = '' + autoindex on; + ''; + }; + serverAliases = [ "www.bsima.me" ]; + forceSSL = true; + useACMEHost = rootDomain; + }; + + "hoogle.${rootDomain}" = { + locations."/".proxyPass = + "http://${ports.bensIp}:${toString ports.hoogle}"; + forceSSL = true; + useACMEHost = rootDomain; + }; + + "tv.${rootDomain}" = { + locations."/".proxyPass = + "http://${ports.bensIp}:${toString ports.jellyfin}"; + forceSSL = true; + useACMEHost = rootDomain; + }; + + "cal.${rootDomain}" = { + locations."/".proxyPass = + "http://localhost:${toString ports.radicale}"; + forceSSL = true; + useACMEHost = rootDomain; + extraConfig = '' + proxy_set_header X-Script-Name /radicale; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $host; + proxy_pass_header Authorization; + ''; + }; + + "reddit.${rootDomain}" = { + locations."/".proxyPass = + "http://localhost:${toString ports.libreddit}"; + forceSSL = true; + useACMEHost = rootDomain; + }; + "www.reddit.${rootDomain}" = { + forceSSL = true; + useACMEHost = rootDomain; + globalRedirect = "reddit.${rootDomain}"; + }; + "old.reddit.${rootDomain}" = { + forceSSL = true; + useACMEHost = rootDomain; + globalRedirect = "reddit.${rootDomain}"; + }; + + "youtube.${rootDomain}" = { + locations."/".proxyPass = + "http://localhost:${toString ports.invidious}"; + forceSSL = true; + useACMEHost = rootDomain; + }; + "www.youtube.${rootDomain}" = { + forceSSL = true; + useACMEHost = rootDomain; + globalRedirect = "youtube.${rootDomain}"; + }; + "m.youtube.${rootDomain}" = { + forceSSL = true; + useACMEHost = rootDomain; + globalRedirect = "youtube.${rootDomain}"; + }; + + "dandel-rovbur.${rootDomain}" = { + locations."/".proxyPass = + "http://${ports.bensIp}:${toString ports.dandel-rovbur}"; + forceSSL = true; + useACMEHost = rootDomain; + }; + + "sabten.${rootDomain}" = { + locations."/".proxyPass = "http://localhost:${toString ports.sabten}"; + forceSSL = true; + useACMEHost = rootDomain; + }; + + "sd.${rootDomain}" = { + forceSSL = true; + useACMEHost = rootDomain; + locations."/" = { + proxyPass = + "http://${ports.bensIp}:${toString ports.stableDiffusion}"; + proxyWebsockets = true; + }; + }; + + "music.${rootDomain}" = { + forceSSL = true; + useACMEHost = rootDomain; + locations."/".proxyPass = + "http://localhost:${toString ports.botamusique}"; + }; + + "nostr.${rootDomain}" = { + forceSSL = true; + useACMEHost = rootDomain; + locations."/" = { + proxyPass = "http://localhost:${toString ports.nostr-relay}"; + extraConfig = '' + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + ''; + }; + }; + + "notebook.${rootDomain}" = { + forceSSL = true; + useACMEHost = rootDomain; + locations = { + "/" = { + proxyPass = "http://${ports.bensIp}:${toString ports.jupyter}"; + proxyWebsockets = true; + extraConfig = '' + proxy_buffering off; + proxy_read_timeout 86400; + ''; + }; + "/(api/kernels/[^/]+/channels|terminals/websocket)/" = { + proxyPass = "http://${ports.bensIp}:${toString ports.jupyter}"; + proxyWebsockets = true; + }; + }; + }; + + }; + }; + }; + + # This must contain all of the other domains we host + security.acme.certs.${rootDomain}.extraDomainNames = + [ "bsima.me" "www.bsima.me" ] ++ map (sub: "${sub}.${rootDomain}") [ + "music" + "tv" + "matrix" + "chat" + "hoogle" + "dandel-rovbur" + "sabten" + "cal" + "notebook" + "nostr" + "reddit" + "old.reddit" + "www.reddit" + "youtube" + "www.youtube" + "m.youtube" + "sd" + "gerrit" + ]; +} diff --git a/Omni/Cloud/Znc.nix b/Omni/Cloud/Znc.nix new file mode 100644 index 0000000..e68ebc7 --- /dev/null +++ b/Omni/Cloud/Znc.nix @@ -0,0 +1,76 @@ +/* N.B.: generate znc passwords with 'nix-shell -p znc --command "znc --makepass"' + + - https://wiki.znc.in/Configuration +*/ + +{ pkgs, ... }: + +{ + services = { + znc = { + enable = true; + openFirewall = true; + modulePackages = with pkgs.zncModules; + [ + #backlog clientaway clientbuffer + #ignore + ]; + useLegacyConfig = false; + config = { + LoadModule = [ "adminlog" ]; + Motd = "welcome to znc.simatime.com"; + User.bsima = { + Admin = true; + Nick = "bsima"; + AltNick = "bsima1"; + LoadModule = [ "chansaver" "controlpanel" "log" ]; + Network = { + efnet = { + Server = "irc.efnet.info +6697"; + LoadModule = [ "simple_away" ]; + }; + libera = { + Server = "irc.libera.chat +6697"; + LoadModule = [ "simple_away" "nickserv" "sasl" ]; + Nick = "bsima"; + Chan = { + "#emacs" = { Detached = true; }; + "#guile" = { }; + "#guix" = { Detached = true; }; + "#haskell" = { }; + "#hledger" = { }; + "#nixos" = { }; + "#notmuch" = { Detached = true; }; + "#org-mode" = { Detached = true; }; + "#scheme" = { Detached = true; }; + "#sr.ht" = { Detached = true; }; + "#xmonad" = { Detached = true; }; + }; + }; + oftc = { + Server = "irc.oftc.net +6697"; + LoadModule = [ "simple_away" "nickserv" ]; + Nick = "bsima"; + Chan = { "#home-manager" = { }; }; + }; + zeronode = { + Server = "irc.zeronode.net +6697"; + LoadModule = [ "simple_away" "nickserv" ]; + Nick = "bsima"; + Chan = { "#NoAgenda" = { }; }; + }; + #sorcery = { + # Server = "irc.sorcery.net +6697"; + #}; + }; + Pass.password = { + Method = "sha256"; + Hash = + "bead16d806e7bf5cbbc31d572b20f01e2b253eb60e2497ce465df56306becd02"; + Salt = "/GhmBMc+E6b7qd8muFEe"; + }; + }; + }; + }; + }; +} diff --git a/Omni/Cloud/post-receive.sh b/Omni/Cloud/post-receive.sh new file mode 100755 index 0000000..179fbd0 --- /dev/null +++ b/Omni/Cloud/post-receive.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# +# creates an archive of a git repo on push +# +# unfortunately the nixos gitolite module does not copy the 'commonHooks' +# properly, so we have to manually deploy this like so: +# +# scp Omni/Cloud/post-receive \ +# root@simatime.com:/srv/git/.gitolite/hooks/common/post-receive +# +# One time only: +# +# ssh root@simatime.com "sudo -u git gitolite setup -ho" +# +# Also on first-time setup, might need to manually check the permissions are +# correct on $webroot/archive or wherever else. +# +set -euo pipefail +while read -r _ newrev refname +do + if [[ -e ./git-daemon-export-ok ]] + then + repo=$(basename "$PWD" | sed 's/.git//g') + branch=$(git rev-parse --symbolic --abbrev-ref "$refname") + webroot="/srv/www/simatime.com/" + outdir="$webroot/archive/$repo/$branch" + mkdir -p "$outdir" + echo " making: https://simatime.com/archive/$repo/$branch/$newrev.tar.gz" + git archive "$branch" --prefix "$repo-$branch/" --format tar \ + | gzip > "$outdir/$newrev.tar.gz" + echo " making: https://simatime.com/archive/$repo/$branch/$newrev.sha256" + hash=$(nix-prefetch-url --unpack file://"$outdir"/"$newrev".tar.gz 2>/dev/null) + echo "$hash" > "$outdir/$newrev.sha256" + echo " commit: $newrev" + echo " sha256: $hash" + echo "in omni: deps update $repo --branch $branch --rev $newrev --attribute sha256=$hash" + chmod -R 755 "$webroot/archive" + fi +done -- cgit v1.2.3