commit 5a0afc48d4ce858cc2f12b5fa6f4482ee50a92fa Author: Sebastien Bourdeauducq Date: Sat Jun 20 17:54:21 2020 +0800 import from nix-scripts diff --git a/nixbld-etc-nixos/backup-module.nix b/nixbld-etc-nixos/backup-module.nix new file mode 100644 index 0000000..4688c8c --- /dev/null +++ b/nixbld-etc-nixos/backup-module.nix @@ -0,0 +1,54 @@ +{ config, pkgs, lib, ... }: +with lib; +let + makeBackup = pkgs.writeScript "make-backup" '' + #!${pkgs.bash}/bin/bash + + set -e + umask 0077 + + DBDUMPDIR=`mktemp -d` + pushd $DBDUMPDIR + + ${config.services.mysql.package}/bin/mysqldump --single-transaction flarum > flarum.sql + ${pkgs.sudo}/bin/sudo -u mattermost ${config.services.postgresql.package}/bin/pg_dump mattermost > mattermost.sql + + ${pkgs.gnutar}/bin/tar cf - --exclude "/var/lib/gitea/repositories/*/*.git/archives" /etc/nixos /var/lib/gitea flarum.sql mattermost.sql | \ + ${pkgs.bzip2}/bin/bzip2 | \ + ${pkgs.gnupg}/bin/gpg --symmetric --batch --passphrase-file /etc/nixos/secret/backup-passphrase | \ + ${pkgs.rclone}/bin/rclone rcat --config /etc/nixos/secret/rclone.conf dropbox:backup-`date +%F`.tar.bz2.gpg + + popd + rm -rf $DBDUMPDIR + + echo Backup done + ''; + cfg = config.services.mlabs-backup; +in +{ + options.services.mlabs-backup = { + enable = mkOption { + type = types.bool; + default = false; + description = "Enable backups"; + }; + }; + + config = mkIf cfg.enable { + systemd.services.mlabs-backup = { + description = "M-Labs backup"; + serviceConfig = { + Type = "oneshot"; + User = "root"; + Group = "root"; + ExecStart = "${makeBackup}"; + }; + }; + + systemd.timers.mlabs-backup = { + description = "M-Labs backup"; + wantedBy = [ "timers.target" ]; + timerConfig.OnCalendar = "weekly"; + }; + }; +} diff --git a/nixbld-etc-nixos/configuration.nix b/nixbld-etc-nixos/configuration.nix new file mode 100644 index 0000000..73217cb --- /dev/null +++ b/nixbld-etc-nixos/configuration.nix @@ -0,0 +1,667 @@ +# Edit this configuration file to define what should be installed on +# your system. Help is available in the configuration.nix(5) man page +# and in the NixOS manual (accessible by running ‘nixos-help’). + +{ config, pkgs, ... }: + +let + netifWan = "enp0s31f6"; + netifLan = "enp3s0"; + netifWifi = "wlp1s0"; + netifSit = "henet0"; + hydraWwwOutputs = "/var/www/hydra-outputs"; +in +{ + imports = + [ + ./hardware-configuration.nix + ./homu/nixos-module.nix + ./backup-module.nix + (builtins.fetchTarball { + url = "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/archive/v2.3.0/nixos-mailserver-v2.3.0.tar.gz"; + sha256 = "0lpz08qviccvpfws2nm83n7m2r8add2wvfg9bljx9yxx8107r919"; + }) + ]; + + # Use the systemd-boot EFI boot loader. + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + boot.blacklistedKernelModules = ["iwlwifi"]; + + security.apparmor.enable = true; + + networking = { + hostName = "nixbld"; + firewall = { + allowedTCPPorts = [ 80 443 ]; + allowedUDPPorts = [ 53 67 ]; + trustedInterfaces = [ netifLan ]; + }; + interfaces."${netifLan}" = { + ipv4.addresses = [{ + address = "192.168.1.1"; + prefixLength = 24; + }]; + ipv6.addresses = [{ + address = "2001:470:f821:1::"; + prefixLength = 64; + }]; + }; + interfaces."${netifWifi}" = { + ipv4.addresses = [{ + address = "192.168.12.1"; + prefixLength = 24; + }]; + ipv6.addresses = [{ + address = "2001:470:f821:2::"; + prefixLength = 64; + }]; + }; + nat = { + enable = true; + externalInterface = netifWan; + internalInterfaces = [ netifLan netifWifi ]; + forwardPorts = [ + { sourcePort = 2201; destination = "192.168.1.201:22"; proto = "tcp"; } + { sourcePort = 2202; destination = "192.168.1.202:22"; proto = "tcp"; } + { sourcePort = 2203; destination = "192.168.1.203:22"; proto = "tcp"; } + { sourcePort = 2204; destination = "192.168.1.204:22"; proto = "tcp"; } + { sourcePort = 2205; destination = "192.168.1.205:22"; proto = "tcp"; } + ]; + extraCommands = '' + iptables -w -N block-lan-from-wifi + iptables -w -A block-lan-from-wifi -i ${netifLan} -o ${netifWifi} -j DROP + iptables -w -A block-lan-from-wifi -i ${netifWifi} -o ${netifLan} -j DROP + iptables -w -A FORWARD -j block-lan-from-wifi + ''; + extraStopCommands = '' + iptables -w -D FORWARD -j block-lan-from-wifi 2>/dev/null|| true + iptables -w -F block-lan-from-wifi 2>/dev/null|| true + iptables -w -X block-lan-from-wifi 2>/dev/null|| true + ''; + }; + sits."${netifSit}" = { + dev = netifWan; + remote = "216.218.221.6"; + local = "42.200.147.171"; + ttl = 255; + }; + interfaces."${netifSit}".ipv6 = { + addresses = [{ address = "2001:470:18:629::2"; prefixLength = 64; }]; + routes = [{ address = "::"; prefixLength = 0; }]; + }; + }; + boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = "1"; + boot.kernel.sysctl."net.ipv6.conf.default.forwarding" = "1"; + + services.unbound = { + enable = true; + extraConfig = + '' + server: + port: 5353 + ''; + }; + + services.hostapd = { + enable = true; + interface = netifWifi; + hwMode = "g"; + ssid = "M-Labs"; + wpaPassphrase = (import /etc/nixos/secret/wifi_password.nix); + extraConfig = '' + ieee80211d=1 + country_code=HK + ieee80211n=1 + wmm_enabled=1 + auth_algs=1 + wpa_key_mgmt=WPA-PSK + rsn_pairwise=CCMP + ''; + }; + services.dnsmasq = { + enable = true; + servers = ["::1#5353"]; + extraConfig = '' + interface=${netifLan} + interface=${netifWifi} + bind-interfaces + dhcp-range=interface:${netifLan},192.168.1.81,192.168.1.254,24h + dhcp-range=interface:${netifWifi},192.168.12.10,192.168.12.254,24h + enable-ra + dhcp-range=interface:${netifLan},::,constructor:${netifLan},ra-names + dhcp-range=interface:${netifWifi},::,constructor:${netifWifi},ra-only + + no-resolv + + # Static IPv4s to make Red Pitayas less annoying + dhcp-host=rp-f05cc9,192.168.1.190 + dhcp-host=rp-f0612e,192.168.1.191 + # Static IPv4s to make port redirections work + dhcp-host=rpi-1,192.168.1.201 + dhcp-host=rpi-2,192.168.1.202 + dhcp-host=rpi-3,192.168.1.203 + dhcp-host=rpi-4,192.168.1.204 + dhcp-host=rpi-5,192.168.1.205 + + # Default IP addresses for ARTIQ boards + address=/thermostat/192.168.1.26 + address=/kc705/192.168.1.50 + address=/zc706/192.168.1.51 + address=/zc706-2/192.168.1.52 + address=/sayma/192.168.1.60 + address=/metlino/192.168.1.65 + address=/kasli/192.168.1.70 + address=/kasli-customer/192.168.1.75 + address=/stabilizer-customer/192.168.1.76 + # uTCA MCH from NAT + address=/tschernobyl/192.168.1.80 + ''; + }; + + # Select internationalisation properties. + i18n.defaultLocale = "en_US.UTF-8"; + console = { + font = "Lat2-Terminus16"; + keyMap = "de"; + }; + + # Set your time zone. + time.timeZone = "Asia/Hong_Kong"; + + # List packages installed in system profile. To search, run: + # $ nix search wget + environment.systemPackages = with pkgs; [ + wget vim git file lm_sensors acpi pciutils psmisc telnet nixops + irssi tmux usbutils imagemagick jq zip unzip + ]; + + # Some programs need SUID wrappers, can be configured further or are + # started in user sessions. + # programs.mtr.enable = true; + # programs.gnupg.agent = { enable = true; enableSSHSupport = true; }; + + # List services that you want to enable: + + services.apcupsd.enable = true; + services.apcupsd.configText = '' + UPSTYPE usb + NISIP 127.0.0.1 + BATTERYLEVEL 10 + MINUTES 5 + ''; + + # Enable the OpenSSH daemon. + services.openssh.enable = true; + services.openssh.forwardX11 = true; + services.openssh.passwordAuthentication = false; + programs.mosh.enable = true; + + programs.fish.enable = true; + + # Enable CUPS to print documents. + services.avahi.enable = true; + services.avahi.interfaces = [ netifLan ]; + services.avahi.publish.enable = true; + services.avahi.publish.userServices = true; + nixpkgs.config.allowUnfree = true; + services.printing.enable = true; + services.printing.drivers = [ pkgs.hplipWithPlugin ]; + services.printing.browsing = true; + services.printing.listenAddresses = [ "*:631" ]; + services.printing.defaultShared = true; + hardware.sane.enable = true; + hardware.sane.extraBackends = [ pkgs.hplipWithPlugin ]; + + users.extraGroups.plugdev = { }; + users.extraUsers.sb = { + isNormalUser = true; + extraGroups = ["wheel" "plugdev" "dialout" "lp" "scanner"]; + shell = pkgs.fish; + openssh.authorizedKeys.keys = [ + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCyPk5WyFoWSvF4ozehxcVBoZ+UHgrI7VW/OoQfFFwIQe0qvetUZBMZwR2FwkLPAMZV8zz1v4EfncudEkVghy4P+/YVLlDjqDq9zwZnh8Nd/ifu84wmcNWHT2UcqnhjniCdshL8a44memzABnxfLLv+sXhP2x32cJAamo5y6fukr2qLp2jbXzR+3sv3klE0ruUXis/BR1lLqNJEYP8jB6fLn2sLKinnZPfn6DwVOk10mGeQsdME/eGl3phpjhODH9JW5V2V5nJBbC0rBnq+78dyArKVqjPSmIcSy72DEIpTctnMEN1W34BGrnsDd5Xd/DKxKxHKTMCHtZRwLC2X0NWN" + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCMALVC8RDTHec+PC8y1s3tcpUAODgq6DEzQdHDf/cyvDMfmCaPiMxfIdmkns5lMa03hymIfSmLUF0jFFDc7biRp7uf9AAXNsrTmplHii0l0McuOOZGlSdZM4eL817P7UwJqFMxJyFXDjkubhQiX6kp25Kfuj/zLnupRCaiDvE7ho/xay6Jrv0XLz935TPDwkc7W1asLIvsZLheB+sRz9SMOb9gtrvk5WXZl5JTOFOLu+JaRwQLHL/xdcHJTOod7tqHYfpoC5JHrEwKzbhTOwxZBQBfTQjQktKENQtBxXHTe71rUEWfEZQGg60/BC4BrRmh4qJjlJu3v4VIhC7SSHn1" + ]; + }; + users.extraUsers.rj = { + isNormalUser = true; + extraGroups = ["wheel" "plugdev" "dialout"]; + }; + users.extraUsers.astro = { + isNormalUser = true; + extraGroups = ["plugdev" "dialout"]; + shell = pkgs.bashInteractive; + }; + users.extraUsers.nix = { + isNormalUser = true; + }; + security.sudo.wheelNeedsPassword = false; + security.hideProcessInformation = true; + boot.kernel.sysctl."kernel.dmesg_restrict" = true; + services.udev.packages = [ pkgs.sane-backends ]; + + nix.distributedBuilds = true; + nix.buildMachines = [ + { + hostName = "localhost"; + maxJobs = 4; + system = "x86_64-linux"; + supportedFeatures = ["big-parallel"]; + } + { + hostName = "rpi-3"; + sshUser = "nix"; + sshKey = "/etc/nixos/secret/nix_id_rsa"; + maxJobs = 1; + system = "aarch64-linux"; + } + ]; + services.hydra = { + enable = true; + package = pkgs.hydra-unstable; + useSubstitutes = true; + hydraURL = "https://nixbld.m-labs.hk"; + notificationSender = "hydra@m-labs.hk"; + minimumDiskFree = 15; # in GB + minimumDiskFreeEvaluator = 1; + extraConfig = + '' + binary_cache_secret_key_file = /etc/nixos/secret/nixbld.m-labs.hk-1 + max_output_size = 10000000000 + + + job = web:web:web + command = [ $(jq '.buildStatus' < $HYDRA_JSON) = 0 ] && ln -sfn $(jq -r '.outputs[0].path' < $HYDRA_JSON) ${hydraWwwOutputs}/web + + + + job = artiq:full:sipyco-manual-html + command = [ $(jq '.buildStatus' < $HYDRA_JSON) = 0 ] && ln -sfn $(jq -r '.outputs[0].path' < $HYDRA_JSON) ${hydraWwwOutputs}/sipyco-manual-html + + + job = artiq:full:sipyco-manual-latexpdf + command = [ $(jq '.buildStatus' < $HYDRA_JSON) = 0 ] && ln -sfn $(jq -r '.outputs[0].path' < $HYDRA_JSON) ${hydraWwwOutputs}/sipyco-manual-latexpdf + + + + job = artiq:full-beta:artiq-manual-html + command = [ $(jq '.buildStatus' < $HYDRA_JSON) = 0 ] && ln -sfn $(jq -r '.outputs[0].path' < $HYDRA_JSON) ${hydraWwwOutputs}/artiq-manual-html-beta + + + job = artiq:full-beta:artiq-manual-latexpdf + command = [ $(jq '.buildStatus' < $HYDRA_JSON) = 0 ] && ln -sfn $(jq -r '.outputs[0].path' < $HYDRA_JSON) ${hydraWwwOutputs}/artiq-manual-latexpdf-beta + + + job = artiq:full-beta:conda-channel + command = [ $(jq '.buildStatus' < $HYDRA_JSON) = 0 ] && ln -sfn $(jq -r '.outputs[0].path' < $HYDRA_JSON) ${hydraWwwOutputs}/artiq-conda-channel-beta + + + + job = artiq:full:artiq-manual-html + command = [ $(jq '.buildStatus' < $HYDRA_JSON) = 0 ] && ln -sfn $(jq -r '.outputs[0].path' < $HYDRA_JSON) ${hydraWwwOutputs}/artiq-manual-html + + + job = artiq:full:artiq-manual-latexpdf + command = [ $(jq '.buildStatus' < $HYDRA_JSON) = 0 ] && ln -sfn $(jq -r '.outputs[0].path' < $HYDRA_JSON) ${hydraWwwOutputs}/artiq-manual-latexpdf + + + job = artiq:full:conda-channel + command = [ $(jq '.buildStatus' < $HYDRA_JSON) = 0 ] && ln -sfn $(jq -r '.outputs[0].path' < $HYDRA_JSON) ${hydraWwwOutputs}/artiq-conda-channel + + ''; + }; + systemd.services.hydra-www-outputs-init = { + description = "Set up a hydra-owned directory for build outputs"; + wantedBy = [ "multi-user.target" ]; + requiredBy = [ "hydra-queue-runner.service" ]; + before = [ "hydra-queue-runner.service" ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = [ "${pkgs.coreutils}/bin/mkdir -p ${hydraWwwOutputs}" "${pkgs.coreutils}/bin/chown hydra-queue-runner:hydra ${hydraWwwOutputs}" ]; + }; + }; + + + nix.extraOptions = '' + secret-key-files = /etc/nixos/secret/nixbld.m-labs.hk-1 + ''; + nix.sandboxPaths = ["/opt"]; + + services.munin-node.enable = true; + services.munin-cron = { + enable = true; + hosts = '' + [${config.networking.hostName}] + address localhost + ''; + }; + services.mlabs-backup.enable = true; + + services.gitea = { + enable = true; + httpPort = 3001; + rootUrl = "https://git.m-labs.hk/"; + appName = "M-Labs Git"; + cookieSecure = true; + disableRegistration = true; + mailerPasswordFile = "/etc/nixos/secret/mailerpassword"; + extraConfig = + '' + [mailer] + ENABLED = true + HOST = ssl.serverraum.org:587 + FROM = sysop@m-labs.hk + USER = sysop@m-labs.hk + + [service] + ENABLE_NOTIFY_MAIL = true + + [attachment] + ALLOWED_TYPES = */* + ''; + }; + systemd.tmpfiles.rules = [ + "L+ '${config.services.gitea.stateDir}/custom/templates/home.tmpl' - - - - ${./gitea-home.tmpl}" + ]; + + services.mattermost = { + enable = true; + siteUrl = "https://chat.m-labs.hk/"; + mutableConfig = true; + }; + + services.matterbridge = { + enable = true; + configPath = "/etc/nixos/secret/matterbridge.toml"; + }; + + nixpkgs.config.packageOverrides = super: let self = super.pkgs; in { + hydra-unstable = super.hydra-unstable.overrideAttrs(oa: { + patches = oa.patches or [] ++ [ ./hydra-conda.patch ./hydra-retry.patch ./hydra-unbreak-sysbuild.patch ]; + hydraPath = oa.hydraPath + ":" + super.lib.makeBinPath [ super.jq ]; + }); + matterbridge = super.matterbridge.overrideAttrs(oa: { + patches = oa.patches or [] ++ [ ./matterbridge-disable-github.patch ]; + }); + }; + + security.acme.acceptTerms = true; + security.acme.email = "sb" + "@m-labs.hk"; + security.acme.certs = { + "nixbld.m-labs.hk" = { + group = "nginx"; + user = "nginx"; + webroot = "/var/lib/acme/acme-challenge"; + extraDomains = { + "m-labs.hk" = null; + "www.m-labs.hk" = null; + "conda.m-labs.hk" = null; + "lab.m-labs.hk" = null; + "git.m-labs.hk" = null; + "chat.m-labs.hk" = null; + "hooks.m-labs.hk" = null; + "forum.m-labs.hk" = null; + "perso.m-labs.hk" = null; + "nmigen.org" = null; + "www.nmigen.org" = null; + + "openhardware.hk" = null; + "git.openhardware.hk" = null; + }; + }; + }; + services.nginx = { + enable = true; + recommendedProxySettings = true; + recommendedGzipSettings = true; + virtualHosts = let + mainWebsite = { + addSSL = true; + useACMEHost = "nixbld.m-labs.hk"; + root = "${hydraWwwOutputs}/web"; + extraConfig = '' + error_page 404 /404.html; + ''; + locations."^~ /fonts/".extraConfig = '' + expires 60d; + ''; + locations."^~ /js/".extraConfig = '' + expires 60d; + ''; + locations."/MathJax/" = { + alias = "/var/www/MathJax/"; + extraConfig = '' + expires 60d; + ''; + }; + + # legacy URLs, redirect to avoid breaking people's bookmarks + locations."/gateware.html".extraConfig = '' + return 301 /gateware/migen/; + ''; + locations."/migen".extraConfig = '' + return 301 /gateware/migen/; + ''; + locations."/artiq".extraConfig = '' + return 301 /experiment-control/artiq/; + ''; + locations."/artiq/resources.html".extraConfig = '' + return 301 /experiment-control/resources/; + ''; + + # autogenerated manuals + locations."/artiq/sipyco-manual/" = { + alias = "${hydraWwwOutputs}/sipyco-manual-html/share/doc/sipyco-manual/html/"; + }; + locations."=/artiq/sipyco-manual.pdf" = { + alias = "${hydraWwwOutputs}/sipyco-manual-latexpdf/share/doc/sipyco-manual/SiPyCo.pdf"; + }; + locations."/artiq/manual-beta/" = { + alias = "${hydraWwwOutputs}/artiq-manual-html-beta/share/doc/artiq-manual/html/"; + }; + locations."=/artiq/manual-beta.pdf" = { + alias = "${hydraWwwOutputs}/artiq-manual-latexpdf-beta/share/doc/artiq-manual/ARTIQ.pdf"; + }; + locations."/artiq/manual/" = { + alias = "${hydraWwwOutputs}/artiq-manual-html/share/doc/artiq-manual/html/"; + }; + locations."=/artiq/manual.pdf" = { + alias = "${hydraWwwOutputs}/artiq-manual-latexpdf/share/doc/artiq-manual/ARTIQ.pdf"; + }; + + # legacy content + locations."/migen/manual/" = { + alias = "/var/www/m-labs.hk.old/migen/manual/"; + }; + locations."/artiq/manual-release-4/" = { + alias = "/var/www/m-labs.hk.old/artiq/manual-release-4/"; + }; + locations."/artiq/manual-release-3/" = { + alias = "/var/www/m-labs.hk.old/artiq/manual-release-3/"; + }; + locations."/artiq/manual-release-2/" = { + alias = "/var/www/m-labs.hk.old/artiq/manual-release-2/"; + }; + }; + in { + "m-labs.hk" = mainWebsite; + "www.m-labs.hk" = mainWebsite; + "lab.m-labs.hk" = { + addSSL = true; + useACMEHost = "nixbld.m-labs.hk"; + locations."/munin/".alias = "/var/www/munin/"; + locations."/munin".extraConfig = '' + auth_basic "Munin"; + auth_basic_user_file /etc/nixos/secret/muninpasswd; + ''; + locations."/homu/".proxyPass = "http://127.0.0.1:54856/"; + }; + "nixbld.m-labs.hk" = { + forceSSL = true; + useACMEHost = "nixbld.m-labs.hk"; + locations."/".proxyPass = "http://127.0.0.1:3000"; + }; + "conda.m-labs.hk" = { + forceSSL = true; + useACMEHost = "nixbld.m-labs.hk"; + locations."/artiq-beta/" = { + alias = "${hydraWwwOutputs}/artiq-conda-channel-beta/"; + extraConfig = '' + autoindex on; + index bogus_index_file; + ''; + }; + locations."/artiq/" = { + alias = "${hydraWwwOutputs}/artiq-conda-channel/"; + extraConfig = '' + autoindex on; + index bogus_index_file; + ''; + }; + }; + "git.m-labs.hk" = { + forceSSL = true; + useACMEHost = "nixbld.m-labs.hk"; + locations."/".proxyPass = "http://127.0.0.1:3001"; + extraConfig = '' + client_max_body_size 300M; + ''; + }; + "chat.m-labs.hk" = { + forceSSL = true; + useACMEHost = "nixbld.m-labs.hk"; + locations."/".proxyPass = "http://127.0.0.1:8065"; + locations."~ /api/v[0-9]+/(users/)?websocket$".proxyPass = "http://127.0.0.1:8065"; + locations."~ /api/v[0-9]+/(users/)?websocket$".proxyWebsockets = true; + }; + "hooks.m-labs.hk" = { + forceSSL = true; + useACMEHost = "nixbld.m-labs.hk"; + locations."/mattermost-github".extraConfig = '' + include ${pkgs.nginx}/conf/uwsgi_params; + uwsgi_pass unix:${config.services.uwsgi.runDir}/uwsgi-mgi.sock; + ''; + locations."/rfq".extraConfig = '' + include ${pkgs.nginx}/conf/uwsgi_params; + uwsgi_pass unix:${config.services.uwsgi.runDir}/uwsgi-rfq.sock; + ''; + }; + "forum.m-labs.hk" = { + forceSSL = true; + useACMEHost = "nixbld.m-labs.hk"; + root = "/var/www/flarum/public"; + locations."~ \.php$".extraConfig = '' + fastcgi_pass unix:${config.services.phpfpm.pools.flarum.socket}; + fastcgi_index index.php; + ''; + extraConfig = '' + index index.php; + include /var/www/flarum/.nginx.conf; + ''; + }; + "perso.m-labs.hk" = { + addSSL = true; + useACMEHost = "nixbld.m-labs.hk"; + root = "/var/www/perso"; + }; + "nmigen.org" = { + addSSL = true; + useACMEHost = "nixbld.m-labs.hk"; + locations."/".extraConfig = '' + return 307 https://m-labs.hk/gateware/nmigen/; + ''; + }; + "www.nmigen.org" = { + addSSL = true; + useACMEHost = "nixbld.m-labs.hk"; + locations."/".extraConfig = '' + return 307 https://m-labs.hk/gateware/nmigen/; + ''; + }; + + "git.openhardware.hk" = { + forceSSL = true; + useACMEHost = "nixbld.m-labs.hk"; + locations."/".proxyPass = "http://127.0.0.1:3002"; + extraConfig = '' + client_max_body_size 300M; + ''; + }; + }; + }; + services.uwsgi = { + enable = true; + plugins = [ "python3" ]; + instance = { + type = "emperor"; + vassals = { + mattermostgithub = import ./mattermost-github-integration/uwsgi-config.nix { inherit config pkgs; }; + rfq = import ./rfq/uwsgi-config.nix { inherit config pkgs; }; + }; + }; + }; + services.mysql = { + enable = true; + package = pkgs.mariadb; + }; + services.phpfpm.pools.flarum = { + user = "nobody"; + settings = { + "listen.owner" = "nginx"; + "listen.group" = "nginx"; + "listen.mode" = "0600"; + "pm" = "dynamic"; + "pm.max_children" = 5; + "pm.start_servers" = 2; + "pm.min_spare_servers" = 1; + "pm.max_spare_servers" = 3; + "pm.max_requests" = 500; + }; + }; + + services.homu = { + enable = true; + config = "/etc/nixos/secret/homu.toml"; + }; + + mailserver = { + enable = true; + localDnsResolver = false; # conflicts with dnsmasq + # Some mail servers do reverse DNS lookups to filter spam. + # Getting a proper reverse DNS record from ISP is difficult, so use whatever already exists. + fqdn = "42-200-147-171.static.imsbiz.com"; + domains = [ "nmigen.org" ]; + loginAccounts = (import /etc/nixos/secret/email_accounts.nix); + certificateScheme = 3; + }; + security.acme.certs."${config.mailserver.fqdn}".extraDomains = { + "mail.nmigen.org" = null; + }; + + containers.openhardwarehk = { + autoStart = true; + config = + { config, pkgs, ... }: + { + services.gitea = { + enable = true; + httpPort = 3002; + rootUrl = "https://git.openhardware.hk/"; + appName = "Open Hardware HK"; + cookieSecure = true; + disableRegistration = true; + extraConfig = + '' + [attachment] + ALLOWED_TYPES = */* + ''; + }; + }; + }; + + # This value determines the NixOS release with which your system is to be + # compatible, in order to avoid breaking some software such as database + # servers. You should change this only after NixOS release notes say you + # should. + system.stateVersion = "18.09"; # Did you read the comment? +} diff --git a/nixbld-etc-nixos/gitea-home.tmpl b/nixbld-etc-nixos/gitea-home.tmpl new file mode 100644 index 0000000..e70c4f4 --- /dev/null +++ b/nixbld-etc-nixos/gitea-home.tmpl @@ -0,0 +1,23 @@ +{{template "base/head" .}} +
+
+
+
+ +
+
+

+ {{AppName}} +

+
+
+
+
+
+

+ Welcome! This Gitea instance is here to support projects related to M-Labs. You may want to browse the M-Labs organization where many projects are located. If you would like an account (we give them to anyone who wants to contribute on projects related to Sinara, ARTIQ, nMigen, etc.), simply write a short email to sb@m-***.hk stating the username you would like to have. +

+
+
+
+{{template "base/footer" .}} diff --git a/nixbld-etc-nixos/homu/disable-ssh-host-keycheck.patch b/nixbld-etc-nixos/homu/disable-ssh-host-keycheck.patch new file mode 100644 index 0000000..ef39ad9 --- /dev/null +++ b/nixbld-etc-nixos/homu/disable-ssh-host-keycheck.patch @@ -0,0 +1,13 @@ +diff --git a/homu/git_helper.py b/homu/git_helper.py +index 0f70c69..f53fb57 100755 +--- a/homu/git_helper.py ++++ b/homu/git_helper.py +@@ -7,7 +7,7 @@ SSH_KEY_FILE = os.path.join(os.path.dirname(__file__), '../cache/key') + + + def main(): +- args = ['ssh', '-i', SSH_KEY_FILE, '-S', 'none'] + sys.argv[1:] ++ args = ['ssh', '-o', 'StrictHostKeyChecking=no', '-i', SSH_KEY_FILE, '-S', 'none'] + sys.argv[1:] + os.execvp('ssh', args) + + diff --git a/nixbld-etc-nixos/homu/nixos-module.nix b/nixbld-etc-nixos/homu/nixos-module.nix new file mode 100644 index 0000000..3f5574d --- /dev/null +++ b/nixbld-etc-nixos/homu/nixos-module.nix @@ -0,0 +1,52 @@ +{ config, pkgs, lib, ... }: +with lib; +let + homu = pkgs.callPackage ./pkg.nix {}; + cfg = config.services.homu; +in + +{ + options.services.homu = { + enable = mkOption { + type = types.bool; + default = false; + description = "Enable the bot"; + }; + dbDir = mkOption { + type = types.str; + default = "/var/db/homu"; + description = "Path to the database file (use the same path in config.toml)"; + }; + config = mkOption { + description = "Location of config.toml"; + type = types.str; + }; + }; + + config = mkIf cfg.enable { + users.users.homu = { + group = "homu"; + home = cfg.dbDir; + createHome = true; + }; + users.groups.homu = {}; + + systemd.services.homu = { + description = "Homu bot"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + Type = "simple"; + ExecStart = "${homu}/bin/homu -c ${cfg.config}"; + + Restart = "always"; + RestartSec = "5sec"; + + User = "homu"; + Group = "homu"; + }; + }; + }; + +} + diff --git a/nixbld-etc-nixos/homu/patch-cache-directory.patch b/nixbld-etc-nixos/homu/patch-cache-directory.patch new file mode 100644 index 0000000..0cf2c90 --- /dev/null +++ b/nixbld-etc-nixos/homu/patch-cache-directory.patch @@ -0,0 +1,26 @@ +diff --git a/homu/git_helper.py b/homu/git_helper.py +index 0f70c69..732230c 100755 +--- a/homu/git_helper.py ++++ b/homu/git_helper.py +@@ -3,7 +3,7 @@ + import sys + import os + +-SSH_KEY_FILE = os.path.join(os.path.dirname(__file__), '../cache/key') ++SSH_KEY_FILE = os.path.expanduser("~/cache/key") + + + def main(): +diff --git a/homu/main.py b/homu/main.py +index 16b60a2..a2e109a 100644 +--- a/homu/main.py ++++ b/homu/main.py +@@ -649,7 +649,7 @@ def git_push(git_cmd, branch, state): + + + def init_local_git_cmds(repo_cfg, git_cfg): +- fpath = 'cache/{}/{}'.format(repo_cfg['owner'], repo_cfg['name']) ++ fpath = '{}/cache/{}/{}'.format(os.path.expanduser("~"), repo_cfg['owner'], repo_cfg['name']) + url = 'git@github.com:{}/{}.git'.format(repo_cfg['owner'], repo_cfg['name']) # noqa + + if not os.path.exists(SSH_KEY_FILE): diff --git a/nixbld-etc-nixos/homu/pkg.nix b/nixbld-etc-nixos/homu/pkg.nix new file mode 100644 index 0000000..43cb2a0 --- /dev/null +++ b/nixbld-etc-nixos/homu/pkg.nix @@ -0,0 +1,34 @@ +{ python3Packages, python3, fetchFromGitHub, git, openssh }: + +let + uritemplate_0_2_0 = python3Packages.github3_py.overrideAttrs(oa: rec { + version = "0.2.0"; + src = python3Packages.fetchPypi { + pname = "uritemplate.py"; + inherit version; + sha256 = "1pfk04pmnysz0383lwzgig8zqlwiv2n4pmq51f0mc60zz1jimq4g"; + }; + }); + github3_py_0_9_6 = python3Packages.github3_py.overrideAttrs(oa: rec { + version = "0.9.6"; + src = python3Packages.fetchPypi { + pname = "github3.py"; + inherit version; + sha256 = "1i8xnh586z4kka7pjl7cy08fmzjs14c8jdp8ykb9jjpzsy2xncdq"; + }; + propagatedBuildInputs = [ python3Packages.requests uritemplate_0_2_0 ]; + }); +in + python3Packages.buildPythonApplication { + name = "homu"; + src = fetchFromGitHub { + owner = "servo"; + repo = "homu"; + rev = "2ea53e76ebac3e5fa11bc39054b3cd4c42eff607"; + sha256 = "1ih7s8zfbpq0qb9vqbxzr0r4s9ff52l4ipr916kwbck3ygliq3r9"; + }; + patches = [ ./patch-cache-directory.patch ./disable-ssh-host-keycheck.patch ]; + postInstall = "chmod 755 $out/${python3.sitePackages}/homu/git_helper.py"; + propagatedBuildInputs = [ github3_py_0_9_6 git openssh ] ++ (with python3Packages; [ toml jinja2 requests bottle waitress retrying ]); + checkPhase = "python -m unittest discover tests -v"; + } diff --git a/nixbld-etc-nixos/hydra-conda.patch b/nixbld-etc-nixos/hydra-conda.patch new file mode 100644 index 0000000..606cdcf --- /dev/null +++ b/nixbld-etc-nixos/hydra-conda.patch @@ -0,0 +1,47 @@ +commit 5aa5f8d5742883d41d7278a2c8bc2c9a2ddfef45 +Author: Sebastien Bourdeauducq +Date: Sun Apr 14 18:25:27 2019 +0800 + + add SVG icon for conda package + +diff --git a/src/root/product-list.tt b/src/root/product-list.tt +index 298d0a66..85914bbd 100644 +--- a/src/root/product-list.tt ++++ b/src/root/product-list.tt +@@ -157,6 +157,11 @@ + DEB + + Debian package ++ [% CASE "conda" %] ++ ++ Conda ++ ++ Conda package + [% CASE "iso" %] + + ISO +diff --git a/src/root/static/images/conda.svg b/src/root/static/images/conda.svg +new file mode 100644 +index 00000000..67859731 +--- /dev/null ++++ b/src/root/static/images/conda.svg +@@ -0,0 +1,18 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +\ No newline at end of file diff --git a/nixbld-etc-nixos/hydra-retry.patch b/nixbld-etc-nixos/hydra-retry.patch new file mode 100644 index 0000000..4221ced --- /dev/null +++ b/nixbld-etc-nixos/hydra-retry.patch @@ -0,0 +1,19 @@ +commit 86bf81c0b8a51bffa4b4b566e1caaac6f0e041d3 +Author: Sebastien Bourdeauducq +Date: Thu Mar 14 17:45:32 2019 +0800 + + add option to disable retries on transient failures + +diff --git a/src/hydra-queue-runner/build-remote.cc b/src/hydra-queue-runner/build-remote.cc +index 69c430eb..bdbc808d 100644 +--- a/src/hydra-queue-runner/build-remote.cc ++++ b/src/hydra-queue-runner/build-remote.cc +@@ -344,7 +344,7 @@ void State::buildRemote(ref destStore, + break; + case BuildResult::TransientFailure: + result.stepStatus = bsFailed; +- result.canRetry = true; ++ result.canRetry = get(step->drv->env, "__hydraRetry").value_or("1") == "1"; + result.errorMsg = ""; + break; + case BuildResult::TimedOut: diff --git a/nixbld-etc-nixos/hydra-unbreak-sysbuild.patch b/nixbld-etc-nixos/hydra-unbreak-sysbuild.patch new file mode 100644 index 0000000..31a96b7 --- /dev/null +++ b/nixbld-etc-nixos/hydra-unbreak-sysbuild.patch @@ -0,0 +1,25 @@ +diff --git a/src/lib/Hydra/Schema/Builds.pm b/src/lib/Hydra/Schema/Builds.pm +index d4334300..014d07ce 100644 +--- a/src/lib/Hydra/Schema/Builds.pm ++++ b/src/lib/Hydra/Schema/Builds.pm +@@ -608,6 +608,7 @@ makeQueries('', ""); + makeQueries('ForProject', "and project = ?"); + makeQueries('ForJobset', "and jobset_id = ?"); + makeQueries('ForJob', "and jobset_id = ? and job = ?"); ++makeQueries('ForJobName', "and jobset_id = (select id from jobsets j where j.name = ?) and job = ?"); + + + my %hint = ( +diff --git a/src/script/hydra-eval-jobset b/src/script/hydra-eval-jobset +index ea336bfc..2f208418 100755 +--- a/src/script/hydra-eval-jobset ++++ b/src/script/hydra-eval-jobset +@@ -142,7 +142,7 @@ sub fetchInputSystemBuild { + $projectName ||= $project->name; + $jobsetName ||= $jobset->name; + +- my @latestBuilds = $db->resultset('LatestSucceededForJob') ++ my @latestBuilds = $db->resultset('LatestSucceededForJobName') + ->search({}, {bind => [$jobsetName, $jobName]}); + + my @validBuilds = (); diff --git a/nixbld-etc-nixos/matterbridge-disable-github.patch b/nixbld-etc-nixos/matterbridge-disable-github.patch new file mode 100644 index 0000000..dc659c2 --- /dev/null +++ b/nixbld-etc-nixos/matterbridge-disable-github.patch @@ -0,0 +1,15 @@ +diff --git a/bridge/mattermost/helpers.go b/bridge/mattermost/helpers.go +index 14b7469d..d9b77bdf 100644 +--- a/bridge/mattermost/helpers.go ++++ b/bridge/mattermost/helpers.go +@@ -206,6 +206,10 @@ func (b *Bmattermost) skipMessage(message *matterclient.Message) bool { + return true + } + ++ if message.Username == "github" { ++ return true ++ } ++ + // if the message has reactions don't repost it (for now, until we can correlate reaction with message) + if message.Post.HasReactions { + return true diff --git a/nixbld-etc-nixos/mattermost-github-integration/pkg.nix b/nixbld-etc-nixos/mattermost-github-integration/pkg.nix new file mode 100644 index 0000000..ea7fc7f --- /dev/null +++ b/nixbld-etc-nixos/mattermost-github-integration/pkg.nix @@ -0,0 +1,32 @@ +{ fetchFromGitHub, python3Packages }: +with python3Packages; + +buildPythonPackage rec { + pname = "mattermost-github-integration"; + version = "0.0.0-unstable"; + src = fetchFromGitHub { + owner = "softdevteam"; + repo = "mattermost-github-integration"; + rev = "1124a0ff233b50ed6070cb84cfffd128ad219831"; + sha256 = "1hfvjaxjhliy8sv9j3616fkdwd2jqhfsj9ai7ggx88zhxknrfx85"; + }; + propagatedBuildInputs = [ + appdirs + click + flask + itsdangerous + jinja2 + markupsafe + olefile + packaging + pillow + pyparsing + requests + six + werkzeug + ]; + checkInputs = [ + pytest + ]; + doCheck = true; +} diff --git a/nixbld-etc-nixos/mattermost-github-integration/uwsgi-config.nix b/nixbld-etc-nixos/mattermost-github-integration/uwsgi-config.nix new file mode 100644 index 0000000..51387c6 --- /dev/null +++ b/nixbld-etc-nixos/mattermost-github-integration/uwsgi-config.nix @@ -0,0 +1,15 @@ +{ config, pkgs }: + +let + pkg = pkgs.callPackage ./pkg.nix {}; +in { + type = "normal"; + pythonPackages = self: [ pkg ]; + module = "mattermostgithub:app"; + env = [ + "MGI_CONFIG_FILE=${./../secret/mattermost-github-integration.py}" + ]; + socket = "${config.services.uwsgi.runDir}/uwsgi-mgi.sock"; + # allow access from nginx + chmod-socket = 666; +} diff --git a/nixbld-etc-nixos/notifico/nixos-module.nix b/nixbld-etc-nixos/notifico/nixos-module.nix new file mode 100644 index 0000000..535450d --- /dev/null +++ b/nixbld-etc-nixos/notifico/nixos-module.nix @@ -0,0 +1,93 @@ +{ config, pkgs, lib, ... }: +with lib; +let + notifico = (pkgs.callPackage ./pkg.nix {}) + .overrideAttrs (attrs: { + buildInputs = attrs.buildInputs ++ [ pkgs.makeWrapper ]; + # Extend the module path so that local_config.py can be found + postInstall = '' + ${attrs.postInstall} + + wrapProgram $out/bin/notifico \ + --set PYTHONPATH "$${PYTHONPATH}:${cfg.dbDir}" + ''; + }); + cfg = config.services.notifico; +in + +{ + options.services.notifico = { + enable = mkOption { + type = types.bool; + default = false; + description = "Enable the commit notification service"; + }; + enableLocalRedis = mkOption { + type = types.bool; + default = true; + description = "Enable a local Redis server"; + }; + dbDir = mkOption { + type = types.str; + default = "/var/db/notifico"; + description = "Home directory and location of the database file"; + }; + config = mkOption { + description = "Path to local_config.py, https://github.com/notifico/notifico/raw/master/notifico/config.py"; + type = types.str; + }; + }; + + config = mkIf cfg.enable { + users.users.notifico = { + group = "notifico"; + home = cfg.dbDir; + createHome = true; + }; + users.groups.notifico = {}; + + services.redis = mkIf cfg.enableLocalRedis { + enable = true; + bind = "127.0.0.1"; + }; + + systemd.services = + let + User = "notifico"; + Group = "notifico"; + WorkingDirectory = "${cfg.dbDir}"; + ExecStartPre = [ + "${pkgs.coreutils}/bin/rm -f local_config.pyc" + "${pkgs.coreutils}/bin/ln -sf ${cfg.config} local_config.py" + ]; + + notifico-init = { + description = "Notifico initialization"; + serviceConfig = { + inherit User Group WorkingDirectory ExecStartPre; + Type = "oneshot"; + ExecStart = "${notifico}/bin/notifico init"; + }; + }; + notificoService = component: { + description = "Notifico ${component}"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" "notifico-init.service" ]; + requires = [ "notifico-init.service" ]; + serviceConfig = { + inherit User Group WorkingDirectory ExecStartPre; + Type = "simple"; + ExecStart = "${notifico}/bin/notifico ${component}"; + + Restart = "always"; + RestartSec = "5sec"; + }; + }; + in { + inherit notifico-init; + notifico-www = notificoService "www"; + notifico-worker = notificoService "worker"; + notifico-bots = notificoService "bots"; + }; + }; +} diff --git a/nixbld-etc-nixos/notifico/pkg.nix b/nixbld-etc-nixos/notifico/pkg.nix new file mode 100644 index 0000000..8f47953 --- /dev/null +++ b/nixbld-etc-nixos/notifico/pkg.nix @@ -0,0 +1,107 @@ +{ python2Packages, python2, fetchFromGitHub, fetchurl }: + +let + Flask-Gravatar = python2Packages.buildPythonPackage { + name = "Flask-Gravatar"; + src = python2Packages.fetchPypi { + pname = "Flask-Gravatar"; + version = "0.5.0"; + sha256 = "1qb2ylirjajdqsmldhwfdhf8i86k7vlh3y4gnqfqj4n6q8qmyrk0"; + }; + propagatedBuildInputs = with python2Packages; [ + pytestrunner + flask + ]; + checkInputs = with python2Packages; [ + check-manifest + coverage + isort + pydocstyle + pytestcache + pytestcov + pytestpep8 + pytest + pygments + ]; + }; + utopia = python2Packages.buildPythonPackage { + name = "utopia"; + src = fetchFromGitHub { + owner = "notifico"; + repo = "utopia"; + rev = "70293ed5e1ca55232e0fae71061e7e9b9b29be6f"; + sha256 = "11cnh9l4d9jlhafnfis9si6kgk9zsdd5439qnhxh6dca3x4a986q"; + }; + propagatedBuildInputs = with python2Packages; [ + gevent + blinker + ]; + doCheck = false; + }; + Flask-WTF = python2Packages.flask_wtf.overrideAttrs(oa: rec { + version = "0.8.4"; + src = python2Packages.fetchPypi { + pname = "Flask-WTF"; + inherit version; + sha256 = "1khbwmlrcnk9f46f7kf531n06pkyfs6nc8fk273js9mj2igngg2y"; + }; + }); + Flask-XML-RPC = python2Packages.flask_wtf.overrideAttrs(oa: rec { + version = "0.1.2"; + src = python2Packages.fetchPypi { + pname = "Flask-XML-RPC"; + inherit version; + sha256 = "1dwalj7pc5iid9l1k50q5mllirnn9f5s7jq54a66x48a4j179p2a"; + }; + }); +in + python2Packages.buildPythonApplication { + name = "notifico"; + src = fetchFromGitHub { + owner = "notifico"; + repo = "notifico"; + rev = "6af849e4c75dff4d740051676f5a2093a44efcee"; + sha256 = "18jifqdvjy4x5s1bh7vx501pin52g4n3hhw1z4m2c0h512z4spdr"; + }; + patches = [ + (fetchurl { + url = https://github.com/whitequark/notifico/commit/22b582fad6cb97af6f7437e8462d720ddacc42ef.patch; + sha256 = "0w8i8hf1r8b0p1y1zn9vyvnyi20qp120aiyalqymhsxsh17mma52"; + }) + ]; + propagatedBuildInputs = with python2Packages; [ + flask + Flask-WTF + Flask-Gravatar + flask_sqlalchemy + Flask-XML-RPC + flask_mail + flask-caching + Fabric + sqlalchemy + utopia + gevent + oauth2 + redis + gunicorn + requests + PyGithub + xmltodict + unidecode + raven + blinker + docopt + celery + ]; + postInstall = '' + mkdir $out/bin + cat << EOF > $out/bin/notifico + #!${python2}/bin/python + import sys + from notifico.__main__ import main + + sys.exit(main(sys.argv)) + EOF + chmod +x $out/bin/notifico + ''; + } diff --git a/nixbld-etc-nixos/rfq/pkg.nix b/nixbld-etc-nixos/rfq/pkg.nix new file mode 100644 index 0000000..d7fcc0b --- /dev/null +++ b/nixbld-etc-nixos/rfq/pkg.nix @@ -0,0 +1,9 @@ +{ python3Packages, runCommand }: + +# Note: we do not use fetchgit but a local copy instead to avoid +# chicken-and-egg problem if reinstalling nixbld.m-labs.hk from scratch. +with python3Packages; buildPythonPackage rec { + name = "rfq"; + src = ./src; + propagatedBuildInputs = [ flask flask_mail python-dotenv ]; +} diff --git a/nixbld-etc-nixos/rfq/src/requirements.txt b/nixbld-etc-nixos/rfq/src/requirements.txt new file mode 100644 index 0000000..df5ebfe --- /dev/null +++ b/nixbld-etc-nixos/rfq/src/requirements.txt @@ -0,0 +1,10 @@ +blinker +click +Flask +Flask-Mail +itsdangerous +Jinja2 +MarkupSafe +python-dotenv +six +Werkzeug diff --git a/nixbld-etc-nixos/rfq/src/rfq/__init__.py b/nixbld-etc-nixos/rfq/src/rfq/__init__.py new file mode 100644 index 0000000..35734cf --- /dev/null +++ b/nixbld-etc-nixos/rfq/src/rfq/__init__.py @@ -0,0 +1,75 @@ +from os import getenv + +from dotenv import load_dotenv +from flask import Flask +from flask import current_app +from flask import json +from flask import jsonify +from flask import make_response +from flask import request +from flask_mail import Mail +from flask_mail import Message +from werkzeug.middleware.proxy_fix import ProxyFix + + +load_dotenv() + +app = Flask(__name__) +app.config.update( + DEBUG=getenv("FLASK_DEBUG") == "True", + MAIL_SERVER=getenv("FLASK_MAIL_SERVER"), + MAIL_PORT=getenv("FLASK_MAIL_PORT"), + MAIL_USE_SSL=getenv("FLASK_MAIL_USE_SSL"), + MAIL_DEBUG=False, + MAIL_USERNAME=getenv("FLASK_MAIL_USERNAME"), + MAIL_PASSWORD=getenv("FLASK_MAIL_PASSWORD"), + MAIL_RECIPIENT=getenv("FLASK_MAIL_RECIPIENT"), + MAIL_SENDER=getenv("FLASK_MAIL_SENDER") +) +app.wsgi_app = ProxyFix(app.wsgi_app) + +mail = Mail(app) + + +@app.after_request +def after(response): + response.headers["Access-Control-Allow-Origin"] = "*" + response.headers["Access-Control-Allow-Headers"] = "*" + return response + + +@app.route("/rfq", methods=["POST"]) +def send_rfq(): + payload = request.json + payload = json.loads(json.htmlsafe_dumps(payload)) + + if payload is None: + resp = jsonify(error="invalid data") + return make_response(resp, 400) + + if "email" not in payload: + resp = jsonify(error="missing email") + return make_response(resp, 400) + + if "note" not in payload: + resp = jsonify(error="missing note") + return make_response(resp, 400) + + if "configuration" not in payload: + resp = jsonify(error="missing configuration") + return make_response(resp, 400) + + sender = current_app.config["MAIL_SENDER"] + recipient = current_app.config["MAIL_RECIPIENT"] + + msg = Message( + "RFQ for Sinara hardware from {}".format(payload["email"]), + sender=sender, + recipients=[recipient, payload["email"]]) + msg.body = ("From: {}\nConfiguration: {}\nNote: {}" + .format(payload["email"], payload["configuration"], payload["note"])) + + with mail.connect() as conn: + conn.send(msg) + + return jsonify("ok") diff --git a/nixbld-etc-nixos/rfq/src/setup.py b/nixbld-etc-nixos/rfq/src/setup.py new file mode 100644 index 0000000..df9cf61 --- /dev/null +++ b/nixbld-etc-nixos/rfq/src/setup.py @@ -0,0 +1,14 @@ +from setuptools import setup +from setuptools import find_packages + +setup( + name='rfq', + version='1.0.0', + packages=find_packages(), + zip_safe=False, + install_requires=[ + 'Flask', + 'Flask-Mail', + 'python-dotenv' + ] +) diff --git a/nixbld-etc-nixos/rfq/uwsgi-config.nix b/nixbld-etc-nixos/rfq/uwsgi-config.nix new file mode 100644 index 0000000..b301fa3 --- /dev/null +++ b/nixbld-etc-nixos/rfq/uwsgi-config.nix @@ -0,0 +1,21 @@ +{ config, pkgs }: + +let + pkg = pkgs.callPackage ./pkg.nix {}; +in { + type = "normal"; + pythonPackages = self: [ pkg ]; + module = "rfq:app"; + env = [ + "FLASK_MAIL_SERVER=ssl.serverraum.org" + "FLASK_MAIL_PORT=465" + "FLASK_MAIL_USE_SSL=True" + "FLASK_MAIL_USERNAME=sales@m-labs.hk" + "FLASK_MAIL_PASSWORD=${import /etc/nixos/secret/sales_password.nix}" + "FLASK_MAIL_RECIPIENT=sales@m-labs.hk" + "FLASK_MAIL_SENDER=sales@m-labs.hk" + ]; + socket = "${config.services.uwsgi.runDir}/uwsgi-rfq.sock"; + # allow access from nginx + chmod-socket = 666; +} diff --git a/nixbld-etc-nixos/secret_permissions.txt b/nixbld-etc-nixos/secret_permissions.txt new file mode 100644 index 0000000..bc91931 --- /dev/null +++ b/nixbld-etc-nixos/secret_permissions.txt @@ -0,0 +1,11 @@ +-rw------- 1 root root backup-passphrase +-rw------- 1 root root email_accounts.nix +-rw------- 1 homu homu homu.toml +-rw-rw---- 1 gitea gitea mailerpassword +-rw------- 1 matterbridge matterbridge matterbridge.toml +-rw------- 1 uwsgi uwsgi mattermost-github-integration.py +-rw------- 1 nginx nginx muninpasswd +-rw-rw---- 1 hydra hydra nixbld.m-labs.hk-1 +-rw-rw---- 1 hydra hydra nix_id_rsa +-rw------- 1 root root rclone.conf +-rw------- 1 root root wifi_password.nix diff --git a/nixops/chiron-hardware-configuration.nix b/nixops/chiron-hardware-configuration.nix new file mode 100644 index 0000000..643722a --- /dev/null +++ b/nixops/chiron-hardware-configuration.nix @@ -0,0 +1,33 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ config, lib, pkgs, ... }: + +{ + imports = + [ + ]; + + boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "nvme" "usbhid" "usb_storage" "sd_mod" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = + { device = "/dev/disk/by-uuid/89db7e45-7b7e-44ec-bebc-a02935cc5ba0"; + fsType = "ext4"; + }; + + fileSystems."/boot" = + { device = "/dev/disk/by-uuid/060C-8772"; + fsType = "vfat"; + }; + + swapDevices = [ ]; + + nix.maxJobs = lib.mkDefault 16; + powerManagement.cpuFreqGovernor = lib.mkDefault "powersave"; + + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; +} diff --git a/nixops/cnc-hardware-configuration.nix b/nixops/cnc-hardware-configuration.nix new file mode 100644 index 0000000..a5ae41a --- /dev/null +++ b/nixops/cnc-hardware-configuration.nix @@ -0,0 +1,29 @@ +{ config, lib, pkgs, ... }: + +{ + imports = + [ + ]; + + boot.initrd.availableKernelModules = [ "ata_generic" "uhci_hcd" "ehci_pci" "ahci" "usb_storage" "usbhid" "floppy" "sd_mod" "sr_mod" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = + { device = "/dev/disk/by-uuid/35d9c50c-e479-43a9-8324-b8ded5b71844"; + fsType = "ext4"; + }; + + swapDevices = + [ { device = "/dev/disk/by-uuid/d8480389-c558-4c46-a58f-00207315dbdd"; } + ]; + + nix.maxJobs = lib.mkDefault 2; + + boot.loader.grub.enable = true; + boot.loader.grub.version = 2; + boot.loader.grub.device = "/dev/sda"; + + services.xserver.videoDrivers = ["intel"]; +} diff --git a/nixops/common-users.nix b/nixops/common-users.nix new file mode 100644 index 0000000..4341c9a --- /dev/null +++ b/nixops/common-users.nix @@ -0,0 +1,58 @@ +{ pkgs, ... }: + +{ + sb = { + isNormalUser = true; + extraGroups = ["wheel" "plugdev" "dialout"]; + openssh.authorizedKeys.keys = [ + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCyPk5WyFoWSvF4ozehxcVBoZ+UHgrI7VW/OoQfFFwIQe0qvetUZBMZwR2FwkLPAMZV8zz1v4EfncudEkVghy4P+/YVLlDjqDq9zwZnh8Nd/ifu84wmcNWHT2UcqnhjniCdshL8a44memzABnxfLLv+sXhP2x32cJAamo5y6fukr2qLp2jbXzR+3sv3klE0ruUXis/BR1lLqNJEYP8jB6fLn2sLKinnZPfn6DwVOk10mGeQsdME/eGl3phpjhODH9JW5V2V5nJBbC0rBnq+78dyArKVqjPSmIcSy72DEIpTctnMEN1W34BGrnsDd5Xd/DKxKxHKTMCHtZRwLC2X0NWN" + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCMALVC8RDTHec+PC8y1s3tcpUAODgq6DEzQdHDf/cyvDMfmCaPiMxfIdmkns5lMa03hymIfSmLUF0jFFDc7biRp7uf9AAXNsrTmplHii0l0McuOOZGlSdZM4eL817P7UwJqFMxJyFXDjkubhQiX6kp25Kfuj/zLnupRCaiDvE7ho/xay6Jrv0XLz935TPDwkc7W1asLIvsZLheB+sRz9SMOb9gtrvk5WXZl5JTOFOLu+JaRwQLHL/xdcHJTOod7tqHYfpoC5JHrEwKzbhTOwxZBQBfTQjQktKENQtBxXHTe71rUEWfEZQGg60/BC4BrRmh4qJjlJu3v4VIhC7SSHn1" + ]; + }; + rj = { + isNormalUser = true; + extraGroups = ["wheel" "plugdev" "dialout"]; + openssh.authorizedKeys.keys = [ + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDZOitjBp9uB+Hldt5M040Jq/3rVFBbw40Xemkau3BLvMn8TzkJs5NLrlNa4vcwFecA/nh7aPzdGHc1b/E2EYCfM29oo/oVBJsp/L66YUbnYrneFNVp8Ccw3tZPPAiADjLZWta0JQLVVY6Dqtt0SH/oU5jC1F1qCa+krWqkKAVE8rfxYVspBGagxlpZuE83UC0j2yXrbHq6ZrAW917wXUEpcIR+mKalDM2Aa1FAZZH9upty2yysyOHh6/ljurz6tMRqjzjdJtVJ2YXf4GZpIuYcxCU1kvLKPLN0MZA+aXtraCGmEdjdx38sfRqHBnffXhCkJIo+W4aw4Xae2xplmGeInWqnUwsWxuVJENdPfbBOBdMRuFemuPZdmBcohczDygOC3h+oljBvQF6Ffyvk38pVLbd91p1+qgvtW7OcXTUjm17K1Oa54RGUcm1W2w3yJKCc8RQZXlwVtneTX0VoK39LC1yWfyMBg8sWeT66oE+v2CCEzsB0A1xZx/dK0r5bdfv8uNAH5d8RGL++zNEVrsA4iZF6FEeXgaoje3tKMqKTgOx4EDh93ie2rv7oE8xrPL5g0vb8wBQ1Kf4rukd7FPVu+E4+W5oSnQ42BJ9Z4sFCLQ9Dnhbg4VvREzAe9rVzfAG368iCVKkFcSq+AaLquqrBpwLbz10V3GLDARlF2IZZGQ== rj@lab.m-labs.hk" + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC27krR8G8Pb59YuYm7+X2mmNnVdk/t9myYgO8LH0zfb2MeeXX5+90nW9kMjKflJss/oLl8dkD85jbJ0fRbRkfJd20pGCqCUuYAbYKkowigFVEkbrbWSLkmf+clRjzJOuBuUA0uq0XKS17uMC3qhu+dDdBOAIKb3L83NfVE8p8Pjb4BPktQrdxefM43/x4jTMuc7tgxVmTOEge3+rmVPK2GnLkUBgBn8b6S+9ElPd63HXI5J5f61v21l5N9V0mhTu1pv6PiDRdFIlFDK9dLVZcZ2qlzpKmCnFrOoreBEgre44SpfFe5/MMItxvWiVsj/rij/rHZZiol1k7JiQCnEHeCCbjjvcBBka5HxZgcb3vBZVceTOawrmjbdbA2dq35sUptz/bEgdZ1UVCmVpWsdROAlEDBmSSbcVwxzcvhoKnkpbuP4Q0V3tVKSLW053ADFNB4frtwY5nAZfsVErFLLphjwb8nlyJoDRNapQrn5syEiW0ligX2AAskZTYIl2A5AYyWPrmX6HJOPqZGatMU3qQiRMxs+hFqhyyCmBgl0kcsgW09MBKtJWk1Fbii98MHqgRUN9R7AUiYy5p78Pnv9DC8DT8Ubl9zoP0g5d40P9NGK2LAhMxLXvtckJ4ERqbSEcNZJw+q4jBrOHnMTz+NLdAUiEtru+6T2OdhaHv+eiNlFQ== robert-jordens-rsa4096" + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCUdbne3NtIG+iy/jer76/OY+IksuS3BDLSXPnWrGejWnig9h+L6sUV0lEVI6dqp+W/b8jWqPB8nh5S0NZsCd3Ta3Go82k/SPPkh9lB2PpfquhCjLnmC/RNc3TgC4FuiS+NZHqXaTggYHubNwEK+8gynMqkMQXjOGU02U0CtUfsYdAm75AW60DySZCRNwOcU0Ndpn1UCpha7fL1k179Dd/OtArkYsIL24ohlfxFeOB3jGYQK6ATmzbvCRjwIKXcyECuajWwfnDg9FtDWrqHNzu5dJlvmxoWm8zCDgMj53uiA7TjujQN81MYrIJNeEwSr5jXQMqzA3mzlk4k3Z0qs3TP robert-jordens-64FEFBAF-4D0749B2-rsa2048" + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMUaB2G1jexxfkdlly3fdWslH54/s/bOuvk9AxqpjtAY robert-jordens-ed25519" + ]; + }; + harry = { + isNormalUser = true; + extraGroups = ["plugdev" "dialout" "wireshark"]; + openssh.authorizedKeys.keys = [ + "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBDcPNCgtdz8erFPRrAwCr4JrkeYXJUUvoRBgP0X2HlzJgDe1Inuo6sC6CGcO3IXbf4MwVA9XEp8BYPHARVeEHhufg/0wnIABLx2GcK99yxOLDUe4h/3YwtqvOcqHEsDx7w==" + ]; + }; + astro = { + isNormalUser = true; + extraGroups = ["plugdev" "dialout"]; + openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGJJTSJdpDh82486uPiMhhyhnci4tScp5uUe7156MBC8 a" + ]; + shell = pkgs.bashInteractive; + }; + pca006132 = { + isNormalUser = true; + extraGroups = ["plugdev" "dialout"]; + openssh.authorizedKeys.keys = [ + "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBE/sPOOiw3843+rrcYV2pOVkffNc1xsOgnuCUmy1Fa2VF8x9kqmgQv61sxsuKRkKKoinvqrASxLkWVd6nkiiDuEISibEXs8r1BwuT05cS7RkEhCakSMZ6y/iqOtjt2bx+A==" + ]; + }; + occheung = { + isNormalUser = true; + extraGroups = ["plugdev" "dialout"]; + openssh.authorizedKeys.keys = [ + "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBPEvmWmxpFpMgp5fpjKud8ev0cyf/+X5fEpQt/YD/+u4mbvZYPE300DLqQ0h/qjgvaGMz1ndf4idYnRdy+plJEC/+hmlRW5NlcpAr3S/LYAisacgKToFVl+MlBo+emS9Ig==" + ]; + }; + dsleung = { + isNormalUser = true; + extraGroups = ["plugdev" "dialout"]; + openssh.authorizedKeys.keys = [ + "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBDbE7HzZKSwGbgRnzwrzCzb3gZKLSritwnEpHS4sa9oXJ5oLFkuFZOpPYDeiMlbUJ9jCk5FRmkLYIkrbz06SUr7P/eUjxu79ENi3RhfVu+ZrrPvgkhKvM/CiXvw3xCOu0w==" + ]; + }; +} diff --git a/nixops/desktop.nix b/nixops/desktop.nix new file mode 100644 index 0000000..5e46bcd --- /dev/null +++ b/nixops/desktop.nix @@ -0,0 +1,149 @@ +{ host }: + +{ config, pkgs, ... }: +let + m-labs = import (fetchTarball https://nixbld.m-labs.hk/channel/custom/artiq/full/artiq-full/nixexprs.tar.xz) { inherit pkgs; }; + pkgs-unstable = import (fetchTarball https://github.com/NixOS/nixpkgs/archive/master.tar.gz) {}; +in +{ + deployment.targetHost = host; + + disabledModules = [ "security/pam.nix" ]; + imports = + [ + (./. + "/${host}-hardware-configuration.nix") + ./pam_p11 + ]; + + networking.hostName = host; + + time.timeZone = "Asia/Hong_Kong"; + + # List packages installed in system profile. To search, run: + # $ nix search wget + nixpkgs.config.allowUnfree = true; + environment.systemPackages = with pkgs; [ + opensc yubikey-manager yubikey-manager-qt + wget vim gitAndTools.gitFull firefox chromium thunderbird hexchat + usbutils pciutils file lm_sensors audacious acpi + gimp imagemagick + (python3.withPackages(ps: with ps; [ numpy scipy matplotlib qtconsole regex ])) + mosh psmisc libreoffice-fresh + gtkwave telnet unzip zip gnupg + gnome3.gnome-tweaks + jq sublime3 rink qemu_kvm + tmux xc3sprog m-labs.openocd screen gdb minicom picocom tigervnc + emacs bat ripgrep + pkgs-unstable.rust-analyzer + (pkgs-unstable.vscode-with-extensions.override { + vscodeExtensions = [ + pkgs-unstable.vscode-extensions.matklad.rust-analyzer + ]; + }) + (import ./fish-nix-shell) + ]; + programs.wireshark.enable = true; + + services.openssh.enable = true; + services.openssh.forwardX11 = true; + services.openssh.passwordAuthentication = false; + hardware.u2f.enable = true; + services.pcscd.enable = true; + programs.ssh.extraConfig = + '' + PKCS11Provider "${pkgs.opensc}/lib/opensc-pkcs11.so" + ''; + programs.ssh.startAgent = true; + services.gnome3.gnome-keyring.enable = pkgs.lib.mkForce false; + programs.ssh.agentPKCS11Whitelist = "${pkgs.opensc}/lib/opensc-pkcs11.so"; + security.pam.p11.enable = true; + + # Enable CUPS to print documents. + services.printing = { + enable = true; + extraConf = + '' + Browsing Off + BrowseLocalProtocols none + ''; + browsedConf = + '' + BrowseRemoteProtocols none + BrowseProtocols none + ''; + }; + services.avahi = { + enable = true; + nssmdns = true; + }; + + # Enable sound. + sound.enable = true; + hardware.pulseaudio = { + enable = true; + extraModules = [ pkgs.pulseaudio-modules-bt ]; + package = pkgs.pulseaudioFull; + }; + + i18n.inputMethod = { + enabled = "fcitx"; + fcitx.engines = with pkgs.fcitx-engines; [ table-extra m17n ]; + }; + fonts.fonts = [ pkgs.noto-fonts pkgs.noto-fonts-cjk pkgs.noto-fonts-emoji pkgs.noto-fonts-extra pkgs.emacs-all-the-icons-fonts ]; + + # Enable the X11 windowing system. + services.xserver.enable = true; + services.xserver.layout = "us"; + services.xserver.xkbOptions = "eurosign:e"; + + # Enable touchpad support. + services.xserver.libinput.enable = true; + + services.xserver.displayManager.gdm.enable = true; + services.xserver.displayManager.gdm.autoSuspend = false; + powerManagement.enable = false; + services.xserver.desktopManager.gnome3.enable = true; + environment.gnome3.excludePackages = [ pkgs.epiphany pkgs.gnome3.geary ]; + + hardware.bluetooth.enable = true; + + programs.fish.enable = true; + programs.fish.promptInit = '' + fish-nix-shell --info-right | source + ''; + users.mutableUsers = false; + users.defaultUserShell = pkgs.fish; + users.extraGroups.plugdev = { }; + users.extraUsers = import ./common-users.nix { inherit pkgs; }; + security.sudo.wheelNeedsPassword = false; + services.udev.packages = [ m-labs.openocd ]; + services.udev.extraRules = '' +# leaf maple +SUBSYSTEM=="usb", ATTRS{idVendor}=="1eaf", ATTRS{idProduct}=="0003", MODE="0660", GROUP="plugdev" +SUBSYSTEM=="usb", ATTRS{idVendor}=="1eaf", ATTRS{idProduct}=="0004", MODE="0660", GROUP="plugdev" +# glasgow +SUBSYSTEM=="usb", ATTRS{idVendor}=="20b7", ATTRS{idProduct}=="9db1", MODE="0660", GROUP="plugdev" +# hackrf +SUBSYSTEM=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="6089", MODE="0660", GROUP="plugdev" +# bladerf +SUBSYSTEM=="usb", ATTRS{idVendor}=="2cf0", ATTRS{idProduct}=="5250", MODE="0660", GROUP="plugdev" +# personal measurement device +SUBSYSTEM=="usb", ATTRS{idVendor}=="09db", ATTRS{idProduct}=="007a", MODE="0660", GROUP="plugdev" +# saleae +SUBSYSTEM=="usb", ATTRS{idVendor}=="0925", ATTRS{idProduct}=="3881", MODE="0660", GROUP="plugdev" +# ocean optics +SUBSYSTEM=="usb", ATTRS{idVendor}=="2457", ATTRS{idProduct}=="1002", MODE="0660", GROUP="plugdev" +# yubikey +SUBSYSTEM=="usb", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0116", MODE="0660", GROUP="plugdev" + ''; + + nix.binaryCachePublicKeys = ["nixbld.m-labs.hk-1:5aSRVA5b320xbNvu30tqxVPXpld73bhtOeH6uAjRyHc="]; + nix.binaryCaches = ["https://nixbld.m-labs.hk" "https://cache.nixos.org"]; + nix.sandboxPaths = ["/opt"]; + + # This value determines the NixOS release with which your system is to be + # compatible, in order to avoid breaking some software such as database + # servers. You should change this only after NixOS release notes say you + # should. + system.stateVersion = "19.03"; # Did you read the comment? +} diff --git a/nixops/fish-nix-shell/LICENSE b/nixops/fish-nix-shell/LICENSE new file mode 100644 index 0000000..d8940be --- /dev/null +++ b/nixops/fish-nix-shell/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 haslersn + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/nixops/fish-nix-shell/README.md b/nixops/fish-nix-shell/README.md new file mode 100644 index 0000000..5db8dfd --- /dev/null +++ b/nixops/fish-nix-shell/README.md @@ -0,0 +1,50 @@ +# fish-nix-shell +fish support for the *nix-shell* environment of the Nix package manager. + +## Installation + +### Installation in the user environment + +Execute + +``` +nix-env -if https://github.com/haslersn/fish-nix-shell/archive/master.tar.gz +``` + +and add the following to your *~/.config/fish/config.fish*. Create it if it doesn't exist. + +``` +fish-nix-shell --info-right | source +``` + +### System-wide installation + +Add the package to your */etc/nixos/configuration.nix*: + +``` + environment.systemPackages = with pkgs; [ + # + # Other packages here ... + # + (import (fetchGit "https://github.com/haslersn/fish-nix-shell")) + ]; +``` + +and then execute: `sudo nixos-rebuild switch` + +If you want to configure it system-wide, also add: + +``` + programs.fish.enable = true; + programs.fish.promptInit = '' + fish-nix-shell --info-right | source + ''; +``` + +## Flags + +The `fish-nix-shell` command **optionally** takes the following flags: + +| Flag | Meaning | +| - | - | +| `--info-right` | While in a *fish-nix-shell*, display information about the loaded packages at the right. diff --git a/nixops/fish-nix-shell/bin/fish-nix-shell b/nixops/fish-nix-shell/bin/fish-nix-shell new file mode 100755 index 0000000..efe72a1 --- /dev/null +++ b/nixops/fish-nix-shell/bin/fish-nix-shell @@ -0,0 +1,34 @@ +#!/bin/sh + +function init_fish () { + cat < {}; stdenv.mkDerivation rec { + name = "fish-nix-shell"; + src = fetchGit "https://github.com/haslersn/fish-nix-shell"; + nativeBuildInputs = [ makeWrapper ]; + installPhase = '' + mkdir -p $out + cp LICENSE $out + cp -r bin $out + wrapProgram $out/bin/fish-nix-shell + wrapProgram $out/bin/fish-nix-shell-wrapper --prefix PATH ":" ${fish}/bin + wrapProgram $out/bin/nix-shell-info + ''; + meta.description = "fish support for the nix-shell environment of the Nix package manager."; + meta.license = "MIT"; + meta.homepage = https://github.com/haslersn/fish-nix-shell; +} \ No newline at end of file diff --git a/nixops/hera-hardware-configuration.nix b/nixops/hera-hardware-configuration.nix new file mode 100644 index 0000000..969f7ec --- /dev/null +++ b/nixops/hera-hardware-configuration.nix @@ -0,0 +1,33 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ config, lib, pkgs, ... }: + +{ + imports = + [ + ]; + + boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "nvme" "usbhid" "usb_storage" "sd_mod" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = + { device = "/dev/disk/by-uuid/a86f2eff-c873-4af1-bb80-a903383b9c8c"; + fsType = "ext4"; + }; + + fileSystems."/boot" = + { device = "/dev/disk/by-uuid/8C30-F6DC"; + fsType = "vfat"; + }; + + swapDevices = [ ]; + + nix.maxJobs = lib.mkDefault 16; + powerManagement.cpuFreqGovernor = lib.mkDefault "powersave"; + + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; +} diff --git a/nixops/hestia-hardware-configuration.nix b/nixops/hestia-hardware-configuration.nix new file mode 100644 index 0000000..ee34091 --- /dev/null +++ b/nixops/hestia-hardware-configuration.nix @@ -0,0 +1,33 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ config, lib, pkgs, ... }: + +{ + imports = + [ + ]; + + boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "nvme" "usbhid" "usb_storage" "sd_mod" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = + { device = "/dev/disk/by-uuid/2fe28058-4186-4e65-9d3d-f7ec5dffc171"; + fsType = "ext4"; + }; + + fileSystems."/boot" = + { device = "/dev/disk/by-uuid/E085-5F21"; + fsType = "vfat"; + }; + + swapDevices = [ ]; + + nix.maxJobs = lib.mkDefault 16; + powerManagement.cpuFreqGovernor = lib.mkDefault "powersave"; + + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; +} diff --git a/nixops/juno-hardware-configuration.nix b/nixops/juno-hardware-configuration.nix new file mode 100644 index 0000000..bb8e0da --- /dev/null +++ b/nixops/juno-hardware-configuration.nix @@ -0,0 +1,30 @@ +{ config, lib, pkgs, ... }: + +{ + imports = + [ + ]; + + boot.initrd.availableKernelModules = [ "xhci_pci" "ehci_pci" "ahci" "usbhid" "usb_storage" "sd_mod" "sr_mod" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = + { device = "/dev/disk/by-uuid/62a38d9c-452c-4648-be12-6131e95b8276"; + fsType = "ext4"; + }; + + fileSystems."/boot" = + { device = "/dev/disk/by-uuid/88F6-46F2"; + fsType = "vfat"; + }; + + swapDevices = [ ]; + + nix.maxJobs = lib.mkDefault 8; + powerManagement.cpuFreqGovernor = lib.mkDefault "powersave"; + + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; +} diff --git a/nixops/light.nix b/nixops/light.nix new file mode 100644 index 0000000..cfa0e8e --- /dev/null +++ b/nixops/light.nix @@ -0,0 +1,112 @@ +{ host }: + +{ config, pkgs, ... }: +{ + deployment.targetHost = host; + + disabledModules = [ "security/pam.nix" ]; + imports = + [ + (./. + "/${host}-hardware-configuration.nix") + ./pam_p11 + ]; + + networking.hostName = host; + + time.timeZone = "Asia/Hong_Kong"; + + # List packages installed in system profile. To search, run: + # $ nix search wget + documentation.enable = false; + nixpkgs.config.allowUnfree = true; + environment.systemPackages = with pkgs; [ + opensc + wget vim git firefox usbutils pciutils file lm_sensors acpi + gimp imagemagick + (python3.withPackages(ps: with ps; [ numpy scipy ])) + psmisc + telnet unzip zip gnupg + sublime3 rink + tmux screen tigervnc + (import ./fish-nix-shell) + ]; + programs.wireshark.enable = true; + + services.openssh.enable = true; + services.openssh.forwardX11 = true; + services.openssh.passwordAuthentication = false; + hardware.u2f.enable = true; + services.pcscd.enable = true; + programs.ssh.extraConfig = + '' + PKCS11Provider "${pkgs.opensc}/lib/opensc-pkcs11.so" + ''; + programs.ssh.startAgent = true; + programs.ssh.agentPKCS11Whitelist = "${pkgs.opensc}/lib/opensc-pkcs11.so"; + security.pam.p11.enable = true; + + # Enable CUPS to print documents. + services.printing = { + enable = true; + extraConf = + '' + Browsing Off + BrowseLocalProtocols none + ''; + browsedConf = + '' + BrowseRemoteProtocols none + BrowseProtocols none + ''; + }; + services.avahi = { + enable = true; + nssmdns = true; + }; + + # Enable sound. + sound.enable = true; + hardware.pulseaudio = { + enable = true; + extraModules = [ pkgs.pulseaudio-modules-bt ]; + package = pkgs.pulseaudioFull; + }; + + i18n.inputMethod = { + enabled = "fcitx"; + fcitx.engines = with pkgs.fcitx-engines; [ table-extra m17n ]; + }; + fonts.fonts = [ pkgs.noto-fonts pkgs.noto-fonts-cjk pkgs.noto-fonts-emoji pkgs.noto-fonts-extra ]; + + # Enable the X11 windowing system. + services.xserver.enable = true; + services.xserver.layout = "us"; + services.xserver.xkbOptions = "eurosign:e"; + + # Enable touchpad support. + services.xserver.libinput.enable = true; + + services.xserver.displayManager.lightdm.enable = true; + services.xserver.desktopManager.xfce.enable = true; + + programs.fish.enable = true; + programs.fish.promptInit = '' + fish-nix-shell --info-right | source + ''; + users.mutableUsers = false; + users.defaultUserShell = pkgs.fish; + users.extraGroups.plugdev = { }; + users.extraUsers = import ./common-users.nix { inherit pkgs; }; + + security.sudo.wheelNeedsPassword = false; + + nix.binaryCachePublicKeys = ["nixbld.m-labs.hk-1:5aSRVA5b320xbNvu30tqxVPXpld73bhtOeH6uAjRyHc="]; + nix.binaryCaches = ["https://nixbld.m-labs.hk" "https://cache.nixos.org"]; + nix.sandboxPaths = ["/opt"]; + + # This value determines the NixOS release with which your system is to be + # compatible, in order to avoid breaking some software such as database + # servers. You should change this only after NixOS release notes say you + # should. + system.stateVersion = "19.03"; # Did you read the comment? +} diff --git a/nixops/nixops.nix b/nixops/nixops.nix new file mode 100644 index 0000000..cd64858 --- /dev/null +++ b/nixops/nixops.nix @@ -0,0 +1,13 @@ +{ + rpi-1 = import ./rpi.nix { host = "rpi-1"; rpi4 = false; }; + rpi-2 = import ./rpi.nix { host = "rpi-2"; rpi4 = false; }; + rpi-3 = import ./rpi.nix { host = "rpi-3"; rpi4 = true; }; + rpi-4 = import ./rpi.nix { host = "rpi-4"; rpi4 = true; }; + rpi-5 = import ./rpi.nix { host = "rpi-5"; rpi4 = true; }; + juno = import ./desktop.nix { host = "juno"; }; + zeus = import ./desktop.nix { host = "zeus"; }; + hera = import ./desktop.nix { host = "hera"; }; + hestia = import ./desktop.nix { host = "hestia"; }; + chiron = import ./desktop.nix { host = "chiron"; }; + cnc = import ./light.nix { host = "cnc"; }; +} diff --git a/nixops/pam_p11/default.nix b/nixops/pam_p11/default.nix new file mode 100644 index 0000000..5945535 --- /dev/null +++ b/nixops/pam_p11/default.nix @@ -0,0 +1,843 @@ +# This module provides configuration for the PAM (Pluggable +# Authentication Modules) system. + +{ config, lib, pkgs, ... }: + +with lib; + +let + pam_p11 = pkgs.callPackage ./pam_p11.nix {}; + + parentConfig = config; + + pamOpts = { config, name, ... }: let cfg = config; in let config = parentConfig; in { + + options = { + + name = mkOption { + example = "sshd"; + type = types.str; + description = "Name of the PAM service."; + }; + + unixAuth = mkOption { + default = true; + type = types.bool; + description = '' + Whether users can log in with passwords defined in + /etc/shadow. + ''; + }; + + rootOK = mkOption { + default = false; + type = types.bool; + description = '' + If set, root doesn't need to authenticate (e.g. for the + useradd service). + ''; + }; + + p11Auth = mkOption { + default = config.security.pam.p11.enable; + type = types.bool; + description = '' + If set, keys listed in + ~/.ssh/authorized_keys and + ~/.eid/authorized_certificates + can be used to log in with the associated PKCS#11 tokens. + ''; + }; + + u2fAuth = mkOption { + default = config.security.pam.u2f.enable; + type = types.bool; + description = '' + If set, users listed in + $XDG_CONFIG_HOME/Yubico/u2f_keys (or + $HOME/.config/Yubico/u2f_keys if XDG variable is + not set) are able to log in with the associated U2F key. Path can be + changed using option. + ''; + }; + + yubicoAuth = mkOption { + default = config.security.pam.yubico.enable; + type = types.bool; + description = '' + If set, users listed in + ~/.yubico/authorized_yubikeys + are able to log in with the associated Yubikey tokens. + ''; + }; + + googleAuthenticator = { + enable = mkOption { + default = false; + type = types.bool; + description = '' + If set, users with enabled Google Authenticator (created + ~/.google_authenticator) will be required + to provide Google Authenticator token to log in. + ''; + }; + }; + + usbAuth = mkOption { + default = config.security.pam.usb.enable; + type = types.bool; + description = '' + If set, users listed in + /etc/pamusb.conf are able to log in + with the associated USB key. + ''; + }; + + otpwAuth = mkOption { + default = config.security.pam.enableOTPW; + type = types.bool; + description = '' + If set, the OTPW system will be used (if + ~/.otpw exists). + ''; + }; + + googleOsLoginAccountVerification = mkOption { + default = false; + type = types.bool; + description = '' + If set, will use the Google OS Login PAM modules + (pam_oslogin_login, + pam_oslogin_admin) to verify possible OS Login + users and set sudoers configuration accordingly. + This only makes sense to enable for the sshd PAM + service. + ''; + }; + + googleOsLoginAuthentication = mkOption { + default = false; + type = types.bool; + description = '' + If set, will use the pam_oslogin_login's user + authentication methods to authenticate users using 2FA. + This only makes sense to enable for the sshd PAM + service. + ''; + }; + + fprintAuth = mkOption { + default = config.services.fprintd.enable; + type = types.bool; + description = '' + If set, fingerprint reader will be used (if exists and + your fingerprints are enrolled). + ''; + }; + + oathAuth = mkOption { + default = config.security.pam.oath.enable; + type = types.bool; + description = '' + If set, the OATH Toolkit will be used. + ''; + }; + + sshAgentAuth = mkOption { + default = false; + type = types.bool; + description = '' + If set, the calling user's SSH agent is used to authenticate + against the keys in the calling user's + ~/.ssh/authorized_keys. This is useful + for sudo on password-less remote systems. + ''; + }; + + duoSecurity = { + enable = mkOption { + default = false; + type = types.bool; + description = '' + If set, use the Duo Security pam module + pam_duo for authentication. Requires + configuration of options. + ''; + }; + }; + + startSession = mkOption { + default = false; + type = types.bool; + description = '' + If set, the service will register a new session with + systemd's login manager. For local sessions, this will give + the user access to audio devices, CD-ROM drives. In the + default PolicyKit configuration, it also allows the user to + reboot the system. + ''; + }; + + setEnvironment = mkOption { + type = types.bool; + default = true; + description = '' + Whether the service should set the environment variables + listed in + using pam_env.so. + ''; + }; + + setLoginUid = mkOption { + type = types.bool; + description = '' + Set the login uid of the process + (/proc/self/loginuid) for auditing + purposes. The login uid is only set by ‘entry points’ like + login and sshd, not by + commands like sudo. + ''; + }; + + forwardXAuth = mkOption { + default = false; + type = types.bool; + description = '' + Whether X authentication keys should be passed from the + calling user to the target user (e.g. for + su) + ''; + }; + + pamMount = mkOption { + default = config.security.pam.mount.enable; + type = types.bool; + description = '' + Enable PAM mount (pam_mount) system to mount fileystems on user login. + ''; + }; + + allowNullPassword = mkOption { + default = false; + type = types.bool; + description = '' + Whether to allow logging into accounts that have no password + set (i.e., have an empty password field in + /etc/passwd or + /etc/group). This does not enable + logging into disabled accounts (i.e., that have the password + field set to !). Note that regardless of + what the pam_unix documentation says, accounts with hashed + empty passwords are always allowed to log in. + ''; + }; + + nodelay = mkOption { + default = false; + type = types.bool; + description = '' + Wheather the delay after typing a wrong password should be disabled. + ''; + }; + + requireWheel = mkOption { + default = false; + type = types.bool; + description = '' + Whether to permit root access only to members of group wheel. + ''; + }; + + limits = mkOption { + description = '' + Attribute set describing resource limits. Defaults to the + value of . + ''; + }; + + showMotd = mkOption { + default = false; + type = types.bool; + description = "Whether to show the message of the day."; + }; + + makeHomeDir = mkOption { + default = false; + type = types.bool; + description = '' + Whether to try to create home directories for users + with $HOMEs pointing to nonexistent + locations on session login. + ''; + }; + + updateWtmp = mkOption { + default = false; + type = types.bool; + description = "Whether to update /var/log/wtmp."; + }; + + logFailures = mkOption { + default = false; + type = types.bool; + description = "Whether to log authentication failures in /var/log/faillog."; + }; + + enableAppArmor = mkOption { + default = false; + type = types.bool; + description = '' + Enable support for attaching AppArmor profiles at the + user/group level, e.g., as part of a role based access + control scheme. + ''; + }; + + enableKwallet = mkOption { + default = false; + type = types.bool; + description = '' + If enabled, pam_wallet will attempt to automatically unlock the + user's default KDE wallet upon login. If the user has no wallet named + "kdewallet", or the login password does not match their wallet + password, KDE will prompt separately after login. + ''; + }; + sssdStrictAccess = mkOption { + default = false; + type = types.bool; + description = "enforce sssd access control"; + }; + + enableGnomeKeyring = mkOption { + default = false; + type = types.bool; + description = '' + If enabled, pam_gnome_keyring will attempt to automatically unlock the + user's default Gnome keyring upon login. If the user login password does + not match their keyring password, Gnome Keyring will prompt separately + after login. + ''; + }; + + text = mkOption { + type = types.nullOr types.lines; + description = "Contents of the PAM service file."; + }; + + }; + + config = { + name = mkDefault name; + setLoginUid = mkDefault cfg.startSession; + limits = mkDefault config.security.pam.loginLimits; + + # !!! TODO: move the LDAP stuff to the LDAP module, and the + # Samba stuff to the Samba module. This requires that the PAM + # module provides the right hooks. + text = mkDefault + ('' + # Account management. + account required pam_unix.so + ${optionalString use_ldap + "account sufficient ${pam_ldap}/lib/security/pam_ldap.so"} + ${optionalString (config.services.sssd.enable && cfg.sssdStrictAccess==false) + "account sufficient ${pkgs.sssd}/lib/security/pam_sss.so"} + ${optionalString (config.services.sssd.enable && cfg.sssdStrictAccess) + "account [default=bad success=ok user_unknown=ignore] ${pkgs.sssd}/lib/security/pam_sss.so"} + ${optionalString config.krb5.enable + "account sufficient ${pam_krb5}/lib/security/pam_krb5.so"} + ${optionalString cfg.googleOsLoginAccountVerification '' + account [success=ok ignore=ignore default=die] ${pkgs.google-compute-engine-oslogin}/lib/pam_oslogin_login.so + account [success=ok default=ignore] ${pkgs.google-compute-engine-oslogin}/lib/pam_oslogin_admin.so + ''} + + # Authentication management. + ${optionalString cfg.googleOsLoginAuthentication + "auth [success=done perm_denied=bad default=ignore] ${pkgs.google-compute-engine-oslogin}/lib/pam_oslogin_login.so"} + ${optionalString cfg.rootOK + "auth sufficient pam_rootok.so"} + ${optionalString cfg.requireWheel + "auth required pam_wheel.so use_uid"} + ${optionalString cfg.logFailures + "auth required pam_tally.so"} + ${optionalString (config.security.pam.enableSSHAgentAuth && cfg.sshAgentAuth) + "auth sufficient ${pkgs.pam_ssh_agent_auth}/libexec/pam_ssh_agent_auth.so file=~/.ssh/authorized_keys:~/.ssh/authorized_keys2:/etc/ssh/authorized_keys.d/%u"} + ${optionalString cfg.fprintAuth + "auth sufficient ${pkgs.fprintd}/lib/security/pam_fprintd.so"} + ${let p11 = config.security.pam.p11; in optionalString cfg.p11Auth + "auth ${p11.control} ${pam_p11}/lib/security/pam_p11.so ${pkgs.opensc}/lib/opensc-pkcs11.so"} + ${let u2f = config.security.pam.u2f; in optionalString cfg.u2fAuth + "auth ${u2f.control} ${pkgs.pam_u2f}/lib/security/pam_u2f.so ${optionalString u2f.debug "debug"} ${optionalString (u2f.authFile != null) "authfile=${u2f.authFile}"} ${optionalString u2f.interactive "interactive"} ${optionalString u2f.cue "cue"}"} + ${optionalString cfg.usbAuth + "auth sufficient ${pkgs.pam_usb}/lib/security/pam_usb.so"} + ${let oath = config.security.pam.oath; in optionalString cfg.oathAuth + "auth requisite ${pkgs.oathToolkit}/lib/security/pam_oath.so window=${toString oath.window} usersfile=${toString oath.usersFile} digits=${toString oath.digits}"} + ${let yubi = config.security.pam.yubico; in optionalString cfg.yubicoAuth + "auth ${yubi.control} ${pkgs.yubico-pam}/lib/security/pam_yubico.so mode=${toString yubi.mode} ${optionalString (yubi.mode == "client") "id=${toString yubi.id}"} ${optionalString yubi.debug "debug"}"} + '' + + # Modules in this block require having the password set in PAM_AUTHTOK. + # pam_unix is marked as 'sufficient' on NixOS which means nothing will run + # after it succeeds. Certain modules need to run after pam_unix + # prompts the user for password so we run it once with 'required' at an + # earlier point and it will run again with 'sufficient' further down. + # We use try_first_pass the second time to avoid prompting password twice + (optionalString (cfg.unixAuth && + (config.security.pam.enableEcryptfs + || cfg.pamMount + || cfg.enableKwallet + || cfg.enableGnomeKeyring + || cfg.googleAuthenticator.enable + || cfg.duoSecurity.enable)) '' + auth required pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth + ${optionalString config.security.pam.enableEcryptfs + "auth optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so unwrap"} + ${optionalString cfg.pamMount + "auth optional ${pkgs.pam_mount}/lib/security/pam_mount.so"} + ${optionalString cfg.enableKwallet + ("auth optional ${pkgs.plasma5.kwallet-pam}/lib/security/pam_kwallet5.so" + + " kwalletd=${pkgs.libsForQt5.kwallet.bin}/bin/kwalletd5")} + ${optionalString cfg.enableGnomeKeyring + "auth optional ${pkgs.gnome3.gnome-keyring}/lib/security/pam_gnome_keyring.so"} + ${optionalString cfg.googleAuthenticator.enable + "auth required ${pkgs.googleAuthenticator}/lib/security/pam_google_authenticator.so no_increment_hotp"} + ${optionalString cfg.duoSecurity.enable + "auth required ${pkgs.duo-unix}/lib/security/pam_duo.so"} + '') + '' + ${optionalString cfg.unixAuth + "auth sufficient pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth try_first_pass"} + ${optionalString cfg.otpwAuth + "auth sufficient ${pkgs.otpw}/lib/security/pam_otpw.so"} + ${optionalString use_ldap + "auth sufficient ${pam_ldap}/lib/security/pam_ldap.so use_first_pass"} + ${optionalString config.services.sssd.enable + "auth sufficient ${pkgs.sssd}/lib/security/pam_sss.so use_first_pass"} + ${optionalString config.krb5.enable '' + auth [default=ignore success=1 service_err=reset] ${pam_krb5}/lib/security/pam_krb5.so use_first_pass + auth [default=die success=done] ${pam_ccreds}/lib/security/pam_ccreds.so action=validate use_first_pass + auth sufficient ${pam_ccreds}/lib/security/pam_ccreds.so action=store use_first_pass + ''} + auth required pam_deny.so + + # Password management. + password sufficient pam_unix.so nullok sha512 + ${optionalString config.security.pam.enableEcryptfs + "password optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"} + ${optionalString cfg.pamMount + "password optional ${pkgs.pam_mount}/lib/security/pam_mount.so"} + ${optionalString use_ldap + "password sufficient ${pam_ldap}/lib/security/pam_ldap.so"} + ${optionalString config.services.sssd.enable + "password sufficient ${pkgs.sssd}/lib/security/pam_sss.so use_authtok"} + ${optionalString config.krb5.enable + "password sufficient ${pam_krb5}/lib/security/pam_krb5.so use_first_pass"} + ${optionalString config.services.samba.syncPasswordsByPam + "password optional ${pkgs.samba}/lib/security/pam_smbpass.so nullok use_authtok try_first_pass"} + ${optionalString cfg.enableGnomeKeyring + "password optional ${pkgs.gnome3.gnome-keyring}/lib/security/pam_gnome_keyring.so use_authtok"} + + # Session management. + ${optionalString cfg.setEnvironment '' + session required pam_env.so conffile=${config.system.build.pamEnvironment} readenv=0 + ''} + session required pam_unix.so + ${optionalString cfg.setLoginUid + "session ${ + if config.boot.isContainer then "optional" else "required" + } pam_loginuid.so"} + ${optionalString cfg.makeHomeDir + "session required ${pkgs.pam}/lib/security/pam_mkhomedir.so silent skel=${config.security.pam.makeHomeDir.skelDirectory} umask=0022"} + ${optionalString cfg.updateWtmp + "session required ${pkgs.pam}/lib/security/pam_lastlog.so silent"} + ${optionalString config.security.pam.enableEcryptfs + "session optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"} + ${optionalString use_ldap + "session optional ${pam_ldap}/lib/security/pam_ldap.so"} + ${optionalString config.services.sssd.enable + "session optional ${pkgs.sssd}/lib/security/pam_sss.so"} + ${optionalString config.krb5.enable + "session optional ${pam_krb5}/lib/security/pam_krb5.so"} + ${optionalString cfg.otpwAuth + "session optional ${pkgs.otpw}/lib/security/pam_otpw.so"} + ${optionalString cfg.startSession + "session optional ${pkgs.systemd}/lib/security/pam_systemd.so"} + ${optionalString cfg.forwardXAuth + "session optional pam_xauth.so xauthpath=${pkgs.xorg.xauth}/bin/xauth systemuser=99"} + ${optionalString (cfg.limits != []) + "session required ${pkgs.pam}/lib/security/pam_limits.so conf=${makeLimitsConf cfg.limits}"} + ${optionalString (cfg.showMotd && config.users.motd != null) + "session optional ${pkgs.pam}/lib/security/pam_motd.so motd=${motd}"} + ${optionalString cfg.pamMount + "session optional ${pkgs.pam_mount}/lib/security/pam_mount.so"} + ${optionalString (cfg.enableAppArmor && config.security.apparmor.enable) + "session optional ${pkgs.apparmor-pam}/lib/security/pam_apparmor.so order=user,group,default debug"} + ${optionalString (cfg.enableKwallet) + ("session optional ${pkgs.plasma5.kwallet-pam}/lib/security/pam_kwallet5.so" + + " kwalletd=${pkgs.libsForQt5.kwallet.bin}/bin/kwalletd5")} + ${optionalString (cfg.enableGnomeKeyring) + "session optional ${pkgs.gnome3.gnome-keyring}/lib/security/pam_gnome_keyring.so auto_start"} + ${optionalString (config.virtualisation.lxc.lxcfs.enable) + "session optional ${pkgs.lxc}/lib/security/pam_cgfs.so -c all"} + ''); + }; + + }; + + + inherit (pkgs) pam_krb5 pam_ccreds; + + use_ldap = (config.users.ldap.enable && config.users.ldap.loginPam); + pam_ldap = if config.users.ldap.daemon.enable then pkgs.nss_pam_ldapd else pkgs.pam_ldap; + + # Create a limits.conf(5) file. + makeLimitsConf = limits: + pkgs.writeText "limits.conf" + (concatMapStrings ({ domain, type, item, value }: + "${domain} ${type} ${item} ${toString value}\n") + limits); + + motd = pkgs.writeText "motd" config.users.motd; + + makePAMService = name: service: + { name = "pam.d/${name}"; + value.source = pkgs.writeText "${name}.pam" service.text; + }; + +in + +{ + + imports = [ + (mkRenamedOptionModule [ "security" "pam" "enableU2F" ] [ "security" "pam" "u2f" "enable" ]) + ]; + + ###### interface + + options = { + + security.pam.loginLimits = mkOption { + default = []; + example = + [ { domain = "ftp"; + type = "hard"; + item = "nproc"; + value = "0"; + } + { domain = "@student"; + type = "-"; + item = "maxlogins"; + value = "4"; + } + ]; + + description = + '' Define resource limits that should apply to users or groups. + Each item in the list should be an attribute set with a + domain, type, + item, and value + attribute. The syntax and semantics of these attributes + must be that described in the limits.conf(5) man page. + + Note that these limits do not apply to systemd services, + whose limits can be changed via + instead. + ''; + }; + + security.pam.services = mkOption { + default = []; + type = with types; loaOf (submodule pamOpts); + description = + '' + This option defines the PAM services. A service typically + corresponds to a program that uses PAM, + e.g. login or passwd. + Each attribute of this set defines a PAM service, with the attribute name + defining the name of the service. + ''; + }; + + security.pam.makeHomeDir.skelDirectory = mkOption { + type = types.str; + default = "/var/empty"; + example = "/etc/skel"; + description = '' + Path to skeleton directory whose contents are copied to home + directories newly created by pam_mkhomedir. + ''; + }; + + security.pam.enableSSHAgentAuth = mkOption { + type = types.bool; + default = false; + description = + '' + Enable sudo logins if the user's SSH agent provides a key + present in ~/.ssh/authorized_keys. + This allows machines to exclusively use SSH keys instead of + passwords. + ''; + }; + + security.pam.enableOTPW = mkEnableOption "the OTPW (one-time password) PAM module"; + + security.pam.p11 = { + enable = mkOption { + default = false; + type = types.bool; + description = '' + Enables P11 PAM (pam_p11) module. + + If set, users can log in with SSH keys and PKCS#11 tokens. + + More information can be found here. + ''; + }; + + control = mkOption { + default = "sufficient"; + type = types.enum [ "required" "requisite" "sufficient" "optional" ]; + description = '' + This option sets pam "control". + If you want to have multi factor authentication, use "required". + If you want to use the PKCS#11 device instead of the regular password, + use "sufficient". + + Read + + pam.conf + 5 + + for better understanding of this option. + ''; + }; + }; + + security.pam.u2f = { + enable = mkOption { + default = false; + type = types.bool; + description = '' + Enables U2F PAM (pam-u2f) module. + + If set, users listed in + $XDG_CONFIG_HOME/Yubico/u2f_keys (or + $HOME/.config/Yubico/u2f_keys if XDG variable is + not set) are able to log in with the associated U2F key. The path can + be changed using option. + + File format is: + username:first_keyHandle,first_public_key: second_keyHandle,second_public_key + This file can be generated using pamu2fcfg command. + + More information can be found here. + ''; + }; + + authFile = mkOption { + default = null; + type = with types; nullOr path; + description = '' + By default pam-u2f module reads the keys from + $XDG_CONFIG_HOME/Yubico/u2f_keys (or + $HOME/.config/Yubico/u2f_keys if XDG variable is + not set). + + If you want to change auth file locations or centralize database (for + example use /etc/u2f-mappings) you can set this + option. + + File format is: + username:first_keyHandle,first_public_key: second_keyHandle,second_public_key + This file can be generated using pamu2fcfg command. + + More information can be found here. + ''; + }; + + control = mkOption { + default = "sufficient"; + type = types.enum [ "required" "requisite" "sufficient" "optional" ]; + description = '' + This option sets pam "control". + If you want to have multi factor authentication, use "required". + If you want to use U2F device instead of regular password, use "sufficient". + + Read + + pam.conf + 5 + + for better understanding of this option. + ''; + }; + + debug = mkOption { + default = false; + type = types.bool; + description = '' + Debug output to stderr. + ''; + }; + + interactive = mkOption { + default = false; + type = types.bool; + description = '' + Set to prompt a message and wait before testing the presence of a U2F device. + Recommended if your device doesn’t have a tactile trigger. + ''; + }; + + cue = mkOption { + default = false; + type = types.bool; + description = '' + By default pam-u2f module does not inform user + that he needs to use the u2f device, it just waits without a prompt. + + If you set this option to true, + cue option is added to pam-u2f + module and reminder message will be displayed. + ''; + }; + }; + + security.pam.yubico = { + enable = mkOption { + default = false; + type = types.bool; + description = '' + Enables Yubico PAM (yubico-pam) module. + + If set, users listed in + ~/.yubico/authorized_yubikeys + are able to log in with the associated Yubikey tokens. + + The file must have only one line: + username:yubikey_token_id1:yubikey_token_id2 + More information can be found here. + ''; + }; + control = mkOption { + default = "sufficient"; + type = types.enum [ "required" "requisite" "sufficient" "optional" ]; + description = '' + This option sets pam "control". + If you want to have multi factor authentication, use "required". + If you want to use Yubikey instead of regular password, use "sufficient". + + Read + + pam.conf + 5 + + for better understanding of this option. + ''; + }; + id = mkOption { + example = "42"; + type = types.str; + description = "client id"; + }; + + debug = mkOption { + default = false; + type = types.bool; + description = '' + Debug output to stderr. + ''; + }; + mode = mkOption { + default = "client"; + type = types.enum [ "client" "challenge-response" ]; + description = '' + Mode of operation. + + Use "client" for online validation with a YubiKey validation service such as + the YubiCloud. + + Use "challenge-response" for offline validation using YubiKeys with HMAC-SHA-1 + Challenge-Response configurations. See the man-page ykpamcfg(1) for further + details on how to configure offline Challenge-Response validation. + + More information can be found here. + ''; + }; + }; + + security.pam.enableEcryptfs = mkEnableOption "eCryptfs PAM module (mounting ecryptfs home directory on login)"; + + users.motd = mkOption { + default = null; + example = "Today is Sweetmorn, the 4th day of The Aftermath in the YOLD 3178."; + type = types.nullOr types.lines; + description = "Message of the day shown to users when they log in."; + }; + + }; + + + ###### implementation + + config = { + + environment.systemPackages = + # Include the PAM modules in the system path mostly for the manpages. + [ pkgs.pam ] + ++ optional config.users.ldap.enable pam_ldap + ++ optional config.services.sssd.enable pkgs.sssd + ++ optionals config.krb5.enable [pam_krb5 pam_ccreds] + ++ optionals config.security.pam.enableOTPW [ pkgs.otpw ] + ++ optionals config.security.pam.oath.enable [ pkgs.oathToolkit ] + ++ optionals config.security.pam.p11.enable [ pam_p11 ] + ++ optionals config.security.pam.u2f.enable [ pkgs.pam_u2f ]; + + boot.supportedFilesystems = optionals config.security.pam.enableEcryptfs [ "ecryptfs" ]; + + security.wrappers = { + unix_chkpwd = { + source = "${pkgs.pam}/sbin/unix_chkpwd.orig"; + owner = "root"; + setuid = true; + }; + }; + + environment.etc = mapAttrs' makePAMService config.security.pam.services; + + security.pam.services = + { other.text = + '' + auth required pam_warn.so + auth required pam_deny.so + account required pam_warn.so + account required pam_deny.so + password required pam_warn.so + password required pam_deny.so + session required pam_warn.so + session required pam_deny.so + ''; + + # Most of these should be moved to specific modules. + i3lock = {}; + i3lock-color = {}; + vlock = {}; + xlock = {}; + xscreensaver = {}; + + runuser = { rootOK = true; unixAuth = false; setEnvironment = false; }; + + /* FIXME: should runuser -l start a systemd session? Currently + it complains "Cannot create session: Already running in a + session". */ + runuser-l = { rootOK = true; unixAuth = false; }; + }; + + }; + +} diff --git a/nixops/pam_p11/pam_p11.nix b/nixops/pam_p11/pam_p11.nix new file mode 100644 index 0000000..2257bd5 --- /dev/null +++ b/nixops/pam_p11/pam_p11.nix @@ -0,0 +1,23 @@ +{ stdenv, fetchFromGitHub, autoreconfHook, pkg-config, openssl, libp11, pam }: + +stdenv.mkDerivation rec { + pname = "pam_p11"; + version = "0.3.1"; + + src = fetchFromGitHub { + owner = "OpenSC"; + repo = "pam_p11"; + rev = "pam_p11-${version}"; + sha256 = "1caidy18rq5zk82d51x8vwidmkhwmanf3qm25x1yrdlbhxv6m7lk"; + }; + + patchPhase = + '' + substituteInPlace src/match_openssh.c --replace \ + '"%s/.ssh/authorized_keys", pw->pw_dir)' \ + '"/etc/ssh/authorized_keys.d/%s", pw->pw_name)' + ''; + + nativeBuildInputs = [ autoreconfHook pkg-config ]; + buildInputs = [ pam openssl libp11 ]; +} diff --git a/nixops/rpi.nix b/nixops/rpi.nix new file mode 100644 index 0000000..b96bbc0 --- /dev/null +++ b/nixops/rpi.nix @@ -0,0 +1,63 @@ +{ host, rpi4 }: + +{ config, pkgs, ... }: +let + m-labs = import (fetchTarball https://nixbld.m-labs.hk/channel/custom/artiq/full/artiq-full/nixexprs.tar.xz) { inherit pkgs; }; +in +{ + deployment.targetHost = host; + nixpkgs.system = "aarch64-linux"; + + boot.loader.grub.enable = false; + + boot.loader.generic-extlinux-compatible.enable = !rpi4; + boot.loader.raspberryPi = pkgs.lib.mkIf rpi4 { + enable = true; + version = 4; + }; + boot.kernelPackages = pkgs.lib.mkIf rpi4 pkgs.linuxPackages_latest; + + fileSystems = if rpi4 then { + "/boot" = { + device = "/dev/disk/by-label/FIRMWARE"; + fsType = "vfat"; + }; + "/" = { + device = "/dev/disk/by-label/NIXOS_SD"; + fsType = "ext4"; + }; + } else { + "/" = { + device = "/dev/disk/by-label/NIXOS_SD"; + fsType = "ext4"; + }; + }; + + services.openssh.enable = true; + services.openssh.passwordAuthentication = false; + + networking.hostName = host; + time.timeZone = "Asia/Hong_Kong"; + + users.extraGroups.plugdev = { }; + users.mutableUsers = false; + users.defaultUserShell = pkgs.fish; + users.extraUsers = (import ./common-users.nix { inherit pkgs; }) // { + nix = { + isNormalUser = true; + }; + }; + security.sudo.wheelNeedsPassword = false; + services.udev.packages = [ m-labs.openocd ]; + + documentation.enable = false; + environment.systemPackages = with pkgs; [ + psmisc wget vim git usbutils lm_sensors file telnet mosh tmux xc3sprog m-labs.openocd screen gdb minicom picocom + ]; + programs.fish.enable = true; + programs.wireshark.enable = true; + + nix.binaryCachePublicKeys = ["nixbld.m-labs.hk-1:5aSRVA5b320xbNvu30tqxVPXpld73bhtOeH6uAjRyHc="]; + nix.binaryCaches = ["https://cache.nixos.org" "https://nixbld.m-labs.hk"]; + nix.trustedUsers = ["root" "nix"]; +} diff --git a/nixops/zeus-hardware-configuration.nix b/nixops/zeus-hardware-configuration.nix new file mode 100644 index 0000000..bcbec3d --- /dev/null +++ b/nixops/zeus-hardware-configuration.nix @@ -0,0 +1,33 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ config, lib, pkgs, ... }: + +{ + imports = + [ + ]; + + boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "nvme" "usbhid" "usb_storage" "sd_mod" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = + { device = "/dev/disk/by-uuid/890b20a2-08d5-4635-b3a7-003ad4a11a19"; + fsType = "ext4"; + }; + + fileSystems."/boot" = + { device = "/dev/disk/by-uuid/91B4-E546"; + fsType = "vfat"; + }; + + swapDevices = [ ]; + + nix.maxJobs = lib.mkDefault 16; + powerManagement.cpuFreqGovernor = lib.mkDefault "powersave"; + + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; +}