diff options
Diffstat (limited to '3modules')
| -rw-r--r-- | 3modules/tv/consul.nix | 122 | ||||
| -rw-r--r-- | 3modules/tv/ejabberd.nix | 171 | ||||
| -rw-r--r-- | 3modules/tv/git.nix | 406 | ||||
| -rw-r--r-- | 3modules/tv/identity.nix | 71 | ||||
| -rw-r--r-- | 3modules/tv/iptables.nix | 129 | ||||
| -rw-r--r-- | 3modules/tv/nginx.nix | 83 | ||||
| -rw-r--r-- | 3modules/tv/retiolum.nix | 241 | ||||
| -rw-r--r-- | 3modules/tv/urlwatch.nix | 156 | 
8 files changed, 1379 insertions, 0 deletions
| diff --git a/3modules/tv/consul.nix b/3modules/tv/consul.nix new file mode 100644 index 000000000..dc9a180dd --- /dev/null +++ b/3modules/tv/consul.nix @@ -0,0 +1,122 @@ +{ config, lib, pkgs, ... }: + +# if quorum gets lost, then start any node with a config that doesn't contain bootstrap_expect +# but -bootstrap +# TODO consul-bootstrap HOST  that actually does is +# TODO tools to inspect state of a cluster in outage state + +with builtins; +with lib; +let +  cfg = config.tv.consul; + +  out = { +    imports = [ ../../3modules/tv/iptables.nix ]; +    options.tv.consul = api; +    config = mkIf cfg.enable (mkMerge [ +      imp +      { tv.iptables.input-retiolum-accept-new-tcp = [ "8300" "8301" ]; } +      # TODO udp for 8301 +    ]); +  }; + +  api = { +    # TODO inherit (lib) api.options.enable; oder so +    enable = mkOption { +      type = types.bool; +      default = false; +      description = "enable tv.consul"; +    }; +    dc = mkOption { +      type = types.unspecified; +    }; +    hosts = mkOption { +      type = with types; listOf unspecified; +    }; +    encrypt-file = mkOption { +      type = types.str; # TODO path (but not just into store) +      default = "/root/src/secrets/consul-encrypt.json"; +    }; +    data-dir = mkOption { +      type = types.str; # TODO path (but not just into store) +      default = "/var/lib/consul"; +    }; +    self = mkOption { +      type = types.unspecified; +    }; +    server = mkOption { +      type = types.bool; +      default = false; +    }; +    GOMAXPROCS = mkOption { +      type = types.int; +      default = cfg.self.cores; +    }; +  }; + +  consul-config = { +    datacenter = cfg.dc; +    data_dir = cfg.data-dir; +    log_level = "INFO"; +    #node_name = +    server = cfg.server; +    bind_addr = cfg.self.addr; # TODO cfg.addr +    enable_syslog = true; +    retry_join = map (getAttr "addr") (filter (host: host.fqdn != cfg.self.fqdn) cfg.hosts); +    leave_on_terminate = true; +  } // optionalAttrs cfg.server { +    bootstrap_expect = length cfg.hosts; +    leave_on_terminate = false; +  }; + +  imp = { +    environment.systemPackages = with pkgs; [ +      consul +    ]; + +    systemd.services.consul = { +      after = [ "network.target" ]; +      wantedBy = [ "multi-user.target" ]; +      path = with pkgs; [ +        consul +      ]; +      environment = { +        GOMAXPROCS = toString cfg.GOMAXPROCS; +      }; +      serviceConfig = { +        PermissionsStartOnly = "true"; +        SyslogIdentifier = "consul"; +        User = user.name; +        PrivateTmp = "true"; +        Restart = "always"; +        ExecStartPre = pkgs.writeScript "consul-init" '' +          #! /bin/sh +          mkdir -p ${cfg.data-dir} +          chown consul: ${cfg.data-dir} +          install -o ${user.name} -m 0400 ${cfg.encrypt-file} /tmp/encrypt.json +        ''; +        ExecStart = pkgs.writeScript "consul-service" '' +          #! /bin/sh +          set -euf +          exec >/dev/null +          exec consul agent \ +            -config-file=${toFile "consul.json" (toJSON consul-config)} \ +            -config-file=/tmp/encrypt.json +        ''; +        #-node=${cfg.self.fqdn} \ +        #ExecStart = "${tinc}/sbin/tincd -c ${confDir} -d 0 -U ${user} -D"; +      }; +    }; + +    users.extraUsers = singleton { +      inherit (user) name uid; +    }; +  }; + +  user = { +    name = "consul"; +    uid = 2983239726; # genid consul +  }; + +in +out diff --git a/3modules/tv/ejabberd.nix b/3modules/tv/ejabberd.nix new file mode 100644 index 000000000..ffcc5cfb4 --- /dev/null +++ b/3modules/tv/ejabberd.nix @@ -0,0 +1,171 @@ +{ config, lib, pkgs, ... }: + +with builtins; +with lib; +let +  cfg = config.tv.ejabberd; + +  out = { +    options.tv.ejabberd = api; +    config = mkIf cfg.enable imp; +  }; + +  api = { +    enable = mkOption { +      type = types.bool; +      default = false; +    }; + +    certFile = mkOption { +      type = types.str; +      default = "/root/src/secrets/ejabberd.pem"; +    }; + +    hosts = mkOption { +      type = with types; listOf str; +    }; +  }; + +  imp = { +    environment.systemPackages = [ my-ejabberdctl ]; + +    systemd.services.ejabberd = { +      wantedBy = [ "multi-user.target" ]; +      after = [ "network.target" ]; +      serviceConfig = { +        Type = "oneshot"; +        RemainAfterExit = "yes"; +        PermissionsStartOnly = "true"; +        SyslogIdentifier = "ejabberd"; +        User = user.name; +        PrivateTmp = "true"; +        ExecStartPre = pkgs.writeScript "ejabberd-start" '' +          #! /bin/sh +          install -o ${user.name} -m 0400 ${cfg.certFile} /tmp/certfile.pem +        ''; +        ExecStart = pkgs.writeScript "ejabberd-service" '' +          #! /bin/sh +          ${my-ejabberdctl}/bin/ejabberdctl start +        ''; +      }; +    }; + +    users.extraUsers = singleton { +      inherit (user) name uid; +      home = "/var/ejabberd"; +      createHome = true; +    }; +  }; + +  user = { +    name = "ejabberd"; +    uid = 405222;  +    # TODO uid = 3483034447; # genid ejabberd +  }; + +  my-ejabberdctl = pkgs.writeScriptBin "ejabberdctl" '' +    #! /bin/sh +    set -euf +    exec env \ +        SPOOLDIR=/var/ejabberd \ +        EJABBERD_CONFIG_PATH=${config-file} \ +      ${pkgs.ejabberd}/bin/ejabberdctl \ +        --logs /var/ejabberd \ +        "$@" +  ''; + +  config-file = pkgs.writeText "ejabberd.cfg" '' +    {loglevel, 3}. +    {hosts, ${toErlang cfg.hosts}}. +    {listen, +     [ +      {5222, ejabberd_c2s, [ +          starttls, +          {certfile, "/tmp/certfile.pem"}, +          {access, c2s}, +          {shaper, c2s_shaper}, +          {max_stanza_size, 65536} +               ]}, +      {5269, ejabberd_s2s_in, [ +             {shaper, s2s_shaper}, +             {max_stanza_size, 131072} +            ]}, +      {5280, ejabberd_http, [ +           captcha, +           http_bind, +           http_poll, +           web_admin +          ]} +     ]}. +    {s2s_use_starttls, required}. +    {s2s_certfile, "/tmp/certfile.pem"}. +    {auth_method, internal}. +    {shaper, normal, {maxrate, 1000}}. +    {shaper, fast, {maxrate, 50000}}. +    {max_fsm_queue, 1000}. +    {acl, local, {user_regexp, ""}}. +    {access, max_user_sessions, [{10, all}]}. +    {access, max_user_offline_messages, [{5000, admin}, {100, all}]}. +    {access, local, [{allow, local}]}. +    {access, c2s, [{deny, blocked}, +             {allow, all}]}. +    {access, c2s_shaper, [{none, admin}, +              {normal, all}]}. +    {access, s2s_shaper, [{fast, all}]}. +    {access, announce, [{allow, admin}]}. +    {access, configure, [{allow, admin}]}. +    {access, muc_admin, [{allow, admin}]}. +    {access, muc_create, [{allow, local}]}. +    {access, muc, [{allow, all}]}. +    {access, pubsub_createnode, [{allow, local}]}. +    {access, register, [{allow, all}]}. +    {language, "en"}. +    {modules, +     [ +      {mod_adhoc,    []}, +      {mod_announce, [{access, announce}]}, +      {mod_blocking,[]}, +      {mod_caps,     []}, +      {mod_configure,[]}, +      {mod_disco,    []}, +      {mod_irc,      []}, +      {mod_http_bind, []}, +      {mod_last,     []}, +      {mod_muc,      [ +          {access, muc}, +          {access_create, muc_create}, +          {access_persistent, muc_create}, +          {access_admin, muc_admin} +         ]}, +      {mod_offline,  [{access_max_user_messages, max_user_offline_messages}]}, +      {mod_ping,     []}, +      {mod_privacy,  []}, +      {mod_private,  []}, +      {mod_pubsub,   [ +          {access_createnode, pubsub_createnode}, +          {ignore_pep_from_offline, true}, +          {last_item_cache, false}, +          {plugins, ["flat", "hometree", "pep"]} +         ]}, +      {mod_register, [ +          {welcome_message, {"Welcome!", +                 "Hi.\nWelcome to this XMPP server."}}, +          {ip_access, [{allow, "127.0.0.0/8"}, +                 {deny, "0.0.0.0/0"}]}, +          {access, register} +         ]}, +      {mod_roster,   []}, +      {mod_shared_roster,[]}, +      {mod_stats,    []}, +      {mod_time,     []}, +      {mod_vcard,    []}, +      {mod_version,  []} +     ]}. +  ''; + + +  # XXX this is a placeholder that happens to work the default strings. +  toErlang = builtins.toJSON; + +in +out diff --git a/3modules/tv/git.nix b/3modules/tv/git.nix new file mode 100644 index 000000000..4ab0df257 --- /dev/null +++ b/3modules/tv/git.nix @@ -0,0 +1,406 @@ +arg@{ config, pkgs, lib, ... }: + +with builtins; +with lib; +let +  cfg = config.tv.git; + +  out = { +    imports = [ +      ../../3modules/tv/nginx.nix +    ]; +    options.tv.git = api; +    config = mkIf cfg.enable (mkMerge [ +      imp +    ]); +  }; + +  api = { +    enable = mkOption { +      type = types.bool; +      default = false; +      description = "Enable Git repository hosting."; +    }; +    cgit = mkOption { +      type = types.bool; +      default = true; +      description = "Enable cgit."; # TODO better desc; talk about nginx +    }; +    dataDir = mkOption { +      type = types.str; +      default = "/var/lib/git"; +      description = "Directory used to store repositories."; +    }; +    etcDir = mkOption { +      type = types.str; +      default = "/etc/git"; +    }; +    rules = mkOption { +      type = types.unspecified; +    }; +    repos = mkOption { +      type = types.attrsOf (types.submodule ({ +        options = { +          desc = mkOption { +            type = types.nullOr types.str; +            default = null; +            description = '' +              Repository description. +            ''; +          }; +          section = mkOption { +            type = types.nullOr types.str; +            default = null; +            description = '' +              Repository section. +            ''; +          }; +          name = mkOption { +            type = types.str; +            description = '' +              Repository name. +            ''; +          }; +          hooks = mkOption { +            type = types.attrsOf types.str; +            description = '' +              Repository-specific hooks. +            ''; +          }; +          public = mkOption { +            type = types.bool; +            default = false; +            description = '' +              Allow everybody to read the repository via HTTP if cgit enabled. +            ''; +            # TODO allow every configured user to fetch the repository via SSH. +          }; +        }; +      })); + +      default = {}; + +      example = literalExample '' +        { +          testing = { +            name = "testing"; +            hooks.post-update = ''' +              #! /bin/sh +              set -euf +              echo post-update hook: $* >&2 +            '''; +          }; +          testing2 = { name = "testing2"; }; +        } +      ''; + +      description = '' +        Repositories. +      ''; +    }; +    users = mkOption { +      type = types.unspecified; +    }; +  }; + +  imp = { +    system.activationScripts.git-init = "${init-script}"; +     +    # TODO maybe put all scripts here and then use PATH? +    environment.etc."${etc-base}".source = +      scriptFarm "git-ssh-authorizers" { +        authorize-command = makeAuthorizeScript (map ({ repo, user, perm }: [ +          (map getName (ensureList user)) +          (map getName (ensureList repo)) +          (map getName perm.allow-commands) +        ]) cfg.rules); +     +        authorize-push = makeAuthorizeScript (map ({ repo, user, perm }: [ +          (map getName (ensureList user)) +          (map getName (ensureList repo)) +          (ensureList perm.allow-receive-ref) +          (map getName perm.allow-receive-modes) +        ]) (filter (x: hasAttr "allow-receive-ref" x.perm) cfg.rules)); +      }; +     +    users.extraUsers = singleton { +      description = "Git repository hosting user"; +      name = "git"; +      shell = "/bin/sh"; +      openssh.authorizedKeys.keys = +        mapAttrsToList (_: makeAuthorizedKey git-ssh-command) cfg.users; +      uid = 112606723; # genid git +    }; +  }; + + +  ensureList = x: +    if typeOf x == "list" then x else [x]; + +  getName = x: x.name; + +  isPublicRepo = getAttr "public"; # TODO this is also in ./cgit.nix + +  makeAuthorizedKey = git-ssh-command: user@{ name, pubkey }: +    # TODO assert name +    # TODO assert pubkey +    let +      options = concatStringsSep "," [ +        ''command="exec ${git-ssh-command} ${name}"'' +        "no-agent-forwarding" +        "no-port-forwarding" +        "no-pty" +        "no-X11-forwarding" +      ]; +    in +    "${options} ${pubkey}"; + +  # [case-pattern] -> shell-script +  # Create a shell script that succeeds (exit 0) when all its arguments +  # match the case patterns (in the given order). +  makeAuthorizeScript = +    let +      # TODO escape +      to-pattern = x: concatStringsSep "|" (ensureList x); +      go = i: ps: +        if ps == [] +          then "exit 0" +          else '' +            case ''$${toString i} in ${to-pattern (head ps)}) +            ${go (i + 1) (tail ps)} +            esac''; +    in +    patterns: '' +      #! /bin/sh +      set -euf +      ${concatStringsSep "\n" (map (go 1) patterns)} +      exit -1 +    ''; + +  reponames = rules: sort lessThan (unique (map (x: x.repo.name) rules)); + +  # TODO makeGitHooks that uses runCommand instead of scriptFarm? +  scriptFarm = +    farm-name: scripts: +    let +      makeScript = script-name: script-string: { +        name = script-name; +        path = pkgs.writeScript "${farm-name}_${script-name}" script-string; +      }; +    in +    pkgs.linkFarm farm-name (mapAttrsToList makeScript scripts); + + +  git-ssh-command = pkgs.writeScript "git-ssh-command" '' +    #! /bin/sh +    set -euf + +    PATH=${makeSearchPath "bin" (with pkgs; [ +      coreutils +      git +      gnugrep +      gnused +      systemd +    ])} + +    abort() { +      echo "error: $1" >&2 +      systemd-cat -p err -t git echo "error: $1" +      exit -1 +    } + +    GIT_SSH_USER=$1 + +    systemd-cat -p info -t git echo \ +      "authorizing $GIT_SSH_USER $SSH_CONNECTION $SSH_ORIGINAL_COMMAND" + +    # References: The Base Definitions volume of +    # POSIX.1‐2013, Section 3.278, Portable Filename Character Set +    portable_filename_bre="^[A-Za-z0-9._-]\\+$" + +    command=$(echo "$SSH_ORIGINAL_COMMAND" \ +      | sed -n 's/^\([^ ]*\) '"'"'\(.*\)'"'"'/\1/p' \ +      | grep "$portable_filename_bre" \ +      || abort 'cannot read command') + +    GIT_SSH_REPO=$(echo "$SSH_ORIGINAL_COMMAND" \ +      | sed -n 's/^\([^ ]*\) '"'"'\(.*\)'"'"'/\2/p' \ +      | grep "$portable_filename_bre" \ +      || abort 'cannot read reponame') + +    ${cfg.etcDir}/authorize-command \ +        "$GIT_SSH_USER" "$GIT_SSH_REPO" "$command" \ +      || abort 'access denied' + +    repodir=${escapeShellArg cfg.dataDir}/$GIT_SSH_REPO + +    systemd-cat -p info -t git \ +      echo "authorized exec $command $repodir" + +    export GIT_SSH_USER +    export GIT_SSH_REPO +    exec "$command" "$repodir" +  ''; + +  init-script = pkgs.writeScript "git-init" '' +    #! /bin/sh +    set -euf + +    PATH=${makeSearchPath "bin" (with pkgs; [ +      coreutils +      findutils +      gawk +      git +      gnugrep +      gnused +    ])} + +    dataDir=${escapeShellArg cfg.dataDir} +    mkdir -p "$dataDir" + +    # Notice how the presence of hooks symlinks determine whether +    # we manage a repositry or not. + +    # Make sure that no existing repository has hooks.  We can delete +    # symlinks because we assume we created them. +    find "$dataDir" -mindepth 2 -maxdepth 2 -name hooks -type l -delete +    bad_hooks=$(find "$dataDir" -mindepth 2 -maxdepth 2 -name hooks) +    if echo "$bad_hooks" | grep -q .; then +      printf 'error: unknown hooks:\n%s\n' \ +        "$(echo "$bad_hooks" | sed 's/^/  /')" \ +        >&2 +      exit -1 +    fi + +    # Initialize repositories. +    ${concatMapStringsSep "\n" (repo: +      let +        hooks = scriptFarm "git-hooks" (makeHooks repo); +      in +      '' +        reponame=${escapeShellArg repo.name} +        repodir=$dataDir/$reponame +        mode=${toString (if isPublicRepo repo then 0711 else 0700)} +        if ! test -d "$repodir"; then +          mkdir -m "$mode" "$repodir" +          git init --bare --template=/var/empty "$repodir" +          chown -R git:nogroup "$repodir" +        fi +        ln -s ${hooks} "$repodir/hooks" +      '' +    ) (attrValues cfg.repos)} + +    # Warn about repositories that exist but aren't mentioned in the +    # current configuration (and thus didn't receive a hooks symlink). +    unknown_repos=$(find "$dataDir" -mindepth 1 -maxdepth 1 \ +      -type d \! -exec test -e '{}/hooks' \; -print) +    if echo "$unknown_repos" | grep -q .; then +      printf 'warning: stale repositories:\n%s\n' \ +        "$(echo "$unknown_repos" | sed 's/^/  /')" \ +        >&2 +    fi +  ''; + +  makeHooks = repo: removeAttrs repo.hooks [ "pre-receive" ] // { +    pre-receive = '' +      #! /bin/sh +      set -euf + +      PATH=${makeSearchPath "bin" (with pkgs; [ +        coreutils # env +        git +        systemd +      ])} + +      accept() { +        #systemd-cat -p info -t git echo "authorized $1" +        accept_string="''${accept_string+$accept_string +      }authorized $1" +      } +      reject() { +        #systemd-cat -p err -t git echo "denied $1" +        #echo 'access denied' >&2 +        #exit_code=-1 +        reject_string="''${reject_string+$reject_string +      }access denied: $1" +      } + +      empty=0000000000000000000000000000000000000000 + +      accept_string= +      reject_string= +      while read oldrev newrev ref; do + +        if [ $oldrev = $empty ]; then +          receive_mode=create +        elif [ $newrev = $empty ]; then +          receive_mode=delete +        elif [ "$(git merge-base $oldrev $newrev)" = $oldrev ]; then +          receive_mode=fast-forward +        else +          receive_mode=non-fast-forward +        fi + +        if ${cfg.etcDir}/authorize-push \ +            "$GIT_SSH_USER" "$GIT_SSH_REPO" "$ref" "$receive_mode"; then +          accept "$receive_mode $ref" +        else +          reject "$receive_mode $ref" +        fi +      done + +      if [ -n "$reject_string" ]; then +        systemd-cat -p err -t git echo "$reject_string" +        exit -1 +      fi + +      systemd-cat -p info -t git echo "$accept_string" + +      ${optionalString (hasAttr "post-receive" repo.hooks) '' +        # custom post-receive hook +        ${repo.hooks.post-receive}''} +    ''; +  }; + +  etc-base = +    assert (hasPrefix "/etc/" cfg.etcDir); +    removePrefix "/etc/" cfg.etcDir; + +in +out + + + + + + + + + + + +#let +#  inherit (lib) mkIf mkMerge; +# +#  cfg = config.tv.git; +#  arg' = arg // { inherit cfg; }; +#in +# +## TODO unify logging of shell scripts to user and journal +## TODO move all scripts to ${etcDir}, so ControlMaster connections +##       immediately pick up new authenticators +## TODO when authorized_keys changes, then restart ssh +##       (or kill already connected users somehow) +# +#{ +#  imports = [ +#    ../../3modules/tv/nginx.nix +#  ]; +# +#  options.tv.git = import ./options.nix arg'; +# +#  config = mkIf cfg.enable (mkMerge [ +#    (import ./config.nix arg') +#    (mkIf cfg.cgit (import ./cgit.nix arg')) +#  ]); +#} diff --git a/3modules/tv/identity.nix b/3modules/tv/identity.nix new file mode 100644 index 000000000..7cf90de01 --- /dev/null +++ b/3modules/tv/identity.nix @@ -0,0 +1,71 @@ +{ lib, ... }: + +with lib; + +let + +  cfg = config.tv.identity; + +  out = { +    options.tv.identity = api; +    #config = mkIf cfg.enable imp; +  }; + +  api = { +    enable = mkOption { +      type = types.bool; +      default = false; +    }; +    self = mkOption { +      type = types.unspecified; +    }; +    hosts = mkOption { +      type = with types; attrsOf unspecified; +      default = { +        cd = { +          #dc = "cac"; +          dc = "tv"; +          fqdn = "cd.retiolum"; +          addr = "10.243.113.222"; +          #addr6 = "42:4522:25f8:36bb:8ccb:0150:231a:2af3"; +          #internet-addr = "162.219.5.183"; +          cores = 2; +        }; +        mkdir = { +          #dc = "cac"; +          dc = "tv"; +          fqdn = "mkdir.retiolum"; +          addr = "10.243.113.223"; +          cores = 1; +        }; +        nomic = { +          #dc = "gg"; +          dc = "tv"; +          fqdn = "nomic.retiolum"; +          addr = "10.243.0.110"; +          cores = 2; +        }; +        rmdir = { +          #dc = "cac"; +          dc = "tv"; +          fqdn = "rmdir.retiolum"; +          addr = "10.243.113.224"; +          #addr = "42:4522:25f8:36bb:8ccb:0150:231a:2af5"; +          cores = 1; +        }; +        wu = { +          #dc = "gg"; +          dc = "tv"; +          fqdn = "wu.retiolum"; +          addr = "10.243.13.37"; +          cores = 8; +        }; +      }; +    }; +  }; + +  #imp = { +  #}; + +in +out diff --git a/3modules/tv/iptables.nix b/3modules/tv/iptables.nix new file mode 100644 index 000000000..096b04e1d --- /dev/null +++ b/3modules/tv/iptables.nix @@ -0,0 +1,129 @@ +{ config, lib, pkgs, ... }: + +with builtins; +with lib; +let +  cfg = config.tv.iptables; + +  out = { +    options.tv.iptables = api; +    config = mkIf cfg.enable imp; +  }; + +  api = { +    enable = mkOption { +      type = types.bool; +      default = false; +    }; + +    input-internet-accept-new-tcp = mkOption { +      type = with types; listOf str; +      default = []; +    }; + +    input-retiolum-accept-new-tcp = mkOption { +      type = with types; listOf str; +      default = []; +    }; +  }; + +  imp = { +    networking.firewall.enable = false; + +    systemd.services.tv-iptables = { +      description = "tv-iptables"; +      wantedBy = [ "network-pre.target" ]; +      before = [ "network-pre.target" ]; +      after = [ "systemd-modules-load.service" ]; + +      path = with pkgs; [ +        iptables +      ]; +       +      restartIfChanged = true; +       +      serviceConfig = { +        Type = "simple"; +        RemainAfterExit = true; +        Restart = "always"; +        ExecStart = "@${startScript} tv-iptables_start"; +      }; +    }; +  }; + + +  accept-new-tcp = port: +    "-p tcp -m tcp --dport ${port} -m conntrack --ctstate NEW -j ACCEPT"; + +  rules = iptables-version: +    pkgs.writeText "tv-iptables-rules${toString iptables-version}" '' +      *nat +      :PREROUTING ACCEPT [0:0] +      :INPUT ACCEPT [0:0] +      :OUTPUT ACCEPT [0:0] +      :POSTROUTING ACCEPT [0:0] +      ${concatMapStringsSep "\n" (rule: "-A PREROUTING ${rule}") ([] +        ++ [ +          "! -i retiolum -p tcp -m tcp --dport 22 -j REDIRECT --to-ports 0" +          "-p tcp -m tcp --dport 11423 -j REDIRECT --to-ports 22" +        ] +      )} +      COMMIT +      *filter +      :INPUT DROP [0:0] +      :FORWARD DROP [0:0] +      :OUTPUT ACCEPT [0:0] +      :Retiolum - [0:0] +      ${concatMapStringsSep "\n" (rule: "-A INPUT ${rule}") ([] +        ++ [ +          "-m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT" +          "-i lo -j ACCEPT" +        ] +        ++ map accept-new-tcp cfg.input-internet-accept-new-tcp +        ++ ["-i retiolum -j Retiolum"] +      )} +      ${concatMapStringsSep "\n" (rule: "-A Retiolum ${rule}") ([] +        ++ { +          ip4tables = [ +            "-p icmp -m icmp --icmp-type echo-request -j ACCEPT" +          ]; +          ip6tables = [ +            "-p ipv6-icmp -m icmp6 --icmpv6-type echo-request -j ACCEPT" +          ]; +        }."ip${toString iptables-version}tables" +        ++ map accept-new-tcp cfg.input-retiolum-accept-new-tcp +        ++ { +          ip4tables = [ +            "-p tcp -j REJECT --reject-with tcp-reset" +            "-p udp -j REJECT --reject-with icmp-port-unreachable" +            "-j REJECT --reject-with icmp-proto-unreachable" +          ]; +          ip6tables = [ +            "-p tcp -j REJECT --reject-with tcp-reset" +            "-p udp -j REJECT --reject-with icmp6-port-unreachable" +            "-j REJECT" +          ]; +        }."ip${toString iptables-version}tables" +      )} +      COMMIT +    ''; + +  startScript = pkgs.writeScript "tv-iptables_start" '' +    #! /bin/sh +    set -euf +    iptables-restore < ${rules 4} +    ip6tables-restore < ${rules 6} +  ''; + +in +out + +#let +#  cfg = config.tv.iptables; +#  arg' = arg // { inherit cfg; }; +#in +# +#{ +#  options.tv.iptables = import ./options.nix arg'; +#  config = lib.mkIf cfg.enable (import ./config.nix arg'); +#} diff --git a/3modules/tv/nginx.nix b/3modules/tv/nginx.nix new file mode 100644 index 000000000..d9d95be84 --- /dev/null +++ b/3modules/tv/nginx.nix @@ -0,0 +1,83 @@ +{ config, pkgs, lib, ... }: + +with builtins; +with lib; +let +  cfg = config.tv.nginx; + +  out = { +    options.tv.nginx = api; +    config = mkIf cfg.enable imp; +  }; + +  api = { +    enable = mkOption { +      type = types.bool; +      default = false; +      description = "Enable nginx."; +    }; + +    retiolum-locations = mkOption { +      type = with types; listOf (attrsOf str); +      default = []; +    }; +  }; + +  imp = { +    services.nginx = +      let +        name = config.tv.retiolum.name; +        qname = "${name}.retiolum"; +      in +      assert config.tv.retiolum.enable; +      { +        enable = true; +        httpConfig = '' +          include           ${pkgs.nginx}/conf/mime.types; +          default_type      application/octet-stream; +          sendfile          on; +          keepalive_timeout 65; +          gzip              on; +          server { +            listen 80 default_server; +            server_name _; +            location / { +              return 404; +            } +          } +          server { +            listen 80; +            server_name ${name} ${qname}; + +            ${indent (concatStrings (map to-location cfg.retiolum-locations))} + +            location / { +              return 404; +            } +          } +        ''; +      }; +  }; + +   +  indent = replaceChars ["\n"] ["\n  "]; + +  to-location = { name, value }: '' +    location ${name} { +      ${indent value} +    } +  ''; + +in +out + + +#let +#  cfg = config.tv.nginx; +#  arg' = arg // { inherit cfg; }; +#in +# +#{ +#  options.tv.nginx = import ./options.nix arg'; +#  config = lib.mkIf cfg.enable (import ./config.nix arg'); +#} diff --git a/3modules/tv/retiolum.nix b/3modules/tv/retiolum.nix new file mode 100644 index 000000000..7c78d7124 --- /dev/null +++ b/3modules/tv/retiolum.nix @@ -0,0 +1,241 @@ +{ config, pkgs, lib, ... }: + +with builtins; +with lib; +let +  cfg = config.tv.retiolum; + +  out = { +    options.tv.retiolum = api; +    config = mkIf cfg.enable imp; +  }; + +  api = { +    enable = mkOption { +      type = types.bool; +      default = false; +      description = "Enable tinc daemon for Retiolum."; +    }; + +    name = mkOption { +      type = types.str; +      default = config.networking.hostName; +      # Description stolen from tinc.conf(5). +      description = '' +        This is the name which identifies this tinc daemon.  It must +        be unique for the virtual private network this daemon will +        connect to.  The Name may only consist of alphanumeric and +        underscore characters.  If Name starts with a $, then the +        contents of the environment variable that follows will be +        used.  In that case, invalid characters will be converted to +        underscores.  If Name is $HOST, but no such environment +        variable exist, the hostname will be read using the +        gethostnname() system call This is the name which identifies +        the this tinc daemon. +      ''; +    }; + +    generateEtcHosts = mkOption { +      type = types.str; +      default = "both"; +      description = '' +        If set to <literal>short</literal>, <literal>long</literal>, or <literal>both</literal>, +        then generate entries in <filename>/etc/hosts</filename> from subnets. +      ''; +    }; + +    network = mkOption { +      type = types.str; +      default = "retiolum"; +      description = '' +        The tinc network name. +        It is used to generate long host entries, +        derive the name of the user account under which tincd runs, +        and name the TUN device. +      ''; +    }; + +    tincPackage = mkOption { +      type = types.package; +      default = pkgs.tinc; +      description = "Tincd package to use."; +    }; + +    hosts = mkOption { +      default = null; +      description = '' +        Hosts package or path to use. +        If a path is given, then it will be used to generate an ad-hoc package. +      ''; +    }; + +    iproutePackage = mkOption { +      type = types.package; +      default = pkgs.iproute; +      description = "Iproute2 package to use."; +    }; + + +    privateKeyFile = mkOption { +      # TODO if it's types.path then it gets copied to /nix/store with +      #      bad unsafe permissions... +      type = types.str; +      default = "/root/src/secrets/retiolum.rsa_key.priv"; +      description = "Generate file with <literal>tincd -K</literal>."; +    }; + +    connectTo = mkOption { +      type = types.listOf types.str; +      default = [ "fastpoke" "pigstarter" "kheurop" ]; +      description = "TODO describe me"; | 
