commit a8947d6822c35006b63b013474deb8ba445e280e Author: rnhmjoj Date: Tue Oct 20 01:11:28 2020 +0200 initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..699dbbc --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +#pattern filter=crypt diff=crypt +secrets/*/** filter=crypt diff=crypt +secrets/default.nix filter=crypt diff=crypt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b2be92b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +result diff --git a/assets/magnetico-merge.py b/assets/magnetico-merge.py new file mode 100644 index 0000000..9abd1e8 --- /dev/null +++ b/assets/magnetico-merge.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 + +import sqlite3 +import argparse + + +def main(main_db, merged_db): + print(f"Merging {merged_db} into {main_db}") + connection = sqlite3.connect(main_db) + connection.row_factory = sqlite3.Row + connection.text_factory = bytes + + cursor = connection.cursor() + cursor.execute("ATTACH ? AS merged_db", (merged_db,)) + print("Gathering database statistics:") + + cursor.execute("SELECT count(*) from merged_db.torrents") + total_merged = cursor.fetchone()[0] + cursor.execute( + "SELECT name FROM pragma_table_info('files') " + "WHERE name not in ('id', 'torrent_id')" + ) + remaining_file_colums = [row[0].decode() for row in cursor] + cursor.execute( + "SELECT name FROM pragma_table_info('torrents')" + "WHERE name not in ('id')" + ) + remaining_torrent_colums = [row[0].decode() for row in cursor] + + print(f"{total_merged} torrents to merge.") + + insert_files_statement = ( + f"INSERT INTO files (torrent_id, {','.join(remaining_file_colums)}) " + f"SELECT ?, {','.join(remaining_file_colums)} " + f"FROM merged_db.files WHERE torrent_id = ?" + ) + insert_torrents_statement = ( + f"INSERT INTO torrents ({','.join(remaining_torrent_colums)})" + f"VALUES ({','.join('?' * len(remaining_torrent_colums))})" + ) + failed_count = 0 + + cursor.execute("BEGIN") + merged = cursor.execute("SELECT * FROM merged_db.torrents") + for i, row in enumerate(merged): + try: + torrent_merge = connection.execute( + insert_torrents_statement, (*row[1:],)) + # Now merge files + connection.execute( + insert_files_statement, (torrent_merge.lastrowid, row["id"])) + except sqlite3.IntegrityError: + failed_count += 1 + + print("Comitting… ", end="") + connection.commit() + print("OK." + f"{total_merged} torrents processed.", + f"{total_merged - failed_count} new torrents added.", + sep="\n") + connection.close() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Tool to merge magnetico DBs") + parser.add_argument("main", type=str, + help="main dabatase") + parser.add_argument("merge", type=str, + help="dabatase to merge into main") + args = parser.parse_args() + main(args.main, args.merge) diff --git a/configuration.nix b/configuration.nix new file mode 100644 index 0000000..964bbfc --- /dev/null +++ b/configuration.nix @@ -0,0 +1,439 @@ +{ config, lib, pkgs, ... }: + +{ + imports = [ + ./hardware.nix + ./variables.nix + ./packages.nix + ./jobs.nix + ./matrix.nix + ./magnetico.nix + ./nameserver.nix + ./custom + ./secrets + ]; + + ### State + # Stateful things to do before updating: + # 1. Postgres migration + # 2. Matrix Synapse migration + system.stateVersion = "20.03"; + + boot.kernelPackages = pkgs.linuxPackages_latest; + boot.tmpOnTmpfs = true; + boot.kernel.sysctl = { + # avoid OOM hangs + "vm.admin_reserve_kbytes" = 262144; + }; + + time.timeZone = "Europe/Rome"; + i18n.defaultLocale = "en_US.UTF-8"; + + systemd.enableEmergencyMode = false; + + networking = { + hostName = "maxwell"; + + firewall.allowedTCPPorts = [ + 443 80 # reverse proxy + 8080 # hubot + 5349 # turn server + 5350 # turn server + 3551 # apcups + 5001 # iperf server + 18080 # monero p2p + 20000 # syncthing transfert + 64738 # mumble server + ]; + firewall.allowedUDPPorts = [ + 53 # powerdns + 1194 # dnscrypt + 21027 # syncthing discovery + 64738 # mumble server + ]; + firewall.allowedUDPPortRanges = [ + { from=49152; to=49999; } # turn relay + ]; + + usePredictableInterfaceNames = false; + nameservers = [ "127.0.0.1" ]; + hosts."127.0.0.1" = [ config.var.hostname ]; + }; + + # Only declarative users and no password logins + users.mutableUsers = false; + + users.users ={ + # Only needed for local (read emergency) shell access + root.passwordFile = config.secrets.passwords.root; + + # Admin + rnhmjoj = { + uid = 1000; + extraGroups = [ "wheel" ]; + isNormalUser = true; + shell = pkgs.fish; + openssh.authorizedKeys.keyFiles = [ config.secrets.publicKeys.rnhmjoj ]; + }; + + # Admin + fazo = { + extraGroups = [ "wheel" ]; + isNormalUser = true; + openssh.authorizedKeys.keyFiles = [ config.secrets.publicKeys.fazo]; + }; + + # Runs two chatbots + meme = { + extraGroups = [ "ubino" "miguelbridge" ]; + isNormalUser = true; + shell = pkgs.fish; + openssh.authorizedKeys.keyFiles = [ config.secrets.publicKeys.meme ]; + }; + + # Hosts the cactalogo + giu = { + isNormalUser = true; + shell = pkgs.fish; + openssh.authorizedKeys.keyFiles = with config.secrets.publicKeys; + [ rnhmjoj giu ]; + }; + + # Needed to perform remote builds on Maxwell + builder = { + description = "Remote Nix builds user"; + isNormalUser = true; + openssh.authorizedKeys.keyFiles = [ config.secrets.publicKeys.rnhmjoj-builder ]; + }; + + # Use "git" instead of the default name to make + # SSH operation handier, example: + # git clone git@maxwell:user/repo + git = { + description = "Git server user"; + home = "/var/lib/gitea"; + useDefaultShell = true; + }; + }; + + # Generate Diffie-Hellman parameters + # for TLS applications, like nginx. + security.dhparams = { + enable = true; + params.nginx = 2048; # prime modulus bits + }; + + security.sudo = { + enable = true; + # Users don't have a password + wheelNeedsPassword = false; + extraConfig = + let + path = "/run/current-system/sw/bin"; + journal = name: "${path}/journalctl -* ${name}"; + services = lib.concatMapStringsSep "," (name: "${journal name}"); + in '' + # Allow meme to see his logs. + Cmnd_Alias MEME_UNITS = ${services ["ubino" "miguelbridge"]} + meme ALL=(root) NOPASSWD: MEME_UNITS + ''; + }; + + security.polkit.extraConfig = '' + // Allow meme to manage his services. + polkit.addRule(function(action, subject) { + if (action.id == "org.freedesktop.systemd1.manage-units" && + subject.user == "meme" && + (action.lookup("unit") == "ubino.service" || + action.lookup("unit") == "miguelbridge.service")) { + return polkit.Result.YES; + } + }); + ''; + + # Limit user process to stop fork bombs + security.pam.loginLimits = [ + { domain = "@users"; + type = "hard"; + item = "nproc"; + value = "400"; + } + ]; + + ### ACME certificates + security.acme = with config.var; { + email = "rnhmjoj@inventati.org"; + acceptTerms = true; + + certs."${hostname}" = { + group = "maxwell-ydns-eu"; + }; + + certs."riot.${hostname}" = { + group = "riot-maxwell-ydns-eu"; + }; + }; + + # Allow read access to ACME certificate + # to specific (service) users. + users.groups."maxwell-ydns-eu".members = [ "murmur" "turnserver" ]; + users.groups."riot-maxwell-ydns-eu".members = [ "nginx" ]; + + + services.openssh = { + enable = true; + permitRootLogin = "no"; + passwordAuthentication = false; + challengeResponseAuthentication = false; + }; + + # Traceroute easter egg + services.fakeroute = { + enable = true; + route = [ + "89.111.117.32" "99.97.110.110" "111.116.32.104" "105.100.101.46" + "32.73.32.115" "101.101.32.121" "111.117.46.32" "84.104.101.114" + "101.32.105.115" "32.110.111.32" "108.105.102.101" "32.105.110.32" + "116.104.101.32" "118.111.105.100" "46.32.79.110" "108.121.32.100" + "101.97.116.104" ]; + }; + + ### Mumble server + services.murmur = { + enable = true; + password = "allwellthatmaxwell"; + registerHostname = config.var.hostname; + registerName = "Maxwell Mumble"; + registerPassword = config.secrets.murmur.password; + users = 10; + extraConfig = with config.var; '' + sslCert=/var/lib/acme/${hostname}/fullchain.pem + sslKey=/var/lib/acme/${hostname}/key.pem + ''; + }; + + ### Syncthing node + services.syncthing = { + enable = true; + openDefaultPorts = true; + }; + + ### Monero node with local RPC + services.monero = { + enable = true; + mining = { + enable = false; + threads = 4; + address = config.secrets.monero.address; + }; + limits = { + upload = 250; + download = 625; + threads = 4; + }; + rpc.user = config.secrets.monero.user; + rpc.password = config.secrets.monero.password; + }; + + ### URL shortner + services.breve = { + enable = true; + hostname = "localhost"; + baseUrl = "https://brve.bit/"; + port = 2000; + certificate = "/var/lib/breve/breve.crt"; + key = "/var/lib/breve/breve.key"; + }; + + ### Git server + services.gitea = with config.var; { + enable = true; + domain = hostname; + appName = "Maxwell git server"; + rootUrl = "https://${hostname}/git/"; + user = "git"; + database.user = "git"; + log.level = "Error"; + cookieSecure = true; + disableRegistration = false; + settings = { + security.LOGIN_REMEMBER_DAYS = 365; + attachment.MAX_SIZE = 10; + }; + }; + + ### Searx instance + services.searx = { + enable = true; + configFile = ./assets/searx-settings.yml; + }; + + + ### Reverse Proxy + services.nginx = + with config.var; + let + disableLog = '' + error_log syslog:server=unix:/dev/log crit; + access_log off; + ''; + enableSTS = '' + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + ''; + in + { + enable = true; + enableReload = true; + commonHttpConfig = '' + # recommendedTlsSettings = true; + # android doesn't like this one: + # ssl_ecdh_curve secp384r1; + ssl_session_cache shared:SSL:42m; + ssl_session_timeout 23m; + ssl_prefer_server_ciphers on; + ssl_stapling on; + ssl_stapling_verify on; + ''; + recommendedGzipSettings = true; + recommendedProxySettings = true; + + # Large enough to allow file uploads. + clientMaxBodySize = "1000M"; + + sslDhparam = "${config.security.dhparams.path}/nginx.pem"; + + # Maxwell + virtualHosts."${hostname}" = + { + enableACME = true; + forceSSL = true; + default = true; + + extraConfig = disableLog + enableSTS; + + # Returns IP address + locations."/ip".extraConfig = "return 200 $remote_addr;"; + + # Asjon code coverage reports + locations."/asjon/report/" = { + index = "index.html"; + alias = "/var/lib/asjon/tree/report/"; + }; + + # Searx instance + locations."/srx/" = { + proxyPass = "http://localhost:8083/"; + extraConfig = '' + proxy_set_header X-Scheme $scheme; + proxy_set_header X-Script-Name /srx/; + proxy_buffering off; + ''; + }; + + # Git server + locations."/git/" .proxyPass = "http://localhost:3000/"; + + # Syncthing + locations."/sync/".proxyPass = "http://localhost:8384/"; + }; + + # Breve URL shortner + virtualHosts."brve.bit" = { + forceSSL = true; + + sslCertificate = "/var/lib/breve/breve.crt"; + sslCertificateKey = "/var/lib/breve/breve.key"; + + locations."/" = { + proxyPass = "https://localhost:2000"; + extraConfig = "proxy_ssl_verify off;"; + }; + extraConfig = disableLog; + }; + + # The Cactalogue + virtualHosts."cacta.bit" = { + locations."/".alias = "/home/giu/cactalogue/"; + extraConfig = disableLog; + }; + }; + + + ### Misc. services + services.ubino.enable = true; + services.miguelbridge.enable = true; + services.asjon.enable = true; + + # Needed for the Asjon memory module + services.redis.enable = true; + + + ### Program configuration + programs = { + fish.enable = true; + mosh.enable = true; + tmux = { + enable = true; + newSession = true; + baseIndex = 1; + escapeTime = 0; + historyLimit = 4096; + keyMode = "vi"; + terminal = "screen-256color"; + customPaneNavigationAndResize = true; + extraConfig = '' + set -g mouse on + + # bindings + bind | split-window -h + bind - split-window -v + bind : command-prompt + bind -n C-k clear-history + + # colors + set -g pane-border-style fg=brightblack + set -g pane-active-border fg=green + set -g message-style fg=white,bg=black + set -g status-style fg=brightblue,bg=black + setw -g mode-style fg=black,bg=cyan + + # status line + set -g status on + set -g status-justify left + set -g status-left "" + set -g status-right-length 60 + set -g status-right '#[fg=yellow]#(cut -d\ -f 1-3 /proc/loadavg) | #[fg=brightgreen]%a %H:%M' + setw -g window-status-format "#[fg=black#,bg=brightblack] #I #[fg=blue#,bg=black] #W " + setw -g window-status-current-format "#[fg=white#,bg=cyan] #I #[fg=black#,bg=brightblack] #W " + ''; + }; + }; + + nix = { + useSandbox = true; + # Can connect to the Nix daemon + # and upload/run code as root! + trustedUsers = [ "builder" "rnhmjoj" ]; + # Use at most half the cores + buildCores = 8; + extraOptions = '' + # Always keep at least 256MiB free + min-free = 268435456 + ''; + }; + + environment.variables = { + PATH = "$HOME/.local/bin/:$PATH"; + XDG_CONFIG_HOME = "$HOME/.config"; + XDG_DATA_HOME = "$HOME/.local/share"; + XDG_CACHE_HOME = "$HOME/.cache"; + NIX_PROFILE = "$XDG_CONFIG_HOME/nix/profile"; + }; + + # Needed to make the mosh server survive a + # user logout: systemd kills everything by default + environment.shellAliases = { + mosh-server = "systemd-run --user --scope mosh-server"; + }; + +} diff --git a/custom/default.nix b/custom/default.nix new file mode 100644 index 0000000..07c2e3a --- /dev/null +++ b/custom/default.nix @@ -0,0 +1,17 @@ +{ ... }: + +# These are custom NixOS modules that are +# not yet in Nixpkgs or can't be upstreamed. +{ + imports = + [ # Misc. system services + ./modules/breve.nix + ./modules/asjon.nix + ./modules/ubino.nix + ./modules/miguelbridge.nix + + # Safely handle secrets + ./modules/secrets-store.nix + ]; + +} diff --git a/custom/modules/asjon.nix b/custom/modules/asjon.nix new file mode 100644 index 0000000..24653fb --- /dev/null +++ b/custom/modules/asjon.nix @@ -0,0 +1,109 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.asjon; + +in { + + options.services.asjon = { + enable = mkEnableOption "Asjon: our chat bot"; + + dataDir = mkOption { + type = types.path; + default = "/var/lib/asjon"; + description = '' + Path where the settings and source tree will exist. + ''; + }; + + user = mkOption { + type = types.str; + default = "asjon"; + description = '' + Asjon will be run under this user (user will be created if it doesn't exist. + This can be your user name). + ''; + }; + + }; + + config = mkIf cfg.enable { + + users.extraUsers."${cfg.user}" = { + home = cfg.dataDir; + createHome = true; + description = "asjon user"; + shell = "${pkgs.bash}/bin/bash"; + }; + + systemd.services.asjon = { + description = "asjon: our chat bot"; + after = [ "nginx.service" "matrix-synapse.service" "asjon-init.service" ]; + requires = [ "nginx.service" "matrix-synapse.service" "asjon-init.service" ]; + wantedBy = [ "multi-user.target" ]; + + path = with pkgs; [ + nodejs nodePackages.coffee-script + yarn openssh graphicsmagick git + bash + ]; + + environment = { + # Matrix login + HUBOT_MATRIX_HOST_SERVER = "https://${config.var.hostname}"; + + # Git integration + HUBOT_GIT_URL = "https://${config.var.hostname}/git"; + HUBOT_GIT_API = "https://${config.var.hostname}/git/api/v1"; + HUBOT_GIT_REPO = "rnhmjoj/asjon"; + + # Scripts + AUTO_KILL_ON_UPDATE = "1"; + AUTO_INFORM_ON_START = "!kvLvoCovzInhiablSq:maxwell.ydns.eu"; + ADMIN_ROOM = "!kvLvoCovzInhiablSq:maxwell.ydns.eu"; + REV_REMOTE_HOST = "proxy@rnhmjoj.ydns.eu"; + REV_REMOTE_PORT = "22"; + REV_KEY = "~/.ssh/proxy"; + }; + + serviceConfig = { + User = cfg.user; + ExecStart = "${cfg.dataDir}/tree/bin/hubot -a matrix"; + Restart = "always"; + WorkingDirectory = "${cfg.dataDir}/tree"; + # API keys and passwords definitions + EnvironmentFile = config.secrets.asjon.environment; + }; + + }; + + systemd.services.asjon-init = { + description = "Initialize Asjon service (first time only)"; + wants = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig.User = cfg.user; + path = with pkgs; [ git yarn acl ]; + + script = '' + if test -d ${cfg.dataDir}/tree/.git; then + exit 0 + fi + + # clone repository and install packages + git clone https://github.com/rnhmjoj/asjon.git ${cfg.dataDir}/tree + cd ${cfg.dataDir}/tree + yarn install + + # give read/traverse permission to nginx + setfacl -m g:nginx:x ${cfg.dataDir} + setfacl -m g:nginx:x ${cfg.dataDir}/tree + setfacl -Rdm g:nginx:rx ${cfg.dataDir}/tree/report + ''; + }; + + }; + +} diff --git a/custom/modules/breve.nix b/custom/modules/breve.nix new file mode 100644 index 0000000..9357e09 --- /dev/null +++ b/custom/modules/breve.nix @@ -0,0 +1,137 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.breve; + dataDir = "/var/lib/breve"; + + configFile = pkgs.writeText "breve.conf" '' + hostname = "${cfg.hostname}" + port = ${toString cfg.port} + baseurl = "${cfg.baseUrl}" + urltable = "${dataDir}/urls" + tls { + cert = "${cfg.certificate}" + key = "${cfg.key}" + } + ''; + +in { + + options.services.breve = { + enable = mkEnableOption '' + Breve: a url shortner service. + ''; + + openPorts = mkOption { + type = types.bool; + default = false; + example = literalExample "true"; + description = '' + Open the default ports in the firewall: + - TCP 443 (or specific port) for HTTPS + - TCP 80 (or specific port) for HTTP->HTTPS redirect + ''; + }; + + user = mkOption { + type = types.str; + default = "breve"; + description = '' + Breve will run under this user (user will be created if it doesn't exist. + This can be your user name). + ''; + }; + + hostname = mkOption { + type = types.str; + default = config.networking.hostName; + description = '' + Breve will bind and generate URLs accorting to this hostname. + ''; + }; + + baseUrl = mkOption { + type = types.str; + default = "https://localhost:3000/"; + example = "https://example.com"; + description = '' + URL to reach the breve index page. Needed in case Breve is served by + a reverse proxy on a different url. + ''; + }; + + port = mkOption { + type = types.int; + default = 443; + example = 8080; + description = '' + Breve main interface will be listening on this port. + ''; + }; + + certificate = mkOption { + type = types.path; + default = "${dataDir}/breve.crt"; + description = '' + The TLS certificate that Breve will be using to encrypt traffic. + ''; + }; + + key = mkOption { + type = types.path; + default = "${dataDir}/breve.key"; + description = '' + The TLS key that Breve will be using to encrypt traffic. + ''; + }; + + certificateChain = mkOption { + type = types.listOf types.path; + default = []; + description = '' + List of paths to the TLS certificates chain. + ''; + }; + }; + + config = mkIf cfg.enable { + + users.extraUsers."${cfg.user}" = { + isSystemUser = true; + description = "Breve daemon user"; + }; + + networking.firewall = mkIf cfg.openPorts { + allowedTCPPorts = [ cfg.port ] + ++ optional (cfg.port == 443) 80; + }; + + systemd.services.breve = { + description = "breve: url shortner"; + wants = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + environment.XDG_CONFIG_HOME = "${dataDir}/conf"; + serviceConfig = { + User = cfg.user; + ExecStart = "${pkgs.haskellPackages.breve}/bin/breve"; + Restart = "on-failure"; + StateDirectory = "breve"; + }; + + preStart = '' + # link configuration + mkdir -p ${dataDir}/conf + if [ "$(realpath ${dataDir}/conf/breve)" != "${configFile}" ]; then + rm -f ${dataDir}/conf/breve + ln -s ${configFile} ${dataDir}/conf/breve + fi + ''; + + }; + + }; + +} diff --git a/custom/modules/miguelbridge.nix b/custom/modules/miguelbridge.nix new file mode 100644 index 0000000..1ec35c0 --- /dev/null +++ b/custom/modules/miguelbridge.nix @@ -0,0 +1,52 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.miguelbridge; + +in { + + options.services.miguelbridge = { + enable = mkEnableOption "miguelbridge: Bridge Telegram - Matrix."; + + user = mkOption { + type = types.str; + default = "miguelbridge"; + description = '' + miguelbridge will be run under this user (user will be created if it doesn't exist. + This can be your user name). + ''; + }; + + }; + + config = mkIf cfg.enable { + + users.groups.miguelbridge = {}; + + users.extraUsers."${cfg.user}" = { + isSystemUser = true; + group = "miguelbridge"; + description = "miguelbridge user"; + }; + + systemd.services.miguelbridge = { + description = "miguelbridge: Bridge Telegram - Matrix"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + User = cfg.user; + Group = "miguelbridge"; + ExecStart = "${pkgs.openjdk}/bin/java -jar MiguelBridge.jar"; + Restart = "always"; + StateDirectory = "miguelbridge"; + WorkingDirectory = "%S/miguelbridge"; + }; + + }; + + }; + +} diff --git a/custom/modules/secrets-store.nix b/custom/modules/secrets-store.nix new file mode 100644 index 0000000..80986db --- /dev/null +++ b/custom/modules/secrets-store.nix @@ -0,0 +1,117 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.security.runtimeSecrets; + + # A recursive attrset of submodule + storeType = types.attrsOf (types.submodule + { freeformType = storeType; + options = secretOptions; + }); + + # Secret file definition + secretOptions = + { user = mkOption + { type = types.str; + default = "root"; + description = "Owner of the secret"; + }; + group = mkOption + { type = types.str; + default = "root"; + description = "Group with access to the secret."; + }; + mode = mkOption + { type = types.str; + default = "0400"; + description = "File permission (octal format)"; + }; + path = mkOption + { type = types.nullOr types.path; + default = null; + apply = toString; + description = "File to include in the secret store"; + }; + }; + + # Turns a nested attrset into a list + # of (path, value) pairs. It recurs + # until `cond val` is false. + attrsToIndex = cond: set: + let recurse = path: set: + let index = name: value: + if isAttrs value && cond value + then recurse (path ++ [name]) value + else singleton { path = path ++ [name]; value = value; }; + in concatLists (mapAttrsToList index set); + in recurse [] set; + + isFile = v: isAttrs v && v.path != ""; + + # Secrets flattened to an index. This is needed + # to iterate over the set. + flatSecrets = attrsToIndex (v: !isFile v) cfg; + + # Secrets with paths rewritten to the store location + storedSecrets = mapAttrsRecursiveCond (v: !isFile v) + (names: secret: + if isFile secret + then "/run/secret/${concatStringsSep "-" names}" + else secret) cfg; + +in { + options.security.runtimeSecrets = mkOption { + type = storeType; + default = { }; + description = '' + Definitions of runtime secrets. This is a freeform attributes + set: it can contain arbitrarily nested sets of secrets. + Secrets are paths to be copied into the secrets store + (/run/secrets) with proper permission and owenership. + ''; + }; + + options.security.buildSecrets = mkOption { + type = types.attrs; + default = { }; + description = '' + Definitions of build secrets. This is a freeform attrset + to be merged with the secrets-store and intended to store + unsafe secrets. This will be copied into the world-readable + Nix store, only use at a last resort. + ''; + }; + + options.secrets = mkOption { + type = types.attrs; + readOnly = true; + default = recursiveUpdate storedSecrets config.security.buildSecrets; + description = '' + The attrset used to access stored secrets from NixOS + configuration and modules. + ''; + }; + + config.system.activationScripts.secretsStore = { + deps = [ ]; + text = + '' + # Initialise clean directory + rm -rf /run/secrets + '' + concatMapStrings (pair: + let + name = "${concatStringsSep "-" pair.path}"; + secret = pair.value; + in + optionalString (isFile secret) + '' + # Install secret ${name} + install -m ${secret.mode} \ + -o ${secret.user} -g ${secret.group} \ + -D ${secret.path} /run/secrets/${name} + '') flatSecrets; + }; + +} diff --git a/custom/modules/ubino.nix b/custom/modules/ubino.nix new file mode 100644 index 0000000..56d475e --- /dev/null +++ b/custom/modules/ubino.nix @@ -0,0 +1,52 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.ubino; + +in { + + options.services.ubino = { + enable = mkEnableOption "Ubino: assistente virtuale di Ube, sottoforma di bot di Telegram."; + + user = mkOption { + type = types.str; + default = "ubino"; + description = '' + Ubino will be run under this user (user will be created if it doesn't exist. + This can be your user name). + ''; + }; + + }; + + config = mkIf cfg.enable { + + users.groups.ubino = {}; + + users.extraUsers."${cfg.user}" = { + isSystemUser = true; + group = "ubino"; + description = "Ubino user"; + }; + + systemd.services.ubino = { + description = "Ubino: assistente virtuale di Ube, sottoforma di bot di Telegram."; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + User = cfg.user; + Group = "ubino"; + ExecStart = "${pkgs.openjdk}/bin/java -jar UbinoBot.jar"; + Restart = "always"; + StateDirectory = "ubino"; + WorkingDirectory = "%S/ubino"; + }; + + }; + + }; + +} diff --git a/custom/packages/maxwell-notify.nix b/custom/packages/maxwell-notify.nix new file mode 100644 index 0000000..a736066 --- /dev/null +++ b/custom/packages/maxwell-notify.nix @@ -0,0 +1,20 @@ +{ writeScriptBin, fish, curl +, homeserver +, roomId +, authToken +}: + +writeScriptBin "notify" '' + #!${fish}/bin/fish + + set token (cat ${authToken}) + + if test (id -u) != 0 + echo 'you must be root to send a notice' + exit 1 + end + + set url '${homeserver}/rooms/${roomId}/send/m.room.message?access_token='$token + set msg '{"msgtype":"m.text", "body": "'$argv[1]'"}' + ${curl}/bin/curl -s -XPOST -d $msg $url +'' diff --git a/hardware.nix b/hardware.nix new file mode 100644 index 0000000..d13a000 --- /dev/null +++ b/hardware.nix @@ -0,0 +1,85 @@ +{ config, lib, pkgs, ... }: + +{ + imports = [ + + ]; + + boot.kernelModules = [ "kvm-intel" ]; + boot.initrd.availableKernelModules = [ + "uhci_hcd" "ehci_pci" "ata_piix" + "usbhid" "usb_storage" "sd_mod" + ]; + boot.loader.grub = { + enable = true; + version = 2; + device = "/dev/sda"; + }; + + fileSystems."/" = + { device = "/dev/main/nixos"; + fsType = "ext4"; + }; + + fileSystems."/home" = + { device = "/dev/main/home"; + fsType = "ext4"; + }; + + fileSystems."/var/lib" = + { device = "/dev/data/data"; + fsType = "ext4"; + }; + + nix.maxJobs = lib.mkDefault 16; + powerManagement.cpuFreqGovernor = "ondemand"; + + services.apcupsd = { + enable = true; + configText = '' + UPSTYPE usb + UPSCABLE usb + NETSERVER on + NISPORT 3551 + MINUTES 5 + ''; + hooks = + let + # Send notifications on the Maxwell + # room when something bad happens. + notify = msg: ''${pkgs.maxwell-notify}/bin/notify "UPS: ${msg}"''; + in + { + changeme = notify "sostituire le batterie"; + battdetach = notify "batterie disconnesse"; + battattach = notify "batterie riconnesse"; + commfailure = notify "connessione persa"; + commok = notify "connessione ristabilita"; + loadlimit = notify "livello batterie critico (5%)"; + runlimit = notify "autonomia batterie critico (5min)"; + doshutdown = notify "inizio sequenza di spegnimento"; + powerout = notify "rete elettrica disconnessa"; + mainsback = notify "rete elettrica riconnessa"; + onbattery = notify "attivate batterie"; + offbattery = notify "disattivate batterie"; + emergency = notify "malfunzionamento batterie, possibile spegnimento!"; + }; + }; + + services.smartd = + let + # Send a notification on the Maxwell + # when a disk is starting to fail. + failHook = with pkgs; writeScript "disk-fail-hook" '' + #!/bin/sh + ${pkgs.maxwell-notify}/bin/notify \ + "SMART: rilevato problema al disco $SMARTD_DEVICESTRING:" + ${pkgs.maxwell-notify}/bin/notify "> $SMARTD_MESSAGE" + ''; + in + { + enable = true; + defaults.monitored = "-a -M exec ${failHook}"; + }; + +} diff --git a/jobs.nix b/jobs.nix new file mode 100644 index 0000000..c5ee877 --- /dev/null +++ b/jobs.nix @@ -0,0 +1,129 @@ +{ config, pkgs, lib, ... }: + +with lib; + +{ + +systemd.services.ydns = { + description = "update ydns address record"; + after = [ "network-online.target" ]; + startAt = "*:0/30"; + + serviceConfig.Type = "oneshot"; + serviceConfig.environmentFile = config.secrets.ydns.environment; + + path = with pkgs; [ curl cacert gawk iproute ]; + environment = { + YDNS_HOST = config.var.hostname; + CURL_CA_BUNDLE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"; + }; + + script = '' + update() { + ret=$(curl -$1 --basic --silent \ + -u "$YDNS_USER:$YDNS_PASSWD" \ + "https://ydns.io/api/v1/update/?host=$YDNS_HOST&ip=$2" || exit 0) + + case "$ret" in + ok) + echo "updated successfully: $YDNS_HOST ($2)" + ;; + + badauth) + echo "updated failed: $YDNS_HOST (authentication failed)" + ;; + + *) + echo "update failed: $YDNS_HOST ($ret)" + ;; + esac + } + + update 4 "$(curl -s -4 https://ydns.io/api/v1/ip)" + update 6 "$(ip addr show mngtmpaddr | awk '/inet6/{print $2; exit}' | cut -d/ -f1)" + ''; +}; + + +systemd.services.backup = { + description = "run system backup"; + after = [ "network-online.target" ]; + startAt = "weekly"; + + serviceConfig.Type = "oneshot"; + + path = with pkgs; [ bup git nfs-utils ]; + + environment.BUP_DIR = "/mnt/backup"; + + script = '' + ${pkgs.fish}/bin/fish << 'EOF' + + set locations \ + /etc/lvm \ + /etc/nixos \ + /var/lib \ + /home + + set excluded \ + /var/lib/alsa \ + /var/lib/systemd \ + /var/lib/udisks2 \ + /var/lib/udev \ + /var/lib/postgresql + + # mount NFS share + mkdir -p $BUP_DIR + mount.nfs -o nolock 192.168.1.3:/maxwell $BUP_DIR + + # check if properly mounted + if not mountpoint -q $BUP_DIR + echo mount failed! 1>&2 + exit 1 + end + + # init backup + if not test -e $BUP_DIR/bupindex + bup init + end + + # build indices and copy + for i in $locations + eval bup index $i --exclude=(string join " --exclude=" $excluded) + bup save -n (basename $i) $i + end + + # postgresql backup + set dir /var/lib/postgresql-backup + mkdir -p $dir + sudo -u postgres pg_dumpall | gzip > $dir/db.bak + bup index $dir + bup save -n postgresql $dir + rm -rf $dir + + umount /mnt/backup + EOF + ''; +}; + + +systemd.services.namecoin-update = +let + userFile = with config.services.namecoind; + pkgs.writeText "namecoin.conf" '' + rpcbind=${rpc.address} + rpcport=${toString rpc.port} + rpcuser=${rpc.user} + rpcpassword=${rpc.password} + ''; +in { + description = "update namecoin names"; + after = [ "namecoind.service" ]; + startAt = "hourly"; + + path = [ pkgs.namecoind ]; + serviceConfig.Type = "oneshot"; + serviceConfig.ExecStart = "${pkgs.haskellPackages.namecoin-update}/bin/namecoin-update ${userFile}"; +}; + +} diff --git a/magnetico.nix b/magnetico.nix new file mode 100644 index 0000000..5124392 --- /dev/null +++ b/magnetico.nix @@ -0,0 +1,68 @@ +{ config, pkgs, ... }: + +# Setup: +# Maxwell runs the web UI (magneticow) but doesn't +# run the crawler (magneticod) because it's too +# network intesive. The latter is run by Wigfrid, +# which periodically uploads a sqlite database. +# Once received, Maxwell merges it with the local one. + +{ + ### Reverse proxy location + services.nginx.virtualHosts."${config.var.hostname}" = + { locations."/dht/" = { + proxyPass = "http://localhost:8082/"; + # Rewrite all absolute paths, magneticow + # was not designed to work behind a proxy. + extraConfig = '' + sub_filter_once off; + sub_filter_types *; + sub_filter 'action="/' 'action="/dht/'; + sub_filter 'href="/' 'href="/dht/'; + sub_filter 'src="/' 'src="/dht/'; + sub_filter '/api/' '/dht/api/'; + sub_filter '/feed?' '/dht/feed?'; + sub_filter 'split("/")[2]' 'split("/").pop()'; + ''; + }; + }; + + ### Magneticow + services.magnetico = { + enable = true; + web.port = 8082; + web.credentialsFile = config.secrets.passwords.magnetico; + }; + + # Disable the crawler: it's run by wigfrid + systemd.services.magneticod.enable = false; + + # Start the database merge as soon + # as a new one is uploaded. + systemd.paths.merge-magnetico = { + pathConfig.PathExists = "/var/lib/magnetico/update.sqlite3"; + wantedBy = [ "multi-user.target" ]; + }; + + # Merge wigfrid update database with + # the current one and restart magneticow. + systemd.services.merge-magnetico = { + path = [ pkgs.python3 ]; + script = '' + set -e + systemctl stop magneticow + cd /var/lib/magnetico + python3 ${./assets/magnetico-merge.py} database.sqlite3 update.sqlite3 + rm update.sqlite3 + systemctl start magneticow + ''; + }; + + # SSH access to allow uploading + # the magnetico database. + users.users.magnetico = { + useDefaultShell = true; + openssh.authorizedKeys.keyFiles = [ config.secrets.publicKeys.magnetico ]; + }; + +} diff --git a/matrix.nix b/matrix.nix new file mode 100644 index 0000000..d3f7f2c --- /dev/null +++ b/matrix.nix @@ -0,0 +1,180 @@ +{ config, lib, pkgs, ... }: + +with config.var; + +let + ### Element (Riot) configuration + conf = with config.var; { + default_server_config."m.homeserver" = + { base_url = "https://${hostname}"; + server_name = "Maxwell"; + }; + default_server_config."m.identity_server" = + { base_url = "https://matrix.org"; }; + roomDirectory.servers = [ "matrix.org" hostname ]; + + brand = "Maxwell matrix"; + defaultCountryCode = "IT"; + showLabsSettings = true; + + # Use a trusted Jitsi instance + jitsi.preferredDomain = "jitsi.openspeed.org"; + jitsi.externalApiUrl = "https://jitsi.openspeed.org/libs/external_api.min.js"; + }; +in + +{ + ### Reverse proxy locations + services.nginx.virtualHosts."${config.var.hostname}" = + let + client = + { "m.homeserver" = { "base_url" = "https://${config.var.hostname}"; }; + "m.identity_server" = { "base_url" = "https://matrix.org"; }; + }; + server = { "m.server" = "${config.var.hostname}:443"; }; + in + { + # Needed for matrix federation + locations."/.well-known/matrix/server".extraConfig = '' + add_header Content-Type application/json; + return 200 '${builtins.toJSON server}'; + ''; + + # Needed for automatic homeserver + # setup of matrix clients + locations."/.well-known/matrix/client".extraConfig = '' + add_header Content-Type application/json; + add_header Access-Control-Allow-Origin *; + return 200 '${builtins.toJSON client}'; + ''; + + # Forward matrix API calls to synapse + locations."/_matrix".proxyPass = "http://localhost:8448"; + }; + + + ### Element/Riot static location + services.nginx.virtualHosts."riot.${config.var.hostname}" = + { enableACME = true; + forceSSL = true; + + locations."/" = + { index = "index.html"; + alias = (pkgs.element-web.override { inherit conf; }) + "/"; + }; + }; + + + ### Homeserver + services.matrix-synapse = { + enable = true; + server_name = config.var.hostname; + + # Tell users about our TURN server + turn_uris = [ + "turn:${config.var.hostname}:5349?transport=udp" + "turn:${config.var.hostname}:5350?transport=udp" + "turn:${config.var.hostname}:5349?transport=tcp" + "turn:${config.var.hostname}:5350?transport=tcp" + ]; + + # Bind on localhost and used a reverse proxy + listeners = [ + { bind_address = "localhost"; + port = 8448; + type = "http"; + tls = false; + resources = [ + { compress = true; names = [ "client" ] ; } + { compress = false; names = [ "federation" ]; } + ]; + x_forwarded = true; + } + ]; + + # Connect to Postrges + database_type = "psycopg2"; + database_args = { + user = "matrix-synapse"; + database = "matrix-synapse"; + }; + + # Make logging less verbose + logConfig = '' + version: 1 + formatters: + journal_fmt: + format: '%(name)s: [%(request)s] %(message)s' + filters: + context: + (): synapse.util.logcontext.LoggingContextFilter + request: "" + handlers: + journal: + class: systemd.journal.JournalHandler + formatter: journal_fmt + filters: [context] + SYSLOG_IDENTIFIER: synapse + root: + level: WARN + handlers: [journal] + disable_existing_loggers: False + ''; + + allow_guest_access = true; + expire_access_token = true; + event_cache_size = "2K"; + max_upload_size = "1000M"; + turn_user_lifetime = "1d"; + + # Needed to restrict access to the TURN + # server to only our matrix users. + turn_shared_secret = config.secrets.matrix.turn; + # Needed by the register_new_matrix_user script + registration_shared_secret = config.secrets.matrix.registration; + }; + + + # Use the Postrges database + services.postgresql.enable = true; + + + # Handles users behind a NAT, + # needed for reliable VoIP. + services.coturn = { + enable = true; + + # Only allow users vouched for + # by the Matrix server. + lt-cred-mech = true; + use-auth-secret = true; + static-auth-secret = config.secrets.matrix.turn; + + # Use maxwell certificate for TLS + realm = config.var.hostname; + cert = "/var/lib/acme/${config.var.hostname}/fullchain.pem"; + pkey = "/var/lib/acme/${config.var.hostname}/key.pem"; + + # Port range for TURN relaying + min-port = 49152; + max-port = 49999; + + # Enable TLS + secure-stun = true; + no-tcp-relay = false; + + extraConfig = '' + external-ip=${config.var.ipAddress} + cipher-list=HIGH + no-loopback-peers + no-multicast-peers + denied-peer-ip=10.0.0.0-10.255.255.255 + denied-peer-ip=192.168.0.0-192.168.255.255 + allowed-peer-ip=192.168.1.5 + user-quota=12 + total-quota=1200 + verbose=true + ''; + }; + +} diff --git a/nameserver.nix b/nameserver.nix new file mode 100644 index 0000000..5f841da --- /dev/null +++ b/nameserver.nix @@ -0,0 +1,44 @@ +{ config, ... }: + +# Setup: +# PDNS recursor on port 53 +# DNSCrypt wrapper on port 1194 +# NCDNS for Namecoin bit. zone resolution + +{ + # Recursive DNS resolver + services.pdns-recursor = { + enable = true; + # Configures the bit. zone + resolveNamecoin = true; + dns.allowFrom = [ "0.0.0.0/0" ]; + }; + + # Wrap the local recursive resolver + # in DNSCrypt on the default OpenVPN port. + # This port is chosen because it's usually + # not blocked in corporate networks. + services.dnscrypt-wrapper = { + enable = true; + address = "0.0.0.0"; + port = 1194; + }; + + # Namecoin resolver + services.ncdns = { + enable = true; + # This is currently broken, see ncdns issue: + # https://github.com/namecoin/ncdns/issues/127 + dnssec.enable = false; + }; + + # Namecoin daemon with RPC server + services.namecoind = { + enable = true; + # This are used by the resolver (ncdns) + # to query the blockchain. + rpc.user = config.secrets.namecoin.user; + rpc.password = config.secrets.namecoin.password; + }; + +} diff --git a/packages.nix b/packages.nix new file mode 100644 index 0000000..5d947f0 --- /dev/null +++ b/packages.nix @@ -0,0 +1,36 @@ +{ config, pkgs, lib, ... }: + +let + unstable = import { }; +in + +{ + nixpkgs.overlays = lib.singleton + (self: super: + { maxwell-notify = self.callPackage ./custom/packages/maxwell-notify.nix + { homeserver = "https://${config.var.hostname}/_matrix/client/r0"; + roomId = "!FsUSHSNMPMVTFFcvJo:maxwell.ydns.eu"; + authToken = config.secrets.privateKeys.matrix; + }; + + monero = unstable.monero; + element-web = unstable.element-web; + }); + + environment.systemPackages = with pkgs; [ + # utilities + iftop curl ranger neovim + nix-script + jq ack + + # backup + bup git nfs-utils + + # admin + dnsutils + matrix-synapse + maxwell-notify + smartmontools + ]; + +} diff --git a/secrets/certs/breve.crt b/secrets/certs/breve.crt new file mode 100644 index 0000000..4d0792c --- /dev/null +++ b/secrets/certs/breve.crt @@ -0,0 +1,41 @@ +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 diff --git a/secrets/certs/breve.key b/secrets/certs/breve.key new file mode 100644 index 0000000..46645fc --- /dev/null +++ b/secrets/certs/breve.key @@ -0,0 +1,224 @@ +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 diff --git a/secrets/default.nix b/secrets/default.nix new file mode 100644 index 0000000..5a9ef1e --- /dev/null +++ b/secrets/default.nix @@ -0,0 +1,43 @@ +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 +p6krltwsQGFLXH+N diff --git a/secrets/keys/fazo.pub b/secrets/keys/fazo.pub new file mode 100644 index 0000000..20b58f7 --- /dev/null +++ b/secrets/keys/fazo.pub @@ -0,0 +1,9 @@ +U2FsdGVkX1+Ay4K0S+xuJiyoMRj00oMkaw5sYVvu9VBR68aypK8eLTng8xoqKwzm +uD1YNhLdh515CgHyMI7/LraT2yDYIlF+pNEfftH6U2qU5IfWSoukD59RscfaAft+ +/dend9Y6HyG1WdlyPyLVabruHFScx3d+oaLwEcgggnI/M9coWnHyvBXspo6E75um +gyvFntN4GmJLf1sMQIn0I7lW9djC8nupjSTstRo5HNLM/LwlhwAYRb/jbJUOkYSK +D/SDHW5p/9OrACQzAKHFB3mg4+9SufD+cju8qIAn9uFcyCJxkri6Mz+SGqdXtSgi +fEZE2r1aCUXFa8Nq+qoYbxVue3BFlzxetC7fZrx2zWnmkSgOn7LWDn6q3B3KWeT5 +om/g+Ph/RE4piKzm9m2jIx+0TlkUHlpOKAf4Xzwdaivmm6HaCNc5pt1Hw0le1fTW +JE+6BkXFDJz/8ytROujTGlMaMCB/JHgK04diEAnQJmNQnYVG03PxmRHmmqXc1czQ +OnIzyUraBCpBsHSAVsN/afC8 diff --git a/secrets/keys/giu.pub b/secrets/keys/giu.pub new file mode 100644 index 0000000..e394dfd --- /dev/null +++ b/secrets/keys/giu.pub @@ -0,0 +1,3 @@ +U2FsdGVkX19/6c3AzyWTN5p17ujhKlbDdk91iQs9z7Q0HiyA4L7BKFT/ZOk4VNqY +2Yh7r0b1F1ScFjvKH3aJ7jYGHT0i+w3LSHsufCDATEUejN/Z9JtEIXYaodOCJaYE +YuIaLuBwWdkAPUl1lhs= diff --git a/secrets/keys/magnetico.pub b/secrets/keys/magnetico.pub new file mode 100644 index 0000000..d3664f1 --- /dev/null +++ b/secrets/keys/magnetico.pub @@ -0,0 +1,3 @@ +U2FsdGVkX1/StXpT1GfebxPB+1TyCHLo5fjFZLNkkWXnCS04WnREE2xlV7OXw0Iq +llqZTflZ/z1hSz7NuUO/vrR57RRo6icf3UXnxvJ8HD6Z9q7uxI+WpIj+ME2zij6B +Jg== diff --git a/secrets/keys/matrix.tok b/secrets/keys/matrix.tok new file mode 100644 index 0000000..a8b3e8c --- /dev/null +++ b/secrets/keys/matrix.tok @@ -0,0 +1,7 @@ +U2FsdGVkX19J8VE7lArWiwLIURQ8NjPEUwkOAh4m1oR0yrmBCI5u/vhwSQTC+ETb +S3b80dqDKkR8QKRxIaquJHw/KRvQqKViZbu1OsHQTPQhK//mvs6vZ8G00vfucphc +6XIuiJS0u1zbzP6CKoLlkUyVOxFsmVSmxRx8460vgqK00JHSXf82mCAXcePVfHX9 +uV9w34x3QkqSzmptx1orJrWa/Y/+et19ghJ/d6Utll+kg5Ldkd6vYcSA5bYFMe6L +LzAjJDLSvRpGkwP7EH2/9Kin5qDA7OUQmrXyFvmb9viCnYSD4TUaxXHYy4SPYcPY +qgFFeNDry5PAhkqLCTKgQWylCZXNbnA7JHp5fdbQCyRFD2sNxVN9ptuqNJd5x+hf +0PzTFokhgtE= diff --git a/secrets/keys/meme.pub b/secrets/keys/meme.pub new file mode 100644 index 0000000..97431ae --- /dev/null +++ b/secrets/keys/meme.pub @@ -0,0 +1,3 @@ +U2FsdGVkX18yVvW7ZvcS0Xc/LsBJmDBTjmHeODQqsVSq8AlzjHH0Z15cY2ibL0+2 +/fq+Sb12nfYhXkdFePGNJl+pwTVN2KmQhQtTPUawwa0bmvqC3wPXmHn8O1AndVP9 +8g== diff --git a/secrets/keys/rnhmjoj-builder.pub b/secrets/keys/rnhmjoj-builder.pub new file mode 100644 index 0000000..7299f72 --- /dev/null +++ b/secrets/keys/rnhmjoj-builder.pub @@ -0,0 +1,3 @@ +U2FsdGVkX19MH3jJZJHEhLZLqIGcQCvd7JS2I8vWztP1Htde6A/xfy3zP8U6NUOc +QPBYfycwXLqUM89gVrKnnj28HQiAzQNf2zzPqG7MOpQKA6zdRF6i9n+CGtvXC36u +zQ== diff --git a/secrets/keys/rnhmjoj.pub b/secrets/keys/rnhmjoj.pub new file mode 100644 index 0000000..78f36cf --- /dev/null +++ b/secrets/keys/rnhmjoj.pub @@ -0,0 +1,3 @@ +U2FsdGVkX18X2ltRnCWQnXMSt/FSKiq/ScbhdjFP4wmPHi5njgtam/c1Dg+0T1fj +JzOYe53LglUBfjDMbIepcIymHXPteizligpJzNE7DwuzsCp2JTkn9KWzKJb45Qa/ +/UtVdTfkS9WH diff --git a/secrets/misc/asjon.env b/secrets/misc/asjon.env new file mode 100644 index 0000000..67461f9 --- /dev/null +++ b/secrets/misc/asjon.env @@ -0,0 +1,10 @@ +U2FsdGVkX18ufnreJQQJJ52gMxajdK5bLn8A7Gqb3OvqThlWWb5mo4UV+VqEf/ob +VSydFr03zlSYuAuyvpHcunlTHnJR6RPgEdv0qV2NFBaVAlVjqJDgZHPKNLCp2Zws +LOgrWiaRGTKrBAD/80JlzsFyk5YVSXd9fTqo05bTym8qKv39vFrrmZQu1SOKRKmn +qrIUr9MjG25iLCxR6ajcANgfb3+hgQMo5ypr7AwjMp1PwkU/IWf1atIWFJzf0ZU1 +4JOsDB4FvuX0hdi8J8LKRe+t0hsjQxb4FS3sMWrSDKhjvjRP+AEEwdj/3YbX856i +l9h2Yd36BtKOrwgrMQTS0pHvnUwj+o/4KeFrteccwgJP5bBJYVts10vg52FldNTg +qTrnnjVrjVm/by8Of435ttSXNmqn5g10MUKKLIIgNZXJcUY/fW4v07xduDHFMUYA +YJWfOfyR4Jlb2lJjmG0VwgPqhVMLAqFrL8XLGlqv1D/nKchktwp58cOqo95js+BT +Q8yvEzMbbtPM4MIGUhzfMbVhXFMmQRgfSpQFAPHe/33V0Ddsp7nCj0n7P+g0b8Ka +2BBS8ez8+7DCyTIerKCwB2+Hu9vy1bkhr8ugZXbmxvL3+fSHgMJ//KWFClQ= diff --git a/secrets/misc/ydns.env b/secrets/misc/ydns.env new file mode 100644 index 0000000..0d6402e --- /dev/null +++ b/secrets/misc/ydns.env @@ -0,0 +1,2 @@ +U2FsdGVkX1+nBNkZvBovUtzVk+hzQxFfQJ2NoORch7iPe33Zf+UIKOqkAWK3hjgb +aYDcTVL3ef1iD4saMpueUpoz36+TtwXAowPzGq0+BVLDyVikU9LM6QBlQQ== diff --git a/secrets/pass/magnetico.pwds b/secrets/pass/magnetico.pwds new file mode 100644 index 0000000..54bb75b --- /dev/null +++ b/secrets/pass/magnetico.pwds @@ -0,0 +1,2 @@ +U2FsdGVkX19YVs+neL4R4JDT1CSsndTtbggYoDxEF2iwRCRDJRtrBBJthnxRrUsr +c+A5NSSRRAu0LQ5vjaHlOYiCtmVCdYu7ECrpHQ40KqYgYhXJAw== diff --git a/secrets/pass/root.pwd b/secrets/pass/root.pwd new file mode 100644 index 0000000..7fd5426 --- /dev/null +++ b/secrets/pass/root.pwd @@ -0,0 +1,3 @@ +U2FsdGVkX1+1zBjw7Y2NlBeTLcGS8o3Er/ngQMU57HLCN8jSfKBU0/C4o9D4NDjl +C7pRu3oOHmz0Pn9ipLaP87ST9RzVncHw/kqNBh8Dg29n3jNoTdSfwTn6xV/mBwQO +a4OsKusYMI/dCriATixomxe1GkC06YfwJg== diff --git a/secrets/transcrypt b/secrets/transcrypt new file mode 100755 index 0000000..5474c93 --- /dev/null +++ b/secrets/transcrypt @@ -0,0 +1,888 @@ +#!/usr/bin/env nix-shell +#! nix-shell -i bash --pure +#! nix-shell -p bash openssl git unixtools.column +set -euo pipefail + +# +# transcrypt - https://github.com/elasticdog/transcrypt +# +# A script to configure transparent encryption of sensitive files stored in +# a Git repository. It utilizes OpenSSL's symmetric cipher routines and follows +# the gitattributes(5) man page regarding the use of filters. +# +# Copyright (c) 2014-2019 Aaron Bull Schaefer +# This source code is provided under the terms of the MIT License +# that can be be found in the LICENSE file. +# + +##### CONSTANTS + +# the release version of this script +readonly VERSION='2.0.0' + +# the default cipher to utilize +readonly DEFAULT_CIPHER='aes-256-ctr' + +# the openssl options to encrypt/decrypt the files +# shellcheck disable=SC2016 +readonly ENCRYPT_OPTIONS='-$cipher -pbkdf2 -iter 200000' + +# regular expression used to test user input +readonly YES_REGEX='^[Yy]$' + +## Repository Metadata + +# whether or not transcrypt is already configured +readonly CONFIGURED=$(git config --get --local transcrypt.version 2>/dev/null) + +# the current git repository's top-level directory +readonly REPO=$(git rev-parse --show-toplevel 2>/dev/null) + +# whether or not a HEAD revision exists +readonly HEAD_EXISTS=$(git rev-parse --verify --quiet HEAD 2>/dev/null) + +# https://github.com/RichiH/vcsh +# whether or not the git repository is running under vcsh +readonly IS_VCSH=$(git config --get --local --bool vcsh.vcsh 2>/dev/null) + +# whether or not the git repository is bare +readonly IS_BARE=$(git rev-parse --is-bare-repository 2>/dev/null) + +## Git Directory Handling + +# print a canonicalized absolute pathname +realpath() { + local path=$1 + + # make path absolute + local abspath=$path + if [[ -n ${abspath##/*} ]]; then + abspath=$(pwd -P)/$abspath + fi + + # canonicalize path + local dirname= + if [[ -d $abspath ]]; then + dirname=$(cd "$abspath" && pwd -P) + abspath=$dirname + elif [[ -e $abspath ]]; then + dirname=$(cd "${abspath%/*}/" 2>/dev/null && pwd -P) + abspath=$dirname/${abspath##*/} + fi + + if [[ -d $dirname && -e $abspath ]]; then + printf '%s\n' "$abspath" + else + printf 'invalid path: %s\n' "$path" >&2 + exit 1 + fi +} + +# the current git repository's .git directory +RELATIVE_GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) +readonly GIT_DIR=$(realpath "$RELATIVE_GIT_DIR" 2>/dev/null) + +# the current git repository's gitattributes file +readonly CORE_ATTRIBUTES=$(git config --get --local --path core.attributesFile) +if [[ $CORE_ATTRIBUTES ]]; then + readonly GIT_ATTRIBUTES=$CORE_ATTRIBUTES +elif [[ $IS_BARE == 'true' ]] || [[ $IS_VCSH == 'true' ]]; then + readonly GIT_ATTRIBUTES="${GIT_DIR}/info/attributes" +else + readonly GIT_ATTRIBUTES="${REPO}/.gitattributes" +fi + +##### FUNCTIONS + +# print a message to stderr +warn() { + local fmt="$1" + shift + # shellcheck disable=SC2059 + printf "transcrypt: $fmt\n" "$@" >&2 +} + +# print a message to stderr and exit with either +# the given status or that of the most recent command +die() { + local st="$?" + if [[ "$1" != *[^0-9]* ]]; then + st="$1" + shift + fi + warn "$@" + exit "$st" +} + +# verify that all requirements have been met +run_safety_checks() { + # validate that we're in a git repository + [[ $GIT_DIR ]] || die 'you are not currently in a git repository; did you forget to run "git init"?' + + # exit if transcrypt is not in the required state + if [[ $requires_existing_config ]] && [[ ! $CONFIGURED ]]; then + die 1 'the current repository is not configured' + elif [[ ! $requires_existing_config ]] && [[ $CONFIGURED ]]; then + die 1 'the current repository is already configured; see --display' + fi + + # check for dependencies + for cmd in {column,grep,mktemp,openssl,sed,tee}; do + command -v $cmd >/dev/null || die 'required command "%s" was not found' "$cmd" + done + + # ensure the repository is clean (if it has a HEAD revision) so we can force + # checkout files without the destruction of uncommitted changes + if [[ $requires_clean_repo ]] && [[ $HEAD_EXISTS ]] && [[ $IS_BARE == 'false' ]]; then + # check if the repo is dirty + if ! git diff-index --quiet HEAD --; then + die 1 'the repo is dirty; commit or stash your changes before running transcrypt' + fi + fi +} + +# unset the cipher variable if it is not supported by openssl +validate_cipher() { + local list_cipher_commands + list_cipher_commands='openssl enc -ciphers' + remove_dash() { + sed 's#\(^\| \)-#\1#g' + } + + + local supported + supported=$($list_cipher_commands | remove_dash | tr -s ' ' '\n' | grep --line-regexp "$cipher") || true + if [[ ! $supported ]]; then + if [[ $interactive ]]; then + printf '"%s" is not a valid cipher; choose one of the following:\n\n' "$cipher" + $list_cipher_commands | remove_dash | column -c 80 + printf '\n' + cipher='' + else + # shellcheck disable=SC2016 + die 1 '"%s" is not a valid cipher; see `%s`' "$cipher" "$($list_cipher_commands | remove_dash)" + fi + fi +} + +# ensure we have a cipher to encrypt with +get_cipher() { + while [[ ! $cipher ]]; do + local answer= + if [[ $interactive ]]; then + printf 'Encrypt using which cipher? [%s] ' "$DEFAULT_CIPHER" + read -r answer + fi + + # use the default cipher if the user gave no answer; + # otherwise verify the given cipher is supported by openssl + if [[ ! $answer ]]; then + cipher=$DEFAULT_CIPHER + else + cipher=$answer + validate_cipher + fi + done +} + +# ensure we have a password to encrypt with +get_password() { + while [[ ! $password ]]; do + local answer= + if [[ $interactive ]]; then + printf 'Generate a random password? [Y/n] ' + read -r -n 1 -s answer + printf '\n' + fi + + # generate a random password if the user answered yes; + # otherwise prompt the user for a password + if [[ $answer =~ $YES_REGEX ]] || [[ ! $answer ]]; then + local password_length=30 + local random_base64 + random_base64=$(openssl rand -base64 $password_length) + password=$random_base64 + else + printf 'Password: ' + read -r password + [[ $password ]] || printf 'no password was specified\n' + fi + done +} + +# confirm the transcrypt configuration +confirm_configuration() { + local answer= + + printf '\nRepository metadata:\n\n' + [[ ! $REPO ]] || printf ' GIT_WORK_TREE: %s\n' "$REPO" + printf ' GIT_DIR: %s\n' "$GIT_DIR" + printf ' GIT_ATTRIBUTES: %s\n\n' "$GIT_ATTRIBUTES" + printf 'The following configuration will be saved:\n\n' + printf ' CIPHER: %s\n' "$cipher" + printf ' PASSWORD: %s\n\n' "$password" + printf 'Does this look correct? [Y/n] ' + read -r -n 1 -s answer + + # exit if the user did not confirm + if [[ $answer =~ $YES_REGEX ]] || [[ ! $answer ]]; then + printf '\n\n' + else + printf '\n' + die 1 'configuration has been aborted' + fi +} + +# confirm the rekey configuration +confirm_rekey() { + local answer= + + printf '\nRepository metadata:\n\n' + [[ ! $REPO ]] || printf ' GIT_WORK_TREE: %s\n' "$REPO" + printf ' GIT_DIR: %s\n' "$GIT_DIR" + printf ' GIT_ATTRIBUTES: %s\n\n' "$GIT_ATTRIBUTES" + printf 'The following configuration will be saved:\n\n' + printf ' CIPHER: %s\n' "$cipher" + printf ' PASSWORD: %s\n\n' "$password" + printf 'You are about to re-encrypt all encrypted files using new credentials.\n' + printf 'Once you do this, their historical diffs will no longer display in plain text.\n\n' + printf 'Proceed with rekey? [y/N] ' + read -r answer + + # only rekey if the user explicitly confirmed + if [[ $answer =~ $YES_REGEX ]]; then + printf '\n' + else + die 1 'rekeying has been aborted' + fi +} + +# automatically stage rekeyed files in preparation for the user to commit them +stage_rekeyed_files() { + local encrypted_files + encrypted_files=$(git ls-crypt) + if [[ $encrypted_files ]] && [[ $IS_BARE == 'false' ]]; then + # touch all encrypted files to prevent stale stat info + cd "$REPO" || die 1 'could not change into the "%s" directory' "$REPO" + # shellcheck disable=SC2086 + touch $encrypted_files + # shellcheck disable=SC2086 + git update-index --add -- $encrypted_files + + printf '*** rekeyed files have been staged ***\n' + printf '*** COMMIT THESE CHANGES RIGHT AWAY! ***\n\n' + fi +} + +# save helper scripts under the repository's git directory +save_helper_scripts() { + mkdir -p "${GIT_DIR}/crypt" + + openssl_command="openssl enc $ENCRYPT_OPTIONS -pass env:ENC_PASS" + + # The `decryption -> encryption` process on an unchanged file must be + # deterministic for everything to work transparently. To do that, the same + # salt must be used each time we encrypt the same file. An HMAC has been + # proven to be a PRF, so we generate an HMAC-SHA256 for each decrypted file + # (keyed with a combination of the filename and transcrypt password), and + # then use the last 16 bytes of that HMAC for the file's unique salt. + + cat <<-'EOF' >"${GIT_DIR}/crypt/clean" + #!/usr/bin/env bash + filename=$1 + # ignore empty files + if [[ -s $filename ]]; then + # cache STDIN to test if it's already encrypted + tempfile=$(mktemp 2>/dev/null || mktemp -t tmp) + trap 'rm -f "$tempfile"' EXIT + tee "$tempfile" &>/dev/null + # the first bytes of an encrypted file are always "Salted" in Base64 + read -n 8 firstbytes <"$tempfile" + if [[ $firstbytes == "U2FsdGVk" ]]; then + cat "$tempfile" + else + cipher=$(git config --get --local transcrypt.cipher) + password=$(git config --get --local transcrypt.password) + salt=$(openssl dgst -hmac "${filename}:${password}" -sha256 "$filename" | tr -d '\r\n' | tail -c 16) + ENC_PASS=$password @openssl_command@ -e -a -S "$salt" -in "$tempfile" + fi + fi + EOF + + cat <<-'EOF' >"${GIT_DIR}/crypt/smudge" + #!/usr/bin/env bash + tempfile=$(mktemp 2>/dev/null || mktemp -t tmp) + trap 'rm -f "$tempfile"' EXIT + cipher=$(git config --get --local transcrypt.cipher) + password=$(git config --get --local transcrypt.password) + tee "$tempfile" | ENC_PASS=$password @openssl_command@ -d -a 2>/dev/null || cat "$tempfile" + EOF + + cat <<-'EOF' >"${GIT_DIR}/crypt/textconv" + #!/usr/bin/env bash + filename=$1 + # ignore empty files + if [[ -s $filename ]]; then + cipher=$(git config --get --local transcrypt.cipher) + password=$(git config --get --local transcrypt.password) + ENC_PASS=$password @openssl_command@ -d -a -in "$filename" 2>/dev/null || cat "$filename" + fi + EOF + + # make scripts executable + for script in {clean,smudge,textconv}; do + chmod 0755 "${GIT_DIR}/crypt/${script}" + sed "s/@openssl_command@/$openssl_command/" -i "${GIT_DIR}/crypt/${script}" + done +} + +# write the configuration to the repository's git config +save_configuration() { + save_helper_scripts + + # write the encryption info + git config transcrypt.version "$VERSION" + git config transcrypt.cipher "$cipher" + git config transcrypt.password "$password" + + # write the filter settings + if [[ -d $(git rev-parse --git-common-dir) ]]; then + # this allows us to support multiple working trees via git-worktree + # ...but the --git-common-dir flag was only added in November 2014 + # shellcheck disable=SC2016 + git config filter.crypt.clean '"$(git rev-parse --git-common-dir)"/crypt/clean %f' + # shellcheck disable=SC2016 + git config filter.crypt.smudge '"$(git rev-parse --git-common-dir)"/crypt/smudge' + # shellcheck disable=SC2016 + git config diff.crypt.textconv '"$(git rev-parse --git-common-dir)"/crypt/textconv' + else + # shellcheck disable=SC2016 + git config filter.crypt.clean '"$(git rev-parse --git-dir)"/crypt/clean %f' + # shellcheck disable=SC2016 + git config filter.crypt.smudge '"$(git rev-parse --git-dir)"/crypt/smudge' + # shellcheck disable=SC2016 + git config diff.crypt.textconv '"$(git rev-parse --git-dir)"/crypt/textconv' + fi + git config filter.crypt.required 'true' + git config diff.crypt.cachetextconv 'true' + git config diff.crypt.binary 'true' + git config merge.renormalize 'true' + + # add a git alias for listing encrypted files + git config alias.ls-crypt "!git ls-files | git check-attr --stdin filter | awk 'BEGIN { FS = \":\" }; /crypt$/{ print \$1 }'" +} + +# display the current configuration settings +display_configuration() { + local current_cipher + current_cipher=$(git config --get --local transcrypt.cipher) + local current_password + current_password=$(git config --get --local transcrypt.password) + local escaped_password=${current_password//\'/\'\\\'\'} + + printf 'The current repository was configured using transcrypt version %s\n' "$CONFIGURED" + printf 'and has the following configuration:\n\n' + [[ ! $REPO ]] || printf ' GIT_WORK_TREE: %s\n' "$REPO" + printf ' GIT_DIR: %s\n' "$GIT_DIR" + printf ' GIT_ATTRIBUTES: %s\n\n' "$GIT_ATTRIBUTES" + printf ' CIPHER: %s\n' "$current_cipher" + printf ' PASSWORD: %s\n\n' "$current_password" + printf 'Copy and paste the following command to initialize a cloned repository:\n\n' + printf " transcrypt -c %s -p '%s'\n" "$current_cipher" "$escaped_password" +} + +# remove transcrypt-related settings from the repository's git config +clean_gitconfig() { + git config --remove-section transcrypt 2>/dev/null || true + git config --remove-section filter.crypt 2>/dev/null || true + git config --remove-section diff.crypt 2>/dev/null || true + git config --unset merge.renormalize + + # remove the merge section if it's now empty + local merge_values + merge_values=$(git config --get-regex --local 'merge\..*') || true + if [[ ! $merge_values ]]; then + git config --remove-section merge 2>/dev/null || true + fi +} + +# force the checkout of any files with the crypt filter applied to them; +# this will decrypt existing encrypted files if you've just cloned a repository, +# or it will encrypt locally decrypted files if you've just flushed the credentials +force_checkout() { + # make sure a HEAD revision exists + if [[ $HEAD_EXISTS ]] && [[ $IS_BARE == 'false' ]]; then + # this would normally delete uncommitted changes in the working directory, + # but we already made sure the repo was clean during the safety checks + local encrypted_files + encrypted_files=$(git ls-crypt) + cd "$REPO" || die 1 'could not change into the "%s" directory' "$REPO" + IFS=$'\n' + for file in $encrypted_files; do + rm "$file" + git checkout --force HEAD -- "$file" >/dev/null + done + unset IFS + fi +} + +# remove the locally cached encryption credentials and +# re-encrypt any files that had been previously decrypted +flush_credentials() { + local answer= + + if [[ $interactive ]]; then + printf 'You are about to flush the local credentials; make sure you have saved them elsewhere.\n' + printf 'All previously decrypted files will revert to their encrypted form.\n\n' + printf 'Proceed with credential flush? [y/N] ' + read -r answer + printf '\n' + else + # although destructive, we should support the --yes option + answer='y' + fi + + # only flush if the user explicitly confirmed + if [[ $answer =~ $YES_REGEX ]]; then + clean_gitconfig + + # re-encrypt any files that had been previously decrypted + force_checkout + + printf 'The local transcrypt credentials have been successfully flushed.\n' + else + die 1 'flushing of credentials has been aborted' + fi +} + +# remove all transcrypt configuration from the repository +uninstall_transcrypt() { + local answer= + + if [[ $interactive ]]; then + printf 'You are about to remove all transcrypt configuration from your repository.\n' + printf 'All previously encrypted files will remain decrypted in this working copy.\n\n' + printf 'Proceed with uninstall? [y/N] ' + read -r answer + printf '\n' + else + # although destructive, we should support the --yes option + answer='y' + fi + + # only uninstall if the user explicitly confirmed + if [[ $answer =~ $YES_REGEX ]]; then + clean_gitconfig + + # remove helper scripts + for script in {clean,smudge,textconv}; do + [[ ! -f "${GIT_DIR}/crypt/${script}" ]] || rm "${GIT_DIR}/crypt/${script}" + done + [[ ! -d "${GIT_DIR}/crypt" ]] || rmdir "${GIT_DIR}/crypt" + + # touch all encrypted files to prevent stale stat info + local encrypted_files + encrypted_files=$(git ls-crypt) + if [[ $encrypted_files ]] && [[ $IS_BARE == 'false' ]]; then + cd "$REPO" || die 1 'could not change into the "%s" directory' "$REPO" + # shellcheck disable=SC2086 + touch $encrypted_files + fi + + # remove the `git ls-crypt` alias + git config --unset alias.ls-crypt + + # remove the alias section if it's now empty + local alias_values + alias_values=$(git config --get-regex --local 'alias\..*') || true + if [[ ! $alias_values ]]; then + git config --remove-section alias 2>/dev/null || true + fi + + # remove any defined crypt patterns in gitattributes + case $OSTYPE in + darwin*) + /usr/bin/sed -i '' '/filter=crypt diff=crypt[ \t]*$/d' "$GIT_ATTRIBUTES" + ;; + linux*) + sed -i '/filter=crypt diff=crypt[ \t]*$/d' "$GIT_ATTRIBUTES" + ;; + esac + + printf 'The transcrypt configuration has been completely removed from the repository.\n' + else + die 1 'uninstallation has been aborted' + fi +} + +# list all of the currently encrypted files in the repository +list_files() { + if [[ $IS_BARE == 'false' ]]; then + cd "$REPO" || die 1 'could not change into the "%s" directory' "$REPO" + git ls-files | git check-attr --stdin filter | awk 'BEGIN { FS = ":" }; /crypt$/{ print $1 }' + fi +} + +# show the raw file as stored in the git commit object +show_raw_file() { + if [[ -f $show_file ]]; then + # ensure the file is currently being tracked + local escaped_file=${show_file//\//\\\/} + if git ls-files --others -- "$show_file" | awk "/${escaped_file}/{ exit 1 }"; then + file_paths=$(git ls-tree --name-only --full-name HEAD "$show_file") + else + die 1 'the file "%s" is not currently being tracked by git' "$show_file" + fi + elif [[ $show_file == '*' ]]; then + file_paths=$(git ls-crypt) + else + die 1 'the file "%s" does not exist' "$show_file" + fi + + IFS=$'\n' + for file in $file_paths; do + printf '==> %s <==\n' "$file" >&2 + git --no-pager show HEAD:"$file" --no-textconv + printf '\n' >&2 + done + unset IFS +} + +# export password and cipher to a gpg encrypted file +export_gpg() { + # check for dependencies + command -v gpg >/dev/null || die 'required command "gpg" was not found' + + # ensure the recipient key exists + if ! gpg --list-keys "$gpg_recipient" 2>/dev/null; then + die 1 'GPG recipient key "%s" does not exist' "$gpg_recipient" + fi + + local current_cipher + current_cipher=$(git config --get --local transcrypt.cipher) + local current_password + current_password=$(git config --get --local transcrypt.password) + mkdir -p "${GIT_DIR}/crypt" + + local gpg_encrypt_cmd="gpg --batch --recipient $gpg_recipient --trust-model always --yes --armor --quiet --encrypt -" + printf 'password=%s\ncipher=%s\n' "$current_password" "$current_cipher" | $gpg_encrypt_cmd >"${GIT_DIR}/crypt/${gpg_recipient}.asc" + printf "The transcrypt configuration has been encrypted and exported to:\n%s/crypt/%s.asc\n" "$GIT_DIR" "$gpg_recipient" +} + +# import password and cipher from a gpg encrypted file +import_gpg() { + # check for dependencies + command -v gpg >/dev/null || die 'required command "gpg" was not found' + + local path + if [[ -f "${GIT_DIR}/crypt/${gpg_import_file}" ]]; then + path="${GIT_DIR}/crypt/${gpg_import_file}" + elif [[ -f "${GIT_DIR}/crypt/${gpg_import_file}.asc" ]]; then + path="${GIT_DIR}/crypt/${gpg_import_file}.asc" + elif [[ ! -f $gpg_import_file ]]; then + die 1 'the file "%s" does not exist' "$gpg_import_file" + else + path="$gpg_import_file" + fi + + local configuration='' + local safety_counter=0 # fix for intermittent 'no secret key' decryption failures + while [[ ! $configuration ]]; do + configuration=$(gpg --batch --quiet --decrypt "$path") + + safety_counter=$((safety_counter + 1)) + if [[ $safety_counter -eq 3 ]]; then + die 1 'unable to decrypt the file "%s"' "$path" + fi + done + + cipher=$(printf '%s' "$configuration" | grep '^cipher' | cut -d'=' -f 2-) + password=$(printf '%s' "$configuration" | grep '^password' | cut -d'=' -f 2-) +} + +# print this script's usage message to stderr +usage() { + cat <<-EOF >&2 + usage: transcrypt [-c CIPHER] [-p PASSWORD] [-h] + EOF +} + +# print this script's help message to stdout +help() { + cat <<-EOF + NAME + transcrypt -- transparently encrypt files within a git repository + + SYNOPSIS + transcrypt [options...] + + DESCRIPTION + + transcrypt will configure a Git repository to support the transparent + encryption/decryption of files by utilizing OpenSSL's symmetric cipher + routines and Git's built-in clean/smudge filters. It will also add a + Git alias "ls-crypt" to list all transparently encrypted files within + the repository. + + The transcrypt source code and full documentation may be downloaded + from https://github.com/elasticdog/transcrypt. + + OPTIONS + -c, --cipher=CIPHER + the symmetric cipher to utilize for encryption; + defaults to aes-256-cbc + + -p, --password=PASSWORD + the password to derive the key from; + defaults to 30 random base64 characters + + -y, --yes + assume yes and accept defaults for non-specified options + + -d, --display + display the current repository's cipher and password + + -r, --rekey + re-encrypt all encrypted files using new credentials + + -f, --flush-credentials + remove the locally cached encryption credentials and re-encrypt + any files that had been previously decrypted + + -F, --force + ignore whether the git directory is clean, proceed with the + possibility that uncommitted changes are overwritten + + -u, --uninstall + remove all transcrypt configuration from the repository and + leave files in the current working copy decrypted + + -l, --list + list all of the transparently encrypted files in the repository, + relative to the top-level directory + + -s, --show-raw=FILE + show the raw file as stored in the git commit object; use this + to check if files are encrypted as expected + + -e, --export-gpg=RECIPIENT + export the repository's cipher and password to a file encrypted + for a gpg recipient + + -i, --import-gpg=FILE + import the password and cipher from a gpg encrypted file + + -v, --version + print the version information + + -h, --help + view this help message + + EXAMPLES + + To initialize a Git repository to support transparent encryption, just + change into the repo and run the transcrypt script. transcrypt will + prompt you interactively for all required information if the corre- + sponding option flags were not given. + + $ cd / + $ transcrypt + + Once a repository has been configured with transcrypt, you can trans- + parently encrypt files by applying the "crypt" filter and diff to a + pattern in the top-level .gitattributes config. If that pattern matches + a file in your repository, the file will be transparently encrypted + once you stage and commit it: + + $ echo 'sensitive_file filter=crypt diff=crypt' >> .gitattributes + $ git add .gitattributes sensitive_file + $ git commit -m 'Add encrypted version of a sensitive file' + + See the gitattributes(5) man page for more information. + + If you have just cloned a repository containing files that are + encrypted, you'll want to configure transcrypt with the same cipher and + password as the origin repository. Once transcrypt has stored the + matching credentials, it will force a checkout of any existing + encrypted files in order to decrypt them. + + If the origin repository has just rekeyed, all clones should flush + their transcrypt credentials, fetch and merge the new encrypted files + via Git, and then re-configure transcrypt with the new credentials. + + AUTHOR + Aaron Bull Schaefer + + SEE ALSO + enc(1), gitattributes(5) + EOF +} + +##### MAIN + +# reset all variables that might be set +cipher='' +password='' +interactive='true' +display_config='' +rekey='' +flush_creds='' +uninstall='' +show_file='' +gpg_recipient='' +gpg_import_file='' + +# used to bypass certain safety checks +requires_existing_config='' +requires_clean_repo='true' + +# parse command line options +while [[ "${1:-}" != '' ]]; do + case $1 in + -c | --cipher) + cipher=$2 + shift + ;; + --cipher=*) + cipher=${1#*=} + ;; + -p | --password) + password=$2 + shift + ;; + --password=*) + password=${1#*=} + ;; + -y | --yes) + interactive='' + ;; + -d | --display) + display_config='true' + requires_existing_config='true' + requires_clean_repo='' + ;; + -r | --rekey) + rekey='true' + requires_existing_config='true' + ;; + -f | --flush-credentials) + flush_creds='true' + requires_existing_config='true' + ;; + -F | --force) + requires_clean_repo='' + ;; + -u | --uninstall) + uninstall='true' + requires_existing_config='true' + requires_clean_repo='' + ;; + -l | --list) + list_files + exit 0 + ;; + -s | --show-raw) + show_file=$2 + show_raw_file + exit 0 + ;; + --show-raw=*) + show_file=${1#*=} + show_raw_file + exit 0 + ;; + -e | --export-gpg) + gpg_recipient=$2 + requires_existing_config='true' + requires_clean_repo='' + shift + ;; + --export-gpg=*) + gpg_recipient=${1#*=} + requires_existing_config='true' + requires_clean_repo='' + ;; + -i | --import-gpg) + gpg_import_file=$2 + shift + ;; + --import-gpg=*) + gpg_import_file=${1#*=} + ;; + -v | --version) + printf 'transcrypt %s\n' "$VERSION" + exit 0 + ;; + -h | --help | -\?) + help + exit 0 + ;; + --*) + warn 'unknown option -- %s' "${1#--}" + usage + exit 1 + ;; + *) + warn 'unknown option -- %s' "${1#-}" + usage + exit 1 + ;; + esac + shift +done + +# always run our safety checks +run_safety_checks + +# in order to keep behavior consistent no matter what order the options were +# specified in, we must run these here rather than in the case statement above +if [[ $uninstall ]]; then + uninstall_transcrypt + exit 0 +elif [[ $display_config ]] && [[ $flush_creds ]]; then + display_configuration + printf '\n' + flush_credentials + exit 0 +elif [[ $display_config ]]; then + display_configuration + exit 0 +elif [[ $flush_creds ]]; then + flush_credentials + exit 0 +elif [[ $gpg_recipient ]]; then + export_gpg + exit 0 +elif [[ $gpg_import_file ]]; then + import_gpg +elif [[ $cipher ]]; then + validate_cipher +fi + +# perform function calls to configure transcrypt +get_cipher +get_password + +if [[ $rekey ]] && [[ $interactive ]]; then + confirm_rekey +elif [[ $interactive ]]; then + confirm_configuration +fi + +save_configuration + +if [[ $rekey ]]; then + stage_rekeyed_files +else + force_checkout +fi + +# ensure the git attributes file exists +if [[ ! -f $GIT_ATTRIBUTES ]]; then + mkdir -p "${GIT_ATTRIBUTES%/*}" + printf '#pattern filter=crypt diff=crypt\n' >"$GIT_ATTRIBUTES" +fi + +printf 'The repository has been successfully configured by transcrypt.\n' + +exit 0 diff --git a/variables.nix b/variables.nix new file mode 100644 index 0000000..78f594c --- /dev/null +++ b/variables.nix @@ -0,0 +1,18 @@ +{ lib, ... }: + +# This file contains global constants that are +# used thoughout the configuration files. They are +# "variables", in the sense that they can change +# from time to time and we don't like to search-replace. +{ + options.var = lib.mkOption { + type = lib.types.attrs; + readOnly = true; + default = { + hostname = "maxwell.ydns.eu"; + ipAddress = "2.25.5.112"; + }; + description = "Global constants."; + }; + +}