summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/default.nix13
-rw-r--r--modules/myids.nix22
-rw-r--r--modules/private/buildbot/common/build_helpers.py256
-rw-r--r--modules/private/buildbot/common/master.cfg69
-rw-r--r--modules/private/buildbot/default.nix198
-rw-r--r--modules/private/buildbot/projects/caldance/__init__.py190
-rw-r--r--modules/private/buildbot/projects/cryptoportfolio/__init__.py169
-rw-r--r--modules/private/buildbot/projects/test/__init__.py188
-rw-r--r--modules/private/certificates.nix52
-rw-r--r--modules/private/databases/default.nix63
-rw-r--r--modules/private/databases/mariadb.nix149
-rw-r--r--modules/private/databases/openldap/default.nix154
-rw-r--r--modules/private/databases/openldap/immae.schema167
-rw-r--r--modules/private/databases/postgresql.nix220
-rw-r--r--modules/private/databases/redis.nix57
-rw-r--r--modules/private/default.nix65
-rw-r--r--modules/private/dns.nix132
-rw-r--r--modules/private/ftp.nix118
-rw-r--r--modules/private/gitolite/default.nix63
-rwxr-xr-xmodules/private/gitolite/gitolite_ldap_groups.sh15
-rw-r--r--modules/private/irc.nix54
-rw-r--r--modules/private/mail.nix13
-rw-r--r--modules/private/mpd.nix56
-rw-r--r--modules/private/pub/default.nix52
-rw-r--r--modules/private/pub/restrict64
-rw-r--r--modules/private/pub/tmux.restrict.conf43
-rw-r--r--modules/private/ssh/default.nix40
-rwxr-xr-xmodules/private/ssh/ldap_authorized_keys.sh152
-rw-r--r--modules/private/system.nix30
-rw-r--r--modules/private/system/eldiron.nix63
-rw-r--r--modules/private/tasks/default.nix327
-rw-r--r--modules/private/tasks/www/index.php157
-rw-r--r--modules/private/websites/aten/builder.nix102
-rw-r--r--modules/private/websites/aten/integration.nix32
-rw-r--r--modules/private/websites/aten/production.nix34
-rw-r--r--modules/private/websites/capitaines/mastodon_static/index.html29
-rw-r--r--modules/private/websites/capitaines/mastodon_static/oops.pngbin0 -> 120305 bytes
-rw-r--r--modules/private/websites/capitaines/production.nix44
-rw-r--r--modules/private/websites/chloe/builder.nix102
-rw-r--r--modules/private/websites/chloe/config/chmod.php4
-rw-r--r--modules/private/websites/chloe/config/connect.php15
-rw-r--r--modules/private/websites/chloe/config/ldap.php9
-rw-r--r--modules/private/websites/chloe/integration.nix36
-rw-r--r--modules/private/websites/chloe/production.nix38
-rw-r--r--modules/private/websites/commons/adminer.nix21
-rw-r--r--modules/private/websites/connexionswing/builder.nix163
-rw-r--r--modules/private/websites/connexionswing/integration.nix36
-rw-r--r--modules/private/websites/connexionswing/production.nix38
-rw-r--r--modules/private/websites/default.nix265
-rw-r--r--modules/private/websites/denisejerome/production.nix31
-rw-r--r--modules/private/websites/emilia/moodle/pause.html48
-rw-r--r--modules/private/websites/emilia/production.nix66
-rw-r--r--modules/private/websites/florian/app.nix36
-rw-r--r--modules/private/websites/florian/builder_app.nix152
-rw-r--r--modules/private/websites/florian/integration.nix34
-rw-r--r--modules/private/websites/florian/production.nix34
-rw-r--r--modules/private/websites/immae/production.nix64
-rw-r--r--modules/private/websites/immae/release.nix39
-rw-r--r--modules/private/websites/immae/temp.nix36
-rw-r--r--modules/private/websites/leila/production.nix82
-rw-r--r--modules/private/websites/ludivinecassal/builder.nix155
-rw-r--r--modules/private/websites/ludivinecassal/integration.nix32
-rw-r--r--modules/private/websites/ludivinecassal/production.nix33
-rw-r--r--modules/private/websites/nassime/production.nix34
-rw-r--r--modules/private/websites/naturaloutil/production.nix96
-rw-r--r--modules/private/websites/papa/surveillance.nix49
-rw-r--r--modules/private/websites/piedsjaloux/builder.nix144
-rw-r--r--modules/private/websites/piedsjaloux/integration.nix32
-rw-r--r--modules/private/websites/piedsjaloux/production.nix34
-rw-r--r--modules/private/websites/tools/cloud/default.nix188
-rw-r--r--modules/private/websites/tools/dav/davical.nix139
-rw-r--r--modules/private/websites/tools/dav/default.nix53
-rw-r--r--modules/private/websites/tools/db/default.nix21
-rw-r--r--modules/private/websites/tools/diaspora/default.nix181
-rw-r--r--modules/private/websites/tools/ether/default.nix175
-rw-r--r--modules/private/websites/tools/git/default.nix45
-rw-r--r--modules/private/websites/tools/git/gitweb.nix64
-rw-r--r--modules/private/websites/tools/git/mantisbt.nix96
-rw-r--r--modules/private/websites/tools/mastodon/default.nix128
-rw-r--r--modules/private/websites/tools/mgoblin/default.nix122
-rw-r--r--modules/private/websites/tools/peertube/default.nix179
-rw-r--r--modules/private/websites/tools/tools/adminer.nix47
-rw-r--r--modules/private/websites/tools/tools/default.nix302
-rw-r--r--modules/private/websites/tools/tools/dokuwiki.nix61
-rw-r--r--modules/private/websites/tools/tools/kanboard.nix86
-rw-r--r--modules/private/websites/tools/tools/ldap.nix74
-rw-r--r--modules/private/websites/tools/tools/rainloop.nix59
-rw-r--r--modules/private/websites/tools/tools/rompr.nix77
-rw-r--r--modules/private/websites/tools/tools/roundcubemail.nix121
-rw-r--r--modules/private/websites/tools/tools/shaarli.nix65
-rw-r--r--modules/private/websites/tools/tools/ttrss.nix131
-rw-r--r--modules/private/websites/tools/tools/wallabag.nix148
-rw-r--r--modules/private/websites/tools/tools/ympd.nix40
-rw-r--r--modules/private/websites/tools/tools/yourls.nix93
-rw-r--r--modules/secrets.nix61
-rw-r--r--modules/webapps/diaspora.nix171
-rw-r--r--modules/webapps/etherpad-lite.nix158
-rw-r--r--modules/webapps/mastodon.nix223
-rw-r--r--modules/webapps/mediagoblin.nix237
-rw-r--r--modules/webapps/peertube.nix105
-rw-r--r--modules/webapps/webstats/default.nix81
-rw-r--r--modules/webapps/webstats/goaccess.conf99
-rw-r--r--modules/websites/default.nix199
-rw-r--r--modules/websites/httpd-service-builder.nix746
-rw-r--r--modules/websites/nosslVhost/index.html11
105 files changed, 10396 insertions, 0 deletions
diff --git a/modules/default.nix b/modules/default.nix
new file mode 100644
index 00000000..acb0bb51
--- /dev/null
+++ b/modules/default.nix
@@ -0,0 +1,13 @@
1{
2 myids = ./myids.nix;
3 secrets = ./secrets.nix;
4
5 webstats = ./webapps/webstats;
6 diaspora = ./webapps/diaspora.nix;
7 etherpad-lite = ./webapps/etherpad-lite.nix;
8 mastodon = ./webapps/mastodon.nix;
9 mediagoblin = ./webapps/mediagoblin.nix;
10 peertube = ./webapps/peertube.nix;
11
12 websites = ./websites;
13} // (if builtins.pathExists ./private then import ./private else {})
diff --git a/modules/myids.nix b/modules/myids.nix
new file mode 100644
index 00000000..4fb26269
--- /dev/null
+++ b/modules/myids.nix
@@ -0,0 +1,22 @@
1{ ... }:
2{
3 # Check that there is no clash with nixos/modules/misc/ids.nix
4 config = {
5 ids.uids = {
6 peertube = 394;
7 redis = 395;
8 nullmailer = 396;
9 mediagoblin = 397;
10 diaspora = 398;
11 mastodon = 399;
12 };
13 ids.gids = {
14 peertube = 394;
15 redis = 395;
16 nullmailer = 396;
17 mediagoblin = 397;
18 diaspora = 398;
19 mastodon = 399;
20 };
21 };
22}
diff --git a/modules/private/buildbot/common/build_helpers.py b/modules/private/buildbot/common/build_helpers.py
new file mode 100644
index 00000000..384b1ac3
--- /dev/null
+++ b/modules/private/buildbot/common/build_helpers.py
@@ -0,0 +1,256 @@
1from buildbot.plugins import util, steps, schedulers
2from buildbot_buildslist import BuildsList
3
4__all__ = [
5 "force_scheduler", "deploy_scheduler", "hook_scheduler",
6 "clean_branch", "package_and_upload", "SlackStatusPush",
7 "XMPPStatusPush"
8 ]
9
10# Small helpers"
11@util.renderer
12def clean_branch(props):
13 if props.hasProperty("branch") and len(props["branch"]) > 0:
14 return props["branch"].replace("/", "_")
15 else:
16 return "HEAD"
17
18def package_and_upload(package, package_dest, package_url):
19 return [
20 steps.ShellCommand(name="build package",
21 logEnviron=False, haltOnFailure=True, workdir="source",
22 command=["git", "archive", "HEAD", "-o", package]),
23
24 steps.FileUpload(name="upload package", workersrc=package,
25 workdir="source", masterdest=package_dest,
26 url=package_url, mode=0o644),
27
28 steps.ShellCommand(name="cleanup package", logEnviron=False,
29 haltOnFailure=True, workdir="source", alwaysRun=True,
30 command=["rm", "-f", package]),
31 ]
32
33# Schedulers
34def force_scheduler(name, builders):
35 return schedulers.ForceScheduler(name=name,
36 label="Force build", buttonName="Force build",
37 reason=util.StringParameter(name="reason", label="Reason", default="Force build"),
38 codebases=[
39 util.CodebaseParameter("",
40 branch=util.StringParameter(
41 name="branch", label="Git reference (tag, branch)", required=True),
42 revision=util.FixedParameter(name="revision", default=""),
43 repository=util.FixedParameter(name="repository", default=""),
44 project=util.FixedParameter(name="project", default=""),
45 ),
46 ],
47 username=util.FixedParameter(name="username", default="Web button"),
48 builderNames=builders)
49
50def deploy_scheduler(name, builders):
51 return schedulers.ForceScheduler(name=name,
52 builderNames=builders,
53 label="Deploy built package", buttonName="Deploy",
54 username=util.FixedParameter(name="username", default="Web button"),
55 codebases=[
56 util.CodebaseParameter(codebase="",
57 branch=util.FixedParameter(name="branch", default=""),
58 revision=util.FixedParameter(name="revision", default=""),
59 repository=util.FixedParameter(name="repository", default=""),
60 project=util.FixedParameter(name="project", default=""))],
61 reason=util.FixedParameter(name="reason", default="Deploy"),
62 properties=[
63 util.ChoiceStringParameter(label="Environment",
64 name="environment", default="integration",
65 choices=["integration", "production"]),
66 BuildsList(label="Build to deploy", name="build"),
67 ]
68 )
69
70def hook_scheduler(project, timer=10):
71 return schedulers.AnyBranchScheduler(
72 change_filter=util.ChangeFilter(category="hooks", project=project),
73 name=project, treeStableTimer=timer, builderNames=["{}_build".format(project)])
74
75# Slack/XMPP status push
76from buildbot.reporters.http import HttpStatusPushBase
77from twisted.internet import defer
78from twisted.python import log
79from buildbot.util import httpclientservice
80from buildbot.reporters import utils
81from buildbot.process import results
82from twisted.words.protocols.jabber.jid import JID
83from wokkel import client, xmppim
84from functools import partial
85
86class SlackStatusPush(HttpStatusPushBase):
87 name = "SlackStatusPush"
88
89 @defer.inlineCallbacks
90 def reconfigService(self, serverUrl, **kwargs):
91 yield HttpStatusPushBase.reconfigService(self, **kwargs)
92 self._http = yield httpclientservice.HTTPClientService.getService(
93 self.master, serverUrl)
94
95 @defer.inlineCallbacks
96 def send(self, build):
97 yield utils.getDetailsForBuild(self.master, build, wantProperties=True)
98 response = yield self._http.post("", json=self.format(build))
99 if response.code != 200:
100 log.msg("%s: unable to upload status: %s" %
101 (response.code, response.content))
102
103 def format(self, build):
104 colors = [
105 "#36A64F", # success
106 "#F1E903", # warnings
107 "#DA0505", # failure
108 "#FFFFFF", # skipped
109 "#000000", # exception
110 "#FFFFFF", # retry
111 "#D02CA9", # cancelled
112 ]
113
114 if "environment" in build["properties"]:
115 msg = "{} environment".format(build["properties"]["environment"][0])
116 if "build" in build["properties"]:
117 msg = "of archive {} in ".format(build["properties"]["build"][0]) + msg
118 elif len(build["buildset"]["sourcestamps"][0]["branch"]) > 0:
119 msg = "revision {}".format(build["buildset"]["sourcestamps"][0]["branch"])
120 else:
121 msg = "build"
122
123 if build["complete"]:
124 timedelta = int((build["complete_at"] - build["started_at"]).total_seconds())
125 hours, rest = divmod(timedelta, 3600)
126 minutes, seconds = divmod(rest, 60)
127 if hours > 0:
128 duration = "{}h {}min {}s".format(hours, minutes, seconds)
129 elif minutes > 0:
130 duration = "{}min {}s".format(minutes, seconds)
131 else:
132 duration = "{}s".format(seconds)
133
134 text = "Build <{}|{}> of {}'s {} was {} in {}.".format(
135 build["url"], build["buildid"],
136 build["builder"]["name"],
137 msg,
138 results.Results[build["results"]],
139 duration,
140 )
141 fields = [
142 {
143 "title": "Build",
144 "value": "<{}|{}>".format(build["url"], build["buildid"]),
145 "short": True,
146 },
147 {
148 "title": "Project",
149 "value": build["builder"]["name"],
150 "short": True,
151 },
152 {
153 "title": "Build status",
154 "value": results.Results[build["results"]],
155 "short": True,
156 },
157 {
158 "title": "Build duration",
159 "value": duration,
160 "short": True,
161 },
162 ]
163 if "environment" in build["properties"]:
164 fields.append({
165 "title": "Environment",
166 "value": build["properties"]["environment"][0],
167 "short": True,
168 })
169 if "build" in build["properties"]:
170 fields.append({
171 "title": "Archive",
172 "value": build["properties"]["build"][0],
173 "short": True,
174 })
175 attachments = [{
176 "fallback": "",
177 "color": colors[build["results"]],
178 "fields": fields
179 }]
180 else:
181 text = "Build <{}|{}> of {}'s {} started.".format(
182 build["url"], build["buildid"],
183 build["builder"]["name"],
184 msg,
185 )
186 attachments = []
187
188 return {
189 "username": "Buildbot",
190 "icon_url": "http://docs.buildbot.net/current/_static/icon.png",
191 "text": text,
192 "attachments": attachments,
193 }
194
195class XMPPStatusPush(HttpStatusPushBase):
196 name = "XMPPStatusPush"
197
198 @defer.inlineCallbacks
199 def reconfigService(self, password, recipients, **kwargs):
200 yield HttpStatusPushBase.reconfigService(self, **kwargs)
201 self.password = password
202 self.recipients = recipients
203
204 @defer.inlineCallbacks
205 def send(self, build):
206 yield utils.getDetailsForBuild(self.master, build, wantProperties=True)
207 body = self.format(build)
208 factory = client.DeferredClientFactory(JID("notify_bot@immae.fr/buildbot"), self.password)
209 d = client.clientCreator(factory)
210 def send_message(recipient, stream):
211 message = xmppim.Message(recipient=JID(recipient), body=body)
212 message.stanzaType = 'chat'
213 stream.send(message.toElement())
214 # To allow chaining
215 return stream
216 for recipient in self.recipients:
217 d.addCallback(partial(send_message, recipient))
218 d.addCallback(lambda _: factory.streamManager.xmlstream.sendFooter())
219 d.addErrback(log.err)
220
221 def format(self, build):
222 if "environment" in build["properties"]:
223 msg = "{} environment".format(build["properties"]["environment"][0])
224 if "build" in build["properties"]:
225 msg = "of archive {} in ".format(build["properties"]["build"][0]) + msg
226 elif len(build["buildset"]["sourcestamps"][0]["branch"]) > 0:
227 msg = "revision {}".format(build["buildset"]["sourcestamps"][0]["branch"])
228 else:
229 msg = "build"
230
231 if build["complete"]:
232 timedelta = int((build["complete_at"] - build["started_at"]).total_seconds())
233 hours, rest = divmod(timedelta, 3600)
234 minutes, seconds = divmod(rest, 60)
235 if hours > 0:
236 duration = "{}h {}min {}s".format(hours, minutes, seconds)
237 elif minutes > 0:
238 duration = "{}min {}s".format(minutes, seconds)
239 else:
240 duration = "{}s".format(seconds)
241
242 text = "Build {} ( {} ) of {}'s {} was {} in {}.".format(
243 build["buildid"], build["url"],
244 build["builder"]["name"],
245 msg,
246 results.Results[build["results"]],
247 duration,
248 )
249 else:
250 text = "Build {} ( {} ) of {}'s {} started.".format(
251 build["buildid"], build["url"],
252 build["builder"]["name"],
253 msg,
254 )
255
256 return text
diff --git a/modules/private/buildbot/common/master.cfg b/modules/private/buildbot/common/master.cfg
new file mode 100644
index 00000000..abe08e0a
--- /dev/null
+++ b/modules/private/buildbot/common/master.cfg
@@ -0,0 +1,69 @@
1# -*- python -*-
2# ex: set filetype=python:
3
4from buildbot.plugins import secrets, util, webhooks
5from buildbot.util import bytes2unicode
6import re
7import os
8from buildbot_config import E, configure
9import json
10
11class CustomBase(webhooks.base):
12 def getChanges(self, request):
13 try:
14 content = request.content.read()
15 args = json.loads(bytes2unicode(content))
16 except Exception as e:
17 raise ValueError("Error loading JSON: " + str(e))
18
19 args.setdefault("comments", "")
20 args.setdefault("repository", "")
21 args.setdefault("author", args.get("who"))
22
23 return ([args], None)
24
25userInfoProvider = util.LdapUserInfo(
26 uri=E.LDAP_URL,
27 bindUser=E.LDAP_ADMIN_USER,
28 bindPw=open(E.SECRETS_FILE + "/ldap", "r").read().rstrip(),
29 accountBase=E.LDAP_BASE,
30 accountPattern=E.LDAP_PATTERN,
31 accountFullName='cn',
32 accountEmail='mail',
33 avatarData="jpegPhoto",
34 groupBase=E.LDAP_BASE,
35 groupName="cn",
36 groupMemberPattern=E.LDAP_GROUP_PATTERN,
37 )
38
39c = BuildmasterConfig = {
40 "title": E.TITLE,
41 "titleURL": E.TITLE_URL,
42 "db": {
43 "db_url": "sqlite:///state.sqlite"
44 },
45 "protocols": { "pb": { "port": E.PB_SOCKET } },
46 "workers": [],
47 "change_source": [],
48 "schedulers": [],
49 "builders": [],
50 "services": [],
51 "secretsProviders": [
52 secrets.SecretInAFile(E.SECRETS_FILE),
53 ],
54 "www": {
55 "change_hook_dialects": { "base": { "custom_class": CustomBase } },
56 "plugins": {
57 "waterfall_view": {},
58 "console_view": {},
59 "grid_view": {},
60 "buildslist": {},
61 },
62 "auth": util.RemoteUserAuth(
63 header=b"X-Remote-User",
64 userInfoProvider=userInfoProvider,
65 headerRegex=re.compile(br"(?P<username>[^ @]+)")),
66 }
67 }
68
69configure(c)
diff --git a/modules/private/buildbot/default.nix b/modules/private/buildbot/default.nix
new file mode 100644
index 00000000..fa6a6f20
--- /dev/null
+++ b/modules/private/buildbot/default.nix
@@ -0,0 +1,198 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 varDir = "/var/lib/buildbot";
4 buildbot_common = pkgs.python3Packages.buildPythonPackage rec {
5 name = "buildbot_common";
6 src = ./common;
7 format = "other";
8 installPhase = ''
9 mkdir -p $out/${pkgs.python3.pythonForBuild.sitePackages}
10 cp -a $src $out/${pkgs.python3.pythonForBuild.sitePackages}/buildbot_common
11 '';
12 };
13 buildbot = pkgs.python3Packages.buildbot-full;
14in
15{
16 options = {
17 myServices.buildbot.enable = lib.mkOption {
18 type = lib.types.bool;
19 default = false;
20 description = ''
21 Whether to enable buildbot.
22 '';
23 };
24 };
25
26 config = lib.mkIf config.myServices.buildbot.enable {
27 ids.uids.buildbot = myconfig.env.buildbot.user.uid;
28 ids.gids.buildbot = myconfig.env.buildbot.user.gid;
29
30 users.groups.buildbot.gid = config.ids.gids.buildbot;
31 users.users.buildbot = {
32 name = "buildbot";
33 uid = config.ids.uids.buildbot;
34 group = "buildbot";
35 description = "Buildbot user";
36 home = varDir;
37 extraGroups = [ "keys" ];
38 };
39
40 services.websites.tools.vhostConfs.git.extraConfig = lib.attrsets.mapAttrsToList (k: project: ''
41 RedirectMatch permanent "^/buildbot/${project.name}$" "/buildbot/${project.name}/"
42 RewriteEngine On
43 RewriteRule ^/buildbot/${project.name}/ws(.*)$ unix:///run/buildbot/${project.name}.sock|ws://git.immae.eu/ws$1 [P,NE,QSA,L]
44 ProxyPass /buildbot/${project.name}/ unix:///run/buildbot/${project.name}.sock|http://${project.name}-git.immae.eu/
45 ProxyPassReverse /buildbot/${project.name}/ unix:///run/buildbot/${project.name}.sock|http://${project.name}-git.immae.eu/
46 <Location /buildbot/${project.name}/>
47 Use LDAPConnect
48 Require ldap-group cn=users,ou=${project.name},cn=buildbot,ou=services,dc=immae,dc=eu
49
50 SetEnvIf X-Url-Scheme https HTTPS=1
51 ProxyPreserveHost On
52 </Location>
53 <Location /buildbot/${project.name}/change_hook/base>
54 <RequireAny>
55 Require local
56 Require ldap-group cn=users,ou=${project.name},cn=buildbot,ou=services,dc=immae,dc=eu
57 Include /var/secrets/buildbot/${project.name}/webhook-httpd-include
58 </RequireAny>
59 </Location>
60 '') myconfig.env.buildbot.projects;
61
62 system.activationScripts = lib.attrsets.mapAttrs' (k: project: lib.attrsets.nameValuePair "buildbot-${project.name}" {
63 deps = [ "users" "wrappers" ];
64 text = project.activationScript;
65 }) myconfig.env.buildbot.projects;
66
67 secrets.keys = (
68 lib.lists.flatten (
69 lib.attrsets.mapAttrsToList (k: project:
70 lib.attrsets.mapAttrsToList (k: v:
71 {
72 permissions = "0600";
73 user = "buildbot";
74 group = "buildbot";
75 text = v;
76 dest = "buildbot/${project.name}/${k}";
77 }
78 ) project.secrets
79 ++ [
80 {
81 permissions = "0600";
82 user = "wwwrun";
83 group = "wwwrun";
84 text = lib.optionalString (lib.attrsets.hasAttr "webhookTokens" project) ''
85 Require expr "req('Access-Key') in { ${builtins.concatStringsSep ", " (map (x: "'${x}'") project.webhookTokens)} }"
86 '';
87 dest = "buildbot/${project.name}/webhook-httpd-include";
88 }
89 ]
90 ) myconfig.env.buildbot.projects
91 )
92 ) ++ [
93 {
94 permissions = "0600";
95 user = "buildbot";
96 group = "buildbot";
97 text = myconfig.env.buildbot.ldap.password;
98 dest = "buildbot/ldap";
99 }
100 {
101 permissions = "0600";
102 user = "buildbot";
103 group = "buildbot";
104 text = builtins.readFile "${myconfig.privateFiles}/buildbot_ssh_key";
105 dest = "buildbot/ssh_key";
106 }
107 ];
108
109 systemd.services = lib.attrsets.mapAttrs' (k: project: lib.attrsets.nameValuePair "buildbot-${project.name}" {
110 description = "Buildbot Continuous Integration Server ${project.name}.";
111 after = [ "network-online.target" ];
112 wantedBy = [ "multi-user.target" ];
113 path = project.packages pkgs ++ (project.pythonPackages buildbot.pythonModule pkgs);
114 preStart = let
115 master-cfg = "${buildbot_common}/${pkgs.python3.pythonForBuild.sitePackages}/buildbot_common/master.cfg";
116 tac_file = pkgs.writeText "buildbot.tac" ''
117 import os
118
119 from twisted.application import service
120 from buildbot.master import BuildMaster
121
122 basedir = '${varDir}/${project.name}'
123 rotateLength = 10000000
124 maxRotatedFiles = 10
125 configfile = '${master-cfg}'
126
127 # Default umask for server
128 umask = None
129
130 # if this is a relocatable tac file, get the directory containing the TAC
131 if basedir == '.':
132 import os
133 basedir = os.path.abspath(os.path.dirname(__file__))
134
135 # note: this line is matched against to check that this is a buildmaster
136 # directory; do not edit it.
137 application = service.Application('buildmaster')
138 from twisted.python.logfile import LogFile
139 from twisted.python.log import ILogObserver, FileLogObserver
140 logfile = LogFile.fromFullPath(os.path.join(basedir, "twistd.log"), rotateLength=rotateLength,
141 maxRotatedFiles=maxRotatedFiles)
142 application.setComponent(ILogObserver, FileLogObserver(logfile).emit)
143
144 m = BuildMaster(basedir, configfile, umask)
145 m.setServiceParent(application)
146 m.log_rotation.rotateLength = rotateLength
147 m.log_rotation.maxRotatedFiles = maxRotatedFiles
148 '';
149 in ''
150 if [ ! -f ${varDir}/${project.name}/buildbot.tac ]; then
151 ${buildbot}/bin/buildbot create-master -c "${master-cfg}" "${varDir}/${project.name}"
152 rm -f ${varDir}/${project.name}/master.cfg.sample
153 rm -f ${varDir}/${project.name}/buildbot.tac
154 fi
155 ln -sf ${tac_file} ${varDir}/${project.name}/buildbot.tac
156 # different buildbots may be trying that simultaneously, add the || true to avoid complaining in case of race
157 install -Dm600 -o buildbot -g buildbot -T /var/secrets/buildbot/ssh_key ${varDir}/buildbot_key || true
158 buildbot_secrets=${varDir}/${project.name}/secrets
159 install -m 0700 -o buildbot -g buildbot -d $buildbot_secrets
160 install -Dm600 -o buildbot -g buildbot -T /var/secrets/buildbot/ldap $buildbot_secrets/ldap
161 ${builtins.concatStringsSep "\n" (lib.attrsets.mapAttrsToList
162 (k: v: "install -Dm600 -o buildbot -g buildbot -T /var/secrets/buildbot/${project.name}/${k} $buildbot_secrets/${k}") project.secrets
163 )}
164 '';
165 environment = let
166 project_env = lib.attrsets.mapAttrs' (k: v: lib.attrsets.nameValuePair "BUILDBOT_${k}" v) project.environment;
167 buildbot_config = pkgs.python3Packages.buildPythonPackage (rec {
168 name = "buildbot_config-${project.name}";
169 src = ./projects + "/${project.name}";
170 format = "other";
171 installPhase = ''
172 mkdir -p $out/${pkgs.python3.pythonForBuild.sitePackages}
173 cp -a $src $out/${pkgs.python3.pythonForBuild.sitePackages}/buildbot_config
174 '';
175 });
176 HOME = "${varDir}/${project.name}";
177 PYTHONPATH = "${buildbot.pythonModule.withPackages (self: project.pythonPackages self pkgs ++ [
178 pkgs.python3Packages.wokkel
179 pkgs.python3Packages.treq pkgs.python3Packages.ldap3 buildbot
180 pkgs.python3Packages.buildbot-worker
181 buildbot_common buildbot_config
182 ])}/${buildbot.pythonModule.sitePackages}${if project.pythonPathHome then ":${varDir}/${project.name}/.local/${pkgs.python3.pythonForBuild.sitePackages}" else ""}";
183 in project_env // { inherit PYTHONPATH HOME; };
184
185 serviceConfig = {
186 Type = "forking";
187 User = "buildbot";
188 Group = "buildbot";
189 RuntimeDirectory = "buildbot";
190 RuntimeDirectoryPreserve = "yes";
191 StateDirectory = "buildbot";
192 SupplementaryGroups = "keys";
193 WorkingDirectory = "${varDir}/${project.name}";
194 ExecStart = "${buildbot}/bin/buildbot start";
195 };
196 }) myconfig.env.buildbot.projects;
197 };
198}
diff --git a/modules/private/buildbot/projects/caldance/__init__.py b/modules/private/buildbot/projects/caldance/__init__.py
new file mode 100644
index 00000000..2c0bad5b
--- /dev/null
+++ b/modules/private/buildbot/projects/caldance/__init__.py
@@ -0,0 +1,190 @@
1from buildbot.plugins import *
2from buildbot_common.build_helpers import *
3import os
4from buildbot.util import bytes2unicode
5import json
6
7__all__ = [ "configure", "E" ]
8
9class E():
10 PROJECT = "caldance"
11 BUILDBOT_URL = "https://git.immae.eu/buildbot/{}/".format(PROJECT)
12 SOCKET = "unix:/run/buildbot/{}.sock".format(PROJECT)
13 PB_SOCKET = "unix:address=/run/buildbot/{}_pb.sock".format(PROJECT)
14 RELEASE_PATH = "/var/lib/ftp/release.immae.eu/{}".format(PROJECT)
15 RELEASE_URL = "https://release.immae.eu/{}".format(PROJECT)
16 GIT_URL = "gitolite@git.immae.eu:perso/simon_descarpentries/www.cal-dance.com"
17 SSH_KEY_PATH = "/var/lib/buildbot/buildbot_key"
18 SSH_HOST_KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIFbhFTl2A2RJn5L51yxJM4XfCS2ZaiSX/jo9jFSdghF"
19 LDAP_HOST = "ldap.immae.eu"
20 LDAP_DN = "cn=buildbot,ou=services,dc=immae,dc=eu"
21 LDAP_ROLES_BASE = "ou=roles,ou=hosts,dc=immae,dc=eu"
22 XMPP_RECIPIENTS = os.environ["BUILDBOT_XMPP_RECIPIENTS"].split(" ")
23
24 PUPPET_HOST = {
25 "integration": "root@caldance.immae.eu",
26 }
27
28 # master.cfg
29 SECRETS_FILE = os.getcwd() + "/secrets"
30 LDAP_URL = "ldaps://ldap.immae.eu:636"
31 LDAP_ADMIN_USER = "cn=buildbot,ou=services,dc=immae,dc=eu"
32 LDAP_BASE = "dc=immae,dc=eu"
33 LDAP_PATTERN = "(uid=%(username)s)"
34 LDAP_GROUP_PATTERN = "(&(memberOf=cn=groups,ou=caldance,cn=buildbot,ou=services,dc=immae,dc=eu)(member=%(dn)s))"
35 TITLE_URL = "https://caldance.immae.eu"
36 TITLE = "Caldance"
37
38class CustomBase(webhooks.base):
39 def getChanges(self, request):
40 try:
41 content = request.content.read()
42 args = json.loads(bytes2unicode(content))
43 except Exception as e:
44 raise ValueError("Error loading JSON: " + str(e))
45
46 args.setdefault("comments", "")
47 args.setdefault("repository", "")
48 args.setdefault("author", args.get("who", "unknown"))
49
50 if args["category"] == "deploy_webhook":
51 args = {
52 "category": "deploy_webhook",
53 "comments": "",
54 "repository": "",
55 "author": "webhook",
56 "project": "Caldance",
57 "properties": {
58 "environment": args.get("environment", "integration"),
59 "build": "caldance_{}.tar.gz".format(args.get("build", "master"))
60 }
61 }
62
63 return ([args], None)
64
65def deploy_hook_scheduler(project, timer=1):
66 return schedulers.AnyBranchScheduler(
67 change_filter=util.ChangeFilter(category="deploy_webhook", project=project),
68 name="{}_deploy".format(project), treeStableTimer=timer, builderNames=["{}_deploy".format(project)])
69
70def configure(c):
71 c["buildbotURL"] = E.BUILDBOT_URL
72 c["www"]["port"] = E.SOCKET
73
74 c["www"]["change_hook_dialects"]["base"] = {
75 "custom_class": CustomBase
76 }
77 c['workers'].append(worker.LocalWorker("generic-worker"))
78 c['workers'].append(worker.LocalWorker("deploy-worker"))
79
80 c['schedulers'].append(hook_scheduler("Caldance", timer=1))
81 c['schedulers'].append(force_scheduler("force_caldance", ["Caldance_build"]))
82 c['schedulers'].append(deploy_scheduler("deploy_caldance", ["Caldance_deploy"]))
83 c['schedulers'].append(deploy_hook_scheduler("Caldance", timer=1))
84
85 c['builders'].append(factory("caldance"))
86
87 c['builders'].append(deploy_factory("caldance"))
88
89 c['services'].append(SlackStatusPush(
90 name="slack_status_caldance",
91 builders=["Caldance_build", "Caldance_deploy"],
92 serverUrl=open(E.SECRETS_FILE + "/slack_webhook", "r").read().rstrip()))
93 c['services'].append(XMPPStatusPush(
94 name="xmpp_status_caldance",
95 builders=["Caldance_build", "Caldance_deploy"],
96 recipients=E.XMPP_RECIPIENTS,
97 password=open(E.SECRETS_FILE + "/notify_xmpp_password", "r").read().rstrip()))
98
99def factory(project, ignore_fails=False):
100 release_file = "{1}/{0}_%(kw:clean_branch)s.tar.gz"
101
102 package = util.Interpolate("{0}_%(kw:clean_branch)s.tar.gz".format(project), clean_branch=clean_branch)
103 package_dest = util.Interpolate(release_file.format(project, E.RELEASE_PATH), clean_branch=clean_branch)
104 package_url = util.Interpolate(release_file.format(project, E.RELEASE_URL), clean_branch=clean_branch)
105
106 factory = util.BuildFactory()
107 factory.addStep(steps.Git(logEnviron=False, repourl=E.GIT_URL,
108 sshPrivateKey=open(E.SSH_KEY_PATH).read().rstrip(),
109 sshHostKey=E.SSH_HOST_KEY, mode="full", method="copy"))
110 factory.addSteps(package_and_upload(package, package_dest, package_url))
111
112 return util.BuilderConfig(
113 name="{}_build".format(project.capitalize()),
114 workernames=["generic-worker"], factory=factory)
115
116def compute_build_infos(project):
117 @util.renderer
118 def compute(props):
119 import re, hashlib
120 build_file = props.getProperty("build")
121 package_dest = "{1}/{0}".format(build_file, E.RELEASE_PATH)
122 version = re.match(r"{0}_(.*).tar.gz".format(project), build_file).group(1)
123 with open(package_dest, "rb") as f:
124 sha = hashlib.sha256(f.read()).hexdigest()
125 return {
126 "build_version": version,
127 "build_hash": sha,
128 }
129 return compute
130
131@util.renderer
132def puppet_host(props):
133 environment = props["environment"] if props.hasProperty("environment") else "integration"
134 return E.PUPPET_HOST.get(environment, "host.invalid")
135
136def deploy_factory(project):
137 package_dest = util.Interpolate("{0}/%(prop:build)s".format(E.RELEASE_PATH))
138
139 factory = util.BuildFactory()
140 factory.addStep(steps.MasterShellCommand(command=["test", "-f", package_dest]))
141 factory.addStep(steps.SetProperties(properties=compute_build_infos(project)))
142 factory.addStep(LdapPush(environment=util.Property("environment"),
143 project=project, build_version=util.Property("build_version"),
144 build_hash=util.Property("build_hash"), ldap_password=util.Secret("ldap")))
145 factory.addStep(steps.MasterShellCommand(command=[
146 "ssh", "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no", "-o", "CheckHostIP=no", "-i", E.SSH_KEY_PATH, puppet_host]))
147 return util.BuilderConfig(name="{}_deploy".format(project.capitalize()), workernames=["deploy-worker"], factory=factory)
148
149from twisted.internet import defer
150from buildbot.process.buildstep import FAILURE
151from buildbot.process.buildstep import SUCCESS
152from buildbot.process.buildstep import BuildStep
153
154class LdapPush(BuildStep):
155 name = "LdapPush"
156 renderables = ["environment", "project", "build_version", "build_hash", "ldap_password"]
157
158 def __init__(self, **kwargs):
159 self.environment = kwargs.pop("environment")
160 self.project = kwargs.pop("project")
161 self.build_version = kwargs.pop("build_version")
162 self.build_hash = kwargs.pop("build_hash")
163 self.ldap_password = kwargs.pop("ldap_password")
164 self.ldap_host = kwargs.pop("ldap_host", E.LDAP_HOST)
165 super().__init__(**kwargs)
166
167 def run(self):
168 import json
169 from ldap3 import Reader, Writer, Server, Connection, ObjectDef
170 server = Server(self.ldap_host)
171 conn = Connection(server,
172 user=E.LDAP_DN,
173 password=self.ldap_password)
174 conn.bind()
175 obj = ObjectDef("immaePuppetClass", conn)
176 r = Reader(conn, obj,
177 "cn=caldance.{},{}".format(self.environment, E.LDAP_ROLES_BASE))
178 r.search()
179 if len(r) > 0:
180 w = Writer.from_cursor(r)
181 for value in w[0].immaePuppetJson.values:
182 config = json.loads(value)
183 if "role::caldance::{}_version".format(self.project) in config:
184 config["role::caldance::{}_version".format(self.project)] = self.build_version
185 config["role::caldance::{}_sha256".format(self.project)] = self.build_hash
186 w[0].immaePuppetJson -= value
187 w[0].immaePuppetJson += json.dumps(config, indent=" ")
188 w.commit()
189 return defer.succeed(SUCCESS)
190 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 00000000..5d70f957
--- /dev/null
+++ b/modules/private/buildbot/projects/cryptoportfolio/__init__.py
@@ -0,0 +1,169 @@
1from buildbot.plugins import *
2from buildbot_common.build_helpers import *
3import os
4
5__all__ = [ "configure", "E" ]
6
7class E():
8 PROJECT = "cryptoportfolio"
9 BUILDBOT_URL = "https://git.immae.eu/buildbot/{}/".format(PROJECT)
10 SOCKET = "unix:/run/buildbot/{}.sock".format(PROJECT)
11 PB_SOCKET = "unix:address=/run/buildbot/{}_pb.sock".format(PROJECT)
12 RELEASE_PATH = "/var/lib/ftp/release.immae.eu/{}".format(PROJECT)
13 RELEASE_URL = "https://release.immae.eu/{}".format(PROJECT)
14 GIT_URL = "https://git.immae.eu/perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/{0}.git"
15 SSH_KEY_PATH = "/var/lib/buildbot/buildbot_key"
16 LDAP_HOST = "ldap.immae.eu"
17 LDAP_DN = "cn=buildbot,ou=services,dc=immae,dc=eu"
18 LDAP_ROLES_BASE = "ou=roles,ou=hosts,dc=immae,dc=eu"
19
20 PUPPET_HOST = {
21 "production": "root@cryptoportfolio.immae.eu",
22 "integration": "root@cryptoportfolio-dev.immae.eu"
23 }
24
25 # master.cfg
26 SECRETS_FILE = os.getcwd() + "/secrets"
27 LDAP_URL = "ldaps://ldap.immae.eu:636"
28 LDAP_ADMIN_USER = "cn=buildbot,ou=services,dc=immae,dc=eu"
29 LDAP_BASE = "dc=immae,dc=eu"
30 LDAP_PATTERN = "(uid=%(username)s)"
31 LDAP_GROUP_PATTERN = "(&(memberOf=cn=groups,ou=cryptoportfolio,cn=buildbot,ou=services,dc=immae,dc=eu)(member=%(dn)s))"
32 TITLE_URL = "https://git.immae.eu"
33 TITLE = "Cryptoportfolio"
34
35# eval .. dans .zshrc_local
36# mkdir -p $BUILD/go
37# export GOPATH=$BUILD/go
38# go get -u github.com/golang/dep/cmd/dep
39# export PATH=$PATH:$BUILD/go/bin
40# go get git.immae.eu/Cryptoportfolio/Front.git
41# cd $BUILD/go/src/git.immae.eu/Cryptoportfolio/Front.git
42# git checkout dev
43# dep ensure
44def configure(c):
45 c["buildbotURL"] = E.BUILDBOT_URL
46 c["www"]["port"] = E.SOCKET
47
48 c['workers'].append(worker.LocalWorker("generic-worker"))
49 c['workers'].append(worker.LocalWorker("deploy-worker"))
50
51 c['schedulers'].append(hook_scheduler("Trader"))
52 c['schedulers'].append(hook_scheduler("Front"))
53 c['schedulers'].append(force_scheduler(
54 "force_cryptoportfolio", ["Trader_build", "Front_build"]))
55 c['schedulers'].append(deploy_scheduler("deploy_cryptoportfolio",
56 ["Trader_deploy", "Front_deploy"]))
57
58 c['builders'].append(factory("trader"))
59 c['builders'].append(factory("front", ignore_fails=True))
60
61 c['builders'].append(deploy_factory("trader"))
62 c['builders'].append(deploy_factory("front"))
63
64 c['services'].append(SlackStatusPush(
65 name="slack_status_cryptoportfolio",
66 builders=["Front_build", "Trader_build", "Front_deploy", "Trader_deploy"],
67 serverUrl=open(E.SECRETS_FILE + "/slack_webhook", "r").read().rstrip()))
68
69def factory(project, ignore_fails=False):
70 release_file = "{1}/{0}/{0}_%(kw:clean_branch)s.tar.gz"
71
72 url = E.GIT_URL.format(project.capitalize())
73
74 package = util.Interpolate("{0}_%(kw:clean_branch)s.tar.gz".format(project), clean_branch=clean_branch)
75 package_dest = util.Interpolate(release_file.format(project, E.RELEASE_PATH), clean_branch=clean_branch)
76 package_url = util.Interpolate(release_file.format(project, E.RELEASE_URL), clean_branch=clean_branch)
77
78 factory = util.BuildFactory()
79 factory.addStep(steps.Git(logEnviron=False, repourl=url,
80 mode="full", method="copy"))
81 factory.addStep(steps.ShellCommand(name="make install",
82 logEnviron=False, haltOnFailure=(not ignore_fails),
83 warnOnFailure=ignore_fails, flunkOnFailure=(not ignore_fails),
84 command=["make", "install"]))
85 factory.addStep(steps.ShellCommand(name="make test",
86 logEnviron=False, haltOnFailure=(not ignore_fails),
87 warnOnFailure=ignore_fails, flunkOnFailure=(not ignore_fails),
88 command=["make", "test"]))
89 factory.addSteps(package_and_upload(package, package_dest, package_url))
90
91 return util.BuilderConfig(
92 name="{}_build".format(project.capitalize()),
93 workernames=["generic-worker"], factory=factory)
94
95def compute_build_infos(project):
96 @util.renderer
97 def compute(props):
98 import re, hashlib
99 build_file = props.getProperty("build")
100 package_dest = "{2}/{0}/{1}".format(project, build_file, E.RELEASE_PATH)
101 version = re.match(r"{0}_(.*).tar.gz".format(project), build_file).group(1)
102 with open(package_dest, "rb") as f:
103 sha = hashlib.sha256(f.read()).hexdigest()
104 return {
105 "build_version": version,
106 "build_hash": sha,
107 }
108 return compute
109
110@util.renderer
111def puppet_host(props):
112 environment = props["environment"] if props.hasProperty("environment") else "integration"
113 return E.PUPPET_HOST.get(environment, "host.invalid")
114
115def deploy_factory(project):
116 package_dest = util.Interpolate("{1}/{0}/%(prop:build)s".format(project, E.RELEASE_PATH))
117
118 factory = util.BuildFactory()
119 factory.addStep(steps.MasterShellCommand(command=["test", "-f", package_dest]))
120 factory.addStep(steps.SetProperties(properties=compute_build_infos(project)))
121 factory.addStep(LdapPush(environment=util.Property("environment"),
122 project=project, build_version=util.Property("build_version"),
123 build_hash=util.Property("build_hash"), ldap_password=util.Secret("ldap")))
124 factory.addStep(steps.MasterShellCommand(command=[
125 "ssh", "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no", "-o", "CheckHostIP=no", "-i", E.SSH_KEY_PATH, puppet_host]))
126 return util.BuilderConfig(name="{}_deploy".format(project.capitalize()), workernames=["deploy-worker"], factory=factory)
127
128from twisted.internet import defer
129from buildbot.process.buildstep import FAILURE
130from buildbot.process.buildstep import SUCCESS
131from buildbot.process.buildstep import BuildStep
132
133class LdapPush(BuildStep):
134 name = "LdapPush"
135 renderables = ["environment", "project", "build_version", "build_hash", "ldap_password"]
136
137 def __init__(self, **kwargs):
138 self.environment = kwargs.pop("environment")
139 self.project = kwargs.pop("project")
140 self.build_version = kwargs.pop("build_version")
141 self.build_hash = kwargs.pop("build_hash")
142 self.ldap_password = kwargs.pop("ldap_password")
143 self.ldap_host = kwargs.pop("ldap_host", E.LDAP_HOST)
144 super().__init__(**kwargs)
145
146 def run(self):
147 import json
148 from ldap3 import Reader, Writer, Server, Connection, ObjectDef
149 server = Server(self.ldap_host)
150 conn = Connection(server,
151 user=E.LDAP_DN,
152 password=self.ldap_password)
153 conn.bind()
154 obj = ObjectDef("immaePuppetClass", conn)
155 r = Reader(conn, obj,
156 "cn=cryptoportfolio.{},{}".format(self.environment, E.LDAP_ROLES_BASE))
157 r.search()
158 if len(r) > 0:
159 w = Writer.from_cursor(r)
160 for value in w[0].immaePuppetJson.values:
161 config = json.loads(value)
162 if "role::cryptoportfolio::{}_version".format(self.project) in config:
163 config["role::cryptoportfolio::{}_version".format(self.project)] = self.build_version
164 config["role::cryptoportfolio::{}_sha256".format(self.project)] = self.build_hash
165 w[0].immaePuppetJson -= value
166 w[0].immaePuppetJson += json.dumps(config, indent=" ")
167 w.commit()
168 return defer.succeed(SUCCESS)
169 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 00000000..e6b8d514
--- /dev/null
+++ b/modules/private/buildbot/projects/test/__init__.py
@@ -0,0 +1,188 @@
1from buildbot.plugins import *
2from buildbot_common.build_helpers import *
3import os
4from buildbot.util import bytes2unicode
5import json
6
7__all__ = [ "configure", "E" ]
8
9class E():
10 PROJECT = "test"
11 BUILDBOT_URL = "https://git.immae.eu/buildbot/{}/".format(PROJECT)
12 SOCKET = "unix:/run/buildbot/{}.sock".format(PROJECT)
13 PB_SOCKET = "unix:address=/run/buildbot/{}_pb.sock".format(PROJECT)
14 RELEASE_PATH = "/var/lib/ftp/release.immae.eu/{}".format(PROJECT)
15 RELEASE_URL = "https://release.immae.eu/{}".format(PROJECT)
16 GIT_URL = "https://git.immae.eu/perso/Immae/TestProject.git"
17 SSH_KEY_PATH = "/var/lib/buildbot/buildbot_key"
18 PUPPET_HOST = "root@backup-1.v.immae.eu"
19 LDAP_HOST = "ldap.immae.eu"
20 LDAP_DN = "cn=buildbot,ou=services,dc=immae,dc=eu"
21 LDAP_ROLES_BASE = "ou=roles,ou=hosts,dc=immae,dc=eu"
22 XMPP_RECIPIENTS = os.environ["BUILDBOT_XMPP_RECIPIENTS"].split(" ")
23
24 # master.cfg
25 SECRETS_FILE = os.getcwd() + "/secrets"
26 LDAP_URL = "ldaps://ldap.immae.eu:636"
27 LDAP_ADMIN_USER = "cn=buildbot,ou=services,dc=immae,dc=eu"
28 LDAP_BASE = "dc=immae,dc=eu"
29 LDAP_PATTERN = "(uid=%(username)s)"
30 LDAP_GROUP_PATTERN = "(&(memberOf=cn=groups,ou=test,cn=buildbot,ou=services,dc=immae,dc=eu)(member=%(dn)s))"
31 TITLE_URL = "https://git.immae.eu/?p=perso/Immae/TestProject.git;a=summary"
32 TITLE = "Test project"
33
34class CustomBase(webhooks.base):
35 def getChanges(self, request):
36 try:
37 content = request.content.read()
38 args = json.loads(bytes2unicode(content))
39 except Exception as e:
40 raise ValueError("Error loading JSON: " + str(e))
41
42 args.setdefault("comments", "")
43 args.setdefault("repository", "")
44 args.setdefault("author", args.get("who", "unknown"))
45
46 if args["category"] == "deploy_webhook":
47 args = {
48 "category": "deploy_webhook",
49 "comments": "",
50 "repository": "",
51 "author": "unknown",
52 "project": "TestProject",
53 "properties": {
54 "environment": args.get("environment", "integration"),
55 "build": "test_{}.tar.gz".format(args.get("branch", "master"))
56 }
57 }
58
59 return ([args], None)
60
61def deploy_hook_scheduler(project, timer=1):
62 return schedulers.AnyBranchScheduler(
63 change_filter=util.ChangeFilter(category="deploy_webhook", project=project),
64 name="{}_deploy".format(project), treeStableTimer=timer, builderNames=["{}_deploy".format(project)])
65
66def configure(c):
67 c["buildbotURL"] = E.BUILDBOT_URL
68 c["www"]["port"] = E.SOCKET
69
70 c["www"]["change_hook_dialects"]["base"] = {
71 "custom_class": CustomBase
72 }
73 c['workers'].append(worker.LocalWorker("generic-worker-test"))
74 c['workers'].append(worker.LocalWorker("deploy-worker-test"))
75
76 c['schedulers'].append(hook_scheduler("TestProject", timer=1))
77 c['schedulers'].append(force_scheduler("force_test", ["TestProject_build"]))
78 c['schedulers'].append(deploy_scheduler("deploy_test", ["TestProject_deploy"]))
79 c['schedulers'].append(deploy_hook_scheduler("TestProject", timer=1))
80
81 c['builders'].append(factory())
82 c['builders'].append(deploy_factory())
83
84 c['services'].append(SlackStatusPush(
85 name="slack_status_test_project",
86 builders=["TestProject_build", "TestProject_deploy"],
87 serverUrl=open(E.SECRETS_FILE + "/slack_webhook", "r").read().rstrip()))
88 c['services'].append(XMPPStatusPush(
89 name="xmpp_status_test_project",
90 builders=["TestProject_build", "TestProject_deploy"],
91 recipients=E.XMPP_RECIPIENTS,
92 password=open(E.SECRETS_FILE + "/notify_xmpp_password", "r").read().rstrip()))
93
94def factory():
95 package = util.Interpolate("test_%(kw:clean_branch)s.tar.gz", clean_branch=clean_branch)
96 package_dest = util.Interpolate("{}/test_%(kw:clean_branch)s.tar.gz".format(E.RELEASE_PATH), clean_branch=clean_branch)
97 package_url = util.Interpolate("{}/test_%(kw:clean_branch)s.tar.gz".format(E.RELEASE_URL), clean_branch=clean_branch)
98
99 factory = util.BuildFactory()
100 factory.addStep(steps.Git(logEnviron=False,
101 repourl=E.GIT_URL, mode="full", method="copy"))
102 factory.addStep(steps.ShellCommand(name="env",
103 logEnviron=False, command=["env"]))
104 factory.addStep(steps.ShellCommand(name="pwd",
105 logEnviron=False, command=["pwd"]))
106 factory.addStep(steps.ShellCommand(name="true",
107 logEnviron=False, command=["true"]))
108 factory.addStep(steps.ShellCommand(name="echo",
109 logEnviron=False, command=["echo", package]))
110 factory.addSteps(package_and_upload(package, package_dest, package_url))
111
112 return util.BuilderConfig(name="TestProject_build", workernames=["generic-worker-test"], factory=factory)
113
114
115def compute_build_infos():
116 @util.renderer
117 def compute(props):
118 import re, hashlib
119 build_file = props.getProperty("build")
120 package_dest = "{}/{}".format(E.RELEASE_PATH, build_file)
121 version = re.match(r"{0}_(.*).tar.gz".format("test"), build_file).group(1)
122 with open(package_dest, "rb") as f:
123 sha = hashlib.sha256(f.read()).hexdigest()
124 return {
125 "build_version": version,
126 "build_hash": sha,
127 }
128 return compute
129
130@util.renderer
131def puppet_host(props):
132 return E.PUPPET_HOST
133
134def deploy_factory():
135 package_dest = util.Interpolate("{}/%(prop:build)s".format(E.RELEASE_PATH))
136
137 factory = util.BuildFactory()
138 factory.addStep(steps.MasterShellCommand(command=["test", "-f", package_dest]))
139 factory.addStep(steps.SetProperties(properties=compute_build_infos()))
140 factory.addStep(LdapPush(environment=util.Property("environment"),
141 build_version=util.Property("build_version"),
142 build_hash=util.Property("build_hash"),
143 ldap_password=util.Secret("ldap")))
144 factory.addStep(steps.MasterShellCommand(command=[
145 "ssh", "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no", "-o", "CheckHostIP=no", "-i", E.SSH_KEY_PATH, puppet_host]))
146 return util.BuilderConfig(name="TestProject_deploy", workernames=["deploy-worker-test"], factory=factory)
147
148from twisted.internet import defer
149from buildbot.process.buildstep import FAILURE
150from buildbot.process.buildstep import SUCCESS
151from buildbot.process.buildstep import BuildStep
152
153class LdapPush(BuildStep):
154 name = "LdapPush"
155 renderables = ["environment", "build_version", "build_hash", "ldap_password"]
156
157 def __init__(self, **kwargs):
158 self.environment = kwargs.pop("environment")
159 self.build_version = kwargs.pop("build_version")
160 self.build_hash = kwargs.pop("build_hash")
161 self.ldap_password = kwargs.pop("ldap_password")
162 self.ldap_host = kwargs.pop("ldap_host", E.LDAP_HOST)
163 super().__init__(**kwargs)
164
165 def run(self):
166 import json
167 from ldap3 import Reader, Writer, Server, Connection, ObjectDef
168 server = Server(self.ldap_host)
169 conn = Connection(server,
170 user=E.LDAP_DN,
171 password=self.ldap_password)
172 conn.bind()
173 obj = ObjectDef("immaePuppetClass", conn)
174 r = Reader(conn, obj,
175 "cn=test.{},{}".format(self.environment, E.LDAP_ROLES_BASE))
176 r.search()
177 if len(r) > 0:
178 w = Writer.from_cursor(r)
179 for value in w[0].immaePuppetJson.values:
180 config = json.loads(value)
181 if "test_version" in config:
182 config["test_version"] = self.build_version
183 config["test_sha256"] = self.build_hash
184 w[0].immaePuppetJson -= value
185 w[0].immaePuppetJson += json.dumps(config, indent=" ")
186 w.commit()
187 return defer.succeed(SUCCESS)
188 return defer.succeed(FAILURE)
diff --git a/modules/private/certificates.nix b/modules/private/certificates.nix
new file mode 100644
index 00000000..43f6a234
--- /dev/null
+++ b/modules/private/certificates.nix
@@ -0,0 +1,52 @@
1{ lib, pkgs, config, ... }:
2{
3 options.services.myCertificates = {
4 certConfig = lib.mkOption {
5 default = {
6 webroot = "${config.security.acme.directory}/acme-challenge";
7 email = "ismael@bouya.org";
8 postRun = ''
9 systemctl reload httpdTools.service httpdInte.service httpdProd.service
10 '';
11 plugins = [ "cert.pem" "chain.pem" "fullchain.pem" "full.pem" "key.pem" "account_key.json" ];
12 };
13 description = "Default configuration for certificates";
14 };
15 };
16
17 config = {
18 services.websitesCerts = config.services.myCertificates.certConfig;
19 myServices.databasesCerts = config.services.myCertificates.certConfig;
20 myServices.ircCerts = config.services.myCertificates.certConfig;
21
22 security.acme.preliminarySelfsigned = true;
23
24 security.acme.certs = {
25 "eldiron" = config.services.myCertificates.certConfig // {
26 domain = "eldiron.immae.eu";
27 };
28 };
29
30 systemd.services = lib.attrsets.mapAttrs' (k: v:
31 lib.attrsets.nameValuePair "acme-selfsigned-${k}" (lib.mkBefore { script =
32 (lib.optionalString (builtins.elem "cert.pem" v.plugins) ''
33 cp $workdir/server.crt ${config.security.acme.directory}/${k}/cert.pem
34 chown '${v.user}:${v.group}' ${config.security.acme.directory}/${k}/cert.pem
35 chmod ${if v.allowKeysForGroup then "750" else "700"} ${config.security.acme.directory}/${k}/cert.pem
36 '') +
37 (lib.optionalString (builtins.elem "chain.pem" v.plugins) ''
38 cp $workdir/ca.crt ${config.security.acme.directory}/${k}/chain.pem
39 chown '${v.user}:${v.group}' ${config.security.acme.directory}/${k}/chain.pem
40 chmod ${if v.allowKeysForGroup then "750" else "700"} ${config.security.acme.directory}/${k}/chain.pem
41 '')
42 ; })
43 ) config.security.acme.certs // {
44 httpdProd.after = [ "acme-selfsigned-certificates.target" ];
45 httpdProd.wants = [ "acme-selfsigned-certificates.target" ];
46 httpdTools.after = [ "acme-selfsigned-certificates.target" ];
47 httpdTools.wants = [ "acme-selfsigned-certificates.target" ];
48 httpdInte.after = [ "acme-selfsigned-certificates.target" ];
49 httpdInte.wants = [ "acme-selfsigned-certificates.target" ];
50 };
51 };
52}
diff --git a/modules/private/databases/default.nix b/modules/private/databases/default.nix
new file mode 100644
index 00000000..3f7a44bf
--- /dev/null
+++ b/modules/private/databases/default.nix
@@ -0,0 +1,63 @@
1{ lib, config, myconfig, ... }:
2let
3 cfg = config.myServices.databases;
4in
5{
6 options.myServices = {
7 databases.enable = lib.mkEnableOption "my databases service";
8 databasesCerts = lib.mkOption {
9 description = "Default databases configurations for certificates as accepted by acme";
10 };
11 };
12
13 config.nixpkgs.overlays = lib.mkIf cfg.enable [ (self: super: {
14 postgresql = self.postgresql_11_custom;
15 }) ];
16
17 config.myServices.databases = lib.mkIf cfg.enable {
18 mariadb = {
19 enable = true;
20 ldapConfig = {
21 inherit (myconfig.env.ldap) host base;
22 inherit (myconfig.env.databases.mysql.pam) dn filter password;
23 };
24 credentials.root = myconfig.env.databases.mysql.systemUsers.root;
25 };
26
27 openldap = {
28 accessFile = "${myconfig.privateFiles}/ldap.conf";
29 baseDn = myconfig.env.ldap.base;
30 rootDn = myconfig.env.ldap.root_dn;
31 rootPw = myconfig.env.ldap.root_pw;
32 enable = true;
33 };
34
35 postgresql = {
36 ldapConfig = {
37 inherit (myconfig.env.ldap) host base;
38 inherit (myconfig.env.databases.postgresql.pam) dn filter password;
39 };
40 replicationLdapConfig = {
41 inherit (myconfig.env.ldap) host base password;
42 dn = myconfig.env.ldap.host_dn;
43 };
44 authorizedHosts = {
45 immaeEu = [{
46 ip4 = [
47 myconfig.env.servers.immaeEu.ips.main.ip4
48 myconfig.env.servers.immaeEu.ips.alt.ip4
49 ];
50 }];
51 };
52 replicationHosts = {
53 backup-1 = {
54 ip4 = [myconfig.env.servers.backup-1.ips.main.ip4];
55 ip6 = myconfig.env.servers.backup-1.ips.main.ip6;
56 };
57 };
58 enable = true;
59 };
60
61 redis.enable = true;
62 };
63}
diff --git a/modules/private/databases/mariadb.nix b/modules/private/databases/mariadb.nix
new file mode 100644
index 00000000..a7239c0e
--- /dev/null
+++ b/modules/private/databases/mariadb.nix
@@ -0,0 +1,149 @@
1{ lib, pkgs, config, ... }:
2let
3 cfg = config.myServices.databases.mariadb;
4in {
5 options.myServices.databases = {
6 mariadb = {
7 enable = lib.mkOption {
8 default = cfg.enable;
9 example = true;
10 description = "Whether to enable mariadb database";
11 type = lib.types.bool;
12 };
13 package = lib.mkOption {
14 type = lib.types.package;
15 default = pkgs.mariadb;
16 description = ''
17 Mariadb package to use.
18 '';
19 };
20 credentials = lib.mkOption {
21 default = {};
22 description = "Credentials";
23 type = lib.types.attrsOf lib.types.str;
24 };
25 ldapConfig = lib.mkOption {
26 description = "LDAP configuration to allow PAM identification via LDAP";
27 type = lib.types.submodule {
28 options = {
29 host = lib.mkOption { type = lib.types.str; };
30 base = lib.mkOption { type = lib.types.str; };
31 dn = lib.mkOption { type = lib.types.str; };
32 password = lib.mkOption { type = lib.types.str; };
33 filter = lib.mkOption { type = lib.types.str; };
34 };
35 };
36 };
37 dataDir = lib.mkOption {
38 type = lib.types.path;
39 default = "/var/lib/mysql";
40 description = ''
41 The directory where Mariadb stores its data.
42 '';
43 };
44 # Output variables
45 socketsDir = lib.mkOption {
46 type = lib.types.path;
47 default = "/run/mysqld";
48 description = ''
49 The directory where Mariadb puts sockets.
50 '';
51 };
52 sockets = lib.mkOption {
53 type = lib.types.attrsOf lib.types.path;
54 default = {
55 mysqld = "${cfg.socketsDir}/mysqld.sock";
56 };
57 readOnly = true;
58 description = ''
59 Mariadb sockets
60 '';
61 };
62 };
63 };
64
65 config = lib.mkIf cfg.enable {
66 networking.firewall.allowedTCPPorts = [ 3306 ];
67
68 # for adminer, ssl is implemented with mysqli only, which is
69 # currently disabled because it’s not compatible with pam.
70 # Thus we need to generate two users for each 'remote': one remote
71 # with SSL, and one localhost without SSL.
72 # User identified by LDAP:
73 # CREATE USER foo@% IDENTIFIED VIA pam USING 'mysql' REQUIRE SSL;
74 # CREATE USER foo@localhost IDENTIFIED VIA pam USING 'mysql';
75 services.mysql = {
76 enable = true;
77 package = cfg.package;
78 dataDir = cfg.dataDir;
79 extraOptions = ''
80 ssl_ca = ${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt
81 ssl_key = ${config.security.acme.directory}/mysql/key.pem
82 ssl_cert = ${config.security.acme.directory}/mysql/fullchain.pem
83 '';
84 };
85
86 users.users.mysql.extraGroups = [ "keys" ];
87 security.acme.certs."mysql" = config.myServices.databasesCerts // {
88 user = "mysql";
89 group = "mysql";
90 plugins = [ "fullchain.pem" "key.pem" "account_key.json" ];
91 domain = "db-1.immae.eu";
92 postRun = ''
93 systemctl restart mysql.service
94 '';
95 };
96
97 secrets.keys = [
98 {
99 dest = "mysql/mysqldump";
100 permissions = "0400";
101 user = "root";
102 group = "root";
103 text = ''
104 [mysqldump]
105 user = root
106 password = ${cfg.credentials.root}
107 '';
108 }
109 {
110 dest = "mysql/pam";
111 permissions = "0400";
112 user = "mysql";
113 group = "mysql";
114 text = with cfg.ldapConfig; ''
115 host ${host}
116 base ${base}
117 binddn ${dn}
118 bindpw ${password}
119 pam_filter ${filter}
120 ssl start_tls
121 '';
122 }
123 ];
124
125 services.cron = {
126 enable = true;
127 systemCronJobs = [
128 ''
129 30 1,13 * * * root ${cfg.package}/bin/mysqldump --defaults-file=${config.secrets.location}/mysql/mysqldump --all-databases > ${cfg.dataDir}/backup.sql
130 ''
131 ];
132 };
133
134 security.pam.services = let
135 pam_ldap = "${pkgs.pam_ldap}/lib/security/pam_ldap.so";
136 in [
137 {
138 name = "mysql";
139 text = ''
140 # https://mariadb.com/kb/en/mariadb/pam-authentication-plugin/
141 auth required ${pam_ldap} config=${config.secrets.location}/mysql/pam
142 account required ${pam_ldap} config=${config.secrets.location}/mysql/pam
143 '';
144 }
145 ];
146
147 };
148}
149
diff --git a/modules/private/databases/openldap/default.nix b/modules/private/databases/openldap/default.nix
new file mode 100644
index 00000000..e048d565
--- /dev/null
+++ b/modules/private/databases/openldap/default.nix
@@ -0,0 +1,154 @@
1{ lib, pkgs, config, ... }:
2let
3 cfg = config.myServices.databases.openldap;
4 ldapConfig = let
5 kerberosSchema = pkgs.fetchurl {
6 url = "https://raw.githubusercontent.com/krb5/krb5/master/src/plugins/kdb/ldap/libkdb_ldap/kerberos.schema";
7 sha256 = "17fnkkf6s3lznsl7wp6914pqsc78d038rh38l638big8z608ksww";
8 };
9 puppetSchema = pkgs.fetchurl {
10 url = "https://raw.githubusercontent.com/puppetlabs/puppet/master/ext/ldap/puppet.schema";
11 sha256 = "11bjf5zfvqlim7p9vddcafs0wiq3v8ys77x8h6fbp9c6bdfh0awh";
12 };
13 in ''
14 include ${pkgs.openldap}/etc/schema/core.schema
15 include ${pkgs.openldap}/etc/schema/cosine.schema
16 include ${pkgs.openldap}/etc/schema/inetorgperson.schema
17 include ${pkgs.openldap}/etc/schema/nis.schema
18 include ${puppetSchema}
19 include ${kerberosSchema}
20 include ${./immae.schema}
21
22 pidfile ${cfg.pids.pid}
23 argsfile ${cfg.pids.args}
24
25 moduleload back_hdb
26 backend hdb
27
28 moduleload memberof
29 database hdb
30 suffix "${cfg.baseDn}"
31 rootdn "${cfg.rootDn}"
32 include ${config.secrets.location}/ldap/password
33 directory ${cfg.dataDir}
34 overlay memberof
35
36 TLSCertificateFile ${config.security.acme.directory}/ldap/cert.pem
37 TLSCertificateKeyFile ${config.security.acme.directory}/ldap/key.pem
38 TLSCACertificateFile ${config.security.acme.directory}/ldap/fullchain.pem
39 TLSCACertificatePath ${pkgs.cacert.unbundled}/etc/ssl/certs/
40 #This makes openldap crash
41 #TLSCipherSuite DEFAULT
42
43 sasl-host kerberos.immae.eu
44 include ${config.secrets.location}/ldap/access
45 '';
46in
47{
48 options.myServices.databases = {
49 openldap = {
50 enable = lib.mkOption {
51 default = cfg.enable;
52 example = true;
53 description = "Whether to enable ldap";
54 type = lib.types.bool;
55 };
56 baseDn = lib.mkOption {
57 type = lib.types.str;
58 description = ''
59 Base DN for LDAP
60 '';
61 };
62 rootDn = lib.mkOption {
63 type = lib.types.str;
64 description = ''
65 Root DN
66 '';
67 };
68 rootPw = lib.mkOption {
69 type = lib.types.str;
70 description = ''
71 Root (Hashed) password
72 '';
73 };
74 accessFile = lib.mkOption {
75 type = lib.types.path;
76 description = ''
77 The file path that defines the access
78 '';
79 };
80 dataDir = lib.mkOption {
81 type = lib.types.path;
82 default = "/var/lib/openldap";
83 description = ''
84 The directory where Openldap stores its data.
85 '';
86 };
87 socketsDir = lib.mkOption {
88 type = lib.types.path;
89 default = "/run/slapd";
90 description = ''
91 The directory where Openldap puts sockets and pid files.
92 '';
93 };
94 # Output variables
95 pids = lib.mkOption {
96 type = lib.types.attrsOf lib.types.path;
97 default = {
98 pid = "${cfg.socketsDir}/slapd.pid";
99 args = "${cfg.socketsDir}/slapd.args";
100 };
101 readOnly = true;
102 description = ''
103 Slapd pid files
104 '';
105 };
106 };
107 };
108
109 config = lib.mkIf cfg.enable {
110 secrets.keys = [
111 {
112 dest = "ldap/password";
113 permissions = "0400";
114 user = "openldap";
115 group = "openldap";
116 text = "rootpw ${cfg.rootPw}";
117 }
118 {
119 dest = "ldap/access";
120 permissions = "0400";
121 user = "openldap";
122 group = "openldap";
123 text = builtins.readFile "${cfg.accessFile}";
124 }
125 ];
126 users.users.openldap.extraGroups = [ "keys" ];
127 networking.firewall.allowedTCPPorts = [ 636 389 ];
128
129 services.cron = {
130 systemCronJobs = [
131 ''
132 35 1,13 * * * root ${pkgs.openldap}/bin/slapcat -v -b "dc=immae,dc=eu" -f ${pkgs.writeText "slapd.conf" ldapConfig} -l ${cfg.dataDir}/backup.ldif | ${pkgs.gnugrep}/bin/grep -v "^# id=[0-9a-f]*$"
133 ''
134 ];
135 };
136
137 security.acme.certs."ldap" = config.myServices.databasesCerts // {
138 user = "openldap";
139 group = "openldap";
140 plugins = [ "fullchain.pem" "key.pem" "cert.pem" "account_key.json" ];
141 domain = "ldap.immae.eu";
142 postRun = ''
143 systemctl restart openldap.service
144 '';
145 };
146
147 services.openldap = {
148 enable = true;
149 dataDir = cfg.dataDir;
150 urlList = [ "ldap://" "ldaps://" ];
151 extraConfig = ldapConfig;
152 };
153 };
154}
diff --git a/modules/private/databases/openldap/immae.schema b/modules/private/databases/openldap/immae.schema
new file mode 100644
index 00000000..f5ee5d54
--- /dev/null
+++ b/modules/private/databases/openldap/immae.schema
@@ -0,0 +1,167 @@
1# vim: set filetype=slapd:
2objectIdentifier Immaeroot 1.3.6.1.4.1.50071
3
4objectIdentifier Immae Immaeroot:2
5objectIdentifier ImmaeattributeType Immae:3
6objectIdentifier ImmaeobjectClass Immae:4
7
8# TT-RSS
9attributetype ( ImmaeattributeType:1 NAME 'immaeTtrssLogin'
10 DESC 'login for TTRSS'
11 EQUALITY caseIgnoreMatch
12 SUBSTR caseIgnoreSubstringsMatch
13 SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
14
15objectclass ( ImmaeobjectClass:1 NAME 'immaeTtrssClass'
16 DESC 'Expansion of the existing object classes for ttrss'
17 SUP top AUXILIARY
18 MUST ( immaeTtrssLogin ) )
19
20# FTP
21attributetype ( ImmaeattributeType:2 NAME 'immaeFtpDirectory'
22 DESC 'home directory for ftp'
23 EQUALITY caseExactIA5Match
24 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
25
26attributetype ( ImmaeattributeType:3 NAME 'immaeFtpUid'
27 DESC 'user id for ftp'
28 EQUALITY integerMatch
29 SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )
30
31attributetype ( ImmaeattributeType:4 NAME 'immaeFtpGid'
32 DESC 'group id for ftp'
33 EQUALITY integerMatch
34 SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )
35
36objectclass ( ImmaeobjectClass:2 NAME 'immaeFtpClass'
37 DESC 'Expansion of the existing object classes for ftp'
38 SUP top AUXILIARY
39 MUST ( immaeFtpDirectory $ immaeFtpGid $ immaeFtpUid ) )
40
41
42# SSH keys
43attributetype ( ImmaeattributeType:5 NAME 'immaeSshKey'
44 DESC 'OpenSSH Public key'
45 EQUALITY octetStringMatch
46 SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
47
48objectClass ( ImmaeobjectClass:3 NAME 'immaeSshClass'
49 DESC 'OpenSSH class'
50 SUP top AUXILIARY
51 MAy ( immaeSSHKey ) )
52
53# Specific access
54attributetype (ImmaeattributeType:6 NAME 'immaeAccessDn'
55 EQUALITY distinguishedNameMatch
56 SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
57
58attributetype (ImmaeattributeType:17 NAME 'immaeAccessWriteDn'
59 EQUALITY distinguishedNameMatch
60 SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
61
62attributetype (ImmaeattributeType:18 NAME 'immaeAccessReadSubtree'
63 EQUALITY distinguishedNameMatch
64 SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
65
66objectClass ( ImmaeobjectClass:4 NAME 'immaeAccessClass'
67 DESC 'Access class'
68 SUP top AUXILIARY
69 MAY ( immaeAccessDn $ immaeAccessWriteDn $ immaeAccessReadSubtree ) )
70
71# Xmpp uid
72attributetype ( ImmaeattributeType:7 NAME 'immaeXmppUid'
73 DESC 'user part for Xmpp'
74 EQUALITY caseIgnoreMatch
75 SUBSTR caseIgnoreSubstringsMatch
76 SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
77
78objectclass ( ImmaeobjectClass:5 NAME 'immaeXmppClass'
79 DESC 'Expansion of the existing object classes for XMPP'
80 SUP top AUXILIARY
81 MUST ( immaeXmppUid ) )
82
83# Postfix accounts
84attributetype ( ImmaeattributeType:8 NAME 'immaePostfixAddress'
85 DESC 'the dovecot address to match as username'
86 EQUALITY caseIgnoreIA5Match
87 SUBSTR caseIgnoreIA5SubstringsMatch
88 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
89
90attributetype ( ImmaeattributeType:9 NAME 'immaePostfixHome'
91 DESC 'the postfix home directory'
92 EQUALITY caseExactIA5Match
93 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
94
95attributetype ( ImmaeattributeType:10 NAME 'immaePostfixMail'
96 DESC 'the dovecot mail location'
97 EQUALITY caseExactIA5Match
98 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
99
100attributetype ( ImmaeattributeType:11 NAME 'immaePostfixUid'
101 DESC 'the dovecot uid'
102 EQUALITY caseExactIA5Match
103 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
104
105attributetype ( ImmaeattributeType:12 NAME 'immaePostfixGid'
106 DESC 'the dovecot gid'
107 EQUALITY caseExactIA5Match
108 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
109
110objectclass ( ImmaeobjectClass:6 NAME 'immaePostfixClass'
111 DESC 'Expansion of the existing object classes for Postfix'
112 SUP top AUXILIARY
113 MUST ( immaePostfixAddress $ immaePostfixHome $
114 immaePostfixMail $ immaePostfixUid $ immaePostfixGid )
115 )
116
117# Tinc informations
118# Domaine = une classe a part ou une partie du dn ?
119# attributetype ( ImmaeattributeType:13 NAME 'immaeTincIpSegment'
120# DESC 'the internal ip segment in tinc'
121# EQUALITY caseIgnoreIA5Match
122# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
123#
124# attributetype ( ImmaeattributeType:14 NAME 'immaeTincSubdomain'
125# DESC 'the host subdomain'
126# EQUALITY caseIgnoreIA5Match
127# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
128#
129# attributetype ( ImmaeattributeType:15 NAME 'immaeTincHostname'
130# DESC 'the host name'
131# EQUALITY caseIgnoreIA5Match
132# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
133#
134# objectclass ( ImmaeobjectClass:7 NAME 'immaeTincHostClass'
135# DESC 'Expansion of the existing object classes for Tinc'
136# SUP top AUXILIARY
137# MUST ( immaeTincInternalIp $ immaeTincSubdomain $
138# immaeTincHostname )
139# )
140
141attributetype (ImmaeattributeType:16 NAME 'immaePuppetJson'
142 DESC 'Puppet hiera json'
143 EQUALITY octetStringMatch
144 SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
145
146objectclass ( ImmaeobjectClass:8 NAME 'immaePuppetClass'
147 DESC 'Expansion of the existing object classes for Puppet'
148 SUP top AUXILIARY
149 MUST ( immaePuppetJson )
150 )
151
152attributetype (ImmaeattributeType:19 NAME 'immaeTaskId'
153 DESC 'Taskwarrior server Org:Name:Key'
154 EQUALITY caseIgnoreMatch
155 SUBSTR caseIgnoreSubstringsMatch
156 SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
157
158objectclass ( ImmaeobjectClass:9 NAME 'immaeTaskClass'
159 DESC 'Expansion of the existing object classes for Task'
160 SUP top AUXILIARY
161 MUST ( immaeTaskId )
162 )
163
164# Last:
165# attributetype (ImmaeattributeType:19 NAME 'immaeTaskId'
166# objectclass ( ImmaeobjectClass:9 NAME 'immaeTaskClass'
167
diff --git a/modules/private/databases/postgresql.nix b/modules/private/databases/postgresql.nix
new file mode 100644
index 00000000..911a6d1b
--- /dev/null
+++ b/modules/private/databases/postgresql.nix
@@ -0,0 +1,220 @@
1{ lib, pkgs, config, ... }:
2let
3 cfg = config.myServices.databases.postgresql;
4in {
5 options.myServices.databases = {
6 postgresql = {
7 enable = lib.mkOption {
8 default = cfg.enable;
9 example = true;
10 description = "Whether to enable postgresql database";
11 type = lib.types.bool;
12 };
13 package = lib.mkOption {
14 type = lib.types.package;
15 default = pkgs.postgresql;
16 description = ''
17 Postgresql package to use.
18 '';
19 };
20 ldapConfig = lib.mkOption {
21 description = "LDAP configuration to allow PAM identification via LDAP";
22 type = lib.types.submodule {
23 options = {
24 host = lib.mkOption { type = lib.types.str; };
25 base = lib.mkOption { type = lib.types.str; };
26 dn = lib.mkOption { type = lib.types.str; };
27 password = lib.mkOption { type = lib.types.str; };
28 filter = lib.mkOption { type = lib.types.str; };
29 };
30 };
31 };
32 replicationLdapConfig = lib.mkOption {
33 description = "LDAP configuration to allow replication";
34 type = lib.types.submodule {
35 options = {
36 host = lib.mkOption { type = lib.types.str; };
37 base = lib.mkOption { type = lib.types.str; };
38 dn = lib.mkOption { type = lib.types.str; };
39 password = lib.mkOption { type = lib.types.str; };
40 };
41 };
42 };
43 authorizedHosts = lib.mkOption {
44 default = {};
45 description = "Hosts to allow connections from";
46 type = lib.types.attrsOf (lib.types.listOf (lib.types.submodule {
47 options = {
48 method = lib.mkOption {
49 default = "md5";
50 type = lib.types.str;
51 };
52 username = lib.mkOption {
53 default = "all";
54 type = lib.types.str;
55 };
56 database = lib.mkOption {
57 default = "all";
58 type = lib.types.str;
59 };
60 ip4 = lib.mkOption {
61 default = [];
62 type = lib.types.listOf lib.types.str;
63 };
64 ip6 = lib.mkOption {
65 default = [];
66 type = lib.types.listOf lib.types.str;
67 };
68 };
69 }));
70 };
71 replicationHosts = lib.mkOption {
72 default = {};
73 description = "Hosts to allow replication from";
74 type = lib.types.attrsOf (lib.types.submodule {
75 options = {
76 ip4 = lib.mkOption {
77 type = lib.types.listOf lib.types.str;
78 };
79 ip6 = lib.mkOption {
80 type = lib.types.listOf lib.types.str;
81 };
82 };
83 });
84 };
85 # Output variables
86 socketsDir = lib.mkOption {
87 type = lib.types.path;
88 default = "/run/postgresql";
89 description = ''
90 The directory where Postgresql puts sockets.
91 '';
92 readOnly = true;
93 };
94 systemdRuntimeDirectory = lib.mkOption {
95 type = lib.types.str;
96 # Use ReadWritePaths= instead if socketsDir is outside of /run
97 default = assert lib.strings.hasPrefix "/run/" cfg.socketsDir;
98 lib.strings.removePrefix "/run/" cfg.socketsDir;
99 description = ''
100 Adjusted Postgresql sockets directory for systemd
101 '';
102 readOnly = true;
103 };
104 };
105 };
106
107 config = lib.mkIf cfg.enable {
108 networking.firewall.allowedTCPPorts = [ 5432 ];
109
110 security.acme.certs."postgresql" = config.myServices.databasesCerts // {
111 user = "postgres";
112 group = "postgres";
113 plugins = [ "fullchain.pem" "key.pem" "account_key.json" ];
114 domain = "db-1.immae.eu";
115 postRun = ''
116 systemctl reload postgresql.service
117 '';
118 };
119
120 systemd.services.postgresql.serviceConfig = {
121 SupplementaryGroups = "keys";
122 RuntimeDirectory = cfg.systemdRuntimeDirectory;
123 };
124 services.postgresql = {
125 enable = true;
126 package = cfg.package;
127 enableTCPIP = true;
128 extraConfig = ''
129 max_connections = 100
130 wal_level = logical
131 shared_buffers = 512MB
132 work_mem = 10MB
133 max_wal_size = 1GB
134 min_wal_size = 80MB
135 log_timezone = 'Europe/Paris'
136 datestyle = 'iso, mdy'
137 timezone = 'Europe/Paris'
138 lc_messages = 'en_US.UTF-8'
139 lc_monetary = 'en_US.UTF-8'
140 lc_numeric = 'en_US.UTF-8'
141 lc_time = 'en_US.UTF-8'
142 default_text_search_config = 'pg_catalog.english'
143 ssl = on
144 ssl_cert_file = '${config.security.acme.directory}/postgresql/fullchain.pem'
145 ssl_key_file = '${config.security.acme.directory}/postgresql/key.pem'
146 '';
147 authentication = let
148 hosts = builtins.concatStringsSep "\n" (
149 lib.lists.flatten (lib.mapAttrsToList (k: vs: map (v:
150 map (ip6: "hostssl ${v.database} ${v.username} ${ip6}/128 ${v.method}") v.ip6
151 ++ map (ip4: "hostssl ${v.database} ${v.username} ${ip4}/32 ${v.method}") v.ip4
152 ) vs) cfg.authorizedHosts
153 ));
154 replication = builtins.concatStringsSep "\n" (
155 lib.lists.flatten (lib.mapAttrsToList (k: v:
156 map (ip6: "hostssl replication ${k} ${ip6}/128 pam pamservice=postgresql_replication") v.ip6
157 ++ map (ip4: "hostssl replication ${k} ${ip4}/32 pam pamservice=postgresql_replication") v.ip4
158 ) cfg.replicationHosts
159 ));
160 in ''
161 local all postgres ident
162 local all all md5
163 ${hosts}
164 hostssl all all all pam
165 ${replication}
166 '';
167 };
168
169 secrets.keys = [
170 {
171 dest = "postgresql/pam";
172 permissions = "0400";
173 group = "postgres";
174 user = "postgres";
175 text = with cfg.ldapConfig; ''
176 host ${host}
177 base ${base}
178 binddn ${dn}
179 bindpw ${password}
180 pam_filter ${filter}
181 ssl start_tls
182 '';
183 }
184 {
185 dest = "postgresql/pam_replication";
186 permissions = "0400";
187 group = "postgres";
188 user = "postgres";
189 text = with cfg.replicationLdapConfig; ''
190 host ${host}
191 base ${base}
192 binddn ${dn}
193 bindpw ${password}
194 pam_login_attribute cn
195 ssl start_tls
196 '';
197 }
198 ];
199
200 security.pam.services = let
201 pam_ldap = "${pkgs.pam_ldap}/lib/security/pam_ldap.so";
202 in [
203 {
204 name = "postgresql";
205 text = ''
206 auth required ${pam_ldap} config=${config.secrets.location}/postgresql/pam
207 account required ${pam_ldap} config=${config.secrets.location}/postgresql/pam
208 '';
209 }
210 {
211 name = "postgresql_replication";
212 text = ''
213 auth required ${pam_ldap} config=${config.secrets.location}/postgresql/pam_replication
214 account required ${pam_ldap} config=${config.secrets.location}/postgresql/pam_replication
215 '';
216 }
217 ];
218 };
219}
220
diff --git a/modules/private/databases/redis.nix b/modules/private/databases/redis.nix
new file mode 100644
index 00000000..1ba6eed6
--- /dev/null
+++ b/modules/private/databases/redis.nix
@@ -0,0 +1,57 @@
1{ lib, config, ... }:
2let
3 cfg = config.myServices.databases.redis;
4in {
5 options.myServices.databases.redis = {
6 enable = lib.mkOption {
7 default = cfg.enable;
8 example = true;
9 description = "Whether to enable redis database";
10 type = lib.types.bool;
11 };
12 socketsDir = lib.mkOption {
13 type = lib.types.path;
14 default = "/run/redis";
15 description = ''
16 The directory where Redis puts sockets.
17 '';
18 };
19 # Output variables
20 systemdRuntimeDirectory = lib.mkOption {
21 type = lib.types.str;
22 # Use ReadWritePaths= instead if socketsDir is outside of /run
23 default = assert lib.strings.hasPrefix "/run/" cfg.socketsDir;
24 lib.strings.removePrefix "/run/" cfg.socketsDir;
25 description = ''
26 Adjusted redis sockets directory for systemd
27 '';
28 readOnly = true;
29 };
30 sockets = lib.mkOption {
31 type = lib.types.attrsOf lib.types.path;
32 default = {
33 redis = "${cfg.socketsDir}/redis.sock";
34 };
35 readOnly = true;
36 description = ''
37 Redis sockets
38 '';
39 };
40 };
41
42 config = lib.mkIf cfg.enable {
43 users.users.redis.uid = config.ids.uids.redis;
44 users.groups.redis.gid = config.ids.gids.redis;
45 services.redis = rec {
46 enable = true;
47 bind = "127.0.0.1";
48 unixSocket = cfg.sockets.redis;
49 extraConfig = ''
50 unixsocketperm 777
51 maxclients 1024
52 '';
53 };
54 systemd.services.redis.serviceConfig.RuntimeDirectory = cfg.systemdRuntimeDirectory;
55 };
56}
57
diff --git a/modules/private/default.nix b/modules/private/default.nix
new file mode 100644
index 00000000..894efb76
--- /dev/null
+++ b/modules/private/default.nix
@@ -0,0 +1,65 @@
1let
2set = {
3 # adatped from nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix
4 httpdInte = import ../websites/httpd-service-builder.nix { httpdName = "Inte"; withUsers = false; };
5 httpdProd = import ../websites/httpd-service-builder.nix { httpdName = "Prod"; withUsers = false; };
6 httpdTools = import ../websites/httpd-service-builder.nix { httpdName = "Tools"; withUsers = true; };
7
8 databases = ./databases;
9 mariadb = ./databases/mariadb.nix;
10 openldap = ./databases/openldap;
11 postgresql = ./databases/postgresql.nix;
12 redis = ./databases/redis.nix;
13
14 websites = ./websites;
15 atenInte = ./websites/aten/integration.nix;
16 atenProd = ./websites/aten/production.nix;
17 capitainesProd = ./websites/capitaines/production.nix;
18 chloeInte = ./websites/chloe/integration.nix;
19 chloeProd = ./websites/chloe/production.nix;
20 connexionswingInte = ./websites/connexionswing/integration.nix;
21 connexionswingProd = ./websites/connexionswing/production.nix;
22 denisejeromeProd = ./websites/denisejerome/production.nix;
23 emiliaProd = ./websites/emilia/production.nix;
24 florianApp = ./websites/florian/app.nix;
25 florianInte = ./websites/florian/integration.nix;
26 florianProd = ./websites/florian/production.nix;
27 immaeProd = ./websites/immae/production.nix;
28 immaeRelease = ./websites/immae/release.nix;
29 immaeTemp = ./websites/immae/temp.nix;
30 leilaProd = ./websites/leila/production.nix;
31 ludivinecassalInte = ./websites/ludivinecassal/integration.nix;
32 ludivinecassalProd = ./websites/ludivinecassal/production.nix;
33 nassimeProd = ./websites/nassime/production.nix;
34 naturaloutilProd = ./websites/naturaloutil/production.nix;
35 papaSurveillance = ./websites/papa/surveillance.nix;
36 piedsjalouxInte = ./websites/piedsjaloux/integration.nix;
37 piedsjalouxProd = ./websites/piedsjaloux/production.nix;
38
39 cloudTool = ./websites/tools/cloud;
40 davTool = ./websites/tools/dav;
41 dbTool = ./websites/tools/db;
42 diasporaTool = ./websites/tools/diaspora;
43 etherTool = ./websites/tools/ether;
44 gitTool = ./websites/tools/git;
45 mastodonTool = ./websites/tools/mastodon;
46 mgoblinTool = ./websites/tools/mgoblin;
47 peertubeTool = ./websites/tools/peertube;
48 toolsTool = ./websites/tools/tools;
49
50 buildbot = ./buildbot;
51 certificates = ./certificates.nix;
52 gitolite = ./gitolite;
53 irc = ./irc.nix;
54 pub = ./pub;
55 tasks = ./tasks;
56 dns = ./dns.nix;
57 ftp = ./ftp.nix;
58 mail = ./mail.nix;
59 mpd = ./mpd.nix;
60 ssh = ./ssh;
61
62 system = ./system.nix;
63};
64in
65builtins.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 00000000..ced8d9b0
--- /dev/null
+++ b/modules/private/dns.nix
@@ -0,0 +1,132 @@
1{ lib, pkgs, config, myconfig, ... }:
2{
3 config = let
4 cfg = config.services.bind;
5 configFile = pkgs.writeText "named.conf" ''
6 include "/etc/bind/rndc.key";
7 controls {
8 inet 127.0.0.1 allow {localhost;} keys {"rndc-key";};
9 };
10
11 acl cachenetworks { ${lib.concatMapStrings (entry: " ${entry}; ") cfg.cacheNetworks} };
12 acl badnetworks { ${lib.concatMapStrings (entry: " ${entry}; ") cfg.blockedNetworks} };
13
14 options {
15 listen-on { ${lib.concatMapStrings (entry: " ${entry}; ") cfg.listenOn} };
16 listen-on-v6 { ${lib.concatMapStrings (entry: " ${entry}; ") cfg.listenOnIpv6} };
17 allow-query { cachenetworks; };
18 blackhole { badnetworks; };
19 forward first;
20 forwarders { ${lib.concatMapStrings (entry: " ${entry}; ") cfg.forwarders} };
21 directory "/var/run/named";
22 pid-file "/var/run/named/named.pid";
23 ${cfg.extraOptions}
24 };
25
26 ${cfg.extraConfig}
27
28 ${ lib.concatMapStrings
29 ({ name, file, master ? true, extra ? "", slaves ? [], masters ? [] }:
30 ''
31 zone "${name}" {
32 type ${if master then "master" else "slave"};
33 file "${file}";
34 ${ if lib.lists.length slaves > 0 then
35 ''
36 allow-transfer {
37 ${lib.concatMapStrings (ip: "${ip};\n") slaves}
38 };
39 '' else ""}
40 ${ if lib.lists.length masters > 0 then
41 ''
42 masters {
43 ${lib.concatMapStrings (ip: "${ip};\n") masters}
44 };
45 '' else ""}
46 allow-query { any; };
47 ${extra}
48 };
49 '')
50 cfg.zones }
51 '';
52 in
53 {
54 networking.firewall.allowedUDPPorts = [ 53 ];
55 networking.firewall.allowedTCPPorts = [ 53 ];
56 services.bind = {
57 enable = true;
58 cacheNetworks = ["any"];
59 configFile = configFile;
60 extraOptions = ''
61 allow-recursion { 127.0.0.1; };
62 allow-transfer { none; };
63
64 notify-source ${myconfig.env.servers.eldiron.ips.main.ip4};
65 notify-source-v6 ${lib.head myconfig.env.servers.eldiron.ips.main.ip6};
66 version none;
67 hostname none;
68 server-id none;
69 '';
70 zones = with myconfig.env.dns;
71 assert (builtins.substring ((builtins.stringLength soa.email)-1) 1 soa.email) != ".";
72 assert (builtins.substring ((builtins.stringLength soa.primary)-1) 1 soa.primary) != ".";
73 (map (conf: {
74 name = conf.name;
75 master = false;
76 file = "/var/run/named/${conf.name}.zone";
77 masters = if lib.attrsets.hasAttr "masters" conf
78 then lib.lists.flatten (map (n: lib.attrsets.attrValues ns.${n}) conf.masters)
79 else [];
80 }) slaveZones)
81 ++ (map (conf: {
82 name = conf.name;
83 master = true;
84 extra = if lib.attrsets.hasAttr "extra" conf then conf.extra else "";
85 slaves = if lib.attrsets.hasAttr "slaves" conf
86 then lib.lists.flatten (map (n: lib.attrsets.attrValues ns.${n}) conf.slaves)
87 else [];
88 file = pkgs.writeText "${conf.name}.zone" ''
89 $TTL 10800
90 @ IN SOA ${soa.primary}. ${builtins.replaceStrings ["@"] ["."] soa.email}. ${soa.serial} ${soa.refresh} ${soa.retry} ${soa.expire} ${soa.ttl}
91
92 ${lib.concatStringsSep "\n" (map (x: "@ IN NS ${x}.") (lib.concatMap (n: lib.attrsets.mapAttrsToList (k: v: k) ns.${n}) conf.ns))}
93
94 ${conf.entries}
95
96 ${if lib.attrsets.hasAttr "withEmail" conf && lib.lists.length conf.withEmail > 0 then ''
97 mail IN A ${myconfig.env.servers.immaeEu.ips.main.ip4}
98 mx-1 IN A ${myconfig.env.servers.eldiron.ips.main.ip4}
99 ${builtins.concatStringsSep "\n" (map (i: "mail IN AAAA ${i}") myconfig.env.servers.immaeEu.ips.main.ip6)}
100 ${builtins.concatStringsSep "\n" (map (i: "mx-1 IN AAAA ${i}") myconfig.env.servers.eldiron.ips.main.ip6)}
101 ${lib.concatStringsSep "\n\n" (map (e:
102 let
103 n = if e.domain == "" then "@" else "${e.domain} ";
104 suffix = if e.domain == "" then "" else ".${e.domain}";
105 in
106 ''
107 ; ------------------ mail: ${n} ---------------------------
108 ${if e.receive then "${n} IN MX 10 mail.${conf.name}." else ""}
109 ;${if e.receive then "${n} IN MX 50 mx-1.${conf.name}." else ""}
110
111 ; Mail sender authentications
112 ${n} IN TXT "v=spf1 mx ~all"
113 _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;"
114 ${if e.send then ''
115 immae_eu._domainkey${suffix} IN TXT ( "v=DKIM1; k=rsa; s=email; "
116 "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzl3vLd8W5YAuumC5+ZT9OV7/14Pmh5JYtwyqKI3cfe9NnAqInt3xO4bZ7oqIxRKWN4SD39vm7O/QOvFdBt00ENOOzdP90s5gKw6eIP/4+vPTh0IWltAsmu9B2agzdtWUE7t2xFKIzEn8l9niRE2QYbVaqZv4sub98vY55fIgFoHtjkmNC7325S8fjDJGp6OPbyhAs6Xl5/adjF"
117 "0ko4Y2p6RaxLQfjlS0bxmK4Qg6C14pIXHtzVeqOuWrwApqt5+AULSn97iUtqV/IJlEEjC6DUR44t3C/G0G/k46iFclCqRRi0hdPrOHCtZDbtMubnTN9eaUiNpkXh1WnCflHwtjQwIDAQAB" )
118 '' else ""}
119 '') conf.withEmail)}
120 '' + (if conf.name == "immae.eu" then ''
121 ; ----------------- Accept DMARC reports -------------------
122 ${lib.concatStringsSep "\n" (
123 lib.flatten (
124 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
125 )
126 )}
127 '' else "") else ""}
128 '';
129 }) masterZones);
130 };
131 };
132}
diff --git a/modules/private/ftp.nix b/modules/private/ftp.nix
new file mode 100644
index 00000000..842d2d65
--- /dev/null
+++ b/modules/private/ftp.nix
@@ -0,0 +1,118 @@
1{ lib, pkgs, config, myconfig, ... }:
2{
3 options = {
4 services.pure-ftpd.enable = lib.mkOption {
5 type = lib.types.bool;
6 default = false;
7 description = ''
8 Whether to enable pure-ftpd.
9 '';
10 };
11 };
12
13 config = lib.mkIf config.services.pure-ftpd.enable {
14 security.acme.certs."ftp" = config.services.myCertificates.certConfig // {
15 domain = "eldiron.immae.eu";
16 postRun = ''
17 systemctl restart pure-ftpd.service
18 '';
19 extraDomains = { "ftp.immae.eu" = null; };
20 };
21
22 networking = {
23 firewall = {
24 allowedTCPPorts = [ 21 ];
25 allowedTCPPortRanges = [ { from = 40000; to = 50000; } ];
26 };
27 };
28
29 users.users = [
30 {
31 name = "ftp";
32 uid = config.ids.uids.ftp; # 8
33 group = "ftp";
34 description = "Anonymous FTP user";
35 home = "/homeless-shelter";
36 extraGroups = [ "keys" ];
37 }
38 ];
39
40 users.groups.ftp.gid = config.ids.gids.ftp;
41
42 system.activationScripts.pure-ftpd = ''
43 install -m 0755 -o ftp -g ftp -d /var/lib/ftp
44 '';
45
46 secrets.keys = [{
47 dest = "pure-ftpd-ldap";
48 permissions = "0400";
49 user = "ftp";
50 group = "ftp";
51 text = ''
52 LDAPServer ${myconfig.env.ftp.ldap.host}
53 LDAPPort 389
54 LDAPUseTLS True
55 LDAPBaseDN ${myconfig.env.ftp.ldap.base}
56 LDAPBindDN ${myconfig.env.ftp.ldap.dn}
57 LDAPBindPW ${myconfig.env.ftp.ldap.password}
58 LDAPDefaultUID 500
59 LDAPForceDefaultUID False
60 LDAPDefaultGID 100
61 LDAPForceDefaultGID False
62 LDAPFilter ${myconfig.env.ftp.ldap.filter}
63
64 LDAPAuthMethod BIND
65
66 # Pas de possibilite de donner l'Uid/Gid !
67 # Compile dans pure-ftpd directement avec immaeFtpUid / immaeFtpGid
68 LDAPHomeDir immaeFtpDirectory
69 '';
70 }];
71
72 systemd.services.pure-ftpd = let
73 configFile = pkgs.writeText "pure-ftpd.conf" ''
74 PassivePortRange 40000 50000
75 ChrootEveryone yes
76 CreateHomeDir yes
77 BrokenClientsCompatibility yes
78 MaxClientsNumber 50
79 Daemonize yes
80 MaxClientsPerIP 8
81 VerboseLog no
82 DisplayDotFiles yes
83 AnonymousOnly no
84 NoAnonymous no
85 SyslogFacility ftp
86 DontResolve yes
87 MaxIdleTime 15
88 LDAPConfigFile /var/secrets/pure-ftpd-ldap
89 LimitRecursion 10000 8
90 AnonymousCanCreateDirs no
91 MaxLoad 4
92 AntiWarez yes
93 Umask 133:022
94 # ftp
95 MinUID 8
96 AllowUserFXP no
97 AllowAnonymousFXP no
98 ProhibitDotFilesWrite no
99 ProhibitDotFilesRead no
100 AutoRename no
101 AnonymousCantUpload no
102 MaxDiskUsage 99
103 CustomerProof yes
104 TLS 1
105 CertFile ${config.security.acme.directory}/ftp/full.pem
106 '';
107 in {
108 description = "Pure-FTPd server";
109 wantedBy = [ "multi-user.target" ];
110 after = [ "network.target" ];
111
112 serviceConfig.ExecStart = "${pkgs.pure-ftpd}/bin/pure-ftpd ${configFile}";
113 serviceConfig.Type = "forking";
114 serviceConfig.PIDFile = "/run/pure-ftpd.pid";
115 };
116 };
117
118}
diff --git a/modules/private/gitolite/default.nix b/modules/private/gitolite/default.nix
new file mode 100644
index 00000000..b9914a16
--- /dev/null
+++ b/modules/private/gitolite/default.nix
@@ -0,0 +1,63 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 cfg = config.myServices.gitolite;
4in {
5 options.myServices.gitolite = {
6 enable = lib.mkEnableOption "my gitolite service";
7 gitoliteDir = lib.mkOption {
8 type = lib.types.string;
9 default = "/var/lib/gitolite";
10 };
11 };
12
13 config = lib.mkIf cfg.enable {
14 networking.firewall.allowedTCPPorts = [ 9418 ];
15
16 services.gitDaemon = {
17 enable = true;
18 user = "gitolite";
19 group = "gitolite";
20 basePath = "${cfg.gitoliteDir}/repositories";
21 };
22
23 system.activationScripts.gitolite = let
24 gitolite_ldap_groups = pkgs.mylibs.wrap {
25 name = "gitolite_ldap_groups.sh";
26 file = ./gitolite_ldap_groups.sh;
27 vars = {
28 LDAP_PASS = myconfig.env.tools.gitolite.ldap.password;
29 };
30 paths = [ pkgs.openldap pkgs.stdenv.shellPackage pkgs.gnugrep pkgs.coreutils ];
31 };
32 in {
33 deps = [ "users" ];
34 text = ''
35 if [ -d ${cfg.gitoliteDir} ]; then
36 ln -sf ${gitolite_ldap_groups} ${cfg.gitoliteDir}/gitolite_ldap_groups.sh
37 chmod g+rx ${cfg.gitoliteDir}
38 fi
39 if [ -f ${cfg.gitoliteDir}/projects.list ]; then
40 chmod g+r ${cfg.gitoliteDir}/projects.list
41 fi
42 '';
43 };
44
45 users.users.wwwrun.extraGroups = [ "gitolite" ];
46
47 users.users.gitolite.packages = let
48 python-packages = python-packages: with python-packages; [
49 simplejson
50 urllib3
51 sleekxmpp
52 ];
53 in
54 [
55 (pkgs.python3.withPackages python-packages)
56 ];
57 # Installation: https://git.immae.eu/mantisbt/view.php?id=93
58 services.gitolite = {
59 enable = true;
60 adminPubkey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDXqRbiHw7QoHADNIEuo4nUT9fSOIEBMdJZH0bkQAxXyJFyCM1IMz0pxsHV0wu9tdkkr36bPEUj2aV5bkYLBN6nxcV2Y49X8bjOSCPfx3n6Own1h+NeZVBj4ZByrFmqCbTxUJIZ2bZKcWOFncML39VmWdsVhNjg0X4NBBehqXRIKr2gt3E/ESAxTYJFm0BnU0baciw9cN0bsRGqvFgf5h2P48CIAfwhVcGmPQnnAwabnosYQzRWxR0OygH5Kd8mePh6FheIRIigfXsDO8f/jdxwut8buvNIf3m5EBr3tUbTsvM+eV3M5vKGt7sk8T64DVtepTSdOOWtp+47ktsnHOMh immae@immae.eu";
61 };
62 };
63}
diff --git a/modules/private/gitolite/gitolite_ldap_groups.sh b/modules/private/gitolite/gitolite_ldap_groups.sh
new file mode 100755
index 00000000..7db0da40
--- /dev/null
+++ b/modules/private/gitolite/gitolite_ldap_groups.sh
@@ -0,0 +1,15 @@
1#!/usr/bin/env bash
2
3uid_param="$1"
4ldap_host="ldap.immae.eu"
5ldap_binddn="cn=gitolite,ou=services,dc=immae,dc=eu"
6ldap_bindpw="$LDAP_PASS"
7ldap_searchbase="dc=immae,dc=eu"
8ldap_scope="subtree"
9
10ldap_options="-h ${ldap_host} -ZZ -x -D ${ldap_binddn} -w ${ldap_bindpw} -b ${ldap_searchbase} -s ${ldap_scope}"
11
12ldap_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)))"
13ldap_result=$(ldapsearch ${ldap_options} -LLL "${ldap_filter}" cn | grep 'cn:' | cut -d' ' -f2)
14
15echo "$ldap_result"
diff --git a/modules/private/irc.nix b/modules/private/irc.nix
new file mode 100644
index 00000000..b3fe91f4
--- /dev/null
+++ b/modules/private/irc.nix
@@ -0,0 +1,54 @@
1{ lib, pkgs, config, ... }:
2let
3 cfg = config.myServices.irc;
4in
5{
6 options.myServices = {
7 ircCerts = lib.mkOption {
8 description = "Default ircconfigurations for certificates as accepted by acme";
9 };
10 irc.enable = lib.mkOption {
11 type = lib.types.bool;
12 default = false;
13 description = ''
14 Whether to enable irc stuff.
15 '';
16 };
17 };
18
19 config = lib.mkIf cfg.enable {
20 security.acme.certs."irc" = config.myServices.ircCerts // {
21 domain = "irc.immae.eu";
22 postRun = ''
23 systemctl restart stunnel.service
24 '';
25 };
26
27 networking.firewall.allowedTCPPorts = [ 6697 ];
28 services.bitlbee = with pkgs; {
29 enable = true;
30 authMode = "Registered";
31 libpurple_plugins = [
32 purple-hangouts
33 purple-matrix
34 ];
35 plugins = [
36 bitlbee-mastodon
37 bitlbee-facebook
38 bitlbee-discord
39 bitlbee-steam
40 ];
41 };
42
43 services.stunnel = {
44 enable = true;
45 servers = {
46 bitlbee = {
47 accept = 6697;
48 connect = 6667;
49 cert = "${config.security.acme.directory}/irc/full.pem";
50 };
51 };
52 };
53 };
54}
diff --git a/modules/private/mail.nix b/modules/private/mail.nix
new file mode 100644
index 00000000..611c8b41
--- /dev/null
+++ b/modules/private/mail.nix
@@ -0,0 +1,13 @@
1{ lib, pkgs, config, myconfig, ... }:
2{
3 config.users.users.nullmailer.uid = config.ids.uids.nullmailer;
4 config.users.groups.nullmailer.gid = config.ids.gids.nullmailer;
5
6 config.services.nullmailer = {
7 enable = true;
8 config = {
9 me = myconfig.env.mail.host;
10 remotes = "${myconfig.env.mail.relay} smtp";
11 };
12 };
13}
diff --git a/modules/private/mpd.nix b/modules/private/mpd.nix
new file mode 100644
index 00000000..9903bdf0
--- /dev/null
+++ b/modules/private/mpd.nix
@@ -0,0 +1,56 @@
1{ lib, pkgs, config, myconfig, ... }:
2{
3 config = {
4 secrets.keys = [
5 {
6 dest = "mpd";
7 permissions = "0400";
8 text = myconfig.env.mpd.password;
9 }
10 {
11 dest = "mpd-config";
12 permissions = "0400";
13 user = "mpd";
14 group = "mpd";
15 text = ''
16 password "${myconfig.env.mpd.password}@read,add,control,admin"
17 '';
18 }
19 ];
20 networking.firewall.allowedTCPPorts = [ 6600 ];
21 users.users.mpd.extraGroups = [ "wwwrun" "keys" ];
22 systemd.services.mpd.serviceConfig.RuntimeDirectory = "mpd";
23 services.mpd = {
24 enable = true;
25 network.listenAddress = "any";
26 musicDirectory = myconfig.env.mpd.folder;
27 extraConfig = ''
28 include "/var/secrets/mpd-config"
29 audio_output {
30 type "null"
31 name "No Output"
32 mixer_type "none"
33 }
34 audio_output {
35 type "httpd"
36 name "OGG"
37 encoder "vorbis"
38 bind_to_address "/run/mpd/ogg.sock"
39 quality "5.0"
40 format "44100:16:1"
41 }
42 audio_output {
43 type "httpd"
44 name "MP3"
45 encoder "lame"
46 bind_to_address "/run/mpd/mp3.sock"
47 quality "5.0"
48 format "44100:16:1"
49 }
50
51
52 '';
53 };
54 };
55}
56
diff --git a/modules/private/pub/default.nix b/modules/private/pub/default.nix
new file mode 100644
index 00000000..c31c8eb0
--- /dev/null
+++ b/modules/private/pub/default.nix
@@ -0,0 +1,52 @@
1{ lib, pkgs, config, myconfig, ... }:
2{
3 options = {
4 myServices.pub.enable = lib.mkOption {
5 type = lib.types.bool;
6 default = false;
7 description = ''
8 Whether to enable pub user.
9 '';
10 };
11 };
12
13 config = lib.mkIf config.myServices.pub.enable {
14 users.users.pub = let
15 restrict = pkgs.runCommand "restrict" {
16 file = ./restrict;
17 buildInputs = [ pkgs.makeWrapper ];
18 } ''
19 mkdir -p $out/bin
20 cp $file $out/bin/restrict
21 chmod a+x $out/bin/restrict
22 patchShebangs $out/bin/restrict
23 wrapProgram $out/bin/restrict \
24 --prefix PATH : ${lib.makeBinPath [ pkgs.bubblewrap pkgs.rrsync ]} \
25 --set TMUX_RESTRICT ${./tmux.restrict.conf}
26 '';
27 purple-hangouts = pkgs.purple-hangouts.overrideAttrs(old: {
28 installPhase = ''
29 install -Dm755 -t $out/lib/purple-2/ libhangouts.so
30 for size in 16 22 24 48; do
31 install -TDm644 hangouts$size.png $out/share/pixmaps/pidgin/protocols/$size/hangouts.png
32 done
33 '';
34 });
35 in {
36 createHome = true;
37 description = "Restricted shell user";
38 home = "/var/lib/pub";
39 uid = myconfig.env.users.pub.uid;
40 useDefaultShell = true;
41 packages = [
42 restrict
43 pkgs.tmux
44 (pkgs.pidgin.override { plugins = [
45 pkgs.purple-plugin-pack purple-hangouts
46 pkgs.purple-discord pkgs.purple-facebook
47 pkgs.telegram-purple
48 ]; })
49 ];
50 };
51 };
52}
diff --git a/modules/private/pub/restrict b/modules/private/pub/restrict
new file mode 100644
index 00000000..b2f3be36
--- /dev/null
+++ b/modules/private/pub/restrict
@@ -0,0 +1,64 @@
1#!/usr/bin/env bash
2user="$1"
3rootuser="$HOME/$user/"
4mkdir -p $rootuser
5
6orig="$SSH_ORIGINAL_COMMAND"
7if [ -z "$orig" ]; then
8 orig="/bin/bash -l"
9fi
10if [ "${orig:0:7}" = "command" ]; then
11 orig="${orig:8}"
12fi
13
14case "$orig" in
15rsync*)
16 rrsync $HOME/$user/
17 ;;
18*)
19 nix_store_paths() {
20 nix-store -q -R \
21 /run/current-system/sw \
22 /etc/profiles/per-user/pub \
23 /etc/ssl/certs/ca-bundle.crt \
24 | while read i; do
25 printf '%s--ro-bind\0'$i'\0'$i'\0' ''
26 done
27 }
28
29 set -euo pipefail
30 (exec -c bwrap --ro-bind /usr /usr \
31 --args 10 \
32 --dir /tmp \
33 --dir /var \
34 --symlink ../tmp var/tmp \
35 --proc /proc \
36 --dev /dev \
37 --ro-bind /etc/resolv.conf /etc/resolv.conf \
38 --ro-bind /etc/zoneinfo /etc/zoneinfo \
39 --ro-bind /etc/ssl /etc/ssl \
40 --ro-bind /etc/static/ssl/certs /etc/static/ssl/certs \
41 --ro-bind /run/current-system/sw/lib/locale/locale-archive /etc/locale-archive \
42 --ro-bind /run/current-system/sw/bin /bin \
43 --ro-bind /etc/profiles/per-user/pub/bin /bin-pub \
44 --bind /var/lib/pub/$user /var/lib/pub \
45 --dir /var/lib/commons \
46 --ro-bind $TMUX_RESTRICT /var/lib/commons/tmux.restrict.conf \
47 --chdir /var/lib/pub \
48 --unshare-all \
49 --share-net \
50 --dir /run/user/$(id -u) \
51 --setenv TERM "$TERM" \
52 --setenv LOCALE_ARCHIVE "/etc/locale-archive" \
53 --setenv XDG_RUNTIME_DIR "/run/user/`id -u`" \
54 --setenv PS1 "$user@pub $ " \
55 --setenv PATH "/bin:/bin-pub" \
56 --setenv HOME "/var/lib/pub" \
57 --file 11 /etc/passwd \
58 --file 12 /etc/group \
59 -- $orig) \
60 10< <(nix_store_paths) \
61 11< <(getent passwd $UID 65534) \
62 12< <(getent group $(id -g) 65534)
63 ;;
64esac
diff --git a/modules/private/pub/tmux.restrict.conf b/modules/private/pub/tmux.restrict.conf
new file mode 100644
index 00000000..5aefd1c7
--- /dev/null
+++ b/modules/private/pub/tmux.restrict.conf
@@ -0,0 +1,43 @@
1# Pour les nostalgiques de screen
2# comme les raccourcis ne sont pas les mêmes, j'évite
3set -g prefix C-a
4unbind-key C-b
5
6unbind-key -a
7bind-key -n C-h list-keys
8bind-key C-d detach
9bind-key & confirm-before -p "kill-window #W? (y/n)" kill-window
10
11# même hack que sur screen lorsqu'on veut profiter du scroll du terminal
12# (xterm ...)
13set -g terminal-overrides 'xterm*:smcup@:rmcup@'
14
15#Pour les ctrl+arrow
16set-option -g xterm-keys on
17
18# c'est un minimum (defaut 2000)
19set-option -g history-limit 10000
20
21# lorsque j'ai encore un tmux ailleurs seule
22# sa fenetre active réduit la taille de ma fenetre locale
23setw -g aggressive-resize on
24
25# Pour etre alerté sur un changement dans une autre fenêtre
26setw -g monitor-activity on
27#set -g visual-activity on
28#set -g visual-bell on
29
30set -g base-index 1
31
32# repercuter le contenu de la fenetre dans la barre de titre
33# reference des string : man tmux (status-left)
34set -g set-titles on
35set -g set-titles-string '#H #W #T' # host window command
36
37#Dans les valeurs par defaut deja, avec le ssh-agent
38set -g update-environment "DISPLAY SSH_ASKPASS SSH_AUTH_SOCK SSH_AGENT_PID SSH_CONNECTION WINDOWID XAUTHORITY PATH"
39
40set -g status off
41set -g status-left ''
42set -g status-right ''
43
diff --git a/modules/private/ssh/default.nix b/modules/private/ssh/default.nix
new file mode 100644
index 00000000..beedaff5
--- /dev/null
+++ b/modules/private/ssh/default.nix
@@ -0,0 +1,40 @@
1{ lib, pkgs, config, myconfig, ... }:
2{
3 config = {
4 networking.firewall.allowedTCPPorts = [ 22 ];
5
6 services.openssh.extraConfig = ''
7 AuthorizedKeysCommand /etc/ssh/ldap_authorized_keys
8 AuthorizedKeysCommandUser nobody
9 '';
10
11 secrets.keys = [{
12 dest = "ssh-ldap";
13 user = "nobody";
14 group = "nogroup";
15 permissions = "0400";
16 text = myconfig.env.sshd.ldap.password;
17 }];
18 system.activationScripts.sshd = {
19 deps = [ "secrets" ];
20 text = ''
21 install -Dm400 -o nobody -g nogroup -T /var/secrets/ssh-ldap /etc/ssh/ldap_password
22 '';
23 };
24 # ssh is strict about parent directory having correct rights, don't
25 # move it in the nix store.
26 environment.etc."ssh/ldap_authorized_keys" = let
27 ldap_authorized_keys =
28 pkgs.mylibs.wrap {
29 name = "ldap_authorized_keys";
30 file = ./ldap_authorized_keys.sh;
31 paths = [ pkgs.which pkgs.gitolite pkgs.openldap pkgs.stdenv.shellPackage pkgs.gnugrep pkgs.gnused pkgs.coreutils ];
32 };
33 in {
34 enable = true;
35 mode = "0755";
36 user = "root";
37 source = ldap_authorized_keys;
38 };
39 };
40}
diff --git a/modules/private/ssh/ldap_authorized_keys.sh b/modules/private/ssh/ldap_authorized_keys.sh
new file mode 100755
index 00000000..d556452d
--- /dev/null
+++ b/modules/private/ssh/ldap_authorized_keys.sh
@@ -0,0 +1,152 @@
1#!/usr/bin/env bash
2
3LDAPSEARCH=ldapsearch
4KEY="immaeSshKey"
5LDAP_BIND="cn=ssh,ou=services,dc=immae,dc=eu"
6LDAP_PASS=$(cat /etc/ssh/ldap_password)
7LDAP_HOST="ldap.immae.eu"
8LDAP_MEMBER="cn=users,cn=ssh,ou=services,dc=immae,dc=eu"
9LDAP_GITOLITE_MEMBER="cn=users,cn=gitolite,ou=services,dc=immae,dc=eu"
10LDAP_PUB_RESTRICT_MEMBER="cn=restrict,cn=pub,ou=services,dc=immae,dc=eu"
11LDAP_PUB_FORWARD_MEMBER="cn=forward,cn=pub,ou=services,dc=immae,dc=eu"
12LDAP_BASE="dc=immae,dc=eu"
13GITOLITE_SHELL=$(which gitolite-shell)
14ECHO=$(which echo)
15
16suitable_for() {
17 type_for="$1"
18 key="$2"
19
20 if [[ $key != *$'\n'* ]] && [[ $key == ssh-* ]]; then
21 echo "$key"
22 else
23 key_type=$(cut -d " " -f 1 <<< "$key")
24
25 if grep -q "\b-$type_for\b" <<< "$key_type"; then
26 echo ""
27 elif grep -q "\b$type_for\b" <<< "$key_type"; then
28 echo $(sed -e "s/^[^ ]* //g" <<< "$key")
29 else
30 echo ""
31 fi
32 fi
33}
34
35clean_key_line() {
36 type_for="$1"
37 line="$2"
38
39 if [[ "$line" == $KEY::* ]]; then
40 # base64 keys should't happen, unless wrong copy-pasting
41 key=""
42 else
43 key=$(sed -e "s/^$KEY: *//" -e "s/ *$//" <<< "$line")
44 fi
45
46 suitable_for "$type_for" "$key"
47}
48
49ldap_search() {
50 $LDAPSEARCH -h $LDAP_HOST -ZZ -b $LDAP_BASE -D $LDAP_BIND -w "$LDAP_PASS" -x -o ldif-wrap=no -LLL "$@"
51}
52
53ldap_keys() {
54 user=$1;
55 if [[ $user == gitolite ]]; then
56 ldap_search '(&(memberOf='$LDAP_GITOLITE_MEMBER')('$KEY'=*))' $KEY | \
57 while read line ;
58 do
59 if [ ! -z "$line" ]; then
60 if [[ $line == dn* ]]; then
61 user=$(sed -n 's/.*uid=\([^,]*\).*/\1/p' <<< "$line")
62 if [ -n "$user" ]; then
63 if [[ $user == "immae" ]] || [[ $user == "denise" ]]; then
64 # Capitalize first letter (backward compatibility)
65 user=$(sed -r 's/^([a-z])/\U\1/' <<< "$user")
66 fi
67 else
68 # Service fake user
69 user=$(sed -n 's/.*cn=\([^,]*\).*/\1/p' <<< "$line")
70 fi
71 elif [[ $line == $KEY* ]]; then
72 key=$(clean_key_line git "$line")
73 if [ ! -z "$key" ]; then
74 if [[ $key != *$'\n'* ]] && [[ $key == ssh-* ]]; then
75 echo -n 'command="'$GITOLITE_SHELL' '$user'",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty '
76 echo $key
77 fi
78 fi
79 fi
80 fi
81 done
82 exit 0
83 elif [[ $user == pub ]]; then
84 ldap_search '(&(memberOf='$LDAP_PUB_RESTRICT_MEMBER')('$KEY'=*))' $KEY | \
85 while read line ;
86 do
87 if [ ! -z "$line" ]; then
88 if [[ $line == dn* ]]; then
89 echo ""
90 user=$(sed -n 's/.*uid=\([^,]*\).*/\1/p' <<< "$line")
91 echo "# $user"
92 elif [[ $line == $KEY* ]]; then
93 key=$(clean_key_line pub "$line")
94 key_forward=$(clean_key_line forward "$line")
95 if [ ! -z "$key" ]; then
96 if [[ $key != *$'\n'* ]] && [[ $key == ssh-* ]]; then
97 echo -n 'command="/etc/profiles/per-user/pub/bin/restrict '$user'" '
98 echo $key
99 fi
100 elif [ ! -z "$key_forward" ]; then
101 if [[ $key_forward != *$'\n'* ]] && [[ $key_forward == ssh-* ]]; then
102 echo "# forward only"
103 echo -n 'no-pty,no-X11-forwarding,command="'$ECHO' forward only" '
104 echo $key_forward
105 fi
106 fi
107 fi
108 fi
109 done
110
111 echo ""
112 ldap_search '(&(memberOf='$LDAP_PUB_FORWARD_MEMBER')('$KEY'=*))' $KEY | \
113 while read line ;
114 do
115 if [ ! -z "$line" ]; then
116 if [[ $line == dn* ]]; then
117 echo ""
118 user=$(sed -n 's/.*uid=\([^,]*\).*/\1/p' <<< "$line")
119 echo "# $user"
120 elif [[ $line == $KEY* ]]; then
121 key=$(clean_key_line forward "$line")
122 if [ ! -z "$key" ]; then
123 if [[ $key != *$'\n'* ]] && [[ $key == ssh-* ]]; then
124 echo -n 'no-pty,no-X11-forwarding,command="'$ECHO' forward only" '
125 echo $key
126 fi
127 fi
128 fi
129 fi
130 done
131 exit 0
132 else
133 ldap_search '(&(memberOf='$LDAP_MEMBER')('$KEY'=*)(uid='$user'))' $KEY | \
134 while read line ;
135 do
136 if [ ! -z "$line" ]; then
137 if [[ $line == dn* ]]; then
138 user=$(sed -n 's/.*uid=\([^,]*\).*/\1/p' <<< "$line")
139 elif [[ $line == $KEY* ]]; then
140 key=$(clean_key_line ssh "$line")
141 if [ ! -z "$key" ]; then
142 if [[ $key != *$'\n'* ]] && [[ $key == ssh-* ]]; then
143 echo $key
144 fi
145 fi
146 fi
147 fi
148 done
149 fi
150}
151
152ldap_keys $@
diff --git a/modules/private/system.nix b/modules/private/system.nix
new file mode 100644
index 00000000..fba504e9
--- /dev/null
+++ b/modules/private/system.nix
@@ -0,0 +1,30 @@
1{ pkgs, privateFiles, ... }:
2{
3 config = {
4 nixpkgs.overlays = builtins.attrValues (import ../../overlays);
5 _module.args = {
6 pkgsNext = import <nixpkgsNext> {};
7 pkgsPrevious = import <nixpkgsPrevious> {};
8 myconfig = {
9 inherit privateFiles;
10 env = import "${privateFiles}/environment.nix";
11 };
12 };
13
14 services.journald.extraConfig = ''
15 MaxLevelStore="warning"
16 MaxRetentionSec="1year"
17 '';
18
19 users.users.root.packages = [
20 pkgs.telnet
21 pkgs.htop
22 pkgs.iftop
23 ];
24
25 environment.systemPackages = [
26 pkgs.vim
27 ];
28
29 };
30}
diff --git a/modules/private/system/eldiron.nix b/modules/private/system/eldiron.nix
new file mode 100644
index 00000000..b71df33d
--- /dev/null
+++ b/modules/private/system/eldiron.nix
@@ -0,0 +1,63 @@
1{ privateFiles }:
2{ config, pkgs, myconfig, ... }:
3{
4 boot.kernelPackages = pkgs.linuxPackages_latest;
5 _module.args.privateFiles = privateFiles;
6
7 networking = {
8 firewall.enable = true;
9 # 176.9.151.89 declared in nixops -> infra / tools
10 interfaces."eth0".ipv4.addresses = pkgs.lib.attrsets.mapAttrsToList
11 (n: ips: { address = ips.ip4; prefixLength = 32; })
12 (pkgs.lib.attrsets.filterAttrs (n: v: n != "main") myconfig.env.servers.eldiron.ips);
13 interfaces."eth0".ipv6.addresses = pkgs.lib.flatten (pkgs.lib.attrsets.mapAttrsToList
14 (n: ips: map (ip: { address = ip; prefixLength = (if n == "main" && ip == pkgs.lib.head ips.ip6 then 64 else 128); }) (ips.ip6 or []))
15 myconfig.env.servers.eldiron.ips);
16 };
17
18 imports = builtins.attrValues (import ../..);
19
20 myServices.buildbot.enable = true;
21 myServices.databases.enable = true;
22 myServices.gitolite.enable = true;
23 myServices.irc.enable = true;
24 myServices.pub.enable = true;
25 myServices.tasks.enable = true;
26 services.pure-ftpd.enable = true;
27
28 deployment = {
29 targetEnv = "hetzner";
30 hetzner = {
31 robotUser = myconfig.env.hetzner.user;
32 robotPass = myconfig.env.hetzner.pass;
33 mainIPv4 = myconfig.env.servers.eldiron.ips.main.ip4;
34 partitions = ''
35 clearpart --all --initlabel --drives=sda,sdb
36
37 part swap1 --recommended --label=swap1 --fstype=swap --ondisk=sda
38 part swap2 --recommended --label=swap2 --fstype=swap --ondisk=sdb
39
40 part raid.1 --grow --ondisk=sda
41 part raid.2 --grow --ondisk=sdb
42
43 raid / --level=1 --device=md0 --fstype=ext4 --label=root raid.1 raid.2
44 '';
45 };
46 };
47
48 services.cron = {
49 enable = true;
50 systemCronJobs = [
51 ''
52 # The star after /var/lib/* avoids deleting all folders in case of problem
53 0 3,9,15,21 * * * root rsync -e "ssh -i /root/.ssh/id_charon_vpn" -aAXvz --delete --numeric-ids --super --rsync-path="sudo rsync" /var/lib/* immae@immae.eu: > /dev/null
54 ''
55 ];
56 };
57
58 # This value determines the NixOS release with which your system is
59 # to be compatible, in order to avoid breaking some software such as
60 # database servers. You should change this only after NixOS release
61 # notes say you should.
62 system.stateVersion = "18.09"; # Did you read the comment?
63}
diff --git a/modules/private/tasks/default.nix b/modules/private/tasks/default.nix
new file mode 100644
index 00000000..30f49ee9
--- /dev/null
+++ b/modules/private/tasks/default.nix
@@ -0,0 +1,327 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 cfg = config.myServices.tasks;
4 server_vardir = config.services.taskserver.dataDir;
5 fqdn = "task.immae.eu";
6 user = config.services.taskserver.user;
7 env = myconfig.env.tools.task;
8 group = config.services.taskserver.group;
9 taskserver-user-certs = pkgs.runCommand "taskserver-user-certs" {} ''
10 mkdir -p $out/bin
11 cat > $out/bin/taskserver-user-certs <<"EOF"
12 #!/usr/bin/env bash
13
14 user=$1
15
16 silent_certtool() {
17 if ! output="$("${pkgs.gnutls.bin}/bin/certtool" "$@" 2>&1)"; then
18 echo "GNUTLS certtool invocation failed with output:" >&2
19 echo "$output" >&2
20 fi
21 }
22
23 silent_certtool -p \
24 --bits 4096 \
25 --outfile "${server_vardir}/userkeys/$user.key.pem"
26 ${pkgs.gnused}/bin/sed -i -n -e '/^-----BEGIN RSA PRIVATE KEY-----$/,$p' "${server_vardir}/userkeys/$user.key.pem"
27
28 silent_certtool -c \
29 --template "${pkgs.writeText "taskserver-ca.template" ''
30 tls_www_client
31 encryption_key
32 signing_key
33 expiration_days = 3650
34 ''}" \
35 --load-ca-certificate "${server_vardir}/keys/ca.cert" \
36 --load-ca-privkey "${server_vardir}/keys/ca.key" \
37 --load-privkey "${server_vardir}/userkeys/$user.key.pem" \
38 --outfile "${server_vardir}/userkeys/$user.cert.pem"
39 EOF
40 chmod a+x $out/bin/taskserver-user-certs
41 patchShebangs $out/bin/taskserver-user-certs
42 '';
43 taskwarrior-web = pkgs.webapps.taskwarrior-web;
44 socketsDir = "/run/taskwarrior-web";
45 varDir = "/var/lib/taskwarrior-web";
46 taskwebPages = let
47 uidPages = lib.attrsets.zipAttrs (
48 lib.lists.flatten
49 (lib.attrsets.mapAttrsToList (k: c: map (v: { "${v}" = k; }) c.uid) env.taskwarrior-web)
50 );
51 pages = lib.attrsets.mapAttrs (uid: items:
52 if lib.lists.length items == 1 then
53 ''
54 <html>
55 <head>
56 <meta http-equiv="refresh" content="0; url=/taskweb/${lib.lists.head items}/" />
57 </head>
58 <body></body>
59 </html>
60 ''
61 else
62 ''
63 <html>
64 <head>
65 <title>To-do list disponibles</title>
66 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
67 <meta name="viewport" content="width=device-width, initial-scale=1" />
68 </head>
69 <body>
70 <ul>
71 ${builtins.concatStringsSep "\n" (map (item: "<li><a href='/taskweb/${item}'>${item}</a></li>") items)}
72 </ul>
73 </body>
74 </html>
75 ''
76 ) uidPages;
77 in
78 pkgs.runCommand "taskwerver-pages" {} ''
79 mkdir -p $out/
80 ${builtins.concatStringsSep "\n" (lib.attrsets.mapAttrsToList (k: v: "cp ${pkgs.writeText k v} $out/${k}.html") pages)}
81 echo "Please login" > $out/index.html
82 '';
83in {
84 options.myServices.tasks = {
85 enable = lib.mkEnableOption "my tasks service";
86 };
87
88 config = lib.mkIf cfg.enable {
89 secrets.keys = [{
90 dest = "webapps/tools-taskwarrior-web";
91 user = "wwwrun";
92 group = "wwwrun";
93 permissions = "0400";
94 text = ''
95 SetEnv TASKD_HOST "${fqdn}:${toString config.services.taskserver.listenPort}"
96 SetEnv TASKD_VARDIR "${server_vardir}"
97 SetEnv TASKD_LDAP_HOST "ldaps://${env.ldap.host}"
98 SetEnv TASKD_LDAP_DN "${env.ldap.dn}"
99 SetEnv TASKD_LDAP_PASSWORD "${env.ldap.password}"
100 SetEnv TASKD_LDAP_BASE "${env.ldap.base}"
101 SetEnv TASKD_LDAP_FILTER "${env.ldap.search}"
102 '';
103 }];
104 services.websites.tools.modules = [ "proxy_fcgi" "sed" ];
105 services.websites.tools.vhostConfs.task = {
106 certName = "eldiron";
107 addToCerts = true;
108 hosts = [ "task.immae.eu" ];
109 root = "/run/current-system/webapps/_task";
110 extraConfig = [ ''
111 <Directory /run/current-system/webapps/_task>
112 DirectoryIndex index.php
113 Use LDAPConnect
114 Require ldap-group cn=users,cn=taskwarrior,ou=services,dc=immae,dc=eu
115 <FilesMatch "\.php$">
116 SetHandler "proxy:unix:/var/run/phpfpm/task.sock|fcgi://localhost"
117 </FilesMatch>
118 Include /var/secrets/webapps/tools-taskwarrior-web
119 </Directory>
120 ''
121 ''
122 <Macro Taskwarrior %{folderName}>
123 ProxyPass "unix://${socketsDir}/%{folderName}.sock|http://localhost-%{folderName}/"
124 ProxyPassReverse "unix://${socketsDir}/%{folderName}.sock|http://localhost-%{folderName}/"
125 ProxyPassReverse http://${fqdn}/
126
127 SetOutputFilter Sed
128 OutputSed "s|/ajax|/taskweb/%{folderName}/ajax|g"
129 OutputSed "s|\([^x]\)/tasks|\1/taskweb/%{folderName}/tasks|g"
130 OutputSed "s|\([^x]\)/projects|\1/taskweb/%{folderName}/projects|g"
131 OutputSed "s|http://${fqdn}/|/taskweb/%{folderName}/|g"
132 OutputSed "s|/img/relax.jpg|/taskweb/%{folderName}/img/relax.jpg|g"
133 </Macro>
134 ''
135 ''
136 Alias /taskweb ${taskwebPages}
137 <Directory "${taskwebPages}">
138 DirectoryIndex index.html
139 Require all granted
140 </Directory>
141
142 RewriteEngine on
143 RewriteRule ^/taskweb$ /taskweb/ [R=301,L]
144 RedirectMatch permanent ^/taskweb/([^/]+)$ /taskweb/$1/
145
146 RewriteCond %{LA-U:REMOTE_USER} !=""
147 RewriteCond ${taskwebPages}/%{LA-U:REMOTE_USER}.html -f
148 RewriteRule ^/taskweb/?$ ${taskwebPages}/%{LA-U:REMOTE_USER}.html [L]
149
150 <Location /taskweb/>
151 Use LDAPConnect
152 Require ldap-group cn=users,cn=taskwarrior,ou=services,dc=immae,dc=eu
153 </Location>
154 ''
155 ] ++ (lib.attrsets.mapAttrsToList (k: v: ''
156 <Location /taskweb/${k}/>
157 ${builtins.concatStringsSep "\n" (map (uid: "Require ldap-attribute uid=${uid}") v.uid)}
158
159 Use Taskwarrior ${k}
160 </Location>
161 '') env.taskwarrior-web);
162 };
163 services.phpfpm.poolConfigs = {
164 tasks = ''
165 listen = /var/run/phpfpm/task.sock
166 user = ${user}
167 group = ${group}
168 listen.owner = wwwrun
169 listen.group = wwwrun
170 pm = dynamic
171 pm.max_children = 60
172 pm.start_servers = 2
173 pm.min_spare_servers = 1
174 pm.max_spare_servers = 10
175
176 ; Needed to avoid clashes in browser cookies (same domain)
177 env[PATH] = "/etc/profiles/per-user/${user}/bin"
178 php_value[session.name] = TaskPHPSESSID
179 php_admin_value[open_basedir] = "${./www}:/tmp:${server_vardir}:/etc/profiles/per-user/${user}/bin/"
180 '';
181 };
182
183 myServices.websites.webappDirs._task = ./www;
184
185 security.acme.certs."task" = config.services.myCertificates.certConfig // {
186 inherit user group;
187 plugins = [ "fullchain.pem" "key.pem" "cert.pem" "account_key.json" ];
188 domain = fqdn;
189 postRun = ''
190 systemctl restart taskserver.service
191 '';
192 };
193
194 users.users.${user}.packages = [ taskserver-user-certs ];
195
196 system.activationScripts.taskserver = {
197 deps = [ "users" ];
198 text = ''
199 install -m 0750 -o ${user} -g ${group} -d ${server_vardir}
200 install -m 0750 -o ${user} -g ${group} -d ${server_vardir}/userkeys
201 install -m 0750 -o ${user} -g ${group} -d ${server_vardir}/keys
202
203 if [ ! -e "${server_vardir}/keys/ca.key" ]; then
204 silent_certtool() {
205 if ! output="$("${pkgs.gnutls.bin}/bin/certtool" "$@" 2>&1)"; then
206 echo "GNUTLS certtool invocation failed with output:" >&2
207 echo "$output" >&2
208 fi
209 }
210
211 silent_certtool -p \
212 --bits 4096 \
213 --outfile "${server_vardir}/keys/ca.key"
214
215 silent_certtool -s \
216 --template "${pkgs.writeText "taskserver-ca.template" ''
217 cn = ${fqdn}
218 expiration_days = -1
219 cert_signing_key
220 ca
221 ''}" \
222 --load-privkey "${server_vardir}/keys/ca.key" \
223 --outfile "${server_vardir}/keys/ca.cert"
224
225 chown :${group} "${server_vardir}/keys/ca.key"
226 chmod g+r "${server_vardir}/keys/ca.key"
227 fi
228 '';
229 };
230
231 services.taskserver = {
232 enable = true;
233 allowedClientIDs = [ "^task [2-9]" "^Mirakel [1-9]" ];
234 inherit fqdn;
235 listenHost = "::";
236 pki.manual.ca.cert = "${server_vardir}/keys/ca.cert";
237 pki.manual.server.cert = "${config.security.acme.directory}/task/fullchain.pem";
238 pki.manual.server.crl = "${config.security.acme.directory}/task/invalid.crl";
239 pki.manual.server.key = "${config.security.acme.directory}/task/key.pem";
240 requestLimit = 104857600;
241 };
242
243 system.activationScripts.taskwarrior-web = {
244 deps = [ "users" ];
245 text = ''
246 if [ ! -f ${server_vardir}/userkeys/taskwarrior-web.cert.pem ]; then
247 ${taskserver-user-certs}/bin/taskserver-user-certs taskwarrior-web
248 chown taskd:taskd ${server_vardir}/userkeys/taskwarrior-web.cert.pem ${server_vardir}/userkeys/taskwarrior-web.key.pem
249 fi
250 '';
251 };
252
253 systemd.services = (lib.attrsets.mapAttrs' (name: userConfig:
254 let
255 credentials = "${userConfig.org}/${name}/${userConfig.key}";
256 dateFormat = userConfig.date;
257 taskrc = pkgs.writeText "taskrc" ''
258 data.location=${varDir}/${name}
259 taskd.certificate=${server_vardir}/userkeys/taskwarrior-web.cert.pem
260 taskd.key=${server_vardir}/userkeys/taskwarrior-web.key.pem
261 # IdenTrust DST Root CA X3
262 # obtained here: https://letsencrypt.org/fr/certificates/
263 taskd.ca=${pkgs.writeText "ca.cert" ''
264 -----BEGIN CERTIFICATE-----
265 MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
266 MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
267 DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
268 PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
269 Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
270 AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
271 rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
272 OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
273 xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
274 7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
275 aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
276 HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
277 SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
278 ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
279 AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
280 R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
281 JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
282 Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
283 -----END CERTIFICATE-----''}
284 taskd.server=${fqdn}:${toString config.services.taskserver.listenPort}
285 taskd.credentials=${credentials}
286 dateformat=${dateFormat}
287 '';
288 in lib.attrsets.nameValuePair "taskwarrior-web-${name}" {
289 description = "Taskwarrior webapp for ${name}";
290 wantedBy = [ "multi-user.target" ];
291 after = [ "network.target" ];
292 path = [ pkgs.taskwarrior ];
293
294 environment.TASKRC = taskrc;
295 environment.BUNDLE_PATH = "${taskwarrior-web.gems}/${taskwarrior-web.gems.ruby.gemPath}";
296 environment.BUNDLE_GEMFILE = "${taskwarrior-web.gems.confFiles}/Gemfile";
297 environment.LC_ALL = "fr_FR.UTF-8";
298
299 script = ''
300 exec ${taskwarrior-web.gems}/${taskwarrior-web.gems.ruby.gemPath}/bin/bundle exec thin start -R config.ru -S ${socketsDir}/${name}.sock
301 '';
302
303 serviceConfig = {
304 User = user;
305 PrivateTmp = true;
306 Restart = "always";
307 TimeoutSec = 60;
308 Type = "simple";
309 WorkingDirectory = taskwarrior-web;
310 StateDirectoryMode = 0750;
311 StateDirectory = assert lib.strings.hasPrefix "/var/lib/" varDir;
312 (lib.strings.removePrefix "/var/lib/" varDir + "/${name}");
313 RuntimeDirectoryPreserve = "yes";
314 RuntimeDirectory = assert lib.strings.hasPrefix "/run/" socketsDir;
315 lib.strings.removePrefix "/run/" socketsDir;
316 };
317
318 unitConfig.RequiresMountsFor = varDir;
319 }) env.taskwarrior-web) // {
320 taskserver-ca.postStart = ''
321 chown :${group} "${server_vardir}/keys/ca.key"
322 chmod g+r "${server_vardir}/keys/ca.key"
323 '';
324 };
325
326 };
327}
diff --git a/modules/private/tasks/www/index.php b/modules/private/tasks/www/index.php
new file mode 100644
index 00000000..deaf8af1
--- /dev/null
+++ b/modules/private/tasks/www/index.php
@@ -0,0 +1,157 @@
1<?php
2if (!isset($_SERVER["REMOTE_USER"])) {
3 die("please login");
4}
5$ldap_user = $_SERVER["REMOTE_USER"];
6$ldap_host = getenv("TASKD_LDAP_HOST");
7$ldap_dn = getenv('TASKD_LDAP_DN');
8$ldap_password = getenv('TASKD_LDAP_PASSWORD');
9$ldap_base = getenv('TASKD_LDAP_BASE');
10$ldap_filter = getenv('TASKD_LDAP_FILTER');
11$host = getenv('TASKD_HOST');
12$vardir = getenv('TASKD_VARDIR');
13
14$connect = ldap_connect($ldap_host);
15ldap_set_option($connect, LDAP_OPT_PROTOCOL_VERSION, 3);
16if (!$connect || !ldap_bind($connect, $ldap_dn, $ldap_password)) {
17 die("impossible to connect to LDAP");
18}
19
20$search_query = str_replace('%login%', ldap_escape($ldap_user), $ldap_filter);
21
22$search = ldap_search($connect, $ldap_base, $search_query);
23$info = ldap_get_entries($connect, $search);
24
25if (ldap_count_entries($connect, $search) != 1) {
26 die("Impossible to find user in LDAP");
27}
28
29$entries = [];
30foreach($info[0]["immaetaskid"] as $key => $value) {
31 if ($key !== "count") {
32 $entries[] = explode(":", $value);
33 }
34}
35
36if (isset($_GET["file"])) {
37 $basecert = $vardir . "/userkeys/" . $ldap_user;
38 if (!file_exists($basecert . ".cert.pem")) {
39 exec("taskserver-user-certs $ldap_user");
40 }
41 $certificate = file_get_contents($basecert . ".cert.pem");
42 $cert_key = file_get_contents($basecert . ".key.pem");
43
44 // IdenTrust DST Root CA X3
45 // obtained here: https://letsencrypt.org/fr/certificates/
46 $server_cert = "-----BEGIN CERTIFICATE-----
47MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
48MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
49DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
50PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
51Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
52AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
53rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
54OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
55xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
567BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
57aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
58HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
59SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
60ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
61AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
62R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
63JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
64Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
65-----END CERTIFICATE-----";
66
67 $file = $_GET["file"];
68 switch($file) {
69 case "ca.cert.pem":
70 $content = $server_cert;
71 $name = "ca.cert.pem";
72 $type = "application/x-x509-ca-cert";
73 break;
74 case "cert.pem":
75 $content = $certificate;
76 $name = $ldap_user . ".cert.pem";
77 $type = "application/x-x509-ca-cert";
78 break;
79 case "key.pem":
80 $content = $cert_key;
81 $name = $ldap_user . ".key.pem";
82 $type = "application/x-x509-ca-cert";
83 break;
84 case "mirakel";
85 foreach ($entries as $entry) {
86 list($org, $user, $key) = $entry;
87 if ($key == $_GET["key"]) { break; }
88 }
89 $name = $user . ".mirakel";
90 $type = "text/plain";
91 $content = "username: $user
92org: $org
93user key: $key
94server: $host
95client.cert:
96$certificate
97Client.key:
98$cert_key
99ca.cert:
100$server_cert
101";
102 break;
103 default:
104 die("invalid file name");
105 break;
106 }
107
108 header("Content-Type: $type");
109 header('Content-Disposition: attachment; filename="' . $name . '"');
110 header('Content-Transfer-Encoding: binary');
111 header('Accept-Ranges: bytes');
112 header('Cache-Control: private');
113 header('Pragma: private');
114 echo $content;
115 exit;
116}
117?>
118<html>
119<header>
120 <title>Taskwarrior configuration</title>
121</header>
122<body>
123<ul>
124 <li><a href="?file=ca.cert.pem">ca.cert.pem</a></li>
125 <li><a href="?file=cert.pem"><?php echo $ldap_user; ?>.cert.pem</a></li>
126 <li><a href="?file=key.pem"><?php echo $ldap_user; ?>.key.pem</a></li>
127</ul>
128For command line interface, download the files, put them near your Taskwarrior
129configuration files, and add that to your Taskwarrior configuration:
130<pre>
131taskd.certificate=/path/to/<?php echo $ldap_user; ?>.cert.pem
132taskd.key=/path/to/<?php echo $ldap_user; ?>.key.pem
133taskd.server=<?php echo $host ."\n"; ?>
134<?php if (count($entries) > 1) {
135 echo "# Chose one of them\n";
136 foreach($entries as $entry) {
137 list($org, $user, $key) = $entry;
138 echo "# taskd.credentials=$org/$user/$key\n";
139 }
140} else { ?>
141taskd.credentials=<?php echo $entries[0][0]; ?>/<?php echo $entries[0][1]; ?>/<?php echo $entries[0][2]; ?>
142<?php } ?>
143taskd.ca=/path/to/ca.cert.pem
144</pre>
145For Mirakel, download and import the file:
146<ul>
147<?php
148foreach ($entries as $entry) {
149 list($org, $user, $key) = $entry;
150 echo '<li><a href="?file=mirakel&key='.$key.'">' . $user . '.mirakel</a></li>';
151}
152?>
153</ul>
154For Android Taskwarrior app, see instructions <a href="https://bitbucket.org/kvorobyev/taskwarriorandroid/wiki/Configuration">here</a>.
155</body>
156</html>
157
diff --git a/modules/private/websites/aten/builder.nix b/modules/private/websites/aten/builder.nix
new file mode 100644
index 00000000..9a2e1a7d
--- /dev/null
+++ b/modules/private/websites/aten/builder.nix
@@ -0,0 +1,102 @@
1{ apacheUser, apacheGroup, aten, lib, config }: rec {
2 app = aten.override { inherit (config) environment; };
3 phpFpm = rec {
4 preStart = ''
5 if [ ! -f "${app.varDir}/currentWebappDir" -o \
6 ! -f "${app.varDir}/currentKey" -o \
7 "${app}" != "$(cat ${app.varDir}/currentWebappDir 2>/dev/null)" ] \
8 || ! sha512sum -c --status ${app.varDir}/currentKey; then
9 pushd ${app} > /dev/null
10 /run/wrappers/bin/sudo -u ${apacheUser} APP_ENV=${app.environment} ./bin/console --env=${app.environment} cache:clear --no-warmup
11 popd > /dev/null
12 echo -n "${app}" > ${app.varDir}/currentWebappDir
13 sha512sum /var/secrets/webapps/${app.environment}-aten > ${app.varDir}/currentKey
14 fi
15 '';
16 serviceDeps = [ "postgresql.service" ];
17 socket = "/var/run/phpfpm/aten-${app.environment}.sock";
18 pool = ''
19 listen = ${socket}
20 user = ${apacheUser}
21 group = ${apacheGroup}
22 listen.owner = ${apacheUser}
23 listen.group = ${apacheGroup}
24 php_admin_value[upload_max_filesize] = 20M
25 php_admin_value[post_max_size] = 20M
26 ;php_admin_flag[log_errors] = on
27 php_admin_value[open_basedir] = "${app}:${app.varDir}:/tmp"
28 php_admin_value[session.save_path] = "${app.varDir}/phpSessions"
29 ${if app.environment == "dev" then ''
30 pm = ondemand
31 pm.max_children = 5
32 pm.process_idle_timeout = 60
33 env[SYMFONY_DEBUG_MODE] = "yes"
34 '' else ''
35 pm = dynamic
36 pm.max_children = 20
37 pm.start_servers = 2
38 pm.min_spare_servers = 1
39 pm.max_spare_servers = 3
40 ''}'';
41 };
42 keys = [{
43 dest = "webapps/${app.environment}-aten";
44 user = apacheUser;
45 group = apacheGroup;
46 permissions = "0400";
47 text = ''
48 SetEnv APP_ENV "${app.environment}"
49 SetEnv APP_SECRET "${config.secret}"
50 SetEnv DATABASE_URL "${config.psql_url}"
51 '';
52 }];
53 apache = rec {
54 modules = [ "proxy_fcgi" ];
55 webappName = "aten_${app.environment}";
56 root = "/run/current-system/webapps/${webappName}";
57 vhostConf = ''
58 <FilesMatch "\.php$">
59 SetHandler "proxy:unix:${phpFpm.socket}|fcgi://localhost"
60 </FilesMatch>
61
62 Include /var/secrets/webapps/${app.environment}-aten
63
64 ${if app.environment == "dev" then ''
65 <Location />
66 Use LDAPConnect
67 Require ldap-group cn=dev.aten.pro,cn=httpd,ou=services,dc=immae,dc=eu
68 ErrorDocument 401 "<html><meta http-equiv=\"refresh\" content=\"0;url=https://aten.pro\"></html>"
69 </Location>
70
71 <Location /backend>
72 Use LDAPConnect
73 Require ldap-group cn=dev.aten.pro,cn=httpd,ou=services,dc=immae,dc=eu
74 ErrorDocument 401 "<html><meta http-equiv=\"refresh\" content=\"0;url=https://aten.pro\"></html>"
75 </Location>
76 '' else ''
77 Use Stats aten.pro
78
79 <Location /backend>
80 Use LDAPConnect
81 Require ldap-group cn=aten.pro,cn=httpd,ou=services,dc=immae,dc=eu
82 ErrorDocument 401 "<html><meta http-equiv=\"refresh\" content=\"0;url=https://aten.pro\"></html>"
83 </Location>
84 ''}
85
86 <Directory ${root}>
87 Options Indexes FollowSymLinks MultiViews Includes
88 AllowOverride All
89 Require all granted
90 DirectoryIndex index.php
91 FallbackResource /index.php
92 </Directory>
93 '';
94 };
95 activationScript = {
96 deps = [ "wrappers" ];
97 text = ''
98 install -m 0755 -o ${apacheUser} -g ${apacheGroup} -d ${app.varDir}
99 install -m 0750 -o ${apacheUser} -g ${apacheGroup} -d ${app.varDir}/phpSessions
100 '';
101 };
102}
diff --git a/modules/private/websites/aten/integration.nix b/modules/private/websites/aten/integration.nix
new file mode 100644
index 00000000..748e3885
--- /dev/null
+++ b/modules/private/websites/aten/integration.nix
@@ -0,0 +1,32 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 aten = pkgs.callPackage ./builder.nix {
4 inherit (pkgs.webapps) aten;
5 config = myconfig.env.websites.aten.integration;
6 apacheUser = config.services.httpd.Inte.user;
7 apacheGroup = config.services.httpd.Inte.group;
8 };
9
10 cfg = config.myServices.websites.aten.integration;
11in {
12 options.myServices.websites.aten.integration.enable = lib.mkEnableOption "enable Aten's website in integration";
13
14 config = lib.mkIf cfg.enable {
15 secrets.keys = aten.keys;
16 systemd.services.phpfpm-aten_dev.preStart = lib.mkAfter aten.phpFpm.preStart;
17 systemd.services.phpfpm-aten_dev.after = lib.mkAfter aten.phpFpm.serviceDeps;
18 systemd.services.phpfpm-aten_dev.wants = aten.phpFpm.serviceDeps;
19 services.phpfpm.poolConfigs.aten_dev = aten.phpFpm.pool;
20 system.activationScripts.aten_dev = aten.activationScript;
21 myServices.websites.webappDirs."${aten.apache.webappName}" = aten.app.webRoot;
22 services.websites.integration.modules = aten.apache.modules;
23 services.websites.integration.vhostConfs.aten = {
24 certName = "eldiron";
25 addToCerts = true;
26 hosts = [ "dev.aten.pro" ];
27 root = aten.apache.root;
28 extraConfig = [ aten.apache.vhostConf ];
29 };
30 };
31}
32
diff --git a/modules/private/websites/aten/production.nix b/modules/private/websites/aten/production.nix
new file mode 100644
index 00000000..7a4adb5a
--- /dev/null
+++ b/modules/private/websites/aten/production.nix
@@ -0,0 +1,34 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 aten = pkgs.callPackage ./builder.nix {
4 inherit (pkgs.webapps) aten;
5 config = myconfig.env.websites.aten.production;
6 apacheUser = config.services.httpd.Prod.user;
7 apacheGroup = config.services.httpd.Prod.group;
8 };
9
10 cfg = config.myServices.websites.aten.production;
11in {
12 options.myServices.websites.aten.production.enable = lib.mkEnableOption "enable Aten's website in production";
13
14 config = lib.mkIf cfg.enable {
15 secrets.keys = aten.keys;
16 services.webstats.sites = [ { name = "aten.pro"; } ];
17
18 systemd.services.phpfpm-aten_prod.preStart = lib.mkAfter aten.phpFpm.preStart;
19 systemd.services.phpfpm-aten_prod.after = lib.mkAfter aten.phpFpm.serviceDeps;
20 systemd.services.phpfpm-aten_prod.wants = aten.phpFpm.serviceDeps;
21 services.phpfpm.poolConfigs.aten_prod = aten.phpFpm.pool;
22 system.activationScripts.aten_prod = aten.activationScript;
23 myServices.websites.webappDirs."${aten.apache.webappName}" = aten.app.webRoot;
24 services.websites.production.modules = aten.apache.modules;
25 services.websites.production.vhostConfs.aten = {
26 certName = "aten";
27 certMainHost = "aten.pro";
28 hosts = [ "aten.pro" "www.aten.pro" ];
29 root = aten.apache.root;
30 extraConfig = [ aten.apache.vhostConf ];
31 };
32 };
33}
34
diff --git a/modules/private/websites/capitaines/mastodon_static/index.html b/modules/private/websites/capitaines/mastodon_static/index.html
new file mode 100644
index 00000000..fae4152d
--- /dev/null
+++ b/modules/private/websites/capitaines/mastodon_static/index.html
@@ -0,0 +1,29 @@
1<!DOCTYPE html>
2<html lang='en'>
3 <head>
4 <meta content='text/html; charset=UTF-8' http-equiv='Content-Type'>
5 <title>This instance is now closed - Mastodon</title>
6 <style>
7 body {
8 text-align: center;
9 background: #282c37;
10 font-family: sans-serif;
11 }
12 img {
13 max-width: 470px;
14 width: 100%;
15 }
16 h1 {
17 font-size: 20px;
18 font-weight: 400;
19 color: #9baec8;
20 }
21 </style>
22 </head>
23 <body>
24 <div>
25 <img alt='Mastodon' src='/oops.png'>
26 <h1>Sorry, this instance is closed now.</h1>
27 </div>
28 </body>
29</html>
diff --git a/modules/private/websites/capitaines/mastodon_static/oops.png b/modules/private/websites/capitaines/mastodon_static/oops.png
new file mode 100644
index 00000000..0abddad3
--- /dev/null
+++ b/modules/private/websites/capitaines/mastodon_static/oops.png
Binary files differ
diff --git a/modules/private/websites/capitaines/production.nix b/modules/private/websites/capitaines/production.nix
new file mode 100644
index 00000000..57d87873
--- /dev/null
+++ b/modules/private/websites/capitaines/production.nix
@@ -0,0 +1,44 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 cfg = config.myServices.websites.capitaines.production;
4 env = myconfig.env.websites.capitaines;
5 webappName = "capitaines_mastodon";
6 root = "/run/current-system/webapps/${webappName}";
7 siteDir = ./mastodon_static;
8in {
9 options.myServices.websites.capitaines.production.enable = lib.mkEnableOption "enable Capitaines's website";
10
11 config = lib.mkIf cfg.enable {
12 myServices.websites.webappDirs."${webappName}" = siteDir;
13
14 services.websites.production.vhostConfs.capitaines_mastodon = {
15 certName = "capitaines";
16 certMainHost = "mastodon.capitaines.fr";
17 hosts = [ "mastodon.capitaines.fr" ];
18 root = root;
19 extraConfig = [
20 ''
21 ErrorDocument 404 /index.html
22 <Directory ${root}>
23 DirectoryIndex index.html
24 Options Indexes FollowSymLinks MultiViews Includes
25 Require all granted
26 </Directory>
27 ''
28 ];
29 };
30
31 services.websites.production.vhostConfs.capitaines = {
32 certName = "capitaines";
33 addToCerts = true;
34 hosts = [ "capitaines.fr" ];
35 root = "/run/current-system/webapps/_www";
36 extraConfig = [ ''
37 <Directory /run/current-system/webapps/_www>
38 DirectoryIndex index.htm
39 Require all granted
40 </Directory>
41 '' ];
42 };
43 };
44}
diff --git a/modules/private/websites/chloe/builder.nix b/modules/private/websites/chloe/builder.nix
new file mode 100644
index 00000000..f65e9a95
--- /dev/null
+++ b/modules/private/websites/chloe/builder.nix
@@ -0,0 +1,102 @@
1{ apacheUser, apacheGroup, chloe, config }:
2rec {
3 app = chloe.override { inherit (config) environment; };
4 phpFpm = rec {
5 serviceDeps = [ "mysql.service" ];
6 socket = "/var/run/phpfpm/chloe-${app.environment}.sock";
7 pool = ''
8 user = ${apacheUser}
9 group = ${apacheGroup}
10 listen.owner = ${apacheUser}
11 listen.group = ${apacheGroup}
12 php_admin_value[upload_max_filesize] = 20M
13 php_admin_value[post_max_size] = 20M
14 ;php_admin_flag[log_errors] = on
15 php_admin_value[open_basedir] = "${app.spipConfig}:${configDir}:${app}:${app.varDir}:/tmp"
16 php_admin_value[session.save_path] = "${app.varDir}/phpSessions"
17 ${if app.environment == "dev" then ''
18 pm = ondemand
19 pm.max_children = 5
20 pm.process_idle_timeout = 60
21 '' else ''
22 pm = dynamic
23 pm.max_children = 20
24 pm.start_servers = 2
25 pm.min_spare_servers = 1
26 pm.max_spare_servers = 3
27 ''}'';
28 };
29 keys = [{
30 dest = "webapps/${app.environment}-chloe";
31 user = apacheUser;
32 group = apacheGroup;
33 permissions = "0400";
34 text = ''
35 SetEnv SPIP_CONFIG_DIR "${configDir}"
36 SetEnv SPIP_VAR_DIR "${app.varDir}"
37 SetEnv SPIP_SITE "chloe-${app.environment}"
38 SetEnv SPIP_LDAP_BASE "dc=immae,dc=eu"
39 SetEnv SPIP_LDAP_HOST "ldaps://ldap.immae.eu"
40 SetEnv SPIP_LDAP_SEARCH_DN "${config.ldap.dn}"
41 SetEnv SPIP_LDAP_SEARCH_PW "${config.ldap.password}"
42 SetEnv SPIP_LDAP_SEARCH "${config.ldap.search}"
43 SetEnv SPIP_MYSQL_HOST "${config.mysql.host}"
44 SetEnv SPIP_MYSQL_PORT "${config.mysql.port}"
45 SetEnv SPIP_MYSQL_DB "${config.mysql.name}"
46 SetEnv SPIP_MYSQL_USER "${config.mysql.user}"
47 SetEnv SPIP_MYSQL_PASSWORD "${config.mysql.password}"
48 '';
49 }];
50 apache = rec {
51 modules = [ "proxy_fcgi" ];
52 webappName = "chloe_${app.environment}";
53 root = "/run/current-system/webapps/${webappName}";
54 vhostConf = ''
55 Include /var/secrets/webapps/${app.environment}-chloe
56
57 RewriteEngine On
58 ${if app.environment == "prod" then ''
59 RewriteRule ^/news.rss /spip.php?page=backend&id_rubrique=1
60 '' else ""}
61
62 <FilesMatch "\.php$">
63 SetHandler "proxy:unix:${phpFpm.socket}|fcgi://localhost"
64 </FilesMatch>
65
66 <Directory ${root}>
67 DirectoryIndex index.php index.htm index.html
68 Options -Indexes +FollowSymLinks +MultiViews +Includes
69 Include ${root}/htaccess.txt
70
71 AllowOverride AuthConfig FileInfo Limit
72 Require all granted
73 </Directory>
74
75 <DirectoryMatch "${root}/squelettes">
76 Require all denied
77 </DirectoryMatch>
78
79 <FilesMatch "(.htaccess|rewrite-rules|.gitignore)$">
80 Require all denied
81 </FilesMatch>
82
83 ${if app.environment == "dev" then ''
84 <Location />
85 Use LDAPConnect
86 Require ldap-group cn=chloe.immae.eu,cn=httpd,ou=services,dc=immae,dc=eu
87 ErrorDocument 401 "<html><meta http-equiv=\"refresh\" content=\"0;url=https://osteopathe-cc.fr\"></html>"
88 </Location>
89 '' else ''
90 Use Stats osteopathe-cc.fr
91 ''}
92 '';
93 };
94 activationScript = {
95 deps = [ "wrappers" ];
96 text = ''
97 install -m 0755 -o ${apacheUser} -g ${apacheGroup} -d ${app.varDir} ${app.varDir}/IMG ${app.varDir}/tmp ${app.varDir}/local
98 install -m 0750 -o ${apacheUser} -g ${apacheGroup} -d ${app.varDir}/phpSessions
99 '';
100 };
101 configDir = ./config;
102}
diff --git a/modules/private/websites/chloe/config/chmod.php b/modules/private/websites/chloe/config/chmod.php
new file mode 100644
index 00000000..aae16cdf
--- /dev/null
+++ b/modules/private/websites/chloe/config/chmod.php
@@ -0,0 +1,4 @@
1<?php
2if (!defined("_ECRIRE_INC_VERSION")) return;
3if (!defined('_SPIP_CHMOD')) define('_SPIP_CHMOD', 0777);
4?> \ No newline at end of file
diff --git a/modules/private/websites/chloe/config/connect.php b/modules/private/websites/chloe/config/connect.php
new file mode 100644
index 00000000..18b09330
--- /dev/null
+++ b/modules/private/websites/chloe/config/connect.php
@@ -0,0 +1,15 @@
1<?php
2if (!defined("_ECRIRE_INC_VERSION")) return;
3define('_MYSQL_SET_SQL_MODE',true);
4$GLOBALS['spip_connect_version'] = 0.7;
5spip_connect_db(
6 getenv("SPIP_MYSQL_HOST"),
7 getenv("SPIP_MYSQL_PORT"),
8 getenv("SPIP_MYSQL_USER"),
9 getenv("SPIP_MYSQL_PASSWORD"),
10 getenv("SPIP_MYSQL_DB"),
11 'mysql',
12 'spip',
13 'ldap.php'
14);
15?>
diff --git a/modules/private/websites/chloe/config/ldap.php b/modules/private/websites/chloe/config/ldap.php
new file mode 100644
index 00000000..825b7edb
--- /dev/null
+++ b/modules/private/websites/chloe/config/ldap.php
@@ -0,0 +1,9 @@
1<?php
2if (!defined("_ECRIRE_INC_VERSION")) return;
3$GLOBALS['ldap_base'] = getenv("SPIP_LDAP_BASE");
4$GLOBALS['ldap_link'] = @ldap_connect(getenv("SPIP_LDAP_HOST"));
5@ldap_set_option($GLOBALS['ldap_link'],LDAP_OPT_PROTOCOL_VERSION,'3');
6@ldap_bind($GLOBALS['ldap_link'],getenv("SPIP_LDAP_SEARCH_DN"), getenv("SPIP_LDAP_SEARCH_PW"));
7$GLOBALS['ldap_champs'] = array('login' => array('sAMAccountName','uid','login','userid','cn','sn'),'nom' => 'cn','email' => 'mail','bio' => 'description',);
8$GLOBALS['ldap_search'] = getenv("SPIP_LDAP_SEARCH");
9?>
diff --git a/modules/private/websites/chloe/integration.nix b/modules/private/websites/chloe/integration.nix
new file mode 100644
index 00000000..c42a4282
--- /dev/null
+++ b/modules/private/websites/chloe/integration.nix
@@ -0,0 +1,36 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 chloe = pkgs.callPackage ./builder.nix {
4 inherit (pkgs.webapps) chloe;
5 config = myconfig.env.websites.chloe.integration;
6 apacheUser = config.services.httpd.Inte.user;
7 apacheGroup = config.services.httpd.Inte.group;
8 };
9
10 cfg = config.myServices.websites.chloe.integration;
11in {
12 options.myServices.websites.chloe.integration.enable = lib.mkEnableOption "enable Chloe's website in integration";
13
14 config = lib.mkIf cfg.enable {
15 secrets.keys = chloe.keys;
16 systemd.services.phpfpm-chloe_dev.after = lib.mkAfter chloe.phpFpm.serviceDeps;
17 systemd.services.phpfpm-chloe_dev.wants = chloe.phpFpm.serviceDeps;
18 services.phpfpm.pools.chloe_dev = {
19 listen = chloe.phpFpm.socket;
20 extraConfig = chloe.phpFpm.pool;
21 phpOptions = config.services.phpfpm.phpOptions + ''
22 extension=${pkgs.php}/lib/php/extensions/mysqli.so
23 '';
24 };
25 system.activationScripts.chloe_dev = chloe.activationScript;
26 myServices.websites.webappDirs."${chloe.apache.webappName}" = chloe.app.webRoot;
27 services.websites.integration.modules = chloe.apache.modules;
28 services.websites.integration.vhostConfs.chloe = {
29 certName = "eldiron";
30 addToCerts = true;
31 hosts = ["chloe.immae.eu" ];
32 root = chloe.apache.root;
33 extraConfig = [ chloe.apache.vhostConf ];
34 };
35 };
36}
diff --git a/modules/private/websites/chloe/production.nix b/modules/private/websites/chloe/production.nix
new file mode 100644
index 00000000..0bf2d8fd
--- /dev/null
+++ b/modules/private/websites/chloe/production.nix
@@ -0,0 +1,38 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 chloe = pkgs.callPackage ./builder.nix {
4 inherit (pkgs.webapps) chloe;
5 config = myconfig.env.websites.chloe.production;
6 apacheUser = config.services.httpd.Prod.user;
7 apacheGroup = config.services.httpd.Prod.group;
8 };
9
10 cfg = config.myServices.websites.chloe.production;
11in {
12 options.myServices.websites.chloe.production.enable = lib.mkEnableOption "enable Chloe's website in production";
13
14 config = lib.mkIf cfg.enable {
15 secrets.keys = chloe.keys;
16 services.webstats.sites = [ { name = "osteopathe-cc.fr"; } ];
17
18 systemd.services.phpfpm-chloe_prod.after = lib.mkAfter chloe.phpFpm.serviceDeps;
19 systemd.services.phpfpm-chloe_prod.wants = chloe.phpFpm.serviceDeps;
20 services.phpfpm.pools.chloe_prod = {
21 listen = chloe.phpFpm.socket;
22 extraConfig = chloe.phpFpm.pool;
23 phpOptions = config.services.phpfpm.phpOptions + ''
24 extension=${pkgs.php}/lib/php/extensions/mysqli.so
25 '';
26 };
27 system.activationScripts.chloe_prod = chloe.activationScript;
28 myServices.websites.webappDirs."${chloe.apache.webappName}" = chloe.app.webRoot;
29 services.websites.production.modules = chloe.apache.modules;
30 services.websites.production.vhostConfs.chloe = {
31 certName = "chloe";
32 certMainHost = "osteopathe-cc.fr";
33 hosts = ["osteopathe-cc.fr" "www.osteopathe-cc.fr" ];
34 root = chloe.apache.root;
35 extraConfig = [ chloe.apache.vhostConf ];
36 };
37 };
38}
diff --git a/modules/private/websites/commons/adminer.nix b/modules/private/websites/commons/adminer.nix
new file mode 100644
index 00000000..98ab4619
--- /dev/null
+++ b/modules/private/websites/commons/adminer.nix
@@ -0,0 +1,21 @@
1{}:
2rec {
3 phpFpm = {
4 socket = "/var/run/phpfpm/adminer.sock";
5 };
6 apache = rec {
7 modules = [ "proxy_fcgi" ];
8 webappName = "_adminer";
9 root = "/run/current-system/webapps/${webappName}";
10 vhostConf = ''
11 Alias /adminer ${root}
12 <Directory ${root}>
13 DirectoryIndex index.php
14 Require all granted
15 <FilesMatch "\.php$">
16 SetHandler "proxy:unix:${phpFpm.socket}|fcgi://localhost"
17 </FilesMatch>
18 </Directory>
19 '';
20 };
21}
diff --git a/modules/private/websites/connexionswing/builder.nix b/modules/private/websites/connexionswing/builder.nix
new file mode 100644
index 00000000..b4b04cb2
--- /dev/null
+++ b/modules/private/websites/connexionswing/builder.nix
@@ -0,0 +1,163 @@
1{ apacheUser, apacheGroup, connexionswing, pkgs, phpPackages, config }:
2rec {
3 app = connexionswing.override { inherit (config) environment; };
4 keys = [{
5 dest = "webapps/${app.environment}-connexionswing";
6 user = apacheUser;
7 group = apacheGroup;
8 permissions = "0400";
9 text = ''
10 # This file is auto-generated during the composer install
11 parameters:
12 database_host: ${config.mysql.host}
13 database_port: ${config.mysql.port}
14 database_name: ${config.mysql.name}
15 database_user: ${config.mysql.user}
16 database_password: ${config.mysql.password}
17 database_server_version: ${pkgs.mariadb.mysqlVersion}
18 mailer_transport: sendmail
19 mailer_host: null
20 mailer_user: null
21 mailer_password: null
22 subscription_email: ${config.email}
23 allow_robots: true
24 secret: ${config.secret}
25 ${if app.environment == "prod" then ''
26 services:
27 swiftmailer.mailer.default.transport:
28 class: Swift_SendmailTransport
29 arguments: ['/run/wrappers/bin/sendmail -bs']
30 '' else ""}
31 '';
32 }];
33 phpFpm = rec {
34 preStart = ''
35 if [ ! -f "${app.varDir}/currentWebappDir" -o \
36 ! -f "${app.varDir}/currentKey" -o \
37 "${app}" != "$(cat ${app.varDir}/currentWebappDir 2>/dev/null)" ] \
38 || ! sha512sum -c --status ${app.varDir}/currentKey; then
39 pushd ${app} > /dev/null
40 /run/wrappers/bin/sudo -u ${apacheUser} ./bin/console --env=${app.environment} cache:clear --no-warmup
41 popd > /dev/null
42 echo -n "${app}" > ${app.varDir}/currentWebappDir
43 sha512sum /var/secrets/webapps/${app.environment}-connexionswing > ${app.varDir}/currentKey
44 fi
45 '';
46 serviceDeps = [ "mysql.service" ];
47 socket = "/var/run/phpfpm/connexionswing-${app.environment}.sock";
48 phpConfig = ''
49 extension=${phpPackages.imagick}/lib/php/extensions/imagick.so
50 '';
51 pool = ''
52 user = ${apacheUser}
53 group = ${apacheGroup}
54 listen.owner = ${apacheUser}
55 listen.group = ${apacheGroup}
56 php_admin_value[upload_max_filesize] = 20M
57 php_admin_value[post_max_size] = 20M
58 ;php_admin_flag[log_errors] = on
59 php_admin_value[open_basedir] = "/run/wrappers/bin/sendmail:/var/secrets/webapps/${app.environment}-connexionswing:${app}:${app.varDir}:/tmp"
60 php_admin_value[session.save_path] = "${app.varDir}/phpSessions"
61 ${if app.environment == "dev" then ''
62 pm = ondemand
63 pm.max_children = 5
64 pm.process_idle_timeout = 60
65 env[SYMFONY_DEBUG_MODE] = "yes"
66 '' else ''
67 pm = dynamic
68 pm.max_children = 20
69 pm.start_servers = 2
70 pm.min_spare_servers = 1
71 pm.max_spare_servers = 3
72 ''}'';
73 };
74 apache = rec {
75 modules = [ "proxy_fcgi" ];
76 webappName = "connexionswing_${app.environment}";
77 root = "/run/current-system/webapps/${webappName}";
78 vhostConf = ''
79 <FilesMatch "\.php$">
80 SetHandler "proxy:unix:${phpFpm.socket}|fcgi://localhost"
81 </FilesMatch>
82
83 <Directory ${app.varDir}/medias>
84 Options FollowSymLinks
85 AllowOverride None
86 Require all granted
87 </Directory>
88
89 <Directory ${app.varDir}/uploads>
90 Options FollowSymLinks
91 AllowOverride None
92 Require all granted
93 </Directory>
94
95 ${if app.environment == "dev" then ''
96 <Location />
97 Use LDAPConnect
98 Require ldap-group cn=connexionswing.immae.eu,cn=httpd,ou=services,dc=immae,dc=eu
99 ErrorDocument 401 "<html><meta http-equiv=\"refresh\" content=\"0;url=https://connexionswing.com\"></html>"
100 </Location>
101
102 <Directory ${root}>
103 Options Indexes FollowSymLinks MultiViews Includes
104 AllowOverride None
105 Require all granted
106
107 DirectoryIndex app_dev.php
108
109 <IfModule mod_negotiation.c>
110 Options -MultiViews
111 </IfModule>
112
113 <IfModule mod_rewrite.c>
114 RewriteEngine On
115
116 RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$
117 RewriteRule ^(.*) - [E=BASE:%1]
118
119 # Maintenance script
120 RewriteCond %{DOCUMENT_ROOT}/maintenance.php -f
121 RewriteCond %{SCRIPT_FILENAME} !maintenance.php
122 RewriteRule ^.*$ %{ENV:BASE}/maintenance.php [R=503,L]
123 ErrorDocument 503 /maintenance.php
124
125 # Sets the HTTP_AUTHORIZATION header removed by Apache
126 RewriteCond %{HTTP:Authorization} .
127 RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
128
129 RewriteCond %{ENV:REDIRECT_STATUS} ^$
130 RewriteRule ^app_dev\.php(?:/(.*)|$) %{ENV:BASE}/$1 [R=301,L]
131
132 # If the requested filename exists, simply serve it.
133 # We only want to let Apache serve files and not directories.
134 RewriteCond %{REQUEST_FILENAME} -f
135 RewriteRule ^ - [L]
136
137 # Rewrite all other queries to the front controller.
138 RewriteRule ^ %{ENV:BASE}/app_dev.php [L]
139 </IfModule>
140
141 </Directory>
142 '' else ''
143 Use Stats connexionswing.com
144
145 <Directory ${root}>
146 Options Indexes FollowSymLinks MultiViews Includes
147 AllowOverride All
148 Require all granted
149 </Directory>
150 ''}
151 '';
152 };
153 activationScript = {
154 deps = [ "wrappers" ];
155 text = ''
156 install -m 0755 -o ${apacheUser} -g ${apacheGroup} -d ${app.varDir} \
157 ${app.varDir}/medias \
158 ${app.varDir}/uploads \
159 ${app.varDir}/var
160 install -m 0750 -o ${apacheUser} -g ${apacheGroup} -d ${app.varDir}/phpSessions
161 '';
162 };
163}
diff --git a/modules/private/websites/connexionswing/integration.nix b/modules/private/websites/connexionswing/integration.nix
new file mode 100644
index 00000000..1d8488a9
--- /dev/null
+++ b/modules/private/websites/connexionswing/integration.nix
@@ -0,0 +1,36 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 connexionswing = pkgs.callPackage ./builder.nix {
4 inherit (pkgs.webapps) connexionswing;
5 config = myconfig.env.websites.connexionswing.integration;
6 apacheUser = config.services.httpd.Inte.user;
7 apacheGroup = config.services.httpd.Inte.group;
8 };
9
10 cfg = config.myServices.websites.connexionswing.integration;
11in {
12 options.myServices.websites.connexionswing.integration.enable = lib.mkEnableOption "enable Connexionswing's website in integration";
13
14 config = lib.mkIf cfg.enable {
15 secrets.keys = connexionswing.keys;
16 systemd.services.phpfpm-connexionswing_dev.after = lib.mkAfter connexionswing.phpFpm.serviceDeps;
17 systemd.services.phpfpm-connexionswing_dev.wants = connexionswing.phpFpm.serviceDeps;
18 systemd.services.phpfpm-connexionswing_dev.preStart = lib.mkAfter connexionswing.phpFpm.preStart;
19 services.phpfpm.pools.connexionswing_dev = {
20 listen = connexionswing.phpFpm.socket;
21 extraConfig = connexionswing.phpFpm.pool;
22 phpOptions = config.services.phpfpm.phpOptions + connexionswing.phpFpm.phpConfig;
23 };
24 system.activationScripts.connexionswing_dev = connexionswing.activationScript;
25 myServices.websites.webappDirs."${connexionswing.apache.webappName}" = connexionswing.app.webRoot;
26 services.websites.integration.modules = connexionswing.apache.modules;
27 services.websites.integration.vhostConfs.connexionswing = {
28 certName = "eldiron";
29 addToCerts = true;
30 hosts = ["connexionswing.immae.eu" "sandetludo.immae.eu" ];
31 root = connexionswing.apache.root;
32 extraConfig = [ connexionswing.apache.vhostConf ];
33 };
34 };
35}
36
diff --git a/modules/private/websites/connexionswing/production.nix b/modules/private/websites/connexionswing/production.nix
new file mode 100644
index 00000000..555f129f
--- /dev/null
+++ b/modules/private/websites/connexionswing/production.nix
@@ -0,0 +1,38 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 connexionswing = pkgs.callPackage ./builder.nix {
4 inherit (pkgs.webapps) connexionswing;
5 config = myconfig.env.websites.connexionswing.production;
6 apacheUser = config.services.httpd.Prod.user;
7 apacheGroup = config.services.httpd.Prod.group;
8 };
9
10 cfg = config.myServices.websites.connexionswing.production;
11in {
12 options.myServices.websites.connexionswing.production.enable = lib.mkEnableOption "enable Connexionswing's website in production";
13
14 config = lib.mkIf cfg.enable {
15 secrets.keys = connexionswing.keys;
16 services.webstats.sites = [ { name = "connexionswing.com"; } ];
17
18 systemd.services.phpfpm-connexionswing_prod.after = lib.mkAfter connexionswing.phpFpm.serviceDeps;
19 systemd.services.phpfpm-connexionswing_prod.wants = connexionswing.phpFpm.serviceDeps;
20 systemd.services.phpfpm-connexionswing_prod.preStart = lib.mkAfter connexionswing.phpFpm.preStart;
21 services.phpfpm.pools.connexionswing_prod = {
22 listen = connexionswing.phpFpm.socket;
23 extraConfig = connexionswing.phpFpm.pool;
24 phpOptions = config.services.phpfpm.phpOptions + connexionswing.phpFpm.phpConfig;
25 };
26 system.activationScripts.connexionswing_prod = connexionswing.activationScript;
27 myServices.websites.webappDirs."${connexionswing.apache.webappName}" = connexionswing.app.webRoot;
28 services.websites.production.modules = connexionswing.apache.modules;
29 services.websites.production.vhostConfs.connexionswing = {
30 certName = "connexionswing";
31 certMainHost = "connexionswing.com";
32 hosts = ["connexionswing.com" "sandetludo.com" "www.connexionswing.com" "www.sandetludo.com" ];
33 root = connexionswing.apache.root;
34 extraConfig = [ connexionswing.apache.vhostConf ];
35 };
36 };
37}
38
diff --git a/modules/private/websites/default.nix b/modules/private/websites/default.nix
new file mode 100644
index 00000000..8b02977c
--- /dev/null
+++ b/modules/private/websites/default.nix
@@ -0,0 +1,265 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 www_root = "/run/current-system/webapps/_www";
4 theme_root = "/run/current-system/webapps/_theme";
5 apacheConfig = {
6 gzip = {
7 modules = [ "deflate" "filter" ];
8 extraConfig = ''
9 AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript
10 '';
11 };
12 macros = {
13 modules = [ "macro" ];
14 };
15 stats = {
16 extraConfig = ''
17 <Macro Stats %{domain}>
18 Alias /webstats ${config.services.webstats.dataDir}/%{domain}
19 <Directory ${config.services.webstats.dataDir}/%{domain}>
20 DirectoryIndex index.html
21 AllowOverride None
22 Require all granted
23 </Directory>
24 <Location /webstats>
25 Use LDAPConnect
26 Require ldap-group cn=%{domain},ou=stats,cn=httpd,ou=services,dc=immae,dc=eu
27 </Location>
28 </Macro>
29 '';
30 };
31 ldap = {
32 modules = [ "ldap" "authnz_ldap" ];
33 extraConfig = ''
34 <IfModule ldap_module>
35 LDAPSharedCacheSize 500000
36 LDAPCacheEntries 1024
37 LDAPCacheTTL 600
38 LDAPOpCacheEntries 1024
39 LDAPOpCacheTTL 600
40 </IfModule>
41
42 Include /var/secrets/apache-ldap
43 '';
44 };
45 global = {
46 extraConfig = (pkgs.webapps.apache-default.override { inherit www_root;}).apacheConfig;
47 };
48 apaxy = {
49 extraConfig = (pkgs.webapps.apache-theme.override { inherit theme_root; }).apacheConfig;
50 };
51 http2 = {
52 modules = [ "http2" ];
53 extraConfig = ''
54 Protocols h2 http/1.1
55 '';
56 };
57 customLog = {
58 extraConfig = ''
59 LogFormat "%v:%p %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combinedVhost
60 '';
61 };
62 };
63 makeModules = lib.lists.flatten (lib.attrsets.mapAttrsToList (n: v: v.modules or []) apacheConfig);
64 makeExtraConfig = (builtins.filter (x: x != null) (lib.attrsets.mapAttrsToList (n: v: v.extraConfig or null) apacheConfig));
65in
66{
67 options.myServices.websites.webappDirs = lib.mkOption {
68 type = lib.types.attrsOf lib.types.path;
69 description = ''
70 Webapp paths to create in /run/current-system/webapps
71 '';
72 default = {};
73 };
74
75 config = {
76 users.users.wwwrun.extraGroups = [ "keys" ];
77 networking.firewall.allowedTCPPorts = [ 80 443 ];
78
79 nixpkgs.overlays = [ (self: super: rec {
80 #openssl = self.openssl_1_1;
81 php = php72;
82 php72 = (super.php72.override {
83 mysql.connector-c = self.mariadb;
84 config.php.mysqlnd = false;
85 config.php.mysqli = false;
86 }).overrideAttrs(old: rec {
87 # Didn't manage to build with mysqli + mysql_config connector
88 configureFlags = old.configureFlags ++ [
89 "--with-mysqli=shared,mysqlnd"
90 ];
91 # preConfigure = (old.preConfigure or "") + ''
92 # export CPPFLAGS="$CPPFLAGS -I${pkgs.mariadb}/include/mysql/server";
93 # sed -i -e 's/#include "mysqli_priv.h"/#include "mysqli_priv.h"\n#include <mysql_version.h>/' \
94 # ext/mysqli/mysqli.c ext/mysqli/mysqli_prop.c
95 # '';
96 });
97 phpPackages = super.php72Packages.override { inherit php; };
98 }) ];
99
100 secrets.keys = [{
101 dest = "apache-ldap";
102 user = "wwwrun";
103 group = "wwwrun";
104 permissions = "0400";
105 text = ''
106 <Macro LDAPConnect>
107 <IfModule authnz_ldap_module>
108 AuthLDAPURL ldap://ldap.immae.eu:389/dc=immae,dc=eu STARTTLS
109 AuthLDAPBindDN cn=httpd,ou=services,dc=immae,dc=eu
110 AuthLDAPBindPassword "${myconfig.env.httpd.ldap.password}"
111 AuthType Basic
112 AuthName "Authentification requise (Acces LDAP)"
113 AuthBasicProvider ldap
114 </IfModule>
115 </Macro>
116 '';
117 }];
118
119 system.activationScripts = {
120 httpd = ''
121 install -d -m 0755 ${config.security.acme.directory}/acme-challenge
122 install -d -m 0750 -o wwwrun -g wwwrun /var/lib/php/sessions
123 '';
124 };
125
126 services.phpfpm = {
127 phpPackage = pkgs.php;
128 phpOptions = ''
129 session.save_path = "/var/lib/php/sessions"
130 post_max_size = 20M
131 ; 15 days (seconds)
132 session.gc_maxlifetime = 1296000
133 ; 30 days (minutes)
134 session.cache_expire = 43200
135 '';
136 extraConfig = ''
137 log_level = notice
138 '';
139 };
140
141 services.websites.production = {
142 enable = true;
143 adminAddr = "httpd@immae.eu";
144 httpdName = "Prod";
145 ips =
146 let ips = myconfig.env.servers.eldiron.ips.production;
147 in [ips.ip4] ++ (ips.ip6 or []);
148 modules = makeModules;
149 extraConfig = makeExtraConfig;
150 fallbackVhost = {
151 certName = "eldiron";
152 hosts = ["eldiron.immae.eu" ];
153 root = www_root;
154 extraConfig = [ "DirectoryIndex index.htm" ];
155 };
156 };
157
158 services.websites.integration = {
159 enable = true;
160 adminAddr = "httpd@immae.eu";
161 httpdName = "Inte";
162 ips =
163 let ips = myconfig.env.servers.eldiron.ips.integration;
164 in [ips.ip4] ++ (ips.ip6 or []);
165 modules = makeModules;
166 extraConfig = makeExtraConfig;
167 fallbackVhost = {
168 certName = "eldiron";
169 hosts = ["eldiron.immae.eu" ];
170 root = www_root;
171 extraConfig = [ "DirectoryIndex index.htm" ];
172 };
173 };
174
175 services.websites.tools = {
176 enable = true;
177 adminAddr = "httpd@immae.eu";
178 httpdName = "Tools";
179 ips =
180 let ips = myconfig.env.servers.eldiron.ips.main;
181 in [ips.ip4] ++ (ips.ip6 or []);
182 modules = makeModules;
183 extraConfig = makeExtraConfig ++
184 [ ''
185 RedirectMatch ^/licen[cs]es?_et_tip(ping)?$ https://www.immae.eu/licences_et_tip.html
186 RedirectMatch ^/licen[cs]es?_and_tip(ping)?$ https://www.immae.eu/licenses_and_tipping.html
187 RedirectMatch ^/licen[cs]es?$ https://www.immae.eu/licenses_and_tipping.html
188 RedirectMatch ^/tip(ping)?$ https://www.immae.eu/licenses_and_tipping.html
189 RedirectMatch ^/(mentions|mentions_legales|legal)$ https://www.immae.eu/mentions.html
190 RedirectMatch ^/CGU$ https://www.immae.eu/CGU
191 ''
192 ];
193 nosslVhost = {
194 enable = true;
195 host = "nossl.immae.eu";
196 };
197 fallbackVhost = {
198 certName = "eldiron";
199 hosts = ["eldiron.immae.eu" ];
200 root = www_root;
201 extraConfig = [ "DirectoryIndex index.htm" ];
202 };
203 };
204
205 system.extraSystemBuilderCmds = lib.mkIf (builtins.length (builtins.attrValues config.myServices.websites.webappDirs) > 0) ''
206 mkdir -p $out/webapps
207 ${builtins.concatStringsSep "\n" (lib.attrsets.mapAttrsToList (name: path: "ln -s ${path} $out/webapps/${name}") config.myServices.websites.webappDirs)}
208 '';
209
210 myServices.websites = {
211 webappDirs = {
212 _www = pkgs.webapps.apache-default.www;
213 _theme = pkgs.webapps.apache-theme.theme;
214 };
215
216 aten.integration.enable = true;
217 aten.production.enable = true;
218
219 capitaines.production.enable = true;
220
221 chloe.integration.enable = true;
222 chloe.production.enable = true;
223
224 connexionswing.integration.enable = true;
225 connexionswing.production.enable = true;
226
227 denisejerome.production.enable = true;
228
229 emilia.production.enable = true;
230
231 florian.app.enable = true;
232 florian.integration.enable = true;
233 florian.production.enable = true;
234
235 immae.production.enable = true;
236 immae.release.enable = true;
237 immae.temp.enable = true;
238
239 leila.production.enable = true;
240
241 ludivinecassal.integration.enable = true;
242 ludivinecassal.production.enable = true;
243
244 nassime.production.enable = true;
245
246 naturaloutil.production.enable = true;
247
248 papa.surveillance.enable = true;
249
250 piedsjaloux.integration.enable = true;
251 piedsjaloux.production.enable = true;
252
253 tools.cloud.enable = true;
254 tools.dav.enable = true;
255 tools.db.enable = true;
256 tools.diaspora.enable = true;
257 tools.etherpad-lite.enable = true;
258 tools.git.enable = true;
259 tools.mastodon.enable = true;
260 tools.mediagoblin.enable = true;
261 tools.peertube.enable = true;
262 tools.tools.enable = true;
263 };
264 };
265}
diff --git a/modules/private/websites/denisejerome/production.nix b/modules/private/websites/denisejerome/production.nix
new file mode 100644
index 00000000..b5aff942
--- /dev/null
+++ b/modules/private/websites/denisejerome/production.nix
@@ -0,0 +1,31 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 cfg = config.myServices.websites.denisejerome.production;
4 varDir = "/var/lib/ftp/denisejerome";
5 env = myconfig.env.websites.denisejerome;
6in {
7 options.myServices.websites.denisejerome.production.enable = lib.mkEnableOption "enable Denise Jerome's website";
8
9 config = lib.mkIf cfg.enable {
10 services.webstats.sites = [ { name = "denisejerome.piedsjaloux.fr"; } ];
11
12 services.websites.production.vhostConfs.denisejerome = {
13 certName = "denisejerome";
14 certMainHost = "denisejerome.piedsjaloux.fr";
15 hosts = ["denisejerome.piedsjaloux.fr" ];
16 root = varDir;
17 extraConfig = [
18 ''
19 Use Stats denisejerome.piedsjaloux.fr
20
21 <Directory ${varDir}>
22 DirectoryIndex index.htm index.html
23 Options Indexes FollowSymLinks MultiViews Includes
24 AllowOverride AuthConfig
25 Require all granted
26 </Directory>
27 ''
28 ];
29 };
30 };
31}
diff --git a/modules/private/websites/emilia/moodle/pause.html b/modules/private/websites/emilia/moodle/pause.html
new file mode 100644
index 00000000..8b99c59f
--- /dev/null
+++ b/modules/private/websites/emilia/moodle/pause.html
@@ -0,0 +1,48 @@
1<!doctype html>
2<html>
3 <head>
4 <title>Pause</title>
5 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6 <style>
7 body {
8 padding-left: 5px;
9 padding-right: 5px;
10 text-align: center;
11 margin: auto;
12 font: 20px Helvetica, sans-serif;
13 color: #333;
14 }
15 h1 {
16 margin: 0px;
17 font-size: 40px;
18 }
19 article {
20 display: block;
21 max-width: 650px;
22 margin: 0 auto;
23 padding-top: 30px;
24 }
25 article + article {
26 border-top: 1px solid lightgrey;
27 }
28 article div {
29 text-align: justify;
30 }
31 a {
32 color: #dc8100;
33 text-decoration: none;
34 }
35 a:hover {
36 color: #333;
37 }
38 </style>
39 </head>
40 <body>
41 <article>
42 <h1>Site web en pause&nbsp;!</h1>
43 <div>
44 <p>Le site et les cours de photographie sont actuellement en pause.</p>
45 </div>
46 </article>
47 </body>
48</html>
diff --git a/modules/private/websites/emilia/production.nix b/modules/private/websites/emilia/production.nix
new file mode 100644
index 00000000..13f008f7
--- /dev/null
+++ b/modules/private/websites/emilia/production.nix
@@ -0,0 +1,66 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 cfg = config.myServices.websites.emilia.production;
4 env = myconfig.env.websites.emilia;
5 varDir = "/var/lib/moodle";
6 siteDir = ./moodle;
7 webappName = "emilia_moodle";
8 root = "/run/current-system/webapps/${webappName}";
9 # php_admin_value[upload_max_filesize] = 50000000
10 # php_admin_value[post_max_size] = 50000000
11 configFile = ''
12 <?php // Moodle configuration file
13
14 unset($CFG);
15 global $CFG;
16 $CFG = new stdClass();
17
18 $CFG->dbtype = 'pgsql';
19 $CFG->dblibrary = 'native';
20 $CFG->dbhost = '${env.postgresql.host}';
21 $CFG->dbname = '${env.postgresql.database}';
22 $CFG->dbuser = '${env.postgresql.user}';
23 $CFG->dbpass = '${env.postgresql.password}';
24 $CFG->prefix = 'mdl_';
25 $CFG->dboptions = array (
26 'dbpersist' => 0,
27 'dbport' => '${env.postgreesql.port}',
28 'dbsocket' => '${env.postgresql.password}',
29 );
30
31 $CFG->wwwroot = 'https://www.saison-photo.org';
32 $CFG->dataroot = '${varDir}';
33 $CFG->admin = 'admin';
34
35 $CFG->directorypermissions = 02777;
36
37 require_once(__DIR__ . '/lib/setup.php');
38
39 // There is no php closing tag in this file,
40 // it is intentional because it prevents trailing whitespace problems!
41 '';
42in {
43 options.myServices.websites.emilia.production.enable = lib.mkEnableOption "enable Emilia's website";
44
45 config = lib.mkIf cfg.enable {
46 system.activationScripts.emilia = ''
47 install -m 0755 -o wwwrun -g wwwrun -d ${varDir}
48 '';
49 myServices.websites.webappDirs."${webappName}" = siteDir;
50 services.websites.production.vhostConfs.emilia = {
51 certName = "emilia";
52 certMainHost = "saison-photo.org";
53 hosts = [ "saison-photo.org" "www.saison-photo.org" ];
54 root = root;
55 extraConfig = [
56 ''
57 <Directory ${root}>
58 DirectoryIndex pause.html
59 Options Indexes FollowSymLinks MultiViews Includes
60 Require all granted
61 </Directory>
62 ''
63 ];
64 };
65 };
66}
diff --git a/modules/private/websites/florian/app.nix b/modules/private/websites/florian/app.nix
new file mode 100644
index 00000000..3a6d1522
--- /dev/null
+++ b/modules/private/websites/florian/app.nix
@@ -0,0 +1,36 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 adminer = pkgs.callPackage ../commons/adminer.nix {};
4
5 tellesflorian = pkgs.callPackage ./builder_app.nix {
6 inherit (pkgs.webapps) tellesflorian;
7 config = myconfig.env.websites.tellesflorian.integration;
8 apacheUser = config.services.httpd.Inte.user;
9 apacheGroup = config.services.httpd.Inte.group;
10 };
11
12 cfg = config.myServices.websites.florian.app;
13in {
14 options.myServices.websites.florian.app.enable = lib.mkEnableOption "enable Florian's app in integration";
15
16 config = lib.mkIf cfg.enable {
17 secrets.keys = tellesflorian.keys;
18 systemd.services.phpfpm-tellesflorian_dev.after = lib.mkAfter tellesflorian.phpFpm.serviceDeps;
19 systemd.services.phpfpm-tellesflorian_dev.wants = tellesflorian.phpFpm.serviceDeps;
20 systemd.services.phpfpm-tellesflorian_dev.preStart = lib.mkAfter tellesflorian.phpFpm.preStart;
21 services.phpfpm.poolConfigs.tellesflorian_dev = tellesflorian.phpFpm.pool;
22 system.activationScripts.tellesflorian_dev = tellesflorian.activationScript;
23 myServices.websites.webappDirs."${tellesflorian.apache.webappName}" = tellesflorian.app.webRoot;
24 services.websites.integration.modules = adminer.apache.modules ++ tellesflorian.apache.modules;
25 services.websites.integration.vhostConfs.tellesflorian = {
26 certName = "eldiron";
27 addToCerts = true;
28 hosts = ["app.tellesflorian.com" ];
29 root = tellesflorian.apache.root;
30 extraConfig = [
31 tellesflorian.apache.vhostConf
32 adminer.apache.vhostConf
33 ];
34 };
35 };
36}
diff --git a/modules/private/websites/florian/builder_app.nix b/modules/private/websites/florian/builder_app.nix
new file mode 100644
index 00000000..e521f6eb
--- /dev/null
+++ b/modules/private/websites/florian/builder_app.nix
@@ -0,0 +1,152 @@
1{ apacheUser, apacheGroup, tellesflorian, config }:
2rec {
3 app = tellesflorian.override { inherit (config) environment; };
4 keys = [
5 {
6 dest = "webapps/${app.environment}-tellesflorian-passwords";
7 user = apacheUser;
8 group = apacheGroup;
9 permissions = "0400";
10 text = ''
11 invite:${config.invite_passwords}
12 '';
13 }
14 {
15 dest = "webapps/${app.environment}-tellesflorian";
16 user = apacheUser;
17 group = apacheGroup;
18 permissions = "0400";
19 text = ''
20 # This file is auto-generated during the composer install
21 parameters:
22 database_host: ${config.mysql.host}
23 database_port: ${config.mysql.port}
24 database_name: ${config.mysql.name}
25 database_user: ${config.mysql.user}
26 database_password: ${config.mysql.password}
27 mailer_transport: smtp
28 mailer_host: 127.0.0.1
29 mailer_user: null
30 mailer_password: null
31 secret: ${config.secret}
32 '';
33 }
34 ];
35 phpFpm = rec {
36 preStart = ''
37 if [ ! -f "${app.varDir}/currentWebappDir" -o \
38 ! -f "${app.varDir}/currentKey" -o \
39 "${app}" != "$(cat ${app.varDir}/currentWebappDir 2>/dev/null)" ] \
40 || ! sha512sum -c --status ${app.varDir}/currentKey; then
41 pushd ${app} > /dev/null
42 /run/wrappers/bin/sudo -u wwwrun ./bin/console --env=${app.environment} cache:clear --no-warmup
43 popd > /dev/null
44 echo -n "${app}" > ${app.varDir}/currentWebappDir
45 sha512sum /var/secrets/webapps/${app.environment}-tellesflorian > ${app.varDir}/currentKey
46 fi
47 '';
48 serviceDeps = [ "mysql.service" ];
49 socket = "/var/run/phpfpm/floriantelles-${app.environment}.sock";
50 pool = ''
51 listen = ${socket}
52 user = ${apacheUser}
53 group = ${apacheGroup}
54 listen.owner = ${apacheUser}
55 listen.group = ${apacheGroup}
56 php_admin_value[upload_max_filesize] = 20M
57 php_admin_value[post_max_size] = 20M
58 ;php_admin_flag[log_errors] = on
59 php_admin_value[open_basedir] = "/var/secrets/webapps/${app.environment}-tellesflorian:${app}:${app.varDir}:/tmp"
60 php_admin_value[session.save_path] = "${app.varDir}/phpSessions"
61 ${if app.environment == "dev" then ''
62 pm = ondemand
63 pm.max_children = 5
64 pm.process_idle_timeout = 60
65 env[SYMFONY_DEBUG_MODE] = "yes"
66 '' else ''
67 pm = dynamic
68 pm.max_children = 20
69 pm.start_servers = 2
70 pm.min_spare_servers = 1
71 pm.max_spare_servers = 3
72 ''}'';
73 };
74 apache = rec {
75 modules = [ "proxy_fcgi" ];
76 webappName = "florian_${app.environment}";
77 root = "/run/current-system/webapps/${webappName}";
78 vhostConf = ''
79 <FilesMatch "\.php$">
80 SetHandler "proxy:unix:${phpFpm.socket}|fcgi://localhost"
81 </FilesMatch>
82
83 ${if app.environment == "dev" then ''
84 <Location />
85 AuthBasicProvider file ldap
86 Use LDAPConnect
87 Require ldap-group cn=app.tellesflorian.com,cn=httpd,ou=services,dc=immae,dc=eu
88
89 AuthUserFile "/var/secrets/webapps/${app.environment}-tellesflorian-passwords"
90 Require user "invite"
91
92 ErrorDocument 401 "<html><meta http-equiv=\"refresh\" content=\"0;url=https://tellesflorian.com\"></html>"
93 </Location>
94
95 <Directory ${root}>
96 Options Indexes FollowSymLinks MultiViews Includes
97 AllowOverride None
98 Require all granted
99
100 DirectoryIndex app_dev.php
101
102 <IfModule mod_negotiation.c>
103 Options -MultiViews
104 </IfModule>
105
106 <IfModule mod_rewrite.c>
107 RewriteEngine On
108
109 RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$
110 RewriteRule ^(.*) - [E=BASE:%1]
111
112 # Maintenance script
113 RewriteCond %{DOCUMENT_ROOT}/maintenance.php -f
114 RewriteCond %{SCRIPT_FILENAME} !maintenance.php
115 RewriteRule ^.*$ %{ENV:BASE}/maintenance.php [R=503,L]
116 ErrorDocument 503 /maintenance.php
117
118 # Sets the HTTP_AUTHORIZATION header removed by Apache
119 RewriteCond %{HTTP:Authorization} .
120 RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
121
122 RewriteCond %{ENV:REDIRECT_STATUS} ^$
123 RewriteRule ^app_dev\.php(?:/(.*)|$) %{ENV:BASE}/$1 [R=301,L]
124
125 # If the requested filename exists, simply serve it.
126 # We only want to let Apache serve files and not directories.
127 RewriteCond %{REQUEST_FILENAME} -f
128 RewriteRule ^ - [L]
129
130 # Rewrite all other queries to the front controller.
131 RewriteRule ^ %{ENV:BASE}/app_dev.php [L]
132 </IfModule>
133
134 </Directory>
135 '' else ''
136 <Directory ${root}>
137 Options Indexes FollowSymLinks MultiViews Includes
138 AllowOverride All
139 Require all granted
140 </Directory>
141 ''}
142 '';
143 };
144 activationScript = {
145 deps = [ "wrappers" ];
146 text = ''
147 install -m 0755 -o ${apacheUser} -g ${apacheGroup} -d ${app.varDir} \
148 ${app.varDir}/var
149 install -m 0750 -o ${apacheUser} -g ${apacheGroup} -d ${app.varDir}/phpSessions
150 '';
151 };
152}
diff --git a/modules/private/websites/florian/integration.nix b/modules/private/websites/florian/integration.nix
new file mode 100644
index 00000000..424ebd48
--- /dev/null
+++ b/modules/private/websites/florian/integration.nix
@@ -0,0 +1,34 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 adminer = pkgs.callPackage ../commons/adminer.nix {};
4 cfg = config.myServices.websites.florian.integration;
5 varDir = "/var/lib/ftp/florian";
6 env = myconfig.env.websites.florian;
7in {
8 options.myServices.websites.florian.integration.enable = lib.mkEnableOption "enable Florian's website integration";
9
10 config = lib.mkIf cfg.enable {
11 security.acme.certs."ftp".extraDomains."florian.immae.eu" = null;
12
13 services.websites.integration.modules = adminer.apache.modules;
14 services.websites.integration.vhostConfs.florian = {
15 certName = "eldiron";
16 addToCerts = true;
17 hosts = [ "florian.immae.eu" ];
18 root = "${varDir}/florian.immae.eu";
19 extraConfig = [
20 adminer.apache.vhostConf
21 ''
22 ServerAdmin ${env.server_admin}
23
24 <Directory ${varDir}/florian.immae.eu>
25 DirectoryIndex index.php index.htm index.html
26 Options Indexes FollowSymLinks MultiViews Includes
27 AllowOverride None
28 Require all granted
29 </Directory>
30 ''
31 ];
32 };
33 };
34}
diff --git a/modules/private/websites/florian/production.nix b/modules/private/websites/florian/production.nix
new file mode 100644
index 00000000..9b310b8b
--- /dev/null
+++ b/modules/private/websites/florian/production.nix
@@ -0,0 +1,34 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 adminer = pkgs.callPackage ../commons/adminer.nix {};
4 cfg = config.myServices.websites.florian.production;
5 varDir = "/var/lib/ftp/florian";
6 env = myconfig.env.websites.florian;
7in {
8 options.myServices.websites.florian.production.enable = lib.mkEnableOption "enable Florian's website production";
9
10 config = lib.mkIf cfg.enable {
11 security.acme.certs."ftp".extraDomains."tellesflorian.com" = null;
12
13 services.websites.production.modules = adminer.apache.modules;
14 services.websites.production.vhostConfs.florian = {
15 certName = "florian";
16 certMainHost = "tellesflorian.com";
17 hosts = [ "tellesflorian.com" "www.tellesflorian.com" ];
18 root = "${varDir}/tellesflorian.com";
19 extraConfig = [
20 adminer.apache.vhostConf
21 ''
22 ServerAdmin ${env.server_admin}
23
24 <Directory ${varDir}/tellesflorian.com>
25 DirectoryIndex index.php index.htm index.html
26 Options Indexes FollowSymLinks MultiViews Includes
27 AllowOverride None
28 Require all granted
29 </Directory>
30 ''
31 ];
32 };
33 };
34}
diff --git a/modules/private/websites/immae/production.nix b/modules/private/websites/immae/production.nix
new file mode 100644
index 00000000..c3cabb6a
--- /dev/null
+++ b/modules/private/websites/immae/production.nix
@@ -0,0 +1,64 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 cfg = config.myServices.websites.immae.production;
4 varDir = "/var/lib/ftp/immae";
5 env = myconfig.env.websites.immae;
6in {
7 options.myServices.websites.immae.production.enable = lib.mkEnableOption "enable Immae's website";
8
9 config = lib.mkIf cfg.enable {
10 services.webstats.sites = [ { name = "www.immae.eu"; } ];
11
12 services.phpfpm.poolConfigs.immae = ''
13 listen = /run/phpfpm/immae.sock
14 user = wwwrun
15 group = wwwrun
16 listen.owner = wwwrun
17 listen.group = wwwrun
18
19 pm = ondemand
20 pm.max_children = 5
21 pm.process_idle_timeout = 60
22
23 php_admin_value[open_basedir] = "${varDir}:/tmp"
24 '';
25 services.websites.production.modules = [ "proxy_fcgi" ];
26 services.websites.production.vhostConfs.immae = {
27 certName = "eldiron";
28 addToCerts = true;
29 hosts = [ "www.immae.eu" ];
30 root = varDir;
31 extraConfig = [
32 ''
33 Use Stats www.immae.eu
34
35 <FilesMatch "\.php$">
36 SetHandler "proxy:unix:/run/phpfpm/immae.sock|fcgi://localhost"
37 </FilesMatch>
38
39 <Directory ${varDir}>
40 DirectoryIndex index.php index.htm index.html
41 Options Indexes FollowSymLinks MultiViews Includes
42 AllowOverride All
43 Require all granted
44 </Directory>
45
46 <Location /blog_old/>
47 Use LDAPConnect
48 Require ldap-group cn=blog,cn=immae.eu,ou=services,dc=immae,dc=eu
49 </Location>
50 ''
51 ];
52 };
53
54 services.websites.production.vhostConfs.bouya = {
55 certName = "eldiron";
56 addToCerts = true;
57 hosts = [ "bouya.org" "www.bouya.org" ];
58 root = null;
59 extraConfig = [ ''
60 RedirectMatch 301 ^/((?!\.well-known.*$).*)$ https://www.normalesup.org/~bouya/
61 '' ];
62 };
63 };
64}
diff --git a/modules/private/websites/immae/release.nix b/modules/private/websites/immae/release.nix
new file mode 100644
index 00000000..68381a6a
--- /dev/null
+++ b/modules/private/websites/immae/release.nix
@@ -0,0 +1,39 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 cfg = config.myServices.websites.immae.release;
4 varDir = "/var/lib/ftp/release.immae.eu";
5 env = myconfig.env.websites.release;
6in {
7 options.myServices.websites.immae.release.enable = lib.mkEnableOption "enable Release' website";
8
9 config = lib.mkIf cfg.enable {
10 services.webstats.sites = [ { name = "release.immae.eu"; } ];
11
12 services.websites.production.vhostConfs.release = {
13 certName = "eldiron";
14 addToCerts = true;
15 hosts = [ "release.immae.eu" ];
16 root = varDir;
17 extraConfig = [
18 ''
19 Use Stats release.immae.eu
20
21 Use Apaxy "${varDir}" "title .duplicity-ignore"
22 <Directory "${varDir}">
23 Use LDAPConnect
24 Options Indexes
25 AllowOverride All
26 Require all granted
27 </Directory>
28
29 <Directory "${varDir}/packages">
30 Use LDAPConnect
31 Options Indexes FollowSymlinks
32 AllowOverride None
33 Require all granted
34 </Directory>
35 ''
36 ];
37 };
38 };
39}
diff --git a/modules/private/websites/immae/temp.nix b/modules/private/websites/immae/temp.nix
new file mode 100644
index 00000000..0b2a3a3e
--- /dev/null
+++ b/modules/private/websites/immae/temp.nix
@@ -0,0 +1,36 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 cfg = config.myServices.websites.immae.temp;
4 varDir = "/var/lib/ftp/temp.immae.eu";
5 env = myconfig.env.websites.temp;
6in {
7 options.myServices.websites.immae.temp.enable = lib.mkEnableOption "enable Temp' website";
8
9 config = lib.mkIf cfg.enable {
10 services.websites.production.modules = [ "headers" ];
11 services.websites.production.vhostConfs.temp = {
12 certName = "eldiron";
13 addToCerts = true;
14 hosts = [ "temp.immae.eu" ];
15 root = varDir;
16 extraConfig = [
17 ''
18 Use Apaxy "${varDir}" "title .duplicity-ignore"
19 <FilesMatch ".+">
20 Header set Content-Disposition attachment
21 </FilesMatch>
22 <Directory "${varDir}">
23 Options -Indexes
24 AllowOverride None
25 Require all granted
26 </Directory>
27
28 <DirectoryMatch "${varDir}/(.+)">
29 Options Indexes
30 </DirectoryMatch>
31 ''
32 ];
33 };
34 };
35}
36
diff --git a/modules/private/websites/leila/production.nix b/modules/private/websites/leila/production.nix
new file mode 100644
index 00000000..69c8c497
--- /dev/null
+++ b/modules/private/websites/leila/production.nix
@@ -0,0 +1,82 @@
1{ lib, pkgs, config, ... }:
2let
3 cfg = config.myServices.websites.leila.production;
4 varDir = "/var/lib/ftp/leila";
5in {
6 options.myServices.websites.leila.production.enable = lib.mkEnableOption "enable Leila's website in production";
7
8 config = lib.mkIf cfg.enable {
9 services.phpfpm.poolConfigs.leila = ''
10 listen = /run/phpfpm/leila.sock
11 user = wwwrun
12 group = wwwrun
13 listen.owner = wwwrun
14 listen.group = wwwrun
15
16 pm = ondemand
17 pm.max_children = 5
18 pm.process_idle_timeout = 60
19
20 php_admin_value[open_basedir] = "${varDir}:/tmp"
21 '';
22
23 services.webstats.sites = [
24 { name = "leila.bouya.org"; }
25 { name = "chorale.leila.bouya.org"; }
26 ];
27
28 services.websites.production.modules = [ "proxy_fcgi" ];
29 services.websites.production.vhostConfs.leila_chorale = {
30 certName = "leila";
31 addToCerts = true;
32 hosts = [ "chorale.leila.bouya.org" "chorale-vocanta.fr.nf" "www.chorale-vocanta.fr.nf" ];
33 root = "${varDir}/Chorale";
34 extraConfig = [
35 ''
36 Use Stats chorale.leila.bouya.org
37 <Directory ${varDir}/Chorale>
38 DirectoryIndex index.php index.htm index.html
39 Options Indexes FollowSymLinks MultiViews Includes
40 AllowOverride None
41
42 Use LDAPConnect
43 Require ldap-group cn=chorale.leila.bouya.org,cn=httpd,ou=services,dc=immae,dc=eu
44
45 <FilesMatch "\.php$">
46 SetHandler "proxy:unix:/run/phpfpm/leila.sock|fcgi://localhost"
47 </FilesMatch>
48 </Directory>
49 ''
50 ];
51 };
52 services.websites.production.vhostConfs.leila = {
53 certName = "leila";
54 certMainHost = "leila.bouya.org";
55 hosts = [ "leila.bouya.org" ];
56 root = varDir;
57 extraConfig = [
58 ''
59 Use Stats leila.bouya.org
60 <Directory ${varDir}/Chorale>
61 DirectoryIndex index.htm index.html
62 Options Indexes FollowSymLinks MultiViews Includes
63 AllowOverride None
64
65 Use LDAPConnect
66 Require ldap-group cn=chorale.leila.bouya.org,cn=httpd,ou=services,dc=immae,dc=eu
67
68 <FilesMatch "\.php$">
69 SetHandler "proxy:unix:/run/phpfpm/leila.sock|fcgi://localhost"
70 </FilesMatch>
71 </Directory>
72 <Directory ${varDir}>
73 DirectoryIndex index.htm index.html
74 Options Indexes FollowSymLinks MultiViews Includes
75 AllowOverride None
76 Require all granted
77 </Directory>
78 ''
79 ];
80 };
81 };
82}
diff --git a/modules/private/websites/ludivinecassal/builder.nix b/modules/private/websites/ludivinecassal/builder.nix
new file mode 100644
index 00000000..3167bce7
--- /dev/null
+++ b/modules/private/websites/ludivinecassal/builder.nix
@@ -0,0 +1,155 @@
1{ apacheUser, apacheGroup, config, ludivinecassal, pkgs, ruby, sass, imagemagick }:
2rec {
3 app = ludivinecassal.override { inherit (config) environment; };
4 varDir = "/var/lib/ludivinecassal_${app.environment}";
5 keys = [{
6 dest = "webapps/${app.environment}-ludivinecassal";
7 user = apacheUser;
8 group = apacheGroup;
9 permissions = "0400";
10 text = ''
11 # This file is auto-generated during the composer install
12 parameters:
13 database_host: ${config.mysql.host}
14 database_port: ${config.mysql.port}
15 database_name: ${config.mysql.name}
16 database_user: ${config.mysql.user}
17 database_password: ${config.mysql.password}
18 database_server_version: ${pkgs.mariadb.mysqlVersion}
19 mailer_transport: smtp
20 mailer_host: 127.0.0.1
21 mailer_user: null
22 mailer_password: null
23 secret: ${config.secret}
24 ldap_host: ldap.immae.eu
25 ldap_port: 636
26 ldap_version: 3
27 ldap_ssl: true
28 ldap_tls: false
29 ldap_user_bind: 'uid={username},ou=users,dc=immae,dc=eu'
30 ldap_base_dn: 'dc=immae,dc=eu'
31 ldap_search_dn: '${config.ldap.dn}'
32 ldap_search_password: '${config.ldap.password}'
33 ldap_search_filter: '${config.ldap.search}'
34 leapt_im:
35 binary_path: ${imagemagick}/bin
36 assetic:
37 sass: ${sass}/bin/sass
38 ruby: ${ruby}/bin/ruby
39 '';
40 }];
41 phpFpm = rec {
42 preStart = ''
43 if [ ! -f "${app.varDir}/currentWebappDir" -o \
44 ! -f "${app.varDir}/currentKey" -o \
45 "${app}" != "$(cat ${app.varDir}/currentWebappDir 2>/dev/null)" ] \
46 || ! sha512sum -c --status ${app.varDir}/currentKey; then
47 pushd ${app} > /dev/null
48 /run/wrappers/bin/sudo -u ${apacheUser} ./bin/console --env=${app.environment} cache:clear --no-warmup
49 popd > /dev/null
50 echo -n "${app}" > ${app.varDir}/currentWebappDir
51 sha512sum /var/secrets/webapps/${app.environment}-ludivinecassal > ${app.varDir}/currentKey
52 fi
53 '';
54 serviceDeps = [ "mysql.service" ];
55 socket = "/var/run/phpfpm/ludivinecassal-${app.environment}.sock";
56 pool = ''
57 listen = ${socket}
58 user = ${apacheUser}
59 group = ${apacheGroup}
60 listen.owner = ${apacheUser}
61 listen.group = ${apacheGroup}
62 php_admin_value[upload_max_filesize] = 20M
63 php_admin_value[post_max_size] = 20M
64 ;php_admin_flag[log_errors] = on
65 php_admin_value[open_basedir] = "/var/secrets/webapps/${app.environment}-ludivinecassal:${app}:${app.varDir}:/tmp"
66 php_admin_value[session.save_path] = "${app.varDir}/phpSessions"
67 ${if app.environment == "dev" then ''
68 pm = ondemand
69 pm.max_children = 5
70 pm.process_idle_timeout = 60
71 env[SYMFONY_DEBUG_MODE] = "yes"
72 '' else ''
73 pm = dynamic
74 pm.max_children = 20
75 pm.start_servers = 2
76 pm.min_spare_servers = 1
77 pm.max_spare_servers = 3
78 ''}'';
79 };
80 apache = rec {
81 modules = [ "proxy_fcgi" ];
82 webappName = "ludivine_${app.environment}";
83 root = "/run/current-system/webapps/${webappName}";
84 vhostConf = ''
85 <FilesMatch "\.php$">
86 SetHandler "proxy:unix:${phpFpm.socket}|fcgi://localhost"
87 </FilesMatch>
88
89 ${if app.environment == "dev" then ''
90 <Location />
91 Use LDAPConnect
92 Require ldap-group cn=ludivine.immae.eu,cn=httpd,ou=services,dc=immae,dc=eu
93 ErrorDocument 401 "<html><meta http-equiv=\"refresh\" content=\"0;url=https://ludivinecassal.com\"></html>"
94 </Location>
95
96 <Directory ${root}>
97 Options Indexes FollowSymLinks MultiViews Includes
98 AllowOverride None
99 Require all granted
100
101 DirectoryIndex app_dev.php
102
103 <IfModule mod_negotiation.c>
104 Options -MultiViews
105 </IfModule>
106
107 <IfModule mod_rewrite.c>
108 RewriteEngine On
109
110 RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$
111 RewriteRule ^(.*) - [E=BASE:%1]
112
113 # Maintenance script
114 RewriteCond %{DOCUMENT_ROOT}/maintenance.php -f
115 RewriteCond %{SCRIPT_FILENAME} !maintenance.php
116 RewriteRule ^.*$ %{ENV:BASE}/maintenance.php [R=503,L]
117 ErrorDocument 503 /maintenance.php
118
119 # Sets the HTTP_AUTHORIZATION header removed by Apache
120 RewriteCond %{HTTP:Authorization} .
121 RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
122
123 RewriteCond %{ENV:REDIRECT_STATUS} ^$
124 RewriteRule ^app_dev\.php(?:/(.*)|$) %{ENV:BASE}/$1 [R=301,L]
125
126 # If the requested filename exists, simply serve it.
127 # We only want to let Apache serve files and not directories.
128 RewriteCond %{REQUEST_FILENAME} -f
129 RewriteRule ^ - [L]
130
131 # Rewrite all other queries to the front controller.
132 RewriteRule ^ %{ENV:BASE}/app_dev.php [L]
133 </IfModule>
134
135 </Directory>
136 '' else ''
137 Use Stats ludivinecassal.com
138
139 <Directory ${root}>
140 Options Indexes FollowSymLinks MultiViews Includes
141 AllowOverride All
142 Require all granted
143 </Directory>
144 ''}
145 '';
146 };
147 activationScript = {
148 deps = [ "wrappers" ];
149 text = ''
150 install -m 0755 -o ${apacheUser} -g ${apacheGroup} -d ${app.varDir}
151 install -m 0755 -o ${apacheUser} -g ${apacheGroup} -d ${app.varDir}/tmp
152 install -m 0750 -o ${apacheUser} -g ${apacheGroup} -d ${app.varDir}/phpSessions
153 '';
154 };
155}
diff --git a/modules/private/websites/ludivinecassal/integration.nix b/modules/private/websites/ludivinecassal/integration.nix
new file mode 100644
index 00000000..ed0dc9fe
--- /dev/null
+++ b/modules/private/websites/ludivinecassal/integration.nix
@@ -0,0 +1,32 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 ludivinecassal = pkgs.callPackage ./builder.nix {
4 inherit (pkgs.webapps) ludivinecassal;
5 config = myconfig.env.websites.ludivinecassal.integration;
6 apacheUser = config.services.httpd.Inte.user;
7 apacheGroup = config.services.httpd.Inte.group;
8 };
9
10 cfg = config.myServices.websites.ludivinecassal.integration;
11in {
12 options.myServices.websites.ludivinecassal.integration.enable = lib.mkEnableOption "enable Ludivine's website in integration";
13
14 config = lib.mkIf cfg.enable {
15 secrets.keys = ludivinecassal.keys;
16
17 systemd.services.phpfpm-ludivinecassal_dev.after = lib.mkAfter ludivinecassal.phpFpm.serviceDeps;
18 systemd.services.phpfpm-ludivinecassal_dev.wants = ludivinecassal.phpFpm.serviceDeps;
19 systemd.services.phpfpm-ludivinecassal_dev.preStart = lib.mkAfter ludivinecassal.phpFpm.preStart;
20 services.phpfpm.poolConfigs.ludivinecassal_dev = ludivinecassal.phpFpm.pool;
21 system.activationScripts.ludivinecassal_dev = ludivinecassal.activationScript;
22 myServices.websites.webappDirs."${ludivinecassal.apache.webappName}" = ludivinecassal.app.webRoot;
23 services.websites.integration.modules = ludivinecassal.apache.modules;
24 services.websites.integration.vhostConfs.ludivine = {
25 certName = "eldiron";
26 addToCerts = true;
27 hosts = [ "ludivine.immae.eu" ];
28 root = ludivinecassal.apache.root;
29 extraConfig = [ ludivinecassal.apache.vhostConf ];
30 };
31 };
32}
diff --git a/modules/private/websites/ludivinecassal/production.nix b/modules/private/websites/ludivinecassal/production.nix
new file mode 100644
index 00000000..3df5613f
--- /dev/null
+++ b/modules/private/websites/ludivinecassal/production.nix
@@ -0,0 +1,33 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 ludivinecassal = pkgs.callPackage ./builder.nix {
4 inherit (pkgs.webapps) ludivinecassal;
5 config = myconfig.env.websites.ludivinecassal.production;
6 apacheUser = config.services.httpd.Prod.user;
7 apacheGroup = config.services.httpd.Prod.group;
8 };
9
10 cfg = config.myServices.websites.ludivinecassal.production;
11in {
12 options.myServices.websites.ludivinecassal.production.enable = lib.mkEnableOption "enable Ludivine's website in production";
13
14 config = lib.mkIf cfg.enable {
15 secrets.keys = ludivinecassal.keys;
16 services.webstats.sites = [ { name = "ludivinecassal.com"; } ];
17
18 systemd.services.phpfpm-ludivinecassal_prod.after = lib.mkAfter ludivinecassal.phpFpm.serviceDeps;
19 systemd.services.phpfpm-ludivinecassal_prod.wants = ludivinecassal.phpFpm.serviceDeps;
20 systemd.services.phpfpm-ludivinecassal_prod.preStart = lib.mkAfter ludivinecassal.phpFpm.preStart;
21 services.phpfpm.poolConfigs.ludivinecassal_prod = ludivinecassal.phpFpm.pool;
22 system.activationScripts.ludivinecassal_prod = ludivinecassal.activationScript;
23 myServices.websites.webappDirs."${ludivinecassal.apache.webappName}" = ludivinecassal.app.webRoot;
24 services.websites.production.modules = ludivinecassal.apache.modules;
25 services.websites.production.vhostConfs.ludivine = {
26 certName = "ludivinecassal";
27 certMainHost = "ludivinecassal.com";
28 hosts = ["ludivinecassal.com" "www.ludivinecassal.com" ];
29 root = ludivinecassal.apache.root;
30 extraConfig = [ ludivinecassal.apache.vhostConf ];
31 };
32 };
33}
diff --git a/modules/private/websites/nassime/production.nix b/modules/private/websites/nassime/production.nix
new file mode 100644
index 00000000..a1097784
--- /dev/null
+++ b/modules/private/websites/nassime/production.nix
@@ -0,0 +1,34 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 cfg = config.myServices.websites.nassime.production;
4 varDir = "/var/lib/ftp/nassime";
5 env = myconfig.env.websites.nassime;
6in {
7 options.myServices.websites.nassime.production.enable = lib.mkEnableOption "enable Nassime's website";
8
9 config = lib.mkIf cfg.enable {
10 services.webstats.sites = [ { name = "nassime.bouya.org"; } ];
11
12 security.acme.certs."ftp".extraDomains."nassime.bouya.org" = null;
13
14 services.websites.production.vhostConfs.nassime = {
15 certName = "nassime";
16 certMainHost = "nassime.bouya.org";
17 hosts = ["nassime.bouya.org" ];
18 root = varDir;
19 extraConfig = [
20 ''
21 Use Stats nassime.bouya.org
22 ServerAdmin ${env.server_admin}
23
24 <Directory ${varDir}>
25 DirectoryIndex index.php index.htm index.html
26 Options Indexes FollowSymLinks MultiViews Includes
27 AllowOverride None
28 Require all granted
29 </Directory>
30 ''
31 ];
32 };
33 };
34}
diff --git a/modules/private/websites/naturaloutil/production.nix b/modules/private/websites/naturaloutil/production.nix
new file mode 100644
index 00000000..f59957da
--- /dev/null
+++ b/modules/private/websites/naturaloutil/production.nix
@@ -0,0 +1,96 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 adminer = pkgs.callPackage ../commons/adminer.nix {};
4 cfg = config.myServices.websites.naturaloutil.production;
5 varDir = "/var/lib/ftp/jerome";
6 env = myconfig.env.websites.jerome;
7in {
8 options.myServices.websites.naturaloutil.production.enable = lib.mkEnableOption "enable Naturaloutil's website";
9
10 config = lib.mkIf cfg.enable {
11 services.webstats.sites = [ { name = "naturaloutil.immae.eu"; } ];
12
13 security.acme.certs."ftp".extraDomains."naturaloutil.immae.eu" = null;
14
15 secrets.keys = [{
16 dest = "webapps/prod-naturaloutil";
17 user = "wwwrun";
18 group = "wwwrun";
19 permissions = "0400";
20 text = ''
21 <?php
22 $mysql_user = '${env.mysql.user}' ;
23 $mysql_server = '${env.mysql.host}' ;
24 $mysql_base = '${env.mysql.name}' ;
25 $mysql_password = '${env.mysql.password}' ;
26 //connect to db
27 $db = mysqli_init();
28 ${if env.mysql.host != "localhost" then ''
29 mysqli_options ($db, MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, true);
30 $db->ssl_set(NULL, NULL, "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt", NULL, NULL);
31 '' else ""}
32 $database = connect_db($db, $mysql_server, $mysql_base, $mysql_user, $mysql_password);
33 ?>
34 '';
35 }];
36 system.activationScripts.naturaloutil = {
37 deps = [ "httpd" ];
38 text = ''
39 install -m 0755 -o wwwrun -g wwwrun -d /var/lib/php/sessions/naturaloutil
40 '';
41 };
42 systemd.services.phpfpm-jerome.after = lib.mkAfter [ "mysql.service" ];
43 systemd.services.phpfpm-jerome.wants = [ "mysql.service" ];
44 services.phpfpm.pools.jerome = {
45 listen = "/run/phpfpm/naturaloutil.sock";
46 extraConfig = ''
47 user = wwwrun
48 group = wwwrun
49 listen.owner = wwwrun
50 listen.group = wwwrun
51
52 pm = ondemand
53 pm.max_children = 5
54 pm.process_idle_timeout = 60
55
56 env[BDD_CONNECT] = "/var/secrets/webapps/prod-naturaloutil"
57 php_admin_value[open_basedir] = "/var/lib/php/sessions/naturaloutil:/var/secrets/webapps/prod-naturaloutil:${varDir}:/tmp"
58 php_admin_value[session.save_path] = "/var/lib/php/sessions/naturaloutil"
59 '';
60 phpOptions = config.services.phpfpm.phpOptions + ''
61 extension=${pkgs.php}/lib/php/extensions/mysqli.so
62 '';
63 };
64 services.websites.production.modules = adminer.apache.modules ++ [ "proxy_fcgi" ];
65 services.websites.production.vhostConfs.naturaloutil = {
66 certName = "naturaloutil";
67 certMainHost = "naturaloutil.immae.eu";
68 hosts = ["naturaloutil.immae.eu" ];
69 root = varDir;
70 extraConfig = [
71 adminer.apache.vhostConf
72 ''
73 Use Stats naturaloutil.immae.eu
74 ServerAdmin ${env.server_admin}
75 ErrorLog "${varDir}/logs/error_log"
76 CustomLog "${varDir}/logs/access_log" combined
77
78 <FilesMatch "\.php$">
79 SetHandler "proxy:unix:/run/phpfpm/naturaloutil.sock|fcgi://localhost"
80 </FilesMatch>
81
82 <Directory ${varDir}/logs>
83 AllowOverride None
84 Require all denied
85 </Directory>
86 <Directory ${varDir}>
87 DirectoryIndex index.php index.htm index.html
88 Options Indexes FollowSymLinks MultiViews Includes
89 AllowOverride None
90 Require all granted
91 </Directory>
92 ''
93 ];
94 };
95 };
96}
diff --git a/modules/private/websites/papa/surveillance.nix b/modules/private/websites/papa/surveillance.nix
new file mode 100644
index 00000000..8e7cd9db
--- /dev/null
+++ b/modules/private/websites/papa/surveillance.nix
@@ -0,0 +1,49 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 cfg = config.myServices.websites.papa.surveillance;
4 varDir = "/var/lib/ftp/papa";
5in {
6 options.myServices.websites.papa.surveillance.enable = lib.mkEnableOption "enable Papa surveillance's website";
7
8 config = lib.mkIf cfg.enable {
9 security.acme.certs."ftp".extraDomains."surveillance.maison.bbc.bouya.org" = null;
10
11 services.cron = {
12 systemCronJobs = let
13 script = pkgs.writeScript "cleanup-papa" ''
14 #!${pkgs.stdenv.shell}
15 d=$(date -d "7 days ago" +%Y%m%d)
16 for i in /var/lib/ftp/papa/*/20[0-9][0-9][0-9][0-9][0-9][0-9]; do
17 if [ "$d" -gt $(basename $i) ]; then
18 rm -rf "$i"
19 fi
20 done
21 '';
22 in
23 [
24 ''
25 0 6 * * * wwwrun ${script}
26 ''
27 ];
28 };
29
30 services.websites.production.vhostConfs.papa = {
31 certName = "papa";
32 certMainHost = "surveillance.maison.bbc.bouya.org";
33 hosts = [ "surveillance.maison.bbc.bouya.org" ];
34 root = varDir;
35 extraConfig = [
36 ''
37 Use Apaxy "${varDir}" "title .duplicity-ignore"
38 <Directory ${varDir}>
39 Use LDAPConnect
40 Options Indexes
41 AllowOverride None
42 Require ldap-group cn=surveillance.maison.bbc.bouya.org,cn=httpd,ou=services,dc=immae,dc=eu
43 </Directory>
44 ''
45 ];
46 };
47 };
48}
49
diff --git a/modules/private/websites/piedsjaloux/builder.nix b/modules/private/websites/piedsjaloux/builder.nix
new file mode 100644
index 00000000..9fcc8fb6
--- /dev/null
+++ b/modules/private/websites/piedsjaloux/builder.nix
@@ -0,0 +1,144 @@
1{ apacheUser, apacheGroup, piedsjaloux, config, pkgs, lib, texlive, imagemagick }:
2rec {
3 app = piedsjaloux.override { inherit (config) environment; };
4 varDir = "/var/lib/piedsjaloux_${app.environment}";
5 keys = [{
6 dest = "webapps/${app.environment}-piedsjaloux";
7 user = apacheUser;
8 group = apacheGroup;
9 permissions = "0400";
10 text = ''
11 # This file is auto-generated during the composer install
12 parameters:
13 database_host: ${config.mysql.host}
14 database_port: ${config.mysql.port}
15 database_name: ${config.mysql.name}
16 database_user: ${config.mysql.user}
17 database_password: ${config.mysql.password}
18 database_server_version: ${pkgs.mariadb.mysqlVersion}
19 mailer_transport: smtp
20 mailer_host: 127.0.0.1
21 mailer_user: null
22 mailer_password: null
23 secret: ${config.secret}
24 pdflatex: "${texlive.combine { inherit (texlive) attachfile preprint scheme-small; }}/bin/pdflatex"
25 leapt_im:
26 binary_path: ${imagemagick}/bin
27 '';
28 }];
29 phpFpm = rec {
30 preStart = ''
31 if [ ! -f "${app.varDir}/currentWebappDir" -o \
32 ! -f "${app.varDir}/currentKey" -o \
33 "${app}" != "$(cat ${app.varDir}/currentWebappDir 2>/dev/null)" ] \
34 || ! sha512sum -c --status ${app.varDir}/currentKey; then
35 pushd ${app} > /dev/null
36 /run/wrappers/bin/sudo -u ${apacheUser} ./bin/console --env=${app.environment} cache:clear --no-warmup
37 popd > /dev/null
38 echo -n "${app}" > ${app.varDir}/currentWebappDir
39 sha512sum /var/secrets/webapps/${app.environment}-piedsjaloux > ${app.varDir}/currentKey
40 fi
41 '';
42 serviceDeps = [ "mysql.service" ];
43 socket = "/var/run/phpfpm/piedsjaloux-${app.environment}.sock";
44 pool = ''
45 listen = ${socket}
46 user = ${apacheUser}
47 group = ${apacheGroup}
48 listen.owner = ${apacheUser}
49 listen.group = ${apacheGroup}
50 php_admin_value[upload_max_filesize] = 20M
51 php_admin_value[post_max_size] = 20M
52 ;php_admin_flag[log_errors] = on
53 php_admin_value[open_basedir] = "/var/secrets/webapps/${app.environment}-piedsjaloux:${app}:${app.varDir}:/tmp"
54 php_admin_value[session.save_path] = "${app.varDir}/phpSessions"
55 env[PATH] = ${lib.makeBinPath [ pkgs.apg pkgs.unzip ]}
56 ${if app.environment == "dev" then ''
57 pm = ondemand
58 pm.max_children = 5
59 pm.process_idle_timeout = 60
60 env[SYMFONY_DEBUG_MODE] = "yes"
61 '' else ''
62 pm = dynamic
63 pm.max_children = 20
64 pm.start_servers = 2
65 pm.min_spare_servers = 1
66 pm.max_spare_servers = 3
67 ''}'';
68 };
69 apache = rec {
70 modules = [ "proxy_fcgi" ];
71 webappName = "piedsjaloux_${app.environment}";
72 root = "/run/current-system/webapps/${webappName}";
73 vhostConf = ''
74 <FilesMatch "\.php$">
75 SetHandler "proxy:unix:${phpFpm.socket}|fcgi://localhost"
76 </FilesMatch>
77
78 ${if app.environment == "dev" then ''
79 <Location />
80 Use LDAPConnect
81 Require ldap-group cn=piedsjaloux.immae.eu,cn=httpd,ou=services,dc=immae,dc=eu
82 ErrorDocument 401 "<html><meta http-equiv=\"refresh\" content=\"0;url=https://piedsjaloux.fr\"></html>"
83 </Location>
84
85 <Directory ${root}>
86 Options Indexes FollowSymLinks MultiViews Includes
87 AllowOverride None
88 Require all granted
89
90 DirectoryIndex app_dev.php
91
92 <IfModule mod_negotiation.c>
93 Options -MultiViews
94 </IfModule>
95
96 <IfModule mod_rewrite.c>
97 RewriteEngine On
98
99 RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$
100 RewriteRule ^(.*) - [E=BASE:%1]
101
102 # Maintenance script
103 RewriteCond %{DOCUMENT_ROOT}/maintenance.php -f
104 RewriteCond %{SCRIPT_FILENAME} !maintenance.php
105 RewriteRule ^.*$ %{ENV:BASE}/maintenance.php [R=503,L]
106 ErrorDocument 503 /maintenance.php
107
108 # Sets the HTTP_AUTHORIZATION header removed by Apache
109 RewriteCond %{HTTP:Authorization} .
110 RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
111
112 RewriteCond %{ENV:REDIRECT_STATUS} ^$
113 RewriteRule ^app_dev\.php(?:/(.*)|$) %{ENV:BASE}/$1 [R=301,L]
114
115 # If the requested filename exists, simply serve it.
116 # We only want to let Apache serve files and not directories.
117 RewriteCond %{REQUEST_FILENAME} -f
118 RewriteRule ^ - [L]
119
120 # Rewrite all other queries to the front controller.
121 RewriteRule ^ %{ENV:BASE}/app_dev.php [L]
122 </IfModule>
123
124 </Directory>
125 '' else ''
126 Use Stats piedsjaloux.fr
127
128 <Directory ${root}>
129 Options Indexes FollowSymLinks MultiViews Includes
130 AllowOverride All
131 Require all granted
132 </Directory>
133 ''}
134 '';
135 };
136 activationScript = {
137 deps = [ "wrappers" ];
138 text = ''
139 install -m 0755 -o ${apacheUser} -g ${apacheGroup} -d ${app.varDir} \
140 ${app.varDir}/tmp
141 install -m 0750 -o ${apacheUser} -g ${apacheGroup} -d ${app.varDir}/phpSessions
142 '';
143 };
144}
diff --git a/modules/private/websites/piedsjaloux/integration.nix b/modules/private/websites/piedsjaloux/integration.nix
new file mode 100644
index 00000000..5f574e1a
--- /dev/null
+++ b/modules/private/websites/piedsjaloux/integration.nix
@@ -0,0 +1,32 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 piedsjaloux = pkgs.callPackage ./builder.nix {
4 inherit (pkgs.webapps) piedsjaloux;
5 config = myconfig.env.websites.piedsjaloux.integration;
6 apacheUser = config.services.httpd.Inte.user;
7 apacheGroup = config.services.httpd.Inte.group;
8 };
9
10 cfg = config.myServices.websites.piedsjaloux.integration;
11in {
12 options.myServices.websites.piedsjaloux.integration.enable = lib.mkEnableOption "enable PiedsJaloux's website in integration";
13
14 config = lib.mkIf cfg.enable {
15 secrets.keys = piedsjaloux.keys;
16 systemd.services.phpfpm-piedsjaloux_dev.after = lib.mkAfter piedsjaloux.phpFpm.serviceDeps;
17 systemd.services.phpfpm-piedsjaloux_dev.wants = piedsjaloux.phpFpm.serviceDeps;
18 systemd.services.phpfpm-piedsjaloux_dev.preStart = lib.mkAfter piedsjaloux.phpFpm.preStart;
19 services.phpfpm.poolConfigs.piedsjaloux_dev = piedsjaloux.phpFpm.pool;
20 system.activationScripts.piedsjaloux_dev = piedsjaloux.activationScript;
21 myServices.websites.webappDirs."${piedsjaloux.apache.webappName}" = piedsjaloux.app.webRoot;
22 services.websites.integration.modules = piedsjaloux.apache.modules;
23 services.websites.integration.vhostConfs.piedsjaloux = {
24 certName = "eldiron";
25 addToCerts = true;
26 hosts = [ "piedsjaloux.immae.eu" ];
27 root = piedsjaloux.apache.root;
28 extraConfig = [ piedsjaloux.apache.vhostConf ];
29 };
30 };
31}
32
diff --git a/modules/private/websites/piedsjaloux/production.nix b/modules/private/websites/piedsjaloux/production.nix
new file mode 100644
index 00000000..e3bd2ddc
--- /dev/null
+++ b/modules/private/websites/piedsjaloux/production.nix
@@ -0,0 +1,34 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 piedsjaloux = pkgs.callPackage ./builder.nix {
4 inherit (pkgs.webapps) piedsjaloux;
5 config = myconfig.env.websites.piedsjaloux.production;
6 apacheUser = config.services.httpd.Prod.user;
7 apacheGroup = config.services.httpd.Prod.group;
8 };
9
10 cfg = config.myServices.websites.piedsjaloux.production;
11in {
12 options.myServices.websites.piedsjaloux.production.enable = lib.mkEnableOption "enable PiedsJaloux's website in production";
13
14 config = lib.mkIf cfg.enable {
15 secrets.keys = piedsjaloux.keys;
16 services.webstats.sites = [ { name = "piedsjaloux.fr"; } ];
17
18 systemd.services.phpfpm-piedsjaloux_prod.after = lib.mkAfter piedsjaloux.phpFpm.serviceDeps;
19 systemd.services.phpfpm-piedsjaloux_prod.wants = piedsjaloux.phpFpm.serviceDeps;
20 systemd.services.phpfpm-piedsjaloux_prod.preStart = lib.mkAfter piedsjaloux.phpFpm.preStart;
21 services.phpfpm.poolConfigs.piedsjaloux_prod = piedsjaloux.phpFpm.pool;
22 system.activationScripts.piedsjaloux_prod = piedsjaloux.activationScript;
23 myServices.websites.webappDirs."${piedsjaloux.apache.webappName}" = piedsjaloux.app.webRoot;
24 services.websites.production.modules = piedsjaloux.apache.modules;
25 services.websites.production.vhostConfs.piedsjaloux = {
26 certName = "piedsjaloux";
27 certMainHost = "piedsjaloux.fr";
28 hosts = [ "piedsjaloux.fr" "www.piedsjaloux.fr" ];
29 root = piedsjaloux.apache.root;
30 extraConfig = [ piedsjaloux.apache.vhostConf ];
31 };
32 };
33}
34
diff --git a/modules/private/websites/tools/cloud/default.nix b/modules/private/websites/tools/cloud/default.nix
new file mode 100644
index 00000000..ceb8f772
--- /dev/null
+++ b/modules/private/websites/tools/cloud/default.nix
@@ -0,0 +1,188 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 nextcloud = pkgs.webapps.nextcloud.withApps (builtins.attrValues pkgs.webapps.nextcloud-apps);
4 env = myconfig.env.tools.nextcloud;
5 varDir = "/var/lib/nextcloud";
6 webappName = "tools_nextcloud";
7 apacheRoot = "/run/current-system/webapps/${webappName}";
8 cfg = config.myServices.websites.tools.cloud;
9 phpFpm = rec {
10 basedir = builtins.concatStringsSep ":" (
11 [ nextcloud varDir ]
12 ++ builtins.attrValues pkgs.webapps.nextcloud-apps);
13 socket = "/var/run/phpfpm/nextcloud.sock";
14 phpConfig = ''
15 extension=${pkgs.phpPackages.redis}/lib/php/extensions/redis.so
16 extension=${pkgs.phpPackages.apcu}/lib/php/extensions/apcu.so
17 zend_extension=${pkgs.php}/lib/php/extensions/opcache.so
18 '';
19 pool = ''
20 user = wwwrun
21 group = wwwrun
22 listen.owner = wwwrun
23 listen.group = wwwrun
24 pm = ondemand
25 pm.max_children = 60
26 pm.process_idle_timeout = 60
27
28 php_admin_value[output_buffering] = 0
29 php_admin_value[max_execution_time] = 1800
30 php_admin_value[zend_extension] = "opcache"
31 ;already enabled by default?
32 ;php_value[opcache.enable] = 1
33 php_value[opcache.enable_cli] = 1
34 php_value[opcache.interned_strings_buffer] = 8
35 php_value[opcache.max_accelerated_files] = 10000
36 php_value[opcache.memory_consumption] = 128
37 php_value[opcache.save_comments] = 1
38 php_value[opcache.revalidate_freq] = 1
39 php_admin_value[memory_limit] = 512M
40
41 php_admin_value[open_basedir] = "/run/wrappers/bin/sendmail:${basedir}:/proc/meminfo:/dev/urandom:/proc/self/fd:/tmp"
42 php_admin_value[session.save_path] = "${varDir}/phpSessions"
43 '';
44 };
45in {
46 options.myServices.websites.tools.cloud = {
47 enable = lib.mkEnableOption "enable cloud website";
48 };
49
50 config = lib.mkIf cfg.enable {
51 services.websites.tools.modules = [ "proxy_fcgi" ];
52
53 services.websites.tools.vhostConfs.cloud = {
54 certName = "eldiron";
55 addToCerts = true;
56 hosts = ["cloud.immae.eu" ];
57 root = apacheRoot;
58 extraConfig = [
59 ''
60 SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
61 <Directory ${apacheRoot}>
62 AcceptPathInfo On
63 DirectoryIndex index.php
64 Options FollowSymlinks
65 Require all granted
66 AllowOverride all
67
68 <IfModule mod_headers.c>
69 Header always set Strict-Transport-Security "max-age=15552000; includeSubDomains; preload"
70 </IfModule>
71 <FilesMatch "\.php$">
72 CGIPassAuth on
73 SetHandler "proxy:unix:${phpFpm.socket}|fcgi://localhost"
74 </FilesMatch>
75
76 </Directory>
77 ''
78 ];
79 };
80
81 secrets.keys = [{
82 dest = "webapps/tools-nextcloud";
83 user = "wwwrun";
84 group = "wwwrun";
85 permissions = "0600";
86 text = ''
87 <?php
88 $CONFIG = array (
89 // FIXME: change this value when nextcloud starts getting slow
90 'instanceid' => '${env.instance_id}1',
91 'datadirectory' => '/var/lib/nextcloud/',
92 'passwordsalt' => '${env.password_salt}',
93 'debug' => false,
94 'dbtype' => 'pgsql',
95 'version' => '16.0.0.9',
96 'dbname' => '${env.postgresql.database}',
97 'dbhost' => '${env.postgresql.socket}',
98 'dbtableprefix' => 'oc_',
99 'dbuser' => '${env.postgresql.user}',
100 'dbpassword' => '${env.postgresql.password}',
101 'installed' => true,
102 'maxZipInputSize' => 0,
103 'allowZipDownload' => true,
104 'forcessl' => true,
105 'theme' => ${"''"},
106 'maintenance' => false,
107 'trusted_domains' =>
108 array (
109 0 => 'cloud.immae.eu',
110 ),
111 'secret' => '${env.secret}',
112 'appstoreenabled' => false,
113 'appstore.experimental.enabled' => true,
114 'loglevel' => 2,
115 'trashbin_retention_obligation' => 'auto',
116 'htaccess.RewriteBase' => '/',
117 'mail_smtpmode' => 'sendmail',
118 'mail_smtphost' => '127.0.0.1',
119 'mail_smtpname' => ''',
120 'mail_smtppassword' => ''',
121 'mail_from_address' => 'nextcloud',
122 'mail_smtpauth' => false,
123 'mail_domain' => 'tools.immae.eu',
124 'memcache.local' => '\\OC\\Memcache\\APCu',
125 'memcache.locking' => '\\OC\\Memcache\\Redis',
126 'filelocking.enabled' => true,
127 'redis' =>
128 array (
129 'host' => '${env.redis.socket}',
130 'port' => 0,
131 'dbindex' => ${env.redis.db_index},
132 ),
133 'overwrite.cli.url' => 'https://cloud.immae.eu',
134 'ldapIgnoreNamingRules' => false,
135 'ldapProviderFactory' => '\\OCA\\User_LDAP\\LDAPProviderFactory',
136 'has_rebuilt_cache' => true,
137 );
138 '';
139 }];
140 users.users.root.packages = let
141 occ = pkgs.writeScriptBin "nextcloud-occ" ''
142 #! ${pkgs.stdenv.shell}
143 cd ${nextcloud}
144 NEXTCLOUD_CONFIG_DIR="${nextcloud}/config" \
145 exec \
146 sudo -u wwwrun ${pkgs.php}/bin/php \
147 -c ${pkgs.php}/etc/php.ini \
148 occ $*
149 '';
150 in [ occ ];
151
152 system.activationScripts.nextcloud = {
153 deps = [ "secrets" ];
154 text = let
155 confs = lib.attrsets.mapAttrs (n: v: pkgs.writeText "${n}.json" (builtins.toJSON v)) nextcloud.otherConfig;
156 in
157 ''
158 install -m 0755 -o wwwrun -g wwwrun -d ${varDir}
159 install -m 0750 -o wwwrun -g wwwrun -d ${varDir}/phpSessions
160 ${builtins.concatStringsSep "\n" (lib.attrsets.mapAttrsToList (n: v:
161 "install -D -m 0644 -o wwwrun -g wwwrun -T ${v} ${varDir}/config/${n}.json"
162 ) confs)}
163 install -D -m 0600 -o wwwrun -g wwwrun -T /var/secrets/webapps/tools-nextcloud ${varDir}/config/config.php
164 '';
165 };
166 # FIXME: add a warning when config.php changes
167 system.extraSystemBuilderCmds = ''
168 mkdir -p $out/webapps
169 ln -s ${nextcloud} $out/webapps/${webappName}
170 '';
171
172 services.phpfpm.pools.nextcloud = {
173 listen = phpFpm.socket;
174 extraConfig = phpFpm.pool;
175 phpOptions = config.services.phpfpm.phpOptions + phpFpm.phpConfig;
176 };
177
178 services.cron = {
179 enable = true;
180 systemCronJobs = [
181 ''
182 LOCALE_ARCHIVE=/run/current-system/sw/lib/locale/locale-archive
183 */15 * * * * wwwrun ${pkgs.php}/bin/php -f ${nextcloud}/cron.php
184 ''
185 ];
186 };
187 };
188}
diff --git a/modules/private/websites/tools/dav/davical.nix b/modules/private/websites/tools/dav/davical.nix
new file mode 100644
index 00000000..98cebee9
--- /dev/null
+++ b/modules/private/websites/tools/dav/davical.nix
@@ -0,0 +1,139 @@
1{ stdenv, fetchurl, gettext, writeText, env, awl, davical }:
2rec {
3 activationScript = {
4 deps = [ "httpd" ];
5 text = ''
6 install -m 0755 -o ${apache.user} -g ${apache.group} -d /var/lib/php/sessions/davical
7 '';
8 };
9 keys = [{
10 dest = "webapps/dav-davical";
11 user = apache.user;
12 group = apache.group;
13 permissions = "0400";
14 text = ''
15 <?php
16 $c->pg_connect[] = "dbname=${env.postgresql.database} user=${env.postgresql.user} host=${env.postgresql.socket} password=${env.postgresql.password}";
17
18 $c->readonly_webdav_collections = false;
19
20 $c->admin_email ='davical@tools.immae.eu';
21
22 $c->restrict_setup_to_admin = true;
23
24 $c->collections_always_exist = false;
25
26 $c->external_refresh = 60;
27
28 $c->enable_scheduling = true;
29
30 $c->iMIP = (object) array("send_email" => true);
31
32 $c->authenticate_hook['optional'] = false;
33 $c->authenticate_hook['call'] = 'LDAP_check';
34 $c->authenticate_hook['config'] = array(
35 'host' => 'ldap.immae.eu',
36 'port' => '389',
37 'startTLS' => 'yes',
38 'bindDN'=> 'cn=davical,ou=services,dc=immae,dc=eu',
39 'passDN'=> '${env.ldap.password}',
40 'protocolVersion' => '3',
41 'baseDNUsers'=> array('ou=users,dc=immae,dc=eu', 'ou=group_users,dc=immae,dc=eu'),
42 'filterUsers' => 'memberOf=cn=users,cn=davical,ou=services,dc=immae,dc=eu',
43 'baseDNGroups' => 'ou=groups,dc=immae,dc=eu',
44 'filterGroups' => 'memberOf=cn=groups,cn=davical,ou=services,dc=immae,dc=eu',
45 'mapping_field' => array(
46 "username" => "uid",
47 "fullname" => "cn",
48 "email" => "mail",
49 "modified" => "modifyTimestamp",
50 ),
51 'format_updated'=> array('Y' => array(0,4),'m' => array(4,2),'d'=> array(6,2),'H' => array(8,2),'M'=>array(10,2),'S' => array(12,2)),
52 /** used to set default value for all users, will be overcharged by ldap if defined also in mapping_field **/
53 // 'default_value' => array("date_format_type" => "E","locale" => "fr_FR"),
54 'group_mapping_field' => array(
55 "username" => "cn",
56 "updated" => "modifyTimestamp",
57 "fullname" => "givenName",
58 "displayname" => "givenName",
59 "members" => "memberUid",
60 "email" => "mail",
61 ),
62 );
63
64 $c->do_not_sync_from_ldap = array('admin' => true);
65 include('drivers_ldap.php');
66 '';
67 }];
68 webapp = davical.override { davical_config = "/var/secrets/webapps/dav-davical"; };
69 webRoot = "${webapp}/htdocs";
70 apache = rec {
71 user = "wwwrun";
72 group = "wwwrun";
73 modules = [ "proxy_fcgi" ];
74 webappName = "tools_davical";
75 root = "/run/current-system/webapps/${webappName}";
76 vhostConf = ''
77 Alias /davical "${root}"
78 Alias /caldav.php "${root}/caldav.php"
79 <Directory "${root}">
80 DirectoryIndex index.php index.html
81 AcceptPathInfo On
82 AllowOverride None
83 Require all granted
84
85 <FilesMatch "\.php$">
86 CGIPassAuth on
87 SetHandler "proxy:unix:${phpFpm.socket}|fcgi://localhost"
88 </FilesMatch>
89
90 RewriteEngine On
91 <IfModule mod_headers.c>
92 Header unset Access-Control-Allow-Origin
93 Header unset Access-Control-Allow-Methods
94 Header unset Access-Control-Allow-Headers
95 Header unset Access-Control-Allow-Credentials
96 Header unset Access-Control-Expose-Headers
97
98 Header always set Access-Control-Allow-Origin "*"
99 Header always set Access-Control-Allow-Methods "GET,POST,OPTIONS,PROPFIND,PROPPATCH,REPORT,PUT,MOVE,DELETE,LOCK,UNLOCK"
100 Header always set Access-Control-Allow-Headers "User-Agent,Authorization,Content-type,Depth,If-match,If-None-Match,Lock-Token,Timeout,Destination,Overwrite,Prefer,X-client,X-Requested-With"
101 Header always set Access-Control-Allow-Credentials false
102 Header always set Access-Control-Expose-Headers "Etag,Preference-Applied"
103
104 RewriteCond %{HTTP:Access-Control-Request-Method} !^$
105 RewriteCond %{REQUEST_METHOD} OPTIONS
106 RewriteRule ^(.*)$ $1 [R=200,L]
107 </IfModule>
108 </Directory>
109 '';
110 };
111 phpFpm = rec {
112 serviceDeps = [ "postgresql.service" "openldap.service" ];
113 basedir = builtins.concatStringsSep ":" [ webapp "/var/secrets/webapps/dav-davical" awl ];
114 socket = "/var/run/phpfpm/davical.sock";
115 pool = ''
116 listen = ${socket}
117 user = ${apache.user}
118 group = ${apache.group}
119 listen.owner = ${apache.user}
120 listen.group = ${apache.group}
121 pm = dynamic
122 pm.max_children = 60
123 pm.start_servers = 2
124 pm.min_spare_servers = 1
125 pm.max_spare_servers = 10
126
127 ; Needed to avoid clashes in browser cookies (same domain)
128 php_value[session.name] = DavicalPHPSESSID
129 php_admin_value[open_basedir] = "${basedir}:/tmp:/var/lib/php/sessions/davical"
130 php_admin_value[include_path] = "${awl}/inc:${webapp}/inc"
131 php_admin_value[session.save_path] = "/var/lib/php/sessions/davical"
132 php_flag[magic_quotes_gpc] = Off
133 php_flag[register_globals] = Off
134 php_admin_value[error_reporting] = "E_ALL & ~E_NOTICE"
135 php_admin_value[default_charset] = "utf-8"
136 php_flag[magic_quotes_runtime] = Off
137 '';
138 };
139}
diff --git a/modules/private/websites/tools/dav/default.nix b/modules/private/websites/tools/dav/default.nix
new file mode 100644
index 00000000..fb0baaec
--- /dev/null
+++ b/modules/private/websites/tools/dav/default.nix
@@ -0,0 +1,53 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 infcloud = rec {
4 webappName = "tools_infcloud";
5 root = "/run/current-system/webapps/${webappName}";
6 vhostConf = ''
7 Alias /carddavmate ${root}
8 Alias /caldavzap ${root}
9 Alias /infcloud ${root}
10 <Directory ${root}>
11 AllowOverride All
12 Options FollowSymlinks
13 Require all granted
14 DirectoryIndex index.html
15 </Directory>
16 '';
17 };
18 davical = pkgs.callPackage ./davical.nix {
19 env = myconfig.env.tools.davical;
20 inherit (pkgs.webapps) davical awl;
21 };
22
23 cfg = config.myServices.websites.tools.dav;
24in {
25 options.myServices.websites.tools.dav = {
26 enable = lib.mkEnableOption "enable dav website";
27 };
28
29 config = lib.mkIf cfg.enable {
30 system.activationScripts.davical = davical.activationScript;
31 secrets.keys = davical.keys;
32 services.websites.tools.modules = davical.apache.modules;
33
34 services.websites.tools.vhostConfs.dav = {
35 certName = "eldiron";
36 addToCerts = true;
37 hosts = ["dav.immae.eu" ];
38 root = null;
39 extraConfig = [
40 infcloud.vhostConf
41 davical.apache.vhostConf
42 ];
43 };
44
45 services.phpfpm.poolConfigs = {
46 davical = davical.phpFpm.pool;
47 };
48
49 myServices.websites.webappDirs."${davical.apache.webappName}" = davical.webRoot;
50 myServices.websites.webappDirs."${infcloud.webappName}" = pkgs.webapps.infcloud;
51 };
52}
53
diff --git a/modules/private/websites/tools/db/default.nix b/modules/private/websites/tools/db/default.nix
new file mode 100644
index 00000000..361e204d
--- /dev/null
+++ b/modules/private/websites/tools/db/default.nix
@@ -0,0 +1,21 @@
1{ lib, pkgs, config, ... }:
2let
3 adminer = pkgs.callPackage ../../commons/adminer.nix {};
4
5 cfg = config.myServices.websites.tools.db;
6in {
7 options.myServices.websites.tools.db = {
8 enable = lib.mkEnableOption "enable database's website";
9 };
10
11 config = lib.mkIf cfg.enable {
12 services.websites.tools.modules = adminer.apache.modules;
13 services.websites.tools.vhostConfs.db-1 = {
14 certName = "eldiron";
15 addToCerts = true;
16 hosts = ["db-1.immae.eu" ];
17 root = null;
18 extraConfig = [ adminer.apache.vhostConf ];
19 };
20 };
21}
diff --git a/modules/private/websites/tools/diaspora/default.nix b/modules/private/websites/tools/diaspora/default.nix
new file mode 100644
index 00000000..efa1fabb
--- /dev/null
+++ b/modules/private/websites/tools/diaspora/default.nix
@@ -0,0 +1,181 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 env = myconfig.env.tools.diaspora;
4 root = "/run/current-system/webapps/tools_diaspora";
5 cfg = config.myServices.websites.tools.diaspora;
6 dcfg = config.services.diaspora;
7in {
8 options.myServices.websites.tools.diaspora = {
9 enable = lib.mkEnableOption "enable diaspora's website";
10 };
11
12 config = lib.mkIf cfg.enable {
13 users.users.diaspora.extraGroups = [ "keys" ];
14
15 secrets.keys = [
16 {
17 dest = "webapps/diaspora/diaspora.yml";
18 user = "diaspora";
19 group = "diaspora";
20 permissions = "0400";
21 text = ''
22 configuration:
23 environment:
24 url: "https://diaspora.immae.eu/"
25 certificate_authorities: '${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt'
26 redis: '${env.redis_url}'
27 sidekiq:
28 s3:
29 assets:
30 logging:
31 logrotate:
32 debug:
33 server:
34 listen: '${dcfg.sockets.rails}'
35 rails_environment: 'production'
36 chat:
37 server:
38 bosh:
39 log:
40 map:
41 mapbox:
42 privacy:
43 piwik:
44 statistics:
45 camo:
46 settings:
47 enable_registrations: false
48 welcome_message:
49 invitations:
50 open: false
51 paypal_donations:
52 community_spotlight:
53 captcha:
54 enable: false
55 terms:
56 maintenance:
57 remove_old_users:
58 default_metas:
59 csp:
60 services:
61 twitter:
62 tumblr:
63 wordpress:
64 mail:
65 enable: true
66 sender_address: 'diaspora@tools.immae.eu'
67 method: 'sendmail'
68 smtp:
69 sendmail:
70 location: '/run/wrappers/bin/sendmail'
71 admins:
72 account: "ismael"
73 podmin_email: 'diaspora@tools.immae.eu'
74 relay:
75 outbound:
76 inbound:
77 ldap:
78 enable: true
79 host: ldap.immae.eu
80 port: 636
81 only_ldap: true
82 mail_attribute: mail
83 skip_email_confirmation: true
84 use_bind_dn: true
85 bind_dn: "cn=diaspora,ou=services,dc=immae,dc=eu"
86 bind_pw: "${env.ldap.password}"
87 search_base: "dc=immae,dc=eu"
88 search_filter: "(&(memberOf=cn=users,cn=diaspora,ou=services,dc=immae,dc=eu)(uid=%{username}))"
89 production:
90 environment:
91 development:
92 environment:
93 '';
94 }
95 {
96 dest = "webapps/diaspora/database.yml";
97 user = "diaspora";
98 group = "diaspora";
99 permissions = "0400";
100 text = ''
101 postgresql: &postgresql
102 adapter: postgresql
103 host: "${env.postgresql.socket}"
104 port: "${env.postgresql.port}"
105 username: "${env.postgresql.user}"
106 password: "${env.postgresql.password}"
107 encoding: unicode
108 common: &common
109 <<: *postgresql
110 combined: &combined
111 <<: *common
112 development:
113 <<: *combined
114 database: diaspora_development
115 production:
116 <<: *combined
117 database: ${env.postgresql.database}
118 test:
119 <<: *combined
120 database: "diaspora_test"
121 integration1:
122 <<: *combined
123 database: diaspora_integration1
124 integration2:
125 <<: *combined
126 database: diaspora_integration2
127 '';
128 }
129 {
130 dest = "webapps/diaspora/secret_token.rb";
131 user = "diaspora";
132 group = "diaspora";
133 permissions = "0400";
134 text = ''
135 Diaspora::Application.config.secret_key_base = '${env.secret_token}'
136 '';
137 }
138 ];
139
140 services.diaspora = {
141 enable = true;
142 package = pkgs.webapps.diaspora.override { ldap = true; };
143 dataDir = "/var/lib/diaspora_immae";
144 adminEmail = "diaspora@tools.immae.eu";
145 configDir = "/var/secrets/webapps/diaspora";
146 };
147
148 services.websites.tools.modules = [
149 "headers" "proxy" "proxy_http"
150 ];
151 system.extraSystemBuilderCmds = ''
152 mkdir -p $out/webapps
153 ln -s ${dcfg.workdir}/public/ $out/webapps/tools_diaspora
154 '';
155 services.websites.tools.vhostConfs.diaspora = {
156 certName = "eldiron";
157 addToCerts = true;
158 hosts = [ "diaspora.immae.eu" ];
159 root = root;
160 extraConfig = [ ''
161 RewriteEngine On
162 RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
163 RewriteRule ^/(.*)$ unix://${dcfg.sockets.rails}|http://diaspora.immae.eu/%{REQUEST_URI} [P,NE,QSA,L]
164
165 ProxyRequests Off
166 ProxyVia On
167 ProxyPreserveHost On
168 RequestHeader set X_FORWARDED_PROTO https
169
170 <Proxy *>
171 Require all granted
172 </Proxy>
173
174 <Directory ${root}>
175 Require all granted
176 Options -MultiViews
177 </Directory>
178 '' ];
179 };
180 };
181}
diff --git a/modules/private/websites/tools/ether/default.nix b/modules/private/websites/tools/ether/default.nix
new file mode 100644
index 00000000..ebcbf618
--- /dev/null
+++ b/modules/private/websites/tools/ether/default.nix
@@ -0,0 +1,175 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 env = myconfig.env.tools.etherpad-lite;
4 cfg = config.myServices.websites.tools.etherpad-lite;
5 # Make sure we’re not rebuilding whole libreoffice just because of a
6 # dependency
7 libreoffice = (import <nixpkgs> { overlays = []; }).libreoffice-fresh;
8 ecfg = config.services.etherpad-lite;
9in {
10 options.myServices.websites.tools.etherpad-lite = {
11 enable = lib.mkEnableOption "enable etherpad's website";
12 };
13
14 config = lib.mkIf cfg.enable {
15 secrets.keys = [
16 {
17 dest = "webapps/tools-etherpad-apikey";
18 permissions = "0400";
19 text = env.api_key;
20 }
21 {
22 dest = "webapps/tools-etherpad-sessionkey";
23 permissions = "0400";
24 text = env.session_key;
25 }
26 {
27 dest = "webapps/tools-etherpad";
28 permissions = "0400";
29 text = ''
30 {
31 "title": "Etherpad",
32 "favicon": "favicon.ico",
33
34 "ip": "",
35 "port" : "${ecfg.sockets.node}",
36 "showSettingsInAdminPage" : false,
37 "dbType" : "postgres",
38 "dbSettings" : {
39 "user" : "${env.postgresql.user}",
40 "host" : "${env.postgresql.socket}",
41 "password": "${env.postgresql.password}",
42 "database": "${env.postgresql.database}",
43 "charset" : "utf8mb4"
44 },
45
46 "defaultPadText" : "Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at http:\/\/etherpad.org\n",
47 "padOptions": {
48 "noColors": false,
49 "showControls": true,
50 "showChat": true,
51 "showLineNumbers": true,
52 "useMonospaceFont": false,
53 "userName": false,
54 "userColor": false,
55 "rtl": false,
56 "alwaysShowChat": false,
57 "chatAndUsers": false,
58 "lang": "en-gb"
59 },
60
61 "suppressErrorsInPadText" : false,
62 "requireSession" : false,
63 "editOnly" : false,
64 "sessionNoPassword" : false,
65 "minify" : true,
66 "maxAge" : 21600,
67 "abiword" : null,
68 "soffice" : "${libreoffice}/bin/soffice",
69 "tidyHtml" : "${pkgs.html-tidy}/bin/tidy",
70 "allowUnknownFileEnds" : true,
71 "requireAuthentication" : false,
72 "requireAuthorization" : false,
73 "trustProxy" : false,
74 "disableIPlogging" : false,
75 "automaticReconnectionTimeout" : 0,
76 "scrollWhenFocusLineIsOutOfViewport": {
77 "percentage": {
78 "editionAboveViewport": 0,
79 "editionBelowViewport": 0
80 },
81 "duration": 0,
82 "scrollWhenCaretIsInTheLastLineOfViewport": false,
83 "percentageToScrollWhenUserPressesArrowUp": 0
84 },
85 "users": {
86 "ldapauth": {
87 "url": "ldaps://${env.ldap.host}",
88 "accountBase": "${env.ldap.base}",
89 "accountPattern": "(&(memberOf=cn=users,cn=etherpad,ou=services,dc=immae,dc=eu)(uid={{username}}))",
90 "displayNameAttribute": "cn",
91 "searchDN": "cn=etherpad,ou=services,dc=immae,dc=eu",
92 "searchPWD": "${env.ldap.password}",
93 "groupSearchBase": "${env.ldap.base}",
94 "groupAttribute": "member",
95 "groupAttributeIsDN": true,
96 "searchScope": "sub",
97 "groupSearch": "(memberOf=cn=groups,cn=etherpad,ou=services,dc=immae,dc=eu)",
98 "anonymousReadonly": false
99 }
100 },
101 "socketTransportProtocols" : ["xhr-polling", "jsonp-polling", "htmlfile"],
102 "loadTest": false,
103 "indentationOnNewLine": false,
104 "toolbar": {
105 "left": [
106 ["bold", "italic", "underline", "strikethrough"],
107 ["orderedlist", "unorderedlist", "indent", "outdent"],
108 ["undo", "redo"],
109 ["clearauthorship"]
110 ],
111 "right": [
112 ["importexport", "timeslider", "savedrevision"],
113 ["settings", "embed"],
114 ["showusers"]
115 ],
116 "timeslider": [
117 ["timeslider_export", "timeslider_returnToPad"]
118 ]
119 },
120 "loglevel": "INFO",
121 "logconfig" : { "appenders": [ { "type": "console" } ] }
122 }
123 '';
124 }
125 ];
126 services.etherpad-lite = {
127 enable = true;
128 modules = builtins.attrValues pkgs.webapps.etherpad-lite-modules;
129 sessionKeyFile = "/var/secrets/webapps/tools-etherpad-sessionkey";
130 apiKeyFile = "/var/secrets/webapps/tools-etherpad-apikey";
131 configFile = "/var/secrets/webapps/tools-etherpad";
132 };
133
134 systemd.services.etherpad-lite.serviceConfig.SupplementaryGroups = "keys";
135
136 services.websites.tools.modules = [
137 "headers" "proxy" "proxy_http" "proxy_wstunnel"
138 ];
139 services.websites.tools.vhostConfs.etherpad-lite = {
140 certName = "eldiron";
141 addToCerts = true;
142 hosts = [ "ether.immae.eu" ];
143 root = null;
144 extraConfig = [ ''
145 Header always set Strict-Transport-Security "max-age=31536000; includeSubdomains;"
146 RequestHeader set X-Forwarded-Proto "https"
147
148 RewriteEngine On
149
150 RewriteMap redirects "txt:${pkgs.writeText "redirects.txt" myconfig.env.tools.etherpad-lite.redirects}"
151 RewriteCond %{QUERY_STRING} "!noredirect"
152 RewriteCond %{REQUEST_URI} "^(.*)$"
153 RewriteCond ''${redirects:$1|Unknown} "!Unknown"
154 RewriteRule "^(.*)$" ''${redirects:$1} [L,NE,R=301,QSD]
155
156 RewriteCond %{REQUEST_URI} ^/socket.io [NC]
157 RewriteCond %{QUERY_STRING} transport=websocket [NC]
158 RewriteRule /(.*) unix://${ecfg.sockets.node}|ws://ether.immae.eu/$1 [P,NE,QSA,L]
159
160 <IfModule mod_proxy.c>
161 ProxyVia On
162 ProxyRequests Off
163 ProxyPreserveHost On
164 ProxyPass / unix://${ecfg.sockets.node}|http://ether.immae.eu/
165 ProxyPassReverse / unix://${ecfg.sockets.node}|http://ether.immae.eu/
166 <Proxy *>
167 Options FollowSymLinks MultiViews
168 AllowOverride None
169 Require all granted
170 </Proxy>
171 </IfModule>
172 '' ];
173 };
174 };
175}
diff --git a/modules/private/websites/tools/git/default.nix b/modules/private/websites/tools/git/default.nix
new file mode 100644
index 00000000..75d02402
--- /dev/null
+++ b/modules/private/websites/tools/git/default.nix
@@ -0,0 +1,45 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 mantisbt = pkgs.callPackage ./mantisbt.nix {
4 inherit (pkgs.webapps) mantisbt_2 mantisbt_2-plugins;
5 env = myconfig.env.tools.mantisbt;
6 };
7 gitweb = pkgs.callPackage ./gitweb.nix {
8 gitoliteDir = config.myServices.gitolite.gitoliteDir;
9 };
10
11 cfg = config.myServices.websites.tools.git;
12in {
13 options.myServices.websites.tools.git = {
14 enable = lib.mkEnableOption "enable git's website";
15 };
16
17 config = lib.mkIf cfg.enable {
18 secrets.keys = mantisbt.keys;
19 services.websites.tools.modules =
20 gitweb.apache.modules ++
21 mantisbt.apache.modules;
22 myServices.websites.webappDirs."${gitweb.apache.webappName}" = gitweb.webRoot;
23 myServices.websites.webappDirs."${mantisbt.apache.webappName}" = mantisbt.webRoot;
24
25 system.activationScripts.mantisbt = mantisbt.activationScript;
26 services.websites.tools.vhostConfs.git = {
27 certName = "eldiron";
28 addToCerts = true;
29 hosts = ["git.immae.eu" ];
30 root = gitweb.apache.root;
31 extraConfig = [
32 gitweb.apache.vhostConf
33 mantisbt.apache.vhostConf
34 ''
35 RewriteEngine on
36 RewriteCond %{REQUEST_URI} ^/releases
37 RewriteRule /releases(.*) https://release.immae.eu$1 [P,L]
38 ''
39 ];
40 };
41 services.phpfpm.poolConfigs = {
42 mantisbt = mantisbt.phpFpm.pool;
43 };
44 };
45}
diff --git a/modules/private/websites/tools/git/gitweb.nix b/modules/private/websites/tools/git/gitweb.nix
new file mode 100644
index 00000000..2ee7a63a
--- /dev/null
+++ b/modules/private/websites/tools/git/gitweb.nix
@@ -0,0 +1,64 @@
1{ gitweb, writeText, gitolite, git, gitoliteDir, highlight }:
2rec {
3 varDir = gitoliteDir;
4 webRoot = gitweb;
5 config = writeText "gitweb.conf" ''
6 $git_temp = "/tmp";
7
8 # The directories where your projects are. Must not end with a
9 # slash.
10 $projectroot = "${varDir}/repositories";
11
12 $projects_list = "${varDir}/projects.list";
13 $strict_export = "true";
14
15 # Base URLs for links displayed in the web interface.
16 our @git_base_url_list = qw(ssh://gitolite@git.immae.eu https://git.immae.eu);
17
18 $feature{'blame'}{'default'} = [1];
19 $feature{'avatar'}{'default'} = ['gravatar'];
20 $feature{'highlight'}{'default'} = [1];
21
22 @stylesheets = ("gitweb-theme/gitweb.css");
23 $logo = "gitweb-theme/git-logo.png";
24 $favicon = "gitweb-theme/git-favicon.png";
25 $javascript = "gitweb-theme/gitweb.js";
26 $logo_url = "https://git.immae.eu/";
27 $projects_list_group_categories = "true";
28 $projects_list_description_width = 60;
29 $project_list_default_category = "__Others__";
30 $highlight_bin = "${highlight}/bin/highlight";
31 '';
32 apache = rec {
33 user = "wwwrun";
34 group = "wwwrun";
35 modules = [ "cgid" ];
36 webappName = "tools_gitweb";
37 root = "/run/current-system/webapps/${webappName}";
38 vhostConf = ''
39 SetEnv GIT_PROJECT_ROOT ${varDir}/repositories/
40 ScriptAliasMatch \
41 "(?x)^/(.*/(HEAD | \
42 info/refs | \
43 objects/(info/[^/]+ | \
44 [0-9a-f]{2}/[0-9a-f]{38} | \
45 pack/pack-[0-9a-f]{40}\.(pack|idx)) | \
46 git-(upload|receive)-pack))$" \
47 ${git}/libexec/git-core/git-http-backend/$1
48
49 <Directory "${git}/libexec/git-core">
50 Require all granted
51 </Directory>
52 <Directory "${root}">
53 DirectoryIndex gitweb.cgi
54 Require all granted
55 AllowOverride None
56 Options ExecCGI FollowSymLinks
57 <Files gitweb.cgi>
58 SetHandler cgi-script
59 SetEnv GITWEB_CONFIG "${config}"
60 </Files>
61 </Directory>
62 '';
63 };
64}
diff --git a/modules/private/websites/tools/git/mantisbt.nix b/modules/private/websites/tools/git/mantisbt.nix
new file mode 100644
index 00000000..a1b830eb
--- /dev/null
+++ b/modules/private/websites/tools/git/mantisbt.nix
@@ -0,0 +1,96 @@
1{ env, mantisbt_2, mantisbt_2-plugins }:
2rec {
3 activationScript = {
4 deps = [ "httpd" ];
5 text = ''
6 install -m 0755 -o ${apache.user} -g ${apache.group} -d /var/lib/php/sessions/mantisbt
7 '';
8 };
9 keys = [{
10 dest = "webapps/tools-mantisbt";
11 user = apache.user;
12 group = apache.group;
13 permissions = "0400";
14 text = ''
15 <?php
16 $g_hostname = '${env.postgresql.socket}';
17 $g_db_username = '${env.postgresql.user}';
18 $g_db_password = '${env.postgresql.password}';
19 $g_database_name = '${env.postgresql.database}';
20 $g_db_type = 'pgsql';
21 $g_crypto_master_salt = '${env.master_salt}';
22 $g_allow_signup = OFF;
23 $g_allow_anonymous_login = ON;
24 $g_anonymous_account = 'anonymous';
25
26 $g_phpMailer_method = PHPMAILER_METHOD_SENDMAIL;
27 $g_smtp_host = 'localhost';
28 $g_smtp_username = ''';
29 $g_smtp_password = ''';
30 $g_webmaster_email = 'mantisbt@tools.immae.eu';
31 $g_from_email = 'mantisbt@tools.immae.eu';
32 $g_return_path_email = 'mantisbt@tools.immae.eu';
33 $g_from_name = 'Mantis Bug Tracker at git.immae.eu';
34 $g_email_receive_own = OFF;
35 # --- LDAP ---
36 $g_login_method = LDAP;
37 $g_ldap_protocol_version = 3;
38 $g_ldap_server = 'ldaps://ldap.immae.eu:636';
39 $g_ldap_root_dn = 'ou=users,dc=immae,dc=eu';
40 $g_ldap_bind_dn = 'cn=mantisbt,ou=services,dc=immae,dc=eu';
41 $g_ldap_bind_passwd = '${env.ldap.password}';
42 $g_use_ldap_email = ON;
43 $g_use_ldap_realname = ON;
44 $g_ldap_uid_field = 'uid';
45 $g_ldap_realname_field = 'cn';
46 $g_ldap_organization = '(memberOf=cn=users,cn=mantisbt,ou=services,dc=immae,dc=eu)';
47 '';
48 }];
49 webRoot = (mantisbt_2.override { mantis_config = "/var/secrets/webapps/tools-mantisbt"; }).withPlugins (builtins.attrValues mantisbt_2-plugins);
50 apache = rec {
51 user = "wwwrun";
52 group = "wwwrun";
53 modules = [ "proxy_fcgi" ];
54 webappName = "tools_mantisbt";
55 root = "/run/current-system/webapps/${webappName}";
56 vhostConf = ''
57 Alias /mantisbt "${root}"
58 <Directory "${root}">
59 DirectoryIndex index.php
60 <FilesMatch "\.php$">
61 SetHandler "proxy:unix:${phpFpm.socket}|fcgi://localhost"
62 </FilesMatch>
63
64 AllowOverride All
65 Options FollowSymlinks
66 Require all granted
67 </Directory>
68 <Directory "${root}/admin">
69 #Reenable during upgrade
70 Require all denied
71 </Directory>
72 '';
73 };
74 phpFpm = rec {
75 serviceDeps = [ "postgresql.service" "openldap.service" ];
76 basedir = builtins.concatStringsSep ":" (
77 [ webRoot "/var/secrets/webapps/tools-mantisbt" ]
78 ++ webRoot.plugins);
79 socket = "/var/run/phpfpm/mantisbt.sock";
80 pool = ''
81 listen = ${socket}
82 user = ${apache.user}
83 group = ${apache.group}
84 listen.owner = ${apache.user}
85 listen.group = ${apache.group}
86 pm = ondemand
87 pm.max_children = 60
88 pm.process_idle_timeout = 60
89
90 php_admin_value[upload_max_filesize] = 5000000
91
92 php_admin_value[open_basedir] = "${basedir}:/tmp:/var/lib/php/sessions/mantisbt"
93 php_admin_value[session.save_path] = "/var/lib/php/sessions/mantisbt"
94 '';
95 };
96}
diff --git a/modules/private/websites/tools/mastodon/default.nix b/modules/private/websites/tools/mastodon/default.nix
new file mode 100644
index 00000000..d742a33a
--- /dev/null
+++ b/modules/private/websites/tools/mastodon/default.nix
@@ -0,0 +1,128 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 env = myconfig.env.tools.mastodon;
4 root = "/run/current-system/webapps/tools_mastodon";
5 cfg = config.myServices.websites.tools.mastodon;
6 mcfg = config.services.mastodon;
7in {
8 options.myServices.websites.tools.mastodon = {
9 enable = lib.mkEnableOption "enable mastodon's website";
10 };
11
12 config = lib.mkIf cfg.enable {
13 secrets.keys = [{
14 dest = "webapps/tools-mastodon";
15 user = "mastodon";
16 group = "mastodon";
17 permissions = "0400";
18 text = ''
19 REDIS_HOST=${env.redis.host}
20 REDIS_PORT=${env.redis.port}
21 REDIS_DB=${env.redis.db}
22 DB_HOST=${env.postgresql.socket}
23 DB_USER=${env.postgresql.user}
24 DB_NAME=${env.postgresql.database}
25 DB_PASS=${env.postgresql.password}
26 DB_PORT=${env.postgresql.port}
27
28 LOCAL_DOMAIN=mastodon.immae.eu
29 LOCAL_HTTPS=true
30 ALTERNATE_DOMAINS=immae.eu
31
32 PAPERCLIP_SECRET=${env.paperclip_secret}
33 SECRET_KEY_BASE=${env.secret_key_base}
34 OTP_SECRET=${env.otp_secret}
35
36 VAPID_PRIVATE_KEY=${env.vapid.private}
37 VAPID_PUBLIC_KEY=${env.vapid.public}
38
39 SMTP_DELIVERY_METHOD=sendmail
40 SMTP_FROM_ADDRESS=mastodon@tools.immae.eu
41 SENDMAIL_LOCATION="/run/wrappers/bin/sendmail"
42 PAPERCLIP_ROOT_PATH=${mcfg.dataDir}
43
44 STREAMING_CLUSTER_NUM=1
45
46 RAILS_LOG_LEVEL=warn
47
48 # LDAP authentication (optional)
49 LDAP_ENABLED=true
50 LDAP_HOST=ldap.immae.eu
51 LDAP_PORT=636
52 LDAP_METHOD=simple_tls
53 LDAP_BASE="dc=immae,dc=eu"
54 LDAP_BIND_DN="cn=mastodon,ou=services,dc=immae,dc=eu"
55 LDAP_PASSWORD="${env.ldap.password}"
56 LDAP_UID="uid"
57 LDAP_SEARCH_FILTER="(&(%{uid}=%{email})(memberOf=cn=users,cn=mastodon,ou=services,dc=immae,dc=eu))"
58 '';
59 }];
60 services.mastodon = {
61 enable = true;
62 configFile = "/var/secrets/webapps/tools-mastodon";
63 socketsPrefix = "live_immae";
64 dataDir = "/var/lib/mastodon_immae";
65 };
66
67 services.websites.tools.modules = [
68 "headers" "proxy" "proxy_wstunnel" "proxy_http"
69 ];
70 system.extraSystemBuilderCmds = ''
71 mkdir -p $out/webapps
72 ln -s ${mcfg.workdir}/public/ $out/webapps/tools_mastodon
73 '';
74 services.websites.tools.vhostConfs.mastodon = {
75 certName = "eldiron";
76 addToCerts = true;
77 hosts = ["mastodon.immae.eu" ];
78 root = root;
79 extraConfig = [ ''
80 Header always set Referrer-Policy "strict-origin-when-cross-origin"
81 Header always set Strict-Transport-Security "max-age=31536000"
82
83 <LocationMatch "^/(assets|avatars|emoji|headers|packs|sounds|system)>
84 Header always set Cache-Control "public, max-age=31536000, immutable"
85 Require all granted
86 </LocationMatch>
87
88 ProxyPreserveHost On
89 RequestHeader set X-Forwarded-Proto "https"
90
91 RewriteEngine On
92
93 ProxyPass /500.html !
94 ProxyPass /sw.js !
95 ProxyPass /embed.js !
96 ProxyPass /robots.txt !
97 ProxyPass /manifest.json !
98 ProxyPass /browserconfig.xml !
99 ProxyPass /mask-icon.svg !
100 ProxyPassMatch ^(/.*\.(png|ico|gif)$) !
101 ProxyPassMatch ^/(assets|avatars|emoji|headers|packs|sounds|system|.well-known/acme-challenge) !
102
103 RewriteRule ^/api/v1/streaming/(.+)$ unix://${mcfg.sockets.node}|http://mastodon.immae.eu/api/v1/streaming/$1 [P,NE,QSA,L]
104 RewriteRule ^/api/v1/streaming/$ unix://${mcfg.sockets.node}|ws://mastodon.immae.eu/ [P,NE,QSA,L]
105 ProxyPass / unix://${mcfg.sockets.rails}|http://mastodon.immae.eu/
106 ProxyPassReverse / unix://${mcfg.sockets.rails}|http://mastodon.immae.eu/
107
108 Alias /system ${mcfg.dataDir}
109
110 <Directory ${mcfg.dataDir}>
111 Require all granted
112 Options -MultiViews
113 </Directory>
114
115 <Directory ${root}>
116 Require all granted
117 Options -MultiViews +FollowSymlinks
118 </Directory>
119
120 ErrorDocument 500 /500.html
121 ErrorDocument 501 /500.html
122 ErrorDocument 502 /500.html
123 ErrorDocument 503 /500.html
124 ErrorDocument 504 /500.html
125 '' ];
126 };
127 };
128}
diff --git a/modules/private/websites/tools/mgoblin/default.nix b/modules/private/websites/tools/mgoblin/default.nix
new file mode 100644
index 00000000..5da81f68
--- /dev/null
+++ b/modules/private/websites/tools/mgoblin/default.nix
@@ -0,0 +1,122 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 env = myconfig.env.tools.mediagoblin;
4 cfg = config.myServices.websites.tools.mediagoblin;
5 mcfg = config.services.mediagoblin;
6in {
7 options.myServices.websites.tools.mediagoblin = {
8 enable = lib.mkEnableOption "enable mediagoblin's website";
9 };
10
11 config = lib.mkIf cfg.enable {
12 secrets.keys = [{
13 dest = "webapps/tools-mediagoblin";
14 user = "mediagoblin";
15 group = "mediagoblin";
16 permissions = "0400";
17 text = ''
18 [DEFAULT]
19 data_basedir = "${mcfg.dataDir}"
20
21 [mediagoblin]
22 direct_remote_path = /mgoblin_static/
23 email_sender_address = "mediagoblin@tools.immae.eu"
24
25 #sql_engine = sqlite:///%(data_basedir)s/mediagoblin.db
26 sql_engine = ${env.psql_url}
27
28 email_debug_mode = false
29 allow_registration = false
30 allow_reporting = true
31
32 theme = airymodified
33
34 user_privilege_scheme = "uploader,commenter,reporter"
35
36 # We need to redefine them here since we override data_basedir
37 # cf /usr/share/webapps/mediagoblin/mediagoblin/config_spec.ini
38 workbench_path = %(data_basedir)s/media/workbench
39 crypto_path = %(data_basedir)s/crypto
40 theme_install_dir = %(data_basedir)s/themes/
41 theme_linked_assets_dir = %(data_basedir)s/theme_static/
42 plugin_linked_assets_dir = %(data_basedir)s/plugin_static/
43
44 [storage:queuestore]
45 base_dir = %(data_basedir)s/media/queue
46
47 [storage:publicstore]
48 base_dir = %(data_basedir)s/media/public
49 base_url = /mgoblin_media/
50
51 [celery]
52 CELERY_RESULT_DBURI = ${env.redis_url}
53 BROKER_URL = ${env.redis_url}
54 CELERYD_CONCURRENCY = 1
55
56 [plugins]
57 [[mediagoblin.plugins.geolocation]]
58 [[mediagoblin.plugins.ldap]]
59 [[[immae.eu]]]
60 LDAP_SERVER_URI = 'ldaps://ldap.immae.eu:636'
61 LDAP_SEARCH_BASE = 'dc=immae,dc=eu'
62 LDAP_BIND_DN = 'cn=mediagoblin,ou=services,dc=immae,dc=eu'
63 LDAP_BIND_PW = '${env.ldap.password}'
64 LDAP_SEARCH_FILTER = '(&(memberOf=cn=users,cn=mediagoblin,ou=services,dc=immae,dc=eu)(uid={username}))'
65 EMAIL_SEARCH_FIELD = 'mail'
66 [[mediagoblin.plugins.basicsearch]]
67 [[mediagoblin.plugins.piwigo]]
68 [[mediagoblin.plugins.processing_info]]
69 [[mediagoblin.media_types.image]]
70 [[mediagoblin.media_types.video]]
71 '';
72 }];
73
74 users.users.mediagoblin.extraGroups = [ "keys" ];
75
76 services.mediagoblin = {
77 enable = true;
78 plugins = builtins.attrValues pkgs.webapps.mediagoblin-plugins;
79 configFile = "/var/secrets/webapps/tools-mediagoblin";
80 };
81
82 services.websites.tools.modules = [
83 "proxy" "proxy_http"
84 ];
85 users.users.wwwrun.extraGroups = [ "mediagoblin" ];
86 services.websites.tools.vhostConfs.mgoblin = {
87 certName = "eldiron";
88 addToCerts = true;
89 hosts = ["mgoblin.immae.eu" ];
90 root = null;
91 extraConfig = [ ''
92 Alias /mgoblin_media ${mcfg.dataDir}/media/public
93 <Directory ${mcfg.dataDir}/media/public>
94 Options -Indexes +FollowSymLinks +MultiViews +Includes
95 Require all granted
96 </Directory>
97
98 Alias /theme_static ${mcfg.dataDir}/theme_static
99 <Directory ${mcfg.dataDir}/theme_static>
100 Options -Indexes +FollowSymLinks +MultiViews +Includes
101 Require all granted
102 </Directory>
103
104 Alias /plugin_static ${mcfg.dataDir}/plugin_static
105 <Directory ${mcfg.dataDir}/plugin_static>
106 Options -Indexes +FollowSymLinks +MultiViews +Includes
107 Require all granted
108 </Directory>
109
110 ProxyPreserveHost on
111 ProxyVia On
112 ProxyRequests Off
113 ProxyPass /mgoblin_media !
114 ProxyPass /theme_static !
115 ProxyPass /plugin_static !
116 ProxyPassMatch ^/.well-known/acme-challenge !
117 ProxyPass / unix://${mcfg.sockets.paster}|http://mgoblin.immae.eu/
118 ProxyPassReverse / unix://${mcfg.sockets.paster}|http://mgoblin.immae.eu/
119 '' ];
120 };
121 };
122}
diff --git a/modules/private/websites/tools/peertube/default.nix b/modules/private/websites/tools/peertube/default.nix
new file mode 100644
index 00000000..dee1b81d
--- /dev/null
+++ b/modules/private/websites/tools/peertube/default.nix
@@ -0,0 +1,179 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 env = myconfig.env.tools.peertube;
4 cfg = config.myServices.websites.tools.peertube;
5 pcfg = config.services.peertube;
6in {
7 options.myServices.websites.tools.peertube = {
8 enable = lib.mkEnableOption "enable Peertube's website";
9 };
10
11 config = lib.mkIf cfg.enable {
12 services.peertube = {
13 enable = true;
14 configFile = "/var/secrets/webapps/tools-peertube";
15 package = pkgs.webapps.peertube.override { ldap = true; };
16 };
17 users.users.peertube.extraGroups = [ "keys" ];
18
19 secrets.keys = [{
20 dest = "webapps/tools-peertube";
21 user = "peertube";
22 group = "peertube";
23 permissions = "0640";
24 text = ''
25 listen:
26 hostname: 'localhost'
27 port: ${env.listenPort}
28 webserver:
29 https: true
30 hostname: 'peertube.immae.eu'
31 port: 443
32 trust_proxy:
33 - 'loopback'
34 database:
35 hostname: '${env.postgresql.socket}'
36 port: 5432
37 suffix: '_prod'
38 username: '${env.postgresql.user}'
39 password: '${env.postgresql.password}'
40 pool:
41 max: 5
42 redis:
43 socket: '${env.redis.socket}'
44 auth: null
45 db: ${env.redis.db_index}
46 ldap:
47 enable: true
48 ldap_only: false
49 url: ldaps://${env.ldap.host}/${env.ldap.base}
50 bind_dn: ${env.ldap.dn}
51 bind_password: ${env.ldap.password}
52 base: ${env.ldap.base}
53 mail_entry: "mail"
54 user_filter: "${env.ldap.filter}"
55 smtp:
56 transport: sendmail
57 sendmail: '/run/wrappers/bin/sendmail'
58 hostname: null
59 port: 465 # If you use StartTLS: 587
60 username: null
61 password: null
62 tls: true # If you use StartTLS: false
63 disable_starttls: false
64 ca_file: null # Used for self signed certificates
65 from_address: 'peertube@tools.immae.eu'
66 storage:
67 tmp: '${pcfg.dataDir}/storage/tmp/'
68 avatars: '${pcfg.dataDir}/storage/avatars/'
69 videos: '${pcfg.dataDir}/storage/videos/'
70 redundancy: '${pcfg.dataDir}/storage/videos/'
71 logs: '${pcfg.dataDir}/storage/logs/'
72 previews: '${pcfg.dataDir}/storage/previews/'
73 thumbnails: '${pcfg.dataDir}/storage/thumbnails/'
74 torrents: '${pcfg.dataDir}/storage/torrents/'
75 captions: '${pcfg.dataDir}/storage/captions/'
76 cache: '${pcfg.dataDir}/storage/cache/'
77 log:
78 level: 'info'
79 search:
80 remote_uri:
81 users: true
82 anonymous: false
83 trending:
84 videos:
85 interval_days: 7
86 redundancy:
87 videos:
88 check_interval: '1 hour' # How often you want to check new videos to cache
89 strategies: # Just uncomment strategies you want
90 # Following are saved in local-production.json
91 cache:
92 previews:
93 size: 500 # Max number of previews you want to cache
94 captions:
95 size: 500 # Max number of video captions/subtitles you want to cache
96 admin:
97 email: 'peertube@tools.immae.eu'
98 contact_form:
99 enabled: true
100 signup:
101 enabled: false
102 limit: 10
103 requires_email_verification: false
104 filters:
105 cidr:
106 whitelist: []
107 blacklist: []
108 user:
109 video_quota: -1
110 video_quota_daily: -1
111 transcoding:
112 enabled: false
113 allow_additional_extensions: true
114 threads: 1
115 resolutions:
116 240p: false
117 360p: false
118 480p: true
119 720p: true
120 1080p: true
121 hls:
122 enabled: false
123 import:
124 videos:
125 http:
126 enabled: true
127 torrent:
128 enabled: false
129 instance:
130 name: 'Immae&#x2019;s PeerTube'
131 short_description: 'PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.'
132 description: '''
133 terms: '''
134 default_client_route: '/videos/trending'
135 default_nsfw_policy: 'blur'
136 customizations:
137 javascript: '''
138 css: '''
139 robots: |
140 User-agent: *
141 Disallow:
142 securitytxt:
143 "# If you would like to report a security issue\n# you may report it to:\nContact: https://github.com/Chocobozzz/PeerTube/blob/develop/SECURITY.md\nContact: mailto:"
144 services:
145 # You can provide a reporting endpoint for Content Security Policy violations
146 csp-logger:
147 twitter:
148 username: '@_immae'
149 whitelisted: false
150 '';
151 }];
152
153 services.websites.tools.modules = [
154 "headers" "proxy" "proxy_http" "proxy_wstunnel"
155 ];
156 services.websites.tools.vhostConfs.peertube = {
157 certName = "eldiron";
158 addToCerts = true;
159 hosts = [ "peertube.immae.eu" ];
160 root = null;
161 extraConfig = [ ''
162 RewriteEngine On
163
164 RewriteCond %{REQUEST_URI} ^/socket.io [NC]
165 RewriteCond %{QUERY_STRING} transport=websocket [NC]
166 RewriteRule /(.*) ws://localhost:${env.listenPort}/$1 [P,NE,QSA,L]
167
168 RewriteCond %{REQUEST_URI} ^/tracker/socket [NC]
169 RewriteRule /(.*) ws://localhost:${env.listenPort}/$1 [P,NE,QSA,L]
170
171 ProxyPass / http://localhost:${env.listenPort}/
172 ProxyPassReverse / http://localhost:${env.listenPort}/
173
174 ProxyPreserveHost On
175 RequestHeader set X-Real-IP %{REMOTE_ADDR}s
176 '' ];
177 };
178 };
179}
diff --git a/modules/private/websites/tools/tools/adminer.nix b/modules/private/websites/tools/tools/adminer.nix
new file mode 100644
index 00000000..cd51e7fe
--- /dev/null
+++ b/modules/private/websites/tools/tools/adminer.nix
@@ -0,0 +1,47 @@
1{ adminer }:
2rec {
3 activationScript = {
4 deps = [ "httpd" ];
5 text = ''
6 install -m 0755 -o ${apache.user} -g ${apache.group} -d /var/lib/php/sessions/adminer
7 install -m 0755 -o ${apache.user} -g ${apache.group} -d /var/lib/php/tmp/adminer
8 '';
9 };
10 webRoot = adminer;
11 phpFpm = rec {
12 socket = "/var/run/phpfpm/adminer.sock";
13 pool = ''
14 listen = ${socket}
15 user = ${apache.user}
16 group = ${apache.group}
17 listen.owner = ${apache.user}
18 listen.group = ${apache.group}
19 pm = ondemand
20 pm.max_children = 5
21 pm.process_idle_timeout = 60
22 ;php_admin_flag[log_errors] = on
23 ; Needed to avoid clashes in browser cookies (same domain)
24 php_value[session.name] = AdminerPHPSESSID
25 php_admin_value[open_basedir] = "${webRoot}:/tmp:/var/lib/php/sessions/adminer:/var/lib/php/tmp/adminer"
26 php_admin_value[session.save_path] = "/var/lib/php/sessions/adminer"
27 php_admin_value[upload_tmp_dir] = "/var/lib/php/tmp/adminer"
28 '';
29 };
30 apache = rec {
31 user = "wwwrun";
32 group = "wwwrun";
33 modules = [ "proxy_fcgi" ];
34 webappName = "_adminer";
35 root = "/run/current-system/webapps/${webappName}";
36 vhostConf = ''
37 Alias /adminer ${root}
38 <Directory ${root}>
39 DirectoryIndex index.php
40 Require all granted
41 <FilesMatch "\.php$">
42 SetHandler "proxy:unix:${phpFpm.socket}|fcgi://localhost"
43 </FilesMatch>
44 </Directory>
45 '';
46 };
47}
diff --git a/modules/private/websites/tools/tools/default.nix b/modules/private/websites/tools/tools/default.nix
new file mode 100644
index 00000000..94a2be16
--- /dev/null
+++ b/modules/private/websites/tools/tools/default.nix
@@ -0,0 +1,302 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 adminer = pkgs.callPackage ./adminer.nix {
4 inherit (pkgs.webapps) adminer;
5 };
6 ympd = pkgs.callPackage ./ympd.nix {
7 env = myconfig.env.tools.ympd;
8 };
9 ttrss = pkgs.callPackage ./ttrss.nix {
10 inherit (pkgs.webapps) ttrss ttrss-plugins;
11 env = myconfig.env.tools.ttrss;
12 };
13 roundcubemail = pkgs.callPackage ./roundcubemail.nix {
14 inherit (pkgs.webapps) roundcubemail roundcubemail-plugins roundcubemail-skins;
15 env = myconfig.env.tools.roundcubemail;
16 };
17 rainloop = pkgs.callPackage ./rainloop.nix {};
18 kanboard = pkgs.callPackage ./kanboard.nix {
19 env = myconfig.env.tools.kanboard;
20 };
21 wallabag = pkgs.callPackage ./wallabag.nix {
22 inherit (pkgs.webapps) wallabag;
23 env = myconfig.env.tools.wallabag;
24 };
25 yourls = pkgs.callPackage ./yourls.nix {
26 inherit (pkgs.webapps) yourls yourls-plugins;
27 env = myconfig.env.tools.yourls;
28 };
29 rompr = pkgs.callPackage ./rompr.nix {
30 inherit (pkgs.webapps) rompr;
31 env = myconfig.env.tools.rompr;
32 };
33 shaarli = pkgs.callPackage ./shaarli.nix {
34 env = myconfig.env.tools.shaarli;
35 };
36 dokuwiki = pkgs.callPackage ./dokuwiki.nix {
37 inherit (pkgs.webapps) dokuwiki dokuwiki-plugins;
38 };
39 ldap = pkgs.callPackage ./ldap.nix {
40 inherit (pkgs.webapps) phpldapadmin;
41 env = myconfig.env.tools.phpldapadmin;
42 };
43
44 cfg = config.myServices.websites.tools.tools;
45in {
46 options.myServices.websites.tools.tools = {
47 enable = lib.mkEnableOption "enable tools website";
48 };
49
50 config = lib.mkIf cfg.enable {
51 secrets.keys =
52 kanboard.keys
53 ++ ldap.keys
54 ++ roundcubemail.keys
55 ++ shaarli.keys
56 ++ ttrss.keys
57 ++ wallabag.keys
58 ++ yourls.keys;
59
60 services.websites.integration.modules =
61 rainloop.apache.modules;
62
63 services.websites.tools.modules =
64 [ "proxy_fcgi" ]
65 ++ adminer.apache.modules
66 ++ ympd.apache.modules
67 ++ ttrss.apache.modules
68 ++ roundcubemail.apache.modules
69 ++ wallabag.apache.modules
70 ++ yourls.apache.modules
71 ++ rompr.apache.modules
72 ++ shaarli.apache.modules
73 ++ dokuwiki.apache.modules
74 ++ ldap.apache.modules
75 ++ kanboard.apache.modules;
76
77 services.websites.integration.vhostConfs.devtools = {
78 certName = "eldiron";
79 addToCerts = true;
80 hosts = ["devtools.immae.eu" ];
81 root = "/var/lib/ftp/devtools.immae.eu";
82 extraConfig = [
83 ''
84 <Directory "/var/lib/ftp/devtools.immae.eu">
85 DirectoryIndex index.php index.htm index.html
86 AllowOverride all
87 Require all granted
88 <FilesMatch "\.php$">
89 SetHandler "proxy:unix:/var/run/phpfpm/devtools.sock|fcgi://localhost"
90 </FilesMatch>
91 </Directory>
92 ''
93 rainloop.apache.vhostConf
94 ];
95 };
96
97 services.websites.tools.vhostConfs.tools = {
98 certName = "eldiron";
99 addToCerts = true;
100 hosts = ["tools.immae.eu" ];
101 root = "/var/lib/ftp/tools.immae.eu";
102 extraConfig = [
103 ''
104 <Directory "/var/lib/ftp/tools.immae.eu">
105 DirectoryIndex index.php index.htm index.html
106 AllowOverride all
107 Require all granted
108 <FilesMatch "\.php$">
109 SetHandler "proxy:unix:/var/run/phpfpm/tools.sock|fcgi://localhost"
110 </FilesMatch>
111 </Directory>
112 ''
113 adminer.apache.vhostConf
114 ympd.apache.vhostConf
115 ttrss.apache.vhostConf
116 roundcubemail.apache.vhostConf
117 wallabag.apache.vhostConf
118 yourls.apache.vhostConf
119 rompr.apache.vhostConf
120 shaarli.apache.vhostConf
121 dokuwiki.apache.vhostConf
122 ldap.apache.vhostConf
123 kanboard.apache.vhostConf
124 ];
125 };
126
127 services.websites.tools.vhostConfs.outils = {
128 certName = "eldiron";
129 addToCerts = true;
130 hosts = [ "outils.immae.eu" ];
131 root = null;
132 extraConfig = [
133 ''
134 RedirectMatch 301 ^/mediagoblin(.*)$ https://mgoblin.immae.eu$1
135
136 RedirectMatch 301 ^/ether(.*)$ https://ether.immae.eu$1
137
138 RedirectMatch 301 ^/nextcloud(.*)$ https://cloud.immae.eu$1
139 RedirectMatch 301 ^/owncloud(.*)$ https://cloud.immae.eu$1
140
141 RedirectMatch 301 ^/carddavmate(.*)$ https://dav.immae.eu/infcloud$1
142 RedirectMatch 301 ^/caldavzap(.*)$ https://dav.immae.eu/infcloud$1
143 RedirectMatch 301 ^/caldav.php(.*)$ https://dav.immae.eu/caldav.php$1
144 RedirectMatch 301 ^/davical(.*)$ https://dav.immae.eu/davical$1
145
146 RedirectMatch 301 ^/taskweb(.*)$ https://task.immae.eu/taskweb$1
147
148 RedirectMatch 301 ^/(.*)$ https://tools.immae.eu/$1
149 ''
150 ];
151 };
152
153 systemd.services = {
154 phpfpm-dokuwiki = {
155 after = lib.mkAfter dokuwiki.phpFpm.serviceDeps;
156 wants = dokuwiki.phpFpm.serviceDeps;
157 };
158 phpfpm-kanboard = {
159 after = lib.mkAfter kanboard.phpFpm.serviceDeps;
160 wants = kanboard.phpFpm.serviceDeps;
161 };
162 phpfpm-ldap = {
163 after = lib.mkAfter ldap.phpFpm.serviceDeps;
164 wants = ldap.phpFpm.serviceDeps;
165 };
166 phpfpm-rainloop = {
167 after = lib.mkAfter rainloop.phpFpm.serviceDeps;
168 wants = rainloop.phpFpm.serviceDeps;
169 };
170 phpfpm-roundcubemail = {
171 after = lib.mkAfter roundcubemail.phpFpm.serviceDeps;
172 wants = roundcubemail.phpFpm.serviceDeps;
173 };
174 phpfpm-shaarli = {
175 after = lib.mkAfter shaarli.phpFpm.serviceDeps;
176 wants = shaarli.phpFpm.serviceDeps;
177 };
178 phpfpm-ttrss = {
179 after = lib.mkAfter ttrss.phpFpm.serviceDeps;
180 wants = ttrss.phpFpm.serviceDeps;
181 };
182 phpfpm-wallabag = {
183 after = lib.mkAfter wallabag.phpFpm.serviceDeps;
184 wants = wallabag.phpFpm.serviceDeps;
185 preStart = lib.mkAfter wallabag.phpFpm.preStart;
186 };
187 phpfpm-yourls = {
188 after = lib.mkAfter yourls.phpFpm.serviceDeps;
189 wants = yourls.phpFpm.serviceDeps;
190 };
191 ympd = {
192 description = "Standalone MPD Web GUI written in C";
193 wantedBy = [ "multi-user.target" ];
194 script = ''
195 export MPD_PASSWORD=$(cat /var/secrets/mpd)
196 ${pkgs.ympd}/bin/ympd --host ${ympd.config.host} --port ${toString ympd.config.port} --webport ${ympd.config.webPort} --user nobody
197 '';
198 };
199 tt-rss = {
200 description = "Tiny Tiny RSS feeds update daemon";
201 serviceConfig = {
202 User = "wwwrun";
203 ExecStart = "${pkgs.php}/bin/php ${ttrss.webRoot}/update.php --daemon";
204 StandardOutput = "syslog";
205 StandardError = "syslog";
206 PermissionsStartOnly = true;
207 };
208
209 wantedBy = [ "multi-user.target" ];
210 requires = ["postgresql.service"];
211 after = ["network.target" "postgresql.service"];
212 };
213 };
214
215 services.phpfpm.pools.roundcubemail = {
216 listen = roundcubemail.phpFpm.socket;
217 extraConfig = roundcubemail.phpFpm.pool;
218 phpOptions = config.services.phpfpm.phpOptions + roundcubemail.phpFpm.phpConfig;
219 };
220
221 services.phpfpm.pools.devtools = {
222 listen = "/var/run/phpfpm/devtools.sock";
223 extraConfig = ''
224 user = wwwrun
225 group = wwwrun
226 listen.owner = wwwrun
227 listen.group = wwwrun
228 pm = dynamic
229 pm.max_children = 60
230 pm.start_servers = 2
231 pm.min_spare_servers = 1
232 pm.max_spare_servers = 10
233
234 php_admin_value[open_basedir] = "/run/wrappers/bin/sendmail:/var/lib/ftp/devtools.immae.eu:/tmp"
235 '';
236 phpOptions = config.services.phpfpm.phpOptions + ''
237 extension=${pkgs.phpPackages.redis}/lib/php/extensions/redis.so
238 extension=${pkgs.phpPackages.apcu}/lib/php/extensions/apcu.so
239 zend_extension=${pkgs.php}/lib/php/extensions/opcache.so
240 '';
241 };
242
243 services.phpfpm.poolConfigs = {
244 adminer = adminer.phpFpm.pool;
245 ttrss = ttrss.phpFpm.pool;
246 wallabag = wallabag.phpFpm.pool;
247 yourls = yourls.phpFpm.pool;
248 rompr = rompr.phpFpm.pool;
249 shaarli = shaarli.phpFpm.pool;
250 dokuwiki = dokuwiki.phpFpm.pool;
251 ldap = ldap.phpFpm.pool;
252 rainloop = rainloop.phpFpm.pool;
253 kanboard = kanboard.phpFpm.pool;
254 tools = ''
255 listen = /var/run/phpfpm/tools.sock
256 user = wwwrun
257 group = wwwrun
258 listen.owner = wwwrun
259 listen.group = wwwrun
260 pm = dynamic
261 pm.max_children = 60
262 pm.start_servers = 2
263 pm.min_spare_servers = 1
264 pm.max_spare_servers = 10
265
266 ; Needed to avoid clashes in browser cookies (same domain)
267 php_value[session.name] = ToolsPHPSESSID
268 php_admin_value[open_basedir] = "/run/wrappers/bin/sendmail:/var/lib/ftp/tools.immae.eu:/tmp"
269 '';
270 };
271
272 system.activationScripts = {
273 adminer = adminer.activationScript;
274 ttrss = ttrss.activationScript;
275 roundcubemail = roundcubemail.activationScript;
276 wallabag = wallabag.activationScript;
277 yourls = yourls.activationScript;
278 rompr = rompr.activationScript;
279 shaarli = shaarli.activationScript;
280 dokuwiki = dokuwiki.activationScript;
281 rainloop = rainloop.activationScript;
282 kanboard = kanboard.activationScript;
283 ldap = ldap.activationScript;
284 };
285
286 myServices.websites.webappDirs = {
287 _adminer = adminer.webRoot;
288 "${dokuwiki.apache.webappName}" = dokuwiki.webRoot;
289 "${ldap.apache.webappName}" = "${ldap.webRoot}/htdocs";
290 "${rompr.apache.webappName}" = rompr.webRoot;
291 "${roundcubemail.apache.webappName}" = roundcubemail.webRoot;
292 "${shaarli.apache.webappName}" = shaarli.webRoot;
293 "${ttrss.apache.webappName}" = ttrss.webRoot;
294 "${wallabag.apache.webappName}" = wallabag.webRoot;
295 "${yourls.apache.webappName}" = yourls.webRoot;
296 "${rainloop.apache.webappName}" = rainloop.webRoot;
297 "${kanboard.apache.webappName}" = kanboard.webRoot;
298 };
299
300 };
301}
302
diff --git a/modules/private/websites/tools/tools/dokuwiki.nix b/modules/private/websites/tools/tools/dokuwiki.nix
new file mode 100644
index 00000000..c61d15f2
--- /dev/null
+++ b/modules/private/websites/tools/tools/dokuwiki.nix
@@ -0,0 +1,61 @@
1{ lib, stdenv, dokuwiki, dokuwiki-plugins }:
2rec {
3 varDir = "/var/lib/dokuwiki";
4 activationScript = {
5 deps = [ "wrappers" ];
6 text = ''
7 if [ ! -d ${varDir} ]; then
8 install -m 0755 -o ${apache.user} -g ${apache.group} -d ${varDir} \
9 ${varDir}/animals
10 cp -a ${webRoot}/conf.dist ${varDir}/conf
11 cp -a ${webRoot}/data.dist ${varDir}/data
12 cp -a ${webRoot}/
13 chown -R ${apache.user}:${apache.user} ${varDir}/config ${varDir}/data
14 chmod -R 755 ${varDir}/config ${varDir}/data
15 fi
16 install -m 0750 -o ${apache.user} -g ${apache.group} -d ${varDir}/phpSessions
17 '';
18 };
19 webRoot = dokuwiki.withPlugins (builtins.attrValues dokuwiki-plugins);
20 apache = rec {
21 user = "wwwrun";
22 group = "wwwrun";
23 modules = [ "proxy_fcgi" ];
24 webappName = "tools_dokuwiki";
25 root = "/run/current-system/webapps/${webappName}";
26 vhostConf = ''
27 Alias /dokuwiki "${root}"
28 <Directory "${root}">
29 DirectoryIndex index.php
30 <FilesMatch "\.php$">
31 SetHandler "proxy:unix:${phpFpm.socket}|fcgi://localhost"
32 </FilesMatch>
33
34 AllowOverride All
35 Options +FollowSymlinks
36 Require all granted
37 </Directory>
38 '';
39 };
40 phpFpm = rec {
41 serviceDeps = [ "openldap.service" ];
42 basedir = builtins.concatStringsSep ":" (
43 [ webRoot varDir ] ++ webRoot.plugins);
44 socket = "/var/run/phpfpm/dokuwiki.sock";
45 pool = ''
46 listen = ${socket}
47 user = ${apache.user}
48 group = ${apache.group}
49 listen.owner = ${apache.user}
50 listen.group = ${apache.group}
51 pm = ondemand
52 pm.max_children = 60
53 pm.process_idle_timeout = 60
54
55 ; Needed to avoid clashes in browser cookies (same domain)
56 php_value[session.name] = DokuwikiPHPSESSID
57 php_admin_value[open_basedir] = "${basedir}:/tmp"
58 php_admin_value[session.save_path] = "${varDir}/phpSessions"
59 '';
60 };
61}
diff --git a/modules/private/websites/tools/tools/kanboard.nix b/modules/private/websites/tools/tools/kanboard.nix
new file mode 100644
index 00000000..68f92b81
--- /dev/null
+++ b/modules/private/websites/tools/tools/kanboard.nix
@@ -0,0 +1,86 @@
1{ env, kanboard }:
2rec {
3 varDir = "/var/lib/kanboard";
4 activationScript = {
5 deps = [ "wrappers" ];
6 text = ''
7 install -m 0755 -o ${apache.user} -g ${apache.group} -d ${varDir}/data
8 install -m 0750 -o ${apache.user} -g ${apache.group} -d ${varDir}/phpSessions
9 install -TDm644 ${webRoot}/dataold/.htaccess ${varDir}/data/.htaccess
10 install -TDm644 ${webRoot}/dataold/web.config ${varDir}/data/web.config
11 '';
12 };
13 keys = [{
14 dest = "webapps/tools-kanboard";
15 user = apache.user;
16 group = apache.group;
17 permissions = "0400";
18 text = ''
19 <?php
20 define('MAIL_FROM', 'kanboard@tools.immae.eu');
21
22 define('DB_DRIVER', 'postgres');
23 define('DB_USERNAME', '${env.postgresql.user}');
24 define('DB_PASSWORD', '${env.postgresql.password}');
25 define('DB_HOSTNAME', '${env.postgresql.socket}');
26 define('DB_NAME', '${env.postgresql.database}');
27
28 define('DATA_DIR', '${varDir}');
29 define('LDAP_AUTH', true);
30 define('LDAP_SERVER', '${env.ldap.host}');
31 define('LDAP_START_TLS', true);
32
33 define('LDAP_BIND_TYPE', 'proxy');
34 define('LDAP_USERNAME', '${env.ldap.dn}');
35 define('LDAP_PASSWORD', '${env.ldap.password}');
36 define('LDAP_USER_BASE_DN', '${env.ldap.base}');
37 define('LDAP_USER_FILTER', '(&(memberOf=cn=users,cn=kanboard,ou=services,dc=immae,dc=eu)(uid=%s))');
38 define('LDAP_GROUP_ADMIN_DN', 'cn=admins,cn=kanboard,ou=services,dc=immae,dc=eu');
39 ?>
40 '';
41 }];
42 webRoot = kanboard { kanboard_config = "/var/secrets/webapps/tools-kanboard"; };
43 apache = rec {
44 user = "wwwrun";
45 group = "wwwrun";
46 modules = [ "proxy_fcgi" ];
47 webappName = "tools_kanboard";
48 root = "/run/current-system/webapps/${webappName}";
49 vhostConf = ''
50 Alias /kanboard "${root}"
51 <Directory "${root}">
52 DirectoryIndex index.php
53 AllowOverride All
54 Options FollowSymlinks
55 Require all granted
56
57 <FilesMatch "\.php$">
58 SetHandler "proxy:unix:${phpFpm.socket}|fcgi://localhost"
59 </FilesMatch>
60 </Directory>
61 <DirectoryMatch "${root}/data">
62 Require all denied
63 </DirectoryMatch>
64 '';
65 };
66 phpFpm = rec {
67 serviceDeps = [ "postgresql.service" "openldap.service" ];
68 basedir = builtins.concatStringsSep ":" [ webRoot varDir "/var/secrets/webapps/tools-kanboard" ];
69 socket = "/var/run/phpfpm/kanboard.sock";
70 pool = ''
71 listen = ${socket}
72 user = ${apache.user}
73 group = ${apache.group}
74 listen.owner = ${apache.user}
75 listen.group = ${apache.group}
76 pm = ondemand
77 pm.max_children = 60
78 pm.process_idle_timeout = 60
79
80 ; Needed to avoid clashes in browser cookies (same domain)
81 php_value[session.name] = KanboardPHPSESSID
82 php_admin_value[open_basedir] = "${basedir}:/tmp"
83 php_admin_value[session.save_path] = "${varDir}/phpSessions"
84 '';
85 };
86}
diff --git a/modules/private/websites/tools/tools/ldap.nix b/modules/private/websites/tools/tools/ldap.nix
new file mode 100644
index 00000000..4585ee3c
--- /dev/null
+++ b/modules/private/websites/tools/tools/ldap.nix
@@ -0,0 +1,74 @@
1{ lib, php, env, writeText, phpldapadmin }:
2rec {
3 activationScript = {
4 deps = [ "httpd" ];
5 text = ''
6 install -m 0755 -o ${apache.user} -g ${apache.group} -d /var/lib/php/sessions/phpldapadmin
7 '';
8 };
9 keys = [{
10 dest = "webapps/tools-ldap";
11 user = apache.user;
12 group = apache.group;
13 permissions = "0400";
14 text = ''
15 <?php
16 $config->custom->appearance['show_clear_password'] = true;
17 $config->custom->appearance['hide_template_warning'] = true;
18 $config->custom->appearance['theme'] = "tango";
19 $config->custom->appearance['minimalMode'] = true;
20
21 $servers = new Datastore();
22
23 $servers->newServer('ldap_pla');
24 $servers->setValue('server','name','Immae&#x2019;s LDAP');
25 $servers->setValue('server','host','ldaps://${env.ldap.host}');
26 $servers->setValue('login','auth_type','cookie');
27 $servers->setValue('login','bind_id','${env.ldap.dn}');
28 $servers->setValue('login','bind_pass','${env.ldap.password}');
29 $servers->setValue('appearance','password_hash','ssha');
30 $servers->setValue('login','attr','uid');
31 $servers->setValue('login','fallback_dn',true);
32 '';
33 }];
34 webRoot = phpldapadmin.override { config = "/var/secrets/webapps/tools-ldap"; };
35 apache = rec {
36 user = "wwwrun";
37 group = "wwwrun";
38 modules = [ "proxy_fcgi" ];
39 webappName = "tools_ldap";
40 root = "/run/current-system/webapps/${webappName}";
41 vhostConf = ''
42 Alias /ldap "${root}"
43 <Directory "${root}">
44 DirectoryIndex index.php
45 <FilesMatch "\.php$">
46 SetHandler "proxy:unix:${phpFpm.socket}|fcgi://localhost"
47 </FilesMatch>
48
49 AllowOverride None
50 Require all granted
51 </Directory>
52 '';
53 };
54 phpFpm = rec {
55 serviceDeps = [ "openldap.service" ];
56 basedir = builtins.concatStringsSep ":" [ webRoot "/var/secrets/webapps/tools-ldap" ];
57 socket = "/var/run/phpfpm/ldap.sock";
58 pool = ''
59 listen = ${socket}
60 user = ${apache.user}
61 group = ${apache.group}
62 listen.owner = ${apache.user}
63 listen.group = ${apache.group}
64 pm = ondemand
65 pm.max_children = 60
66 pm.process_idle_timeout = 60
67
68 ; Needed to avoid clashes in browser cookies (same domain)
69 php_value[session.name] = LdapPHPSESSID
70 php_admin_value[open_basedir] = "${basedir}:/tmp:/var/lib/php/sessions/phpldapadmin"
71 php_admin_value[session.save_path] = "/var/lib/php/sessions/phpldapadmin"
72 '';
73 };
74}
diff --git a/modules/private/websites/tools/tools/rainloop.nix b/modules/private/websites/tools/tools/rainloop.nix
new file mode 100644
index 00000000..dbf0f248
--- /dev/null
+++ b/modules/private/websites/tools/tools/rainloop.nix
@@ -0,0 +1,59 @@
1{ lib, pkgs, writeText, stdenv, fetchurl }:
2rec {
3 varDir = "/var/lib/rainloop";
4 activationScript = {
5 deps = [ "wrappers" ];
6 text = ''
7 install -m 0755 -o ${apache.user} -g ${apache.group} -d ${varDir}
8 install -m 0750 -o ${apache.user} -g ${apache.group} -d ${varDir}/phpSessions
9 install -m 0750 -o ${apache.user} -g ${apache.group} -d ${varDir}/data
10 '';
11 };
12 webRoot = pkgs.rainloop-community.override { dataPath = "${varDir}/data"; };
13 apache = rec {
14 user = "wwwrun";
15 group = "wwwrun";
16 modules = [ "proxy_fcgi" ];
17 webappName = "tools_rainloop";
18 root = "/run/current-system/webapps/${webappName}";
19 vhostConf = ''
20 Alias /rainloop "${root}"
21 <Directory "${root}">
22 DirectoryIndex index.php
23 AllowOverride All
24 Options -FollowSymlinks
25 Require all granted
26
27 <FilesMatch "\.php$">
28 SetHandler "proxy:unix:${phpFpm.socket}|fcgi://localhost"
29 </FilesMatch>
30 </Directory>
31
32 <DirectoryMatch "${root}/data">
33 Require all denied
34 </DirectoryMatch>
35 '';
36 };
37 phpFpm = rec {
38 serviceDeps = [ "postgresql.service" ];
39 basedir = builtins.concatStringsSep ":" [ webRoot varDir ];
40 socket = "/var/run/phpfpm/rainloop.sock";
41 pool = ''
42 listen = ${socket}
43 user = ${apache.user}
44 group = ${apache.group}
45 listen.owner = ${apache.user}
46 listen.group = ${apache.group}
47 pm = ondemand
48 pm.max_children = 60
49 pm.process_idle_timeout = 60
50
51 ; Needed to avoid clashes in browser cookies (same domain)
52 php_value[session.name] = RainloopPHPSESSID
53 php_admin_value[upload_max_filesize] = 200M
54 php_admin_value[post_max_size] = 200M
55 php_admin_value[open_basedir] = "${basedir}:/tmp"
56 php_admin_value[session.save_path] = "${varDir}/phpSessions"
57 '';
58 };
59}
diff --git a/modules/private/websites/tools/tools/rompr.nix b/modules/private/websites/tools/tools/rompr.nix
new file mode 100644
index 00000000..fea59fc9
--- /dev/null
+++ b/modules/private/websites/tools/tools/rompr.nix
@@ -0,0 +1,77 @@
1{ lib, env, rompr }:
2rec {
3 varDir = "/var/lib/rompr";
4 activationScript = ''
5 install -m 0755 -o ${apache.user} -g ${apache.group} -d ${varDir} \
6 ${varDir}/prefs ${varDir}/albumart ${varDir}/phpSessions
7 '';
8 webRoot = rompr;
9 apache = rec {
10 user = "wwwrun";
11 group = "wwwrun";
12 modules = [ "headers" "mime" "proxy_fcgi" ];
13 webappName = "tools_rompr";
14 root = "/run/current-system/webapps/${webappName}";
15 vhostConf = ''
16 Alias /rompr ${root}
17
18 <Directory ${root}>
19 Options Indexes FollowSymLinks
20 DirectoryIndex index.php
21 AllowOverride all
22 Require all granted
23 Order allow,deny
24 Allow from all
25 ErrorDocument 404 /rompr/404.php
26 AddType image/x-icon .ico
27
28 <FilesMatch "\.php$">
29 SetHandler "proxy:unix:${phpFpm.socket}|fcgi://localhost"
30 </FilesMatch>
31 </Directory>
32
33 <Directory ${root}/albumart/small>
34 Header Set Cache-Control "max-age=0, no-store"
35 Header Set Cache-Control "no-cache, must-revalidate"
36 </Directory>
37
38 <Directory ${root}/albumart/asdownloaded>
39 Header Set Cache-Control "max-age=0, no-store"
40 Header Set Cache-Control "no-cache, must-revalidate"
41 </Directory>
42
43 <LocationMatch "^/rompr">
44 Use LDAPConnect
45 Require ldap-group cn=users,cn=mpd,ou=services,dc=immae,dc=eu
46 </LocationMatch>
47 '';
48 };
49 phpFpm = rec {
50 basedir = builtins.concatStringsSep ":" [ webRoot varDir ];
51 socket = "/var/run/phpfpm/rompr.sock";
52 pool = ''
53 listen = ${socket}
54 user = ${apache.user}
55 group = ${apache.group}
56 listen.owner = ${apache.user}
57 listen.group = ${apache.group}
58 pm = ondemand
59 pm.max_children = 60
60 pm.process_idle_timeout = 60
61
62 ; Needed to avoid clashes in browser cookies (same domain)
63 php_value[session.name] = RomprPHPSESSID
64 php_admin_value[open_basedir] = "${basedir}:/tmp"
65 php_admin_value[session.save_path] = "${varDir}/phpSessions"
66 php_flag[magic_quotes_gpc] = Off
67 php_flag[track_vars] = On
68 php_flag[register_globals] = Off
69 php_admin_flag[allow_url_fopen] = On
70 php_value[include_path] = ${webRoot}
71 php_admin_value[upload_tmp_dir] = "${varDir}/prefs"
72 php_admin_value[post_max_size] = 32M
73 php_admin_value[upload_max_filesize] = 32M
74 php_admin_value[memory_limit] = 256M
75 '';
76 };
77}
diff --git a/modules/private/websites/tools/tools/roundcubemail.nix b/modules/private/websites/tools/tools/roundcubemail.nix
new file mode 100644
index 00000000..8974d1bb
--- /dev/null
+++ b/modules/private/websites/tools/tools/roundcubemail.nix
@@ -0,0 +1,121 @@
1{ env, roundcubemail, roundcubemail-plugins, roundcubemail-skins, phpPackages, apacheHttpd }:
2rec {
3 varDir = "/var/lib/roundcubemail";
4 activationScript = {
5 deps = [ "wrappers" ];
6 text = ''
7 install -m 0755 -o ${apache.user} -g ${apache.group} -d ${varDir} \
8 ${varDir}/cache ${varDir}/logs
9 install -m 0750 -o ${apache.user} -g ${apache.group} -d ${varDir}/phpSessions
10 '';
11 };
12 keys = [{
13 dest = "webapps/tools-roundcube";
14 user = apache.user;
15 group = apache.group;
16 permissions = "0400";
17 text = ''
18 <?php
19 $config['db_dsnw'] = '${env.psql_url}';
20 $config['default_host'] = 'ssl://mail.immae.eu';
21 $config['imap_conn_options'] = array("ssl" => array("verify_peer" => false));
22 $config['smtp_server'] = 'tls://mail.immae.eu';
23 $config['smtp_port'] = '25';
24 $config['managesieve_host'] = 'mail.immae.eu';
25 $config['managesieve_port'] = '4190';
26 $config['managesieve_usetls'] = true;
27 $config['managesieve_conn_options'] = array("ssl" => array("verify_peer" => false));
28
29 $config['imap_cache'] = 'db';
30 $config['messages_cache'] = 'db';
31
32 $config['support_url'] = ''';
33
34 $config['des_key'] = '${env.secret}';
35
36 $config['skin'] = 'elastic';
37 $config['plugins'] = array(
38 'attachment_reminder',
39 'emoticons',
40 'filesystem_attachments',
41 'hide_blockquote',
42 'identicon',
43 'identity_select',
44 'jqueryui',
45 'managesieve',
46 'newmail_notifier',
47 'vcard_attachments',
48 'zipdownload',
49
50 'automatic_addressbook',
51 'message_highlight',
52 'carddav',
53 // Ne marche pas ?: 'ident_switch',
54 // Ne marche pas ?: 'thunderbird_labels',
55 );
56
57 $config['language'] = 'fr_FR';
58
59 $config['drafts_mbox'] = 'Mail/Drafts';
60 $config['junk_mbox'] = 'Mail/Spam';
61 $config['sent_mbox'] = 'Mail/sent';
62 $config['trash_mbox'] = ''';
63 $config['default_folders'] = array('INBOX', 'Mail/Drafts', 'Mail/sent', 'Mail/Spam', ''');
64 $config['draft_autosave'] = 60;
65 $config['enable_installer'] = false;
66 $config['log_driver'] = 'file';
67 $config['temp_dir'] = '${varDir}/cache';
68 $config['mime_types'] = '${apacheHttpd}/conf/mime.types';
69 '';
70 }];
71 webRoot = (roundcubemail.override { roundcube_config = "/var/secrets/webapps/tools-roundcube"; }).withPlugins
72 (builtins.attrValues roundcubemail-plugins) (builtins.attrValues roundcubemail-skins);
73 apache = rec {
74 user = "wwwrun";
75 group = "wwwrun";
76 modules = [ "proxy_fcgi" ];
77 webappName = "tools_roundcubemail";
78 root = "/run/current-system/webapps/${webappName}";
79 vhostConf = ''
80 Alias /roundcube "${root}"
81 <Directory "${root}">
82 DirectoryIndex index.php
83 AllowOverride All
84 Options FollowSymlinks
85 Require all granted
86
87 <FilesMatch "\.php$">
88 SetHandler "proxy:unix:${phpFpm.socket}|fcgi://localhost"
89 </FilesMatch>
90 </Directory>
91 '';
92 };
93 phpFpm = rec {
94 serviceDeps = [ "postgresql.service" ];
95 basedir = builtins.concatStringsSep ":" (
96 [ webRoot "/var/secrets/webapps/tools-roundcube" varDir ]
97 ++ webRoot.plugins
98 ++ webRoot.skins);
99 phpConfig = ''
100 date.timezone = 'CET'
101 extension=${phpPackages.imagick}/lib/php/extensions/imagick.so
102 '';
103 socket = "/var/run/phpfpm/roundcubemail.sock";
104 pool = ''
105 user = ${apache.user}
106 group = ${apache.group}
107 listen.owner = ${apache.user}
108 listen.group = ${apache.group}
109 pm = ondemand
110 pm.max_children = 60
111 pm.process_idle_timeout = 60
112
113 ; Needed to avoid clashes in browser cookies (same domain)
114 php_value[session.name] = RoundcubemailPHPSESSID
115 php_admin_value[upload_max_filesize] = 200M
116 php_admin_value[post_max_size] = 200M
117 php_admin_value[open_basedir] = "${basedir}:${apacheHttpd}/conf/mime.types:/tmp"
118 php_admin_value[session.save_path] = "${varDir}/phpSessions"
119 '';
120 };
121}
diff --git a/modules/private/websites/tools/tools/shaarli.nix b/modules/private/websites/tools/tools/shaarli.nix
new file mode 100644
index 00000000..2e89a473
--- /dev/null
+++ b/modules/private/websites/tools/tools/shaarli.nix
@@ -0,0 +1,65 @@
1{ lib, env, stdenv, fetchurl, shaarli }:
2let
3 varDir = "/var/lib/shaarli";
4in rec {
5 activationScript = ''
6 install -m 0755 -o ${apache.user} -g ${apache.group} -d ${varDir} \
7 ${varDir}/cache ${varDir}/pagecache ${varDir}/tmp ${varDir}/data \
8 ${varDir}/phpSessions
9 '';
10 webRoot = shaarli varDir;
11 apache = rec {
12 user = "wwwrun";
13 group = "wwwrun";
14 modules = [ "proxy_fcgi" "rewrite" "env" ];
15 webappName = "tools_shaarli";
16 root = "/run/current-system/webapps/${webappName}";
17 vhostConf = ''
18 Alias /Shaarli "${root}"
19
20 Include /var/secrets/webapps/tools-shaarli
21 <Directory "${root}">
22 DirectoryIndex index.php index.htm index.html
23 Options Indexes FollowSymLinks MultiViews Includes
24 AllowOverride All
25 Require all granted
26 <FilesMatch "\.php$">
27 SetHandler "proxy:unix:${phpFpm.socket}|fcgi://localhost"
28 </FilesMatch>
29 </Directory>
30 '';
31 };
32 keys = [{
33 dest = "webapps/tools-shaarli";
34 user = apache.user;
35 group = apache.group;
36 permissions = "0400";
37 text = ''
38 SetEnv SHAARLI_LDAP_PASSWORD "${env.ldap.password}"
39 SetEnv SHAARLI_LDAP_DN "${env.ldap.dn}"
40 SetEnv SHAARLI_LDAP_HOST "ldaps://${env.ldap.host}"
41 SetEnv SHAARLI_LDAP_BASE "${env.ldap.base}"
42 SetEnv SHAARLI_LDAP_FILTER "${env.ldap.search}"
43 '';
44 }];
45 phpFpm = rec {
46 serviceDeps = [ "openldap.service" ];
47 basedir = builtins.concatStringsSep ":" [ webRoot varDir ];
48 socket = "/var/run/phpfpm/shaarli.sock";
49 pool = ''
50 listen = ${socket}
51 user = ${apache.user}
52 group = ${apache.group}
53 listen.owner = ${apache.user}
54 listen.group = ${apache.group}
55 pm = ondemand
56 pm.max_children = 60
57 pm.process_idle_timeout = 60
58
59 ; Needed to avoid clashes in browser cookies (same domain)
60 php_value[session.name] = ShaarliPHPSESSID
61 php_admin_value[open_basedir] = "${basedir}:/tmp"
62 php_admin_value[session.save_path] = "${varDir}/phpSessions"
63 '';
64 };
65}
diff --git a/modules/private/websites/tools/tools/ttrss.nix b/modules/private/websites/tools/tools/ttrss.nix
new file mode 100644
index 00000000..05c8cab0
--- /dev/null
+++ b/modules/private/websites/tools/tools/ttrss.nix
@@ -0,0 +1,131 @@
1{ php, env, ttrss, ttrss-plugins }:
2rec {
3 varDir = "/var/lib/ttrss";
4 activationScript = {
5 deps = [ "wrappers" ];
6 text = ''
7 install -m 0755 -o ${apache.user} -g ${apache.group} -d ${varDir} \
8 ${varDir}/lock ${varDir}/cache ${varDir}/feed-icons
9 install -m 0755 -o ${apache.user} -g ${apache.group} -d ${varDir}/cache/export/ \
10 ${varDir}/cache/feeds/ \
11 ${varDir}/cache/images/ \
12 ${varDir}/cache/js/ \
13 ${varDir}/cache/simplepie/ \
14 ${varDir}/cache/upload/
15 touch ${varDir}/feed-icons/index.html
16 install -m 0750 -o ${apache.user} -g ${apache.group} -d ${varDir}/phpSessions
17 '';
18 };
19 keys = [{
20 dest = "webapps/tools-ttrss";
21 user = apache.user;
22 group = apache.group;
23 permissions = "0400";
24 text = ''
25 <?php
26
27 define('PHP_EXECUTABLE', '${php}/bin/php');
28
29 define('LOCK_DIRECTORY', 'lock');
30 define('CACHE_DIR', 'cache');
31 define('ICONS_DIR', 'feed-icons');
32 define('ICONS_URL', 'feed-icons');
33 define('SELF_URL_PATH', 'https://tools.immae.eu/ttrss/');
34
35 define('MYSQL_CHARSET', 'UTF8');
36
37 define('DB_TYPE', 'pgsql');
38 define('DB_HOST', '${env.postgresql.socket}');
39 define('DB_USER', '${env.postgresql.user}');
40 define('DB_NAME', '${env.postgresql.database}');
41 define('DB_PASS', '${env.postgresql.password}');
42 define('DB_PORT', '${env.postgresql.port}');
43
44 define('AUTH_AUTO_CREATE', true);
45 define('AUTH_AUTO_LOGIN', true);
46
47 define('SINGLE_USER_MODE', false);
48
49 define('SIMPLE_UPDATE_MODE', false);
50 define('CHECK_FOR_UPDATES', true);
51
52 define('FORCE_ARTICLE_PURGE', 0);
53 define('SESSION_COOKIE_LIFETIME', 60*60*24*120);
54 define('ENABLE_GZIP_OUTPUT', false);
55
56 define('PLUGINS', 'auth_ldap, note, instances');
57
58 define('LOG_DESTINATION', ''');
59 define('CONFIG_VERSION', 26);
60
61
62 define('SPHINX_SERVER', 'localhost:9312');
63 define('SPHINX_INDEX', 'ttrss, delta');
64
65 define('ENABLE_REGISTRATION', false);
66 define('REG_NOTIFY_ADDRESS', 'ttrss@tools.immae.eu');
67 define('REG_MAX_USERS', 10);
68
69 define('SMTP_FROM_NAME', 'Tiny Tiny RSS');
70 define('SMTP_FROM_ADDRESS', 'ttrss@tools.immae.eu');
71 define('DIGEST_SUBJECT', '[tt-rss] New headlines for last 24 hours');
72
73 define('LDAP_AUTH_SERVER_URI', 'ldap://ldap.immae.eu:389/');
74 define('LDAP_AUTH_USETLS', TRUE);
75 define('LDAP_AUTH_ALLOW_UNTRUSTED_CERT', TRUE);
76 define('LDAP_AUTH_BASEDN', 'dc=immae,dc=eu');
77 define('LDAP_AUTH_ANONYMOUSBEFOREBIND', FALSE);
78 define('LDAP_AUTH_SEARCHFILTER', '(&(memberOf=cn=users,cn=ttrss,ou=services,dc=immae,dc=eu)(|(cn=???)(uid=???)(&(uid:dn:=???)(ou=ttrss))))');
79
80 define('LDAP_AUTH_BINDDN', 'cn=ttrss,ou=services,dc=immae,dc=eu');
81 define('LDAP_AUTH_BINDPW', '${env.ldap.password}');
82 define('LDAP_AUTH_LOGIN_ATTRIB', 'immaeTtrssLogin');
83
84 define('LDAP_AUTH_LOG_ATTEMPTS', FALSE);
85 define('LDAP_AUTH_DEBUG', FALSE);
86 '';
87 }];
88 webRoot = (ttrss.override { ttrss_config = "/var/secrets/webapps/tools-ttrss"; }).withPlugins (builtins.attrValues ttrss-plugins);
89 apache = rec {
90 user = "wwwrun";
91 group = "wwwrun";
92 modules = [ "proxy_fcgi" ];
93 webappName = "tools_ttrss";
94 root = "/run/current-system/webapps/${webappName}";
95 vhostConf = ''
96 Alias /ttrss "${root}"
97 <Directory "${root}">
98 DirectoryIndex index.php
99 <FilesMatch "\.php$">
100 SetHandler "proxy:unix:${phpFpm.socket}|fcgi://localhost"
101 </FilesMatch>
102
103 AllowOverride All
104 Options FollowSymlinks
105 Require all granted
106 </Directory>
107 '';
108 };
109 phpFpm = rec {
110 serviceDeps = [ "postgresql.service" "openldap.service" ];
111 basedir = builtins.concatStringsSep ":" (
112 [ webRoot "/var/secrets/webapps/tools-ttrss" varDir ]
113 ++ webRoot.plugins);
114 socket = "/var/run/phpfpm/ttrss.sock";
115 pool = ''
116 listen = ${socket}
117 user = ${apache.user}
118 group = ${apache.group}
119 listen.owner = ${apache.user}
120 listen.group = ${apache.group}
121 pm = ondemand
122 pm.max_children = 60
123 pm.process_idle_timeout = 60
124
125 ; Needed to avoid clashes in browser cookies (same domain)
126 php_value[session.name] = TtrssPHPSESSID
127 php_admin_value[open_basedir] = "${basedir}:/tmp"
128 php_admin_value[session.save_path] = "${varDir}/phpSessions"
129 '';
130 };
131}
diff --git a/modules/private/websites/tools/tools/wallabag.nix b/modules/private/websites/tools/tools/wallabag.nix
new file mode 100644
index 00000000..d6e58828
--- /dev/null
+++ b/modules/private/websites/tools/tools/wallabag.nix
@@ -0,0 +1,148 @@
1{ env, wallabag }:
2rec {
3 varDir = "/var/lib/wallabag";
4 keys = [{
5 dest = "webapps/tools-wallabag";
6 user = apache.user;
7 group = apache.group;
8 permissions = "0400";
9 text = ''
10 # This file is auto-generated during the composer install
11 parameters:
12 database_driver: pdo_pgsql
13 database_driver_class: Wallabag\CoreBundle\Doctrine\DBAL\Driver\CustomPostgreSQLDriver
14 database_host: ${env.postgresql.socket}
15 database_port: ${env.postgresql.port}
16 database_name: ${env.postgresql.database}
17 database_user: ${env.postgresql.user}
18 database_password: ${env.postgresql.password}
19 database_path: null
20 database_table_prefix: wallabag_
21 database_socket: null
22 database_charset: utf8
23 domain_name: https://tools.immae.eu/wallabag
24 mailer_transport: sendmail
25 mailer_host: 127.0.0.1
26 mailer_user: null
27 mailer_password: null
28 locale: fr
29 secret: ${env.secret}
30 twofactor_auth: true
31 twofactor_sender: wallabag@tools.immae.eu
32 fosuser_registration: false
33 fosuser_confirmation: true
34 from_email: wallabag@tools.immae.eu
35 rss_limit: 50
36 rabbitmq_host: localhost
37 rabbitmq_port: 5672
38 rabbitmq_user: guest
39 rabbitmq_password: guest
40 rabbitmq_prefetch_count: 10
41 redis_scheme: unix
42 redis_host: null
43 redis_port: null
44 redis_path: ${env.redis.socket}
45 redis_password: null
46 sites_credentials: { }
47 ldap_enabled: true
48 ldap_host: ldap.immae.eu
49 ldap_port: 636
50 ldap_tls: false
51 ldap_ssl: true
52 ldap_bind_requires_dn: true
53 ldap_base: 'dc=immae,dc=eu'
54 ldap_manager_dn: 'cn=wallabag,ou=services,dc=immae,dc=eu'
55 ldap_manager_pw: ${env.ldap.password}
56 ldap_filter: '(&(memberOf=cn=users,cn=wallabag,ou=services,dc=immae,dc=eu))'
57 ldap_admin_filter: '(&(memberOf=cn=admins,cn=wallabag,ou=services,dc=immae,dc=eu)(uid=%s))'
58 ldap_username_attribute: uid
59 ldap_email_attribute: mail
60 ldap_name_attribute: cn
61 ldap_enabled_attribute: null
62 services:
63 swiftmailer.mailer.default.transport:
64 class: Swift_SendmailTransport
65 arguments: ['/run/wrappers/bin/sendmail -bs']
66 '';
67 }];
68 webappDir = wallabag.override { ldap = true; wallabag_config = "/var/secrets/webapps/tools-wallabag"; };
69 activationScript = ''
70 install -m 0755 -o ${apache.user} -g ${apache.group} -d ${varDir} \
71 ${varDir}/var ${varDir}/data/db ${varDir}/assets/images
72 '';
73 webRoot = "${webappDir}/web";
74 # Domain migration: Table wallabag_entry contains whole
75 # https://tools.immae.eu/wallabag domain name in preview_picture
76 apache = rec {
77 user = "wwwrun";
78 group = "wwwrun";
79 modules = [ "proxy_fcgi" ];
80 webappName = "tools_wallabag";
81 root = "/run/current-system/webapps/${webappName}";
82 vhostConf = ''
83 Alias /wallabag "${root}"
84 <Directory "${root}">
85 AllowOverride None
86 Require all granted
87 # For OAuth (apps)
88 CGIPassAuth On
89
90 <FilesMatch "\.php$">
91 SetHandler "proxy:unix:${phpFpm.socket}|fcgi://localhost"
92 </FilesMatch>
93
94 <IfModule mod_rewrite.c>
95 Options -MultiViews
96 RewriteEngine On
97 RewriteCond %{REQUEST_FILENAME} !-f
98 RewriteRule ^(.*)$ app.php [QSA,L]
99 </IfModule>
100 </Directory>
101 <Directory "${root}/bundles">
102 <IfModule mod_rewrite.c>
103 RewriteEngine Off
104 </IfModule>
105 </Directory>
106 <Directory "${varDir}/assets">
107 AllowOverride None
108 Require all granted
109 </Directory>
110 '';
111 };
112 phpFpm = rec {
113 preStart = ''
114 if [ ! -f "${varDir}/currentWebappDir" -o \
115 ! -f "${varDir}/currentKey" -o \
116 "${webappDir}" != "$(cat ${varDir}/currentWebappDir 2>/dev/null)" ] \
117 || ! sha512sum -c --status ${varDir}/currentKey; then
118 pushd ${webappDir} > /dev/null
119 /run/wrappers/bin/sudo -u wwwrun ./bin/console --env=prod cache:clear
120 rm -rf /var/lib/wallabag/var/cache/pro_
121 /run/wrappers/bin/sudo -u wwwrun ./bin/console --env=prod doctrine:migrations:migrate --no-interaction
122 popd > /dev/null
123 echo -n "${webappDir}" > ${varDir}/currentWebappDir
124 sha512sum /var/secrets/webapps/tools-wallabag > ${varDir}/currentKey
125 fi
126 '';
127 serviceDeps = [ "postgresql.service" "openldap.service" ];
128 basedir = builtins.concatStringsSep ":" [ webappDir "/var/secrets/webapps/tools-wallabag" varDir ];
129 socket = "/var/run/phpfpm/wallabag.sock";
130 pool = ''
131 listen = ${socket}
132 user = ${apache.user}
133 group = ${apache.group}
134 listen.owner = ${apache.user}
135 listen.group = ${apache.group}
136 pm = dynamic
137 pm.max_children = 60
138 pm.start_servers = 2
139 pm.min_spare_servers = 1
140 pm.max_spare_servers = 10
141
142 ; Needed to avoid clashes in browser cookies (same domain)
143 php_value[session.name] = WallabagPHPSESSID
144 php_admin_value[open_basedir] = "/run/wrappers/bin/sendmail:${basedir}:/tmp"
145 php_value[max_execution_time] = 300
146 '';
147 };
148}
diff --git a/modules/private/websites/tools/tools/ympd.nix b/modules/private/websites/tools/tools/ympd.nix
new file mode 100644
index 00000000..b54c4866
--- /dev/null
+++ b/modules/private/websites/tools/tools/ympd.nix
@@ -0,0 +1,40 @@
1{ env }:
2let
3 ympd = rec {
4 config = {
5 webPort = "localhost:${env.listenPort}";
6 host = env.mpd.host;
7 port = env.mpd.port;
8 };
9 apache = {
10 modules = [
11 "proxy_wstunnel"
12 ];
13 vhostConf = ''
14 <LocationMatch "^/mpd(?!/music.(mp3|ogg))">
15 Use LDAPConnect
16 Require ldap-group cn=users,cn=mpd,ou=services,dc=immae,dc=eu
17 </LocationMatch>
18
19 RedirectMatch permanent "^/mpd$" "/mpd/"
20 <Location "/mpd/">
21 ProxyPass http://${config.webPort}/
22 ProxyPassReverse http://${config.webPort}/
23 ProxyPreserveHost on
24 </Location>
25 <Location "/mpd/ws">
26 ProxyPass ws://${config.webPort}/ws
27 </Location>
28 <Location "/mpd/music.mp3">
29 ProxyPass unix:///run/mpd/mp3.sock|http://tools.immae.eu/
30 ProxyPassReverse unix:///run/mpd/mp3.sock|http://tools.immae.eu/
31 </Location>
32 <Location "/mpd/music.ogg">
33 ProxyPass unix:///run/mpd/ogg.sock|http://tools.immae.eu/
34 ProxyPassReverse unix:///run/mpd/ogg.sock|http://tools.immae.eu/
35 </Location>
36 '';
37 };
38 };
39in
40 ympd
diff --git a/modules/private/websites/tools/tools/yourls.nix b/modules/private/websites/tools/tools/yourls.nix
new file mode 100644
index 00000000..0a8e8377
--- /dev/null
+++ b/modules/private/websites/tools/tools/yourls.nix
@@ -0,0 +1,93 @@
1{ env, yourls, yourls-plugins }:
2rec {
3 activationScript = {
4 deps = [ "httpd" ];
5 text = ''
6 install -m 0755 -o ${apache.user} -g ${apache.group} -d /var/lib/php/sessions/yourls
7 '';
8 };
9 keys = [{
10 dest = "webapps/tools-yourls";
11 user = apache.user;
12 group = apache.group;
13 permissions = "0400";
14 text = ''
15 <?php
16 define( 'YOURLS_DB_USER', '${env.mysql.user}' );
17 define( 'YOURLS_DB_PASS', '${env.mysql.password}' );
18 define( 'YOURLS_DB_NAME', '${env.mysql.database}' );
19 define( 'YOURLS_DB_HOST', '${env.mysql.host}' );
20 define( 'YOURLS_DB_PREFIX', 'yourls_' );
21 define( 'YOURLS_SITE', 'https://tools.immae.eu/url' );
22 define( 'YOURLS_HOURS_OFFSET', 0 );
23 define( 'YOURLS_LANG', ''' );
24 define( 'YOURLS_UNIQUE_URLS', true );
25 define( 'YOURLS_PRIVATE', true );
26 define( 'YOURLS_COOKIEKEY', '${env.cookieKey}' );
27 $yourls_user_passwords = array();
28 define( 'YOURLS_DEBUG', false );
29 define( 'YOURLS_URL_CONVERT', 36 );
30 $yourls_reserved_URL = array();
31 define( 'LDAPAUTH_HOST', 'ldaps://ldap.immae.eu' );
32 define( 'LDAPAUTH_PORT', '636' );
33 define( 'LDAPAUTH_BASE', 'dc=immae,dc=eu' );
34 define( 'LDAPAUTH_SEARCH_USER', 'cn=yourls,ou=services,dc=immae,dc=eu' );
35 define( 'LDAPAUTH_SEARCH_PASS', '${env.ldap.password}' );
36
37 define( 'LDAPAUTH_GROUP_ATTR', 'memberof' );
38 define( 'LDAPAUTH_GROUP_REQ', 'cn=admin,cn=yourls,ou=services,dc=immae,dc=eu');
39
40 define( 'LDAPAUTH_USERCACHE_TYPE', 0);
41 '';
42 }];
43 webRoot = (yourls.override { yourls_config = "/var/secrets/webapps/tools-yourls"; }).withPlugins
44 (builtins.attrValues yourls-plugins);
45 apache = rec {
46 user = "wwwrun";
47 group = "wwwrun";
48 modules = [ "proxy_fcgi" ];
49 webappName = "tools_yourls";
50 root = "/run/current-system/webapps/${webappName}";
51 vhostConf = ''
52 Alias /url "${root}"
53 <Directory "${root}">
54 <FilesMatch "\.php$">
55 SetHandler "proxy:unix:${phpFpm.socket}|fcgi://localhost"
56 </FilesMatch>
57
58 AllowOverride None
59 Require all granted
60 <IfModule mod_rewrite.c>
61 RewriteEngine On
62 RewriteBase /url/
63 RewriteCond %{REQUEST_FILENAME} !-f
64 RewriteCond %{REQUEST_FILENAME} !-d
65 RewriteRule ^.*$ /url/yourls-loader.php [L]
66 </IfModule>
67 DirectoryIndex index.php
68 </Directory>
69 '';
70 };
71 phpFpm = rec {
72 serviceDeps = [ "mysql.service" "openldap.service" ];
73 basedir = builtins.concatStringsSep ":" (
74 [ webRoot "/var/secrets/webapps/tools-yourls" ]
75 ++ webRoot.plugins);
76 socket = "/var/run/phpfpm/yourls.sock";
77 pool = ''
78 listen = ${socket}
79 user = ${apache.user}
80 group = ${apache.group}
81 listen.owner = ${apache.user}
82 listen.group = ${apache.group}
83 pm = ondemand
84 pm.max_children = 60
85 pm.process_idle_timeout = 60
86
87 ; Needed to avoid clashes in browser cookies (same domain)
88 php_value[session.name] = YourlsPHPSESSID
89 php_admin_value[open_basedir] = "${basedir}:/tmp:/var/lib/php/sessions/yourls"
90 php_admin_value[session.save_path] = "/var/lib/php/sessions/yourls"
91 '';
92 };
93}
diff --git a/modules/secrets.nix b/modules/secrets.nix
new file mode 100644
index 00000000..b282e56e
--- /dev/null
+++ b/modules/secrets.nix
@@ -0,0 +1,61 @@
1{ lib, pkgs, config, ... }:
2{
3 options.secrets = {
4 keys = lib.mkOption {
5 type = lib.types.listOf lib.types.unspecified;
6 default = [];
7 description = "Keys to upload to server";
8 };
9 location = lib.mkOption {
10 type = lib.types.path;
11 default = "/var/secrets";
12 description = "Location where to put the keys";
13 };
14 };
15 config = let
16 location = config.secrets.location;
17 keys = config.secrets.keys;
18 empty = pkgs.runCommand "empty" { preferLocalBuild = true; } "mkdir -p $out && touch $out/done";
19 dumpKey = v: ''
20 mkdir -p secrets/$(dirname ${v.dest})
21 echo -n ${lib.strings.escapeShellArg v.text} > secrets/${v.dest}
22 cat >> mods <<EOF
23 ${v.user or "root"} ${v.group or "root"} ${v.permissions or "0600"} secrets/${v.dest}
24 EOF
25 '';
26 secrets = pkgs.runCommand "secrets.tar" {} ''
27 touch mods
28 tar --format=ustar --mtime='1970-01-01' -P --transform="s@${empty}@secrets@" -cf $out ${empty}/done
29 ${builtins.concatStringsSep "\n" (map dumpKey keys)}
30 cat mods | while read u g p k; do
31 tar --format=ustar --mtime='1970-01-01' --owner="$u" --group="$g" --mode="$p" --append -f $out "$k"
32 done
33 '';
34 in lib.mkIf (builtins.length keys > 0) {
35 system.activationScripts.secrets = {
36 deps = [ "users" "wrappers" ];
37 text = ''
38 install -m0750 -o root -g keys -d ${location}
39 if [ -f /run/keys/secrets.tar ]; then
40 if [ ! -f ${location}/currentSecrets ] || ! sha512sum -c --status "${location}/currentSecrets"; then
41 echo "rebuilding secrets"
42 rm -rf ${location}
43 install -m0750 -o root -g keys -d ${location}
44 ${pkgs.gnutar}/bin/tar --strip-components 1 -C ${location} -xf /run/keys/secrets.tar
45 sha512sum /run/keys/secrets.tar > ${location}/currentSecrets
46 find ${location} -type d -exec chown root:keys {} \; -exec chmod o-rx {} \;
47 fi
48 fi
49 '';
50 };
51 deployment.keys."secrets.tar" = {
52 permissions = "0400";
53 # keyFile below is not evaluated at build time by nixops, so the
54 # `secrets` path doesn’t necessarily exist when uploading the
55 # keys, and nixops is unhappy.
56 user = "root${builtins.substring 10000 1 secrets}";
57 group = "root";
58 keyFile = "${secrets}";
59 };
60 };
61}
diff --git a/modules/webapps/diaspora.nix b/modules/webapps/diaspora.nix
new file mode 100644
index 00000000..65599b73
--- /dev/null
+++ b/modules/webapps/diaspora.nix
@@ -0,0 +1,171 @@
1{ lib, pkgs, config, ... }:
2let
3 name = "diaspora";
4 cfg = config.services.diaspora;
5
6 uid = config.ids.uids.diaspora;
7 gid = config.ids.gids.diaspora;
8in
9{
10 options.services.diaspora = {
11 enable = lib.mkEnableOption "Enable Diaspora’s service";
12 user = lib.mkOption {
13 type = lib.types.str;
14 default = name;
15 description = "User account under which Diaspora runs";
16 };
17 group = lib.mkOption {
18 type = lib.types.str;
19 default = name;
20 description = "Group under which Diaspora runs";
21 };
22 adminEmail = lib.mkOption {
23 type = lib.types.str;
24 example = "admin@example.com";
25 description = "Admin e-mail for Diaspora";
26 };
27 dataDir = lib.mkOption {
28 type = lib.types.path;
29 default = "/var/lib/${name}";
30 description = ''
31 The directory where Diaspora stores its data.
32 '';
33 };
34 socketsDir = lib.mkOption {
35 type = lib.types.path;
36 default = "/run/${name}";
37 description = ''
38 The directory where Diaspora puts runtime files and sockets.
39 '';
40 };
41 configDir = lib.mkOption {
42 type = lib.types.path;
43 description = ''
44 The configuration path for Diaspora.
45 '';
46 };
47 package = lib.mkOption {
48 type = lib.types.package;
49 default = pkgs.webapps.diaspora;
50 description = ''
51 Diaspora package to use.
52 '';
53 };
54 # Output variables
55 systemdStateDirectory = lib.mkOption {
56 type = lib.types.str;
57 # Use ReadWritePaths= instead if varDir is outside of /var/lib
58 default = assert lib.strings.hasPrefix "/var/lib/" cfg.dataDir;
59 lib.strings.removePrefix "/var/lib/" cfg.dataDir;
60 description = ''
61 Adjusted Diaspora data directory for systemd
62 '';
63 readOnly = true;
64 };
65 systemdRuntimeDirectory = lib.mkOption {
66 type = lib.types.str;
67 # Use ReadWritePaths= instead if socketsDir is outside of /run
68 default = assert lib.strings.hasPrefix "/run/" cfg.socketsDir;
69 lib.strings.removePrefix "/run/" cfg.socketsDir;
70 description = ''
71 Adjusted Diaspora sockets directory for systemd
72 '';
73 readOnly = true;
74 };
75 workdir = lib.mkOption {
76 type = lib.types.package;
77 default = cfg.package.override {
78 varDir = cfg.dataDir;
79 podmin_email = cfg.adminEmail;
80 config_dir = cfg.configDir;
81 };
82 description = ''
83 Adjusted diaspora package with overriden values
84 '';
85 readOnly = true;
86 };
87 sockets = lib.mkOption {
88 type = lib.types.attrsOf lib.types.path;
89 default = {
90 rails = "${cfg.socketsDir}/diaspora.sock";
91 eye = "${cfg.socketsDir}/eye.sock";
92 };
93 readOnly = true;
94 description = ''
95 Diaspora sockets
96 '';
97 };
98 pids = lib.mkOption {
99 type = lib.types.attrsOf lib.types.path;
100 default = {
101 eye = "${cfg.socketsDir}/eye.pid";
102 };
103 readOnly = true;
104 description = ''
105 Diaspora pids
106 '';
107 };
108 };
109
110 config = lib.mkIf cfg.enable {
111 users.users = lib.optionalAttrs (cfg.user == name) (lib.singleton {
112 inherit name;
113 inherit uid;
114 group = cfg.group;
115 description = "Diaspora user";
116 home = cfg.dataDir;
117 packages = [ cfg.workdir.gems pkgs.nodejs cfg.workdir.gems.ruby ];
118 useDefaultShell = true;
119 });
120 users.groups = lib.optionalAttrs (cfg.group == name) (lib.singleton {
121 inherit name;
122 inherit gid;
123 });
124
125 systemd.services.diaspora = {
126 description = "Diaspora";
127 wantedBy = [ "multi-user.target" ];
128 after = [
129 "network.target" "redis.service" "postgresql.service"
130 ];
131 wants = [
132 "redis.service" "postgresql.service"
133 ];
134
135 environment.RAILS_ENV = "production";
136 environment.BUNDLE_PATH = "${cfg.workdir.gems}/${cfg.workdir.gems.ruby.gemPath}";
137 environment.BUNDLE_GEMFILE = "${cfg.workdir.gems.confFiles}/Gemfile";
138 environment.EYE_SOCK = cfg.sockets.eye;
139 environment.EYE_PID = cfg.pids.eye;
140
141 path = [ cfg.workdir.gems pkgs.nodejs cfg.workdir.gems.ruby pkgs.curl pkgs.which pkgs.gawk ];
142
143 preStart = ''
144 install -m 0755 -d ${cfg.dataDir}/uploads ${cfg.dataDir}/tmp ${cfg.dataDir}/log
145 install -m 0700 -d ${cfg.dataDir}/tmp/pids
146 if [ ! -f ${cfg.dataDir}/schedule.yml ]; then
147 echo "{}" > ${cfg.dataDir}/schedule.yml
148 fi
149 ./bin/bundle exec rails db:migrate
150 '';
151
152 script = ''
153 exec ${cfg.workdir}/script/server
154 '';
155
156 serviceConfig = {
157 User = cfg.user;
158 PrivateTmp = true;
159 Restart = "always";
160 Type = "simple";
161 WorkingDirectory = cfg.workdir;
162 StateDirectory = cfg.systemdStateDirectory;
163 RuntimeDirectory = cfg.systemdRuntimeDirectory;
164 StandardInput = "null";
165 KillMode = "control-group";
166 };
167
168 unitConfig.RequiresMountsFor = cfg.dataDir;
169 };
170 };
171}
diff --git a/modules/webapps/etherpad-lite.nix b/modules/webapps/etherpad-lite.nix
new file mode 100644
index 00000000..7f0e2ed4
--- /dev/null
+++ b/modules/webapps/etherpad-lite.nix
@@ -0,0 +1,158 @@
1{ lib, pkgs, config, ... }:
2let
3 name = "etherpad-lite";
4 cfg = config.services.etherpad-lite;
5
6 uid = config.ids.uids.etherpad-lite;
7 gid = config.ids.gids.etherpad-lite;
8in
9{
10 options.services.etherpad-lite = {
11 enable = lib.mkEnableOption "Enable Etherpad lite’s service";
12 user = lib.mkOption {
13 type = lib.types.str;
14 default = name;
15 description = "User account under which Etherpad lite runs";
16 };
17 group = lib.mkOption {
18 type = lib.types.str;
19 default = name;
20 description = "Group under which Etherpad lite runs";
21 };
22 dataDir = lib.mkOption {
23 type = lib.types.path;
24 default = "/var/lib/${name}";
25 description = ''
26 The directory where Etherpad lite stores its data.
27 '';
28 };
29 socketsDir = lib.mkOption {
30 type = lib.types.path;
31 default = "/run/${name}";
32 description = ''
33 The directory where Etherpad lite stores its sockets.
34 '';
35 };
36 configFile = lib.mkOption {
37 type = lib.types.path;
38 description = ''
39 The config file path for Etherpad lite.
40 '';
41 };
42 sessionKeyFile = lib.mkOption {
43 type = lib.types.path;
44 description = ''
45 The Session key file path for Etherpad lite.
46 '';
47 };
48 apiKeyFile = lib.mkOption {
49 type = lib.types.path;
50 description = ''
51 The API key file path for Etherpad lite.
52 '';
53 };
54 package = lib.mkOption {
55 type = lib.types.package;
56 default = pkgs.webapps.etherpad-lite;
57 description = ''
58 Etherpad lite package to use.
59 '';
60 };
61 modules = lib.mkOption {
62 type = lib.types.listOf lib.types.package;
63 default = [];
64 description = ''
65 Etherpad lite modules to use.
66 '';
67 };
68 # Output variables
69 workdir = lib.mkOption {
70 type = lib.types.package;
71 default = cfg.package.withModules cfg.modules;
72 description = ''
73 Adjusted Etherpad lite package with plugins
74 '';
75 readOnly = true;
76 };
77 systemdStateDirectory = lib.mkOption {
78 type = lib.types.str;
79 # Use ReadWritePaths= instead if varDir is outside of /var/lib
80 default = assert lib.strings.hasPrefix "/var/lib/" cfg.dataDir;
81 lib.strings.removePrefix "/var/lib/" cfg.dataDir;
82 description = ''
83 Adjusted Etherpad lite data directory for systemd
84 '';
85 readOnly = true;
86 };
87 systemdRuntimeDirectory = lib.mkOption {
88 type = lib.types.str;
89 # Use ReadWritePaths= instead if socketsDir is outside of /run
90 default = assert lib.strings.hasPrefix "/run/" cfg.socketsDir;
91 lib.strings.removePrefix "/run/" cfg.socketsDir;
92 description = ''
93 Adjusted Etherpad lite sockets directory for systemd
94 '';
95 readOnly = true;
96 };
97 sockets = lib.mkOption {
98 type = lib.types.attrsOf lib.types.path;
99 default = {
100 node = "${cfg.socketsDir}/etherpad-lite.sock";
101 };
102 readOnly = true;
103 description = ''
104 Etherpad lite sockets
105 '';
106 };
107 };
108
109 config = lib.mkIf cfg.enable {
110 systemd.services.etherpad-lite = {
111 description = "Etherpad-lite";
112 wantedBy = [ "multi-user.target" ];
113 after = [ "network.target" "postgresql.service" ];
114 wants = [ "postgresql.service" ];
115
116 environment.NODE_ENV = "production";
117 environment.HOME = cfg.workdir;
118
119 path = [ pkgs.nodejs ];
120
121 script = ''
122 exec ${pkgs.nodejs}/bin/node ${cfg.workdir}/src/node/server.js \
123 --sessionkey ${cfg.sessionKeyFile} \
124 --apikey ${cfg.apiKeyFile} \
125 --settings ${cfg.configFile}
126 '';
127
128 postStart = ''
129 while [ ! -S ${cfg.sockets.node} ]; do
130 sleep 0.5
131 done
132 chmod a+w ${cfg.sockets.node}
133 '';
134 serviceConfig = {
135 DynamicUser = true;
136 User = cfg.user;
137 Group = cfg.group;
138 WorkingDirectory = cfg.workdir;
139 PrivateTmp = true;
140 NoNewPrivileges = true;
141 PrivateDevices = true;
142 ProtectHome = true;
143 ProtectControlGroups = true;
144 ProtectKernelModules = true;
145 Restart = "always";
146 Type = "simple";
147 TimeoutSec = 60;
148 RuntimeDirectory = cfg.systemdRuntimeDirectory;
149 StateDirectory= cfg.systemdStateDirectory;
150 ExecStartPre = [
151 "+${pkgs.coreutils}/bin/install -d -m 0755 -o ${cfg.user} -g ${cfg.group} ${cfg.dataDir}/ep_initialized"
152 "+${pkgs.coreutils}/bin/chown -R ${cfg.user}:${cfg.group} ${cfg.dataDir} ${cfg.configFile} ${cfg.sessionKeyFile} ${cfg.apiKeyFile}"
153 ];
154 };
155 };
156
157 };
158}
diff --git a/modules/webapps/mastodon.nix b/modules/webapps/mastodon.nix
new file mode 100644
index 00000000..6255de91
--- /dev/null
+++ b/modules/webapps/mastodon.nix
@@ -0,0 +1,223 @@
1{ lib, pkgs, config, ... }:
2let
3 name = "mastodon";
4 cfg = config.services.mastodon;
5
6 uid = config.ids.uids.mastodon;
7 gid = config.ids.gids.mastodon;
8in
9{
10 options.services.mastodon = {
11 enable = lib.mkEnableOption "Enable Mastodon’s service";
12 user = lib.mkOption {
13 type = lib.types.str;
14 default = name;
15 description = "User account under which Mastodon runs";
16 };
17 group = lib.mkOption {
18 type = lib.types.str;
19 default = name;
20 description = "Group under which Mastodon runs";
21 };
22 dataDir = lib.mkOption {
23 type = lib.types.path;
24 default = "/var/lib/${name}";
25 description = ''
26 The directory where Mastodon stores its data.
27 '';
28 };
29 socketsPrefix = lib.mkOption {
30 type = lib.types.string;
31 default = "live";
32 description = ''
33 The prefix to use for Mastodon sockets.
34 '';
35 };
36 socketsDir = lib.mkOption {
37 type = lib.types.path;
38 default = "/run/${name}";
39 description = ''
40 The directory where Mastodon puts runtime files and sockets.
41 '';
42 };
43 configFile = lib.mkOption {
44 type = lib.types.path;
45 description = ''
46 The configuration file path for Mastodon.
47 '';
48 };
49 package = lib.mkOption {
50 type = lib.types.package;
51 default = pkgs.webapps.mastodon;
52 description = ''
53 Mastodon package to use.
54 '';
55 };
56 # Output variables
57 workdir = lib.mkOption {
58 type = lib.types.package;
59 default = cfg.package.override { varDir = cfg.dataDir; };
60 description = ''
61 Adjusted mastodon package with overriden varDir
62 '';
63 readOnly = true;
64 };
65 systemdStateDirectory = lib.mkOption {
66 type = lib.types.str;
67 # Use ReadWritePaths= instead if varDir is outside of /var/lib
68 default = assert lib.strings.hasPrefix "/var/lib/" cfg.dataDir;
69 lib.strings.removePrefix "/var/lib/" cfg.dataDir;
70 description = ''
71 Adjusted Mastodon data directory for systemd
72 '';
73 readOnly = true;
74 };
75 systemdRuntimeDirectory = lib.mkOption {
76 type = lib.types.str;
77 # Use ReadWritePaths= instead if socketsDir is outside of /run
78 default = assert lib.strings.hasPrefix "/run/" cfg.socketsDir;
79 lib.strings.removePrefix "/run/" cfg.socketsDir;
80 description = ''
81 Adjusted Mastodon sockets directory for systemd
82 '';
83 readOnly = true;
84 };
85 sockets = lib.mkOption {
86 type = lib.types.attrsOf lib.types.path;
87 default = {
88 node = "${cfg.socketsDir}/${cfg.socketsPrefix}_node.sock";
89 rails = "${cfg.socketsDir}/${cfg.socketsPrefix}_puma.sock";
90 };
91 readOnly = true;
92 description = ''
93 Mastodon sockets
94 '';
95 };
96 };
97
98 config = lib.mkIf cfg.enable {
99 users.users = lib.optionalAttrs (cfg.user == name) (lib.singleton {
100 inherit name;
101 inherit uid;
102 group = cfg.group;
103 description = "Mastodon user";
104 home = cfg.dataDir;
105 useDefaultShell = true;
106 });
107 users.groups = lib.optionalAttrs (cfg.group == name) (lib.singleton {
108 inherit name;
109 inherit gid;
110 });
111
112 systemd.services.mastodon-streaming = {
113 description = "Mastodon Streaming";
114 wantedBy = [ "multi-user.target" ];
115 after = [ "network.target" "mastodon-web.service" ];
116
117 environment.NODE_ENV = "production";
118 environment.SOCKET = cfg.sockets.node;
119
120 path = [ pkgs.nodejs pkgs.bashInteractive ];
121
122 script = ''
123 exec npm run start
124 '';
125
126 postStart = ''
127 while [ ! -S $SOCKET ]; do
128 sleep 0.5
129 done
130 chmod a+w $SOCKET
131 '';
132
133 postStop = ''
134 rm $SOCKET
135 '';
136
137 serviceConfig = {
138 User = cfg.user;
139 EnvironmentFile = cfg.configFile;
140 PrivateTmp = true;
141 Restart = "always";
142 TimeoutSec = 15;
143 Type = "simple";
144 WorkingDirectory = cfg.workdir;
145 StateDirectory = cfg.systemdStateDirectory;
146 RuntimeDirectory = cfg.systemdRuntimeDirectory;
147 RuntimeDirectoryPreserve = "yes";
148 };
149
150 unitConfig.RequiresMountsFor = cfg.dataDir;
151 };
152
153 systemd.services.mastodon-web = {
154 description = "Mastodon Web app";
155 wantedBy = [ "multi-user.target" ];
156 after = [ "network.target" ];
157
158 environment.RAILS_ENV = "production";
159 environment.BUNDLE_PATH = "${cfg.workdir.gems}/${cfg.workdir.gems.ruby.gemPath}";
160 environment.BUNDLE_GEMFILE = "${cfg.workdir.gems.confFiles}/Gemfile";
161 environment.SOCKET = cfg.sockets.rails;
162
163 path = [ cfg.workdir.gems cfg.workdir.gems.ruby pkgs.file ];
164
165 preStart = ''
166 install -m 0755 -d ${cfg.dataDir}/tmp/cache
167 ./bin/bundle exec rails db:migrate
168 '';
169
170 script = ''
171 exec ./bin/bundle exec puma -C config/puma.rb
172 '';
173
174 serviceConfig = {
175 User = cfg.user;
176 EnvironmentFile = cfg.configFile;
177 PrivateTmp = true;
178 Restart = "always";
179 TimeoutSec = 60;
180 Type = "simple";
181 WorkingDirectory = cfg.workdir;
182 StateDirectory = cfg.systemdStateDirectory;
183 RuntimeDirectory = cfg.systemdRuntimeDirectory;
184 RuntimeDirectoryPreserve = "yes";
185 };
186
187 unitConfig.RequiresMountsFor = cfg.dataDir;
188 };
189
190 systemd.services.mastodon-sidekiq = {
191 description = "Mastodon Sidekiq";
192 wantedBy = [ "multi-user.target" ];
193 after = [ "network.target" "mastodon-web.service" ];
194
195 environment.RAILS_ENV="production";
196 environment.BUNDLE_PATH = "${cfg.workdir.gems}/${cfg.workdir.gems.ruby.gemPath}";
197 environment.BUNDLE_GEMFILE = "${cfg.workdir.gems.confFiles}/Gemfile";
198 environment.DB_POOL="5";
199
200 path = [ cfg.workdir.gems cfg.workdir.gems.ruby pkgs.imagemagick pkgs.ffmpeg pkgs.file ];
201
202 script = ''
203 exec ./bin/bundle exec sidekiq -c 5 -q default -q mailers -q pull -q push
204 '';
205
206 serviceConfig = {
207 User = cfg.user;
208 EnvironmentFile = cfg.configFile;
209 PrivateTmp = true;
210 Restart = "always";
211 TimeoutSec = 15;
212 Type = "simple";
213 WorkingDirectory = cfg.workdir;
214 StateDirectory = cfg.systemdStateDirectory;
215 RuntimeDirectory = cfg.systemdRuntimeDirectory;
216 RuntimeDirectoryPreserve = "yes";
217 };
218
219 unitConfig.RequiresMountsFor = cfg.dataDir;
220 };
221
222 };
223}
diff --git a/modules/webapps/mediagoblin.nix b/modules/webapps/mediagoblin.nix
new file mode 100644
index 00000000..78bbef6f
--- /dev/null
+++ b/modules/webapps/mediagoblin.nix
@@ -0,0 +1,237 @@
1{ lib, pkgs, config, ... }:
2let
3 name = "mediagoblin";
4 cfg = config.services.mediagoblin;
5
6 uid = config.ids.uids.mediagoblin;
7 gid = config.ids.gids.mediagoblin;
8
9 paste_local = pkgs.writeText "paste_local.ini" ''
10 [DEFAULT]
11 debug = false
12
13 [pipeline:main]
14 pipeline = mediagoblin
15
16 [app:mediagoblin]
17 use = egg:mediagoblin#app
18 config = ${cfg.configFile} ${cfg.workdir}/mediagoblin.ini
19 /mgoblin_static = ${cfg.workdir}/mediagoblin/static
20
21 [loggers]
22 keys = root
23
24 [handlers]
25 keys = console
26
27 [formatters]
28 keys = generic
29
30 [logger_root]
31 level = INFO
32 handlers = console
33
34 [handler_console]
35 class = StreamHandler
36 args = (sys.stderr,)
37 level = NOTSET
38 formatter = generic
39
40 [formatter_generic]
41 format = %(levelname)-7.7s [%(name)s] %(message)s
42
43 [filter:errors]
44 use = egg:mediagoblin#errors
45 debug = false
46
47 [server:main]
48 use = egg:waitress#main
49 unix_socket = ${cfg.sockets.paster}
50 unix_socket_perms = 777
51 url_scheme = https
52 '';
53in
54{
55 options.services.mediagoblin = {
56 enable = lib.mkEnableOption "Enable Mediagoblin’s service";
57 user = lib.mkOption {
58 type = lib.types.str;
59 default = name;
60 description = "User account under which Mediagoblin runs";
61 };
62 group = lib.mkOption {
63 type = lib.types.str;
64 default = name;
65 description = "Group under which Mediagoblin runs";
66 };
67 dataDir = lib.mkOption {
68 type = lib.types.path;
69 default = "/var/lib/${name}";
70 description = ''
71 The directory where Mediagoblin stores its data.
72 '';
73 };
74 socketsDir = lib.mkOption {
75 type = lib.types.path;
76 default = "/run/${name}";
77 description = ''
78 The directory where Mediagoblin puts runtime files and sockets.
79 '';
80 };
81 configFile = lib.mkOption {
82 type = lib.types.path;
83 description = ''
84 The configuration file path for Mediagoblin.
85 '';
86 };
87 package = lib.mkOption {
88 type = lib.types.package;
89 default = pkgs.webapps.mediagoblin;
90 description = ''
91 Mediagoblin package to use.
92 '';
93 };
94 plugins = lib.mkOption {
95 type = lib.types.listOf lib.types.package;
96 default = [];
97 description = ''
98 Mediagoblin plugins to use.
99 '';
100 };
101 # Output variables
102 workdir = lib.mkOption {
103 type = lib.types.package;
104 default = cfg.package.withPlugins cfg.plugins;
105 description = ''
106 Adjusted Mediagoblin package with plugins
107 '';
108 readOnly = true;
109 };
110 systemdStateDirectory = lib.mkOption {
111 type = lib.types.str;
112 # Use ReadWritePaths= instead if varDir is outside of /var/lib
113 default = assert lib.strings.hasPrefix "/var/lib/" cfg.dataDir;
114 lib.strings.removePrefix "/var/lib/" cfg.dataDir;
115 description = ''
116 Adjusted Mediagoblin data directory for systemd
117 '';
118 readOnly = true;
119 };
120 systemdRuntimeDirectory = lib.mkOption {
121 type = lib.types.str;
122 # Use ReadWritePaths= instead if socketsDir is outside of /run
123 default = assert lib.strings.hasPrefix "/run/" cfg.socketsDir;
124 lib.strings.removePrefix "/run/" cfg.socketsDir;
125 description = ''
126 Adjusted Mediagoblin sockets directory for systemd
127 '';
128 readOnly = true;
129 };
130 sockets = lib.mkOption {
131 type = lib.types.attrsOf lib.types.path;
132 default = {
133 paster = "${cfg.socketsDir}/mediagoblin.sock";
134 };
135 readOnly = true;
136 description = ''
137 Mediagoblin sockets
138 '';
139 };
140 pids = lib.mkOption {
141 type = lib.types.attrsOf lib.types.path;
142 default = {
143 paster = "${cfg.socketsDir}/mediagoblin.pid";
144 celery = "${cfg.socketsDir}/mediagoblin-celeryd.pid";
145 };
146 readOnly = true;
147 description = ''
148 Mediagoblin pid files
149 '';
150 };
151 };
152
153 config = lib.mkIf cfg.enable {
154 users.users = lib.optionalAttrs (cfg.user == name) (lib.singleton {
155 inherit name;
156 inherit uid;
157 group = cfg.group;
158 description = "Mediagoblin user";
159 home = cfg.dataDir;
160 useDefaultShell = true;
161 });
162 users.groups = lib.optionalAttrs (cfg.group == name) (lib.singleton {
163 inherit name;
164 inherit gid;
165 });
166
167 systemd.services.mediagoblin-web = {
168 description = "Mediagoblin service";
169 wantedBy = [ "multi-user.target" ];
170 after = [ "network.target" ];
171 wants = [ "postgresql.service" "redis.service" ];
172
173 environment.SCRIPT_NAME = "/mediagoblin/";
174
175 script = ''
176 exec ./bin/paster serve \
177 ${paste_local} \
178 --pid-file=${cfg.pids.paster}
179 '';
180 preStop = ''
181 exec ./bin/paster serve \
182 --pid-file=${cfg.pids.paster} \
183 ${paste_local} stop
184 '';
185 preStart = ''
186 if [ -d ${cfg.dataDir}/plugin_static/ ]; then
187 rm ${cfg.dataDir}/plugin_static/coreplugin_basic_auth
188 ln -sf ${cfg.workdir}/mediagoblin/plugins/basic_auth/static ${cfg.dataDir}/plugin_static/coreplugin_basic_auth
189 fi
190 ./bin/gmg -cf ${cfg.configFile} dbupdate
191 '';
192
193 serviceConfig = {
194 User = cfg.user;
195 PrivateTmp = true;
196 Restart = "always";
197 TimeoutSec = 15;
198 Type = "simple";
199 WorkingDirectory = cfg.workdir;
200 RuntimeDirectory = cfg.systemdRuntimeDirectory;
201 StateDirectory= cfg.systemdStateDirectory;
202 PIDFile = cfg.pids.paster;
203 };
204
205 unitConfig.RequiresMountsFor = cfg.dataDir;
206 };
207
208 systemd.services.mediagoblin-celeryd = {
209 description = "Mediagoblin service";
210 wantedBy = [ "multi-user.target" ];
211 after = [ "network.target" "mediagoblin-web.service" ];
212
213 environment.MEDIAGOBLIN_CONFIG = cfg.configFile;
214 environment.CELERY_CONFIG_MODULE = "mediagoblin.init.celery.from_celery";
215
216 script = ''
217 exec ./bin/celery worker \
218 --logfile=${cfg.dataDir}/celery.log \
219 --loglevel=INFO
220 '';
221
222 serviceConfig = {
223 User = cfg.user;
224 PrivateTmp = true;
225 Restart = "always";
226 TimeoutSec = 60;
227 Type = "simple";
228 WorkingDirectory = cfg.workdir;
229 RuntimeDirectory = cfg.systemdRuntimeDirectory;
230 StateDirectory= cfg.systemdStateDirectory;
231 PIDFile = cfg.pids.celery;
232 };
233
234 unitConfig.RequiresMountsFor = cfg.dataDir;
235 };
236 };
237}
diff --git a/modules/webapps/peertube.nix b/modules/webapps/peertube.nix
new file mode 100644
index 00000000..89dcc67a
--- /dev/null
+++ b/modules/webapps/peertube.nix
@@ -0,0 +1,105 @@
1{ lib, pkgs, config, ... }:
2let
3 name = "peertube";
4 cfg = config.services.peertube;
5
6 uid = config.ids.uids.peertube;
7 gid = config.ids.gids.peertube;
8in
9{
10 options.services.peertube = {
11 enable = lib.mkEnableOption "Enable Peertube’s service";
12 user = lib.mkOption {
13 type = lib.types.str;
14 default = name;
15 description = "User account under which Peertube runs";
16 };
17 group = lib.mkOption {
18 type = lib.types.str;
19 default = name;
20 description = "Group under which Peertube runs";
21 };
22 dataDir = lib.mkOption {
23 type = lib.types.path;
24 default = "/var/lib/${name}";
25 description = ''
26 The directory where Peertube stores its data.
27 '';
28 };
29 configFile = lib.mkOption {
30 type = lib.types.path;
31 description = ''
32 The configuration file path for Peertube.
33 '';
34 };
35 package = lib.mkOption {
36 type = lib.types.package;
37 default = pkgs.webapps.peertube;
38 description = ''
39 Peertube package to use.
40 '';
41 };
42 # Output variables
43 systemdStateDirectory = lib.mkOption {
44 type = lib.types.str;
45 # Use ReadWritePaths= instead if varDir is outside of /var/lib
46 default = assert lib.strings.hasPrefix "/var/lib/" cfg.dataDir;
47 lib.strings.removePrefix "/var/lib/" cfg.dataDir;
48 description = ''
49 Adjusted Peertube data directory for systemd
50 '';
51 readOnly = true;
52 };
53 };
54
55 config = lib.mkIf cfg.enable {
56 users.users = lib.optionalAttrs (cfg.user == name) (lib.singleton {
57 inherit name;
58 inherit uid;
59 group = cfg.group;
60 description = "Peertube user";
61 home = cfg.dataDir;
62 useDefaultShell = true;
63 });
64 users.groups = lib.optionalAttrs (cfg.group == name) (lib.singleton {
65 inherit name;
66 inherit gid;
67 });
68
69 systemd.services.peertube = {
70 description = "Peertube";
71 wantedBy = [ "multi-user.target" ];
72 after = [ "network.target" "postgresql.service" ];
73 wants = [ "postgresql.service" ];
74
75 environment.NODE_CONFIG_DIR = "${cfg.dataDir}/config";
76 environment.NODE_ENV = "production";
77 environment.HOME = cfg.package;
78
79 path = [ pkgs.nodejs pkgs.bashInteractive pkgs.ffmpeg pkgs.openssl ];
80
81 script = ''
82 install -m 0750 -d ${cfg.dataDir}/config
83 ln -sf ${cfg.configFile} ${cfg.dataDir}/config/production.yaml
84 exec npm run start
85 '';
86
87 serviceConfig = {
88 User = cfg.user;
89 Group = cfg.group;
90 WorkingDirectory = cfg.package;
91 StateDirectory = cfg.systemdStateDirectory;
92 StateDirectoryMode = 0750;
93 PrivateTmp = true;
94 ProtectHome = true;
95 ProtectControlGroups = true;
96 Restart = "always";
97 Type = "simple";
98 TimeoutSec = 60;
99 };
100
101 unitConfig.RequiresMountsFor = cfg.dataDir;
102 };
103 };
104}
105
diff --git a/modules/webapps/webstats/default.nix b/modules/webapps/webstats/default.nix
new file mode 100644
index 00000000..924d72de
--- /dev/null
+++ b/modules/webapps/webstats/default.nix
@@ -0,0 +1,81 @@
1{ lib, pkgs, config, ... }:
2let
3 name = "goaccess";
4 cfg = config.services.webstats;
5in {
6 options.services.webstats = {
7 dataDir = lib.mkOption {
8 type = lib.types.path;
9 default = "/var/lib/${name}";
10 description = ''
11 The directory where Goaccess stores its data.
12 '';
13 };
14 sites = lib.mkOption {
15 type = lib.types.listOf (lib.types.submodule {
16 options = {
17 conf = lib.mkOption {
18 type = lib.types.nullOr lib.types.path;
19 default = null;
20 description = ''
21 use custom goaccess configuration file instead of the
22 default one.
23 '';
24 };
25 name = lib.mkOption {
26 type = lib.types.string;
27 description = ''
28 Domain name. Corresponds to the Apache file name and the
29 folder name in which the state will be saved.
30 '';
31 };
32 };
33 });
34 default = [];
35 description = "Sites to generate stats";
36 };
37 };
38
39 config = lib.mkIf (builtins.length cfg.sites > 0) {
40 users.users.root.packages = [
41 pkgs.goaccess
42 ];
43
44 services.cron = {
45 enable = true;
46 systemCronJobs = let
47 stats = domain: conf: let
48 config = if builtins.isNull conf
49 then pkgs.runCommand "goaccess.conf" {
50 dbPath = "${cfg.dataDir}/${domain}";
51 } "substituteAll ${./goaccess.conf} $out"
52 else conf;
53 d = pkgs.writeScriptBin "stats-${domain}" ''
54 #!${pkgs.stdenv.shell}
55 set -e
56 shopt -s nullglob
57 date_regex=$(LC_ALL=C date -d yesterday +'%d\/%b\/%Y')
58 TMPFILE=$(mktemp)
59 trap "rm -f $TMPFILE" EXIT
60
61 mkdir -p ${cfg.dataDir}/${domain}
62 cat /var/log/httpd/access-${domain}.log | sed -n "/\\[$date_regex/ p" > $TMPFILE
63 for i in /var/log/httpd/access-${domain}*.gz; do
64 zcat "$i" | sed -n "/\\[$date_regex/ p" >> $TMPFILE
65 done
66 ${pkgs.goaccess}/bin/goaccess $TMPFILE --no-progress -o ${cfg.dataDir}/${domain}/index.html -p ${config}
67 '';
68 in "${d}/bin/stats-${domain}";
69 allStats = sites: pkgs.writeScript "stats" ''
70 #!${pkgs.stdenv.shell}
71
72 mkdir -p ${cfg.dataDir}
73 ${builtins.concatStringsSep "\n" (map (v: stats v.name v.conf) sites)}
74 '';
75 in
76 [
77 "5 0 * * * root ${allStats cfg.sites}"
78 ];
79 };
80 };
81}
diff --git a/modules/webapps/webstats/goaccess.conf b/modules/webapps/webstats/goaccess.conf
new file mode 100644
index 00000000..49189883
--- /dev/null
+++ b/modules/webapps/webstats/goaccess.conf
@@ -0,0 +1,99 @@
1time-format %H:%M:%S
2date-format %d/%b/%Y
3
4#sur immae.eu
5#log-format %v %h %^[%d:%t %^] "%r" %s %b "%R" "%u" $^
6
7log-format VCOMBINED
8#= %v:%^ %h %^[%d:%t %^] "%r" %s %b "%R" "%u"
9
10html-prefs {"theme":"bright","layout":"vertical"}
11
12exclude-ip 188.165.209.148
13exclude-ip 178.33.252.96
14exclude-ip 2001:41d0:2:9c94::1
15exclude-ip 2001:41d0:2:9c94::
16exclude-ip 176.9.151.89
17exclude-ip 2a01:4f8:160:3445::
18exclude-ip 82.255.56.72
19
20no-query-string true
21
22keep-db-files true
23load-from-disk true
24db-path @dbPath@
25
26ignore-panel REFERRERS
27ignore-panel KEYPHRASES
28
29static-file .css
30static-file .js
31static-file .jpg
32static-file .png
33static-file .gif
34static-file .ico
35static-file .jpeg
36static-file .pdf
37static-file .csv
38static-file .mpeg
39static-file .mpg
40static-file .swf
41static-file .woff
42static-file .woff2
43static-file .xls
44static-file .xlsx
45static-file .doc
46static-file .docx
47static-file .ppt
48static-file .pptx
49static-file .txt
50static-file .zip
51static-file .ogg
52static-file .mp3
53static-file .mp4
54static-file .exe
55static-file .iso
56static-file .gz
57static-file .rar
58static-file .svg
59static-file .bmp
60static-file .tar
61static-file .tgz
62static-file .tiff
63static-file .tif
64static-file .ttf
65static-file .flv
66#static-file .less
67#static-file .ac3
68#static-file .avi
69#static-file .bz2
70#static-file .class
71#static-file .cue
72#static-file .dae
73#static-file .dat
74#static-file .dts
75#static-file .ejs
76#static-file .eot
77#static-file .eps
78#static-file .img
79#static-file .jar
80#static-file .map
81#static-file .mid
82#static-file .midi
83#static-file .ogv
84#static-file .webm
85#static-file .mkv
86#static-file .odp
87#static-file .ods
88#static-file .odt
89#static-file .otf
90#static-file .pict
91#static-file .pls
92#static-file .ps
93#static-file .qt
94#static-file .rm
95#static-file .svgz
96#static-file .wav
97#static-file .webp
98
99
diff --git a/modules/websites/default.nix b/modules/websites/default.nix
new file mode 100644
index 00000000..e57f505a
--- /dev/null
+++ b/modules/websites/default.nix
@@ -0,0 +1,199 @@
1{ lib, config, ... }: with lib;
2let
3 cfg = config.services.websites;
4in
5{
6 options.services.websitesCerts = mkOption {
7 description = "Default websites configuration for certificates as accepted by acme";
8 };
9 options.services.websites = with types; mkOption {
10 default = {};
11 description = "Each type of website to enable will target a distinct httpd server";
12 type = attrsOf (submodule {
13 options = {
14 enable = mkEnableOption "Enable websites of this type";
15 adminAddr = mkOption {
16 type = str;
17 description = "Admin e-mail address of the instance";
18 };
19 httpdName = mkOption {
20 type = str;
21 description = "Name of the httpd instance to assign this type to";
22 };
23 ips = mkOption {
24 type = listOf string;
25 default = [];
26 description = "ips to listen to";
27 };
28 modules = mkOption {
29 type = listOf str;
30 default = [];
31 description = "Additional modules to load in Apache";
32 };
33 extraConfig = mkOption {
34 type = listOf lines;
35 default = [];
36 description = "Additional configuration to append to Apache";
37 };
38 nosslVhost = mkOption {
39 description = "A default nossl vhost for captive portals";
40 default = {};
41 type = submodule {
42 options = {
43 enable = mkEnableOption "Add default no-ssl vhost for this instance";
44 host = mkOption {
45 type = string;
46 description = "The hostname to use for this vhost";
47 };
48 root = mkOption {
49 type = path;
50 default = ./nosslVhost;
51 description = "The root folder to serve";
52 };
53 indexFile = mkOption {
54 type = string;
55 default = "index.html";
56 description = "The index file to show.";
57 };
58 };
59 };
60 };
61 fallbackVhost = mkOption {
62 description = "The fallback vhost that will be defined as first vhost in Apache";
63 type = submodule {
64 options = {
65 certName = mkOption { type = string; };
66 hosts = mkOption { type = listOf string; };
67 root = mkOption { type = nullOr path; };
68 extraConfig = mkOption { type = listOf lines; default = []; };
69 };
70 };
71 };
72 vhostConfs = mkOption {
73 default = {};
74 description = "List of vhosts to define for Apache";
75 type = attrsOf (submodule {
76 options = {
77 certName = mkOption { type = string; };
78 addToCerts = mkOption {
79 type = bool;
80 default = false;
81 description = "Use these to certificates. Is ignored (considered true) if certMainHost is not null";
82 };
83 certMainHost = mkOption {
84 type = nullOr string;
85 description = "Use that host as 'main host' for acme certs";
86 default = null;
87 };
88 hosts = mkOption { type = listOf string; };
89 root = mkOption { type = nullOr path; };
90 extraConfig = mkOption { type = listOf lines; default = []; };
91 };
92 });
93 };
94 };
95 });
96 };
97
98 config.services.httpd = let
99 redirectVhost = ips: { # Should go last, catchall http -> https redirect
100 listen = map (ip: { inherit ip; port = 80; }) ips;
101 hostName = "redirectSSL";
102 serverAliases = [ "*" ];
103 enableSSL = false;
104 logFormat = "combinedVhost";
105 documentRoot = "${config.security.acme.directory}/acme-challenge";
106 extraConfig = ''
107 RewriteEngine on
108 RewriteCond "%{REQUEST_URI}" "!^/\.well-known"
109 RewriteRule ^(.+) https://%{HTTP_HOST}$1 [R=301]
110 # To redirect in specific "VirtualHost *:80", do
111 # RedirectMatch 301 ^/((?!\.well-known.*$).*)$ https://host/$1
112 # rather than rewrite
113 '';
114 };
115 nosslVhost = ips: cfg: {
116 listen = map (ip: { inherit ip; port = 80; }) ips;
117 hostName = cfg.host;
118 enableSSL = false;
119 logFormat = "combinedVhost";
120 documentRoot = cfg.root;
121 extraConfig = ''
122 <Directory ${cfg.root}>
123 DirectoryIndex ${cfg.indexFile}
124 AllowOverride None
125 Require all granted
126
127 RewriteEngine on
128 RewriteRule ^/(.+) / [L]
129 </Directory>
130 '';
131 };
132 toVhost = ips: vhostConf: {
133 enableSSL = true;
134 sslServerCert = "${config.security.acme.directory}/${vhostConf.certName}/cert.pem";
135 sslServerKey = "${config.security.acme.directory}/${vhostConf.certName}/key.pem";
136 sslServerChain = "${config.security.acme.directory}/${vhostConf.certName}/chain.pem";
137 logFormat = "combinedVhost";
138 listen = map (ip: { inherit ip; port = 443; }) ips;
139 hostName = builtins.head vhostConf.hosts;
140 serverAliases = builtins.tail vhostConf.hosts or [];
141 documentRoot = vhostConf.root;
142 extraConfig = builtins.concatStringsSep "\n" vhostConf.extraConfig;
143 };
144 in attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair
145 icfg.httpdName (mkIf icfg.enable {
146 enable = true;
147 listen = map (ip: { inherit ip; port = 443; }) icfg.ips;
148 stateDir = "/run/httpd_${name}";
149 logPerVirtualHost = true;
150 multiProcessingModule = "worker";
151 inherit (icfg) adminAddr;
152 logFormat = "combinedVhost";
153 extraModules = lists.unique icfg.modules;
154 extraConfig = builtins.concatStringsSep "\n" icfg.extraConfig;
155 virtualHosts = [ (toVhost icfg.ips icfg.fallbackVhost) ]
156 ++ optionals (icfg.nosslVhost.enable) [ (nosslVhost icfg.ips icfg.nosslVhost) ]
157 ++ (attrsets.mapAttrsToList (n: v: toVhost icfg.ips v) icfg.vhostConfs)
158 ++ [ (redirectVhost icfg.ips) ];
159 })
160 ) cfg;
161
162 config.security.acme.certs = let
163 typesToManage = attrsets.filterAttrs (k: v: v.enable) cfg;
164 flatVhosts = lists.flatten (attrsets.mapAttrsToList (k: v:
165 attrValues v.vhostConfs
166 ) typesToManage);
167 groupedCerts = attrsets.filterAttrs
168 (_: group: builtins.any (v: v.addToCerts || !isNull v.certMainHost) group)
169 (lists.groupBy (v: v.certName) flatVhosts);
170 groupToDomain = group:
171 let
172 nonNull = builtins.filter (v: !isNull v.certMainHost) group;
173 domains = lists.unique (map (v: v.certMainHost) nonNull);
174 in
175 if builtins.length domains == 0
176 then null
177 else assert (builtins.length domains == 1); (elemAt domains 0);
178 extraDomains = group:
179 let
180 mainDomain = groupToDomain group;
181 in
182 lists.remove mainDomain (
183 lists.unique (
184 lists.flatten (map (c: optionals (c.addToCerts || !isNull c.certMainHost) c.hosts) group)
185 )
186 );
187 in attrsets.mapAttrs (k: g:
188 if (!isNull (groupToDomain g))
189 then config.services.websitesCerts // {
190 domain = groupToDomain g;
191 extraDomains = builtins.listToAttrs (
192 map (d: attrsets.nameValuePair d null) (extraDomains g));
193 }
194 else {
195 extraDomains = builtins.listToAttrs (
196 map (d: attrsets.nameValuePair d null) (extraDomains g));
197 }
198 ) groupedCerts;
199}
diff --git a/modules/websites/httpd-service-builder.nix b/modules/websites/httpd-service-builder.nix
new file mode 100644
index 00000000..d049202c
--- /dev/null
+++ b/modules/websites/httpd-service-builder.nix
@@ -0,0 +1,746 @@
1# to help backporting this builder should stay as close as possible to
2# nixos/modules/services/web-servers/apache-httpd/default.nix
3{ httpdName, withUsers ? true }:
4{ config, lib, pkgs, ... }:
5
6with lib;
7
8let
9
10 mainCfg = config.services.httpd."${httpdName}";
11
12 httpd = mainCfg.package.out;
13
14 version24 = !versionOlder httpd.version "2.4";
15
16 httpdConf = mainCfg.configFile;
17
18 php = mainCfg.phpPackage.override { apacheHttpd = httpd.dev; /* otherwise it only gets .out */ };
19
20 phpMajorVersion = head (splitString "." php.version);
21
22 mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = httpd; };
23
24 defaultListen = cfg: if cfg.enableSSL
25 then [{ip = "*"; port = 443;}]
26 else [{ip = "*"; port = 80;}];
27
28 getListen = cfg:
29 let list = (lib.optional (cfg.port != 0) {ip = "*"; port = cfg.port;}) ++ cfg.listen;
30 in if list == []
31 then defaultListen cfg
32 else list;
33
34 listenToString = l: "${l.ip}:${toString l.port}";
35
36 extraModules = attrByPath ["extraModules"] [] mainCfg;
37 extraForeignModules = filter isAttrs extraModules;
38 extraApacheModules = filter isString extraModules;
39
40
41 makeServerInfo = cfg: {
42 # Canonical name must not include a trailing slash.
43 canonicalNames =
44 let defaultPort = (head (defaultListen cfg)).port; in
45 map (port:
46 (if cfg.enableSSL then "https" else "http") + "://" +
47 cfg.hostName +
48 (if port != defaultPort then ":${toString port}" else "")
49 ) (map (x: x.port) (getListen cfg));
50
51 # Admin address: inherit from the main server if not specified for
52 # a virtual host.
53 adminAddr = if cfg.adminAddr != null then cfg.adminAddr else mainCfg.adminAddr;
54
55 vhostConfig = cfg;
56 serverConfig = mainCfg;
57 fullConfig = config; # machine config
58 };
59
60
61 allHosts = [mainCfg] ++ mainCfg.virtualHosts;
62
63
64 callSubservices = serverInfo: defs:
65 let f = svc:
66 let
67 svcFunction =
68 if svc ? function then svc.function
69 # instead of using serviceType="mediawiki"; you can copy mediawiki.nix to any location outside nixpkgs, modify it at will, and use serviceExpression=./mediawiki.nix;
70 else if svc ? serviceExpression then import (toString svc.serviceExpression)
71 else import (toString "${toString ./.}/${if svc ? serviceType then svc.serviceType else svc.serviceName}.nix");
72 config = (evalModules
73 { modules = [ { options = res.options; config = svc.config or svc; } ];
74 check = false;
75 }).config;
76 defaults = {
77 extraConfig = "";
78 extraModules = [];
79 extraModulesPre = [];
80 extraPath = [];
81 extraServerPath = [];
82 globalEnvVars = [];
83 robotsEntries = "";
84 startupScript = "";
85 enablePHP = false;
86 enablePerl = false;
87 phpOptions = "";
88 options = {};
89 documentRoot = null;
90 };
91 res = defaults // svcFunction { inherit config lib pkgs serverInfo php; };
92 in res;
93 in map f defs;
94
95
96 # !!! callSubservices is expensive
97 subservicesFor = cfg: callSubservices (makeServerInfo cfg) cfg.extraSubservices;
98
99 mainSubservices = subservicesFor mainCfg;
100
101 allSubservices = mainSubservices ++ concatMap subservicesFor mainCfg.virtualHosts;
102
103
104 enableSSL = any (vhost: vhost.enableSSL) allHosts;
105
106
107 # Names of modules from ${httpd}/modules that we want to load.
108 apacheModules =
109 [ # HTTP authentication mechanisms: basic and digest.
110 "auth_basic" "auth_digest"
111
112 # Authentication: is the user who he claims to be?
113 "authn_file" "authn_dbm" "authn_anon"
114 (if version24 then "authn_core" else "authn_alias")
115
116 # Authorization: is the user allowed access?
117 "authz_user" "authz_groupfile" "authz_host"
118
119 # Other modules.
120 "ext_filter" "include" "log_config" "env" "mime_magic"
121 "cern_meta" "expires" "headers" "usertrack" /* "unique_id" */ "setenvif"
122 "mime" "dav" "status" "autoindex" "asis" "info" "dav_fs"
123 "vhost_alias" "negotiation" "dir" "imagemap" "actions" "speling"
124 "userdir" "alias" "rewrite" "proxy" "proxy_http"
125 ]
126 ++ optionals version24 [
127 "mpm_${mainCfg.multiProcessingModule}"
128 "authz_core"
129 "unixd"
130 "cache" "cache_disk"
131 "slotmem_shm"
132 "socache_shmcb"
133 # For compatibility with old configurations, the new module mod_access_compat is provided.
134 "access_compat"
135 ]
136 ++ (if mainCfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ])
137 ++ optional enableSSL "ssl"
138 ++ extraApacheModules;
139
140
141 allDenied = if version24 then ''
142 Require all denied
143 '' else ''
144 Order deny,allow
145 Deny from all
146 '';
147
148 allGranted = if version24 then ''
149 Require all granted
150 '' else ''
151 Order allow,deny
152 Allow from all
153 '';
154
155
156 loggingConf = (if mainCfg.logFormat != "none" then ''
157 ErrorLog ${mainCfg.logDir}/error.log
158
159 LogLevel notice
160
161 LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
162 LogFormat "%h %l %u %t \"%r\" %>s %b" common
163 LogFormat "%{Referer}i -> %U" referer
164 LogFormat "%{User-agent}i" agent
165
166 CustomLog ${mainCfg.logDir}/access.log ${mainCfg.logFormat}
167 '' else ''
168 ErrorLog /dev/null
169 '');
170
171
172 browserHacks = ''
173 BrowserMatch "Mozilla/2" nokeepalive
174 BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
175 BrowserMatch "RealPlayer 4\.0" force-response-1.0
176 BrowserMatch "Java/1\.0" force-response-1.0
177 BrowserMatch "JDK/1\.0" force-response-1.0
178 BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully
179 BrowserMatch "^WebDrive" redirect-carefully
180 BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully
181 BrowserMatch "^gnome-vfs" redirect-carefully
182 '';
183
184
185 sslConf = ''
186 SSLSessionCache ${if version24 then "shmcb" else "shm"}:${mainCfg.stateDir}/ssl_scache(512000)
187
188 ${if version24 then "Mutex" else "SSLMutex"} posixsem
189
190 SSLRandomSeed startup builtin
191 SSLRandomSeed connect builtin
192
193 SSLProtocol ${mainCfg.sslProtocols}
194 SSLCipherSuite ${mainCfg.sslCiphers}
195 SSLHonorCipherOrder on
196 '';
197
198
199 mimeConf = ''
200 TypesConfig ${httpd}/conf/mime.types
201
202 AddType application/x-x509-ca-cert .crt
203 AddType application/x-pkcs7-crl .crl
204 AddType application/x-httpd-php .php .phtml
205
206 <IfModule mod_mime_magic.c>
207 MIMEMagicFile ${httpd}/conf/magic
208 </IfModule>
209 '';
210
211
212 perServerConf = isMainServer: cfg: let
213
214 serverInfo = makeServerInfo cfg;
215
216 subservices = callSubservices serverInfo cfg.extraSubservices;
217
218 maybeDocumentRoot = fold (svc: acc:
219 if acc == null then svc.documentRoot else assert svc.documentRoot == null; acc
220 ) null ([ cfg ] ++ subservices);
221
222 documentRoot = if maybeDocumentRoot != null then maybeDocumentRoot else
223 pkgs.runCommand "empty" { preferLocalBuild = true; } "mkdir -p $out";
224
225 documentRootConf = ''
226 DocumentRoot "${documentRoot}"
227
228 <Directory "${documentRoot}">
229 Options Indexes FollowSymLinks
230 AllowOverride None
231 ${allGranted}
232 </Directory>
233 '';
234
235 robotsTxt =
236 concatStringsSep "\n" (filter (x: x != "") (
237 # If this is a vhost, the include the entries for the main server as well.
238 (if isMainServer then [] else [mainCfg.robotsEntries] ++ map (svc: svc.robotsEntries) mainSubservices)
239 ++ [cfg.robotsEntries]
240 ++ (map (svc: svc.robotsEntries) subservices)));
241
242 in ''
243 ${concatStringsSep "\n" (map (n: "ServerName ${n}") serverInfo.canonicalNames)}
244
245 ${concatMapStrings (alias: "ServerAlias ${alias}\n") cfg.serverAliases}
246
247 ${if cfg.sslServerCert != null then ''
248 SSLCertificateFile ${cfg.sslServerCert}
249 SSLCertificateKeyFile ${cfg.sslServerKey}
250 ${if cfg.sslServerChain != null then ''
251 SSLCertificateChainFile ${cfg.sslServerChain}
252 '' else ""}
253 '' else ""}
254
255 ${if cfg.enableSSL then ''
256 SSLEngine on
257 '' else if enableSSL then /* i.e., SSL is enabled for some host, but not this one */
258 ''
259 SSLEngine off
260 '' else ""}
261
262 ${if isMainServer || cfg.adminAddr != null then ''
263 ServerAdmin ${cfg.adminAddr}
264 '' else ""}
265
266 ${if !isMainServer && mainCfg.logPerVirtualHost then ''
267 ErrorLog ${mainCfg.logDir}/error-${cfg.hostName}.log
268 CustomLog ${mainCfg.logDir}/access-${cfg.hostName}.log ${cfg.logFormat}
269 '' else ""}
270
271 ${optionalString (robotsTxt != "") ''
272 Alias /robots.txt ${pkgs.writeText "robots.txt" robotsTxt}
273 ''}
274
275 ${if isMainServer || maybeDocumentRoot != null then documentRootConf else ""}
276
277 ${if cfg.enableUserDir then ''
278
279 UserDir public_html
280 UserDir disabled root
281
282 <Directory "/home/*/public_html">
283 AllowOverride FileInfo AuthConfig Limit Indexes
284 Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
285 <Limit GET POST OPTIONS>
286 ${allGranted}
287 </Limit>
288 <LimitExcept GET POST OPTIONS>
289 ${allDenied}
290 </LimitExcept>
291 </Directory>
292
293 '' else ""}
294
295 ${if cfg.globalRedirect != null && cfg.globalRedirect != "" then ''
296 RedirectPermanent / ${cfg.globalRedirect}
297 '' else ""}
298
299 ${
300 let makeFileConf = elem: ''
301 Alias ${elem.urlPath} ${elem.file}
302 '';
303 in concatMapStrings makeFileConf cfg.servedFiles
304 }
305
306 ${
307 let makeDirConf = elem: ''
308 Alias ${elem.urlPath} ${elem.dir}/
309 <Directory ${elem.dir}>
310 Options +Indexes
311 ${allGranted}
312 AllowOverride All
313 </Directory>
314 '';
315 in concatMapStrings makeDirConf cfg.servedDirs
316 }
317
318 ${concatMapStrings (svc: svc.extraConfig) subservices}
319
320 ${cfg.extraConfig}
321 '';
322
323
324 confFile = pkgs.writeText "httpd.conf" ''
325
326 ServerRoot ${httpd}
327
328 ${optionalString version24 ''
329 DefaultRuntimeDir ${mainCfg.stateDir}/runtime
330 ''}
331
332 PidFile ${mainCfg.stateDir}/httpd.pid
333
334 ${optionalString (mainCfg.multiProcessingModule != "prefork") ''
335 # mod_cgid requires this.
336 ScriptSock ${mainCfg.stateDir}/cgisock
337 ''}
338
339 <IfModule prefork.c>
340 MaxClients ${toString mainCfg.maxClients}
341 MaxRequestsPerChild ${toString mainCfg.maxRequestsPerChild}
342 </IfModule>
343
344 ${let
345 listen = concatMap getListen allHosts;
346 toStr = listen: "Listen ${listenToString listen}\n";
347 uniqueListen = uniqList {inputList = map toStr listen;};
348 in concatStrings uniqueListen
349 }
350
351 User ${mainCfg.user}
352 Group ${mainCfg.group}
353
354 ${let
355 load = {name, path}: "LoadModule ${name}_module ${path}\n";
356 allModules =
357 concatMap (svc: svc.extraModulesPre) allSubservices
358 ++ map (name: {inherit name; path = "${httpd}/modules/mod_${name}.so";}) apacheModules
359 ++ optional mainCfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
360 ++ optional enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; }
361 ++ optional enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
362 ++ concatMap (svc: svc.extraModules) allSubservices
363 ++ extraForeignModules;
364 in concatMapStrings load allModules
365 }
366
367 AddHandler type-map var
368
369 <Files ~ "^\.ht">
370 ${allDenied}
371 </Files>
372
373 ${mimeConf}
374 ${loggingConf}
375 ${browserHacks}
376
377 Include ${httpd}/conf/extra/httpd-default.conf
378 Include ${httpd}/conf/extra/httpd-autoindex.conf
379 Include ${httpd}/conf/extra/httpd-multilang-errordoc.conf
380 Include ${httpd}/conf/extra/httpd-languages.conf
381
382 TraceEnable off
383
384 ${if enableSSL then sslConf else ""}
385
386 # Fascist default - deny access to everything.
387 <Directory />
388 Options FollowSymLinks
389 AllowOverride None
390 ${allDenied}
391 </Directory>
392
393 # Generate directives for the main server.
394 ${perServerConf true mainCfg}
395
396 # Always enable virtual hosts; it doesn't seem to hurt.
397 ${let
398 listen = concatMap getListen allHosts;
399 uniqueListen = uniqList {inputList = listen;};
400 directives = concatMapStrings (listen: "NameVirtualHost ${listenToString listen}\n") uniqueListen;
401 in optionalString (!version24) directives
402 }
403
404 ${let
405 makeVirtualHost = vhost: ''
406 <VirtualHost ${concatStringsSep " " (map listenToString (getListen vhost))}>
407 ${perServerConf false vhost}
408 </VirtualHost>
409 '';
410 in concatMapStrings makeVirtualHost mainCfg.virtualHosts
411 }
412 '';
413
414
415 enablePHP = mainCfg.enablePHP || any (svc: svc.enablePHP) allSubservices;
416
417 enablePerl = mainCfg.enablePerl || any (svc: svc.enablePerl) allSubservices;
418
419
420 # Generate the PHP configuration file. Should probably be factored
421 # out into a separate module.
422 phpIni = pkgs.runCommand "php.ini"
423 { options = concatStringsSep "\n"
424 ([ mainCfg.phpOptions ] ++ (map (svc: svc.phpOptions) allSubservices));
425 preferLocalBuild = true;
426 }
427 ''
428 cat ${php}/etc/php.ini > $out
429 echo "$options" >> $out
430 '';
431
432in
433
434
435{
436
437 ###### interface
438
439 options = {
440
441 services.httpd."${httpdName}" = {
442
443 enable = mkOption {
444 type = types.bool;
445 default = false;
446 description = "Whether to enable the Apache HTTP Server.";
447 };
448
449 package = mkOption {
450 type = types.package;
451 default = pkgs.apacheHttpd;
452 defaultText = "pkgs.apacheHttpd";
453 description = ''
454 Overridable attribute of the Apache HTTP Server package to use.
455 '';
456 };
457
458 configFile = mkOption {
459 type = types.path;
460 default = confFile;
461 defaultText = "confFile";
462 example = literalExample ''pkgs.writeText "httpd.conf" "# my custom config file ..."'';
463 description = ''
464 Override the configuration file used by Apache. By default,
465 NixOS generates one automatically.
466 '';
467 };
468
469 extraConfig = mkOption {
470 type = types.lines;
471 default = "";
472 description = ''
473 Cnfiguration lines appended to the generated Apache
474 configuration file. Note that this mechanism may not work
475 when <option>configFile</option> is overridden.
476 '';
477 };
478
479 extraModules = mkOption {
480 type = types.listOf types.unspecified;
481 default = [];
482 example = literalExample ''[ "proxy_connect" { name = "php5"; path = "''${pkgs.php}/modules/libphp5.so"; } ]'';
483 description = ''
484 Additional Apache modules to be used. These can be
485 specified as a string in the case of modules distributed
486 with Apache, or as an attribute set specifying the
487 <varname>name</varname> and <varname>path</varname> of the
488 module.
489 '';
490 };
491
492 logPerVirtualHost = mkOption {
493 type = types.bool;
494 default = false;
495 description = ''
496 If enabled, each virtual host gets its own
497 <filename>access.log</filename> and
498 <filename>error.log</filename>, namely suffixed by the
499 <option>hostName</option> of the virtual host.
500 '';
501 };
502
503 user = mkOption {
504 type = types.str;
505 default = "wwwrun";
506 description = ''
507 User account under which httpd runs. The account is created
508 automatically if it doesn't exist.
509 '';
510 };
511
512 group = mkOption {
513 type = types.str;
514 default = "wwwrun";
515 description = ''
516 Group under which httpd runs. The account is created
517 automatically if it doesn't exist.
518 '';
519 };
520
521 logDir = mkOption {
522 type = types.path;
523 default = "/var/log/httpd";
524 description = ''
525 Directory for Apache's log files. It is created automatically.
526 '';
527 };
528
529 stateDir = mkOption {
530 type = types.path;
531 default = "/run/httpd";
532 description = ''
533 Directory for Apache's transient runtime state (such as PID
534 files). It is created automatically. Note that the default,
535 <filename>/run/httpd</filename>, is deleted at boot time.
536 '';
537 };
538
539 virtualHosts = mkOption {
540 type = types.listOf (types.submodule (
541 { options = import <nixpkgs/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix> {
542 inherit lib;
543 forMainServer = false;
544 };
545 }));
546 default = [];
547 example = [
548 { hostName = "foo";
549 documentRoot = "/data/webroot-foo";
550 }
551 { hostName = "bar";
552 documentRoot = "/data/webroot-bar";
553 }
554 ];
555 description = ''
556 Specification of the virtual hosts served by Apache. Each
557 element should be an attribute set specifying the
558 configuration of the virtual host. The available options
559 are the non-global options permissible for the main host.
560 '';
561 };
562
563 enableMellon = mkOption {
564 type = types.bool;
565 default = false;
566 description = "Whether to enable the mod_auth_mellon module.";
567 };
568
569 enablePHP = mkOption {
570 type = types.bool;
571 default = false;
572 description = "Whether to enable the PHP module.";
573 };
574
575 phpPackage = mkOption {
576 type = types.package;
577 default = pkgs.php;
578 defaultText = "pkgs.php";
579 description = ''
580 Overridable attribute of the PHP package to use.
581 '';
582 };
583
584 enablePerl = mkOption {
585 type = types.bool;
586 default = false;
587 description = "Whether to enable the Perl module (mod_perl).";
588 };
589
590 phpOptions = mkOption {
591 type = types.lines;
592 default = "";
593 example =
594 ''
595 date.timezone = "CET"
596 '';
597 description =
598 "Options appended to the PHP configuration file <filename>php.ini</filename>.";
599 };
600
601 multiProcessingModule = mkOption {
602 type = types.str;
603 default = "prefork";
604 example = "worker";
605 description =
606 ''
607 Multi-processing module to be used by Apache. Available
608 modules are <literal>prefork</literal> (the default;
609 handles each request in a separate child process),
610 <literal>worker</literal> (hybrid approach that starts a
611 number of child processes each running a number of
612 threads) and <literal>event</literal> (a recent variant of
613 <literal>worker</literal> that handles persistent
614 connections more efficiently).
615 '';
616 };
617
618 maxClients = mkOption {
619 type = types.int;
620 default = 150;
621 example = 8;
622 description = "Maximum number of httpd processes (prefork)";
623 };
624
625 maxRequestsPerChild = mkOption {
626 type = types.int;
627 default = 0;
628 example = 500;
629 description =
630 "Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited";
631 };
632
633 sslCiphers = mkOption {
634 type = types.str;
635 default = "HIGH:!aNULL:!MD5:!EXP";
636 description = "Cipher Suite available for negotiation in SSL proxy handshake.";
637 };
638
639 sslProtocols = mkOption {
640 type = types.str;
641 default = "All -SSLv2 -SSLv3 -TLSv1";
642 example = "All -SSLv2 -SSLv3";
643 description = "Allowed SSL/TLS protocol versions.";
644 };
645 }
646
647 # Include the options shared between the main server and virtual hosts.
648 // (import <nixpkgs/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix> {
649 inherit lib;
650 forMainServer = true;
651 });
652
653 };
654
655
656 ###### implementation
657
658 config = mkIf config.services.httpd."${httpdName}".enable {
659
660 assertions = [ { assertion = mainCfg.enableSSL == true
661 -> mainCfg.sslServerCert != null
662 && mainCfg.sslServerKey != null;
663 message = "SSL is enabled for httpd, but sslServerCert and/or sslServerKey haven't been specified."; }
664 ];
665
666 warnings = map (cfg: ''apache-httpd's port option is deprecated. Use listen = [{/*ip = "*"; */ port = ${toString cfg.port};}]; instead'' ) (lib.filter (cfg: cfg.port != 0) allHosts);
667
668 users.users = optionalAttrs (withUsers && mainCfg.user == "wwwrun") (singleton
669 { name = "wwwrun";
670 group = mainCfg.group;
671 description = "Apache httpd user";
672 uid = config.ids.uids.wwwrun;
673 });
674
675 users.groups = optionalAttrs (withUsers && mainCfg.group == "wwwrun") (singleton
676 { name = "wwwrun";
677 gid = config.ids.gids.wwwrun;
678 });
679
680 environment.systemPackages = [httpd] ++ concatMap (svc: svc.extraPath) allSubservices;
681
682 services.httpd."${httpdName}".phpOptions =
683 ''
684 ; Needed for PHP's mail() function.
685 sendmail_path = sendmail -t -i
686
687 ; Don't advertise PHP
688 expose_php = off
689 '' + optionalString (!isNull config.time.timeZone) ''
690
691 ; Apparently PHP doesn't use $TZ.
692 date.timezone = "${config.time.timeZone}"
693 '';
694
695 systemd.services."httpd${httpdName}" =
696 { description = "Apache HTTPD";
697
698 wantedBy = [ "multi-user.target" ];
699 wants = [ "keys.target" ];
700 after = [ "network.target" "fs.target" "postgresql.service" "keys.target" ];
701
702 path =
703 [ httpd pkgs.coreutils pkgs.gnugrep ]
704 ++ optional enablePHP pkgs.system-sendmail # Needed for PHP's mail() function.
705 ++ concatMap (svc: svc.extraServerPath) allSubservices;
706
707 environment =
708 optionalAttrs enablePHP { PHPRC = phpIni; }
709 // optionalAttrs mainCfg.enableMellon { LD_LIBRARY_PATH = "${pkgs.xmlsec}/lib"; }
710 // (listToAttrs (concatMap (svc: svc.globalEnvVars) allSubservices));
711
712 preStart =
713 ''
714 mkdir -m 0750 -p ${mainCfg.stateDir}
715 [ $(id -u) != 0 ] || chown root.${mainCfg.group} ${mainCfg.stateDir}
716 ${optionalString version24 ''
717 mkdir -m 0750 -p "${mainCfg.stateDir}/runtime"
718 [ $(id -u) != 0 ] || chown root.${mainCfg.group} "${mainCfg.stateDir}/runtime"
719 ''}
720 mkdir -m 0700 -p ${mainCfg.logDir}
721
722 # Get rid of old semaphores. These tend to accumulate across
723 # server restarts, eventually preventing it from restarting
724 # successfully.
725 for i in $(${pkgs.utillinux}/bin/ipcs -s | grep ' ${mainCfg.user} ' | cut -f2 -d ' '); do
726 ${pkgs.utillinux}/bin/ipcrm -s $i
727 done
728
729 # Run the startup hooks for the subservices.
730 for i in ${toString (map (svn: svn.startupScript) allSubservices)}; do
731 echo Running Apache startup hook $i...
732 $i
733 done
734 '';
735
736 serviceConfig.ExecStart = "@${httpd}/bin/httpd httpd -f ${httpdConf}";
737 serviceConfig.ExecStop = "${httpd}/bin/httpd -f ${httpdConf} -k graceful-stop";
738 serviceConfig.ExecReload = "${httpd}/bin/httpd -f ${httpdConf} -k graceful";
739 serviceConfig.Type = "forking";
740 serviceConfig.PIDFile = "${mainCfg.stateDir}/httpd.pid";
741 serviceConfig.Restart = "always";
742 serviceConfig.RestartSec = "5s";
743 };
744
745 };
746}
diff --git a/modules/websites/nosslVhost/index.html b/modules/websites/nosslVhost/index.html
new file mode 100644
index 00000000..4401a806
--- /dev/null
+++ b/modules/websites/nosslVhost/index.html
@@ -0,0 +1,11 @@
1<!DOCTYPE html>
2<html>
3 <head>
4 <title>No SSL site</title>
5 </head>
6 <body>
7 <h1>No SSL on this site</h1>
8 <p>Use for wifi networks with login page that doesn't work well with
9 https.</p>
10 </body>
11</html>