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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 OT^6Rc7pl*rV*dl@721XX literal 0 HcmV?d00001 diff --git a/static/fudo.link/android-chrome-512x512.png b/static/fudo.link/android-chrome-512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..c3b6d04881c4fcece73c38d635f7fe36a8584761 GIT binary patch literal 14022 zcmeHucOcaN|M=_9S&7KZJSjpbS(%qe$OutL-b6NKJL9gBN=Ei3j_jG0bq$nA*<>~B zal$#7-{+z4@Av)w_5J_z?vKvv?)iK^_I|#e=<90GAL2X&K@h#>WiDUTpsVRnw#&MEjB$ZAQW1g->m8YJpj4T(+zCx!dc2ZjV zy_ell7=jUn%!5xv^=S=y8rT+{AgoXuOdQt?&7QZ)vnpF1pqk@qA8=agF*az1G{@pR zYH)!vn>bOh4T2%6M3{P}q`IT@=4F5=W=JTjPBci)M@7@|3oUUamZWGs*j2k4pZ`e$rz(+P3JOFr;PD+QP zD+)s(@?n?@*h9iA8LuWT*{>DcC9)erOD@DaCDD?1-dCROy8vha^!vgG>u~L?cem`2 zKcF-9&tGjQ3ZrXrYah7~JOTh88YE*KUdX^UrDu}Oel{o9IN}U+_`JSIl1QGbQHLS> z9*M9JN{d*L5bcUxreX@bh=t(aSl7+lwoplb)b!IC;i#EJgTA_jF{ItOl!${Bo`;;C zOHN%rN620IV9G$l0clA*FYl+e$G9_*B?`E~hi%X9YUf%$->o2og5>^88BA9H5pVZB zK!VW-iEfmdoAl5Bbqvzw6|u>gsz6SW#Re9j65xaXbQK;86rE&(v>2jSakxu?>_2_> z)rFDfCLUdhCtSAa+{l|h2gR($tv~6a=tIMY&N1J# zpaA>x4Et(yoia|7{zyI{l=2S^bke5&?XAKGx6kfx-a6DC)uuJ;ZlFYgTCCh|?SF}E z{fibqWqH=@$|#IT1J!yH(c!Xd!oSYk;Mzabk1j?#cGs}Kj;4omXkBOA1nh=zPH%geX&rehV2 zf3O^ImwWcSSe0K6*IxmON1oS9@^$n~b%vupDr$yM-FkTF@5U?RzIjex zyx=HJ6HS7ah-BZHg}?lF!)j@wwQRH-45SyKrSPCP&UyO^gR#LD5m>GZKr!v{9*-xg zX6W~g2?3|AKNU-#dzX6ry^KLttRhK;QAB9p7%gvRs1Eg9_46Uddqa=7g;r*ASeRX+6{T(vPAbW=XLjwS5CQfo%&sHuLL+fvw+VR^h+mn4~O! zR}Z)u$t-&tbIw$*BKIze9gHWn;8@W3xuRU*l*u35p znAKN8+A-g*oh8%@KI)9Ed@Iag`-)hNx8I5pXk9JoNJZz6>| z_T~?oq?=WWeBSo%)hM(5oKkY`b3&BJMVU|tPGm$(XX9=xv3KBr?Y3iQ$C$t`W~>Rs$pXzqhF$~u6`@v zMez=NKHz)thSfrV%ksojj)=HRrfmmjLCwHs?v8u(a1i85bSPxF~UCB<7X7nY8p;vboLtl}muH=krv#sL!wx?}Jf_Cfgui<~t zAT13skfpo$lUawanVPeM6^5C9y4n@dLc8iyY3MtqRiBiZQ1}pl0X18Q%Pq^^e1*zx z-JwsI(v`Ui~rujYFWV`RRR_+y1<_2=i z9yp4G29h+6*A3SDKH`)*rFEN7EV~`wR9{R8B+2(zf7}5n)DhqddF@h`J+Zw71mn5B z=28xp8bagL>QBBgtI~6(st{I{juASf1f%ZTozD8!lV_+v?e$}nP4J4(m8?r6Q==^d zOmG?m8xleY;_$<@S;Pfb0^P$4*ox0d`FV!v{~O;3-hlF!a4}Cyp+)BxLA#=>UD|}2 z>a$)CAvhM$gryb!lGQ3p>8DP5p+9P&t6pfQ{8Q!qc@Oquaj9@(V3W8;P zVRpN{oxZdBt(Ki)*+RH;bIC%p#JaU_cA!jWv4SL7tN50EeZIoewCsm_k$J7Ds7YWN z`pBc@<$RO#{>vX_rMws4F0p4HhttUMqaZh{XKS)wtDN0E7ltm3y!PChU9muW8kAV1 z`ZB$cZ>e?~6loy#g_xHAy0zR`5VX4;dn;hgqAP(KM5D+E5MTJDKhs{rqgmfrGj|JAc$KPzqk&NC2)5TA@d_`B!vY+-*@uo{BgJp~dV8!kG zXitn&-rw0qPtR90MWd%K5Ek5{*Vm`B^gKJ2I;Tg`>D@n?`GoG$tv-X`;WUg8_p#ea zRbLweL`OXP&&w~?P#mpXm@5goZrvI;G5U0mB7Hl6&kG^^hA-W)N4Nd!rMK2A$||3k z_0HQw0kh?siYg>W+8z-UG!EA+L=_l!tiGW(?8%Q^Ao2Um=E<(663mmY-{I7NV6s50 zE}x~P>->H^`Q*9!HQ)-t`_~7wlzrFF95n7cg7I=Xpd;X%w?nId25-R=X1#L_FD)7Z zUsR9neDR2o0_D*RGZMnNPU79cvoaKtdLP9=|(;_RkH_I3+AJqK*)FN#)PS+o45YnZbT@ zuz#5jAG{!O-K}ARIA?oL)Vd{RB5kvi0%R&Z0R>ufcX zNroO1u^0W**;{Eh7tzm0{pKFX#aYn~4s!nA3r9f3ltPHUqB@g~WLfFTc+F_u{twSd z3A0aZ|MJZco*pQ#|?I^LP_L=nFcba(4{Rj_YINvuyc0M``My| z6D>|~`5!;~uYY$QvWfS~S@R(FR(SMYJEucbJ4enwVHiieFg9D;^;*2RLgG+&!dV+m zg{pQVy}d$ePbf#Pzg}hYoauVglSTvOv|IrDO2eQ|_;{3?Dh3*V&oKxb|NB!Y=rL1r zb|%m`wL9822u3#$bjBym4XZ`rsM$-OA2Fej8NlX{$d}(3L6AtGxCwZ858(eXku|mN zI56sEvQ}>at={sz`|2tTj`{lL3rM^Z01p#*=)?Yi9C7{Y4{|Y#Iu5z99(YhEI1FLM zf`r~b?lAZYHfL63OTuYtK!)w+UYz%h6^8T_xbqHpE`1<+#>j4GplVL-SaIM0&jShm zYJaNh2u^nMB}-!m;ImUNHJK&fwx4hXU2#W%+Q2r@S>L3)`99;$5 zG{FfcYz$U!6aXpEk+pM42n8)oHGjPXyw@e_i)J8l8-wLuQc{x|fSDSh*5ock8r28s zgM6VJnD1j9i~>}4wL(7zKse=@7Y4z_0nX+r4u?o;(B&IBS*%hPrSg1-uI{b6KSBLqhs3i%ud1Rn`tG4ctS z0gA6DA_BlR18{<7B1=Huo;N2e^8ZQjn3u47^m0I1+-^rY0XJ#I3`&2ep3^MZ0D{&{ z2LTKw6yVDKGU=@n6J#LgQ3c`;7Qn);%MAjG@oguj!L}%H!sRyHbcHc+OYAg2rsH}5 z>CVusFu=NJ43DaSS1bg}=k4s;L7>G)u(guQIz{?OM?4c?lfZ-9TtKCgA#cbk%l5@7 zz{9Jv2B2eN4%kp5*UPf_1d4q7q5{|f%?VKJy>ID8l>k@` zifrUT0O=qLZf7GPZjUpsK%BD$N7`y!v2h$VZK-X{pQA5GV zB~7Oebp5eG{1Bib6X4pMm*b{~z_2i+iV0v)NB|3|tXl;rE|sL+0NZdtlcOYsHwv!- zr%FloQtQBI*H?bnWdSU*9=s4kFrE?-sqesQ{~0XBU=4pIS& z7sT^m26Atl(gF}-UiVyrVC(>Aab4+P^l=F1Htlo_2Kp3?5d8Pua#p~zSj{gWgE*lM z!d{H#*E>Z3m^dRj{D4r5PiNQQ0(Q>9bJ&?5gSi2g4}SfUzz070#P*s+KAgyYi~!i9 zLG@7v*sct05qf{AL-i`)VP;RZ3@~}%g52)r3lB5Hkii%4d;;>h3=HydM063LxbVt- zWw1>c7zysmQ)Nf8W5)h#@_@nrn*6^e|F6ma8|D9v^8Zm$zQ~l)YbY<4ihf9Yd&AQZ zRMST0!)nPg&c?of1OtAn2PSI{R+6 zN_+Pt8K>M2byPwil^N$es67nJo_H;oPQN?iYFJ?#2F`3xT=8rZj$T$yXJ%Rgxsy4y zE{Ogq9M&Q-cW3h+c z-rn@?50$WBw+os|aSw5+Tlk2QmOF+`0>~6o3jzM=KKnYT<0G;4Q;7mOrs3K86=A-JkFW2YpdNR_Dkay*tIX9$KVHg4e;La+hle5yT(dZ#%D>{Z)4^x zb|Yw|<48{(NgeCpCJEo70ZM8Ql(hS&&acV0>x;WjMROd7YeJ6J1|?NVfHuTHIulRd zMZ6Wnp-^=Nv~+s2&O z6VEKUe$J~MdiZ-}ces|$^F34liF>>;Q#oO z2+!%c*0?$neyg^v4!EQMeUYoYAu0(Ag<1Z1uQHn%x)|ov$x&#~RWw7~Uu$rSxBp#QdH zfNjo|KcSbN)pXO|lDS%q0zF*6h05QvT{bUYA1VI%SRkU_SWo%SrEz%{)Sh|OW11ni zsZ@8*$;4^}*G{SegiFnLqnrM}X|d;bh$Wpp*`I3ja|gI<*eHRYaHE%#D0Lbv8VVqd zkNF1El6#7?tKF{j6(=g69a^31*O6%SDdLoM;i!3M=DJ<+Ss@~RhyJi8R-SU536-kG z9f`n3D8Ig$DqktxJIlATRIjTjA%I7z2*Rb?9bFVwJ zxDVH%H+9JVrE$*MdB?snBLm5=pclg2hzCw?u7xXzL+iWu*v%KbO5*x5$@JO0>VMJo zBaSQw(thfChu-xf1PuGuh?chUDmcEmKX{<>QY1?{^l7kTP+M9EP<1{#Xdu492-FgQMCz zi&fvwZ)~m5zv#s=!aafOpFR3xtb@*fCE0oPcdKCsvDWg zmx;>Ts~p5x*3S~#z2=@DB5D1W-o(%K(`fKBMgfvfTyA60&2SwP_nV02qU*;q7+aM8 zvIdUxMQx+qhb9!5qO1yzor&Ju>o3;tT>bx}X#XtkV zEQP4b&Y`?TGZMJD^kweRRX1QY{b)iFO%{u^70ZBQ$<5Ud0$Dv-8ZR%KH2e&&_(K8oEZpv%ut2uUTGMh_kK=cHxBi5_6W4B_q106gC>7M(NlcQaBlzwpIFL%; z6?eIJZl8NQqR)xt0d3(L`J)nNT!zow^cvI@1j7Zc3{o*hah}endaI9CB(aSF6x}j5QhU zhfxop!{E`^&q`ebnNxQ5w&(ZYv2M_!=6(0{+pQnfO{rOQ;t9s3#2Xy2z4#Ythy9$| z^kU-E%Y=~!B*~H@vvk|4rdLvZjRD%edh9%Bf7Fq?w3K5qP20DYn7)uhHF5?%+PTv( z1}oi~?ww=z+gvyzRy}>&b+lle$InjBSAqqkI~2V+I%Q7+0$0Djob8%0cOI>8R#TCB zZUK5EIPW#x`C;@%I!OxkDWc5!6W~wc1Y7D%(MD*X{R{*`G*+xH-8fogi0~sB$8+k3^frrK0eT)h^TS0?(?X{5c;K*XJK*X?wx7hWX z=3C!nd>3|U#Nk?#CsELc(6H#!G(Njow_e#r4c>wZ?SC~4gpyJSW-f_YVO+9iU~RbQOPRs5yV# zyonjxnxs`){xDW~Z({1ntkRbn;&=ns34Yte-EPMcF+*S9 zjZvmzn@_g2W@~keL%OL7)g~ntb(4)B(6{D`#@afkFa8} zIfVd&L-tg+w--c&&#QsVI?U?H{k}pl@Rc1i2j}e+v5Fdlp|tc+=|9$s&Q_m3$ZIRefw=fqlU!m9AiEfbCvVg|17;U)h=z6Yr?L$(q zn|+Sv7#$2b!z?x0M4}QMTfY=KEH(ymv3#>?hT5^AwVnEYi`1TM?R$X_zR5{19AlT; zS}Gv+H#OSVYY%Bh1J3Johr5AEY22hsx2*Hf_2%ZV-r}2~!^wpn(_c(SLo1e0$PW0Z zwyO8^l3^M=X)+W)K1vHwg+#rIV^H#t=t%bV>>A3s1vf70i>b{ zT$;VKs!n%SCXYFM$;f5Yt`LMz zQeFyst;Z~=XxpgAd1Zg>cqE{87)FyCbDx3sF!y>>l1fn7MFlI-Vo<4=C*FpU%B-4= zJm)`%l33jkr=5-PXu_kuBE8Or5Q-xQW4{yF?bGIR{3He!MMl*499*KYMva2XoR$tQ z2%4~htx<(JAcUYUhA><)?)C@;^n!8!jj6QFx;=WgfaBguzS|-Lzmw|u(524Vr%-&* zNrqIOf6X=8?D?j-g$1#%_F8&@a(#6-ai<~EDpbaKs8A=lzSC^i{6->ukN4H9f+V`g zQL`U1cKXDr*@*n6f}LF7;l7{MywQKkTY`4K^+`MM8s03~Aml5f`RKX)_~?6buRhq_ zXqRxL;;be_hcKk#sCg3zn~J(9Bm)Sz+-<_wty`YaxtDC;+Fok>(C1I^BXnLPm^EpR zZVs3Ptu>xO?=A$~E%poA?5LF(;*xRd*&uQ?t<`RX){_PmfQ~9|(fYW4ewF1mzTLpi zoB!Y|;^<4pS4S8~mU~(X?&q|fAs^(dm_s4s5B#uYP;nB+^ zZNf)nKs8EIEpfAL%p8!@9K;tc*$iOY9ne4*U2yue?5;qg28`GbkE}3 z#ETgq4b3Fx-}evP3xs`3$1N7SJ@W~2a}dq5ip2>A6G9pGodybd92;ivlc9^y?|%%c zx6!hQltN}w3X=!BWRio~6C`3MEVipJft&5|2Dbj+(ODGxq%c0dgjCQwhw@atkL6n!$4dHHS3-`mV@ zX|FtQyVPSIn9K+B*_Fc88Lj5vT7u`IWB1)63V8y1DDU1a`zr5{-GWvxIELh!-JL!E zC6X=Y?}cBME68&flg>MRoA73Yd_u?XcRUH+-%WEZqdZ+|v)7LbB9ul43E6z6sic0& zzt4ZvGxBQ@H&jMO?<>?$jPoR+FX2@+7p&)Fvj6g$8>@GMSo|k>SNc=mQKcN~b%>skl zJ-(_PKcg)onx{A6Hws(QqIZ;V-l@Zh6NTH)9*C5&0K84{{X&-qp?Y`Df1L)hDB|A? z__GNW%@wx8{w|8gPu90Gh}{*mgJjNuEH_sbkPR{_&&2=!P-M^9S(|@%HEkbCHgE z{&WY>ajPEws1h&P)N$vZ&*U2efX`SYd~hZEhU}CXza{h`7}(#Wg=W~Y^Zk_rA!Llg zlX3GA**DJ32DErF9Oj37u4#ieFEVgm7m~i4A4nqi8hEi{EWRy`ewM|6e9mZ8{T=Eh zdDw-QZx8OrZ&l+ZHCwf9Miq!ufe$&LdW(A!Y*N7{hYu^VAKGInQs2-ru4gG_qw#}Y zz_cm@)T=!?sxa;_ipo_M80Xjr<#kG5+(rZBg%6!su0l&g?G2$BBvV*@tlNNL7B5*i zC{6_VO__dTao`_SH2^3MymVolE@UGS-=jPxc>c&f;ViWN$8B0Pon4;ixIA?jW1_~) zR%v_1mG^5Vp35Af@!VJ8%Z;ctabfphqpIVx4ewc?{OYr~lAduZ;mrWeu&hyT;=wWu zDmcbhf9PHoQ1_aRu=9{VE8?}by1>cp%Lbt}GN0u41iLdv<0ELS z>bY+mBGZD@!XDD3!_7Z1XxYqcl4@(7HT$LyaI$X#F0M~K`Idt P_@}9^t5&RPdH=rvJb}E) literal 0 HcmV?d00001 diff --git a/static/fudo.link/apple-touch-icon.png b/static/fudo.link/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1575743587bfd2bb8ca78337d1e8d014a5a20132 GIT binary patch literal 3852 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 U#MNVip>hH9YgVR}#_kdS0;aqNV*mgE literal 0 HcmV?d00001 diff --git a/static/fudo.link/favicon-16x16.png b/static/fudo.link/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..a1a9e16bf570280d11bee4e87d9af01d0d31dd7c GIT binary patch literal 254 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`M?GB}LoEE0QxZ7VCMP7UNJ~ga zNLJwela!E)DnzBXqw5+aL9_Ng-Dy*j~ImX?WvSmeHTz-{(KPxw)K2+I-%@ zY`ZY=kPsGwA3r3FZ``;dR3a0`{OOa8WKC_aWK3+d8}9-g9v_LA*x3&oS1eLGxL}pm z!40*)S*J7{mF#6?T*hUuJkdIc@BFbgZv{yh6F?hQAxvX1wIIfh7KP>~{miLSZ|mu`xnu0cp#Bp7+HRrt4-A~S-7{vi6ZMuR$ z+=O0&%E?Z6;%p?*Qj}l@hJk^Z?|q*c=9^CfCSY9!X%lcVnpOk-0Irh~tj9sx8%^BK zDEh7h8`Tjy?^IB03(&{o{yVA=@IMF?iz*(^0*!`-rnRuyCTX=k$!3e>at{Mtp}q-B zPg|*0kC>XWlFvU94Cc^>aDDq0(6(8r?BjONqpBCAQnv#+Hy8g(K)IYXj$&u$j7;WU z2oMAW0YN|z=t{uhm|=T+g-E2(lhU7_;#lmQbo#FA=Bm1e%QZ_VbUa)EW-^M$FG(bB ze%##ab@2L{CJ;#S^71x30d~8M_xD319ba8Nv$b_fv#Fu$Iu?t};^G{B|1#0&D)oAk zrKM0`>AJ5YD3#*)d`>j&!}y3ul7uWz7+q0h6vZ56W3EtmA{;*H>0W$F1p$-5xJ$FK e7pLPb&%XdBGR?zOBs>xT0000GZ(p1@m(vt>#aV5=4m7pcp>JX6~q!Z|_4#zu9Z+5xv}d?z#6I@3|NEhJ6ob@4fa~ z>)UJZefHYxG8V<6*{W5HKP}7_!`L^BF^gsTJu#lKyS!}Y&YAiI#u~n6ERnb2B%ETp zoIkv#BaHpPvzuoGn29D>T7_3Id=*-+&!X+yaW^jycg~#?&zcJt@SwODH`CL-w)dv< z!;2Tu_T~*vEME@mvSldPx)qJJwKy3YtEms--~96BytcWUH{+*$`{tE-<4t8m-{e8> ztF5>4#ZFJ-*ZTUkCagX2E9CZbJ0}OfW@qE-!Gl5d;n}C3cRh1J&m0uAUKfP`3Td6O z@cb>$v_7e%ufAjKdya+O4<1HhZYQ2xCWW_=eQWN^1id|F3nk{Byybh~d#fp!HNAL& zzRphA+uHDVOAG#KZN*~Uhs3^l_APguyE-m%hH|^(RgM%pD zv&UzfVAr{x_IAOa_A#|S9$mhS=arSJZPe&})hYZRl$5CUow;rue!qTQwT&+xYUNt_ ztM#F$PV%3&KY!z|2V*r_iND037Qn@7f6D&z=+Y&_*H*Q8M|CMHSI+Lge^gg5$o}d} zhQ_)&@$KLy%XOc-S6L{`plV-`Re!R}`UvsU1u&oFn1*0X z3%6}URaO>mWoF`+)KuI^PDa_WV<<~X!j0p{QEs)OA|*xC-OR|qt<$Gb#m_Xi&z{Ad z++5tt&qqx`0cr{hVcW1F)i z#eM8=SFWJ_?c3QiFWvK+B|q^cJ0ff;O-)Uqc-Jn!@}#U-fohu#pFe(tn~x9aXO<3R z|70hW$R^Hj*`i67q?j1o=k`yw<7PZ#zc~{ zeW0fYEj%7PFCif;^`MyZD(*Y1OiL5~!fS^PfqZh;4j&fp^xp-FN2hqZV8&I_n7gM) z{3ZS&w1G&zgB0-uA;ik)^;E<{&|Q24>flu3I4Bk(!gO$w%pCJLib0tZ#}V%F1hero OitHr6OcH@$5%?d)HmY0z 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