# based on https://gist.github.com/ajs124/ff04ab14435908d914cf5cedbc56a52e { config, lib, pkgs, ... }: with lib; let cfg = config.services.rt; configFile = pkgs.writeTextFile { name = "RT_SiteConfig.pm"; text = '' use utf8; # System (Base configuration) Set($rtname, '${cfg.rtName}'); # Changing this will break responses to existing tickets Set($Organization, '${cfg.organization}'); # Changing this will break all existing tickets Set($CorrespondAddress, '${cfg.correspondAddress}'); Set($CommentAddress, '${cfg.commentAddress}'); Set($WebDomain, '${cfg.domain}'); Set($Timezone, '${cfg.timeZone}'); Set($DatabaseType, 'Pg'); Set($DatabaseHost, '/run/postgresql'); Set($DatabaseUser, 'rt'); Set($DatabaseName, 'rt5'); # System (Logging) Set($LogToSTDERR, undef); # Don't log twice # System (Incoming mail gateway) Set($OwnerEmail, '${cfg.ownerEmail}'); Set($MaxAttachmentSize, 15360000); Set($CheckMoreMSMailHeaders, 1); Set($RTAddressRegexp, '^(helpdesk)\@(m-labs.hk|m-labs-intl.com)$'); Set($LoopsToRTOwner, 0); # System (Outgoing mail) Set($SetOutgoingMailFrom, 'helpdesk@m-labs.hk'); # System (Sendmail configuration) Set($SendmailPath, '${cfg.sendmailPath}'); Set($SendmailArguments, '${concatStringsSep " " cfg.sendmailArguments}'); # System (Application logic) Set($ParseNewMessageForTicketCcs, 1); # System (Extra Security) Set($RestrictLoginReferrer, 1); # System (Date and time handling) Set($DefaultTimeUnitsToHours, 1); Set($TimeInICal, 1); Set($DateTimeFormat, 'RFC2822'); # System (Authorization and user configuration) Set($AutoLogoff, 262800); # 6 months Set($WebSecureCookies, 1); # Web Interface (Base configuration) Set($CanonicalizeRedirectURLs, 1); Set($CanonicalizeURLsInFeeds, 1); Set($WebBaseURL, '${cfg.baseUrl}'); Set($LogoLinkURL, '${cfg.baseUrl}'); # Web Interface (Home page) Set($DefaultSummaryRows, 50); # Web Interface (Ticket search) Set($DefaultSearchResultOrder, 'DESC'); # Display newer tickets first Set($SearchResultsAutoRedirect, 1); # Don't show result list when there is only one match Set(%FullTextSearch, Enable => 1, Indexed => 1, Column => 'ContentIndex', Table => 'AttachmentsIndex', ); # Web Interface (Ticket options) Set($ShowMoreAboutPrivilegedUsers, 1); Set($MoreAboutRequestorGroupsLimit, undef); Set($HideUnsetFieldsOnDisplay, 1); # Web Interface (Articles) Set($ArticleOnTicketCreate, 0); # Web Interface (Message box properties) Set($MessageBoxRichText, 0); Set($MessageBoxIncludeSignatureOnComment, 0); # Web Interface (Transaction display) Set($MaxInlineBody, 0); Set($SuppressInlineTextFiles, 1); # Web Interface (Administrative interface) Set($ShowRTPortal, 0); Set($ShowEditSsytemConfig, 0); # Features (External storage) Set(%ExternalStorage, Type => 'Disk', Path => '/var/lib/rt/attachments', ); Set($ExternalStorageCutoffSize, 0); # Features (Cryptography) Set(%Crypt, RejectOnMissingPrivateKey => 0, RejectOnBadData => 0, AllowEncryptDataInDB => 0); Set(%SMIME, Enable => 1, Keyring => '${pkgs.cacert}/etc/ssl/certs/'); Set(%GnuPG, Enable => 1); Set(%GnuPGOptions, 'keyserver' => 'hkp://keys.openpgp.org', 'always-trust' => undef, 'auto-key-locate' => 'keyserver', 'keyserver-options' => 'auto-key-retrieve' ); ${cfg.extraConfig} 1; ''; checkPhase = '' ${pkgs.perl}/bin/perl -c $out ''; }; in { options.services.rt = with types; { enable = mkEnableOption "rt system"; package = mkOption { description = "Package to use"; default = pkgs.rt; defaultText = "pkgs.rt"; type = package; }; baseUrl = mkOption { description = "Base URL for web interface"; default = "https://${cfg.domain}"; defaultText = "https://\${cfg.domain}"; type = str; }; commentAddress = mkOption { description = "Default address from/to which comments are sent"; type = str; }; correspondAddress = mkOption { description = "Default address from/to which correspondences are sent"; type = str; }; domain = mkOption { description = "Which domain RT is running on"; type = str; }; ownerEmail = mkOption { description = "Address of a human who manages RT. RT will send errors generated by the mail gateway to this address; it will also be displayed as the contact person on the RT's login page."; type = str; }; port = mkOption { description = "Which port rt-server should listen on"; type = port; default = 4201; }; sendmailPath = mkOption { description = "Sendmail binary used to send... mail"; default = "${pkgs.msmtp}/bin/sendmail"; defaultText = "\${pkgs.msmtp}/bin/sendmail"; type = str; }; sendmailArguments = mkOption { description = "Arguments to call sendmailPath with"; default = [ ]; type = listOf (oneOf [ str path ]); }; timeZone = mkOption { description = "Used to convert times entered by users into GMT, as they are stored in the database, and back again; users can override this"; type = str; default = config.time.timeZone; defaultText = "[time.timeZone]"; }; rtName = mkOption { description = "Name of this RT instance"; type = str; }; organization = mkOption { description = "Name of the organization of this instance"; type = str; }; extraConfig = mkOption { description = "Verbatim config to append to generated on"; type = lines; default = ""; }; }; config = let components = [ "rt-clean-sessions" "rt-email-dashboards" "rt-email-digest-daily" "rt-email-digest-weekly" "rt-externalize-attachments" "rt-fulltext-indexer" "rt-validator" ]; mkTimer = name: { "${name}" = { wantedBy = [ "timers.target" ]; timerConfig.Unit = [ "${name}.service" ]; }; }; mkService = name: extraArgs: { "${name}" = { stopIfChanged = false; serviceConfig = { ExecStart = if extraArgs == "" then "${cfg.package}/bin/${name}" else mkForce "${cfg.package}/bin/${name} ${extraArgs}"; User = "rt"; Group = "rt"; PrivateNetwork = false; MemoryDenyWriteExecute = false; }; environment = { RT_SITE_CONFIG = configFile; }; path = with pkgs; [ w3m ]; }; }; in (mkIf cfg.enable { systemd.services = mkMerge ((map (c: mkService c "") components) ++ [ (mkService "rt-server" "--port ${toString cfg.port} --server Starman") (mkService "rt-clean-sessions" "--skip-user") (mkService "rt-fulltext-indexer" "--limit 500000") (mkService "rt-validator" "--check") { rt-server = { serviceConfig = { StateDirectory = [ "rt/" "rt/attachments/" "rt/shredder/" "rt/smime/" ]; RuntimeDirectory = [ "rt/" "rt/mason_data/" ]; LogsDirectory = "rt/"; }; after = [ "postgresql.service" ]; wantedBy = [ "multi-user.target" ]; }; } { rt-externalize-attachments = { serviceConfig.StateDirectory = "rt/attachments/"; }; } { rt-email-digest-daily.serviceConfig.ExecStart = mkForce "${cfg.package}/bin/rt-email-digest -m daily"; } { rt-email-digest-weekly.serviceConfig.ExecStart = mkForce "${cfg.package}/bin/rt-email-digest -m weekly"; } ]); systemd.timers = mkMerge ((map mkTimer components) ++ [ { rt-clean-sessions.timerConfig.OnCalendar = "daily"; rt-email-dashboards.timerConfig.OnCalendar = "hourly"; rt-email-digest-daily.timerConfig.OnCalendar = "daily"; rt-email-digest-weekly.timerConfig.OnCalendar = "weekly"; rt-externalize-attachments.timerConfig.OnCalendar = "01:00"; rt-fulltext-indexer.timerConfig.OnCalendar = "02:00"; rt-validator.timerConfig.OnCalendar = "*-*-01 03:00:00"; } ]); users.users.rt = { isSystemUser = true; group = "rt"; }; users.groups.rt = {}; systemd.tmpfiles.rules = [ "d /var/lib/secrets/rt 0500 rt rt -" "d /var/lib/rt/gpg 0700 rt rt -" ]; }); }