From 2aa6b8efc6debfe627828ca947759a8a6640a74b Mon Sep 17 00:00:00 2001 From: root Date: Mon, 3 Feb 2020 19:07:46 -0600 Subject: [PATCH] In a pretty good working state --- config/fudo/chat.nix | 220 ++++++++++++++ config/fudo/common.nix | 34 +++ config/fudo/grafana.nix | 45 ++- config/fudo/include/rainloop.nix | 117 +++++++ config/fudo/kdc.nix | 4 +- config/fudo/mail-container.nix | 6 +- config/fudo/mail/postfix.nix | 23 +- config/fudo/node-exporter.nix | 2 +- config/fudo/postgres.nix | 52 ++-- config/fudo/profiles.nix | 32 -- config/fudo/profiles/desktop.nix | 151 ---------- config/fudo/profiles/server.nix | 27 -- config/fudo/sites.nix | 79 ----- config/fudo/webmail.nix | 318 ++++++++++++++++++++ config/local.nix | 7 +- fudo/email.nix | 2 +- fudo/profiles/default.nix | 8 + fudo/profiles/desktop.nix | 154 ++++++++++ fudo/profiles/server.nix | 80 +++++ fudo/sites/default.nix | 8 + fudo/sites/portage.nix | 60 ++++ fudo/sites/seattle.nix | 201 +++++++++++++ fudo/users.nix | 7 + hosts/france.nix | 148 +++++++-- networks/fudo.org.nix | 112 ------- networks/sea.fudo.org.nix | 192 ------------ profiles/.#ldap-server.nix | 1 - profiles/desktop.nix | 151 ---------- profiles/ldap-server.nix | 19 -- profiles/server.nix | 16 - profiles/services/basic_acme.nix | 43 --- profiles/services/local_nameserver.nix | 136 --------- profiles/vm/http.nix | 42 --- profiles/vm/postgres.nix | 75 ----- static/fudo.link/android-chrome-192x192.png | Bin 0 -> 4277 bytes static/fudo.link/android-chrome-512x512.png | Bin 0 -> 14022 bytes static/fudo.link/apple-touch-icon.png | Bin 0 -> 3852 bytes static/fudo.link/favicon-16x16.png | Bin 0 -> 254 bytes static/fudo.link/favicon-32x32.png | Bin 0 -> 436 bytes static/fudo.link/favicon.ico | Bin 0 -> 15406 bytes static/fudo.link/site.webmanifest | 1 + 41 files changed, 1418 insertions(+), 1155 deletions(-) create mode 100644 config/fudo/chat.nix create mode 100644 config/fudo/include/rainloop.nix delete mode 100644 config/fudo/profiles.nix delete mode 100644 config/fudo/profiles/desktop.nix delete mode 100644 config/fudo/profiles/server.nix delete mode 100644 config/fudo/sites.nix create mode 100644 config/fudo/webmail.nix create mode 100644 fudo/profiles/default.nix create mode 100644 fudo/profiles/desktop.nix create mode 100644 fudo/profiles/server.nix create mode 100644 fudo/sites/default.nix create mode 100644 fudo/sites/portage.nix create mode 100644 fudo/sites/seattle.nix delete mode 100644 networks/fudo.org.nix delete mode 100644 networks/sea.fudo.org.nix delete mode 120000 profiles/.#ldap-server.nix delete mode 100644 profiles/desktop.nix delete mode 100644 profiles/ldap-server.nix delete mode 100644 profiles/server.nix delete mode 100644 profiles/services/basic_acme.nix delete mode 100644 profiles/services/local_nameserver.nix delete mode 100644 profiles/vm/http.nix delete mode 100644 profiles/vm/postgres.nix create mode 100644 static/fudo.link/android-chrome-192x192.png create mode 100644 static/fudo.link/android-chrome-512x512.png create mode 100644 static/fudo.link/apple-touch-icon.png create mode 100644 static/fudo.link/favicon-16x16.png create mode 100644 static/fudo.link/favicon-32x32.png create mode 100644 static/fudo.link/favicon.ico create mode 100644 static/fudo.link/site.webmanifest diff --git a/config/fudo/chat.nix b/config/fudo/chat.nix new file mode 100644 index 0000000..d82d10a --- /dev/null +++ b/config/fudo/chat.nix @@ -0,0 +1,220 @@ +{ pkgs, lib, config, ... }: + +with lib; +let + cfg = config.fudo.chat; + +in { + options.fudo.chat = { + enable = mkEnableOption "Enable chat server"; + + hostname = mkOption { + type = types.str; + description = "Hostname at which this chat server is accessible."; + example = "chat.mydomain.com"; + }; + + site-name = mkOption { + type = types.str; + description = "The name of this chat server."; + example = "My Fancy Chat Site"; + }; + + smtp-server = mkOption { + type = types.str; + description = "SMTP server to use for sending notification emails."; + example = "mail.my-site.com"; + }; + + smtp-user = mkOption { + type = types.str; + description = "Username with which to connect to the SMTP server."; + }; + + smtp-password-file = mkOption { + type = types.path; + description = "Path to a file containing the password to use while connecting to the SMTP server."; + }; + + state-directory = mkOption { + type = types.str; + description = "Path at which to store server state data."; + default = "/var/lib/mattermost"; + }; + + database = mkOption { + type = (types.submodule { + options = { + name = mkOption { + type = types.str; + description = "Database name."; + }; + + hostname = mkOption { + type = types.str; + description = "Database host."; + }; + + user = mkOption { + type = types.str; + description = "Database user."; + }; + + password-file = mkOption { + type = types.path; + description = "Path to file containing database password."; + }; + }; + }); + description = "Database configuration."; + example = { + name = "my_database"; + hostname = "my.database.com"; + user = "db_user"; + password-file = /path/to/some/file.pw; + }; + }; + }; + + config = mkIf cfg.enable (let + pkg = pkgs.mattermost; + default-config = builtins.fromJSON (readFile "${pkg}/config/config.json"); + modified-config = recursiveUpdate default-config { + ServiceSettings.SiteURL = "https://${cfg.hostname}"; + ServiceSettings.ListenAddress = "127.0.0.1:8065"; + TeamSettings.SiteName = cfg.site-name; + EmailSettings = { + RequireEmailVerification = true; + SMTPServer = cfg.smtp-server; + SMTPPort = 587; + EnableSMTPAuth = true; + SMTPUsername = cfg.smtp-user; + SMTPPassword = (fileContents cfg.smtp-password-file); + SendEmailNotifications = true; + ConnectionSecurity = "STARTTLS"; + FeedbackEmail = "chat@fudo.org"; + FeedbackName = "Admin"; + }; + EnableEmailInvitations = true; + SqlSettings.DriverName = "postgres"; + SqlSettings.DataSource = + "postgres://${cfg.database.user}:${fileContents cfg.database.password-file}@${cfg.database.hostname}:5432/${cfg.database.name}"; + }; + mattermost-config-file = pkgs.writeText "mattermost-config.json" (builtins.toJSON modified-config); + mattermost-user = "mattermost"; + mattermost-group = "mattermost"; + + in { + users = { + users = { + ${mattermost-user} = { + isSystemUser = true; + group = mattermost-group; + }; + }; + + groups = { + ${mattermost-group} = { + members = [ mattermost-user ]; + }; + }; + }; + + system.activationScripts.mattermost = '' + mkdir -p ${cfg.state-directory} + ''; + + systemd.services.mattermost = { + description = "Mattermost Chat Server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + preStart = '' + mkdir -p ${cfg.state-directory}/config + cp ${mattermost-config-file} ${cfg.state-directory}/config/config.json + ln -sf ${pkg}/bin ${cfg.state-directory} + ln -sf ${pkg}/fonts ${cfg.state-directory} + ln -sf ${pkg}/i18n ${cfg.state-directory} + ln -sf ${pkg}/templates ${cfg.state-directory} + cp -uRL ${pkg}/client ${cfg.state-directory} + chown -R ${mattermost-user}:${mattermost-group} ${cfg.state-directory} + chmod u+w -R ${cfg.state-directory}/client + chmod o-rwx -R ${cfg.state-directory} + ''; + + serviceConfig = { + PermissionsStartOnly = true; + ExecStart = "${pkg}/bin/mattermost"; + WorkingDirectory = cfg.state-directory; + Restart = "always"; + RestartSec = "10"; + LimitNOFILE = "49152"; + User = mattermost-user; + Group = mattermost-group; + }; + }; + + services.nginx = { + enable = true; + + appendHttpConfig = '' + proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mattermost_cache:10m max_size=3g inactive=120m use_temp_path=off; + ''; + + virtualHosts = { + "${cfg.hostname}" = { + enableACME = true; + forceSSL = true; + + locations."/" = { + proxyPass = "http://127.0.0.1:8065"; + + extraConfig = '' + client_max_body_size 50M; + proxy_set_header Connection ""; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-By $server_addr:$server_port; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Frame-Options SAMEORIGIN; + proxy_buffers 256 16k; + proxy_buffer_size 16k; + proxy_read_timeout 600s; + proxy_cache mattermost_cache; + proxy_cache_revalidate on; + proxy_cache_min_uses 2; + proxy_cache_use_stale timeout; + proxy_cache_lock on; + proxy_http_version 1.1; + ''; + }; + + locations."~ /api/v[0-9]+/(users/)?websocket$" = { + proxyPass = "http://127.0.0.1:8065"; + + extraConfig = '' + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + client_max_body_size 50M; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-By $server_addr:$server_port; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Frame-Options SAMEORIGIN; + proxy_buffers 256 16k; + proxy_buffer_size 16k; + client_body_timeout 60; + send_timeout 300; + lingering_timeout 5; + proxy_connect_timeout 90; + proxy_send_timeout 300; + proxy_read_timeout 90s; + ''; + }; + }; + }; + }; + }); +} diff --git a/config/fudo/common.nix b/config/fudo/common.nix index 3679ff9..ddbe3c6 100644 --- a/config/fudo/common.nix +++ b/config/fudo/common.nix @@ -12,5 +12,39 @@ with lib; ''; default = []; }; + + domain = mkOption { + type = types.str; + description = '' + Domain of the + ''; + }; + + profile = mkOption { + type = with types; nullOr str; + example = "desktop"; + description = '' + The profile to use for this host. This will do some profile-dependent + configuration, for example removing X-libs from servers and adding UI + packages to desktops. + ''; + default = null; + }; + + site = mkOption { + type = with types; nullOr str; + example = "seattle"; + description = '' + The site at which this host is located. This will do some site-dependent + configuration. + ''; + default = null; + }; + + www-root = mkOption { + type = types.path; + description = "Path at which to store www files for serving."; + example = /var/www; + }; }; } diff --git a/config/fudo/grafana.nix b/config/fudo/grafana.nix index 10ee540..61f869f 100644 --- a/config/fudo/grafana.nix +++ b/config/fudo/grafana.nix @@ -9,6 +9,27 @@ let database-name = "grafana"; database-user = "grafana"; + databaseOpts = { ... }: { + options = { + name = mkOption { + type = types.str; + description = "Database name."; + }; + hostname = mkOption { + type = types.str; + description = "Hostname of the database server."; + }; + user = mkOption { + type = types.str; + description = "Database username."; + }; + password-file = mkOption { + type = types.path; + description = "File containing the database user's password."; + }; + }; + }; + in { options.fudo.grafana = { @@ -30,9 +51,9 @@ in { description = "Path to a file containing the email user's password."; }; - database-password-file = mkOption { - type = types.path; - description = "Path to a file containing the database user's password."; + database = mkOption { + type = (types.submodule databaseOpts); + description = "Grafana database configuration."; }; admin-password-file = mkOption { @@ -52,15 +73,6 @@ in { }; config = mkIf cfg.enable { - fudo.postgresql = { - databases.${database-name} = {}; - - local-users.${database-user} = { - password = (fileContents cfg.database-password-file); - databases = ["${database-name}"]; - }; - }; - services.nginx = { enable = true; @@ -89,6 +101,7 @@ in { addr = "127.0.0.1"; protocol = "http"; + port = 3000; domain = "${cfg.hostname}"; rootUrl = "https://${cfg.hostname}/"; @@ -106,10 +119,10 @@ in { }; database = { - host = "localhost"; - name = database-name; - user = database-user; - passwordFile = cfg.database-password-file; + host = cfg.database.hostname; + name = cfg.database.name; + user = cfg.database.user; + passwordFile = cfg.database.password-file; type = "postgres"; }; diff --git a/config/fudo/include/rainloop.nix b/config/fudo/include/rainloop.nix new file mode 100644 index 0000000..4ab985a --- /dev/null +++ b/config/fudo/include/rainloop.nix @@ -0,0 +1,117 @@ +lib: site: config: version: +with lib; +let + db-config = if (config.database != null) then + '' + type = "${config.database.type}" + pdo_dsn = "${config.database.type}:host=${config.database.hostname};port=${toString config.database.port};dbname=${config.database.name}" + pdo_user = "${config.database.user}" + pdo_password = "${fileContents config.database.password-file}" + '' + else ""; +in '' + [webmail] + title = "${config.title}" + loading_description = "${config.title}" + favicon_url = "https://${site}/favicon.ico" + theme = "${config.theme}" + allow_themes = On + allow_user_background = Off + language = "en" + language_admin = "en" + allow_languages_on_settings = On + allow_additional_accounts = On + allow_additional_identities = On + messages_per_page = ${toString config.messages-per-page} + attachment_size_limit = ${toString config.max-upload-size} + + [interface] + show_attachment_thumbnail = On + new_move_to_folder_button = On + + [branding] + + [contacts] + enable = On + allow_sync = On + sync_interval = 20 + suggestions_limit = 10 + ${db-config} + + [security] + csrf_protection = On + custom_server_signature = "RainLoop" + x_frame_options_header = "" + openpgp = On + + admin_login = "admin" + admin_password = "" + allow_admin_panel = Off + allow_two_factor_auth = On + force_two_factor_auth = Off + hide_x_mailer_header = Off + admin_panel_host = "" + admin_panel_key = "admin" + content_security_policy = "" + core_install_access_domain = "" + + [login] + default_domain = "${config.domain}" + allow_languages_on_login = On + determine_user_language = On + determine_user_domain = Off + welcome_page = Off + hide_submit_button = On + + [plugins] + enable = Off + + [defaults] + view_editor_type = "${config.edit-mode}" + view_layout = ${if (config.layout-mode == "bottom") then "2" else "1"} + contacts_autosave = On + mail_use_threads = ${if config.enable-threading then "On" else "Off"} + allow_draft_autosave = On + mail_reply_same_folder = Off + show_images = On + + [logs] + enable = ${if config.debug then "On" else "Off"} + + [debug] + enable = ${if config.debug then "On" else "Off"} + hide_passwords = On + filename = "log-{date:Y-m-d}.txt" + + [social] + google_enable = Off + fb_enable = Off + twitter_enable = Off + dropbox_enable = Off + + [cache] + enable = On + index = "v1" + fast_cache_driver = "files" + fast_cache_index = "v1" + http = On + http_expires = 3600 + server_uids = On + + [labs] + allow_mobile_version = ${if config.enable-mobile then "On" else "Off"} + check_new_password_strength = On + allow_gravatar = On + allow_prefetch = On + allow_smart_html_links = On + cache_system_data = On + date_from_headers = On + autocreate_system_folders = On + allow_ctrl_enter_on_compose = On + favicon_status = On + use_local_proxy_for_external_images = On + detect_image_exif_orientation = On + + [version] + current = "${version}" +'' diff --git a/config/fudo/kdc.nix b/config/fudo/kdc.nix index 71954f4..b8a4124 100644 --- a/config/fudo/kdc.nix +++ b/config/fudo/kdc.nix @@ -69,7 +69,9 @@ in { acl_file = ${cfg.acl-file} } addresses = ${stringJoin " " cfg.bind-addresses} - enable-http = true + + # Binds to port 80! + enable-http = false ''; }; }; diff --git a/config/fudo/mail-container.nix b/config/fudo/mail-container.nix index 948172b..c26c974 100644 --- a/config/fudo/mail-container.nix +++ b/config/fudo/mail-container.nix @@ -16,7 +16,7 @@ let container-mail-user = "mailer"; container-mail-user-id = 542; container-mail-group = "mailer"; - trusted-networks = config.fudo.prometheus.trusted-networks; + trusted-networks = config.fudo.common.local-networks; in rec { options.fudo.mail-server.container = { @@ -80,11 +80,11 @@ in rec { proxy-headers = '' proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; - '' - ; + ''; trusted-network-string = optionalString ((length trusted-networks) > 0) (concatStringsSep "\n" (map (network: "allow ${network};") trusted-networks)) + "\ndeny all;"; + in { "${cfg.hostname}" = { enableACME = true; diff --git a/config/fudo/mail/postfix.nix b/config/fudo/mail/postfix.nix index 927f8c5..940ffd3 100644 --- a/config/fudo/mail/postfix.nix +++ b/config/fudo/mail/postfix.nix @@ -51,13 +51,13 @@ let # A list of domains for which we accept mail virtual-mailbox-map-file = builtins.toFile "virtual_mailbox_map" (concatStringsSep "\n" - (map (domain: "@${domain} OK") cfg.local-domains)); + (map (domain: "@${domain} OK") (cfg.local-domains ++ [cfg.domain]))); sender-login-map-file = let escapeDot = (str: replaceStrings ["."] ["\\."] str); in builtins.toFile "sender_login_maps" (concatStringsSep "\n" - (map (domain: "/^(.*)@${escapeDot domain}$/ \${1}") cfg.local-domains)); + (map (domain: "/^(.*)@${escapeDot domain}$/ \${1}") (cfg.local-domains ++ [cfg.domain]))); mapped-file = name: "hash:/var/lib/postfix/conf/${name}"; @@ -95,6 +95,9 @@ in { services.prometheus.exporters.postfix = mkIf cfg.monitoring { enable = true; systemd.enable = true; + showqPath = "/var/lib/postfix/queue/public/showq"; + user = config.services.postfix.user; + group = config.services.postfix.group; }; services.postfix = { @@ -103,7 +106,8 @@ in { origin = cfg.domain; hostname = cfg.hostname; destination = ["localhost" "localhost.localdomain"] ++ - (map (domain: "localhost.${domain}") cfg.local-domains); + (map (domain: "localhost.${domain}") cfg.local-domains) ++ + cfg.local-domains; enableHeaderChecks = true; enableSmtp = true; @@ -153,6 +157,10 @@ in { smtpd_sasl_type = "dovecot"; smtpd_sasl_path = "/run/dovecot2/auth"; smtpd_sasl_auth_enable = "yes"; + smtpd_sasl_local_domain = "fudo.org"; + + smtpd_sasl_security_options = "noanonymous"; + smtpd_sasl_tls_security_options = "noanonymous"; smtpd_sender_login_maps = (pcre-file "sender_login_map"); @@ -184,6 +192,9 @@ in { smtpd_sender_restrictions = [ "check_sender_access ${mapped-file "reject_senders"}" + "permit_mynetworks" + "permit_sasl_authenticated" + "reject_unknown_sender_domain" ]; smtpd_recipient_restrictions = [ @@ -200,6 +211,12 @@ in { "reject_non_fqdn_recipient" ]; + smtpd_helo_restrictions = [ + "permit_mynetworks" + "reject_invalid_hostname" + "permit" + ]; + # Handled by submission smtpd_tls_security_level = "may"; diff --git a/config/fudo/node-exporter.nix b/config/fudo/node-exporter.nix index 82236cb..6bc7cf3 100644 --- a/config/fudo/node-exporter.nix +++ b/config/fudo/node-exporter.nix @@ -39,7 +39,7 @@ in { enableACME = true; forceSSL = true; - location."/metrics/node" = { + locations."/metrics/node" = { extraConfig = '' ${concatStringsSep "\n" (map allow-network fudo-cfg.local-networks)} allow 127.0.0.0/16; diff --git a/config/fudo/postgres.nix b/config/fudo/postgres.nix index d24cc80..5eb348b 100644 --- a/config/fudo/postgres.nix +++ b/config/fudo/postgres.nix @@ -8,14 +8,18 @@ let userOpts = { username, ... }: { options = { password = mkOption { - type = types.str; + type = with types; nullOr str; description = "The user's (plaintext) password."; + default = null; }; databases = mkOption { - type = with types; listOf str; - description = "Databases to which this user has access."; - default = []; + type = with types; loaOf str; + description = "Map of databases to which this user has access, to the required perms."; + default = {}; + example = { + my_database = "ALL PRIVILEGES"; + }; }; }; }; @@ -24,19 +28,16 @@ let options = { users = mkOption { type = with types; listOf str; - description = "A list of users who should have access to this database."; + description = "A list of users who should have full access to this database."; default = []; }; }; }; userDatabaseAccess = user: databases: - listToAttrs (map (database-name: - { - name = "DATABASE ${database-name}"; - value = "ALL PRIVILEGES"; - }) - databases); + mapAttrs' (database: perms: + nameValuePair "DATABASE ${database}" perms) + databases; stringJoin = joiner: els: if (length els) == 0 then @@ -54,9 +55,9 @@ let "ALTER USER ${username} ENCRYPTED PASSWORD '${attrs.password}';"; setPasswordsSql = users: - stringJoin "\n" + (stringJoin "\n" (mapAttrsToList (username: attrs: setPasswordSql username attrs) - users); + (filterAttrs (user: attrs: attrs.password != null) users))) + "\n"; makeLocalUserPasswordEntries = users: stringJoin "\n" @@ -66,7 +67,7 @@ let (map (db: '' host ${username} ${db} 127.0.0.1/16 md5 host ${username} ${db} ::1/128 md5 - '') attrs.databases)) + '') (attrNames attrs.databases))) users); @@ -99,7 +100,7 @@ in { default = []; }; - local-users = mkOption { + users = mkOption { type = with types; loaOf (submodule userOpts); description = "A map of users to user attributes."; example = { @@ -146,6 +147,13 @@ in { group = "postgres"; source = cfg.keytab; }; + + "postgresql/private/user-script.sql" = { + mode = "0400"; + user = "postgres"; + group = "postgres"; + text = setPasswordsSql cfg.users; + }; }; }; @@ -162,7 +170,7 @@ in { #{ "DATABASE ${username}" = "ALL PRIVILEGES"; }; (userDatabaseAccess username attrs.databases); }) - cfg.local-users; + cfg.users; extraConfig = '' @@ -179,7 +187,7 @@ in { '' local all all ident - ${makeLocalUserPasswordEntries cfg.local-users} + ${makeLocalUserPasswordEntries cfg.users} # host-local host all all 127.0.0.1/32 gss include_realm=0 krb_realm=FUDO.ORG @@ -189,9 +197,13 @@ in { ${makeNetworksEntry cfg.local-networks} ''; - initialScript = pkgs.writeText "database-init.sql" '' - ${setPasswordsSql cfg.local-users} - ''; + # initialScript = pkgs.writeText "database-init.sql" '' + # ${setPasswordsSql cfg.users} + # ''; }; + + systemd.services.postgresql.postStart = '' + ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} ${pkgs.postgresql}/bin/psql --port ${toString config.services.postgresql.port} -f /etc/postgresql/private/user-script.sql -d postgres + ''; }; } diff --git a/config/fudo/profiles.nix b/config/fudo/profiles.nix deleted file mode 100644 index 0a1d78d..0000000 --- a/config/fudo/profiles.nix +++ /dev/null @@ -1,32 +0,0 @@ -# Switch between different basic profiles -{ lib, config, pkgs, ... }: - -with lib; -let - profile = config.fudo.profile; - - profiles = { - desktop = ./profiles/desktop.nix { - pkgs = pkgs; - config = config; - }; - server = ./profiles/server.nix { - pkgs = pkgs; - config = config; - }; - }; - -in { - options.fudo.profile = { - type = types.enum (attrNames profiles); - example = "desktop"; - description = '' - The profile to use for this host. This will do some profile-dependent - configuration, for example removing X-libs from servers and adding UI - packages to desktops. - ''; - default = "server"; - }; - - config = optionalAttrs (profiles ? profile) profiles.${profile}; -} diff --git a/config/fudo/profiles/desktop.nix b/config/fudo/profiles/desktop.nix deleted file mode 100644 index 1aa0d53..0000000 --- a/config/fudo/profiles/desktop.nix +++ /dev/null @@ -1,151 +0,0 @@ -{ config, pkgs, ... }: - -{ - environment.systemPackages = with pkgs; [ - cool-retro-term - chrome-gnome-shell - chromium - ffmpeg-full - firefox - gimp - glxinfo - gnome3.gnome-shell - gnome3.gnome-session - google-chrome - gtk2 - gtk2-x11 - gtk3 - gtkimageview - i3lock - libfixposix - minecraft - mplayer - nomacs - openssl_1_1 - redshift - rhythmbox - shotwell - spotify - sqlite - steam - system-config-printer - virtmanager - xorg.xev - xzgv - virtmanager-qt - ]; - - # Splash screen - boot.plymouth.enable = true; - - services.avahi = { - enable = true; - browseDomains = [config.fudo.domain;]; - domainName = config.fudo.domain; - }; - - boot.tmpOnTmpfs = true; - - services.xserver = { - enable = true; - - layout = "us"; - xkbVariant = "dvp"; - xkbOptions = "ctrl:nocaps"; - - desktopManager.gnome3.enable = true; - desktopManager.default = "gnome3"; - - displayManager.gdm.enable = true; - - windowManager.session = pkgs.lib.singleton { - name = "stumpwm"; - start = '' - ${pkgs.lispPackages.stumpwm}/bin/stumpwm & - waidPID=$! - ''; - }; - }; - - services.printing = { - enable = true; - }; - - services.gnome3 = { - evolution-data-server.enable = pkgs.lib.mkForce false; - gnome-user-share.enable = pkgs.lib.mkForce false; - }; - - services.dbus.socketActivated = true; - - services.openssh.forwardX11 = true; - - programs.ssh.forwardX11 = true; - - sound.enable = true; - - hardware.pulseaudio.enable = true; - - fonts = { - enableCoreFonts = true; - enableFontDir = true; - enableGhostscriptFonts = false; - fontconfig.ultimate.enable = true; - - fonts = with pkgs; [ - cantarell_fonts - dejavu_fonts - dina-font - dosemu_fonts - fira-code - fira-code-symbols - freefont_ttf - liberation_ttf - mplus-outline-fonts - nerdfonts - noto-fonts - noto-fonts-cjk - noto-fonts-emoji - proggyfonts - terminus_font - ubuntu_font_family - ucsFonts - unifont - vistafonts - xlibs.fontadobe100dpi - xlibs.fontadobe75dpi - xlibs.fontadobeutopia100dpi - xlibs.fontadobeutopia75dpi - xlibs.fontadobeutopiatype1 - xlibs.fontarabicmisc - xlibs.fontbh100dpi - xlibs.fontbh75dpi - xlibs.fontbhlucidatypewriter100dpi - xlibs.fontbhlucidatypewriter75dpi - xlibs.fontbhttf - xlibs.fontbhtype1 - xlibs.fontbitstream100dpi - xlibs.fontbitstream75dpi - xlibs.fontbitstreamtype1 - xlibs.fontcronyxcyrillic - xlibs.fontcursormisc - xlibs.fontdaewoomisc - xlibs.fontdecmisc - xlibs.fontibmtype1 - xlibs.fontisasmisc - xlibs.fontjismisc - xlibs.fontmicromisc - xlibs.fontmisccyrillic - xlibs.fontmiscethiopic - xlibs.fontmiscmeltho - xlibs.fontmiscmisc - xlibs.fontmuttmisc - xlibs.fontschumachermisc - xlibs.fontscreencyrillic - xlibs.fontsonymisc - xlibs.fontsunmisc - xlibs.fontwinitzkicyrillic - xlibs.fontxfree86type1 - ]; - }; -} diff --git a/config/fudo/profiles/server.nix b/config/fudo/profiles/server.nix deleted file mode 100644 index 70c9271..0000000 --- a/config/fudo/profiles/server.nix +++ /dev/null @@ -1,27 +0,0 @@ -{ pkgs, ... }: - -{ - environment = { - systemPackages = with pkgs; [ - ]; - - noXlibs = true; - }; - - security = { - hideProcessInformation = true; - }; - - boot.tmpOnTmpfs = true; - - services.xserver.enable = false; - - programs = { - gnupg.agent = { - enable = true; - enableSSHSupport = true; - }; - - ssh.startAgent = true; - }; -} diff --git a/config/fudo/sites.nix b/config/fudo/sites.nix deleted file mode 100644 index d7e242e..0000000 --- a/config/fudo/sites.nix +++ /dev/null @@ -1,79 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; -let - site = config.fudo.site; - - hostname = config.networking.hostName; - - winnipeg-networks = [ - "208.81.1.128/28" - "208.81.3.112/28" - "192.168.11.1/24" - ]; - - site-configs = { - global-config = { - }; - - winnipeg = global-config // { - time.timeZone = "America/Winnipeg"; - - fudo.common.local-networks = winnipeg-networks; - - services.cron = { - mailto = "admin@fudo.org"; - }; - - networking = { - domain = "fudo.org"; - search = ["fudo.org"]; - firewall.enable = false; - networkmanager.enable = pkgs.lib.mkForce false; - nameservers = [ "1.1.1.1" "208.81.7.14" "2606:4700:4700::1111" ]; - }; - - security.acme.certs."${hostname}" = { - email = "admin@fudo.org"; - - plugins = [ - "fullchain.pem" - "full.pem" - "key.pem" - "chain.pem" - "cert.pem" - ]; - }; - - fudo.node-exporter = { - enable = true; - hostname = hostname; - }; - - nginx = { - enable = true; - - recommendedGzipSettings = true; - recommendedOptimisation = true; - recommendedTlsSettings = true; - }; - }; - - nutty-club = winnipeg // { - defaultGateway = "208.81.3.113"; - }; - }; - -in { - options.fudo.site = mkOption { - type = types.enum (attrNames site-configs); - example = "nutty-club"; - description = '' - The site at which this host is located. This will do some site-dependent - configuration. - ''; - default = ""; - }; - - config = optionalAttrs (site-configs ? site) site-configs.${site}; -} diff --git a/config/fudo/webmail.nix b/config/fudo/webmail.nix new file mode 100644 index 0000000..310b8bf --- /dev/null +++ b/config/fudo/webmail.nix @@ -0,0 +1,318 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.fudo.webmail; + + inherit (lib.strings) concatStringsSep; + + webmail-user = "webmail-php"; + webmail-group = "webmail-php"; + + base-data-path = "/var/rainloop"; + + fastcgi-conf = builtins.toFile "fastcgi.conf" '' + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param QUERY_STRING $query_string; + fastcgi_param REQUEST_METHOD $request_method; + fastcgi_param CONTENT_TYPE $content_type; + fastcgi_param CONTENT_LENGTH $content_length; + + fastcgi_param SCRIPT_NAME $fastcgi_script_name; + fastcgi_param REQUEST_URI $request_uri; + fastcgi_param DOCUMENT_URI $document_uri; + fastcgi_param DOCUMENT_ROOT $document_root; + fastcgi_param SERVER_PROTOCOL $server_protocol; + fastcgi_param REQUEST_SCHEME $scheme; + fastcgi_param HTTPS $https if_not_empty; + + fastcgi_param GATEWAY_INTERFACE CGI/1.1; + fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; + + fastcgi_param REMOTE_ADDR $remote_addr; + fastcgi_param REMOTE_PORT $remote_port; + fastcgi_param SERVER_ADDR $server_addr; + fastcgi_param SERVER_PORT $server_port; + fastcgi_param SERVER_NAME $server_name; + + # PHP only, required if PHP was built with --enable-force-cgi-redirect + fastcgi_param REDIRECT_STATUS 200; + ''; + + site-packages = mapAttrs (site: site-cfg: + pkgs.rainloop-community.overrideAttrs (oldAttrs: { + # Not sure how to correctly specify this arg... + #dataPath = "${base-data-path}/${site}"; + + # Overwriting, to correctly create data dir + installPhase = '' + mkdir $out + cp -r rainloop/* $out + rm -rf $out/data + ln -s ${base-data-path}/${site} $out/data + ln -s ${site-cfg.favicon} $out/favicon.ico + ''; + })) + cfg.sites; + + siteOpts = { site-host, ... }: { + options = { + title = mkOption { + type = types.str; + description = "Webmail site title"; + example = "My Webmail"; + }; + + debug = mkOption { + type = types.bool; + description = "Turn debug logs on."; + default = false; + }; + + mail-server = mkOption { + type = types.str; + description = "Mail server from which to send & recieve email."; + default = "mail.fudo.org"; + }; + + favicon = mkOption { + type = types.str; + description = "URL of the site favicon"; + example = "https://www.somepage.com/fav.ico"; + }; + + messages-per-page = mkOption { + type = types.int; + description = "Default number of messages to show per page"; + default = 30; + }; + + max-upload-size = mkOption { + type = types.int; + description = "Size limit in MB for uploaded files"; + default = 30; + }; + + theme = mkOption { + type = types.str; + description = "Default theme to use for this webmail site."; + default = "Default"; + }; + + # Ideally, don't even allow admin logins, since they'll just add state that can be clobbered + # admin-password = mkOption { + # type = types.str; + # description = "Password to use for the admin user"; + # }; + + domain = mkOption { + type = types.str; + description = "Domain for which the server acts as webmail server"; + }; + + edit-mode = mkOption { + type = types.enum ["Plain" "Html" "PlainForced" "HtmlForced"]; + description = "Default text editing mode for email"; + default = "Html"; + }; + + layout-mode = mkOption { + type = types.enum ["side" "bottom"]; + description = "Layout mode to use for email preview."; + default = "side"; + }; + + enable-threading = mkOption { + type = types.bool; + description = "Whether to enable threading for email."; + default = true; + }; + + enable-mobile = mkOption { + type = types.bool; + description = "Whether to enable a mobile site view."; + default = true; + }; + + database = mkOption { + type = with types; nullOr (submodule databaseOpts); + description = "Database configuration for storing contact data."; + example = { + name = "my_db"; + host = "db.domain.com"; + user = "my_user"; + password-file = /path/to/some/file.pw; + }; + default = null; + }; + }; + }; + + databaseOpts = { ... }: { + options = { + type = mkOption { + type = types.enum ["pgsql" "mysql"]; + description = "Driver to use when connecting to the database."; + default = "pgsql"; + }; + + hostname = mkOption { + type = types.str; + description = "Name of host running the database."; + example = "my-db.domain.com"; + }; + + port = mkOption { + type = types.int; + description = "Port on which the database server is listening."; + default = 5432; + }; + + name = mkOption { + type = types.str; + description = "Name of the database containing contact info. must have access."; + default = "rainloop_contacts"; + }; + + user = mkOption { + type = types.str; + description = "User as which to connect to the database."; + }; + + password-file = mkOption { + type = types.path; + description = "Password to use when connecting to the database."; + }; + }; + }; + +in { + options.fudo.webmail = { + enable = mkEnableOption "Enable a RainLoop webmail server."; + + sites = mkOption { + type = with types; (loaOf (submodule siteOpts)); + description = "A map of webmail sites to site configurations."; + example = { + "webmail.domain.com" = { + title = "My Awesome Webmail"; + layout-mode = "side"; + favicon = "/path/to/favicon.ico"; + admin-password = "shh-don't-tell"; + }; + }; + }; + }; + + config = mkIf cfg.enable { + users = { + users = { + ${webmail-user} = { + isSystemUser = true; + description = "Webmail PHP FPM user"; + group = webmail-group; + }; + }; + groups = { + ${webmail-group} = { + members = [ webmail-user config.services.nginx.user ]; + }; + }; + }; + + services = { + phpfpm = { + pools.webmail = { + settings = { + "pm" = "dynamic"; + "pm.max_children" = 50; + "pm.start_servers" = 5; + "pm.min_spare_servers" = 1; + "pm.max_spare_servers" = 8; + }; + + # Not working....see chmod below + user = webmail-user; + group = webmail-group; + }; + }; + + nginx = { + enable = true; + + virtualHosts = mapAttrs (site: site-cfg: { + enableACME = true; + forceSSL = true; + + root = "${site-packages.${site}}"; + + locations = { + "/" = { + index = "index.php"; + }; + + "/data" = { + extraConfig = '' + deny all; + return 403; + ''; + }; + }; + + extraConfig = '' + location ~ \.php$ { + expires -1; + + include ${fastcgi-conf}; + fastcgi_index index.php; + fastcgi_pass unix:${config.services.phpfpm.pools.webmail.socket}; + } + ''; + }) + cfg.sites; + }; + }; + + systemd.services.nginx.preStart = let + link-configs = concatStringsSep "\n" (mapAttrsToList (site: site-cfg: let + cfg-file = builtins.toFile "${site}-rainloop.cfg" (import ./include/rainloop.nix lib site site-cfg site-packages.${site}.version); + domain-cfg = builtins.toFile "${site}-domain.cfg" '' + imap_host = "${site-cfg.mail-server}" + imap_port = 143 + imap_secure = "TLS" + imap_short_login = On + sieve_use = Off + sieve_allow_raw = Off + sieve_host = "" + sieve_port = 4190 + sieve_secure = "None" + smtp_host = "${site-cfg.mail-server}" + smtp_port = 587 + smtp_secure = "TLS" + smtp_short_login = On + smtp_auth = On + smtp_php_mail = Off + white_list = "" + ''; + + in '' + mkdir -p ${base-data-path}/${site}/_data_/_default_/configs + cp ${cfg-file} ${base-data-path}/${site}/_data_/_default_/configs/application.ini + + mkdir -p ${base-data-path}/${site}/_data_/_default_/domains/ + cp ${domain-cfg} ${base-data-path}/${site}/_data_/_default_/domains/${site-cfg.domain}.ini + + '') cfg.sites); + + in '' + ${link-configs} + + chown -R ${webmail-user}:${webmail-group} ${base-data-path} + chmod -R ug+w ${base-data-path} + ''; + + systemd.services.phpfpm-webmail.postStart = '' + chown ${webmail-user}:${webmail-group} ${config.services.phpfpm.pools.webmail.socket} + ''; + }; +} diff --git a/config/local.nix b/config/local.nix index 14a1f85..2da9c2c 100644 --- a/config/local.nix +++ b/config/local.nix @@ -5,6 +5,7 @@ with lib; imports = [ ./fudo/acme-for-hostname.nix ./fudo/authentication.nix + ./fudo/chat.nix ./fudo/common.nix ./fudo/grafana.nix ./fudo/kdc.nix @@ -14,8 +15,10 @@ with lib; ./fudo/minecraft-server.nix ./fudo/node-exporter.nix ./fudo/postgres.nix - ./fudo/profiles.nix ./fudo/prometheus.nix - ./fudo/sites.nix + ./fudo/webmail.nix + + ../fudo/profiles + ../fudo/sites ]; } diff --git a/fudo/email.nix b/fudo/email.nix index bea47a5..e622728 100644 --- a/fudo/email.nix +++ b/fudo/email.nix @@ -9,7 +9,6 @@ in { domain = "fudo.org"; local-domains = [ - "fudo.org" "mail.fudo.org" "${config.networking.hostName}" "selby.ca" @@ -23,6 +22,7 @@ in { "selbyhomecentre.com" "stewartsoundservices.ca" "rogerwongphoto.com" + "chat.fudo.org" ]; alias-users = import ./alias-users.nix; diff --git a/fudo/profiles/default.nix b/fudo/profiles/default.nix new file mode 100644 index 0000000..e327416 --- /dev/null +++ b/fudo/profiles/default.nix @@ -0,0 +1,8 @@ +{ config, lib, pkgs, ... }: + +{ + imports = [ + ./desktop.nix + ./server.nix + ]; +} diff --git a/fudo/profiles/desktop.nix b/fudo/profiles/desktop.nix new file mode 100644 index 0000000..302fcc9 --- /dev/null +++ b/fudo/profiles/desktop.nix @@ -0,0 +1,154 @@ +{ config, lib, pkgs, ... }: + +with lib; +{ + config = mkIf (config.fudo.common.profile == "desktop") { + environment.systemPackages = with pkgs; [ + cool-retro-term + chrome-gnome-shell + chromium + ffmpeg-full + firefox + gimp + glxinfo + gnome3.gnome-shell + gnome3.gnome-session + google-chrome + gtk2 + gtk2-x11 + gtk3 + gtkimageview + i3lock + libfixposix + minecraft + mplayer + nomacs + openssl_1_1 + redshift + rhythmbox + shotwell + spotify + sqlite + steam + system-config-printer + virtmanager + xorg.xev + xzgv + virtmanager-qt + ]; + + # Splash screen + boot.plymouth.enable = true; + + services.avahi = { + enable = true; + browseDomains = [config.fudo.domain]; + domainName = config.fudo.domain; + }; + + boot.tmpOnTmpfs = true; + + services.xserver = { + enable = true; + + layout = "us"; + xkbVariant = "dvp"; + xkbOptions = "ctrl:nocaps"; + + desktopManager.gnome3.enable = true; + desktopManager.default = "gnome3"; + + displayManager.gdm.enable = true; + + windowManager.session = pkgs.lib.singleton { + name = "stumpwm"; + start = '' + ${pkgs.lispPackages.stumpwm}/bin/stumpwm & + waidPID=$! + ''; + }; + }; + + services.printing = { + enable = true; + }; + + services.gnome3 = { + evolution-data-server.enable = pkgs.lib.mkForce false; + gnome-user-share.enable = pkgs.lib.mkForce false; + }; + + services.dbus.socketActivated = true; + + services.openssh.forwardX11 = true; + + programs.ssh.forwardX11 = true; + + sound.enable = true; + + hardware.pulseaudio.enable = true; + + fonts = { + enableCoreFonts = true; + enableFontDir = true; + enableGhostscriptFonts = false; + fontconfig.ultimate.enable = true; + + fonts = with pkgs; [ + cantarell_fonts + dejavu_fonts + dina-font + dosemu_fonts + fira-code + fira-code-symbols + freefont_ttf + liberation_ttf + mplus-outline-fonts + nerdfonts + noto-fonts + noto-fonts-cjk + noto-fonts-emoji + proggyfonts + terminus_font + ubuntu_font_family + ucsFonts + unifont + vistafonts + xlibs.fontadobe100dpi + xlibs.fontadobe75dpi + xlibs.fontadobeutopia100dpi + xlibs.fontadobeutopia75dpi + xlibs.fontadobeutopiatype1 + xlibs.fontarabicmisc + xlibs.fontbh100dpi + xlibs.fontbh75dpi + xlibs.fontbhlucidatypewriter100dpi + xlibs.fontbhlucidatypewriter75dpi + xlibs.fontbhttf + xlibs.fontbhtype1 + xlibs.fontbitstream100dpi + xlibs.fontbitstream75dpi + xlibs.fontbitstreamtype1 + xlibs.fontcronyxcyrillic + xlibs.fontcursormisc + xlibs.fontdaewoomisc + xlibs.fontdecmisc + xlibs.fontibmtype1 + xlibs.fontisasmisc + xlibs.fontjismisc + xlibs.fontmicromisc + xlibs.fontmisccyrillic + xlibs.fontmiscethiopic + xlibs.fontmiscmeltho + xlibs.fontmiscmisc + xlibs.fontmuttmisc + xlibs.fontschumachermisc + xlibs.fontscreencyrillic + xlibs.fontsonymisc + xlibs.fontsunmisc + xlibs.fontwinitzkicyrillic + xlibs.fontxfree86type1 + ]; + }; + }; +} diff --git a/fudo/profiles/server.nix b/fudo/profiles/server.nix new file mode 100644 index 0000000..1011659 --- /dev/null +++ b/fudo/profiles/server.nix @@ -0,0 +1,80 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + reboot-if-necessary = pkgs.writeScriptBin "reboot-if-necessary" '' + #!${pkgs.stdenv.shell} + + set -ne + + if [ $# -ne 1 ]; then + echo "FAILED: no sync file provided." + exit 1 + fi + + WALL=${pkgs.utillinux}/bin/wall + + if [ -f $1 ]; then + $WALL "$1 exists, rebooting system" + ${pkgs.systemd}/bin/reboot + else + $WALL "$1 does not exist, aborting reboot." + fi + + exit 0 + ''; + + test-config = pkgs.writeScriptBin "fudo-test-config" '' + #!${pkgs.stdenv.shell} + + set -ne + + if [ $# -gt 1 ]; then + echo "usage: $0 [timeout]" + exit 1 + elif [ $# -eq 1 ]; then + TIMEOUT=$1 + else + TIMEOUT=15m + fi + + SYNCFILE=$TMP/sync-$(date +"%Y%m%d-%H%M%N") + touch $SYNCFILE + ${pkgs.utillinux}/bin/wall "Launching config. System will restart in $TIMEOUT if $SYNCFILE still exists." + systemd-run --on-active=$TIMEOUT ${reboot-if-necessary} $SYNCFILE + nixos-rebuild test + + exit 0 + ''; + +in { + config = mkIf (config.fudo.common.profile == "server") { + environment = { + systemPackages = with pkgs; [ + test-config + reboot-if-necessary + ]; + + noXlibs = true; + }; + + security = { + hideProcessInformation = true; + }; + + networking = { + networkmanager.enable = mkForce false; + }; + + boot.tmpOnTmpfs = true; + + services.xserver.enable = false; + + programs = { + gnupg.agent = { + enable = true; + enableSSHSupport = true; + }; + }; + }; +} diff --git a/fudo/sites/default.nix b/fudo/sites/default.nix new file mode 100644 index 0000000..6caa1b3 --- /dev/null +++ b/fudo/sites/default.nix @@ -0,0 +1,8 @@ +{ config, lib, pkgs, ... }: + +{ + imports = [ + ./portage.nix + ./seattle.nix + ]; +} diff --git a/fudo/sites/portage.nix b/fudo/sites/portage.nix new file mode 100644 index 0000000..e7e1469 --- /dev/null +++ b/fudo/sites/portage.nix @@ -0,0 +1,60 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + admin = "admin@fudo.org"; + + nameservers = [ + "1.1.1.1" + "208.81.7.14" + "2606:4700:4700::1111" + ]; + + hostname = config.networking.hostName; + + gateway = "208.81.3.113"; + gateway6 = "2605:e200:d200:1::1"; + +in { + config = mkIf (config.fudo.common.site == "portage") { + time.timeZone = "America/Winnipeg"; + + services.cron = { + mailto = admin; + }; + + networking = { + domain = "fudo.org"; + search = ["fudo.org"]; + firewall.enable = false; + nameservers = nameservers; + + defaultGateway = gateway; + # defaultGateway6 = gateway6; + }; + + fudo.node-exporter = { + enable = true; + hostname = hostname; + }; + + security.acme.certs.${hostname} = { + email = "admin@fudo.org"; + # plugins = [ + # "fullchain.pem" + # "full.pem" + # "key.pem" + # "chain.pem" + # "cert.pem" + # ]; + }; + + # TODO: We...could run nginx in a container + services.nginx = { + enable = true; + recommendedGzipSettings = true; + recommendedOptimisation = true; + recommendedTlsSettings = true; + }; + }; +} diff --git a/fudo/sites/seattle.nix b/fudo/sites/seattle.nix new file mode 100644 index 0000000..12246a2 --- /dev/null +++ b/fudo/sites/seattle.nix @@ -0,0 +1,201 @@ +{ lib, config, pkgs, ... }: + +with lib; +let + admin = "niten@fudo.org"; + + local-domain = "sea.fudo.org"; + +in { + + config = mkIf (config.fudo.common.site == "seattle") { + + time.timeZone = "America/Los_Angeles"; + + services.cron = { + mailto = admin; + }; + + networking = { + domain = local-domain; + search = [local-domain "fudo.org"]; + firewall.enable = false; + networkmanager.enable = pkgs.lib.mkForce false; + + # Until Comcast gets it's shit together... :( + enableIPv6 = false; + }; + + users.extraUsers = { + guest = { + isNormalUser = true; + uid = 1000; + description = "Guest User"; + extraGroups = ["audio" "video" "disk" "floppy" "lp" "cdrom" "tape" "input"]; + }; + ken = { + isNormalUser = true; + uid = 10035; + createHome = true; + description = "Ken Selby"; + extraGroups = ["audio" "video" "disk" "floppy" "lp" "cdrom" "tape" "input"]; + group = "users"; + home = "/home/selby/ken"; + hashedPassword = "$6$EwK9fpbH8$gYVzYY1IYw2/G0wCeUxXrZZqvjWCkCZbBqCOhxowbMuYtC5G0vp.AoYhVKWOJcHJM2c7TdPmAdnhLIe2KYStf."; + }; + xiaoxuan = { + isNormalUser = true; + uid = 10065; + createHome = true; + description = "Xiaoxuan Jin"; + extraGroups = ["audio" "video" "disk" "floppy" "lp" "cdrom" "tape" "input"]; + group = "users"; + home = "/home/xiaoxuan"; + hashedPassword = "$6$C8lYHrK7KvdKm/RE$cHZ2hg5gEOEjTV8Zoayik8sz5h.Vh0.ClCgOlQn8l/2Qx/qdxqZ7xCsAZ1GZ.IEyESfhJeJbjLpykXDwPpfVF0"; + }; + }; + + fileSystems."/mnt/documents" = { + device = "whitedwarf.${local-domain}:/volume1/Documents"; + fsType = "nfs4"; + }; + fileSystems."/mnt/downloads" = { + device = "whitedwarf.${local-domain}:/volume1/Downloads"; + fsType = "nfs4"; + }; + fileSystems."/mnt/music" = { + device = "doraemon.${local-domain}:/volume1/Music"; + fsType = "nfs4"; + }; + fileSystems."/mnt/video" = { + device = "doraemon.${local-domain}:/volume1/Video"; + fsType = "nfs4"; + }; + # fileSystems."/mnt/security" = { + # device = "panopticon.${local-domain}:/srv/kerberos/data"; + # fsType = "nfs4"; + # }; + fileSystems."/mnt/cargo_video" = { + device = "cargo.${local-domain}:/volume1/video"; + fsType = "nfs4"; + }; + fileSystems."/mnt/photo" = { + device = "cargo.${local-domain}:/volume1/pictures"; + fsType = "nfs4"; + }; + + # Should use this eventually... + # fudo.localNetwork = { + # masterNameServer = { + # ip = "10.0.0.1"; + # ipReverseDomain = "0.10.in-addr.arpa"; + # }; + + # domain = "${local-domain}"; + + # hostAliases = { + # kadmin = "slab"; + # kdc = "slab"; + # photo = "doraemon"; + # music = "doraemon"; + # panopticon = "hyperion"; + # hole = "dnshole"; + # ipfs = "nostromo"; + # }; + + # hosts = { + # slab = { + # ipv4Address = "10.0.0.1"; + # }; + # volsung = { + # ipv4Address = "10.0.0.106"; + # macAddress = "ac:bc:32:7b:75:a5"; + # }; + # nest = { + # ipv4Address = "10.0.0.176"; + # macAddress = "18:b4:30:16:7c:5a"; + # }; + # monolith = { + # ipv4Address = "10.0.0.100"; + # macAddress = "6c:62:6d:c8:b0:d8"; + # }; + # brother-wireless = { + # ipv4Address = "10.0.0.160"; + # macAddress = "c0:38:96:64:49:65"; + # }; + # doraemon = { + # ipv4Address = "10.0.0.52"; + # macAddress = "00:11:32:0a:06:c5"; + # }; + # lm = { + # ipv4Address = "10.0.0.21"; + # macAddress = "52:54:00:D8:34:92"; + # }; + # ubiquiti-wifi = { + # ipv4Address = "10.0.0.126"; + # macAddress = "04:18:d6:20:48:fb"; + # }; + # front-light = { + # ipv4Address = "10.0.0.221"; + # macAddress = "94:10:3e:48:94:ed"; + # }; + # ipad = { + # ipv4Address = "10.0.0.202"; + # macAddress = "9c:35:eb:48:6e:71"; + # }; + # chromecast-2 = { + # ipv4Address = "10.0.0.215"; + # macAddress = "a4:77:33:59:a2:ba"; + # }; + # taipan = { + # ipv4Address = "10.0.0.107"; + # macAddress = "52:54:00:34:c4:78"; + # }; + # dns-hole = { + # ipv4Address = "10.0.0.185"; + # macAddress = "b8:27:eb:b2:95:fd"; + # }; + # family-tv = { + # ipv4Address = "10.0.0.205"; + # macAddress = "84:a4:66:3a:b1:f8"; + # }; + # spark = { + # ipv4Address = "10.0.0.108"; + # macAddress = "78:24:af:04:f7:dd"; + # }; + # babycam = { + # ipv4Address = "10.0.0.206"; + # macAddress = "08:ea:40:59:5f:9e"; + # }; + # hyperion = { + # ipv4Address = "10.0.0.109"; + # macAddress = "52:54:00:33:46:de"; + # }; + # cargo = { + # ipv4Address = "10.0.0.50"; + # macAddress = "00:11:32:75:d8:b7"; + # }; + # cam-entrance = { + # ipv4Address = "10.0.0.31"; + # macAddress = "9c:8e:cd:0e:99:7b"; + # }; + # cam-driveway = { + # ipv4Address = "10.0.0.32"; + # macAddress = "9c:8e:cd:0d:3b:09"; + # }; + # cam-deck = { + # ipv4Address = "10.0.0.33"; + # macAddress = "9c:8e:cd:0e:98:c8"; + # }; + # nostromo = { + # ipv4Address = "10.0.0.2"; + # macAddress = "14:fe:b5:ca:a2:c9"; + # }; + # zbox = { + # ipv4Address = "10.0.0.110"; + # macAddress = "18:60:24:91:CC:27"; + # }; + # }; + # }; + }; +} diff --git a/fudo/users.nix b/fudo/users.nix index 9089b58..85b3322 100644 --- a/fudo/users.nix +++ b/fudo/users.nix @@ -408,4 +408,11 @@ hashed-password = "{SSHA}LSz1WjWfjRwAM3xm+QZ71vFj997dnZC6"; }; + # Used to send messages from the chat server + chat = { + uid = 10111; + group = "fudo"; + common-name = "Fudo Chat"; + hashed-password = "{SSHA}XDYAM2JE4PXssywRzO4tVSbn5lUZOgg7"; + }; } diff --git a/hosts/france.nix b/hosts/france.nix index b9657d7..85ec23d 100644 --- a/hosts/france.nix +++ b/hosts/france.nix @@ -2,8 +2,9 @@ with lib; let - hostname = "france.fudo.org"; - mail-hostname = "france.fudo.org"; + domain = "fudo.org"; + hostname = "france.${domain}"; + mail-hostname = hostname; host_ipv4 = "208.81.3.117"; all-hostnames = []; @@ -29,20 +30,26 @@ in { ../hardware-configuration.nix ../defaults.nix - - # These should really both be settings... - # ../networks/fudo.org.nix - # ../profiles/server.nix ]; - fudo.profile = "server"; - fudo.site = "nutty-club"; - fudo.local-networks = [ - "208.81.1.128/28" - "208.81.3.112/28" - "172.17.0.0/16" - "127.0.0.0/8" - ]; + fudo.common = { + # Sets some server-common settings. See /etc/nixos/fudo/profiles/... + profile = "server"; + + # Sets some common site-specific settings: gateway, monitoring, etc. See /etc/nixos/fudo/sites/... + site = "portage"; + + domain = domain; + + www-root = /srv/www; + + local-networks = [ + "208.81.1.128/28" + "208.81.3.112/28" + "172.17.0.0/16" + "127.0.0.0/8" + ]; + }; environment.systemPackages = with pkgs; [ docker @@ -60,14 +67,6 @@ in { dovecot = [ "dovecot._metrics._tcp.fudo.org" ]; rspamd = [ "rspamd._metrics._tcp.fudo.org" ]; }; - # Connections will be allowed from these networks. No auth is performed--the - # data is read-only anyway. - trusted-networks = [ - "208.81.1.128/28" - "208.81.3.112/28" - "172.17.0.0/16" - "127.0.0.0/8" - ]; }; fudo.grafana = { @@ -75,15 +74,20 @@ in { hostname = "monitor.fudo.org"; smtp-username = "metrics"; smtp-password-file = "/srv/grafana/secure/smtp.passwd"; - database-password-file = "/srv/grafana/secure/db.passwd"; admin-password-file = "/srv/grafana/secure/admin.passwd"; secret-key-file = "/srv/grafana/secure/secret.key"; prometheus-host = "metrics.fudo.org"; + database = { + name = "grafana"; + hostname = "localhost"; + user = "grafana"; + password-file = /srv/grafana/secure/db.passwd; + }; }; # So that grafana waits for postgresql - systemd.services.grafana.requires = [ - "postgresql" + systemd.services.grafana.after = [ + "postgresql.service" ]; fudo.postgresql = { @@ -95,7 +99,7 @@ in { # We allow connections from local networks. Auth is still required. Outside # of these networks, no access is allowed. # - # TODO: that's probably to strict, allow kerberos connections from anywhere. + # TODO: that's probably too strict, allow kerberos connections from anywhere? local-networks = [ "208.81.1.128/28" "208.81.3.112/28" @@ -103,6 +107,34 @@ in { "127.0.0.1/8" "172.17.0.0/16" ]; + + users = { + grafana = { + password = fileContents "/srv/grafana/secure/db.passwd"; + databases = { + grafana = "ALL PRIVILEGES"; + }; + }; + mattermost = { + password = fileContents "/srv/mattermost/secure/db.passwd"; + databases = { + mattermost = "ALL PRIVILEGES"; + }; + }; + webmail = { + password = fileContents "/srv/webmail/secure/db.passwd"; + databases = { + webmail = "ALL PRIVILEGES"; + }; + }; + niten = {}; + }; + + databases = { + grafana = ["niten"]; + mattermost = ["niten"]; + webmail = ["niten"]; + }; }; # Not all users need access to france; don't allow LDAP-user access. @@ -133,10 +165,14 @@ in { # TODO: loop over v4 and v6 IPs. listen-uris = [ - "ldap://${host_ipv4}/" - "ldaps://${host_ipv4}/" - "ldap://localhost/" - "ldaps://localhost/" + "ldap:///" + "ldaps:///" + # "ldap://${host_ipv4}/" + # "ldaps://${host_ipv4}/" + # "ldap://localhost/" + # "ldaps://localhost/" + # "ldap://127.0.1.1/" + # "ldaps://127.0.1.1/" "ldapi:///" ]; @@ -191,13 +227,51 @@ in { dkim.signing = true; }; + fudo.webmail = { + enable = true; + + sites = { + "webmail.fudo.link" = { + title = "Fudo Link Webmail"; + favicon = "/etc/nixos/static/fudo.link/favicon.ico"; + mail-server = mail-hostname; + domain = "fudo.link"; + edit-mode = "Plain"; + layout-mode = "bottom"; + database = { + name = "webmail"; + hostname = "localhost"; + user = "webmail"; + password-file = /srv/webmail/secure/db.passwd; + }; + }; + }; + }; + + fudo.chat = { + enable = true; + + hostname = "chat.fudo.org"; + site-name = "Fudo Chat"; + smtp-server = "france.fudo.org"; + smtp-user = "chat"; + smtp-password-file = /srv/mattermost/secure/smtp.passwd; + database = { + name = "mattermost"; + hostname = "localhost"; + user = "mattermost"; + password-file = /srv/mattermost/secure/db.passwd; + }; + }; + networking = { hostName = hostname; dhcpcd.enable = false; useDHCP = false; - interfaces.enp4s0f0.useDHCP = true; - interfaces.enp4s0f1.useDHCP = true; + # Why on earth would these use DHCP? + # interfaces.enp4s0f0.useDHCP = true; + # interfaces.enp4s0f1.useDHCP = true; # TODO: fix IPv6 enableIPv6 = false; @@ -276,6 +350,16 @@ in { options = ["subvol=gitlab"]; label = "pool0"; }; + "/var/lib/lxd/storage-pools/pool0" = { + fsType = "btrfs"; + label = "pool0"; + device = "/dev/disk/by-label/pool0"; + }; + "/var/lib/lxd/storage-pools/pool1" = { + fsType = "btrfs"; + label = "pool1"; + device = "/dev/france-user/fudo-user"; + }; }; ## diff --git a/networks/fudo.org.nix b/networks/fudo.org.nix deleted file mode 100644 index 9b4ba6e..0000000 --- a/networks/fudo.org.nix +++ /dev/null @@ -1,112 +0,0 @@ -{ lib, config, pkgs, ... }: - -let - hostname = config.networking.hostName; - - www-root = "/var/www"; - - index = pkgs.writeTextFile { - name = "index.html"; - - text = '' - - - ${hostname} - - -

${hostname} - - - ''; - destination = www-root + ("/" + hostname); - }; - -in { - - config = { - time.timeZone = "America/Winnipeg"; - - services.cron = { - mailto = "admin@fudo.org"; - }; - - networking = { - domain = "fudo.org"; - - search = ["fudo.org"]; - - firewall.enable = false; - - networkmanager.enable = pkgs.lib.mkForce false; - - defaultGateway = "208.81.3.113"; - - nameservers = [ "1.1.1.1" "208.81.7.14" "2606:4700:4700::1111" ]; - }; - - security.acme.certs."${hostname}" = { - email = "admin@fudo.org"; - - plugins = [ - "fullchain.pem" - "full.pem" - "key.pem" - "chain.pem" - "cert.pem" - ]; - }; - - services = { - prometheus.exporters.node = { - enable = true; - enabledCollectors = [ "systemd" ]; - user = "node"; - }; - - nginx = { - enable = true; - - recommendedGzipSettings = true; - recommendedOptimisation = true; - recommendedTlsSettings = true; - - virtualHosts = { - - "${hostname}" = { - enableACME = true; - forceSSL = true; - root = www-root + ("/" + hostname); - - listen = [ - { - addr = hostname; - port = 80; - ssl = false; - } - { - addr = hostname; - port = 443; - ssl = true; - } - ]; - - locations."/metrics/node" = { - extraConfig = '' - allow 208.81.1.128/28; - allow 208.81.3.112/28; - allow 127.0.0.0/16; - deny all; - - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header Host $host; - ''; - # proxy_set_header Host $http_host; - - proxyPass = "http://127.0.0.1:9100/metrics"; - }; - }; - }; - }; - }; - }; -} diff --git a/networks/sea.fudo.org.nix b/networks/sea.fudo.org.nix deleted file mode 100644 index b3e3a86..0000000 --- a/networks/sea.fudo.org.nix +++ /dev/null @@ -1,192 +0,0 @@ -{ config, pkgs, ... }: - -{ - config.time.timeZone = "America/Los_Angeles"; - - config.services.cron = { - mailto = "niten@fudo.org"; - }; - - services.printing.enable = true; - - config.networking = { - domain = "sea.fudo.org"; - search = ["sea.fudo.org" "fudo.org"]; - firewall.enable = false; - networkmanager.enable = pkgs.lib.mkForce false; - - # Until Comcast gets it's shit together... :( - enableIPv6 = false; - }; - - config.fileSystems."/mnt/documents" = { - device = "whitedwarf.sea.fudo.org:/volume1/Documents"; - fsType = "nfs4"; - }; - config.fileSystems."/mnt/downloads" = { - device = "whitedwarf.sea.fudo.org:/volume1/Downloads"; - fsType = "nfs4"; - }; - config.fileSystems."/mnt/music" = { - device = "doraemon.sea.fudo.org:/volume1/Music"; - fsType = "nfs4"; - }; - config.fileSystems."/mnt/video" = { - device = "doraemon.sea.fudo.org:/volume1/Video"; - fsType = "nfs4"; - }; - # fileSystems."/mnt/security" = { - # device = "panopticon.sea.fudo.org:/srv/kerberos/data"; - # fsType = "nfs4"; - # }; - config.fileSystems."/mnt/cargo_video" = { - device = "cargo.sea.fudo.org:/volume1/video"; - fsType = "nfs4"; - }; - config.fileSystems."/mnt/photo" = { - device = "cargo.sea.fudo.org:/volume1/pictures"; - fsType = "nfs4"; - }; - - config.users.extraUsers = { - guest = { - isNormalUser = true; - uid = 1000; - description = "Guest User"; - extraGroups = ["audio" "video" "disk" "floppy" "lp" "cdrom" "tape" "input"]; - }; - ken = { - isNormalUser = true; - uid = 10035; - createHome = true; - description = "Ken Selby"; - extraGroups = ["audio" "video" "disk" "floppy" "lp" "cdrom" "tape" "input"]; - group = "users"; - home = "/home/selby/ken"; - hashedPassword = "$6$EwK9fpbH8$gYVzYY1IYw2/G0wCeUxXrZZqvjWCkCZbBqCOhxowbMuYtC5G0vp.AoYhVKWOJcHJM2c7TdPmAdnhLIe2KYStf."; - }; - xiaoxuan = { - isNormalUser = true; - uid = 10065; - createHome = true; - description = "Xiaoxuan Jin"; - extraGroups = ["audio" "video" "disk" "floppy" "lp" "cdrom" "tape" "input"]; - group = "users"; - home = "/home/xiaoxuan"; - hashedPassword = "$6$C8lYHrK7KvdKm/RE$cHZ2hg5gEOEjTV8Zoayik8sz5h.Vh0.ClCgOlQn8l/2Qx/qdxqZ7xCsAZ1GZ.IEyESfhJeJbjLpykXDwPpfVF0"; - }; - }; - - config.fudo.localNetwork = { - masterNameServer = { - ip = "10.0.0.1"; - ipReverseDomain = "0.10.in-addr.arpa"; - }; - - domain = "sea.fudo.org"; - - hostAliases = { - kadmin = "slab"; - kdc = "slab"; - photo = "doraemon"; - music = "doraemon"; - panopticon = "hyperion"; - hole = "dnshole"; - ipfs = "nostromo"; - }; - - hosts = { - slab = { - ipv4Address = "10.0.0.1"; - }; - volsung = { - ipv4Address = "10.0.0.106"; - macAddress = "ac:bc:32:7b:75:a5"; - }; - nest = { - ipv4Address = "10.0.0.176"; - macAddress = "18:b4:30:16:7c:5a"; - }; - monolith = { - ipv4Address = "10.0.0.100"; - macAddress = "6c:62:6d:c8:b0:d8"; - }; - brother-wireless = { - ipv4Address = "10.0.0.160"; - macAddress = "c0:38:96:64:49:65"; - }; - doraemon = { - ipv4Address = "10.0.0.52"; - macAddress = "00:11:32:0a:06:c5"; - }; - lm = { - ipv4Address = "10.0.0.21"; - macAddress = "52:54:00:D8:34:92"; - }; - ubiquiti-wifi = { - ipv4Address = "10.0.0.126"; - macAddress = "04:18:d6:20:48:fb"; - }; - front-light = { - ipv4Address = "10.0.0.221"; - macAddress = "94:10:3e:48:94:ed"; - }; - ipad = { - ipv4Address = "10.0.0.202"; - macAddress = "9c:35:eb:48:6e:71"; - }; - chromecast-2 = { - ipv4Address = "10.0.0.215"; - macAddress = "a4:77:33:59:a2:ba"; - }; - taipan = { - ipv4Address = "10.0.0.107"; - macAddress = "52:54:00:34:c4:78"; - }; - dns-hole = { - ipv4Address = "10.0.0.185"; - macAddress = "b8:27:eb:b2:95:fd"; - }; - family-tv = { - ipv4Address = "10.0.0.205"; - macAddress = "84:a4:66:3a:b1:f8"; - }; - spark = { - ipv4Address = "10.0.0.108"; - macAddress = "78:24:af:04:f7:dd"; - }; - babycam = { - ipv4Address = "10.0.0.206"; - macAddress = "08:ea:40:59:5f:9e"; - }; - hyperion = { - ipv4Address = "10.0.0.109"; - macAddress = "52:54:00:33:46:de"; - }; - cargo = { - ipv4Address = "10.0.0.50"; - macAddress = "00:11:32:75:d8:b7"; - }; - cam-entrance = { - ipv4Address = "10.0.0.31"; - macAddress = "9c:8e:cd:0e:99:7b"; - }; - cam-driveway = { - ipv4Address = "10.0.0.32"; - macAddress = "9c:8e:cd:0d:3b:09"; - }; - cam-deck = { - ipv4Address = "10.0.0.33"; - macAddress = "9c:8e:cd:0e:98:c8"; - }; - nostromo = { - ipv4Address = "10.0.0.2"; - macAddress = "14:fe:b5:ca:a2:c9"; - }; - zbox = { - ipv4Address = "10.0.0.110"; - macAddress = "18:60:24:91:CC:27"; - }; - }; - }; -} diff --git a/profiles/.#ldap-server.nix b/profiles/.#ldap-server.nix deleted file mode 120000 index e216937..0000000 --- a/profiles/.#ldap-server.nix +++ /dev/null @@ -1 +0,0 @@ -root@france.26610:1573312038 \ No newline at end of file diff --git a/profiles/desktop.nix b/profiles/desktop.nix deleted file mode 100644 index 445d47d..0000000 --- a/profiles/desktop.nix +++ /dev/null @@ -1,151 +0,0 @@ -{ config, lib, pkgs, ... }: - -{ - environment.systemPackages = with pkgs; [ - cool-retro-term - chrome-gnome-shell - chromium - ffmpeg-full - firefox - gimp - glxinfo - gnome3.gnome-shell - gnome3.gnome-session - google-chrome - gtk2 - gtk2-x11 - gtk3 - gtkimageview - i3lock - libfixposix - minecraft - mplayer - nomacs - openssl_1_1 - redshift - rhythmbox - shotwell - spotify - sqlite - steam - system-config-printer - virtmanager - xorg.xev - xzgv - virtmanager-qt - ]; - - boot.plymouth.enable = true; - - services.avahi = { - enable = true; - browseDomains = ["sea.fudo.org"]; - domainName = "sea.fudo.org"; - }; - - boot.tmpOnTmpfs = true; - - services.xserver = { - enable = true; - - layout = "us"; - xkbVariant = "dvp"; - xkbOptions = "ctrl:nocaps"; - - desktopManager.gnome3.enable = true; - desktopManager.default = "gnome3"; - - displayManager.gdm.enable = true; - - windowManager.session = pkgs.lib.singleton { - name = "stumpwm"; - start = '' - ${pkgs.lispPackages.stumpwm}/bin/stumpwm & - waidPID=$! - ''; - }; - }; - - services.printing = { - enable = true; - }; - - services.gnome3 = { - evolution-data-server.enable = pkgs.lib.mkForce false; - gnome-user-share.enable = pkgs.lib.mkForce false; - }; - - services.dbus.socketActivated = true; - - services.openssh.forwardX11 = true; - - programs.ssh.forwardX11 = true; - - sound.enable = true; - - hardware.pulseaudio.enable = true; - - fonts = { - enableCoreFonts = true; - enableFontDir = true; - enableGhostscriptFonts = false; - fontconfig.ultimate.enable = true; - - fonts = with pkgs; [ - cantarell_fonts - dejavu_fonts - dina-font - dosemu_fonts - fira-code - fira-code-symbols - freefont_ttf - liberation_ttf - mplus-outline-fonts - nerdfonts - noto-fonts - noto-fonts-cjk - noto-fonts-emoji - proggyfonts - terminus_font - ubuntu_font_family - ucsFonts - unifont - vistafonts - xlibs.fontadobe100dpi - xlibs.fontadobe75dpi - xlibs.fontadobeutopia100dpi - xlibs.fontadobeutopia75dpi - xlibs.fontadobeutopiatype1 - xlibs.fontarabicmisc - xlibs.fontbh100dpi - xlibs.fontbh75dpi - xlibs.fontbhlucidatypewriter100dpi - xlibs.fontbhlucidatypewriter75dpi - xlibs.fontbhttf - xlibs.fontbhtype1 - xlibs.fontbitstream100dpi - xlibs.fontbitstream75dpi - xlibs.fontbitstreamtype1 - xlibs.fontcronyxcyrillic - xlibs.fontcursormisc - xlibs.fontdaewoomisc - xlibs.fontdecmisc - xlibs.fontibmtype1 - xlibs.fontisasmisc - xlibs.fontjismisc - xlibs.fontmicromisc - xlibs.fontmisccyrillic - xlibs.fontmiscethiopic - xlibs.fontmiscmeltho - xlibs.fontmiscmisc - xlibs.fontmuttmisc - xlibs.fontschumachermisc - xlibs.fontscreencyrillic - xlibs.fontsonymisc - xlibs.fontsunmisc - xlibs.fontwinitzkicyrillic - xlibs.fontxfree86type1 - ]; - }; - -} diff --git a/profiles/ldap-server.nix b/profiles/ldap-server.nix deleted file mode 100644 index 84378d3..0000000 --- a/profiles/ldap-server.nix +++ /dev/null @@ -1,19 +0,0 @@ -{ config, pkgs, ... }: - -let - base = "dc=fudo,dc=org"; - ldap = import ../config/fudo/ldap.nix; - -in { - - imports = [ - ../config/fudo/ldap.nix - ]; - - services.openldap = { - enable = true; - suffix = base; - rootdn = "cn=admin,${base}"; - rootpwFile = "/srv/ldap/secure/root.pw"; - }; -} diff --git a/profiles/server.nix b/profiles/server.nix deleted file mode 100644 index cb3ff2c..0000000 --- a/profiles/server.nix +++ /dev/null @@ -1,16 +0,0 @@ -{ config, pkgs, ... }: - -{ - environment = { - systemPackages = with pkgs; [ - ]; - - noXlibs = true; - }; - - security.hideProcessInformation = true; - - boot.tmpOnTmpfs = true; - - services.xserver.enable = false; -} diff --git a/profiles/services/basic_acme.nix b/profiles/services/basic_acme.nix deleted file mode 100644 index b323c04..0000000 --- a/profiles/services/basic_acme.nix +++ /dev/null @@ -1,43 +0,0 @@ -# Starts an Nginx server on $HOSTNAME just to get a cert for this host - -{ config, pkgs, environment, ... }: - -let - hostname = config.networking.hostName; - - wwwRoot = pkgs.writeTextFile { - name = "index.html"; - - text = '' - - - ${hostname} - - -

${hostname} - - - ''; - destination = "/www"; - }; - -in { - - services.nginx = { - enable = true; - - recommendedGzipSettings = true; - recommendedOptimisation = true; - recommendedTlsSettings = true; - - virtualHosts."${hostname}" = { - enableACME = true; - forceSSL = true; - root = wwwRoot + ("/" + "www"); - }; - }; - - security.acme.certs = { - ${hostname}.email = "admin@fudo.org"; - }; -} diff --git a/profiles/services/local_nameserver.nix b/profiles/services/local_nameserver.nix deleted file mode 100644 index c744fe8..0000000 --- a/profiles/services/local_nameserver.nix +++ /dev/null @@ -1,136 +0,0 @@ -{ config, pkgs, environment, ... }: - -let - databaseName = "powerdns"; - userName = "powerdns"; - reverseIp = ip: builtins.concatStringsSep "." (lib.lists.reverseList(lib.strings.splitString "." ip)); - fullReverseIp = ip: "${reverseIp ip}.in-addr.arpa"; - hostRecord = domain_id: type: name: content: '' - INSERT INTO records (domain_id, name, type, content) VALUES ($domain_id, '${name}', '${type}', '${content}'); - ''; - -in { - environment = { - systemPackages = with pkgs; [ - postgresql_11_gssapi - powerdns - ]; - }; - - services.postgresql.users."${userName}" = { - passwd = "some_junk"; - databases = ["${databaseName}"]; - }; - - services.postgresql.databases."${databaseName} = { - "${databaseName}" = '' - CREATE TABLE domains ( - id SERIAL PRIMARY KEY, - name VARCHAR(255) NOT NULL, - master VARCHAR(128) DEFAULT NULL, - last_check INT DEFAULT NULL, - type VARCHAR(6) NOT NULL, - notified_serial INT DEFAULT NULL, - account VARCHAR(40) DEFAULT NULL, - CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT))) - ); - - CREATE UNIQUE INDEX name_index ON domains(name); - - - CREATE TABLE records ( - id BIGSERIAL PRIMARY KEY, - domain_id INT DEFAULT NULL, - name VARCHAR(255) DEFAULT NULL, - type VARCHAR(10) DEFAULT NULL, - content VARCHAR(65535) DEFAULT NULL, - ttl INT DEFAULT NULL, - prio INT DEFAULT NULL, - disabled BOOL DEFAULT 'f', - ordername VARCHAR(255), - auth BOOL DEFAULT 't', - CONSTRAINT domain_exists - FOREIGN KEY(domain_id) REFERENCES domains(id) - ON DELETE CASCADE, - CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT))) - ); - - CREATE INDEX rec_name_index ON records(name); - CREATE INDEX nametype_index ON records(name,type); - CREATE INDEX domain_id ON records(domain_id); - CREATE INDEX recordorder ON records (domain_id, ordername text_pattern_ops); - - - CREATE TABLE supermasters ( - ip INET NOT NULL, - nameserver VARCHAR(255) NOT NULL, - account VARCHAR(40) NOT NULL, - PRIMARY KEY(ip, nameserver) - ); - - - CREATE TABLE comments ( - id SERIAL PRIMARY KEY, - domain_id INT NOT NULL, - name VARCHAR(255) NOT NULL, - type VARCHAR(10) NOT NULL, - modified_at INT NOT NULL, - account VARCHAR(40) DEFAULT NULL, - comment VARCHAR(65535) NOT NULL, - CONSTRAINT domain_exists - FOREIGN KEY(domain_id) REFERENCES domains(id) - ON DELETE CASCADE, - CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT))) - ); - - CREATE INDEX comments_domain_id_idx ON comments (domain_id); - CREATE INDEX comments_name_type_idx ON comments (name, type); - CREATE INDEX comments_order_idx ON comments (domain_id, modified_at); - - - CREATE TABLE domainmetadata ( - id SERIAL PRIMARY KEY, - domain_id INT REFERENCES domains(id) ON DELETE CASCADE, - kind VARCHAR(32), - content TEXT - ); - - CREATE INDEX domainidmetaindex ON domainmetadata(domain_id); - - - CREATE TABLE cryptokeys ( - id SERIAL PRIMARY KEY, - domain_id INT REFERENCES domains(id) ON DELETE CASCADE, - flags INT NOT NULL, - active BOOL, - content TEXT - ); - - CREATE INDEX domainidindex ON cryptokeys(domain_id); - - - CREATE TABLE tsigkeys ( - id SERIAL PRIMARY KEY, - name VARCHAR(255), - algorithm VARCHAR(50), - secret VARCHAR(255), - CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT))) - ); - - CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm); - - INSERT INTO domains (id, name, master, type) VALUES (1, '${config.fudo.localNetwork.domain}', '${config.fudo.localNetwork.masterNameServer.ip}', 'MASTER'); - INSERT INTO domains (id, name, master, type) VALUES (2, '${config.fudo.localNetwork.masterNameServer.ipReverseDomain}', '${config.fudo.localNetwork.masterNameServer.ip}', 'MASTER'); - - ${hostRecord 1 "SOA" config.fudo.localDomain "${config.fudo.localNetwork.domain}. hostmaster.${config.fudo.localNetwork.domain}."} - ${hostRecord 2 "SOA" config.fudo.masterNameServer.ipReverseDomain "${config.fudo.localNetwork.masterNameServer.ipReverseDomain} hostmaster.${config.fudo.localNetwork.domain}."} - ${hostRecord 1 "NS" config.fudo.localNetwork.domain config.fudo.localNetwork.masterNameServer.ip} - ${hostRecord 2 "NS" config.fudo.localNetwork.masterNameServer.ipReverseDomain config.fudo.localNetwork.masterNameServer.ip} - - ${builtins.concatStringsSep "\n" (lib.attrSets.mapAttrs (host: attrs: hostRecord 1 "A" host attrs.ipv4Address) config.fudo.localNetwork.hosts)} - ${builtins.concatStringsSep "\n" (lib.attrSets.mapAttrs (host: attrs: hostRecord 2 "PTR" (fullReverseIp attrs.ipv4Address) host) config.fudo.localNetworkhosts)} - ${builtins.concatStringsSep "\n" (lib.attrSets.mapAttrs (alias: host: hostRecord 1 "CNAME" alias host) config.fudo.localNetwork.hostAliases)} - ''; - }; - }; -} diff --git a/profiles/vm/http.nix b/profiles/vm/http.nix deleted file mode 100644 index b644ef4..0000000 --- a/profiles/vm/http.nix +++ /dev/null @@ -1,42 +0,0 @@ -{ config, pkgs, ... }: - -{ containers.https = - let - hostname = "${config.hostname}.fudo.link"; - incomingCertDir = "/srv/${config.hostname}/certs"; - containerCertsDir = "/etc/letsencrypt/live"; - - in { - autoStart = true; - - bindMounts = [ - { - "${containerCertsDir}" = { - hostPath = "${incomingCertsDir}"; - isReadOnly = false; - }; - } - ]; - - config = { config, pkgs, ... }: - { - environment.systemPackages = with pkgs; [ - nginx - ]; - - services.nginx = { - enable = true; - - virtualHosts."${hostname}" = { - enableACME = true; - forceSSL = true; - root = "/var/www"; - }; - - security.acme.certs = { - "${hostname}".email = config.adminEmail; - }; - }; - }; - }; -} diff --git a/profiles/vm/postgres.nix b/profiles/vm/postgres.nix deleted file mode 100644 index fde59a8..0000000 --- a/profiles/vm/postgres.nix +++ /dev/null @@ -1,75 +0,0 @@ -{ config, pkgs, environment, ... }: - -let - hostPath = /srv + ("/" + config.networking.hostName); - srcCertificateDirectory = hostPath + "/certs"; - dstCertificateDirectory = "/etc/pki/certs/postgres"; - dstPrivateKey = dstCertificateDirectory + /private/privkey.pem; - srcKeytabPath = hostPath + /keytabs/postgres; - dstKeytabPath = "/etc/postgresql-common/keytab"; - -in { - - containers.postgres = { - autoStart = true; - - bindMounts = { - "${dstCertificateDirectory}" = { - hostPath = "${srcCertificateDirectory}"; - isReadOnly = false; - }; - "${dstKeytabPath}" = { - hostPath = "${srcKeytabPath}"; - isReadOnly = false; - }; - }; - - config = { config, pkgs, environment, ... }: { - environment.etc."${dstPrivateKey}".mode = "0400"; - - boot.tmpOnTmpfs = true; - - # Kind of a stupid hack...bindMounts can't specify perms, and it defaults to - # permissive (even for nested files). So, explicitly make the keys private. - # TODO: eventually, use bindMount perms, hopefully? - boot.postBootCommands = '' - chown postgres ${dstKeytabPath}/postgres.keytab - chmod 400 ${dstKeytabPath}/postgres.keytab - chown -R postgres ${dstCertificateDirectory} - chown 400 ${dstCertificateDirectory}/private/privkey.pem - ''; - - services.postgresql = { - enable = true; - package = pkgs.postgresql_10; - enableTCPIP = true; - - extraConfig = - '' - krb_server_keyfile = '${dstKeytabPath}/postgres.keytab' - - ssl = true - ssl_cert_file = '${dstCertificateDirectory}/cert.pem' - ssl_key_file = '${dstCertificateDirectory}/private/privkey.pem' - ''; - - authentication = - '' - local all all ident - - # host-local - host all all 127.0.0.1/32 gss include_realm=0 krb_realm=FUDO.ORG - host all all ::1/128 gss include_realm=0 krb_realm=FUDO.ORG - - # local network - host all all 10.0.0.1/24 gss include_realm=0 krb_realm=FUDO.ORG - host all all 2601:600:997f:fc00::/60 gss include_realm=0 krb_realm=FUDO.ORG - ''; - - initialScript = pkgs.writeText "backend-initscript" '' - CREATE ROLE niten; - ''; - }; - }; - }; -} diff --git a/static/fudo.link/android-chrome-192x192.png b/static/fudo.link/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..777edee2ab297ea6c2ae93b66ae4a2885156b5a0 GIT binary patch literal 4277 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 Ib4q9e0DgC9!~g&Q literal 0 HcmV?d00001 diff --git a/static/fudo.link/favicon-32x32.png b/static/fudo.link/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..4ba22e160c579d0f3e4390d2e1173b9904534e94 GIT binary patch literal 436 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANNf)t)YnArbCxr=Rt5a+GMB zf7!ClBQap2Qe)@3U0l=ix)!W+^oUWBsN4|xWBW8E3cqf4UR>mxyQ|6JPD@ySpSS!na(a}NRh1Y?HGZL$-TRoOsn0#seyhjml=9K(e_xjnYU2UgLy){Z~Vi?nm zx{m96s^rz(x%0a8!>?It0v?|}eKNCQEZg1t{kyZWY<#Tk=B=e(n^!J;Z6(%y=FuaW zy8lKOUub=;n!I#rK>W*H>yW1H(KP1_lEI2tPxOf#H}a1A_(w1A_oa z9Roz10S5?WV5kdaVE7rz!0?|$4AMicUYK1VGhl40&?S0$|L5i9{hynY1Ev=i75!gQ zR`!2dVj|JzFRiHff9u4F|1sR$|D(CM{ugR#{Xf5b`~NsTKBCM3*?(#GZWOh-Dk}e5 zJv~vxaD_KW|3x(YxYS}3AL;r*@pt3M5u(BxJ^YIG^#0Gu&i+3;GxL9^zdu=Kpqoj6 zPe~pC=_NN0g7xAk7Kj!Gl`{$q3=9qg${7X*2Br`OhMG_YhGzuAgPLLf3}h#g-PAOj zXtO|WptHZx-A2zaN4I;FPkP-tAYpcK*RKDs?%)6a?AERSPp)78|MbR<|IcsV{{Qmc zz5j0>J^KId*|Yy2U%mSO<=wmg-#&i)zkTv#s=G6upa1{&&!7K;z|j>e{-;Sw{!bJV z`kx>u_&-@p?0>nj@&837CI3IZeEI*?g9raZ85ybW22dD)>|b78jniyUeg5^shyT@< zmN?arTh6_>dlzK=e^5P!&x~bNRsVNRok}zNmz0;|H9J*8;{WEpKD=u1xgVDt%>RV! z$EBZA@lpFB>2Ear@uz=E(-|)Pqv>yCr$1Qzv#hd`>SZggGzSxp<>iIfzk6oQqDlP< zvkRR*zheiu?eXx^rT_QNo&~o-9$mTee^z=r-NOOhZKHgW$A<=6oDF0j2;2`Hs5AuX zeK2X$w+#bTfFg28c;X4uf2SphX!octYi(hZ_MsZ)qwXFe28_&*^s^8cjh=>L;rW5IIMl9K+b8~b5&(F{Qzp$|I z|H9(p{~0neboYCwukZgGN00vh{PyktA78%w|Muw<3I>hGf-p$#$Jej_e}4P+|L6Db z|9}1X@&EVFpZ|aV`t|=02><>K4u^q?=V(sO|Jx=_`v33mU-0-Th}bcC@_*2raJGWN z{|s4KFwT^d`=76&0iKUt-Prj5?&;I;J}+pVcc9!4vKN$&Kw%4V`@etx{+AgV5=;-V zJUstrWn}z+^Z4=qpI^WJ2l)?V)<9rT`Uj;Gko!StqCiuVp!-4Yj9_8;zo@tvoVEy! zdxO-|3WM_L_b*>S&j0`G`}h9^8X82oALO1S5t087E?)e9|NQy?pf~}E(HkSXAC!(r zaX-kdpg8#S`t|>2S68C_4bn#~OsV@pb48#$1e%|rmYYa1hf?>GVizscj=Fzf$GJws ze>D6l$=9RlXEgncrXNWAjhyxmC1FPmy`%YeH2+ePj)r>rLGC|-#urHKzfcl()X)p+ zdw|AAKpn`xfB*h3(bpf4eiS)=iDGB}zoow)W&8v*HWkLqJgEK9;p6lF>cNBmKfHPc z9=H1X{ylh&B4~XcXe{gfix>Z|A35?rPfcx5`UAA)d}eCu|EUQH;PJxlz`*~YF}d!b zATS@acLB5>9kkw#oOm78Gq82aX!wtYKONJ+P@aPXxg9hIJSfX)BH|Fd76LREPlVdR w=Ev`#^#h&sm!Y(N2Ih8<`$zF;2#^~B0L3<{TmS$7 literal 0 HcmV?d00001 diff --git a/static/fudo.link/site.webmanifest b/static/fudo.link/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/static/fudo.link/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file