diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2018-12-13 21:25:24 +0100 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2019-05-24 01:40:13 +0200 |
commit | 252dd7d899b7a0deea1537cc5d2d48b825afffb0 (patch) | |
tree | f51c3c9cd7429b0b9553a840f26bee489be045bc /modules | |
download | NUR-252dd7d899b7a0deea1537cc5d2d48b825afffb0.tar.gz NUR-252dd7d899b7a0deea1537cc5d2d48b825afffb0.tar.zst NUR-252dd7d899b7a0deea1537cc5d2d48b825afffb0.zip |
Initial commit published for NURnur_root
Diffstat (limited to 'modules')
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 @@ | |||
1 | from buildbot.plugins import util, steps, schedulers | ||
2 | from 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 | ||
12 | def clean_branch(props): | ||
13 | if props.hasProperty("branch") and len(props["branch"]) > 0: | ||
14 | return props["branch"].replace("/", "_") | ||
15 | else: | ||
16 | return "HEAD" | ||
17 | |||
18 | def 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 | ||
34 | def 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 | |||
50 | def 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 | |||
70 | def 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 | ||
76 | from buildbot.reporters.http import HttpStatusPushBase | ||
77 | from twisted.internet import defer | ||
78 | from twisted.python import log | ||
79 | from buildbot.util import httpclientservice | ||
80 | from buildbot.reporters import utils | ||
81 | from buildbot.process import results | ||
82 | from twisted.words.protocols.jabber.jid import JID | ||
83 | from wokkel import client, xmppim | ||
84 | from functools import partial | ||
85 | |||
86 | class 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 | |||
195 | class 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 | |||
4 | from buildbot.plugins import secrets, util, webhooks | ||
5 | from buildbot.util import bytes2unicode | ||
6 | import re | ||
7 | import os | ||
8 | from buildbot_config import E, configure | ||
9 | import json | ||
10 | |||
11 | class 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 | |||
25 | userInfoProvider = 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 | |||
39 | c = 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 | |||
69 | configure(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, ... }: | ||
2 | let | ||
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; | ||
14 | in | ||
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 @@ | |||
1 | from buildbot.plugins import * | ||
2 | from buildbot_common.build_helpers import * | ||
3 | import os | ||
4 | from buildbot.util import bytes2unicode | ||
5 | import json | ||
6 | |||
7 | __all__ = [ "configure", "E" ] | ||
8 | |||
9 | class 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 | |||
38 | class 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 | |||
65 | def 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 | |||
70 | def 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 | |||
99 | def 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 | |||
116 | def 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 | ||
132 | def puppet_host(props): | ||
133 | environment = props["environment"] if props.hasProperty("environment") else "integration" | ||
134 | return E.PUPPET_HOST.get(environment, "host.invalid") | ||
135 | |||
136 | def 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 | |||
149 | from twisted.internet import defer | ||
150 | from buildbot.process.buildstep import FAILURE | ||
151 | from buildbot.process.buildstep import SUCCESS | ||
152 | from buildbot.process.buildstep import BuildStep | ||
153 | |||
154 | class 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 @@ | |||
1 | from buildbot.plugins import * | ||
2 | from buildbot_common.build_helpers import * | ||
3 | import os | ||
4 | |||
5 | __all__ = [ "configure", "E" ] | ||
6 | |||
7 | class 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 | ||
44 | def 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 | |||
69 | def 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 | |||
95 | def 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 | ||
111 | def puppet_host(props): | ||
112 | environment = props["environment"] if props.hasProperty("environment") else "integration" | ||
113 | return E.PUPPET_HOST.get(environment, "host.invalid") | ||
114 | |||
115 | def 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 | |||
128 | from twisted.internet import defer | ||
129 | from buildbot.process.buildstep import FAILURE | ||
130 | from buildbot.process.buildstep import SUCCESS | ||
131 | from buildbot.process.buildstep import BuildStep | ||
132 | |||
133 | class 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 @@ | |||
1 | from buildbot.plugins import * | ||
2 | from buildbot_common.build_helpers import * | ||
3 | import os | ||
4 | from buildbot.util import bytes2unicode | ||
5 | import json | ||
6 | |||
7 | __all__ = [ "configure", "E" ] | ||
8 | |||
9 | class 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 | |||
34 | class 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 | |||
61 | def 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 | |||
66 | def 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 | |||
94 | def 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 | |||
115 | def 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 | ||
131 | def puppet_host(props): | ||
132 | return E.PUPPET_HOST | ||
133 | |||
134 | def 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 | |||
148 | from twisted.internet import defer | ||
149 | from buildbot.process.buildstep import FAILURE | ||
150 | from buildbot.process.buildstep import SUCCESS | ||
151 | from buildbot.process.buildstep import BuildStep | ||
152 | |||
153 | class 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, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.databases; | ||
4 | in | ||
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, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.databases.mariadb; | ||
4 | in { | ||
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, ... }: | ||
2 | let | ||
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 | ''; | ||
46 | in | ||
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: | ||
2 | objectIdentifier Immaeroot 1.3.6.1.4.1.50071 | ||
3 | |||
4 | objectIdentifier Immae Immaeroot:2 | ||
5 | objectIdentifier ImmaeattributeType Immae:3 | ||
6 | objectIdentifier ImmaeobjectClass Immae:4 | ||
7 | |||
8 | # TT-RSS | ||
9 | attributetype ( 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 | |||
15 | objectclass ( ImmaeobjectClass:1 NAME 'immaeTtrssClass' | ||
16 | DESC 'Expansion of the existing object classes for ttrss' | ||
17 | SUP top AUXILIARY | ||
18 | MUST ( immaeTtrssLogin ) ) | ||
19 | |||
20 | # FTP | ||
21 | attributetype ( 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 | |||
26 | attributetype ( 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 | |||
31 | attributetype ( 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 | |||
36 | objectclass ( 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 | ||
43 | attributetype ( 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 | |||
48 | objectClass ( ImmaeobjectClass:3 NAME 'immaeSshClass' | ||
49 | DESC 'OpenSSH class' | ||
50 | SUP top AUXILIARY | ||
51 | MAy ( immaeSSHKey ) ) | ||
52 | |||
53 | # Specific access | ||
54 | attributetype (ImmaeattributeType:6 NAME 'immaeAccessDn' | ||
55 | EQUALITY distinguishedNameMatch | ||
56 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 ) | ||
57 | |||
58 | attributetype (ImmaeattributeType:17 NAME 'immaeAccessWriteDn' | ||
59 | EQUALITY distinguishedNameMatch | ||
60 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 ) | ||
61 | |||
62 | attributetype (ImmaeattributeType:18 NAME 'immaeAccessReadSubtree' | ||
63 | EQUALITY distinguishedNameMatch | ||
64 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 ) | ||
65 | |||
66 | objectClass ( ImmaeobjectClass:4 NAME 'immaeAccessClass' | ||
67 | DESC 'Access class' | ||
68 | SUP top AUXILIARY | ||
69 | MAY ( immaeAccessDn $ immaeAccessWriteDn $ immaeAccessReadSubtree ) ) | ||
70 | |||
71 | # Xmpp uid | ||
72 | attributetype ( 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 | |||
78 | objectclass ( 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 | ||
84 | attributetype ( 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 | |||
90 | attributetype ( 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 | |||
95 | attributetype ( 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 | |||
100 | attributetype ( 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 | |||
105 | attributetype ( 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 | |||
110 | objectclass ( 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 | |||
141 | attributetype (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 | |||
146 | objectclass ( ImmaeobjectClass:8 NAME 'immaePuppetClass' | ||
147 | DESC 'Expansion of the existing object classes for Puppet' | ||
148 | SUP top AUXILIARY | ||
149 | MUST ( immaePuppetJson ) | ||
150 | ) | ||
151 | |||
152 | attributetype (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 | |||
158 | objectclass ( 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, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.databases.postgresql; | ||
4 | in { | ||
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, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.databases.redis; | ||
4 | in { | ||
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 @@ | |||
1 | let | ||
2 | set = { | ||
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 | }; | ||
64 | in | ||
65 | builtins.listToAttrs (map (attr: { name = "priv${attr}"; value = set.${attr}; }) (builtins.attrNames set)) | ||
diff --git a/modules/private/dns.nix b/modules/private/dns.nix new file mode 100644 index 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, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.gitolite; | ||
4 | in { | ||
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 | |||
3 | uid_param="$1" | ||
4 | ldap_host="ldap.immae.eu" | ||
5 | ldap_binddn="cn=gitolite,ou=services,dc=immae,dc=eu" | ||
6 | ldap_bindpw="$LDAP_PASS" | ||
7 | ldap_searchbase="dc=immae,dc=eu" | ||
8 | ldap_scope="subtree" | ||
9 | |||
10 | ldap_options="-h ${ldap_host} -ZZ -x -D ${ldap_binddn} -w ${ldap_bindpw} -b ${ldap_searchbase} -s ${ldap_scope}" | ||
11 | |||
12 | ldap_filter="(&(memberOf=cn=groups,cn=gitolite,ou=services,dc=immae,dc=eu)(|(member=uid=${uid_param},ou=users,dc=immae,dc=eu)(member=uid=${uid_param},ou=group_users,dc=immae,dc=eu)))" | ||
13 | ldap_result=$(ldapsearch ${ldap_options} -LLL "${ldap_filter}" cn | grep 'cn:' | cut -d' ' -f2) | ||
14 | |||
15 | echo "$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, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.irc; | ||
4 | in | ||
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 | ||
2 | user="$1" | ||
3 | rootuser="$HOME/$user/" | ||
4 | mkdir -p $rootuser | ||
5 | |||
6 | orig="$SSH_ORIGINAL_COMMAND" | ||
7 | if [ -z "$orig" ]; then | ||
8 | orig="/bin/bash -l" | ||
9 | fi | ||
10 | if [ "${orig:0:7}" = "command" ]; then | ||
11 | orig="${orig:8}" | ||
12 | fi | ||
13 | |||
14 | case "$orig" in | ||
15 | rsync*) | ||
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 | ;; | ||
64 | esac | ||
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 | ||
3 | set -g prefix C-a | ||
4 | unbind-key C-b | ||
5 | |||
6 | unbind-key -a | ||
7 | bind-key -n C-h list-keys | ||
8 | bind-key C-d detach | ||
9 | bind-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 ...) | ||
13 | set -g terminal-overrides 'xterm*:smcup@:rmcup@' | ||
14 | |||
15 | #Pour les ctrl+arrow | ||
16 | set-option -g xterm-keys on | ||
17 | |||
18 | # c'est un minimum (defaut 2000) | ||
19 | set-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 | ||
23 | setw -g aggressive-resize on | ||
24 | |||
25 | # Pour etre alerté sur un changement dans une autre fenêtre | ||
26 | setw -g monitor-activity on | ||
27 | #set -g visual-activity on | ||
28 | #set -g visual-bell on | ||
29 | |||
30 | set -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) | ||
34 | set -g set-titles on | ||
35 | set -g set-titles-string '#H #W #T' # host window command | ||
36 | |||
37 | #Dans les valeurs par defaut deja, avec le ssh-agent | ||
38 | set -g update-environment "DISPLAY SSH_ASKPASS SSH_AUTH_SOCK SSH_AGENT_PID SSH_CONNECTION WINDOWID XAUTHORITY PATH" | ||
39 | |||
40 | set -g status off | ||
41 | set -g status-left '' | ||
42 | set -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 | |||
3 | LDAPSEARCH=ldapsearch | ||
4 | KEY="immaeSshKey" | ||
5 | LDAP_BIND="cn=ssh,ou=services,dc=immae,dc=eu" | ||
6 | LDAP_PASS=$(cat /etc/ssh/ldap_password) | ||
7 | LDAP_HOST="ldap.immae.eu" | ||
8 | LDAP_MEMBER="cn=users,cn=ssh,ou=services,dc=immae,dc=eu" | ||
9 | LDAP_GITOLITE_MEMBER="cn=users,cn=gitolite,ou=services,dc=immae,dc=eu" | ||
10 | LDAP_PUB_RESTRICT_MEMBER="cn=restrict,cn=pub,ou=services,dc=immae,dc=eu" | ||
11 | LDAP_PUB_FORWARD_MEMBER="cn=forward,cn=pub,ou=services,dc=immae,dc=eu" | ||
12 | LDAP_BASE="dc=immae,dc=eu" | ||
13 | GITOLITE_SHELL=$(which gitolite-shell) | ||
14 | ECHO=$(which echo) | ||
15 | |||
16 | suitable_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 | |||
35 | clean_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 | |||
49 | ldap_search() { | ||
50 | $LDAPSEARCH -h $LDAP_HOST -ZZ -b $LDAP_BASE -D $LDAP_BIND -w "$LDAP_PASS" -x -o ldif-wrap=no -LLL "$@" | ||
51 | } | ||
52 | |||
53 | ldap_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 | |||
152 | ldap_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, ... }: | ||
2 | let | ||
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 | ''; | ||
83 | in { | ||
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 | ||
2 | if (!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); | ||
15 | ldap_set_option($connect, LDAP_OPT_PROTOCOL_VERSION, 3); | ||
16 | if (!$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 | |||
25 | if (ldap_count_entries($connect, $search) != 1) { | ||
26 | die("Impossible to find user in LDAP"); | ||
27 | } | ||
28 | |||
29 | $entries = []; | ||
30 | foreach($info[0]["immaetaskid"] as $key => $value) { | ||
31 | if ($key !== "count") { | ||
32 | $entries[] = explode(":", $value); | ||
33 | } | ||
34 | } | ||
35 | |||
36 | if (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----- | ||
47 | MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ | ||
48 | MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT | ||
49 | DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow | ||
50 | PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD | ||
51 | Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB | ||
52 | AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O | ||
53 | rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq | ||
54 | OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b | ||
55 | xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw | ||
56 | 7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD | ||
57 | aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV | ||
58 | HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG | ||
59 | SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 | ||
60 | ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr | ||
61 | AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz | ||
62 | R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 | ||
63 | JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo | ||
64 | Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ | ||
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 | ||
92 | org: $org | ||
93 | user key: $key | ||
94 | server: $host | ||
95 | client.cert: | ||
96 | $certificate | ||
97 | Client.key: | ||
98 | $cert_key | ||
99 | ca.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> | ||
128 | For command line interface, download the files, put them near your Taskwarrior | ||
129 | configuration files, and add that to your Taskwarrior configuration: | ||
130 | <pre> | ||
131 | taskd.certificate=/path/to/<?php echo $ldap_user; ?>.cert.pem | ||
132 | taskd.key=/path/to/<?php echo $ldap_user; ?>.key.pem | ||
133 | taskd.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 { ?> | ||
141 | taskd.credentials=<?php echo $entries[0][0]; ?>/<?php echo $entries[0][1]; ?>/<?php echo $entries[0][2]; ?> | ||
142 | <?php } ?> | ||
143 | taskd.ca=/path/to/ca.cert.pem | ||
144 | </pre> | ||
145 | For Mirakel, download and import the file: | ||
146 | <ul> | ||
147 | <?php | ||
148 | foreach ($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> | ||
154 | For 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, ... }: | ||
2 | let | ||
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; | ||
11 | in { | ||
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, ... }: | ||
2 | let | ||
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; | ||
11 | in { | ||
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, ... }: | ||
2 | let | ||
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; | ||
8 | in { | ||
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 }: | ||
2 | rec { | ||
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 | ||
2 | if (!defined("_ECRIRE_INC_VERSION")) return; | ||
3 | if (!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 | ||
2 | if (!defined("_ECRIRE_INC_VERSION")) return; | ||
3 | define('_MYSQL_SET_SQL_MODE',true); | ||
4 | $GLOBALS['spip_connect_version'] = 0.7; | ||
5 | spip_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 | ||
2 | if (!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, ... }: | ||
2 | let | ||
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; | ||
11 | in { | ||
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, ... }: | ||
2 | let | ||
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; | ||
11 | in { | ||
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 | {}: | ||
2 | rec { | ||
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 }: | ||
2 | rec { | ||
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, ... }: | ||
2 | let | ||
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; | ||
11 | in { | ||
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, ... }: | ||
2 | let | ||
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; | ||
11 | in { | ||
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, ... }: | ||
2 | let | ||
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)); | ||
65 | in | ||
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, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.websites.denisejerome.production; | ||
4 | varDir = "/var/lib/ftp/denisejerome"; | ||
5 | env = myconfig.env.websites.denisejerome; | ||
6 | in { | ||
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 !</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, ... }: | ||
2 | let | ||
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 | ''; | ||
42 | in { | ||
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, ... }: | ||
2 | let | ||
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; | ||
13 | in { | ||
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 }: | ||
2 | rec { | ||
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, ... }: | ||
2 | let | ||
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; | ||
7 | in { | ||
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, ... }: | ||
2 | let | ||
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; | ||
7 | in { | ||
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, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.websites.immae.production; | ||
4 | varDir = "/var/lib/ftp/immae"; | ||
5 | env = myconfig.env.websites.immae; | ||
6 | in { | ||
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, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.websites.immae.release; | ||
4 | varDir = "/var/lib/ftp/release.immae.eu"; | ||
5 | env = myconfig.env.websites.release; | ||
6 | in { | ||
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, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.websites.immae.temp; | ||
4 | varDir = "/var/lib/ftp/temp.immae.eu"; | ||
5 | env = myconfig.env.websites.temp; | ||
6 | in { | ||
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, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.websites.leila.production; | ||
4 | varDir = "/var/lib/ftp/leila"; | ||
5 | in { | ||
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 }: | ||
2 | rec { | ||
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, ... }: | ||
2 | let | ||
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; | ||
11 | in { | ||
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, ... }: | ||
2 | let | ||
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; | ||
11 | in { | ||
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, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.websites.nassime.production; | ||
4 | varDir = "/var/lib/ftp/nassime"; | ||
5 | env = myconfig.env.websites.nassime; | ||
6 | in { | ||
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, ... }: | ||
2 | let | ||
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; | ||
7 | in { | ||
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, ... }: | ||
2 | let | ||
3 | cfg = config.myServices.websites.papa.surveillance; | ||
4 | varDir = "/var/lib/ftp/papa"; | ||
5 | in { | ||
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 }: | ||
2 | rec { | ||
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, ... }: | ||
2 | let | ||
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; | ||
11 | in { | ||
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, ... }: | ||
2 | let | ||
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; | ||
11 | in { | ||
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, ... }: | ||
2 | let | ||
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 | }; | ||
45 | in { | ||
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 }: | ||
2 | rec { | ||
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, ... }: | ||
2 | let | ||
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; | ||
24 | in { | ||
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, ... }: | ||
2 | let | ||
3 | adminer = pkgs.callPackage ../../commons/adminer.nix {}; | ||
4 | |||
5 | cfg = config.myServices.websites.tools.db; | ||
6 | in { | ||
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, ... }: | ||
2 | let | ||
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; | ||
7 | in { | ||
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, ... }: | ||
2 | let | ||
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; | ||
9 | in { | ||
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, ... }: | ||
2 | let | ||
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; | ||
12 | in { | ||
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 }: | ||
2 | rec { | ||
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 }: | ||
2 | rec { | ||
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, ... }: | ||
2 | let | ||
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; | ||
7 | in { | ||
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, ... }: | ||
2 | let | ||
3 | env = myconfig.env.tools.mediagoblin; | ||
4 | cfg = config.myServices.websites.tools.mediagoblin; | ||
5 | mcfg = config.services.mediagoblin; | ||
6 | in { | ||
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, ... }: | ||
2 | let | ||
3 | env = myconfig.env.tools.peertube; | ||
4 | cfg = config.myServices.websites.tools.peertube; | ||
5 | pcfg = config.services.peertube; | ||
6 | in { | ||
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’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 }: | ||
2 | rec { | ||
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, ... }: | ||
2 | let | ||
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; | ||
45 | in { | ||
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 }: | ||
2 | rec { | ||
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 }: | ||
2 | rec { | ||
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 }: | ||
2 | rec { | ||
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’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 }: | ||
2 | rec { | ||
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 }: | ||
2 | rec { | ||
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 }: | ||
2 | rec { | ||
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 }: | ||
2 | let | ||
3 | varDir = "/var/lib/shaarli"; | ||
4 | in 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 }: | ||
2 | rec { | ||
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 }: | ||
2 | rec { | ||
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 }: | ||
2 | let | ||
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 | }; | ||
39 | in | ||
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 }: | ||
2 | rec { | ||
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, ... }: | ||
2 | let | ||
3 | name = "diaspora"; | ||
4 | cfg = config.services.diaspora; | ||
5 | |||
6 | uid = config.ids.uids.diaspora; | ||
7 | gid = config.ids.gids.diaspora; | ||
8 | in | ||
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, ... }: | ||
2 | let | ||
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; | ||
8 | in | ||
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, ... }: | ||
2 | let | ||
3 | name = "mastodon"; | ||
4 | cfg = config.services.mastodon; | ||
5 | |||
6 | uid = config.ids.uids.mastodon; | ||
7 | gid = config.ids.gids.mastodon; | ||
8 | in | ||
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, ... }: | ||
2 | let | ||
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 | ''; | ||
53 | in | ||
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, ... }: | ||
2 | let | ||
3 | name = "peertube"; | ||
4 | cfg = config.services.peertube; | ||
5 | |||
6 | uid = config.ids.uids.peertube; | ||
7 | gid = config.ids.gids.peertube; | ||
8 | in | ||
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, ... }: | ||
2 | let | ||
3 | name = "goaccess"; | ||
4 | cfg = config.services.webstats; | ||
5 | in { | ||
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 @@ | |||
1 | time-format %H:%M:%S | ||
2 | date-format %d/%b/%Y | ||
3 | |||
4 | #sur immae.eu | ||
5 | #log-format %v %h %^[%d:%t %^] "%r" %s %b "%R" "%u" $^ | ||
6 | |||
7 | log-format VCOMBINED | ||
8 | #= %v:%^ %h %^[%d:%t %^] "%r" %s %b "%R" "%u" | ||
9 | |||
10 | html-prefs {"theme":"bright","layout":"vertical"} | ||
11 | |||
12 | exclude-ip 188.165.209.148 | ||
13 | exclude-ip 178.33.252.96 | ||
14 | exclude-ip 2001:41d0:2:9c94::1 | ||
15 | exclude-ip 2001:41d0:2:9c94:: | ||
16 | exclude-ip 176.9.151.89 | ||
17 | exclude-ip 2a01:4f8:160:3445:: | ||
18 | exclude-ip 82.255.56.72 | ||
19 | |||
20 | no-query-string true | ||
21 | |||
22 | keep-db-files true | ||
23 | load-from-disk true | ||
24 | db-path @dbPath@ | ||
25 | |||
26 | ignore-panel REFERRERS | ||
27 | ignore-panel KEYPHRASES | ||
28 | |||
29 | static-file .css | ||
30 | static-file .js | ||
31 | static-file .jpg | ||
32 | static-file .png | ||
33 | static-file .gif | ||
34 | static-file .ico | ||
35 | static-file .jpeg | ||
36 | static-file .pdf | ||
37 | static-file .csv | ||
38 | static-file .mpeg | ||
39 | static-file .mpg | ||
40 | static-file .swf | ||
41 | static-file .woff | ||
42 | static-file .woff2 | ||
43 | static-file .xls | ||
44 | static-file .xlsx | ||
45 | static-file .doc | ||
46 | static-file .docx | ||
47 | static-file .ppt | ||
48 | static-file .pptx | ||
49 | static-file .txt | ||
50 | static-file .zip | ||
51 | static-file .ogg | ||
52 | static-file .mp3 | ||
53 | static-file .mp4 | ||
54 | static-file .exe | ||
55 | static-file .iso | ||
56 | static-file .gz | ||
57 | static-file .rar | ||
58 | static-file .svg | ||
59 | static-file .bmp | ||
60 | static-file .tar | ||
61 | static-file .tgz | ||
62 | static-file .tiff | ||
63 | static-file .tif | ||
64 | static-file .ttf | ||
65 | static-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; | ||
2 | let | ||
3 | cfg = config.services.websites; | ||
4 | in | ||
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 | |||
6 | with lib; | ||
7 | |||
8 | let | ||
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 | |||
432 | in | ||
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> | ||