Propagate the truth!
[contrib/Conifer.git] / Open-ILS / src / support-scripts / webserver_config.py
1 #!/usr/bin/env python3
2 import os, os.path
3
4 ssl_certname = 'boreal-test.concat.ca';
5
6 # /etc/nginx/concat_ssl.conf
7 ssl_conf = """listen 443 ssl; # managed by Certbot
8 ssl_certificate /etc/letsencrypt/live/{certname}/fullchain.pem; # managed by Certbot
9 ssl_certificate_key /etc/letsencrypt/live/{certname}/privkey.pem; # managed by Certbot
10 include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
11
12 if ($scheme != "https") {{
13     return 301 https://$host$request_uri;
14 }} # managed by Certbot
15
16 # generate with openssl dhparam -out dhparams.pem 2048
17 ssl_dhparam /etc/letsencrypt/dhparams.pem;
18
19 # From https://mozilla.github.io/server-side-tls/ssl-config-generator/
20 ssl_session_tickets off;
21
22 # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
23 add_header Strict-Transport-Security max-age=15768000;
24
25 # OCSP Stapling ---
26 # fetch OCSP records from URL in ssl_certificate and cache them
27 ssl_stapling on;
28 ssl_stapling_verify on;
29 """.format(certname=ssl_certname)
30
31 # /etc/nginx/concat_headers.conf
32 headers_conf = """proxy_set_header Host $host;
33 proxy_set_header X-Real-IP $remote_addr;
34 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
35 proxy_set_header X-Forwarded-Proto $scheme;"""
36
37 # /etc/nginx/osrf_sockets.conf
38 sockets_conf = """location /osrf-websocket-translator {
39     proxy_pass https://localhost:7682;
40     proxy_set_header X-Real-IP $remote_addr;
41     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
42
43     # Needed for websockets proxying.
44     proxy_http_version 1.1;
45     proxy_set_header Upgrade $http_upgrade;
46     proxy_set_header Connection "upgrade";
47
48     # Raise the default nginx proxy timeout values to an arbitrarily
49     # high value so that we can leverage osrf-websocket-translator's 
50     # timeout settings.
51     proxy_connect_timeout 5m;
52     proxy_send_timeout 1h;
53     proxy_read_timeout 1h;
54 }"""
55
56 # /etc/nginx/sites-available/conifer-test
57 server_block = """server {{
58     listen 80;
59     server_name {hostname};
60
61     include /etc/nginx/concat_ssl.conf;
62     include /etc/nginx/osrf_sockets.conf;
63
64     location / {{
65         proxy_pass https://localhost:7443;
66         include /etc/nginx/concat_headers.conf;
67     }}
68 }}
69 """
70
71 domains = {
72     'algoma.concat.ca': {
73         'templates': ['algoma'],
74         'locale': 'fr_ca',
75         'physical_loc': 111
76     },
77     'boreal.concat.ca': {
78         'templates': ['boreal'],
79         'locale': 'fr_ca',
80         'default_locale': 'fr-CA',
81         'physical_loc': 135
82     },
83     'ccrconnect.concat.ca': {
84         'locale': 'fr_ca',
85         'physical_loc': 144
86     },
87     'crc.concat.ca': {
88         'templates': ['laurentian'],
89         'locale': 'fr_ca',
90         'default_locale': 'fr-CA',
91         'physical_loc': 130
92     },
93     'hrsrh.concat.ca': {
94         'templates': ['hrsrh'],
95         'locale': 'fr_ca',
96         'physical_loc': 115
97     },
98     'cdev1.concat.ca': {
99         'locale': 'fr_ca',
100     },
101     'hsn.concat.ca': {
102         'templates': ['hrsrh'],
103         'locale': 'fr_ca',
104         'physical_loc': 115
105     },
106     'huntington.concat.ca': {
107         'templates': ['huntington'],
108         'locale': 'fr_ca',
109         'physical_loc': 104
110     },
111     'laurentian.concat.ca': {
112         'templates': ['laurentian'],
113         'locale': 'fr_ca',
114         'physical_loc': 105,
115         'robots': 'osul'
116     },
117     'laurentienne.concat.ca': {
118         'templates': ['laurentian'],
119         'locale': 'fr_ca',
120         'default_locale': 'fr-CA',
121         'physical_loc': 105
122     },
123     'medb.concat.ca': {
124         'templates': ['laurentian'],
125         'locale': 'fr_ca',
126         'physical_loc': 117
127     },
128     'mediacentre.concat.ca': {
129         'templates': ['laurentian'],
130         'locale': 'fr_ca',
131         'physical_loc': 108
132     },
133     'mrc.concat.ca': {
134         'templates': ['laurentian'],
135         'locale': 'fr_ca',
136         'physical_loc': 131
137     },
138     'nosm.concat.ca': {
139         'templates': ['nosm'],
140         'locale': 'fr_ca',
141         'physical_loc': 125
142     },
143     'sah.concat.ca': {
144         'locale': 'fr_ca',
145         'physical_loc': 116
146     },
147     'sjcg.concat.ca': {
148         'templates': ['sjcg'],
149         'locale': 'fr_ca',
150         'physical_loc': 133
151     },
152     'uhearst.concat.ca': {
153         'templates': ['uhearst'],
154         'locale': 'fr_ca',
155         'default_locale': 'fr-CA',
156         'physical_loc': 114
157     },
158     'usudbury.concat.ca': {
159         'templates': ['laurentian', 'usudbury'],
160         'locale': 'fr_ca',
161         'physical_loc': 107
162     },
163     'www.concat.ca': {
164         'locale': 'fr_ca',
165     },
166     '49thregiment.concat.ca': {
167         'physical_loc': 134
168     }
169 }
170
171 missing_test_domains = (
172     'cfof-test.concat.ca',
173     'medb-test.concat.ca',
174 )
175
176 def mutate_test_hostname(host):
177     "Generate test hostname for a single string"
178     if not host.startswith('cdev1'):
179         x = host.partition('.')
180         host = ''.join((x[0], '-test', x[1], x[2]))
181     return host
182
183 def mutate_test_hostnames(test=True):
184     """Generate proper test hostnames"""
185     testdomains = []
186     
187     for host in sorted(domains.keys()):
188         if test:
189             host = mutate_test_hostname(host)
190         testdomains.append(host)
191     return testdomains
192
193 def generate_config(test=True):
194     """Generate nginx config files"""
195     os.makedirs('nginx/sites-available', exist_ok=True)
196     os.makedirs('apache2/sites-available', exist_ok=True)
197     with open(os.path.join('nginx', 'concat_ssl.conf'), 'w') as f:
198         f.write(ssl_conf)
199
200     with open(os.path.join('nginx', 'concat_headers.conf'), 'w') as f:
201         f.write(headers_conf)
202
203     with open(os.path.join('nginx', 'osrf_sockets.conf'), 'w') as f:
204         f.write(sockets_conf)
205
206     with open(os.path.join('nginx/sites-available', 'conifer.conf'), 'w') as f:
207         for host in mutate_test_hostnames(test):
208             f.write(server_block.format(hostname=host))
209
210     with open(os.path.join('apache2/sites-available', 'conifer.conf'), 'w') as f:
211         f.write(generate_apache_vhost(test))
212
213 def generate_certbot(test=True):
214     """Generate certbot command"""
215     certbot = 'certbot --nginx run '
216     for host in mutate_test_hostnames(test):
217         if host in missing_test_domains:
218             continue
219         certbot = certbot + " -d {hostname}".format(hostname=host)
220     print(certbot)
221
222 def generate_apache_vhost(test=True):
223    """Generate apache2/sites-available/eg.conf"""
224    vhost = ApacheVHost.apache_eg_conf
225    for hostname in sorted(domains.keys()):
226        ahost = ApacheVHost(hostname, test)
227        vhost = vhost + ahost.vhost()
228    return vhost
229
230 class ApacheVHost:
231     # /etc/apache2/sites-available/eg.conf
232     apache_eg_conf = """LogLevel info 
233 # - log locally
234 # CustomLog /var/log/apache2/access.log combined
235 # ErrorLog /var/log/apache2/error.log
236 # - log to syslog 
237 CustomLog "|/usr/bin/logger -p local7.info" common
238 ErrorLog syslog:local7
239
240 # ----------------------------------------------------------------------------------
241 # Set up Perl 
242 # ----------------------------------------------------------------------------------
243
244 # - needed by CGIs
245 PerlRequire /etc/apache2/eg_startup
246 PerlChildInitHandler OpenILS::WWW::Reporter::child_init
247 PerlChildInitHandler OpenILS::WWW::SuperCat::child_init
248 PerlChildInitHandler OpenILS::WWW::AddedContent::child_init
249 PerlChildInitHandler OpenILS::WWW::AutoSuggest::child_init
250 PerlChildInitHandler OpenILS::WWW::PhoneList::child_init
251 PerlChildInitHandler OpenILS::WWW::EGWeb::child_init
252
253 # ----------------------------------------------------------------------------------
254 # Set some defaults for our working directories
255 # ----------------------------------------------------------------------------------
256 <Directory /openils/var/web>
257     Require all granted
258 </Directory>
259
260 # ----------------------------------------------------------------------------------
261 # XUL directory
262 # ----------------------------------------------------------------------------------
263 <Directory /openils/var/web/xul>
264     Options Indexes FollowSymLinks
265     AllowOverride None
266     Require all granted
267 </Directory>
268
269 # ----------------------------------------------------------------------------------
270 # Remove the language portion from the URL
271 # ----------------------------------------------------------------------------------
272 AliasMatch ^/opac/.*/skin/(.*)/(.*)/(.*) /openils/var/web/opac/skin/$1/$2/$3
273 AliasMatch ^/opac/.*/extras/slimpac/(.*) /openils/var/web/opac/extras/slimpac/$1
274 AliasMatch ^/opac/.*/extras/selfcheck/(.*) /openils/var/web/opac/extras/selfcheck/$1
275
276 # ----------------------------------------------------------------------------------
277 # System config CGI scripts go here
278 # ----------------------------------------------------------------------------------
279 Alias /cgi-bin/offline/ "/openils/var/cgi-bin/offline/"
280 <Directory "/openils/var/cgi-bin/offline">
281     AddHandler cgi-script .cgi .pl
282     AllowOverride None
283     Options None
284     Require host 10.0.0.0/8
285     Options FollowSymLinks ExecCGI Indexes
286 </Directory>
287
288 # ----------------------------------------------------------------------------------
289 # Updates folder
290 # ----------------------------------------------------------------------------------
291 Alias /updates/ "/openils/var/updates/pub/"
292 <Directory "/openils/var/updates/pub">
293     <Files check>
294         ForceType cgi-script
295     </Files>
296     <Files update.rdf>
297         ForceType cgi-script
298     </Files>
299     <Files manualupdate.html>
300         ForceType cgi-script
301     </Files>
302     <Files download>
303         ForceType cgi-script
304     </Files>
305     AllowOverride None
306     Options None
307     Options ExecCGI
308     Require all granted
309 </Directory>
310
311 # ----------------------------------------------------------------------------------
312 # OPTIONAL: Set how long the client will cache our content.  Change to suit
313 # ----------------------------------------------------------------------------------
314 ExpiresActive On
315 ExpiresDefault "access plus 1 month"
316 ExpiresByType text/html "access plus 18 hours"
317 ExpiresByType application/xhtml+xml "access plus 18 hours"
318 ExpiresByType application/x-javascript "access plus 18 hours"
319 ExpiresByType application/javascript "access plus 18 hours"
320 ExpiresByType text/css "access plus 50 minutes"
321
322 # ----------------------------------------------------------------------------------
323 # Set up our SSL virtual host
324 # ----------------------------------------------------------------------------------
325 #Listen 443
326 <VirtualHost *:7443>
327     DocumentRoot "/openils/var/web"
328     ServerName localhost:443
329     ServerAlias 127.0.0.1:443
330
331     # - absorb the shared virtual host settings
332     Include eg_vhost.conf
333     Include eg_vhost_ssl.conf
334
335 </VirtualHost>
336 """
337
338     apache_robots = """Alias /robots.txt /openils/var/web/{host}_robots.txt
339 """
340
341     apache_template = """
342         PerlAddVar OILSWebTemplatePath '/openils/var/templates_{template}'"""
343
344     apache_locale = """
345         PerlAddVar OILSWebLocale '{locale}'
346         PerlAddVar OILSWebLocale '/openils/var/data/locale/opac/{locale_upper}.po'"""
347
348     apache_default_locale = """
349         PerlAddVar OILSWebDefaultLocale "{locale}"
350 """
351
352     apache_physical_loc = "SetEnv physical_loc {location}"
353
354     apache_vhost = """
355 <VirtualHost *:7443>
356     DocumentRoot '/openils/var/web'
357     ServerName https://{hostname}:443
358
359     # - absorb the shared virtual host settings
360     Include eg_vhost.conf
361     Include eg_vhost_ssl.conf
362     {robots}
363     <Location /eg>{template}{locale}{default_locale}
364     </Location>
365     {physical_loc}
366 </VirtualHost>
367 """
368
369     def __init__(self, hostname, test=True):
370         self.hostname = hostname
371         host = domains[self.hostname]
372         if test:
373             self.hostname = mutate_test_hostname(hostname)
374         self.robots = ''
375         self.default_locale = ''
376         self.locale = ''
377         self.physical_loc = ''
378         self.templates = ''
379         if 'robots' in host:
380             self.robots = ApacheVHost.apache_robots.format(host=host['robots'])
381         if 'default_locale' in host:
382             self.default_locale = ApacheVHost.apache_default_locale.format(locale=host['default_locale'])
383         if 'locale' in host:
384             locale = host['locale']
385             locale_upper = ''.join((locale[0:2], '-', locale[3:5].upper()))
386             self.locale = ApacheVHost.apache_locale.format(locale=locale, locale_upper=locale_upper)
387         if 'physical_loc' in host:
388            self.physical_loc = ApacheVHost.apache_physical_loc.format(location=host['physical_loc'])
389         if 'templates' in host:
390             for template in host['templates']:
391                 self.templates = self.templates + ApacheVHost.apache_template.format(template=template)
392
393     def vhost(self):
394         return ApacheVHost.apache_vhost.format(
395             hostname=self.hostname,
396             robots=self.robots,
397             template=self.templates,
398             locale = self.locale,
399             default_locale = self.default_locale,
400             physical_loc = self.physical_loc
401         )
402
403 if __name__ == '__main__':
404     test = True
405     generate_config(test)
406     generate_certbot(test)