From 8d213e2b1c934f6861f76aad5eb7c11097fa97de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Bouya?= Date: Wed, 22 May 2019 20:55:28 +0200 Subject: Move rest of the modules outside of nixops --- modules/private/buildbot/common/build_helpers.py | 256 ++++++++++++++++ modules/private/buildbot/common/master.cfg | 69 +++++ modules/private/buildbot/default.nix | 198 +++++++++++++ .../private/buildbot/projects/caldance/__init__.py | 190 ++++++++++++ .../buildbot/projects/cryptoportfolio/__init__.py | 169 +++++++++++ modules/private/buildbot/projects/test/__init__.py | 188 ++++++++++++ modules/private/certificates.nix | 52 ++++ modules/private/default.nix | 12 + modules/private/dns.nix | 132 +++++++++ modules/private/ftp.nix | 118 ++++++++ modules/private/gitolite/default.nix | 63 ++++ modules/private/gitolite/gitolite_ldap_groups.sh | 15 + modules/private/mail.nix | 13 + modules/private/mpd.nix | 56 ++++ modules/private/pub/default.nix | 52 ++++ modules/private/pub/restrict | 64 ++++ modules/private/pub/tmux.restrict.conf | 43 +++ modules/private/ssh/default.nix | 40 +++ modules/private/ssh/ldap_authorized_keys.sh | 152 ++++++++++ modules/private/system.nix | 30 ++ modules/private/tasks/default.nix | 327 +++++++++++++++++++++ modules/private/tasks/www/index.php | 157 ++++++++++ modules/private/websites/tools/git/default.nix | 4 +- nixops/eldiron.nix | 48 +-- nixops/modules/buildbot/common/build_helpers.py | 256 ---------------- nixops/modules/buildbot/common/master.cfg | 69 ----- nixops/modules/buildbot/default.nix | 198 ------------- .../modules/buildbot/projects/caldance/__init__.py | 190 ------------ .../buildbot/projects/cryptoportfolio/__init__.py | 169 ----------- nixops/modules/buildbot/projects/test/__init__.py | 188 ------------ nixops/modules/certificates.nix | 52 ---- nixops/modules/dns.nix | 132 --------- nixops/modules/ftp.nix | 118 -------- nixops/modules/gitolite/default.nix | 63 ---- nixops/modules/gitolite/gitolite_ldap_groups.sh | 15 - nixops/modules/mail.nix | 13 - nixops/modules/mpd.nix | 56 ---- nixops/modules/pub/default.nix | 52 ---- nixops/modules/pub/restrict | 64 ---- nixops/modules/pub/tmux.restrict.conf | 43 --- nixops/modules/ssh/default.nix | 40 --- nixops/modules/ssh/ldap_authorized_keys.sh | 152 ---------- nixops/modules/task/default.nix | 327 --------------------- nixops/modules/task/www/index.php | 157 ---------- 44 files changed, 2406 insertions(+), 2396 deletions(-) create mode 100644 modules/private/buildbot/common/build_helpers.py create mode 100644 modules/private/buildbot/common/master.cfg create mode 100644 modules/private/buildbot/default.nix create mode 100644 modules/private/buildbot/projects/caldance/__init__.py create mode 100644 modules/private/buildbot/projects/cryptoportfolio/__init__.py create mode 100644 modules/private/buildbot/projects/test/__init__.py create mode 100644 modules/private/certificates.nix create mode 100644 modules/private/dns.nix create mode 100644 modules/private/ftp.nix create mode 100644 modules/private/gitolite/default.nix create mode 100755 modules/private/gitolite/gitolite_ldap_groups.sh create mode 100644 modules/private/mail.nix create mode 100644 modules/private/mpd.nix create mode 100644 modules/private/pub/default.nix create mode 100644 modules/private/pub/restrict create mode 100644 modules/private/pub/tmux.restrict.conf create mode 100644 modules/private/ssh/default.nix create mode 100755 modules/private/ssh/ldap_authorized_keys.sh create mode 100644 modules/private/system.nix create mode 100644 modules/private/tasks/default.nix create mode 100644 modules/private/tasks/www/index.php delete mode 100644 nixops/modules/buildbot/common/build_helpers.py delete mode 100644 nixops/modules/buildbot/common/master.cfg delete mode 100644 nixops/modules/buildbot/default.nix delete mode 100644 nixops/modules/buildbot/projects/caldance/__init__.py delete mode 100644 nixops/modules/buildbot/projects/cryptoportfolio/__init__.py delete mode 100644 nixops/modules/buildbot/projects/test/__init__.py delete mode 100644 nixops/modules/certificates.nix delete mode 100644 nixops/modules/dns.nix delete mode 100644 nixops/modules/ftp.nix delete mode 100644 nixops/modules/gitolite/default.nix delete mode 100755 nixops/modules/gitolite/gitolite_ldap_groups.sh delete mode 100644 nixops/modules/mail.nix delete mode 100644 nixops/modules/mpd.nix delete mode 100644 nixops/modules/pub/default.nix delete mode 100644 nixops/modules/pub/restrict delete mode 100644 nixops/modules/pub/tmux.restrict.conf delete mode 100644 nixops/modules/ssh/default.nix delete mode 100755 nixops/modules/ssh/ldap_authorized_keys.sh delete mode 100644 nixops/modules/task/default.nix delete mode 100644 nixops/modules/task/www/index.php diff --git a/modules/private/buildbot/common/build_helpers.py b/modules/private/buildbot/common/build_helpers.py new file mode 100644 index 0000000..384b1ac --- /dev/null +++ b/modules/private/buildbot/common/build_helpers.py @@ -0,0 +1,256 @@ +from buildbot.plugins import util, steps, schedulers +from buildbot_buildslist import BuildsList + +__all__ = [ + "force_scheduler", "deploy_scheduler", "hook_scheduler", + "clean_branch", "package_and_upload", "SlackStatusPush", + "XMPPStatusPush" + ] + +# Small helpers" +@util.renderer +def clean_branch(props): + if props.hasProperty("branch") and len(props["branch"]) > 0: + return props["branch"].replace("/", "_") + else: + return "HEAD" + +def package_and_upload(package, package_dest, package_url): + return [ + steps.ShellCommand(name="build package", + logEnviron=False, haltOnFailure=True, workdir="source", + command=["git", "archive", "HEAD", "-o", package]), + + steps.FileUpload(name="upload package", workersrc=package, + workdir="source", masterdest=package_dest, + url=package_url, mode=0o644), + + steps.ShellCommand(name="cleanup package", logEnviron=False, + haltOnFailure=True, workdir="source", alwaysRun=True, + command=["rm", "-f", package]), + ] + +# Schedulers +def force_scheduler(name, builders): + return schedulers.ForceScheduler(name=name, + label="Force build", buttonName="Force build", + reason=util.StringParameter(name="reason", label="Reason", default="Force build"), + codebases=[ + util.CodebaseParameter("", + branch=util.StringParameter( + name="branch", label="Git reference (tag, branch)", required=True), + revision=util.FixedParameter(name="revision", default=""), + repository=util.FixedParameter(name="repository", default=""), + project=util.FixedParameter(name="project", default=""), + ), + ], + username=util.FixedParameter(name="username", default="Web button"), + builderNames=builders) + +def deploy_scheduler(name, builders): + return schedulers.ForceScheduler(name=name, + builderNames=builders, + label="Deploy built package", buttonName="Deploy", + username=util.FixedParameter(name="username", default="Web button"), + codebases=[ + util.CodebaseParameter(codebase="", + branch=util.FixedParameter(name="branch", default=""), + revision=util.FixedParameter(name="revision", default=""), + repository=util.FixedParameter(name="repository", default=""), + project=util.FixedParameter(name="project", default=""))], + reason=util.FixedParameter(name="reason", default="Deploy"), + properties=[ + util.ChoiceStringParameter(label="Environment", + name="environment", default="integration", + choices=["integration", "production"]), + BuildsList(label="Build to deploy", name="build"), + ] + ) + +def hook_scheduler(project, timer=10): + return schedulers.AnyBranchScheduler( + change_filter=util.ChangeFilter(category="hooks", project=project), + name=project, treeStableTimer=timer, builderNames=["{}_build".format(project)]) + +# Slack/XMPP status push +from buildbot.reporters.http import HttpStatusPushBase +from twisted.internet import defer +from twisted.python import log +from buildbot.util import httpclientservice +from buildbot.reporters import utils +from buildbot.process import results +from twisted.words.protocols.jabber.jid import JID +from wokkel import client, xmppim +from functools import partial + +class SlackStatusPush(HttpStatusPushBase): + name = "SlackStatusPush" + + @defer.inlineCallbacks + def reconfigService(self, serverUrl, **kwargs): + yield HttpStatusPushBase.reconfigService(self, **kwargs) + self._http = yield httpclientservice.HTTPClientService.getService( + self.master, serverUrl) + + @defer.inlineCallbacks + def send(self, build): + yield utils.getDetailsForBuild(self.master, build, wantProperties=True) + response = yield self._http.post("", json=self.format(build)) + if response.code != 200: + log.msg("%s: unable to upload status: %s" % + (response.code, response.content)) + + def format(self, build): + colors = [ + "#36A64F", # success + "#F1E903", # warnings + "#DA0505", # failure + "#FFFFFF", # skipped + "#000000", # exception + "#FFFFFF", # retry + "#D02CA9", # cancelled + ] + + if "environment" in build["properties"]: + msg = "{} environment".format(build["properties"]["environment"][0]) + if "build" in build["properties"]: + msg = "of archive {} in ".format(build["properties"]["build"][0]) + msg + elif len(build["buildset"]["sourcestamps"][0]["branch"]) > 0: + msg = "revision {}".format(build["buildset"]["sourcestamps"][0]["branch"]) + else: + msg = "build" + + if build["complete"]: + timedelta = int((build["complete_at"] - build["started_at"]).total_seconds()) + hours, rest = divmod(timedelta, 3600) + minutes, seconds = divmod(rest, 60) + if hours > 0: + duration = "{}h {}min {}s".format(hours, minutes, seconds) + elif minutes > 0: + duration = "{}min {}s".format(minutes, seconds) + else: + duration = "{}s".format(seconds) + + text = "Build <{}|{}> of {}'s {} was {} in {}.".format( + build["url"], build["buildid"], + build["builder"]["name"], + msg, + results.Results[build["results"]], + duration, + ) + fields = [ + { + "title": "Build", + "value": "<{}|{}>".format(build["url"], build["buildid"]), + "short": True, + }, + { + "title": "Project", + "value": build["builder"]["name"], + "short": True, + }, + { + "title": "Build status", + "value": results.Results[build["results"]], + "short": True, + }, + { + "title": "Build duration", + "value": duration, + "short": True, + }, + ] + if "environment" in build["properties"]: + fields.append({ + "title": "Environment", + "value": build["properties"]["environment"][0], + "short": True, + }) + if "build" in build["properties"]: + fields.append({ + "title": "Archive", + "value": build["properties"]["build"][0], + "short": True, + }) + attachments = [{ + "fallback": "", + "color": colors[build["results"]], + "fields": fields + }] + else: + text = "Build <{}|{}> of {}'s {} started.".format( + build["url"], build["buildid"], + build["builder"]["name"], + msg, + ) + attachments = [] + + return { + "username": "Buildbot", + "icon_url": "http://docs.buildbot.net/current/_static/icon.png", + "text": text, + "attachments": attachments, + } + +class XMPPStatusPush(HttpStatusPushBase): + name = "XMPPStatusPush" + + @defer.inlineCallbacks + def reconfigService(self, password, recipients, **kwargs): + yield HttpStatusPushBase.reconfigService(self, **kwargs) + self.password = password + self.recipients = recipients + + @defer.inlineCallbacks + def send(self, build): + yield utils.getDetailsForBuild(self.master, build, wantProperties=True) + body = self.format(build) + factory = client.DeferredClientFactory(JID("notify_bot@immae.fr/buildbot"), self.password) + d = client.clientCreator(factory) + def send_message(recipient, stream): + message = xmppim.Message(recipient=JID(recipient), body=body) + message.stanzaType = 'chat' + stream.send(message.toElement()) + # To allow chaining + return stream + for recipient in self.recipients: + d.addCallback(partial(send_message, recipient)) + d.addCallback(lambda _: factory.streamManager.xmlstream.sendFooter()) + d.addErrback(log.err) + + def format(self, build): + if "environment" in build["properties"]: + msg = "{} environment".format(build["properties"]["environment"][0]) + if "build" in build["properties"]: + msg = "of archive {} in ".format(build["properties"]["build"][0]) + msg + elif len(build["buildset"]["sourcestamps"][0]["branch"]) > 0: + msg = "revision {}".format(build["buildset"]["sourcestamps"][0]["branch"]) + else: + msg = "build" + + if build["complete"]: + timedelta = int((build["complete_at"] - build["started_at"]).total_seconds()) + hours, rest = divmod(timedelta, 3600) + minutes, seconds = divmod(rest, 60) + if hours > 0: + duration = "{}h {}min {}s".format(hours, minutes, seconds) + elif minutes > 0: + duration = "{}min {}s".format(minutes, seconds) + else: + duration = "{}s".format(seconds) + + text = "Build {} ( {} ) of {}'s {} was {} in {}.".format( + build["buildid"], build["url"], + build["builder"]["name"], + msg, + results.Results[build["results"]], + duration, + ) + else: + text = "Build {} ( {} ) of {}'s {} started.".format( + build["buildid"], build["url"], + build["builder"]["name"], + msg, + ) + + return text diff --git a/modules/private/buildbot/common/master.cfg b/modules/private/buildbot/common/master.cfg new file mode 100644 index 0000000..abe08e0 --- /dev/null +++ b/modules/private/buildbot/common/master.cfg @@ -0,0 +1,69 @@ +# -*- python -*- +# ex: set filetype=python: + +from buildbot.plugins import secrets, util, webhooks +from buildbot.util import bytes2unicode +import re +import os +from buildbot_config import E, configure +import json + +class CustomBase(webhooks.base): + def getChanges(self, request): + try: + content = request.content.read() + args = json.loads(bytes2unicode(content)) + except Exception as e: + raise ValueError("Error loading JSON: " + str(e)) + + args.setdefault("comments", "") + args.setdefault("repository", "") + args.setdefault("author", args.get("who")) + + return ([args], None) + +userInfoProvider = util.LdapUserInfo( + uri=E.LDAP_URL, + bindUser=E.LDAP_ADMIN_USER, + bindPw=open(E.SECRETS_FILE + "/ldap", "r").read().rstrip(), + accountBase=E.LDAP_BASE, + accountPattern=E.LDAP_PATTERN, + accountFullName='cn', + accountEmail='mail', + avatarData="jpegPhoto", + groupBase=E.LDAP_BASE, + groupName="cn", + groupMemberPattern=E.LDAP_GROUP_PATTERN, + ) + +c = BuildmasterConfig = { + "title": E.TITLE, + "titleURL": E.TITLE_URL, + "db": { + "db_url": "sqlite:///state.sqlite" + }, + "protocols": { "pb": { "port": E.PB_SOCKET } }, + "workers": [], + "change_source": [], + "schedulers": [], + "builders": [], + "services": [], + "secretsProviders": [ + secrets.SecretInAFile(E.SECRETS_FILE), + ], + "www": { + "change_hook_dialects": { "base": { "custom_class": CustomBase } }, + "plugins": { + "waterfall_view": {}, + "console_view": {}, + "grid_view": {}, + "buildslist": {}, + }, + "auth": util.RemoteUserAuth( + header=b"X-Remote-User", + userInfoProvider=userInfoProvider, + headerRegex=re.compile(br"(?P[^ @]+)")), + } + } + +configure(c) diff --git a/modules/private/buildbot/default.nix b/modules/private/buildbot/default.nix new file mode 100644 index 0000000..fa6a6f2 --- /dev/null +++ b/modules/private/buildbot/default.nix @@ -0,0 +1,198 @@ +{ lib, pkgs, config, myconfig, ... }: +let + varDir = "/var/lib/buildbot"; + buildbot_common = pkgs.python3Packages.buildPythonPackage rec { + name = "buildbot_common"; + src = ./common; + format = "other"; + installPhase = '' + mkdir -p $out/${pkgs.python3.pythonForBuild.sitePackages} + cp -a $src $out/${pkgs.python3.pythonForBuild.sitePackages}/buildbot_common + ''; + }; + buildbot = pkgs.python3Packages.buildbot-full; +in +{ + options = { + myServices.buildbot.enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether to enable buildbot. + ''; + }; + }; + + config = lib.mkIf config.myServices.buildbot.enable { + ids.uids.buildbot = myconfig.env.buildbot.user.uid; + ids.gids.buildbot = myconfig.env.buildbot.user.gid; + + users.groups.buildbot.gid = config.ids.gids.buildbot; + users.users.buildbot = { + name = "buildbot"; + uid = config.ids.uids.buildbot; + group = "buildbot"; + description = "Buildbot user"; + home = varDir; + extraGroups = [ "keys" ]; + }; + + services.websites.tools.vhostConfs.git.extraConfig = lib.attrsets.mapAttrsToList (k: project: '' + RedirectMatch permanent "^/buildbot/${project.name}$" "/buildbot/${project.name}/" + RewriteEngine On + RewriteRule ^/buildbot/${project.name}/ws(.*)$ unix:///run/buildbot/${project.name}.sock|ws://git.immae.eu/ws$1 [P,NE,QSA,L] + ProxyPass /buildbot/${project.name}/ unix:///run/buildbot/${project.name}.sock|http://${project.name}-git.immae.eu/ + ProxyPassReverse /buildbot/${project.name}/ unix:///run/buildbot/${project.name}.sock|http://${project.name}-git.immae.eu/ + + Use LDAPConnect + Require ldap-group cn=users,ou=${project.name},cn=buildbot,ou=services,dc=immae,dc=eu + + SetEnvIf X-Url-Scheme https HTTPS=1 + ProxyPreserveHost On + + + + Require local + Require ldap-group cn=users,ou=${project.name},cn=buildbot,ou=services,dc=immae,dc=eu + Include /var/secrets/buildbot/${project.name}/webhook-httpd-include + + + '') myconfig.env.buildbot.projects; + + system.activationScripts = lib.attrsets.mapAttrs' (k: project: lib.attrsets.nameValuePair "buildbot-${project.name}" { + deps = [ "users" "wrappers" ]; + text = project.activationScript; + }) myconfig.env.buildbot.projects; + + secrets.keys = ( + lib.lists.flatten ( + lib.attrsets.mapAttrsToList (k: project: + lib.attrsets.mapAttrsToList (k: v: + { + permissions = "0600"; + user = "buildbot"; + group = "buildbot"; + text = v; + dest = "buildbot/${project.name}/${k}"; + } + ) project.secrets + ++ [ + { + permissions = "0600"; + user = "wwwrun"; + group = "wwwrun"; + text = lib.optionalString (lib.attrsets.hasAttr "webhookTokens" project) '' + Require expr "req('Access-Key') in { ${builtins.concatStringsSep ", " (map (x: "'${x}'") project.webhookTokens)} }" + ''; + dest = "buildbot/${project.name}/webhook-httpd-include"; + } + ] + ) myconfig.env.buildbot.projects + ) + ) ++ [ + { + permissions = "0600"; + user = "buildbot"; + group = "buildbot"; + text = myconfig.env.buildbot.ldap.password; + dest = "buildbot/ldap"; + } + { + permissions = "0600"; + user = "buildbot"; + group = "buildbot"; + text = builtins.readFile "${myconfig.privateFiles}/buildbot_ssh_key"; + dest = "buildbot/ssh_key"; + } + ]; + + systemd.services = lib.attrsets.mapAttrs' (k: project: lib.attrsets.nameValuePair "buildbot-${project.name}" { + description = "Buildbot Continuous Integration Server ${project.name}."; + after = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + path = project.packages pkgs ++ (project.pythonPackages buildbot.pythonModule pkgs); + preStart = let + master-cfg = "${buildbot_common}/${pkgs.python3.pythonForBuild.sitePackages}/buildbot_common/master.cfg"; + tac_file = pkgs.writeText "buildbot.tac" '' + import os + + from twisted.application import service + from buildbot.master import BuildMaster + + basedir = '${varDir}/${project.name}' + rotateLength = 10000000 + maxRotatedFiles = 10 + configfile = '${master-cfg}' + + # Default umask for server + umask = None + + # if this is a relocatable tac file, get the directory containing the TAC + if basedir == '.': + import os + basedir = os.path.abspath(os.path.dirname(__file__)) + + # note: this line is matched against to check that this is a buildmaster + # directory; do not edit it. + application = service.Application('buildmaster') + from twisted.python.logfile import LogFile + from twisted.python.log import ILogObserver, FileLogObserver + logfile = LogFile.fromFullPath(os.path.join(basedir, "twistd.log"), rotateLength=rotateLength, + maxRotatedFiles=maxRotatedFiles) + application.setComponent(ILogObserver, FileLogObserver(logfile).emit) + + m = BuildMaster(basedir, configfile, umask) + m.setServiceParent(application) + m.log_rotation.rotateLength = rotateLength + m.log_rotation.maxRotatedFiles = maxRotatedFiles + ''; + in '' + if [ ! -f ${varDir}/${project.name}/buildbot.tac ]; then + ${buildbot}/bin/buildbot create-master -c "${master-cfg}" "${varDir}/${project.name}" + rm -f ${varDir}/${project.name}/master.cfg.sample + rm -f ${varDir}/${project.name}/buildbot.tac + fi + ln -sf ${tac_file} ${varDir}/${project.name}/buildbot.tac + # different buildbots may be trying that simultaneously, add the || true to avoid complaining in case of race + install -Dm600 -o buildbot -g buildbot -T /var/secrets/buildbot/ssh_key ${varDir}/buildbot_key || true + buildbot_secrets=${varDir}/${project.name}/secrets + install -m 0700 -o buildbot -g buildbot -d $buildbot_secrets + install -Dm600 -o buildbot -g buildbot -T /var/secrets/buildbot/ldap $buildbot_secrets/ldap + ${builtins.concatStringsSep "\n" (lib.attrsets.mapAttrsToList + (k: v: "install -Dm600 -o buildbot -g buildbot -T /var/secrets/buildbot/${project.name}/${k} $buildbot_secrets/${k}") project.secrets + )} + ''; + environment = let + project_env = lib.attrsets.mapAttrs' (k: v: lib.attrsets.nameValuePair "BUILDBOT_${k}" v) project.environment; + buildbot_config = pkgs.python3Packages.buildPythonPackage (rec { + name = "buildbot_config-${project.name}"; + src = ./projects + "/${project.name}"; + format = "other"; + installPhase = '' + mkdir -p $out/${pkgs.python3.pythonForBuild.sitePackages} + cp -a $src $out/${pkgs.python3.pythonForBuild.sitePackages}/buildbot_config + ''; + }); + HOME = "${varDir}/${project.name}"; + PYTHONPATH = "${buildbot.pythonModule.withPackages (self: project.pythonPackages self pkgs ++ [ + pkgs.python3Packages.wokkel + pkgs.python3Packages.treq pkgs.python3Packages.ldap3 buildbot + pkgs.python3Packages.buildbot-worker + buildbot_common buildbot_config + ])}/${buildbot.pythonModule.sitePackages}${if project.pythonPathHome then ":${varDir}/${project.name}/.local/${pkgs.python3.pythonForBuild.sitePackages}" else ""}"; + in project_env // { inherit PYTHONPATH HOME; }; + + serviceConfig = { + Type = "forking"; + User = "buildbot"; + Group = "buildbot"; + RuntimeDirectory = "buildbot"; + RuntimeDirectoryPreserve = "yes"; + StateDirectory = "buildbot"; + SupplementaryGroups = "keys"; + WorkingDirectory = "${varDir}/${project.name}"; + ExecStart = "${buildbot}/bin/buildbot start"; + }; + }) myconfig.env.buildbot.projects; + }; +} diff --git a/modules/private/buildbot/projects/caldance/__init__.py b/modules/private/buildbot/projects/caldance/__init__.py new file mode 100644 index 0000000..2c0bad5 --- /dev/null +++ b/modules/private/buildbot/projects/caldance/__init__.py @@ -0,0 +1,190 @@ +from buildbot.plugins import * +from buildbot_common.build_helpers import * +import os +from buildbot.util import bytes2unicode +import json + +__all__ = [ "configure", "E" ] + +class E(): + PROJECT = "caldance" + BUILDBOT_URL = "https://git.immae.eu/buildbot/{}/".format(PROJECT) + SOCKET = "unix:/run/buildbot/{}.sock".format(PROJECT) + PB_SOCKET = "unix:address=/run/buildbot/{}_pb.sock".format(PROJECT) + RELEASE_PATH = "/var/lib/ftp/release.immae.eu/{}".format(PROJECT) + RELEASE_URL = "https://release.immae.eu/{}".format(PROJECT) + GIT_URL = "gitolite@git.immae.eu:perso/simon_descarpentries/www.cal-dance.com" + SSH_KEY_PATH = "/var/lib/buildbot/buildbot_key" + SSH_HOST_KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIFbhFTl2A2RJn5L51yxJM4XfCS2ZaiSX/jo9jFSdghF" + LDAP_HOST = "ldap.immae.eu" + LDAP_DN = "cn=buildbot,ou=services,dc=immae,dc=eu" + LDAP_ROLES_BASE = "ou=roles,ou=hosts,dc=immae,dc=eu" + XMPP_RECIPIENTS = os.environ["BUILDBOT_XMPP_RECIPIENTS"].split(" ") + + PUPPET_HOST = { + "integration": "root@caldance.immae.eu", + } + + # master.cfg + SECRETS_FILE = os.getcwd() + "/secrets" + LDAP_URL = "ldaps://ldap.immae.eu:636" + LDAP_ADMIN_USER = "cn=buildbot,ou=services,dc=immae,dc=eu" + LDAP_BASE = "dc=immae,dc=eu" + LDAP_PATTERN = "(uid=%(username)s)" + LDAP_GROUP_PATTERN = "(&(memberOf=cn=groups,ou=caldance,cn=buildbot,ou=services,dc=immae,dc=eu)(member=%(dn)s))" + TITLE_URL = "https://caldance.immae.eu" + TITLE = "Caldance" + +class CustomBase(webhooks.base): + def getChanges(self, request): + try: + content = request.content.read() + args = json.loads(bytes2unicode(content)) + except Exception as e: + raise ValueError("Error loading JSON: " + str(e)) + + args.setdefault("comments", "") + args.setdefault("repository", "") + args.setdefault("author", args.get("who", "unknown")) + + if args["category"] == "deploy_webhook": + args = { + "category": "deploy_webhook", + "comments": "", + "repository": "", + "author": "webhook", + "project": "Caldance", + "properties": { + "environment": args.get("environment", "integration"), + "build": "caldance_{}.tar.gz".format(args.get("build", "master")) + } + } + + return ([args], None) + +def deploy_hook_scheduler(project, timer=1): + return schedulers.AnyBranchScheduler( + change_filter=util.ChangeFilter(category="deploy_webhook", project=project), + name="{}_deploy".format(project), treeStableTimer=timer, builderNames=["{}_deploy".format(project)]) + +def configure(c): + c["buildbotURL"] = E.BUILDBOT_URL + c["www"]["port"] = E.SOCKET + + c["www"]["change_hook_dialects"]["base"] = { + "custom_class": CustomBase + } + c['workers'].append(worker.LocalWorker("generic-worker")) + c['workers'].append(worker.LocalWorker("deploy-worker")) + + c['schedulers'].append(hook_scheduler("Caldance", timer=1)) + c['schedulers'].append(force_scheduler("force_caldance", ["Caldance_build"])) + c['schedulers'].append(deploy_scheduler("deploy_caldance", ["Caldance_deploy"])) + c['schedulers'].append(deploy_hook_scheduler("Caldance", timer=1)) + + c['builders'].append(factory("caldance")) + + c['builders'].append(deploy_factory("caldance")) + + c['services'].append(SlackStatusPush( + name="slack_status_caldance", + builders=["Caldance_build", "Caldance_deploy"], + serverUrl=open(E.SECRETS_FILE + "/slack_webhook", "r").read().rstrip())) + c['services'].append(XMPPStatusPush( + name="xmpp_status_caldance", + builders=["Caldance_build", "Caldance_deploy"], + recipients=E.XMPP_RECIPIENTS, + password=open(E.SECRETS_FILE + "/notify_xmpp_password", "r").read().rstrip())) + +def factory(project, ignore_fails=False): + release_file = "{1}/{0}_%(kw:clean_branch)s.tar.gz" + + package = util.Interpolate("{0}_%(kw:clean_branch)s.tar.gz".format(project), clean_branch=clean_branch) + package_dest = util.Interpolate(release_file.format(project, E.RELEASE_PATH), clean_branch=clean_branch) + package_url = util.Interpolate(release_file.format(project, E.RELEASE_URL), clean_branch=clean_branch) + + factory = util.BuildFactory() + factory.addStep(steps.Git(logEnviron=False, repourl=E.GIT_URL, + sshPrivateKey=open(E.SSH_KEY_PATH).read().rstrip(), + sshHostKey=E.SSH_HOST_KEY, mode="full", method="copy")) + factory.addSteps(package_and_upload(package, package_dest, package_url)) + + return util.BuilderConfig( + name="{}_build".format(project.capitalize()), + workernames=["generic-worker"], factory=factory) + +def compute_build_infos(project): + @util.renderer + def compute(props): + import re, hashlib + build_file = props.getProperty("build") + package_dest = "{1}/{0}".format(build_file, E.RELEASE_PATH) + version = re.match(r"{0}_(.*).tar.gz".format(project), build_file).group(1) + with open(package_dest, "rb") as f: + sha = hashlib.sha256(f.read()).hexdigest() + return { + "build_version": version, + "build_hash": sha, + } + return compute + +@util.renderer +def puppet_host(props): + environment = props["environment"] if props.hasProperty("environment") else "integration" + return E.PUPPET_HOST.get(environment, "host.invalid") + +def deploy_factory(project): + package_dest = util.Interpolate("{0}/%(prop:build)s".format(E.RELEASE_PATH)) + + factory = util.BuildFactory() + factory.addStep(steps.MasterShellCommand(command=["test", "-f", package_dest])) + factory.addStep(steps.SetProperties(properties=compute_build_infos(project))) + factory.addStep(LdapPush(environment=util.Property("environment"), + project=project, build_version=util.Property("build_version"), + build_hash=util.Property("build_hash"), ldap_password=util.Secret("ldap"))) + factory.addStep(steps.MasterShellCommand(command=[ + "ssh", "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no", "-o", "CheckHostIP=no", "-i", E.SSH_KEY_PATH, puppet_host])) + return util.BuilderConfig(name="{}_deploy".format(project.capitalize()), workernames=["deploy-worker"], factory=factory) + +from twisted.internet import defer +from buildbot.process.buildstep import FAILURE +from buildbot.process.buildstep import SUCCESS +from buildbot.process.buildstep import BuildStep + +class LdapPush(BuildStep): + name = "LdapPush" + renderables = ["environment", "project", "build_version", "build_hash", "ldap_password"] + + def __init__(self, **kwargs): + self.environment = kwargs.pop("environment") + self.project = kwargs.pop("project") + self.build_version = kwargs.pop("build_version") + self.build_hash = kwargs.pop("build_hash") + self.ldap_password = kwargs.pop("ldap_password") + self.ldap_host = kwargs.pop("ldap_host", E.LDAP_HOST) + super().__init__(**kwargs) + + def run(self): + import json + from ldap3 import Reader, Writer, Server, Connection, ObjectDef + server = Server(self.ldap_host) + conn = Connection(server, + user=E.LDAP_DN, + password=self.ldap_password) + conn.bind() + obj = ObjectDef("immaePuppetClass", conn) + r = Reader(conn, obj, + "cn=caldance.{},{}".format(self.environment, E.LDAP_ROLES_BASE)) + r.search() + if len(r) > 0: + w = Writer.from_cursor(r) + for value in w[0].immaePuppetJson.values: + config = json.loads(value) + if "role::caldance::{}_version".format(self.project) in config: + config["role::caldance::{}_version".format(self.project)] = self.build_version + config["role::caldance::{}_sha256".format(self.project)] = self.build_hash + w[0].immaePuppetJson -= value + w[0].immaePuppetJson += json.dumps(config, indent=" ") + w.commit() + return defer.succeed(SUCCESS) + return defer.succeed(FAILURE) diff --git a/modules/private/buildbot/projects/cryptoportfolio/__init__.py b/modules/private/buildbot/projects/cryptoportfolio/__init__.py new file mode 100644 index 0000000..5d70f95 --- /dev/null +++ b/modules/private/buildbot/projects/cryptoportfolio/__init__.py @@ -0,0 +1,169 @@ +from buildbot.plugins import * +from buildbot_common.build_helpers import * +import os + +__all__ = [ "configure", "E" ] + +class E(): + PROJECT = "cryptoportfolio" + BUILDBOT_URL = "https://git.immae.eu/buildbot/{}/".format(PROJECT) + SOCKET = "unix:/run/buildbot/{}.sock".format(PROJECT) + PB_SOCKET = "unix:address=/run/buildbot/{}_pb.sock".format(PROJECT) + RELEASE_PATH = "/var/lib/ftp/release.immae.eu/{}".format(PROJECT) + RELEASE_URL = "https://release.immae.eu/{}".format(PROJECT) + GIT_URL = "https://git.immae.eu/perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/{0}.git" + SSH_KEY_PATH = "/var/lib/buildbot/buildbot_key" + LDAP_HOST = "ldap.immae.eu" + LDAP_DN = "cn=buildbot,ou=services,dc=immae,dc=eu" + LDAP_ROLES_BASE = "ou=roles,ou=hosts,dc=immae,dc=eu" + + PUPPET_HOST = { + "production": "root@cryptoportfolio.immae.eu", + "integration": "root@cryptoportfolio-dev.immae.eu" + } + + # master.cfg + SECRETS_FILE = os.getcwd() + "/secrets" + LDAP_URL = "ldaps://ldap.immae.eu:636" + LDAP_ADMIN_USER = "cn=buildbot,ou=services,dc=immae,dc=eu" + LDAP_BASE = "dc=immae,dc=eu" + LDAP_PATTERN = "(uid=%(username)s)" + LDAP_GROUP_PATTERN = "(&(memberOf=cn=groups,ou=cryptoportfolio,cn=buildbot,ou=services,dc=immae,dc=eu)(member=%(dn)s))" + TITLE_URL = "https://git.immae.eu" + TITLE = "Cryptoportfolio" + +# eval .. dans .zshrc_local +# mkdir -p $BUILD/go +# export GOPATH=$BUILD/go +# go get -u github.com/golang/dep/cmd/dep +# export PATH=$PATH:$BUILD/go/bin +# go get git.immae.eu/Cryptoportfolio/Front.git +# cd $BUILD/go/src/git.immae.eu/Cryptoportfolio/Front.git +# git checkout dev +# dep ensure +def configure(c): + c["buildbotURL"] = E.BUILDBOT_URL + c["www"]["port"] = E.SOCKET + + c['workers'].append(worker.LocalWorker("generic-worker")) + c['workers'].append(worker.LocalWorker("deploy-worker")) + + c['schedulers'].append(hook_scheduler("Trader")) + c['schedulers'].append(hook_scheduler("Front")) + c['schedulers'].append(force_scheduler( + "force_cryptoportfolio", ["Trader_build", "Front_build"])) + c['schedulers'].append(deploy_scheduler("deploy_cryptoportfolio", + ["Trader_deploy", "Front_deploy"])) + + c['builders'].append(factory("trader")) + c['builders'].append(factory("front", ignore_fails=True)) + + c['builders'].append(deploy_factory("trader")) + c['builders'].append(deploy_factory("front")) + + c['services'].append(SlackStatusPush( + name="slack_status_cryptoportfolio", + builders=["Front_build", "Trader_build", "Front_deploy", "Trader_deploy"], + serverUrl=open(E.SECRETS_FILE + "/slack_webhook", "r").read().rstrip())) + +def factory(project, ignore_fails=False): + release_file = "{1}/{0}/{0}_%(kw:clean_branch)s.tar.gz" + + url = E.GIT_URL.format(project.capitalize()) + + package = util.Interpolate("{0}_%(kw:clean_branch)s.tar.gz".format(project), clean_branch=clean_branch) + package_dest = util.Interpolate(release_file.format(project, E.RELEASE_PATH), clean_branch=clean_branch) + package_url = util.Interpolate(release_file.format(project, E.RELEASE_URL), clean_branch=clean_branch) + + factory = util.BuildFactory() + factory.addStep(steps.Git(logEnviron=False, repourl=url, + mode="full", method="copy")) + factory.addStep(steps.ShellCommand(name="make install", + logEnviron=False, haltOnFailure=(not ignore_fails), + warnOnFailure=ignore_fails, flunkOnFailure=(not ignore_fails), + command=["make", "install"])) + factory.addStep(steps.ShellCommand(name="make test", + logEnviron=False, haltOnFailure=(not ignore_fails), + warnOnFailure=ignore_fails, flunkOnFailure=(not ignore_fails), + command=["make", "test"])) + factory.addSteps(package_and_upload(package, package_dest, package_url)) + + return util.BuilderConfig( + name="{}_build".format(project.capitalize()), + workernames=["generic-worker"], factory=factory) + +def compute_build_infos(project): + @util.renderer + def compute(props): + import re, hashlib + build_file = props.getProperty("build") + package_dest = "{2}/{0}/{1}".format(project, build_file, E.RELEASE_PATH) + version = re.match(r"{0}_(.*).tar.gz".format(project), build_file).group(1) + with open(package_dest, "rb") as f: + sha = hashlib.sha256(f.read()).hexdigest() + return { + "build_version": version, + "build_hash": sha, + } + return compute + +@util.renderer +def puppet_host(props): + environment = props["environment"] if props.hasProperty("environment") else "integration" + return E.PUPPET_HOST.get(environment, "host.invalid") + +def deploy_factory(project): + package_dest = util.Interpolate("{1}/{0}/%(prop:build)s".format(project, E.RELEASE_PATH)) + + factory = util.BuildFactory() + factory.addStep(steps.MasterShellCommand(command=["test", "-f", package_dest])) + factory.addStep(steps.SetProperties(properties=compute_build_infos(project))) + factory.addStep(LdapPush(environment=util.Property("environment"), + project=project, build_version=util.Property("build_version"), + build_hash=util.Property("build_hash"), ldap_password=util.Secret("ldap"))) + factory.addStep(steps.MasterShellCommand(command=[ + "ssh", "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no", "-o", "CheckHostIP=no", "-i", E.SSH_KEY_PATH, puppet_host])) + return util.BuilderConfig(name="{}_deploy".format(project.capitalize()), workernames=["deploy-worker"], factory=factory) + +from twisted.internet import defer +from buildbot.process.buildstep import FAILURE +from buildbot.process.buildstep import SUCCESS +from buildbot.process.buildstep import BuildStep + +class LdapPush(BuildStep): + name = "LdapPush" + renderables = ["environment", "project", "build_version", "build_hash", "ldap_password"] + + def __init__(self, **kwargs): + self.environment = kwargs.pop("environment") + self.project = kwargs.pop("project") + self.build_version = kwargs.pop("build_version") + self.build_hash = kwargs.pop("build_hash") + self.ldap_password = kwargs.pop("ldap_password") + self.ldap_host = kwargs.pop("ldap_host", E.LDAP_HOST) + super().__init__(**kwargs) + + def run(self): + import json + from ldap3 import Reader, Writer, Server, Connection, ObjectDef + server = Server(self.ldap_host) + conn = Connection(server, + user=E.LDAP_DN, + password=self.ldap_password) + conn.bind() + obj = ObjectDef("immaePuppetClass", conn) + r = Reader(conn, obj, + "cn=cryptoportfolio.{},{}".format(self.environment, E.LDAP_ROLES_BASE)) + r.search() + if len(r) > 0: + w = Writer.from_cursor(r) + for value in w[0].immaePuppetJson.values: + config = json.loads(value) + if "role::cryptoportfolio::{}_version".format(self.project) in config: + config["role::cryptoportfolio::{}_version".format(self.project)] = self.build_version + config["role::cryptoportfolio::{}_sha256".format(self.project)] = self.build_hash + w[0].immaePuppetJson -= value + w[0].immaePuppetJson += json.dumps(config, indent=" ") + w.commit() + return defer.succeed(SUCCESS) + return defer.succeed(FAILURE) diff --git a/modules/private/buildbot/projects/test/__init__.py b/modules/private/buildbot/projects/test/__init__.py new file mode 100644 index 0000000..e6b8d51 --- /dev/null +++ b/modules/private/buildbot/projects/test/__init__.py @@ -0,0 +1,188 @@ +from buildbot.plugins import * +from buildbot_common.build_helpers import * +import os +from buildbot.util import bytes2unicode +import json + +__all__ = [ "configure", "E" ] + +class E(): + PROJECT = "test" + BUILDBOT_URL = "https://git.immae.eu/buildbot/{}/".format(PROJECT) + SOCKET = "unix:/run/buildbot/{}.sock".format(PROJECT) + PB_SOCKET = "unix:address=/run/buildbot/{}_pb.sock".format(PROJECT) + RELEASE_PATH = "/var/lib/ftp/release.immae.eu/{}".format(PROJECT) + RELEASE_URL = "https://release.immae.eu/{}".format(PROJECT) + GIT_URL = "https://git.immae.eu/perso/Immae/TestProject.git" + SSH_KEY_PATH = "/var/lib/buildbot/buildbot_key" + PUPPET_HOST = "root@backup-1.v.immae.eu" + LDAP_HOST = "ldap.immae.eu" + LDAP_DN = "cn=buildbot,ou=services,dc=immae,dc=eu" + LDAP_ROLES_BASE = "ou=roles,ou=hosts,dc=immae,dc=eu" + XMPP_RECIPIENTS = os.environ["BUILDBOT_XMPP_RECIPIENTS"].split(" ") + + # master.cfg + SECRETS_FILE = os.getcwd() + "/secrets" + LDAP_URL = "ldaps://ldap.immae.eu:636" + LDAP_ADMIN_USER = "cn=buildbot,ou=services,dc=immae,dc=eu" + LDAP_BASE = "dc=immae,dc=eu" + LDAP_PATTERN = "(uid=%(username)s)" + LDAP_GROUP_PATTERN = "(&(memberOf=cn=groups,ou=test,cn=buildbot,ou=services,dc=immae,dc=eu)(member=%(dn)s))" + TITLE_URL = "https://git.immae.eu/?p=perso/Immae/TestProject.git;a=summary" + TITLE = "Test project" + +class CustomBase(webhooks.base): + def getChanges(self, request): + try: + content = request.content.read() + args = json.loads(bytes2unicode(content)) + except Exception as e: + raise ValueError("Error loading JSON: " + str(e)) + + args.setdefault("comments", "") + args.setdefault("repository", "") + args.setdefault("author", args.get("who", "unknown")) + + if args["category"] == "deploy_webhook": + args = { + "category": "deploy_webhook", + "comments": "", + "repository": "", + "author": "unknown", + "project": "TestProject", + "properties": { + "environment": args.get("environment", "integration"), + "build": "test_{}.tar.gz".format(args.get("branch", "master")) + } + } + + return ([args], None) + +def deploy_hook_scheduler(project, timer=1): + return schedulers.AnyBranchScheduler( + change_filter=util.ChangeFilter(category="deploy_webhook", project=project), + name="{}_deploy".format(project), treeStableTimer=timer, builderNames=["{}_deploy".format(project)]) + +def configure(c): + c["buildbotURL"] = E.BUILDBOT_URL + c["www"]["port"] = E.SOCKET + + c["www"]["change_hook_dialects"]["base"] = { + "custom_class": CustomBase + } + c['workers'].append(worker.LocalWorker("generic-worker-test")) + c['workers'].append(worker.LocalWorker("deploy-worker-test")) + + c['schedulers'].append(hook_scheduler("TestProject", timer=1)) + c['schedulers'].append(force_scheduler("force_test", ["TestProject_build"])) + c['schedulers'].append(deploy_scheduler("deploy_test", ["TestProject_deploy"])) + c['schedulers'].append(deploy_hook_scheduler("TestProject", timer=1)) + + c['builders'].append(factory()) + c['builders'].append(deploy_factory()) + + c['services'].append(SlackStatusPush( + name="slack_status_test_project", + builders=["TestProject_build", "TestProject_deploy"], + serverUrl=open(E.SECRETS_FILE + "/slack_webhook", "r").read().rstrip())) + c['services'].append(XMPPStatusPush( + name="xmpp_status_test_project", + builders=["TestProject_build", "TestProject_deploy"], + recipients=E.XMPP_RECIPIENTS, + password=open(E.SECRETS_FILE + "/notify_xmpp_password", "r").read().rstrip())) + +def factory(): + package = util.Interpolate("test_%(kw:clean_branch)s.tar.gz", clean_branch=clean_branch) + package_dest = util.Interpolate("{}/test_%(kw:clean_branch)s.tar.gz".format(E.RELEASE_PATH), clean_branch=clean_branch) + package_url = util.Interpolate("{}/test_%(kw:clean_branch)s.tar.gz".format(E.RELEASE_URL), clean_branch=clean_branch) + + factory = util.BuildFactory() + factory.addStep(steps.Git(logEnviron=False, + repourl=E.GIT_URL, mode="full", method="copy")) + factory.addStep(steps.ShellCommand(name="env", + logEnviron=False, command=["env"])) + factory.addStep(steps.ShellCommand(name="pwd", + logEnviron=False, command=["pwd"])) + factory.addStep(steps.ShellCommand(name="true", + logEnviron=False, command=["true"])) + factory.addStep(steps.ShellCommand(name="echo", + logEnviron=False, command=["echo", package])) + factory.addSteps(package_and_upload(package, package_dest, package_url)) + + return util.BuilderConfig(name="TestProject_build", workernames=["generic-worker-test"], factory=factory) + + +def compute_build_infos(): + @util.renderer + def compute(props): + import re, hashlib + build_file = props.getProperty("build") + package_dest = "{}/{}".format(E.RELEASE_PATH, build_file) + version = re.match(r"{0}_(.*).tar.gz".format("test"), build_file).group(1) + with open(package_dest, "rb") as f: + sha = hashlib.sha256(f.read()).hexdigest() + return { + "build_version": version, + "build_hash": sha, + } + return compute + +@util.renderer +def puppet_host(props): + return E.PUPPET_HOST + +def deploy_factory(): + package_dest = util.Interpolate("{}/%(prop:build)s".format(E.RELEASE_PATH)) + + factory = util.BuildFactory() + factory.addStep(steps.MasterShellCommand(command=["test", "-f", package_dest])) + factory.addStep(steps.SetProperties(properties=compute_build_infos())) + factory.addStep(LdapPush(environment=util.Property("environment"), + build_version=util.Property("build_version"), + build_hash=util.Property("build_hash"), + ldap_password=util.Secret("ldap"))) + factory.addStep(steps.MasterShellCommand(command=[ + "ssh", "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no", "-o", "CheckHostIP=no", "-i", E.SSH_KEY_PATH, puppet_host])) + return util.BuilderConfig(name="TestProject_deploy", workernames=["deploy-worker-test"], factory=factory) + +from twisted.internet import defer +from buildbot.process.buildstep import FAILURE +from buildbot.process.buildstep import SUCCESS +from buildbot.process.buildstep import BuildStep + +class LdapPush(BuildStep): + name = "LdapPush" + renderables = ["environment", "build_version", "build_hash", "ldap_password"] + + def __init__(self, **kwargs): + self.environment = kwargs.pop("environment") + self.build_version = kwargs.pop("build_version") + self.build_hash = kwargs.pop("build_hash") + self.ldap_password = kwargs.pop("ldap_password") + self.ldap_host = kwargs.pop("ldap_host", E.LDAP_HOST) + super().__init__(**kwargs) + + def run(self): + import json + from ldap3 import Reader, Writer, Server, Connection, ObjectDef + server = Server(self.ldap_host) + conn = Connection(server, + user=E.LDAP_DN, + password=self.ldap_password) + conn.bind() + obj = ObjectDef("immaePuppetClass", conn) + r = Reader(conn, obj, + "cn=test.{},{}".format(self.environment, E.LDAP_ROLES_BASE)) + r.search() + if len(r) > 0: + w = Writer.from_cursor(r) + for value in w[0].immaePuppetJson.values: + config = json.loads(value) + if "test_version" in config: + config["test_version"] = self.build_version + config["test_sha256"] = self.build_hash + w[0].immaePuppetJson -= value + w[0].immaePuppetJson += json.dumps(config, indent=" ") + w.commit() + return defer.succeed(SUCCESS) + return defer.succeed(FAILURE) diff --git a/modules/private/certificates.nix b/modules/private/certificates.nix new file mode 100644 index 0000000..43f6a23 --- /dev/null +++ b/modules/private/certificates.nix @@ -0,0 +1,52 @@ +{ lib, pkgs, config, ... }: +{ + options.services.myCertificates = { + certConfig = lib.mkOption { + default = { + webroot = "${config.security.acme.directory}/acme-challenge"; + email = "ismael@bouya.org"; + postRun = '' + systemctl reload httpdTools.service httpdInte.service httpdProd.service + ''; + plugins = [ "cert.pem" "chain.pem" "fullchain.pem" "full.pem" "key.pem" "account_key.json" ]; + }; + description = "Default configuration for certificates"; + }; + }; + + config = { + services.websitesCerts = config.services.myCertificates.certConfig; + myServices.databasesCerts = config.services.myCertificates.certConfig; + myServices.ircCerts = config.services.myCertificates.certConfig; + + security.acme.preliminarySelfsigned = true; + + security.acme.certs = { + "eldiron" = config.services.myCertificates.certConfig // { + domain = "eldiron.immae.eu"; + }; + }; + + systemd.services = lib.attrsets.mapAttrs' (k: v: + lib.attrsets.nameValuePair "acme-selfsigned-${k}" (lib.mkBefore { script = + (lib.optionalString (builtins.elem "cert.pem" v.plugins) '' + cp $workdir/server.crt ${config.security.acme.directory}/${k}/cert.pem + chown '${v.user}:${v.group}' ${config.security.acme.directory}/${k}/cert.pem + chmod ${if v.allowKeysForGroup then "750" else "700"} ${config.security.acme.directory}/${k}/cert.pem + '') + + (lib.optionalString (builtins.elem "chain.pem" v.plugins) '' + cp $workdir/ca.crt ${config.security.acme.directory}/${k}/chain.pem + chown '${v.user}:${v.group}' ${config.security.acme.directory}/${k}/chain.pem + chmod ${if v.allowKeysForGroup then "750" else "700"} ${config.security.acme.directory}/${k}/chain.pem + '') + ; }) + ) config.security.acme.certs // { + httpdProd.after = [ "acme-selfsigned-certificates.target" ]; + httpdProd.wants = [ "acme-selfsigned-certificates.target" ]; + httpdTools.after = [ "acme-selfsigned-certificates.target" ]; + httpdTools.wants = [ "acme-selfsigned-certificates.target" ]; + httpdInte.after = [ "acme-selfsigned-certificates.target" ]; + httpdInte.wants = [ "acme-selfsigned-certificates.target" ]; + }; + }; +} diff --git a/modules/private/default.nix b/modules/private/default.nix index 242eeb9..894efb7 100644 --- a/modules/private/default.nix +++ b/modules/private/default.nix @@ -47,7 +47,19 @@ set = { peertubeTool = ./websites/tools/peertube; toolsTool = ./websites/tools/tools; + buildbot = ./buildbot; + certificates = ./certificates.nix; + gitolite = ./gitolite; irc = ./irc.nix; + pub = ./pub; + tasks = ./tasks; + dns = ./dns.nix; + ftp = ./ftp.nix; + mail = ./mail.nix; + mpd = ./mpd.nix; + ssh = ./ssh; + + system = ./system.nix; }; in builtins.listToAttrs (map (attr: { name = "priv${attr}"; value = set.${attr}; }) (builtins.attrNames set)) diff --git a/modules/private/dns.nix b/modules/private/dns.nix new file mode 100644 index 0000000..ced8d9b --- /dev/null +++ b/modules/private/dns.nix @@ -0,0 +1,132 @@ +{ lib, pkgs, config, myconfig, ... }: +{ + config = let + cfg = config.services.bind; + configFile = pkgs.writeText "named.conf" '' + include "/etc/bind/rndc.key"; + controls { + inet 127.0.0.1 allow {localhost;} keys {"rndc-key";}; + }; + + acl cachenetworks { ${lib.concatMapStrings (entry: " ${entry}; ") cfg.cacheNetworks} }; + acl badnetworks { ${lib.concatMapStrings (entry: " ${entry}; ") cfg.blockedNetworks} }; + + options { + listen-on { ${lib.concatMapStrings (entry: " ${entry}; ") cfg.listenOn} }; + listen-on-v6 { ${lib.concatMapStrings (entry: " ${entry}; ") cfg.listenOnIpv6} }; + allow-query { cachenetworks; }; + blackhole { badnetworks; }; + forward first; + forwarders { ${lib.concatMapStrings (entry: " ${entry}; ") cfg.forwarders} }; + directory "/var/run/named"; + pid-file "/var/run/named/named.pid"; + ${cfg.extraOptions} + }; + + ${cfg.extraConfig} + + ${ lib.concatMapStrings + ({ name, file, master ? true, extra ? "", slaves ? [], masters ? [] }: + '' + zone "${name}" { + type ${if master then "master" else "slave"}; + file "${file}"; + ${ if lib.lists.length slaves > 0 then + '' + allow-transfer { + ${lib.concatMapStrings (ip: "${ip};\n") slaves} + }; + '' else ""} + ${ if lib.lists.length masters > 0 then + '' + masters { + ${lib.concatMapStrings (ip: "${ip};\n") masters} + }; + '' else ""} + allow-query { any; }; + ${extra} + }; + '') + cfg.zones } + ''; + in + { + networking.firewall.allowedUDPPorts = [ 53 ]; + networking.firewall.allowedTCPPorts = [ 53 ]; + services.bind = { + enable = true; + cacheNetworks = ["any"]; + configFile = configFile; + extraOptions = '' + allow-recursion { 127.0.0.1; }; + allow-transfer { none; }; + + notify-source ${myconfig.env.servers.eldiron.ips.main.ip4}; + notify-source-v6 ${lib.head myconfig.env.servers.eldiron.ips.main.ip6}; + version none; + hostname none; + server-id none; + ''; + zones = with myconfig.env.dns; + assert (builtins.substring ((builtins.stringLength soa.email)-1) 1 soa.email) != "."; + assert (builtins.substring ((builtins.stringLength soa.primary)-1) 1 soa.primary) != "."; + (map (conf: { + name = conf.name; + master = false; + file = "/var/run/named/${conf.name}.zone"; + masters = if lib.attrsets.hasAttr "masters" conf + then lib.lists.flatten (map (n: lib.attrsets.attrValues ns.${n}) conf.masters) + else []; + }) slaveZones) + ++ (map (conf: { + name = conf.name; + master = true; + extra = if lib.attrsets.hasAttr "extra" conf then conf.extra else ""; + slaves = if lib.attrsets.hasAttr "slaves" conf + then lib.lists.flatten (map (n: lib.attrsets.attrValues ns.${n}) conf.slaves) + else []; + file = pkgs.writeText "${conf.name}.zone" '' + $TTL 10800 + @ IN SOA ${soa.primary}. ${builtins.replaceStrings ["@"] ["."] soa.email}. ${soa.serial} ${soa.refresh} ${soa.retry} ${soa.expire} ${soa.ttl} + + ${lib.concatStringsSep "\n" (map (x: "@ IN NS ${x}.") (lib.concatMap (n: lib.attrsets.mapAttrsToList (k: v: k) ns.${n}) conf.ns))} + + ${conf.entries} + + ${if lib.attrsets.hasAttr "withEmail" conf && lib.lists.length conf.withEmail > 0 then '' + mail IN A ${myconfig.env.servers.immaeEu.ips.main.ip4} + mx-1 IN A ${myconfig.env.servers.eldiron.ips.main.ip4} + ${builtins.concatStringsSep "\n" (map (i: "mail IN AAAA ${i}") myconfig.env.servers.immaeEu.ips.main.ip6)} + ${builtins.concatStringsSep "\n" (map (i: "mx-1 IN AAAA ${i}") myconfig.env.servers.eldiron.ips.main.ip6)} + ${lib.concatStringsSep "\n\n" (map (e: + let + n = if e.domain == "" then "@" else "${e.domain} "; + suffix = if e.domain == "" then "" else ".${e.domain}"; + in + '' + ; ------------------ mail: ${n} --------------------------- + ${if e.receive then "${n} IN MX 10 mail.${conf.name}." else ""} + ;${if e.receive then "${n} IN MX 50 mx-1.${conf.name}." else ""} + + ; Mail sender authentications + ${n} IN TXT "v=spf1 mx ~all" + _dmarc${suffix} IN TXT "v=DMARC1; p=none; adkim=r; aspf=r; fo=1; rua=mailto:postmaster+rua@immae.eu; ruf=mailto:postmaster+ruf@immae.eu;" + ${if e.send then '' + immae_eu._domainkey${suffix} IN TXT ( "v=DKIM1; k=rsa; s=email; " + "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzl3vLd8W5YAuumC5+ZT9OV7/14Pmh5JYtwyqKI3cfe9NnAqInt3xO4bZ7oqIxRKWN4SD39vm7O/QOvFdBt00ENOOzdP90s5gKw6eIP/4+vPTh0IWltAsmu9B2agzdtWUE7t2xFKIzEn8l9niRE2QYbVaqZv4sub98vY55fIgFoHtjkmNC7325S8fjDJGp6OPbyhAs6Xl5/adjF" + "0ko4Y2p6RaxLQfjlS0bxmK4Qg6C14pIXHtzVeqOuWrwApqt5+AULSn97iUtqV/IJlEEjC6DUR44t3C/G0G/k46iFclCqRRi0hdPrOHCtZDbtMubnTN9eaUiNpkXh1WnCflHwtjQwIDAQAB" ) + '' else ""} + '') conf.withEmail)} + '' + (if conf.name == "immae.eu" then '' + ; ----------------- Accept DMARC reports ------------------- + ${lib.concatStringsSep "\n" ( + lib.flatten ( + map (z: map (e: "${e.domain}${if builtins.stringLength e.domain > 0 then "." else ""}${z.name}._report._dmarc IN TXT \"v=DMARC1;\"") (z.withEmail or [])) masterZones + ) + )} + '' else "") else ""} + ''; + }) masterZones); + }; + }; +} diff --git a/modules/private/ftp.nix b/modules/private/ftp.nix new file mode 100644 index 0000000..842d2d6 --- /dev/null +++ b/modules/private/ftp.nix @@ -0,0 +1,118 @@ +{ lib, pkgs, config, myconfig, ... }: +{ + options = { + services.pure-ftpd.enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether to enable pure-ftpd. + ''; + }; + }; + + config = lib.mkIf config.services.pure-ftpd.enable { + security.acme.certs."ftp" = config.services.myCertificates.certConfig // { + domain = "eldiron.immae.eu"; + postRun = '' + systemctl restart pure-ftpd.service + ''; + extraDomains = { "ftp.immae.eu" = null; }; + }; + + networking = { + firewall = { + allowedTCPPorts = [ 21 ]; + allowedTCPPortRanges = [ { from = 40000; to = 50000; } ]; + }; + }; + + users.users = [ + { + name = "ftp"; + uid = config.ids.uids.ftp; # 8 + group = "ftp"; + description = "Anonymous FTP user"; + home = "/homeless-shelter"; + extraGroups = [ "keys" ]; + } + ]; + + users.groups.ftp.gid = config.ids.gids.ftp; + + system.activationScripts.pure-ftpd = '' + install -m 0755 -o ftp -g ftp -d /var/lib/ftp + ''; + + secrets.keys = [{ + dest = "pure-ftpd-ldap"; + permissions = "0400"; + user = "ftp"; + group = "ftp"; + text = '' + LDAPServer ${myconfig.env.ftp.ldap.host} + LDAPPort 389 + LDAPUseTLS True + LDAPBaseDN ${myconfig.env.ftp.ldap.base} + LDAPBindDN ${myconfig.env.ftp.ldap.dn} + LDAPBindPW ${myconfig.env.ftp.ldap.password} + LDAPDefaultUID 500 + LDAPForceDefaultUID False + LDAPDefaultGID 100 + LDAPForceDefaultGID False + LDAPFilter ${myconfig.env.ftp.ldap.filter} + + LDAPAuthMethod BIND + + # Pas de possibilite de donner l'Uid/Gid ! + # Compile dans pure-ftpd directement avec immaeFtpUid / immaeFtpGid + LDAPHomeDir immaeFtpDirectory + ''; + }]; + + systemd.services.pure-ftpd = let + configFile = pkgs.writeText "pure-ftpd.conf" '' + PassivePortRange 40000 50000 + ChrootEveryone yes + CreateHomeDir yes + BrokenClientsCompatibility yes + MaxClientsNumber 50 + Daemonize yes + MaxClientsPerIP 8 + VerboseLog no + DisplayDotFiles yes + AnonymousOnly no + NoAnonymous no + SyslogFacility ftp + DontResolve yes + MaxIdleTime 15 + LDAPConfigFile /var/secrets/pure-ftpd-ldap + LimitRecursion 10000 8 + AnonymousCanCreateDirs no + MaxLoad 4 + AntiWarez yes + Umask 133:022 + # ftp + MinUID 8 + AllowUserFXP no + AllowAnonymousFXP no + ProhibitDotFilesWrite no + ProhibitDotFilesRead no + AutoRename no + AnonymousCantUpload no + MaxDiskUsage 99 + CustomerProof yes + TLS 1 + CertFile ${config.security.acme.directory}/ftp/full.pem + ''; + in { + description = "Pure-FTPd server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + serviceConfig.ExecStart = "${pkgs.pure-ftpd}/bin/pure-ftpd ${configFile}"; + serviceConfig.Type = "forking"; + serviceConfig.PIDFile = "/run/pure-ftpd.pid"; + }; + }; + +} diff --git a/modules/private/gitolite/default.nix b/modules/private/gitolite/default.nix new file mode 100644 index 0000000..b9914a1 --- /dev/null +++ b/modules/private/gitolite/default.nix @@ -0,0 +1,63 @@ +{ lib, pkgs, config, myconfig, ... }: +let + cfg = config.myServices.gitolite; +in { + options.myServices.gitolite = { + enable = lib.mkEnableOption "my gitolite service"; + gitoliteDir = lib.mkOption { + type = lib.types.string; + default = "/var/lib/gitolite"; + }; + }; + + config = lib.mkIf cfg.enable { + networking.firewall.allowedTCPPorts = [ 9418 ]; + + services.gitDaemon = { + enable = true; + user = "gitolite"; + group = "gitolite"; + basePath = "${cfg.gitoliteDir}/repositories"; + }; + + system.activationScripts.gitolite = let + gitolite_ldap_groups = pkgs.mylibs.wrap { + name = "gitolite_ldap_groups.sh"; + file = ./gitolite_ldap_groups.sh; + vars = { + LDAP_PASS = myconfig.env.tools.gitolite.ldap.password; + }; + paths = [ pkgs.openldap pkgs.stdenv.shellPackage pkgs.gnugrep pkgs.coreutils ]; + }; + in { + deps = [ "users" ]; + text = '' + if [ -d ${cfg.gitoliteDir} ]; then + ln -sf ${gitolite_ldap_groups} ${cfg.gitoliteDir}/gitolite_ldap_groups.sh + chmod g+rx ${cfg.gitoliteDir} + fi + if [ -f ${cfg.gitoliteDir}/projects.list ]; then + chmod g+r ${cfg.gitoliteDir}/projects.list + fi + ''; + }; + + users.users.wwwrun.extraGroups = [ "gitolite" ]; + + users.users.gitolite.packages = let + python-packages = python-packages: with python-packages; [ + simplejson + urllib3 + sleekxmpp + ]; + in + [ + (pkgs.python3.withPackages python-packages) + ]; + # Installation: https://git.immae.eu/mantisbt/view.php?id=93 + services.gitolite = { + enable = true; + adminPubkey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDXqRbiHw7QoHADNIEuo4nUT9fSOIEBMdJZH0bkQAxXyJFyCM1IMz0pxsHV0wu9tdkkr36bPEUj2aV5bkYLBN6nxcV2Y49X8bjOSCPfx3n6Own1h+NeZVBj4ZByrFmqCbTxUJIZ2bZKcWOFncML39VmWdsVhNjg0X4NBBehqXRIKr2gt3E/ESAxTYJFm0BnU0baciw9cN0bsRGqvFgf5h2P48CIAfwhVcGmPQnnAwabnosYQzRWxR0OygH5Kd8mePh6FheIRIigfXsDO8f/jdxwut8buvNIf3m5EBr3tUbTsvM+eV3M5vKGt7sk8T64DVtepTSdOOWtp+47ktsnHOMh immae@immae.eu"; + }; + }; +} diff --git a/modules/private/gitolite/gitolite_ldap_groups.sh b/modules/private/gitolite/gitolite_ldap_groups.sh new file mode 100755 index 0000000..7db0da4 --- /dev/null +++ b/modules/private/gitolite/gitolite_ldap_groups.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +uid_param="$1" +ldap_host="ldap.immae.eu" +ldap_binddn="cn=gitolite,ou=services,dc=immae,dc=eu" +ldap_bindpw="$LDAP_PASS" +ldap_searchbase="dc=immae,dc=eu" +ldap_scope="subtree" + +ldap_options="-h ${ldap_host} -ZZ -x -D ${ldap_binddn} -w ${ldap_bindpw} -b ${ldap_searchbase} -s ${ldap_scope}" + +ldap_filter="(&(memberOf=cn=groups,cn=gitolite,ou=services,dc=immae,dc=eu)(|(member=uid=${uid_param},ou=users,dc=immae,dc=eu)(member=uid=${uid_param},ou=group_users,dc=immae,dc=eu)))" +ldap_result=$(ldapsearch ${ldap_options} -LLL "${ldap_filter}" cn | grep 'cn:' | cut -d' ' -f2) + +echo "$ldap_result" diff --git a/modules/private/mail.nix b/modules/private/mail.nix new file mode 100644 index 0000000..611c8b4 --- /dev/null +++ b/modules/private/mail.nix @@ -0,0 +1,13 @@ +{ lib, pkgs, config, myconfig, ... }: +{ + config.users.users.nullmailer.uid = config.ids.uids.nullmailer; + config.users.groups.nullmailer.gid = config.ids.gids.nullmailer; + + config.services.nullmailer = { + enable = true; + config = { + me = myconfig.env.mail.host; + remotes = "${myconfig.env.mail.relay} smtp"; + }; + }; +} diff --git a/modules/private/mpd.nix b/modules/private/mpd.nix new file mode 100644 index 0000000..9903bdf --- /dev/null +++ b/modules/private/mpd.nix @@ -0,0 +1,56 @@ +{ lib, pkgs, config, myconfig, ... }: +{ + config = { + secrets.keys = [ + { + dest = "mpd"; + permissions = "0400"; + text = myconfig.env.mpd.password; + } + { + dest = "mpd-config"; + permissions = "0400"; + user = "mpd"; + group = "mpd"; + text = '' + password "${myconfig.env.mpd.password}@read,add,control,admin" + ''; + } + ]; + networking.firewall.allowedTCPPorts = [ 6600 ]; + users.users.mpd.extraGroups = [ "wwwrun" "keys" ]; + systemd.services.mpd.serviceConfig.RuntimeDirectory = "mpd"; + services.mpd = { + enable = true; + network.listenAddress = "any"; + musicDirectory = myconfig.env.mpd.folder; + extraConfig = '' + include "/var/secrets/mpd-config" + audio_output { + type "null" + name "No Output" + mixer_type "none" + } + audio_output { + type "httpd" + name "OGG" + encoder "vorbis" + bind_to_address "/run/mpd/ogg.sock" + quality "5.0" + format "44100:16:1" + } + audio_output { + type "httpd" + name "MP3" + encoder "lame" + bind_to_address "/run/mpd/mp3.sock" + quality "5.0" + format "44100:16:1" + } + + + ''; + }; + }; +} + diff --git a/modules/private/pub/default.nix b/modules/private/pub/default.nix new file mode 100644 index 0000000..c31c8eb --- /dev/null +++ b/modules/private/pub/default.nix @@ -0,0 +1,52 @@ +{ lib, pkgs, config, myconfig, ... }: +{ + options = { + myServices.pub.enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether to enable pub user. + ''; + }; + }; + + config = lib.mkIf config.myServices.pub.enable { + users.users.pub = let + restrict = pkgs.runCommand "restrict" { + file = ./restrict; + buildInputs = [ pkgs.makeWrapper ]; + } '' + mkdir -p $out/bin + cp $file $out/bin/restrict + chmod a+x $out/bin/restrict + patchShebangs $out/bin/restrict + wrapProgram $out/bin/restrict \ + --prefix PATH : ${lib.makeBinPath [ pkgs.bubblewrap pkgs.rrsync ]} \ + --set TMUX_RESTRICT ${./tmux.restrict.conf} + ''; + purple-hangouts = pkgs.purple-hangouts.overrideAttrs(old: { + installPhase = '' + install -Dm755 -t $out/lib/purple-2/ libhangouts.so + for size in 16 22 24 48; do + install -TDm644 hangouts$size.png $out/share/pixmaps/pidgin/protocols/$size/hangouts.png + done + ''; + }); + in { + createHome = true; + description = "Restricted shell user"; + home = "/var/lib/pub"; + uid = myconfig.env.users.pub.uid; + useDefaultShell = true; + packages = [ + restrict + pkgs.tmux + (pkgs.pidgin.override { plugins = [ + pkgs.purple-plugin-pack purple-hangouts + pkgs.purple-discord pkgs.purple-facebook + pkgs.telegram-purple + ]; }) + ]; + }; + }; +} diff --git a/modules/private/pub/restrict b/modules/private/pub/restrict new file mode 100644 index 0000000..b2f3be3 --- /dev/null +++ b/modules/private/pub/restrict @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +user="$1" +rootuser="$HOME/$user/" +mkdir -p $rootuser + +orig="$SSH_ORIGINAL_COMMAND" +if [ -z "$orig" ]; then + orig="/bin/bash -l" +fi +if [ "${orig:0:7}" = "command" ]; then + orig="${orig:8}" +fi + +case "$orig" in +rsync*) + rrsync $HOME/$user/ + ;; +*) + nix_store_paths() { + nix-store -q -R \ + /run/current-system/sw \ + /etc/profiles/per-user/pub \ + /etc/ssl/certs/ca-bundle.crt \ + | while read i; do + printf '%s--ro-bind\0'$i'\0'$i'\0' '' + done + } + + set -euo pipefail + (exec -c bwrap --ro-bind /usr /usr \ + --args 10 \ + --dir /tmp \ + --dir /var \ + --symlink ../tmp var/tmp \ + --proc /proc \ + --dev /dev \ + --ro-bind /etc/resolv.conf /etc/resolv.conf \ + --ro-bind /etc/zoneinfo /etc/zoneinfo \ + --ro-bind /etc/ssl /etc/ssl \ + --ro-bind /etc/static/ssl/certs /etc/static/ssl/certs \ + --ro-bind /run/current-system/sw/lib/locale/locale-archive /etc/locale-archive \ + --ro-bind /run/current-system/sw/bin /bin \ + --ro-bind /etc/profiles/per-user/pub/bin /bin-pub \ + --bind /var/lib/pub/$user /var/lib/pub \ + --dir /var/lib/commons \ + --ro-bind $TMUX_RESTRICT /var/lib/commons/tmux.restrict.conf \ + --chdir /var/lib/pub \ + --unshare-all \ + --share-net \ + --dir /run/user/$(id -u) \ + --setenv TERM "$TERM" \ + --setenv LOCALE_ARCHIVE "/etc/locale-archive" \ + --setenv XDG_RUNTIME_DIR "/run/user/`id -u`" \ + --setenv PS1 "$user@pub $ " \ + --setenv PATH "/bin:/bin-pub" \ + --setenv HOME "/var/lib/pub" \ + --file 11 /etc/passwd \ + --file 12 /etc/group \ + -- $orig) \ + 10< <(nix_store_paths) \ + 11< <(getent passwd $UID 65534) \ + 12< <(getent group $(id -g) 65534) + ;; +esac diff --git a/modules/private/pub/tmux.restrict.conf b/modules/private/pub/tmux.restrict.conf new file mode 100644 index 0000000..5aefd1c --- /dev/null +++ b/modules/private/pub/tmux.restrict.conf @@ -0,0 +1,43 @@ +# Pour les nostalgiques de screen +# comme les raccourcis ne sont pas les mêmes, j'évite +set -g prefix C-a +unbind-key C-b + +unbind-key -a +bind-key -n C-h list-keys +bind-key C-d detach +bind-key & confirm-before -p "kill-window #W? (y/n)" kill-window + +# même hack que sur screen lorsqu'on veut profiter du scroll du terminal +# (xterm ...) +set -g terminal-overrides 'xterm*:smcup@:rmcup@' + +#Pour les ctrl+arrow +set-option -g xterm-keys on + +# c'est un minimum (defaut 2000) +set-option -g history-limit 10000 + +# lorsque j'ai encore un tmux ailleurs seule +# sa fenetre active réduit la taille de ma fenetre locale +setw -g aggressive-resize on + +# Pour etre alerté sur un changement dans une autre fenêtre +setw -g monitor-activity on +#set -g visual-activity on +#set -g visual-bell on + +set -g base-index 1 + +# repercuter le contenu de la fenetre dans la barre de titre +# reference des string : man tmux (status-left) +set -g set-titles on +set -g set-titles-string '#H #W #T' # host window command + +#Dans les valeurs par defaut deja, avec le ssh-agent +set -g update-environment "DISPLAY SSH_ASKPASS SSH_AUTH_SOCK SSH_AGENT_PID SSH_CONNECTION WINDOWID XAUTHORITY PATH" + +set -g status off +set -g status-left '' +set -g status-right '' + diff --git a/modules/private/ssh/default.nix b/modules/private/ssh/default.nix new file mode 100644 index 0000000..beedaff --- /dev/null +++ b/modules/private/ssh/default.nix @@ -0,0 +1,40 @@ +{ lib, pkgs, config, myconfig, ... }: +{ + config = { + networking.firewall.allowedTCPPorts = [ 22 ]; + + services.openssh.extraConfig = '' + AuthorizedKeysCommand /etc/ssh/ldap_authorized_keys + AuthorizedKeysCommandUser nobody + ''; + + secrets.keys = [{ + dest = "ssh-ldap"; + user = "nobody"; + group = "nogroup"; + permissions = "0400"; + text = myconfig.env.sshd.ldap.password; + }]; + system.activationScripts.sshd = { + deps = [ "secrets" ]; + text = '' + install -Dm400 -o nobody -g nogroup -T /var/secrets/ssh-ldap /etc/ssh/ldap_password + ''; + }; + # ssh is strict about parent directory having correct rights, don't + # move it in the nix store. + environment.etc."ssh/ldap_authorized_keys" = let + ldap_authorized_keys = + pkgs.mylibs.wrap { + name = "ldap_authorized_keys"; + file = ./ldap_authorized_keys.sh; + paths = [ pkgs.which pkgs.gitolite pkgs.openldap pkgs.stdenv.shellPackage pkgs.gnugrep pkgs.gnused pkgs.coreutils ]; + }; + in { + enable = true; + mode = "0755"; + user = "root"; + source = ldap_authorized_keys; + }; + }; +} diff --git a/modules/private/ssh/ldap_authorized_keys.sh b/modules/private/ssh/ldap_authorized_keys.sh new file mode 100755 index 0000000..d556452 --- /dev/null +++ b/modules/private/ssh/ldap_authorized_keys.sh @@ -0,0 +1,152 @@ +#!/usr/bin/env bash + +LDAPSEARCH=ldapsearch +KEY="immaeSshKey" +LDAP_BIND="cn=ssh,ou=services,dc=immae,dc=eu" +LDAP_PASS=$(cat /etc/ssh/ldap_password) +LDAP_HOST="ldap.immae.eu" +LDAP_MEMBER="cn=users,cn=ssh,ou=services,dc=immae,dc=eu" +LDAP_GITOLITE_MEMBER="cn=users,cn=gitolite,ou=services,dc=immae,dc=eu" +LDAP_PUB_RESTRICT_MEMBER="cn=restrict,cn=pub,ou=services,dc=immae,dc=eu" +LDAP_PUB_FORWARD_MEMBER="cn=forward,cn=pub,ou=services,dc=immae,dc=eu" +LDAP_BASE="dc=immae,dc=eu" +GITOLITE_SHELL=$(which gitolite-shell) +ECHO=$(which echo) + +suitable_for() { + type_for="$1" + key="$2" + + if [[ $key != *$'\n'* ]] && [[ $key == ssh-* ]]; then + echo "$key" + else + key_type=$(cut -d " " -f 1 <<< "$key") + + if grep -q "\b-$type_for\b" <<< "$key_type"; then + echo "" + elif grep -q "\b$type_for\b" <<< "$key_type"; then + echo $(sed -e "s/^[^ ]* //g" <<< "$key") + else + echo "" + fi + fi +} + +clean_key_line() { + type_for="$1" + line="$2" + + if [[ "$line" == $KEY::* ]]; then + # base64 keys should't happen, unless wrong copy-pasting + key="" + else + key=$(sed -e "s/^$KEY: *//" -e "s/ *$//" <<< "$line") + fi + + suitable_for "$type_for" "$key" +} + +ldap_search() { + $LDAPSEARCH -h $LDAP_HOST -ZZ -b $LDAP_BASE -D $LDAP_BIND -w "$LDAP_PASS" -x -o ldif-wrap=no -LLL "$@" +} + +ldap_keys() { + user=$1; + if [[ $user == gitolite ]]; then + ldap_search '(&(memberOf='$LDAP_GITOLITE_MEMBER')('$KEY'=*))' $KEY | \ + while read line ; + do + if [ ! -z "$line" ]; then + if [[ $line == dn* ]]; then + user=$(sed -n 's/.*uid=\([^,]*\).*/\1/p' <<< "$line") + if [ -n "$user" ]; then + if [[ $user == "immae" ]] || [[ $user == "denise" ]]; then + # Capitalize first letter (backward compatibility) + user=$(sed -r 's/^([a-z])/\U\1/' <<< "$user") + fi + else + # Service fake user + user=$(sed -n 's/.*cn=\([^,]*\).*/\1/p' <<< "$line") + fi + elif [[ $line == $KEY* ]]; then + key=$(clean_key_line git "$line") + if [ ! -z "$key" ]; then + if [[ $key != *$'\n'* ]] && [[ $key == ssh-* ]]; then + echo -n 'command="'$GITOLITE_SHELL' '$user'",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ' + echo $key + fi + fi + fi + fi + done + exit 0 + elif [[ $user == pub ]]; then + ldap_search '(&(memberOf='$LDAP_PUB_RESTRICT_MEMBER')('$KEY'=*))' $KEY | \ + while read line ; + do + if [ ! -z "$line" ]; then + if [[ $line == dn* ]]; then + echo "" + user=$(sed -n 's/.*uid=\([^,]*\).*/\1/p' <<< "$line") + echo "# $user" + elif [[ $line == $KEY* ]]; then + key=$(clean_key_line pub "$line") + key_forward=$(clean_key_line forward "$line") + if [ ! -z "$key" ]; then + if [[ $key != *$'\n'* ]] && [[ $key == ssh-* ]]; then + echo -n 'command="/etc/profiles/per-user/pub/bin/restrict '$user'" ' + echo $key + fi + elif [ ! -z "$key_forward" ]; then + if [[ $key_forward != *$'\n'* ]] && [[ $key_forward == ssh-* ]]; then + echo "# forward only" + echo -n 'no-pty,no-X11-forwarding,command="'$ECHO' forward only" ' + echo $key_forward + fi + fi + fi + fi + done + + echo "" + ldap_search '(&(memberOf='$LDAP_PUB_FORWARD_MEMBER')('$KEY'=*))' $KEY | \ + while read line ; + do + if [ ! -z "$line" ]; then + if [[ $line == dn* ]]; then + echo "" + user=$(sed -n 's/.*uid=\([^,]*\).*/\1/p' <<< "$line") + echo "# $user" + elif [[ $line == $KEY* ]]; then + key=$(clean_key_line forward "$line") + if [ ! -z "$key" ]; then + if [[ $key != *$'\n'* ]] && [[ $key == ssh-* ]]; then + echo -n 'no-pty,no-X11-forwarding,command="'$ECHO' forward only" ' + echo $key + fi + fi + fi + fi + done + exit 0 + else + ldap_search '(&(memberOf='$LDAP_MEMBER')('$KEY'=*)(uid='$user'))' $KEY | \ + while read line ; + do + if [ ! -z "$line" ]; then + if [[ $line == dn* ]]; then + user=$(sed -n 's/.*uid=\([^,]*\).*/\1/p' <<< "$line") + elif [[ $line == $KEY* ]]; then + key=$(clean_key_line ssh "$line") + if [ ! -z "$key" ]; then + if [[ $key != *$'\n'* ]] && [[ $key == ssh-* ]]; then + echo $key + fi + fi + fi + fi + done + fi +} + +ldap_keys $@ diff --git a/modules/private/system.nix b/modules/private/system.nix new file mode 100644 index 0000000..fba504e --- /dev/null +++ b/modules/private/system.nix @@ -0,0 +1,30 @@ +{ pkgs, privateFiles, ... }: +{ + config = { + nixpkgs.overlays = builtins.attrValues (import ../../overlays); + _module.args = { + pkgsNext = import {}; + pkgsPrevious = import {}; + myconfig = { + inherit privateFiles; + env = import "${privateFiles}/environment.nix"; + }; + }; + + services.journald.extraConfig = '' + MaxLevelStore="warning" + MaxRetentionSec="1year" + ''; + + users.users.root.packages = [ + pkgs.telnet + pkgs.htop + pkgs.iftop + ]; + + environment.systemPackages = [ + pkgs.vim + ]; + + }; +} diff --git a/modules/private/tasks/default.nix b/modules/private/tasks/default.nix new file mode 100644 index 0000000..30f49ee --- /dev/null +++ b/modules/private/tasks/default.nix @@ -0,0 +1,327 @@ +{ lib, pkgs, config, myconfig, ... }: +let + cfg = config.myServices.tasks; + server_vardir = config.services.taskserver.dataDir; + fqdn = "task.immae.eu"; + user = config.services.taskserver.user; + env = myconfig.env.tools.task; + group = config.services.taskserver.group; + taskserver-user-certs = pkgs.runCommand "taskserver-user-certs" {} '' + mkdir -p $out/bin + cat > $out/bin/taskserver-user-certs <<"EOF" + #!/usr/bin/env bash + + user=$1 + + silent_certtool() { + if ! output="$("${pkgs.gnutls.bin}/bin/certtool" "$@" 2>&1)"; then + echo "GNUTLS certtool invocation failed with output:" >&2 + echo "$output" >&2 + fi + } + + silent_certtool -p \ + --bits 4096 \ + --outfile "${server_vardir}/userkeys/$user.key.pem" + ${pkgs.gnused}/bin/sed -i -n -e '/^-----BEGIN RSA PRIVATE KEY-----$/,$p' "${server_vardir}/userkeys/$user.key.pem" + + silent_certtool -c \ + --template "${pkgs.writeText "taskserver-ca.template" '' + tls_www_client + encryption_key + signing_key + expiration_days = 3650 + ''}" \ + --load-ca-certificate "${server_vardir}/keys/ca.cert" \ + --load-ca-privkey "${server_vardir}/keys/ca.key" \ + --load-privkey "${server_vardir}/userkeys/$user.key.pem" \ + --outfile "${server_vardir}/userkeys/$user.cert.pem" + EOF + chmod a+x $out/bin/taskserver-user-certs + patchShebangs $out/bin/taskserver-user-certs + ''; + taskwarrior-web = pkgs.webapps.taskwarrior-web; + socketsDir = "/run/taskwarrior-web"; + varDir = "/var/lib/taskwarrior-web"; + taskwebPages = let + uidPages = lib.attrsets.zipAttrs ( + lib.lists.flatten + (lib.attrsets.mapAttrsToList (k: c: map (v: { "${v}" = k; }) c.uid) env.taskwarrior-web) + ); + pages = lib.attrsets.mapAttrs (uid: items: + if lib.lists.length items == 1 then + '' + + + + + + + '' + else + '' + + + To-do list disponibles + + + + +
    + ${builtins.concatStringsSep "\n" (map (item: "
  • ${item}
  • ") items)} +
+ + + '' + ) uidPages; + in + pkgs.runCommand "taskwerver-pages" {} '' + mkdir -p $out/ + ${builtins.concatStringsSep "\n" (lib.attrsets.mapAttrsToList (k: v: "cp ${pkgs.writeText k v} $out/${k}.html") pages)} + echo "Please login" > $out/index.html + ''; +in { + options.myServices.tasks = { + enable = lib.mkEnableOption "my tasks service"; + }; + + config = lib.mkIf cfg.enable { + secrets.keys = [{ + dest = "webapps/tools-taskwarrior-web"; + user = "wwwrun"; + group = "wwwrun"; + permissions = "0400"; + text = '' + SetEnv TASKD_HOST "${fqdn}:${toString config.services.taskserver.listenPort}" + SetEnv TASKD_VARDIR "${server_vardir}" + SetEnv TASKD_LDAP_HOST "ldaps://${env.ldap.host}" + SetEnv TASKD_LDAP_DN "${env.ldap.dn}" + SetEnv TASKD_LDAP_PASSWORD "${env.ldap.password}" + SetEnv TASKD_LDAP_BASE "${env.ldap.base}" + SetEnv TASKD_LDAP_FILTER "${env.ldap.search}" + ''; + }]; + services.websites.tools.modules = [ "proxy_fcgi" "sed" ]; + services.websites.tools.vhostConfs.task = { + certName = "eldiron"; + addToCerts = true; + hosts = [ "task.immae.eu" ]; + root = "/run/current-system/webapps/_task"; + extraConfig = [ '' + + DirectoryIndex index.php + Use LDAPConnect + Require ldap-group cn=users,cn=taskwarrior,ou=services,dc=immae,dc=eu + + SetHandler "proxy:unix:/var/run/phpfpm/task.sock|fcgi://localhost" + + Include /var/secrets/webapps/tools-taskwarrior-web + + '' + '' + + ProxyPass "unix://${socketsDir}/%{folderName}.sock|http://localhost-%{folderName}/" + ProxyPassReverse "unix://${socketsDir}/%{folderName}.sock|http://localhost-%{folderName}/" + ProxyPassReverse http://${fqdn}/ + + SetOutputFilter Sed + OutputSed "s|/ajax|/taskweb/%{folderName}/ajax|g" + OutputSed "s|\([^x]\)/tasks|\1/taskweb/%{folderName}/tasks|g" + OutputSed "s|\([^x]\)/projects|\1/taskweb/%{folderName}/projects|g" + OutputSed "s|http://${fqdn}/|/taskweb/%{folderName}/|g" + OutputSed "s|/img/relax.jpg|/taskweb/%{folderName}/img/relax.jpg|g" + + '' + '' + Alias /taskweb ${taskwebPages} + + DirectoryIndex index.html + Require all granted + + + RewriteEngine on + RewriteRule ^/taskweb$ /taskweb/ [R=301,L] + RedirectMatch permanent ^/taskweb/([^/]+)$ /taskweb/$1/ + + RewriteCond %{LA-U:REMOTE_USER} !="" + RewriteCond ${taskwebPages}/%{LA-U:REMOTE_USER}.html -f + RewriteRule ^/taskweb/?$ ${taskwebPages}/%{LA-U:REMOTE_USER}.html [L] + + + Use LDAPConnect + Require ldap-group cn=users,cn=taskwarrior,ou=services,dc=immae,dc=eu + + '' + ] ++ (lib.attrsets.mapAttrsToList (k: v: '' + + ${builtins.concatStringsSep "\n" (map (uid: "Require ldap-attribute uid=${uid}") v.uid)} + + Use Taskwarrior ${k} + + '') env.taskwarrior-web); + }; + services.phpfpm.poolConfigs = { + tasks = '' + listen = /var/run/phpfpm/task.sock + user = ${user} + group = ${group} + listen.owner = wwwrun + listen.group = wwwrun + pm = dynamic + pm.max_children = 60 + pm.start_servers = 2 + pm.min_spare_servers = 1 + pm.max_spare_servers = 10 + + ; Needed to avoid clashes in browser cookies (same domain) + env[PATH] = "/etc/profiles/per-user/${user}/bin" + php_value[session.name] = TaskPHPSESSID + php_admin_value[open_basedir] = "${./www}:/tmp:${server_vardir}:/etc/profiles/per-user/${user}/bin/" + ''; + }; + + myServices.websites.webappDirs._task = ./www; + + security.acme.certs."task" = config.services.myCertificates.certConfig // { + inherit user group; + plugins = [ "fullchain.pem" "key.pem" "cert.pem" "account_key.json" ]; + domain = fqdn; + postRun = '' + systemctl restart taskserver.service + ''; + }; + + users.users.${user}.packages = [ taskserver-user-certs ]; + + system.activationScripts.taskserver = { + deps = [ "users" ]; + text = '' + install -m 0750 -o ${user} -g ${group} -d ${server_vardir} + install -m 0750 -o ${user} -g ${group} -d ${server_vardir}/userkeys + install -m 0750 -o ${user} -g ${group} -d ${server_vardir}/keys + + if [ ! -e "${server_vardir}/keys/ca.key" ]; then + silent_certtool() { + if ! output="$("${pkgs.gnutls.bin}/bin/certtool" "$@" 2>&1)"; then + echo "GNUTLS certtool invocation failed with output:" >&2 + echo "$output" >&2 + fi + } + + silent_certtool -p \ + --bits 4096 \ + --outfile "${server_vardir}/keys/ca.key" + + silent_certtool -s \ + --template "${pkgs.writeText "taskserver-ca.template" '' + cn = ${fqdn} + expiration_days = -1 + cert_signing_key + ca + ''}" \ + --load-privkey "${server_vardir}/keys/ca.key" \ + --outfile "${server_vardir}/keys/ca.cert" + + chown :${group} "${server_vardir}/keys/ca.key" + chmod g+r "${server_vardir}/keys/ca.key" + fi + ''; + }; + + services.taskserver = { + enable = true; + allowedClientIDs = [ "^task [2-9]" "^Mirakel [1-9]" ]; + inherit fqdn; + listenHost = "::"; + pki.manual.ca.cert = "${server_vardir}/keys/ca.cert"; + pki.manual.server.cert = "${config.security.acme.directory}/task/fullchain.pem"; + pki.manual.server.crl = "${config.security.acme.directory}/task/invalid.crl"; + pki.manual.server.key = "${config.security.acme.directory}/task/key.pem"; + requestLimit = 104857600; + }; + + system.activationScripts.taskwarrior-web = { + deps = [ "users" ]; + text = '' + if [ ! -f ${server_vardir}/userkeys/taskwarrior-web.cert.pem ]; then + ${taskserver-user-certs}/bin/taskserver-user-certs taskwarrior-web + chown taskd:taskd ${server_vardir}/userkeys/taskwarrior-web.cert.pem ${server_vardir}/userkeys/taskwarrior-web.key.pem + fi + ''; + }; + + systemd.services = (lib.attrsets.mapAttrs' (name: userConfig: + let + credentials = "${userConfig.org}/${name}/${userConfig.key}"; + dateFormat = userConfig.date; + taskrc = pkgs.writeText "taskrc" '' + data.location=${varDir}/${name} + taskd.certificate=${server_vardir}/userkeys/taskwarrior-web.cert.pem + taskd.key=${server_vardir}/userkeys/taskwarrior-web.key.pem + # IdenTrust DST Root CA X3 + # obtained here: https://letsencrypt.org/fr/certificates/ + taskd.ca=${pkgs.writeText "ca.cert" '' + -----BEGIN CERTIFICATE----- + MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ + MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT + DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow + PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD + Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB + AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O + rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq + OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b + xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw + 7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD + aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV + HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG + SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 + ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr + AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz + R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 + JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo + Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ + -----END CERTIFICATE-----''} + taskd.server=${fqdn}:${toString config.services.taskserver.listenPort} + taskd.credentials=${credentials} + dateformat=${dateFormat} + ''; + in lib.attrsets.nameValuePair "taskwarrior-web-${name}" { + description = "Taskwarrior webapp for ${name}"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + path = [ pkgs.taskwarrior ]; + + environment.TASKRC = taskrc; + environment.BUNDLE_PATH = "${taskwarrior-web.gems}/${taskwarrior-web.gems.ruby.gemPath}"; + environment.BUNDLE_GEMFILE = "${taskwarrior-web.gems.confFiles}/Gemfile"; + environment.LC_ALL = "fr_FR.UTF-8"; + + script = '' + exec ${taskwarrior-web.gems}/${taskwarrior-web.gems.ruby.gemPath}/bin/bundle exec thin start -R config.ru -S ${socketsDir}/${name}.sock + ''; + + serviceConfig = { + User = user; + PrivateTmp = true; + Restart = "always"; + TimeoutSec = 60; + Type = "simple"; + WorkingDirectory = taskwarrior-web; + StateDirectoryMode = 0750; + StateDirectory = assert lib.strings.hasPrefix "/var/lib/" varDir; + (lib.strings.removePrefix "/var/lib/" varDir + "/${name}"); + RuntimeDirectoryPreserve = "yes"; + RuntimeDirectory = assert lib.strings.hasPrefix "/run/" socketsDir; + lib.strings.removePrefix "/run/" socketsDir; + }; + + unitConfig.RequiresMountsFor = varDir; + }) env.taskwarrior-web) // { + taskserver-ca.postStart = '' + chown :${group} "${server_vardir}/keys/ca.key" + chmod g+r "${server_vardir}/keys/ca.key" + ''; + }; + + }; +} diff --git a/modules/private/tasks/www/index.php b/modules/private/tasks/www/index.php new file mode 100644 index 0000000..deaf8af --- /dev/null +++ b/modules/private/tasks/www/index.php @@ -0,0 +1,157 @@ + $value) { + if ($key !== "count") { + $entries[] = explode(":", $value); + } +} + +if (isset($_GET["file"])) { + $basecert = $vardir . "/userkeys/" . $ldap_user; + if (!file_exists($basecert . ".cert.pem")) { + exec("taskserver-user-certs $ldap_user"); + } + $certificate = file_get_contents($basecert . ".cert.pem"); + $cert_key = file_get_contents($basecert . ".key.pem"); + + // IdenTrust DST Root CA X3 + // obtained here: https://letsencrypt.org/fr/certificates/ + $server_cert = "-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow +PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD +Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O +rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq +OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b +xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw +7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD +aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG +SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 +ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr +AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz +R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 +JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo +Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE-----"; + + $file = $_GET["file"]; + switch($file) { + case "ca.cert.pem": + $content = $server_cert; + $name = "ca.cert.pem"; + $type = "application/x-x509-ca-cert"; + break; + case "cert.pem": + $content = $certificate; + $name = $ldap_user . ".cert.pem"; + $type = "application/x-x509-ca-cert"; + break; + case "key.pem": + $content = $cert_key; + $name = $ldap_user . ".key.pem"; + $type = "application/x-x509-ca-cert"; + break; + case "mirakel"; + foreach ($entries as $entry) { + list($org, $user, $key) = $entry; + if ($key == $_GET["key"]) { break; } + } + $name = $user . ".mirakel"; + $type = "text/plain"; + $content = "username: $user +org: $org +user key: $key +server: $host +client.cert: +$certificate +Client.key: +$cert_key +ca.cert: +$server_cert +"; + break; + default: + die("invalid file name"); + break; + } + + header("Content-Type: $type"); + header('Content-Disposition: attachment; filename="' . $name . '"'); + header('Content-Transfer-Encoding: binary'); + header('Accept-Ranges: bytes'); + header('Cache-Control: private'); + header('Pragma: private'); + echo $content; + exit; +} +?> + +
+ Taskwarrior configuration +
+ + +For command line interface, download the files, put them near your Taskwarrior +configuration files, and add that to your Taskwarrior configuration: +
+taskd.certificate=/path/to/.cert.pem
+taskd.key=/path/to/.key.pem
+taskd.server=
+ 1) {
+  echo "# Chose one of them\n";
+  foreach($entries as $entry) {
+    list($org, $user, $key) = $entry;
+    echo "# taskd.credentials=$org/$user/$key\n";
+  }
+} else { ?>
+taskd.credentials=//
+
+taskd.ca=/path/to/ca.cert.pem
+
+For Mirakel, download and import the file: + +For Android Taskwarrior app, see instructions here. + + + diff --git a/modules/private/websites/tools/git/default.nix b/modules/private/websites/tools/git/default.nix index 3e8b605..75d0240 100644 --- a/modules/private/websites/tools/git/default.nix +++ b/modules/private/websites/tools/git/default.nix @@ -4,7 +4,9 @@ let inherit (pkgs.webapps) mantisbt_2 mantisbt_2-plugins; env = myconfig.env.tools.mantisbt; }; - gitweb = pkgs.callPackage ./gitweb.nix { gitoliteDir = config.services.myGitolite.gitoliteDir; }; + gitweb = pkgs.callPackage ./gitweb.nix { + gitoliteDir = config.myServices.gitolite.gitoliteDir; + }; cfg = config.myServices.websites.tools.git; in { diff --git a/nixops/eldiron.nix b/nixops/eldiron.nix index 69231d1..51af1f6 100644 --- a/nixops/eldiron.nix +++ b/nixops/eldiron.nix @@ -7,17 +7,8 @@ eldiron = { config, pkgs, myconfig, ... }: { - nixpkgs.overlays = builtins.attrValues (import ../overlays); - _module.args = { - pkgsNext = import {}; - pkgsPrevious = import {}; - myconfig = { - inherit privateFiles; - env = import "${privateFiles}/environment.nix"; - }; - }; - boot.kernelPackages = pkgs.linuxPackages_latest; + _module.args.privateFiles = privateFiles; networking = { firewall.enable = true; @@ -30,30 +21,15 @@ myconfig.env.servers.eldiron.ips); }; - imports = [ - ./modules/ssh - ./modules/certificates.nix - ./modules/gitolite - ./modules/mpd.nix - ./modules/mail.nix - ./modules/ftp.nix - ./modules/pub - ./modules/task - ./modules/buildbot - ./modules/dns.nix - ] ++ (builtins.attrValues (import ../modules)); + imports = builtins.attrValues (import ../modules); + + myServices.buildbot.enable = true; myServices.databases.enable = true; + myServices.gitolite.enable = true; myServices.irc.enable = true; - services.myGitolite.enable = true; + myServices.pub.enable = true; + myServices.tasks.enable = true; services.pure-ftpd.enable = true; - services.pub.enable = true; - services.myTasks.enable = true; - services.buildbot.enable = true; - - services.journald.extraConfig = '' - MaxLevelStore="warning" - MaxRetentionSec="1year" - ''; deployment = { targetEnv = "hetzner"; @@ -75,16 +51,6 @@ }; }; - users.users.root.packages = [ - pkgs.telnet - pkgs.htop - pkgs.iftop - ]; - - environment.systemPackages = [ - pkgs.vim - ]; - services.cron = { enable = true; systemCronJobs = [ diff --git a/nixops/modules/buildbot/common/build_helpers.py b/nixops/modules/buildbot/common/build_helpers.py deleted file mode 100644 index 384b1ac..0000000 --- a/nixops/modules/buildbot/common/build_helpers.py +++ /dev/null @@ -1,256 +0,0 @@ -from buildbot.plugins import util, steps, schedulers -from buildbot_buildslist import BuildsList - -__all__ = [ - "force_scheduler", "deploy_scheduler", "hook_scheduler", - "clean_branch", "package_and_upload", "SlackStatusPush", - "XMPPStatusPush" - ] - -# Small helpers" -@util.renderer -def clean_branch(props): - if props.hasProperty("branch") and len(props["branch"]) > 0: - return props["branch"].replace("/", "_") - else: - return "HEAD" - -def package_and_upload(package, package_dest, package_url): - return [ - steps.ShellCommand(name="build package", - logEnviron=False, haltOnFailure=True, workdir="source", - command=["git", "archive", "HEAD", "-o", package]), - - steps.FileUpload(name="upload package", workersrc=package, - workdir="source", masterdest=package_dest, - url=package_url, mode=0o644), - - steps.ShellCommand(name="cleanup package", logEnviron=False, - haltOnFailure=True, workdir="source", alwaysRun=True, - command=["rm", "-f", package]), - ] - -# Schedulers -def force_scheduler(name, builders): - return schedulers.ForceScheduler(name=name, - label="Force build", buttonName="Force build", - reason=util.StringParameter(name="reason", label="Reason", default="Force build"), - codebases=[ - util.CodebaseParameter("", - branch=util.StringParameter( - name="branch", label="Git reference (tag, branch)", required=True), - revision=util.FixedParameter(name="revision", default=""), - repository=util.FixedParameter(name="repository", default=""), - project=util.FixedParameter(name="project", default=""), - ), - ], - username=util.FixedParameter(name="username", default="Web button"), - builderNames=builders) - -def deploy_scheduler(name, builders): - return schedulers.ForceScheduler(name=name, - builderNames=builders, - label="Deploy built package", buttonName="Deploy", - username=util.FixedParameter(name="username", default="Web button"), - codebases=[ - util.CodebaseParameter(codebase="", - branch=util.FixedParameter(name="branch", default=""), - revision=util.FixedParameter(name="revision", default=""), - repository=util.FixedParameter(name="repository", default=""), - project=util.FixedParameter(name="project", default=""))], - reason=util.FixedParameter(name="reason", default="Deploy"), - properties=[ - util.ChoiceStringParameter(label="Environment", - name="environment", default="integration", - choices=["integration", "production"]), - BuildsList(label="Build to deploy", name="build"), - ] - ) - -def hook_scheduler(project, timer=10): - return schedulers.AnyBranchScheduler( - change_filter=util.ChangeFilter(category="hooks", project=project), - name=project, treeStableTimer=timer, builderNames=["{}_build".format(project)]) - -# Slack/XMPP status push -from buildbot.reporters.http import HttpStatusPushBase -from twisted.internet import defer -from twisted.python import log -from buildbot.util import httpclientservice -from buildbot.reporters import utils -from buildbot.process import results -from twisted.words.protocols.jabber.jid import JID -from wokkel import client, xmppim -from functools import partial - -class SlackStatusPush(HttpStatusPushBase): - name = "SlackStatusPush" - - @defer.inlineCallbacks - def reconfigService(self, serverUrl, **kwargs): - yield HttpStatusPushBase.reconfigService(self, **kwargs) - self._http = yield httpclientservice.HTTPClientService.getService( - self.master, serverUrl) - - @defer.inlineCallbacks - def send(self, build): - yield utils.getDetailsForBuild(self.master, build, wantProperties=True) - response = yield self._http.post("", json=self.format(build)) - if response.code != 200: - log.msg("%s: unable to upload status: %s" % - (response.code, response.content)) - - def format(self, build): - colors = [ - "#36A64F", # success - "#F1E903", # warnings - "#DA0505", # failure - "#FFFFFF", # skipped - "#000000", # exception - "#FFFFFF", # retry - "#D02CA9", # cancelled - ] - - if "environment" in build["properties"]: - msg = "{} environment".format(build["properties"]["environment"][0]) - if "build" in build["properties"]: - msg = "of archive {} in ".format(build["properties"]["build"][0]) + msg - elif len(build["buildset"]["sourcestamps"][0]["branch"]) > 0: - msg = "revision {}".format(build["buildset"]["sourcestamps"][0]["branch"]) - else: - msg = "build" - - if build["complete"]: - timedelta = int((build["complete_at"] - build["started_at"]).total_seconds()) - hours, rest = divmod(timedelta, 3600) - minutes, seconds = divmod(rest, 60) - if hours > 0: - duration = "{}h {}min {}s".format(hours, minutes, seconds) - elif minutes > 0: - duration = "{}min {}s".format(minutes, seconds) - else: - duration = "{}s".format(seconds) - - text = "Build <{}|{}> of {}'s {} was {} in {}.".format( - build["url"], build["buildid"], - build["builder"]["name"], - msg, - results.Results[build["results"]], - duration, - ) - fields = [ - { - "title": "Build", - "value": "<{}|{}>".format(build["url"], build["buildid"]), - "short": True, - }, - { - "title": "Project", - "value": build["builder"]["name"], - "short": True, - }, - { - "title": "Build status", - "value": results.Results[build["results"]], - "short": True, - }, - { - "title": "Build duration", - "value": duration, - "short": True, - }, - ] - if "environment" in build["properties"]: - fields.append({ - "title": "Environment", - "value": build["properties"]["environment"][0], - "short": True, - }) - if "build" in build["properties"]: - fields.append({ - "title": "Archive", - "value": build["properties"]["build"][0], - "short": True, - }) - attachments = [{ - "fallback": "", - "color": colors[build["results"]], - "fields": fields - }] - else: - text = "Build <{}|{}> of {}'s {} started.".format( - build["url"], build["buildid"], - build["builder"]["name"], - msg, - ) - attachments = [] - - return { - "username": "Buildbot", - "icon_url": "http://docs.buildbot.net/current/_static/icon.png", - "text": text, - "attachments": attachments, - } - -class XMPPStatusPush(HttpStatusPushBase): - name = "XMPPStatusPush" - - @defer.inlineCallbacks - def reconfigService(self, password, recipients, **kwargs): - yield HttpStatusPushBase.reconfigService(self, **kwargs) - self.password = password - self.recipients = recipients - - @defer.inlineCallbacks - def send(self, build): - yield utils.getDetailsForBuild(self.master, build, wantProperties=True) - body = self.format(build) - factory = client.DeferredClientFactory(JID("notify_bot@immae.fr/buildbot"), self.password) - d = client.clientCreator(factory) - def send_message(recipient, stream): - message = xmppim.Message(recipient=JID(recipient), body=body) - message.stanzaType = 'chat' - stream.send(message.toElement()) - # To allow chaining - return stream - for recipient in self.recipients: - d.addCallback(partial(send_message, recipient)) - d.addCallback(lambda _: factory.streamManager.xmlstream.sendFooter()) - d.addErrback(log.err) - - def format(self, build): - if "environment" in build["properties"]: - msg = "{} environment".format(build["properties"]["environment"][0]) - if "build" in build["properties"]: - msg = "of archive {} in ".format(build["properties"]["build"][0]) + msg - elif len(build["buildset"]["sourcestamps"][0]["branch"]) > 0: - msg = "revision {}".format(build["buildset"]["sourcestamps"][0]["branch"]) - else: - msg = "build" - - if build["complete"]: - timedelta = int((build["complete_at"] - build["started_at"]).total_seconds()) - hours, rest = divmod(timedelta, 3600) - minutes, seconds = divmod(rest, 60) - if hours > 0: - duration = "{}h {}min {}s".format(hours, minutes, seconds) - elif minutes > 0: - duration = "{}min {}s".format(minutes, seconds) - else: - duration = "{}s".format(seconds) - - text = "Build {} ( {} ) of {}'s {} was {} in {}.".format( - build["buildid"], build["url"], - build["builder"]["name"], - msg, - results.Results[build["results"]], - duration, - ) - else: - text = "Build {} ( {} ) of {}'s {} started.".format( - build["buildid"], build["url"], - build["builder"]["name"], - msg, - ) - - return text diff --git a/nixops/modules/buildbot/common/master.cfg b/nixops/modules/buildbot/common/master.cfg deleted file mode 100644 index abe08e0..0000000 --- a/nixops/modules/buildbot/common/master.cfg +++ /dev/null @@ -1,69 +0,0 @@ -# -*- python -*- -# ex: set filetype=python: - -from buildbot.plugins import secrets, util, webhooks -from buildbot.util import bytes2unicode -import re -import os -from buildbot_config import E, configure -import json - -class CustomBase(webhooks.base): - def getChanges(self, request): - try: - content = request.content.read() - args = json.loads(bytes2unicode(content)) - except Exception as e: - raise ValueError("Error loading JSON: " + str(e)) - - args.setdefault("comments", "") - args.setdefault("repository", "") - args.setdefault("author", args.get("who")) - - return ([args], None) - -userInfoProvider = util.LdapUserInfo( - uri=E.LDAP_URL, - bindUser=E.LDAP_ADMIN_USER, - bindPw=open(E.SECRETS_FILE + "/ldap", "r").read().rstrip(), - accountBase=E.LDAP_BASE, - accountPattern=E.LDAP_PATTERN, - accountFullName='cn', - accountEmail='mail', - avatarData="jpegPhoto", - groupBase=E.LDAP_BASE, - groupName="cn", - groupMemberPattern=E.LDAP_GROUP_PATTERN, - ) - -c = BuildmasterConfig = { - "title": E.TITLE, - "titleURL": E.TITLE_URL, - "db": { - "db_url": "sqlite:///state.sqlite" - }, - "protocols": { "pb": { "port": E.PB_SOCKET } }, - "workers": [], - "change_source": [], - "schedulers": [], - "builders": [], - "services": [], - "secretsProviders": [ - secrets.SecretInAFile(E.SECRETS_FILE), - ], - "www": { - "change_hook_dialects": { "base": { "custom_class": CustomBase } }, - "plugins": { - "waterfall_view": {}, - "console_view": {}, - "grid_view": {}, - "buildslist": {}, - }, - "auth": util.RemoteUserAuth( - header=b"X-Remote-User", - userInfoProvider=userInfoProvider, - headerRegex=re.compile(br"(?P[^ @]+)")), - } - } - -configure(c) diff --git a/nixops/modules/buildbot/default.nix b/nixops/modules/buildbot/default.nix deleted file mode 100644 index 60279b7..0000000 --- a/nixops/modules/buildbot/default.nix +++ /dev/null @@ -1,198 +0,0 @@ -{ lib, pkgs, config, myconfig, ... }: -let - varDir = "/var/lib/buildbot"; - buildbot_common = pkgs.python3Packages.buildPythonPackage rec { - name = "buildbot_common"; - src = ./common; - format = "other"; - installPhase = '' - mkdir -p $out/${pkgs.python3.pythonForBuild.sitePackages} - cp -a $src $out/${pkgs.python3.pythonForBuild.sitePackages}/buildbot_common - ''; - }; - buildbot = pkgs.python3Packages.buildbot-full; -in -{ - options = { - services.buildbot.enable = lib.mkOption { - type = lib.types.bool; - default = false; - description = '' - Whether to enable buildbot. - ''; - }; - }; - - config = lib.mkIf config.services.buildbot.enable { - ids.uids.buildbot = myconfig.env.buildbot.user.uid; - ids.gids.buildbot = myconfig.env.buildbot.user.gid; - - users.groups.buildbot.gid = config.ids.gids.buildbot; - users.users.buildbot = { - name = "buildbot"; - uid = config.ids.uids.buildbot; - group = "buildbot"; - description = "Buildbot user"; - home = varDir; - extraGroups = [ "keys" ]; - }; - - services.websites.tools.vhostConfs.git.extraConfig = lib.attrsets.mapAttrsToList (k: project: '' - RedirectMatch permanent "^/buildbot/${project.name}$" "/buildbot/${project.name}/" - RewriteEngine On - RewriteRule ^/buildbot/${project.name}/ws(.*)$ unix:///run/buildbot/${project.name}.sock|ws://git.immae.eu/ws$1 [P,NE,QSA,L] - ProxyPass /buildbot/${project.name}/ unix:///run/buildbot/${project.name}.sock|http://${project.name}-git.immae.eu/ - ProxyPassReverse /buildbot/${project.name}/ unix:///run/buildbot/${project.name}.sock|http://${project.name}-git.immae.eu/ - - Use LDAPConnect - Require ldap-group cn=users,ou=${project.name},cn=buildbot,ou=services,dc=immae,dc=eu - - SetEnvIf X-Url-Scheme https HTTPS=1 - ProxyPreserveHost On - - - - Require local - Require ldap-group cn=users,ou=${project.name},cn=buildbot,ou=services,dc=immae,dc=eu - Include /var/secrets/buildbot/${project.name}/webhook-httpd-include - - - '') myconfig.env.buildbot.projects; - - system.activationScripts = lib.attrsets.mapAttrs' (k: project: lib.attrsets.nameValuePair "buildbot-${project.name}" { - deps = [ "users" "wrappers" ]; - text = project.activationScript; - }) myconfig.env.buildbot.projects; - - secrets.keys = ( - lib.lists.flatten ( - lib.attrsets.mapAttrsToList (k: project: - lib.attrsets.mapAttrsToList (k: v: - { - permissions = "0600"; - user = "buildbot"; - group = "buildbot"; - text = v; - dest = "buildbot/${project.name}/${k}"; - } - ) project.secrets - ++ [ - { - permissions = "0600"; - user = "wwwrun"; - group = "wwwrun"; - text = lib.optionalString (lib.attrsets.hasAttr "webhookTokens" project) '' - Require expr "req('Access-Key') in { ${builtins.concatStringsSep ", " (map (x: "'${x}'") project.webhookTokens)} }" - ''; - dest = "buildbot/${project.name}/webhook-httpd-include"; - } - ] - ) myconfig.env.buildbot.projects - ) - ) ++ [ - { - permissions = "0600"; - user = "buildbot"; - group = "buildbot"; - text = myconfig.env.buildbot.ldap.password; - dest = "buildbot/ldap"; - } - { - permissions = "0600"; - user = "buildbot"; - group = "buildbot"; - text = builtins.readFile "${myconfig.privateFiles}/buildbot_ssh_key"; - dest = "buildbot/ssh_key"; - } - ]; - - systemd.services = lib.attrsets.mapAttrs' (k: project: lib.attrsets.nameValuePair "buildbot-${project.name}" { - description = "Buildbot Continuous Integration Server ${project.name}."; - after = [ "network-online.target" ]; - wantedBy = [ "multi-user.target" ]; - path = project.packages pkgs ++ (project.pythonPackages buildbot.pythonModule pkgs); - preStart = let - master-cfg = "${buildbot_common}/${pkgs.python3.pythonForBuild.sitePackages}/buildbot_common/master.cfg"; - tac_file = pkgs.writeText "buildbot.tac" '' - import os - - from twisted.application import service - from buildbot.master import BuildMaster - - basedir = '${varDir}/${project.name}' - rotateLength = 10000000 - maxRotatedFiles = 10 - configfile = '${master-cfg}' - - # Default umask for server - umask = None - - # if this is a relocatable tac file, get the directory containing the TAC - if basedir == '.': - import os - basedir = os.path.abspath(os.path.dirname(__file__)) - - # note: this line is matched against to check that this is a buildmaster - # directory; do not edit it. - application = service.Application('buildmaster') - from twisted.python.logfile import LogFile - from twisted.python.log import ILogObserver, FileLogObserver - logfile = LogFile.fromFullPath(os.path.join(basedir, "twistd.log"), rotateLength=rotateLength, - maxRotatedFiles=maxRotatedFiles) - application.setComponent(ILogObserver, FileLogObserver(logfile).emit) - - m = BuildMaster(basedir, configfile, umask) - m.setServiceParent(application) - m.log_rotation.rotateLength = rotateLength - m.log_rotation.maxRotatedFiles = maxRotatedFiles - ''; - in '' - if [ ! -f ${varDir}/${project.name}/buildbot.tac ]; then - ${buildbot}/bin/buildbot create-master -c "${master-cfg}" "${varDir}/${project.name}" - rm -f ${varDir}/${project.name}/master.cfg.sample - rm -f ${varDir}/${project.name}/buildbot.tac - fi - ln -sf ${tac_file} ${varDir}/${project.name}/buildbot.tac - # different buildbots may be trying that simultaneously, add the || true to avoid complaining in case of race - install -Dm600 -o buildbot -g buildbot -T /var/secrets/buildbot/ssh_key ${varDir}/buildbot_key || true - buildbot_secrets=${varDir}/${project.name}/secrets - install -m 0700 -o buildbot -g buildbot -d $buildbot_secrets - install -Dm600 -o buildbot -g buildbot -T /var/secrets/buildbot/ldap $buildbot_secrets/ldap - ${builtins.concatStringsSep "\n" (lib.attrsets.mapAttrsToList - (k: v: "install -Dm600 -o buildbot -g buildbot -T /var/secrets/buildbot/${project.name}/${k} $buildbot_secrets/${k}") project.secrets - )} - ''; - environment = let - project_env = lib.attrsets.mapAttrs' (k: v: lib.attrsets.nameValuePair "BUILDBOT_${k}" v) project.environment; - buildbot_config = pkgs.python3Packages.buildPythonPackage (rec { - name = "buildbot_config-${project.name}"; - src = ./projects + "/${project.name}"; - format = "other"; - installPhase = '' - mkdir -p $out/${pkgs.python3.pythonForBuild.sitePackages} - cp -a $src $out/${pkgs.python3.pythonForBuild.sitePackages}/buildbot_config - ''; - }); - HOME = "${varDir}/${project.name}"; - PYTHONPATH = "${buildbot.pythonModule.withPackages (self: project.pythonPackages self pkgs ++ [ - pkgs.python3Packages.wokkel - pkgs.python3Packages.treq pkgs.python3Packages.ldap3 buildbot - pkgs.python3Packages.buildbot-worker - buildbot_common buildbot_config - ])}/${buildbot.pythonModule.sitePackages}${if project.pythonPathHome then ":${varDir}/${project.name}/.local/${pkgs.python3.pythonForBuild.sitePackages}" else ""}"; - in project_env // { inherit PYTHONPATH HOME; }; - - serviceConfig = { - Type = "forking"; - User = "buildbot"; - Group = "buildbot"; - RuntimeDirectory = "buildbot"; - RuntimeDirectoryPreserve = "yes"; - StateDirectory = "buildbot"; - SupplementaryGroups = "keys"; - WorkingDirectory = "${varDir}/${project.name}"; - ExecStart = "${buildbot}/bin/buildbot start"; - }; - }) myconfig.env.buildbot.projects; - }; -} diff --git a/nixops/modules/buildbot/projects/caldance/__init__.py b/nixops/modules/buildbot/projects/caldance/__init__.py deleted file mode 100644 index 2c0bad5..0000000 --- a/nixops/modules/buildbot/projects/caldance/__init__.py +++ /dev/null @@ -1,190 +0,0 @@ -from buildbot.plugins import * -from buildbot_common.build_helpers import * -import os -from buildbot.util import bytes2unicode -import json - -__all__ = [ "configure", "E" ] - -class E(): - PROJECT = "caldance" - BUILDBOT_URL = "https://git.immae.eu/buildbot/{}/".format(PROJECT) - SOCKET = "unix:/run/buildbot/{}.sock".format(PROJECT) - PB_SOCKET = "unix:address=/run/buildbot/{}_pb.sock".format(PROJECT) - RELEASE_PATH = "/var/lib/ftp/release.immae.eu/{}".format(PROJECT) - RELEASE_URL = "https://release.immae.eu/{}".format(PROJECT) - GIT_URL = "gitolite@git.immae.eu:perso/simon_descarpentries/www.cal-dance.com" - SSH_KEY_PATH = "/var/lib/buildbot/buildbot_key" - SSH_HOST_KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIFbhFTl2A2RJn5L51yxJM4XfCS2ZaiSX/jo9jFSdghF" - LDAP_HOST = "ldap.immae.eu" - LDAP_DN = "cn=buildbot,ou=services,dc=immae,dc=eu" - LDAP_ROLES_BASE = "ou=roles,ou=hosts,dc=immae,dc=eu" - XMPP_RECIPIENTS = os.environ["BUILDBOT_XMPP_RECIPIENTS"].split(" ") - - PUPPET_HOST = { - "integration": "root@caldance.immae.eu", - } - - # master.cfg - SECRETS_FILE = os.getcwd() + "/secrets" - LDAP_URL = "ldaps://ldap.immae.eu:636" - LDAP_ADMIN_USER = "cn=buildbot,ou=services,dc=immae,dc=eu" - LDAP_BASE = "dc=immae,dc=eu" - LDAP_PATTERN = "(uid=%(username)s)" - LDAP_GROUP_PATTERN = "(&(memberOf=cn=groups,ou=caldance,cn=buildbot,ou=services,dc=immae,dc=eu)(member=%(dn)s))" - TITLE_URL = "https://caldance.immae.eu" - TITLE = "Caldance" - -class CustomBase(webhooks.base): - def getChanges(self, request): - try: - content = request.content.read() - args = json.loads(bytes2unicode(content)) - except Exception as e: - raise ValueError("Error loading JSON: " + str(e)) - - args.setdefault("comments", "") - args.setdefault("repository", "") - args.setdefault("author", args.get("who", "unknown")) - - if args["category"] == "deploy_webhook": - args = { - "category": "deploy_webhook", - "comments": "", - "repository": "", - "author": "webhook", - "project": "Caldance", - "properties": { - "environment": args.get("environment", "integration"), - "build": "caldance_{}.tar.gz".format(args.get("build", "master")) - } - } - - return ([args], None) - -def deploy_hook_scheduler(project, timer=1): - return schedulers.AnyBranchScheduler( - change_filter=util.ChangeFilter(category="deploy_webhook", project=project), - name="{}_deploy".format(project), treeStableTimer=timer, builderNames=["{}_deploy".format(project)]) - -def configure(c): - c["buildbotURL"] = E.BUILDBOT_URL - c["www"]["port"] = E.SOCKET - - c["www"]["change_hook_dialects"]["base"] = { - "custom_class": CustomBase - } - c['workers'].append(worker.LocalWorker("generic-worker")) - c['workers'].append(worker.LocalWorker("deploy-worker")) - - c['schedulers'].append(hook_scheduler("Caldance", timer=1)) - c['schedulers'].append(force_scheduler("force_caldance", ["Caldance_build"])) - c['schedulers'].append(deploy_scheduler("deploy_caldance", ["Caldance_deploy"])) - c['schedulers'].append(deploy_hook_scheduler("Caldance", timer=1)) - - c['builders'].append(factory("caldance")) - - c['builders'].append(deploy_factory("caldance")) - - c['services'].append(SlackStatusPush( - name="slack_status_caldance", - builders=["Caldance_build", "Caldance_deploy"], - serverUrl=open(E.SECRETS_FILE + "/slack_webhook", "r").read().rstrip())) - c['services'].append(XMPPStatusPush( - name="xmpp_status_caldance", - builders=["Caldance_build", "Caldance_deploy"], - recipients=E.XMPP_RECIPIENTS, - password=open(E.SECRETS_FILE + "/notify_xmpp_password", "r").read().rstrip())) - -def factory(project, ignore_fails=False): - release_file = "{1}/{0}_%(kw:clean_branch)s.tar.gz" - - package = util.Interpolate("{0}_%(kw:clean_branch)s.tar.gz".format(project), clean_branch=clean_branch) - package_dest = util.Interpolate(release_file.format(project, E.RELEASE_PATH), clean_branch=clean_branch) - package_url = util.Interpolate(release_file.format(project, E.RELEASE_URL), clean_branch=clean_branch) - - factory = util.BuildFactory() - factory.addStep(steps.Git(logEnviron=False, repourl=E.GIT_URL, - sshPrivateKey=open(E.SSH_KEY_PATH).read().rstrip(), - sshHostKey=E.SSH_HOST_KEY, mode="full", method="copy")) - factory.addSteps(package_and_upload(package, package_dest, package_url)) - - return util.BuilderConfig( - name="{}_build".format(project.capitalize()), - workernames=["generic-worker"], factory=factory) - -def compute_build_infos(project): - @util.renderer - def compute(props): - import re, hashlib - build_file = props.getProperty("build") - package_dest = "{1}/{0}".format(build_file, E.RELEASE_PATH) - version = re.match(r"{0}_(.*).tar.gz".format(project), build_file).group(1) - with open(package_dest, "rb") as f: - sha = hashlib.sha256(f.read()).hexdigest() - return { - "build_version": version, - "build_hash": sha, - } - return compute - -@util.renderer -def puppet_host(props): - environment = props["environment"] if props.hasProperty("environment") else "integration" - return E.PUPPET_HOST.get(environment, "host.invalid") - -def deploy_factory(project): - package_dest = util.Interpolate("{0}/%(prop:build)s".format(E.RELEASE_PATH)) - - factory = util.BuildFactory() - factory.addStep(steps.MasterShellCommand(command=["test", "-f", package_dest])) - factory.addStep(steps.SetProperties(properties=compute_build_infos(project))) - factory.addStep(LdapPush(environment=util.Property("environment"), - project=project, build_version=util.Property("build_version"), - build_hash=util.Property("build_hash"), ldap_password=util.Secret("ldap"))) - factory.addStep(steps.MasterShellCommand(command=[ - "ssh", "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no", "-o", "CheckHostIP=no", "-i", E.SSH_KEY_PATH, puppet_host])) - return util.BuilderConfig(name="{}_deploy".format(project.capitalize()), workernames=["deploy-worker"], factory=factory) - -from twisted.internet import defer -from buildbot.process.buildstep import FAILURE -from buildbot.process.buildstep import SUCCESS -from buildbot.process.buildstep import BuildStep - -class LdapPush(BuildStep): - name = "LdapPush" - renderables = ["environment", "project", "build_version", "build_hash", "ldap_password"] - - def __init__(self, **kwargs): - self.environment = kwargs.pop("environment") - self.project = kwargs.pop("project") - self.build_version = kwargs.pop("build_version") - self.build_hash = kwargs.pop("build_hash") - self.ldap_password = kwargs.pop("ldap_password") - self.ldap_host = kwargs.pop("ldap_host", E.LDAP_HOST) - super().__init__(**kwargs) - - def run(self): - import json - from ldap3 import Reader, Writer, Server, Connection, ObjectDef - server = Server(self.ldap_host) - conn = Connection(server, - user=E.LDAP_DN, - password=self.ldap_password) - conn.bind() - obj = ObjectDef("immaePuppetClass", conn) - r = Reader(conn, obj, - "cn=caldance.{},{}".format(self.environment, E.LDAP_ROLES_BASE)) - r.search() - if len(r) > 0: - w = Writer.from_cursor(r) - for value in w[0].immaePuppetJson.values: - config = json.loads(value) - if "role::caldance::{}_version".format(self.project) in config: - config["role::caldance::{}_version".format(self.project)] = self.build_version - config["role::caldance::{}_sha256".format(self.project)] = self.build_hash - w[0].immaePuppetJson -= value - w[0].immaePuppetJson += json.dumps(config, indent=" ") - w.commit() - return defer.succeed(SUCCESS) - return defer.succeed(FAILURE) diff --git a/nixops/modules/buildbot/projects/cryptoportfolio/__init__.py b/nixops/modules/buildbot/projects/cryptoportfolio/__init__.py deleted file mode 100644 index 5d70f95..0000000 --- a/nixops/modules/buildbot/projects/cryptoportfolio/__init__.py +++ /dev/null @@ -1,169 +0,0 @@ -from buildbot.plugins import * -from buildbot_common.build_helpers import * -import os - -__all__ = [ "configure", "E" ] - -class E(): - PROJECT = "cryptoportfolio" - BUILDBOT_URL = "https://git.immae.eu/buildbot/{}/".format(PROJECT) - SOCKET = "unix:/run/buildbot/{}.sock".format(PROJECT) - PB_SOCKET = "unix:address=/run/buildbot/{}_pb.sock".format(PROJECT) - RELEASE_PATH = "/var/lib/ftp/release.immae.eu/{}".format(PROJECT) - RELEASE_URL = "https://release.immae.eu/{}".format(PROJECT) - GIT_URL = "https://git.immae.eu/perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/{0}.git" - SSH_KEY_PATH = "/var/lib/buildbot/buildbot_key" - LDAP_HOST = "ldap.immae.eu" - LDAP_DN = "cn=buildbot,ou=services,dc=immae,dc=eu" - LDAP_ROLES_BASE = "ou=roles,ou=hosts,dc=immae,dc=eu" - - PUPPET_HOST = { - "production": "root@cryptoportfolio.immae.eu", - "integration": "root@cryptoportfolio-dev.immae.eu" - } - - # master.cfg - SECRETS_FILE = os.getcwd() + "/secrets" - LDAP_URL = "ldaps://ldap.immae.eu:636" - LDAP_ADMIN_USER = "cn=buildbot,ou=services,dc=immae,dc=eu" - LDAP_BASE = "dc=immae,dc=eu" - LDAP_PATTERN = "(uid=%(username)s)" - LDAP_GROUP_PATTERN = "(&(memberOf=cn=groups,ou=cryptoportfolio,cn=buildbot,ou=services,dc=immae,dc=eu)(member=%(dn)s))" - TITLE_URL = "https://git.immae.eu" - TITLE = "Cryptoportfolio" - -# eval .. dans .zshrc_local -# mkdir -p $BUILD/go -# export GOPATH=$BUILD/go -# go get -u github.com/golang/dep/cmd/dep -# export PATH=$PATH:$BUILD/go/bin -# go get git.immae.eu/Cryptoportfolio/Front.git -# cd $BUILD/go/src/git.immae.eu/Cryptoportfolio/Front.git -# git checkout dev -# dep ensure -def configure(c): - c["buildbotURL"] = E.BUILDBOT_URL - c["www"]["port"] = E.SOCKET - - c['workers'].append(worker.LocalWorker("generic-worker")) - c['workers'].append(worker.LocalWorker("deploy-worker")) - - c['schedulers'].append(hook_scheduler("Trader")) - c['schedulers'].append(hook_scheduler("Front")) - c['schedulers'].append(force_scheduler( - "force_cryptoportfolio", ["Trader_build", "Front_build"])) - c['schedulers'].append(deploy_scheduler("deploy_cryptoportfolio", - ["Trader_deploy", "Front_deploy"])) - - c['builders'].append(factory("trader")) - c['builders'].append(factory("front", ignore_fails=True)) - - c['builders'].append(deploy_factory("trader")) - c['builders'].append(deploy_factory("front")) - - c['services'].append(SlackStatusPush( - name="slack_status_cryptoportfolio", - builders=["Front_build", "Trader_build", "Front_deploy", "Trader_deploy"], - serverUrl=open(E.SECRETS_FILE + "/slack_webhook", "r").read().rstrip())) - -def factory(project, ignore_fails=False): - release_file = "{1}/{0}/{0}_%(kw:clean_branch)s.tar.gz" - - url = E.GIT_URL.format(project.capitalize()) - - package = util.Interpolate("{0}_%(kw:clean_branch)s.tar.gz".format(project), clean_branch=clean_branch) - package_dest = util.Interpolate(release_file.format(project, E.RELEASE_PATH), clean_branch=clean_branch) - package_url = util.Interpolate(release_file.format(project, E.RELEASE_URL), clean_branch=clean_branch) - - factory = util.BuildFactory() - factory.addStep(steps.Git(logEnviron=False, repourl=url, - mode="full", method="copy")) - factory.addStep(steps.ShellCommand(name="make install", - logEnviron=False, haltOnFailure=(not ignore_fails), - warnOnFailure=ignore_fails, flunkOnFailure=(not ignore_fails), - command=["make", "install"])) - factory.addStep(steps.ShellCommand(name="make test", - logEnviron=False, haltOnFailure=(not ignore_fails), - warnOnFailure=ignore_fails, flunkOnFailure=(not ignore_fails), - command=["make", "test"])) - factory.addSteps(package_and_upload(package, package_dest, package_url)) - - return util.BuilderConfig( - name="{}_build".format(project.capitalize()), - workernames=["generic-worker"], factory=factory) - -def compute_build_infos(project): - @util.renderer - def compute(props): - import re, hashlib - build_file = props.getProperty("build") - package_dest = "{2}/{0}/{1}".format(project, build_file, E.RELEASE_PATH) - version = re.match(r"{0}_(.*).tar.gz".format(project), build_file).group(1) - with open(package_dest, "rb") as f: - sha = hashlib.sha256(f.read()).hexdigest() - return { - "build_version": version, - "build_hash": sha, - } - return compute - -@util.renderer -def puppet_host(props): - environment = props["environment"] if props.hasProperty("environment") else "integration" - return E.PUPPET_HOST.get(environment, "host.invalid") - -def deploy_factory(project): - package_dest = util.Interpolate("{1}/{0}/%(prop:build)s".format(project, E.RELEASE_PATH)) - - factory = util.BuildFactory() - factory.addStep(steps.MasterShellCommand(command=["test", "-f", package_dest])) - factory.addStep(steps.SetProperties(properties=compute_build_infos(project))) - factory.addStep(LdapPush(environment=util.Property("environment"), - project=project, build_version=util.Property("build_version"), - build_hash=util.Property("build_hash"), ldap_password=util.Secret("ldap"))) - factory.addStep(steps.MasterShellCommand(command=[ - "ssh", "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no", "-o", "CheckHostIP=no", "-i", E.SSH_KEY_PATH, puppet_host])) - return util.BuilderConfig(name="{}_deploy".format(project.capitalize()), workernames=["deploy-worker"], factory=factory) - -from twisted.internet import defer -from buildbot.process.buildstep import FAILURE -from buildbot.process.buildstep import SUCCESS -from buildbot.process.buildstep import BuildStep - -class LdapPush(BuildStep): - name = "LdapPush" - renderables = ["environment", "project", "build_version", "build_hash", "ldap_password"] - - def __init__(self, **kwargs): - self.environment = kwargs.pop("environment") - self.project = kwargs.pop("project") - self.build_version = kwargs.pop("build_version") - self.build_hash = kwargs.pop("build_hash") - self.ldap_password = kwargs.pop("ldap_password") - self.ldap_host = kwargs.pop("ldap_host", E.LDAP_HOST) - super().__init__(**kwargs) - - def run(self): - import json - from ldap3 import Reader, Writer, Server, Connection, ObjectDef - server = Server(self.ldap_host) - conn = Connection(server, - user=E.LDAP_DN, - password=self.ldap_password) - conn.bind() - obj = ObjectDef("immaePuppetClass", conn) - r = Reader(conn, obj, - "cn=cryptoportfolio.{},{}".format(self.environment, E.LDAP_ROLES_BASE)) - r.search() - if len(r) > 0: - w = Writer.from_cursor(r) - for value in w[0].immaePuppetJson.values: - config = json.loads(value) - if "role::cryptoportfolio::{}_version".format(self.project) in config: - config["role::cryptoportfolio::{}_version".format(self.project)] = self.build_version - config["role::cryptoportfolio::{}_sha256".format(self.project)] = self.build_hash - w[0].immaePuppetJson -= value - w[0].immaePuppetJson += json.dumps(config, indent=" ") - w.commit() - return defer.succeed(SUCCESS) - return defer.succeed(FAILURE) diff --git a/nixops/modules/buildbot/projects/test/__init__.py b/nixops/modules/buildbot/projects/test/__init__.py deleted file mode 100644 index e6b8d51..0000000 --- a/nixops/modules/buildbot/projects/test/__init__.py +++ /dev/null @@ -1,188 +0,0 @@ -from buildbot.plugins import * -from buildbot_common.build_helpers import * -import os -from buildbot.util import bytes2unicode -import json - -__all__ = [ "configure", "E" ] - -class E(): - PROJECT = "test" - BUILDBOT_URL = "https://git.immae.eu/buildbot/{}/".format(PROJECT) - SOCKET = "unix:/run/buildbot/{}.sock".format(PROJECT) - PB_SOCKET = "unix:address=/run/buildbot/{}_pb.sock".format(PROJECT) - RELEASE_PATH = "/var/lib/ftp/release.immae.eu/{}".format(PROJECT) - RELEASE_URL = "https://release.immae.eu/{}".format(PROJECT) - GIT_URL = "https://git.immae.eu/perso/Immae/TestProject.git" - SSH_KEY_PATH = "/var/lib/buildbot/buildbot_key" - PUPPET_HOST = "root@backup-1.v.immae.eu" - LDAP_HOST = "ldap.immae.eu" - LDAP_DN = "cn=buildbot,ou=services,dc=immae,dc=eu" - LDAP_ROLES_BASE = "ou=roles,ou=hosts,dc=immae,dc=eu" - XMPP_RECIPIENTS = os.environ["BUILDBOT_XMPP_RECIPIENTS"].split(" ") - - # master.cfg - SECRETS_FILE = os.getcwd() + "/secrets" - LDAP_URL = "ldaps://ldap.immae.eu:636" - LDAP_ADMIN_USER = "cn=buildbot,ou=services,dc=immae,dc=eu" - LDAP_BASE = "dc=immae,dc=eu" - LDAP_PATTERN = "(uid=%(username)s)" - LDAP_GROUP_PATTERN = "(&(memberOf=cn=groups,ou=test,cn=buildbot,ou=services,dc=immae,dc=eu)(member=%(dn)s))" - TITLE_URL = "https://git.immae.eu/?p=perso/Immae/TestProject.git;a=summary" - TITLE = "Test project" - -class CustomBase(webhooks.base): - def getChanges(self, request): - try: - content = request.content.read() - args = json.loads(bytes2unicode(content)) - except Exception as e: - raise ValueError("Error loading JSON: " + str(e)) - - args.setdefault("comments", "") - args.setdefault("repository", "") - args.setdefault("author", args.get("who", "unknown")) - - if args["category"] == "deploy_webhook": - args = { - "category": "deploy_webhook", - "comments": "", - "repository": "", - "author": "unknown", - "project": "TestProject", - "properties": { - "environment": args.get("environment", "integration"), - "build": "test_{}.tar.gz".format(args.get("branch", "master")) - } - } - - return ([args], None) - -def deploy_hook_scheduler(project, timer=1): - return schedulers.AnyBranchScheduler( - change_filter=util.ChangeFilter(category="deploy_webhook", project=project), - name="{}_deploy".format(project), treeStableTimer=timer, builderNames=["{}_deploy".format(project)]) - -def configure(c): - c["buildbotURL"] = E.BUILDBOT_URL - c["www"]["port"] = E.SOCKET - - c["www"]["change_hook_dialects"]["base"] = { - "custom_class": CustomBase - } - c['workers'].append(worker.LocalWorker("generic-worker-test")) - c['workers'].append(worker.LocalWorker("deploy-worker-test")) - - c['schedulers'].append(hook_scheduler("TestProject", timer=1)) - c['schedulers'].append(force_scheduler("force_test", ["TestProject_build"])) - c['schedulers'].append(deploy_scheduler("deploy_test", ["TestProject_deploy"])) - c['schedulers'].append(deploy_hook_scheduler("TestProject", timer=1)) - - c['builders'].append(factory()) - c['builders'].append(deploy_factory()) - - c['services'].append(SlackStatusPush( - name="slack_status_test_project", - builders=["TestProject_build", "TestProject_deploy"], - serverUrl=open(E.SECRETS_FILE + "/slack_webhook", "r").read().rstrip())) - c['services'].append(XMPPStatusPush( - name="xmpp_status_test_project", - builders=["TestProject_build", "TestProject_deploy"], - recipients=E.XMPP_RECIPIENTS, - password=open(E.SECRETS_FILE + "/notify_xmpp_password", "r").read().rstrip())) - -def factory(): - package = util.Interpolate("test_%(kw:clean_branch)s.tar.gz", clean_branch=clean_branch) - package_dest = util.Interpolate("{}/test_%(kw:clean_branch)s.tar.gz".format(E.RELEASE_PATH), clean_branch=clean_branch) - package_url = util.Interpolate("{}/test_%(kw:clean_branch)s.tar.gz".format(E.RELEASE_URL), clean_branch=clean_branch) - - factory = util.BuildFactory() - factory.addStep(steps.Git(logEnviron=False, - repourl=E.GIT_URL, mode="full", method="copy")) - factory.addStep(steps.ShellCommand(name="env", - logEnviron=False, command=["env"])) - factory.addStep(steps.ShellCommand(name="pwd", - logEnviron=False, command=["pwd"])) - factory.addStep(steps.ShellCommand(name="true", - logEnviron=False, command=["true"])) - factory.addStep(steps.ShellCommand(name="echo", - logEnviron=False, command=["echo", package])) - factory.addSteps(package_and_upload(package, package_dest, package_url)) - - return util.BuilderConfig(name="TestProject_build", workernames=["generic-worker-test"], factory=factory) - - -def compute_build_infos(): - @util.renderer - def compute(props): - import re, hashlib - build_file = props.getProperty("build") - package_dest = "{}/{}".format(E.RELEASE_PATH, build_file) - version = re.match(r"{0}_(.*).tar.gz".format("test"), build_file).group(1) - with open(package_dest, "rb") as f: - sha = hashlib.sha256(f.read()).hexdigest() - return { - "build_version": version, - "build_hash": sha, - } - return compute - -@util.renderer -def puppet_host(props): - return E.PUPPET_HOST - -def deploy_factory(): - package_dest = util.Interpolate("{}/%(prop:build)s".format(E.RELEASE_PATH)) - - factory = util.BuildFactory() - factory.addStep(steps.MasterShellCommand(command=["test", "-f", package_dest])) - factory.addStep(steps.SetProperties(properties=compute_build_infos())) - factory.addStep(LdapPush(environment=util.Property("environment"), - build_version=util.Property("build_version"), - build_hash=util.Property("build_hash"), - ldap_password=util.Secret("ldap"))) - factory.addStep(steps.MasterShellCommand(command=[ - "ssh", "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no", "-o", "CheckHostIP=no", "-i", E.SSH_KEY_PATH, puppet_host])) - return util.BuilderConfig(name="TestProject_deploy", workernames=["deploy-worker-test"], factory=factory) - -from twisted.internet import defer -from buildbot.process.buildstep import FAILURE -from buildbot.process.buildstep import SUCCESS -from buildbot.process.buildstep import BuildStep - -class LdapPush(BuildStep): - name = "LdapPush" - renderables = ["environment", "build_version", "build_hash", "ldap_password"] - - def __init__(self, **kwargs): - self.environment = kwargs.pop("environment") - self.build_version = kwargs.pop("build_version") - self.build_hash = kwargs.pop("build_hash") - self.ldap_password = kwargs.pop("ldap_password") - self.ldap_host = kwargs.pop("ldap_host", E.LDAP_HOST) - super().__init__(**kwargs) - - def run(self): - import json - from ldap3 import Reader, Writer, Server, Connection, ObjectDef - server = Server(self.ldap_host) - conn = Connection(server, - user=E.LDAP_DN, - password=self.ldap_password) - conn.bind() - obj = ObjectDef("immaePuppetClass", conn) - r = Reader(conn, obj, - "cn=test.{},{}".format(self.environment, E.LDAP_ROLES_BASE)) - r.search() - if len(r) > 0: - w = Writer.from_cursor(r) - for value in w[0].immaePuppetJson.values: - config = json.loads(value) - if "test_version" in config: - config["test_version"] = self.build_version - config["test_sha256"] = self.build_hash - w[0].immaePuppetJson -= value - w[0].immaePuppetJson += json.dumps(config, indent=" ") - w.commit() - return defer.succeed(SUCCESS) - return defer.succeed(FAILURE) diff --git a/nixops/modules/certificates.nix b/nixops/modules/certificates.nix deleted file mode 100644 index 43f6a23..0000000 --- a/nixops/modules/certificates.nix +++ /dev/null @@ -1,52 +0,0 @@ -{ lib, pkgs, config, ... }: -{ - options.services.myCertificates = { - certConfig = lib.mkOption { - default = { - webroot = "${config.security.acme.directory}/acme-challenge"; - email = "ismael@bouya.org"; - postRun = '' - systemctl reload httpdTools.service httpdInte.service httpdProd.service - ''; - plugins = [ "cert.pem" "chain.pem" "fullchain.pem" "full.pem" "key.pem" "account_key.json" ]; - }; - description = "Default configuration for certificates"; - }; - }; - - config = { - services.websitesCerts = config.services.myCertificates.certConfig; - myServices.databasesCerts = config.services.myCertificates.certConfig; - myServices.ircCerts = config.services.myCertificates.certConfig; - - security.acme.preliminarySelfsigned = true; - - security.acme.certs = { - "eldiron" = config.services.myCertificates.certConfig // { - domain = "eldiron.immae.eu"; - }; - }; - - systemd.services = lib.attrsets.mapAttrs' (k: v: - lib.attrsets.nameValuePair "acme-selfsigned-${k}" (lib.mkBefore { script = - (lib.optionalString (builtins.elem "cert.pem" v.plugins) '' - cp $workdir/server.crt ${config.security.acme.directory}/${k}/cert.pem - chown '${v.user}:${v.group}' ${config.security.acme.directory}/${k}/cert.pem - chmod ${if v.allowKeysForGroup then "750" else "700"} ${config.security.acme.directory}/${k}/cert.pem - '') + - (lib.optionalString (builtins.elem "chain.pem" v.plugins) '' - cp $workdir/ca.crt ${config.security.acme.directory}/${k}/chain.pem - chown '${v.user}:${v.group}' ${config.security.acme.directory}/${k}/chain.pem - chmod ${if v.allowKeysForGroup then "750" else "700"} ${config.security.acme.directory}/${k}/chain.pem - '') - ; }) - ) config.security.acme.certs // { - httpdProd.after = [ "acme-selfsigned-certificates.target" ]; - httpdProd.wants = [ "acme-selfsigned-certificates.target" ]; - httpdTools.after = [ "acme-selfsigned-certificates.target" ]; - httpdTools.wants = [ "acme-selfsigned-certificates.target" ]; - httpdInte.after = [ "acme-selfsigned-certificates.target" ]; - httpdInte.wants = [ "acme-selfsigned-certificates.target" ]; - }; - }; -} diff --git a/nixops/modules/dns.nix b/nixops/modules/dns.nix deleted file mode 100644 index ced8d9b..0000000 --- a/nixops/modules/dns.nix +++ /dev/null @@ -1,132 +0,0 @@ -{ lib, pkgs, config, myconfig, ... }: -{ - config = let - cfg = config.services.bind; - configFile = pkgs.writeText "named.conf" '' - include "/etc/bind/rndc.key"; - controls { - inet 127.0.0.1 allow {localhost;} keys {"rndc-key";}; - }; - - acl cachenetworks { ${lib.concatMapStrings (entry: " ${entry}; ") cfg.cacheNetworks} }; - acl badnetworks { ${lib.concatMapStrings (entry: " ${entry}; ") cfg.blockedNetworks} }; - - options { - listen-on { ${lib.concatMapStrings (entry: " ${entry}; ") cfg.listenOn} }; - listen-on-v6 { ${lib.concatMapStrings (entry: " ${entry}; ") cfg.listenOnIpv6} }; - allow-query { cachenetworks; }; - blackhole { badnetworks; }; - forward first; - forwarders { ${lib.concatMapStrings (entry: " ${entry}; ") cfg.forwarders} }; - directory "/var/run/named"; - pid-file "/var/run/named/named.pid"; - ${cfg.extraOptions} - }; - - ${cfg.extraConfig} - - ${ lib.concatMapStrings - ({ name, file, master ? true, extra ? "", slaves ? [], masters ? [] }: - '' - zone "${name}" { - type ${if master then "master" else "slave"}; - file "${file}"; - ${ if lib.lists.length slaves > 0 then - '' - allow-transfer { - ${lib.concatMapStrings (ip: "${ip};\n") slaves} - }; - '' else ""} - ${ if lib.lists.length masters > 0 then - '' - masters { - ${lib.concatMapStrings (ip: "${ip};\n") masters} - }; - '' else ""} - allow-query { any; }; - ${extra} - }; - '') - cfg.zones } - ''; - in - { - networking.firewall.allowedUDPPorts = [ 53 ]; - networking.firewall.allowedTCPPorts = [ 53 ]; - services.bind = { - enable = true; - cacheNetworks = ["any"]; - configFile = configFile; - extraOptions = '' - allow-recursion { 127.0.0.1; }; - allow-transfer { none; }; - - notify-source ${myconfig.env.servers.eldiron.ips.main.ip4}; - notify-source-v6 ${lib.head myconfig.env.servers.eldiron.ips.main.ip6}; - version none; - hostname none; - server-id none; - ''; - zones = with myconfig.env.dns; - assert (builtins.substring ((builtins.stringLength soa.email)-1) 1 soa.email) != "."; - assert (builtins.substring ((builtins.stringLength soa.primary)-1) 1 soa.primary) != "."; - (map (conf: { - name = conf.name; - master = false; - file = "/var/run/named/${conf.name}.zone"; - masters = if lib.attrsets.hasAttr "masters" conf - then lib.lists.flatten (map (n: lib.attrsets.attrValues ns.${n}) conf.masters) - else []; - }) slaveZones) - ++ (map (conf: { - name = conf.name; - master = true; - extra = if lib.attrsets.hasAttr "extra" conf then conf.extra else ""; - slaves = if lib.attrsets.hasAttr "slaves" conf - then lib.lists.flatten (map (n: lib.attrsets.attrValues ns.${n}) conf.slaves) - else []; - file = pkgs.writeText "${conf.name}.zone" '' - $TTL 10800 - @ IN SOA ${soa.primary}. ${builtins.replaceStrings ["@"] ["."] soa.email}. ${soa.serial} ${soa.refresh} ${soa.retry} ${soa.expire} ${soa.ttl} - - ${lib.concatStringsSep "\n" (map (x: "@ IN NS ${x}.") (lib.concatMap (n: lib.attrsets.mapAttrsToList (k: v: k) ns.${n}) conf.ns))} - - ${conf.entries} - - ${if lib.attrsets.hasAttr "withEmail" conf && lib.lists.length conf.withEmail > 0 then '' - mail IN A ${myconfig.env.servers.immaeEu.ips.main.ip4} - mx-1 IN A ${myconfig.env.servers.eldiron.ips.main.ip4} - ${builtins.concatStringsSep "\n" (map (i: "mail IN AAAA ${i}") myconfig.env.servers.immaeEu.ips.main.ip6)} - ${builtins.concatStringsSep "\n" (map (i: "mx-1 IN AAAA ${i}") myconfig.env.servers.eldiron.ips.main.ip6)} - ${lib.concatStringsSep "\n\n" (map (e: - let - n = if e.domain == "" then "@" else "${e.domain} "; - suffix = if e.domain == "" then "" else ".${e.domain}"; - in - '' - ; ------------------ mail: ${n} --------------------------- - ${if e.receive then "${n} IN MX 10 mail.${conf.name}." else ""} - ;${if e.receive then "${n} IN MX 50 mx-1.${conf.name}." else ""} - - ; Mail sender authentications - ${n} IN TXT "v=spf1 mx ~all" - _dmarc${suffix} IN TXT "v=DMARC1; p=none; adkim=r; aspf=r; fo=1; rua=mailto:postmaster+rua@immae.eu; ruf=mailto:postmaster+ruf@immae.eu;" - ${if e.send then '' - immae_eu._domainkey${suffix} IN TXT ( "v=DKIM1; k=rsa; s=email; " - "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzl3vLd8W5YAuumC5+ZT9OV7/14Pmh5JYtwyqKI3cfe9NnAqInt3xO4bZ7oqIxRKWN4SD39vm7O/QOvFdBt00ENOOzdP90s5gKw6eIP/4+vPTh0IWltAsmu9B2agzdtWUE7t2xFKIzEn8l9niRE2QYbVaqZv4sub98vY55fIgFoHtjkmNC7325S8fjDJGp6OPbyhAs6Xl5/adjF" - "0ko4Y2p6RaxLQfjlS0bxmK4Qg6C14pIXHtzVeqOuWrwApqt5+AULSn97iUtqV/IJlEEjC6DUR44t3C/G0G/k46iFclCqRRi0hdPrOHCtZDbtMubnTN9eaUiNpkXh1WnCflHwtjQwIDAQAB" ) - '' else ""} - '') conf.withEmail)} - '' + (if conf.name == "immae.eu" then '' - ; ----------------- Accept DMARC reports ------------------- - ${lib.concatStringsSep "\n" ( - lib.flatten ( - map (z: map (e: "${e.domain}${if builtins.stringLength e.domain > 0 then "." else ""}${z.name}._report._dmarc IN TXT \"v=DMARC1;\"") (z.withEmail or [])) masterZones - ) - )} - '' else "") else ""} - ''; - }) masterZones); - }; - }; -} diff --git a/nixops/modules/ftp.nix b/nixops/modules/ftp.nix deleted file mode 100644 index 842d2d6..0000000 --- a/nixops/modules/ftp.nix +++ /dev/null @@ -1,118 +0,0 @@ -{ lib, pkgs, config, myconfig, ... }: -{ - options = { - services.pure-ftpd.enable = lib.mkOption { - type = lib.types.bool; - default = false; - description = '' - Whether to enable pure-ftpd. - ''; - }; - }; - - config = lib.mkIf config.services.pure-ftpd.enable { - security.acme.certs."ftp" = config.services.myCertificates.certConfig // { - domain = "eldiron.immae.eu"; - postRun = '' - systemctl restart pure-ftpd.service - ''; - extraDomains = { "ftp.immae.eu" = null; }; - }; - - networking = { - firewall = { - allowedTCPPorts = [ 21 ]; - allowedTCPPortRanges = [ { from = 40000; to = 50000; } ]; - }; - }; - - users.users = [ - { - name = "ftp"; - uid = config.ids.uids.ftp; # 8 - group = "ftp"; - description = "Anonymous FTP user"; - home = "/homeless-shelter"; - extraGroups = [ "keys" ]; - } - ]; - - users.groups.ftp.gid = config.ids.gids.ftp; - - system.activationScripts.pure-ftpd = '' - install -m 0755 -o ftp -g ftp -d /var/lib/ftp - ''; - - secrets.keys = [{ - dest = "pure-ftpd-ldap"; - permissions = "0400"; - user = "ftp"; - group = "ftp"; - text = '' - LDAPServer ${myconfig.env.ftp.ldap.host} - LDAPPort 389 - LDAPUseTLS True - LDAPBaseDN ${myconfig.env.ftp.ldap.base} - LDAPBindDN ${myconfig.env.ftp.ldap.dn} - LDAPBindPW ${myconfig.env.ftp.ldap.password} - LDAPDefaultUID 500 - LDAPForceDefaultUID False - LDAPDefaultGID 100 - LDAPForceDefaultGID False - LDAPFilter ${myconfig.env.ftp.ldap.filter} - - LDAPAuthMethod BIND - - # Pas de possibilite de donner l'Uid/Gid ! - # Compile dans pure-ftpd directement avec immaeFtpUid / immaeFtpGid - LDAPHomeDir immaeFtpDirectory - ''; - }]; - - systemd.services.pure-ftpd = let - configFile = pkgs.writeText "pure-ftpd.conf" '' - PassivePortRange 40000 50000 - ChrootEveryone yes - CreateHomeDir yes - BrokenClientsCompatibility yes - MaxClientsNumber 50 - Daemonize yes - MaxClientsPerIP 8 - VerboseLog no - DisplayDotFiles yes - AnonymousOnly no - NoAnonymous no - SyslogFacility ftp - DontResolve yes - MaxIdleTime 15 - LDAPConfigFile /var/secrets/pure-ftpd-ldap - LimitRecursion 10000 8 - AnonymousCanCreateDirs no - MaxLoad 4 - AntiWarez yes - Umask 133:022 - # ftp - MinUID 8 - AllowUserFXP no - AllowAnonymousFXP no - ProhibitDotFilesWrite no - ProhibitDotFilesRead no - AutoRename no - AnonymousCantUpload no - MaxDiskUsage 99 - CustomerProof yes - TLS 1 - CertFile ${config.security.acme.directory}/ftp/full.pem - ''; - in { - description = "Pure-FTPd server"; - wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ]; - - serviceConfig.ExecStart = "${pkgs.pure-ftpd}/bin/pure-ftpd ${configFile}"; - serviceConfig.Type = "forking"; - serviceConfig.PIDFile = "/run/pure-ftpd.pid"; - }; - }; - -} diff --git a/nixops/modules/gitolite/default.nix b/nixops/modules/gitolite/default.nix deleted file mode 100644 index f085b55..0000000 --- a/nixops/modules/gitolite/default.nix +++ /dev/null @@ -1,63 +0,0 @@ -{ lib, pkgs, config, myconfig, ... }: -let - cfg = config.services.myGitolite; -in { - options.services.myGitolite = { - enable = lib.mkEnableOption "my gitolite service"; - gitoliteDir = lib.mkOption { - type = lib.types.string; - default = "/var/lib/gitolite"; - }; - }; - - config = lib.mkIf cfg.enable { - networking.firewall.allowedTCPPorts = [ 9418 ]; - - services.gitDaemon = { - enable = true; - user = "gitolite"; - group = "gitolite"; - basePath = "${cfg.gitoliteDir}/repositories"; - }; - - system.activationScripts.gitolite = let - gitolite_ldap_groups = pkgs.mylibs.wrap { - name = "gitolite_ldap_groups.sh"; - file = ./gitolite_ldap_groups.sh; - vars = { - LDAP_PASS = myconfig.env.tools.gitolite.ldap.password; - }; - paths = [ pkgs.openldap pkgs.stdenv.shellPackage pkgs.gnugrep pkgs.coreutils ]; - }; - in { - deps = [ "users" ]; - text = '' - if [ -d ${cfg.gitoliteDir} ]; then - ln -sf ${gitolite_ldap_groups} ${cfg.gitoliteDir}/gitolite_ldap_groups.sh - chmod g+rx ${cfg.gitoliteDir} - fi - if [ -f ${cfg.gitoliteDir}/projects.list ]; then - chmod g+r ${cfg.gitoliteDir}/projects.list - fi - ''; - }; - - users.users.wwwrun.extraGroups = [ "gitolite" ]; - - users.users.gitolite.packages = let - python-packages = python-packages: with python-packages; [ - simplejson - urllib3 - sleekxmpp - ]; - in - [ - (pkgs.python3.withPackages python-packages) - ]; - # Installation: https://git.immae.eu/mantisbt/view.php?id=93 - services.gitolite = { - enable = true; - adminPubkey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDXqRbiHw7QoHADNIEuo4nUT9fSOIEBMdJZH0bkQAxXyJFyCM1IMz0pxsHV0wu9tdkkr36bPEUj2aV5bkYLBN6nxcV2Y49X8bjOSCPfx3n6Own1h+NeZVBj4ZByrFmqCbTxUJIZ2bZKcWOFncML39VmWdsVhNjg0X4NBBehqXRIKr2gt3E/ESAxTYJFm0BnU0baciw9cN0bsRGqvFgf5h2P48CIAfwhVcGmPQnnAwabnosYQzRWxR0OygH5Kd8mePh6FheIRIigfXsDO8f/jdxwut8buvNIf3m5EBr3tUbTsvM+eV3M5vKGt7sk8T64DVtepTSdOOWtp+47ktsnHOMh immae@immae.eu"; - }; - }; -} diff --git a/nixops/modules/gitolite/gitolite_ldap_groups.sh b/nixops/modules/gitolite/gitolite_ldap_groups.sh deleted file mode 100755 index 7db0da4..0000000 --- a/nixops/modules/gitolite/gitolite_ldap_groups.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash - -uid_param="$1" -ldap_host="ldap.immae.eu" -ldap_binddn="cn=gitolite,ou=services,dc=immae,dc=eu" -ldap_bindpw="$LDAP_PASS" -ldap_searchbase="dc=immae,dc=eu" -ldap_scope="subtree" - -ldap_options="-h ${ldap_host} -ZZ -x -D ${ldap_binddn} -w ${ldap_bindpw} -b ${ldap_searchbase} -s ${ldap_scope}" - -ldap_filter="(&(memberOf=cn=groups,cn=gitolite,ou=services,dc=immae,dc=eu)(|(member=uid=${uid_param},ou=users,dc=immae,dc=eu)(member=uid=${uid_param},ou=group_users,dc=immae,dc=eu)))" -ldap_result=$(ldapsearch ${ldap_options} -LLL "${ldap_filter}" cn | grep 'cn:' | cut -d' ' -f2) - -echo "$ldap_result" diff --git a/nixops/modules/mail.nix b/nixops/modules/mail.nix deleted file mode 100644 index 611c8b4..0000000 --- a/nixops/modules/mail.nix +++ /dev/null @@ -1,13 +0,0 @@ -{ lib, pkgs, config, myconfig, ... }: -{ - config.users.users.nullmailer.uid = config.ids.uids.nullmailer; - config.users.groups.nullmailer.gid = config.ids.gids.nullmailer; - - config.services.nullmailer = { - enable = true; - config = { - me = myconfig.env.mail.host; - remotes = "${myconfig.env.mail.relay} smtp"; - }; - }; -} diff --git a/nixops/modules/mpd.nix b/nixops/modules/mpd.nix deleted file mode 100644 index 9903bdf..0000000 --- a/nixops/modules/mpd.nix +++ /dev/null @@ -1,56 +0,0 @@ -{ lib, pkgs, config, myconfig, ... }: -{ - config = { - secrets.keys = [ - { - dest = "mpd"; - permissions = "0400"; - text = myconfig.env.mpd.password; - } - { - dest = "mpd-config"; - permissions = "0400"; - user = "mpd"; - group = "mpd"; - text = '' - password "${myconfig.env.mpd.password}@read,add,control,admin" - ''; - } - ]; - networking.firewall.allowedTCPPorts = [ 6600 ]; - users.users.mpd.extraGroups = [ "wwwrun" "keys" ]; - systemd.services.mpd.serviceConfig.RuntimeDirectory = "mpd"; - services.mpd = { - enable = true; - network.listenAddress = "any"; - musicDirectory = myconfig.env.mpd.folder; - extraConfig = '' - include "/var/secrets/mpd-config" - audio_output { - type "null" - name "No Output" - mixer_type "none" - } - audio_output { - type "httpd" - name "OGG" - encoder "vorbis" - bind_to_address "/run/mpd/ogg.sock" - quality "5.0" - format "44100:16:1" - } - audio_output { - type "httpd" - name "MP3" - encoder "lame" - bind_to_address "/run/mpd/mp3.sock" - quality "5.0" - format "44100:16:1" - } - - - ''; - }; - }; -} - diff --git a/nixops/modules/pub/default.nix b/nixops/modules/pub/default.nix deleted file mode 100644 index cdc68db..0000000 --- a/nixops/modules/pub/default.nix +++ /dev/null @@ -1,52 +0,0 @@ -{ lib, pkgs, config, myconfig, ... }: -{ - options = { - services.pub.enable = lib.mkOption { - type = lib.types.bool; - default = false; - description = '' - Whether to enable pub user. - ''; - }; - }; - - config = lib.mkIf config.services.pub.enable { - users.users.pub = let - restrict = pkgs.runCommand "restrict" { - file = ./restrict; - buildInputs = [ pkgs.makeWrapper ]; - } '' - mkdir -p $out/bin - cp $file $out/bin/restrict - chmod a+x $out/bin/restrict - patchShebangs $out/bin/restrict - wrapProgram $out/bin/restrict \ - --prefix PATH : ${lib.makeBinPath [ pkgs.bubblewrap pkgs.rrsync ]} \ - --set TMUX_RESTRICT ${./tmux.restrict.conf} - ''; - purple-hangouts = pkgs.purple-hangouts.overrideAttrs(old: { - installPhase = '' - install -Dm755 -t $out/lib/purple-2/ libhangouts.so - for size in 16 22 24 48; do - install -TDm644 hangouts$size.png $out/share/pixmaps/pidgin/protocols/$size/hangouts.png - done - ''; - }); - in { - createHome = true; - description = "Restricted shell user"; - home = "/var/lib/pub"; - uid = myconfig.env.users.pub.uid; - useDefaultShell = true; - packages = [ - restrict - pkgs.tmux - (pkgs.pidgin.override { plugins = [ - pkgs.purple-plugin-pack purple-hangouts - pkgs.purple-discord pkgs.purple-facebook - pkgs.telegram-purple - ]; }) - ]; - }; - }; -} diff --git a/nixops/modules/pub/restrict b/nixops/modules/pub/restrict deleted file mode 100644 index b2f3be3..0000000 --- a/nixops/modules/pub/restrict +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env bash -user="$1" -rootuser="$HOME/$user/" -mkdir -p $rootuser - -orig="$SSH_ORIGINAL_COMMAND" -if [ -z "$orig" ]; then - orig="/bin/bash -l" -fi -if [ "${orig:0:7}" = "command" ]; then - orig="${orig:8}" -fi - -case "$orig" in -rsync*) - rrsync $HOME/$user/ - ;; -*) - nix_store_paths() { - nix-store -q -R \ - /run/current-system/sw \ - /etc/profiles/per-user/pub \ - /etc/ssl/certs/ca-bundle.crt \ - | while read i; do - printf '%s--ro-bind\0'$i'\0'$i'\0' '' - done - } - - set -euo pipefail - (exec -c bwrap --ro-bind /usr /usr \ - --args 10 \ - --dir /tmp \ - --dir /var \ - --symlink ../tmp var/tmp \ - --proc /proc \ - --dev /dev \ - --ro-bind /etc/resolv.conf /etc/resolv.conf \ - --ro-bind /etc/zoneinfo /etc/zoneinfo \ - --ro-bind /etc/ssl /etc/ssl \ - --ro-bind /etc/static/ssl/certs /etc/static/ssl/certs \ - --ro-bind /run/current-system/sw/lib/locale/locale-archive /etc/locale-archive \ - --ro-bind /run/current-system/sw/bin /bin \ - --ro-bind /etc/profiles/per-user/pub/bin /bin-pub \ - --bind /var/lib/pub/$user /var/lib/pub \ - --dir /var/lib/commons \ - --ro-bind $TMUX_RESTRICT /var/lib/commons/tmux.restrict.conf \ - --chdir /var/lib/pub \ - --unshare-all \ - --share-net \ - --dir /run/user/$(id -u) \ - --setenv TERM "$TERM" \ - --setenv LOCALE_ARCHIVE "/etc/locale-archive" \ - --setenv XDG_RUNTIME_DIR "/run/user/`id -u`" \ - --setenv PS1 "$user@pub $ " \ - --setenv PATH "/bin:/bin-pub" \ - --setenv HOME "/var/lib/pub" \ - --file 11 /etc/passwd \ - --file 12 /etc/group \ - -- $orig) \ - 10< <(nix_store_paths) \ - 11< <(getent passwd $UID 65534) \ - 12< <(getent group $(id -g) 65534) - ;; -esac diff --git a/nixops/modules/pub/tmux.restrict.conf b/nixops/modules/pub/tmux.restrict.conf deleted file mode 100644 index 5aefd1c..0000000 --- a/nixops/modules/pub/tmux.restrict.conf +++ /dev/null @@ -1,43 +0,0 @@ -# Pour les nostalgiques de screen -# comme les raccourcis ne sont pas les mêmes, j'évite -set -g prefix C-a -unbind-key C-b - -unbind-key -a -bind-key -n C-h list-keys -bind-key C-d detach -bind-key & confirm-before -p "kill-window #W? (y/n)" kill-window - -# même hack que sur screen lorsqu'on veut profiter du scroll du terminal -# (xterm ...) -set -g terminal-overrides 'xterm*:smcup@:rmcup@' - -#Pour les ctrl+arrow -set-option -g xterm-keys on - -# c'est un minimum (defaut 2000) -set-option -g history-limit 10000 - -# lorsque j'ai encore un tmux ailleurs seule -# sa fenetre active réduit la taille de ma fenetre locale -setw -g aggressive-resize on - -# Pour etre alerté sur un changement dans une autre fenêtre -setw -g monitor-activity on -#set -g visual-activity on -#set -g visual-bell on - -set -g base-index 1 - -# repercuter le contenu de la fenetre dans la barre de titre -# reference des string : man tmux (status-left) -set -g set-titles on -set -g set-titles-string '#H #W #T' # host window command - -#Dans les valeurs par defaut deja, avec le ssh-agent -set -g update-environment "DISPLAY SSH_ASKPASS SSH_AUTH_SOCK SSH_AGENT_PID SSH_CONNECTION WINDOWID XAUTHORITY PATH" - -set -g status off -set -g status-left '' -set -g status-right '' - diff --git a/nixops/modules/ssh/default.nix b/nixops/modules/ssh/default.nix deleted file mode 100644 index beedaff..0000000 --- a/nixops/modules/ssh/default.nix +++ /dev/null @@ -1,40 +0,0 @@ -{ lib, pkgs, config, myconfig, ... }: -{ - config = { - networking.firewall.allowedTCPPorts = [ 22 ]; - - services.openssh.extraConfig = '' - AuthorizedKeysCommand /etc/ssh/ldap_authorized_keys - AuthorizedKeysCommandUser nobody - ''; - - secrets.keys = [{ - dest = "ssh-ldap"; - user = "nobody"; - group = "nogroup"; - permissions = "0400"; - text = myconfig.env.sshd.ldap.password; - }]; - system.activationScripts.sshd = { - deps = [ "secrets" ]; - text = '' - install -Dm400 -o nobody -g nogroup -T /var/secrets/ssh-ldap /etc/ssh/ldap_password - ''; - }; - # ssh is strict about parent directory having correct rights, don't - # move it in the nix store. - environment.etc."ssh/ldap_authorized_keys" = let - ldap_authorized_keys = - pkgs.mylibs.wrap { - name = "ldap_authorized_keys"; - file = ./ldap_authorized_keys.sh; - paths = [ pkgs.which pkgs.gitolite pkgs.openldap pkgs.stdenv.shellPackage pkgs.gnugrep pkgs.gnused pkgs.coreutils ]; - }; - in { - enable = true; - mode = "0755"; - user = "root"; - source = ldap_authorized_keys; - }; - }; -} diff --git a/nixops/modules/ssh/ldap_authorized_keys.sh b/nixops/modules/ssh/ldap_authorized_keys.sh deleted file mode 100755 index d556452..0000000 --- a/nixops/modules/ssh/ldap_authorized_keys.sh +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/env bash - -LDAPSEARCH=ldapsearch -KEY="immaeSshKey" -LDAP_BIND="cn=ssh,ou=services,dc=immae,dc=eu" -LDAP_PASS=$(cat /etc/ssh/ldap_password) -LDAP_HOST="ldap.immae.eu" -LDAP_MEMBER="cn=users,cn=ssh,ou=services,dc=immae,dc=eu" -LDAP_GITOLITE_MEMBER="cn=users,cn=gitolite,ou=services,dc=immae,dc=eu" -LDAP_PUB_RESTRICT_MEMBER="cn=restrict,cn=pub,ou=services,dc=immae,dc=eu" -LDAP_PUB_FORWARD_MEMBER="cn=forward,cn=pub,ou=services,dc=immae,dc=eu" -LDAP_BASE="dc=immae,dc=eu" -GITOLITE_SHELL=$(which gitolite-shell) -ECHO=$(which echo) - -suitable_for() { - type_for="$1" - key="$2" - - if [[ $key != *$'\n'* ]] && [[ $key == ssh-* ]]; then - echo "$key" - else - key_type=$(cut -d " " -f 1 <<< "$key") - - if grep -q "\b-$type_for\b" <<< "$key_type"; then - echo "" - elif grep -q "\b$type_for\b" <<< "$key_type"; then - echo $(sed -e "s/^[^ ]* //g" <<< "$key") - else - echo "" - fi - fi -} - -clean_key_line() { - type_for="$1" - line="$2" - - if [[ "$line" == $KEY::* ]]; then - # base64 keys should't happen, unless wrong copy-pasting - key="" - else - key=$(sed -e "s/^$KEY: *//" -e "s/ *$//" <<< "$line") - fi - - suitable_for "$type_for" "$key" -} - -ldap_search() { - $LDAPSEARCH -h $LDAP_HOST -ZZ -b $LDAP_BASE -D $LDAP_BIND -w "$LDAP_PASS" -x -o ldif-wrap=no -LLL "$@" -} - -ldap_keys() { - user=$1; - if [[ $user == gitolite ]]; then - ldap_search '(&(memberOf='$LDAP_GITOLITE_MEMBER')('$KEY'=*))' $KEY | \ - while read line ; - do - if [ ! -z "$line" ]; then - if [[ $line == dn* ]]; then - user=$(sed -n 's/.*uid=\([^,]*\).*/\1/p' <<< "$line") - if [ -n "$user" ]; then - if [[ $user == "immae" ]] || [[ $user == "denise" ]]; then - # Capitalize first letter (backward compatibility) - user=$(sed -r 's/^([a-z])/\U\1/' <<< "$user") - fi - else - # Service fake user - user=$(sed -n 's/.*cn=\([^,]*\).*/\1/p' <<< "$line") - fi - elif [[ $line == $KEY* ]]; then - key=$(clean_key_line git "$line") - if [ ! -z "$key" ]; then - if [[ $key != *$'\n'* ]] && [[ $key == ssh-* ]]; then - echo -n 'command="'$GITOLITE_SHELL' '$user'",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ' - echo $key - fi - fi - fi - fi - done - exit 0 - elif [[ $user == pub ]]; then - ldap_search '(&(memberOf='$LDAP_PUB_RESTRICT_MEMBER')('$KEY'=*))' $KEY | \ - while read line ; - do - if [ ! -z "$line" ]; then - if [[ $line == dn* ]]; then - echo "" - user=$(sed -n 's/.*uid=\([^,]*\).*/\1/p' <<< "$line") - echo "# $user" - elif [[ $line == $KEY* ]]; then - key=$(clean_key_line pub "$line") - key_forward=$(clean_key_line forward "$line") - if [ ! -z "$key" ]; then - if [[ $key != *$'\n'* ]] && [[ $key == ssh-* ]]; then - echo -n 'command="/etc/profiles/per-user/pub/bin/restrict '$user'" ' - echo $key - fi - elif [ ! -z "$key_forward" ]; then - if [[ $key_forward != *$'\n'* ]] && [[ $key_forward == ssh-* ]]; then - echo "# forward only" - echo -n 'no-pty,no-X11-forwarding,command="'$ECHO' forward only" ' - echo $key_forward - fi - fi - fi - fi - done - - echo "" - ldap_search '(&(memberOf='$LDAP_PUB_FORWARD_MEMBER')('$KEY'=*))' $KEY | \ - while read line ; - do - if [ ! -z "$line" ]; then - if [[ $line == dn* ]]; then - echo "" - user=$(sed -n 's/.*uid=\([^,]*\).*/\1/p' <<< "$line") - echo "# $user" - elif [[ $line == $KEY* ]]; then - key=$(clean_key_line forward "$line") - if [ ! -z "$key" ]; then - if [[ $key != *$'\n'* ]] && [[ $key == ssh-* ]]; then - echo -n 'no-pty,no-X11-forwarding,command="'$ECHO' forward only" ' - echo $key - fi - fi - fi - fi - done - exit 0 - else - ldap_search '(&(memberOf='$LDAP_MEMBER')('$KEY'=*)(uid='$user'))' $KEY | \ - while read line ; - do - if [ ! -z "$line" ]; then - if [[ $line == dn* ]]; then - user=$(sed -n 's/.*uid=\([^,]*\).*/\1/p' <<< "$line") - elif [[ $line == $KEY* ]]; then - key=$(clean_key_line ssh "$line") - if [ ! -z "$key" ]; then - if [[ $key != *$'\n'* ]] && [[ $key == ssh-* ]]; then - echo $key - fi - fi - fi - fi - done - fi -} - -ldap_keys $@ diff --git a/nixops/modules/task/default.nix b/nixops/modules/task/default.nix deleted file mode 100644 index 9aeaa3f..0000000 --- a/nixops/modules/task/default.nix +++ /dev/null @@ -1,327 +0,0 @@ -{ lib, pkgs, config, myconfig, ... }: -let - cfg = config.services.myTasks; - server_vardir = config.services.taskserver.dataDir; - fqdn = "task.immae.eu"; - user = config.services.taskserver.user; - env = myconfig.env.tools.task; - group = config.services.taskserver.group; - taskserver-user-certs = pkgs.runCommand "taskserver-user-certs" {} '' - mkdir -p $out/bin - cat > $out/bin/taskserver-user-certs <<"EOF" - #!/usr/bin/env bash - - user=$1 - - silent_certtool() { - if ! output="$("${pkgs.gnutls.bin}/bin/certtool" "$@" 2>&1)"; then - echo "GNUTLS certtool invocation failed with output:" >&2 - echo "$output" >&2 - fi - } - - silent_certtool -p \ - --bits 4096 \ - --outfile "${server_vardir}/userkeys/$user.key.pem" - ${pkgs.gnused}/bin/sed -i -n -e '/^-----BEGIN RSA PRIVATE KEY-----$/,$p' "${server_vardir}/userkeys/$user.key.pem" - - silent_certtool -c \ - --template "${pkgs.writeText "taskserver-ca.template" '' - tls_www_client - encryption_key - signing_key - expiration_days = 3650 - ''}" \ - --load-ca-certificate "${server_vardir}/keys/ca.cert" \ - --load-ca-privkey "${server_vardir}/keys/ca.key" \ - --load-privkey "${server_vardir}/userkeys/$user.key.pem" \ - --outfile "${server_vardir}/userkeys/$user.cert.pem" - EOF - chmod a+x $out/bin/taskserver-user-certs - patchShebangs $out/bin/taskserver-user-certs - ''; - taskwarrior-web = pkgs.webapps.taskwarrior-web; - socketsDir = "/run/taskwarrior-web"; - varDir = "/var/lib/taskwarrior-web"; - taskwebPages = let - uidPages = lib.attrsets.zipAttrs ( - lib.lists.flatten - (lib.attrsets.mapAttrsToList (k: c: map (v: { "${v}" = k; }) c.uid) env.taskwarrior-web) - ); - pages = lib.attrsets.mapAttrs (uid: items: - if lib.lists.length items == 1 then - '' - - - - - - - '' - else - '' - - - To-do list disponibles - - - - -
    - ${builtins.concatStringsSep "\n" (map (item: "
  • ${item}
  • ") items)} -
- - - '' - ) uidPages; - in - pkgs.runCommand "taskwerver-pages" {} '' - mkdir -p $out/ - ${builtins.concatStringsSep "\n" (lib.attrsets.mapAttrsToList (k: v: "cp ${pkgs.writeText k v} $out/${k}.html") pages)} - echo "Please login" > $out/index.html - ''; -in { - options.services.myTasks = { - enable = lib.mkEnableOption "my tasks service"; - }; - - config = lib.mkIf cfg.enable { - secrets.keys = [{ - dest = "webapps/tools-taskwarrior-web"; - user = "wwwrun"; - group = "wwwrun"; - permissions = "0400"; - text = '' - SetEnv TASKD_HOST "${fqdn}:${toString config.services.taskserver.listenPort}" - SetEnv TASKD_VARDIR "${server_vardir}" - SetEnv TASKD_LDAP_HOST "ldaps://${env.ldap.host}" - SetEnv TASKD_LDAP_DN "${env.ldap.dn}" - SetEnv TASKD_LDAP_PASSWORD "${env.ldap.password}" - SetEnv TASKD_LDAP_BASE "${env.ldap.base}" - SetEnv TASKD_LDAP_FILTER "${env.ldap.search}" - ''; - }]; - services.websites.tools.modules = [ "proxy_fcgi" "sed" ]; - services.websites.tools.vhostConfs.task = { - certName = "eldiron"; - addToCerts = true; - hosts = [ "task.immae.eu" ]; - root = "/run/current-system/webapps/_task"; - extraConfig = [ '' - - DirectoryIndex index.php - Use LDAPConnect - Require ldap-group cn=users,cn=taskwarrior,ou=services,dc=immae,dc=eu - - SetHandler "proxy:unix:/var/run/phpfpm/task.sock|fcgi://localhost" - - Include /var/secrets/webapps/tools-taskwarrior-web - - '' - '' - - ProxyPass "unix://${socketsDir}/%{folderName}.sock|http://localhost-%{folderName}/" - ProxyPassReverse "unix://${socketsDir}/%{folderName}.sock|http://localhost-%{folderName}/" - ProxyPassReverse http://${fqdn}/ - - SetOutputFilter Sed - OutputSed "s|/ajax|/taskweb/%{folderName}/ajax|g" - OutputSed "s|\([^x]\)/tasks|\1/taskweb/%{folderName}/tasks|g" - OutputSed "s|\([^x]\)/projects|\1/taskweb/%{folderName}/projects|g" - OutputSed "s|http://${fqdn}/|/taskweb/%{folderName}/|g" - OutputSed "s|/img/relax.jpg|/taskweb/%{folderName}/img/relax.jpg|g" - - '' - '' - Alias /taskweb ${taskwebPages} - - DirectoryIndex index.html - Require all granted - - - RewriteEngine on - RewriteRule ^/taskweb$ /taskweb/ [R=301,L] - RedirectMatch permanent ^/taskweb/([^/]+)$ /taskweb/$1/ - - RewriteCond %{LA-U:REMOTE_USER} !="" - RewriteCond ${taskwebPages}/%{LA-U:REMOTE_USER}.html -f - RewriteRule ^/taskweb/?$ ${taskwebPages}/%{LA-U:REMOTE_USER}.html [L] - - - Use LDAPConnect - Require ldap-group cn=users,cn=taskwarrior,ou=services,dc=immae,dc=eu - - '' - ] ++ (lib.attrsets.mapAttrsToList (k: v: '' - - ${builtins.concatStringsSep "\n" (map (uid: "Require ldap-attribute uid=${uid}") v.uid)} - - Use Taskwarrior ${k} - - '') env.taskwarrior-web); - }; - services.phpfpm.poolConfigs = { - tasks = '' - listen = /var/run/phpfpm/task.sock - user = ${user} - group = ${group} - listen.owner = wwwrun - listen.group = wwwrun - pm = dynamic - pm.max_children = 60 - pm.start_servers = 2 - pm.min_spare_servers = 1 - pm.max_spare_servers = 10 - - ; Needed to avoid clashes in browser cookies (same domain) - env[PATH] = "/etc/profiles/per-user/${user}/bin" - php_value[session.name] = TaskPHPSESSID - php_admin_value[open_basedir] = "${./www}:/tmp:${server_vardir}:/etc/profiles/per-user/${user}/bin/" - ''; - }; - - myServices.websites.webappDirs._task = ./www; - - security.acme.certs."task" = config.services.myCertificates.certConfig // { - inherit user group; - plugins = [ "fullchain.pem" "key.pem" "cert.pem" "account_key.json" ]; - domain = fqdn; - postRun = '' - systemctl restart taskserver.service - ''; - }; - - users.users.${user}.packages = [ taskserver-user-certs ]; - - system.activationScripts.taskserver = { - deps = [ "users" ]; - text = '' - install -m 0750 -o ${user} -g ${group} -d ${server_vardir} - install -m 0750 -o ${user} -g ${group} -d ${server_vardir}/userkeys - install -m 0750 -o ${user} -g ${group} -d ${server_vardir}/keys - - if [ ! -e "${server_vardir}/keys/ca.key" ]; then - silent_certtool() { - if ! output="$("${pkgs.gnutls.bin}/bin/certtool" "$@" 2>&1)"; then - echo "GNUTLS certtool invocation failed with output:" >&2 - echo "$output" >&2 - fi - } - - silent_certtool -p \ - --bits 4096 \ - --outfile "${server_vardir}/keys/ca.key" - - silent_certtool -s \ - --template "${pkgs.writeText "taskserver-ca.template" '' - cn = ${fqdn} - expiration_days = -1 - cert_signing_key - ca - ''}" \ - --load-privkey "${server_vardir}/keys/ca.key" \ - --outfile "${server_vardir}/keys/ca.cert" - - chown :${group} "${server_vardir}/keys/ca.key" - chmod g+r "${server_vardir}/keys/ca.key" - fi - ''; - }; - - services.taskserver = { - enable = true; - allowedClientIDs = [ "^task [2-9]" "^Mirakel [1-9]" ]; - inherit fqdn; - listenHost = "::"; - pki.manual.ca.cert = "${server_vardir}/keys/ca.cert"; - pki.manual.server.cert = "${config.security.acme.directory}/task/fullchain.pem"; - pki.manual.server.crl = "${config.security.acme.directory}/task/invalid.crl"; - pki.manual.server.key = "${config.security.acme.directory}/task/key.pem"; - requestLimit = 104857600; - }; - - system.activationScripts.taskwarrior-web = { - deps = [ "users" ]; - text = '' - if [ ! -f ${server_vardir}/userkeys/taskwarrior-web.cert.pem ]; then - ${taskserver-user-certs}/bin/taskserver-user-certs taskwarrior-web - chown taskd:taskd ${server_vardir}/userkeys/taskwarrior-web.cert.pem ${server_vardir}/userkeys/taskwarrior-web.key.pem - fi - ''; - }; - - systemd.services = (lib.attrsets.mapAttrs' (name: userConfig: - let - credentials = "${userConfig.org}/${name}/${userConfig.key}"; - dateFormat = userConfig.date; - taskrc = pkgs.writeText "taskrc" '' - data.location=${varDir}/${name} - taskd.certificate=${server_vardir}/userkeys/taskwarrior-web.cert.pem - taskd.key=${server_vardir}/userkeys/taskwarrior-web.key.pem - # IdenTrust DST Root CA X3 - # obtained here: https://letsencrypt.org/fr/certificates/ - taskd.ca=${pkgs.writeText "ca.cert" '' - -----BEGIN CERTIFICATE----- - MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ - MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT - DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow - PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD - Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB - AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O - rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq - OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b - xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw - 7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD - aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV - HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG - SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 - ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr - AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz - R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 - JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo - Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ - -----END CERTIFICATE-----''} - taskd.server=${fqdn}:${toString config.services.taskserver.listenPort} - taskd.credentials=${credentials} - dateformat=${dateFormat} - ''; - in lib.attrsets.nameValuePair "taskwarrior-web-${name}" { - description = "Taskwarrior webapp for ${name}"; - wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ]; - path = [ pkgs.taskwarrior ]; - - environment.TASKRC = taskrc; - environment.BUNDLE_PATH = "${taskwarrior-web.gems}/${taskwarrior-web.gems.ruby.gemPath}"; - environment.BUNDLE_GEMFILE = "${taskwarrior-web.gems.confFiles}/Gemfile"; - environment.LC_ALL = "fr_FR.UTF-8"; - - script = '' - exec ${taskwarrior-web.gems}/${taskwarrior-web.gems.ruby.gemPath}/bin/bundle exec thin start -R config.ru -S ${socketsDir}/${name}.sock - ''; - - serviceConfig = { - User = user; - PrivateTmp = true; - Restart = "always"; - TimeoutSec = 60; - Type = "simple"; - WorkingDirectory = taskwarrior-web; - StateDirectoryMode = 0750; - StateDirectory = assert lib.strings.hasPrefix "/var/lib/" varDir; - (lib.strings.removePrefix "/var/lib/" varDir + "/${name}"); - RuntimeDirectoryPreserve = "yes"; - RuntimeDirectory = assert lib.strings.hasPrefix "/run/" socketsDir; - lib.strings.removePrefix "/run/" socketsDir; - }; - - unitConfig.RequiresMountsFor = varDir; - }) env.taskwarrior-web) // { - taskserver-ca.postStart = '' - chown :${group} "${server_vardir}/keys/ca.key" - chmod g+r "${server_vardir}/keys/ca.key" - ''; - }; - - }; -} diff --git a/nixops/modules/task/www/index.php b/nixops/modules/task/www/index.php deleted file mode 100644 index deaf8af..0000000 --- a/nixops/modules/task/www/index.php +++ /dev/null @@ -1,157 +0,0 @@ - $value) { - if ($key !== "count") { - $entries[] = explode(":", $value); - } -} - -if (isset($_GET["file"])) { - $basecert = $vardir . "/userkeys/" . $ldap_user; - if (!file_exists($basecert . ".cert.pem")) { - exec("taskserver-user-certs $ldap_user"); - } - $certificate = file_get_contents($basecert . ".cert.pem"); - $cert_key = file_get_contents($basecert . ".key.pem"); - - // IdenTrust DST Root CA X3 - // obtained here: https://letsencrypt.org/fr/certificates/ - $server_cert = "-----BEGIN CERTIFICATE----- -MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ -MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT -DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow -PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD -Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O -rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq -OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b -xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw -7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD -aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV -HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG -SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 -ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr -AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz -R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 -JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo -Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ ------END CERTIFICATE-----"; - - $file = $_GET["file"]; - switch($file) { - case "ca.cert.pem": - $content = $server_cert; - $name = "ca.cert.pem"; - $type = "application/x-x509-ca-cert"; - break; - case "cert.pem": - $content = $certificate; - $name = $ldap_user . ".cert.pem"; - $type = "application/x-x509-ca-cert"; - break; - case "key.pem": - $content = $cert_key; - $name = $ldap_user . ".key.pem"; - $type = "application/x-x509-ca-cert"; - break; - case "mirakel"; - foreach ($entries as $entry) { - list($org, $user, $key) = $entry; - if ($key == $_GET["key"]) { break; } - } - $name = $user . ".mirakel"; - $type = "text/plain"; - $content = "username: $user -org: $org -user key: $key -server: $host -client.cert: -$certificate -Client.key: -$cert_key -ca.cert: -$server_cert -"; - break; - default: - die("invalid file name"); - break; - } - - header("Content-Type: $type"); - header('Content-Disposition: attachment; filename="' . $name . '"'); - header('Content-Transfer-Encoding: binary'); - header('Accept-Ranges: bytes'); - header('Cache-Control: private'); - header('Pragma: private'); - echo $content; - exit; -} -?> - -
- Taskwarrior configuration -
- - -For command line interface, download the files, put them near your Taskwarrior -configuration files, and add that to your Taskwarrior configuration: -
-taskd.certificate=/path/to/.cert.pem
-taskd.key=/path/to/.key.pem
-taskd.server=
- 1) {
-  echo "# Chose one of them\n";
-  foreach($entries as $entry) {
-    list($org, $user, $key) = $entry;
-    echo "# taskd.credentials=$org/$user/$key\n";
-  }
-} else { ?>
-taskd.credentials=//
-
-taskd.ca=/path/to/ca.cert.pem
-
-For Mirakel, download and import the file: - -For Android Taskwarrior app, see instructions here. - - - -- cgit v1.2.3