Added SSL notes to mail/git/web server build log.
[website_subgeniuskitty.com] / data / notes / mail_web_git_server.md
CommitLineData
4ccc706c
AT
1# Overview #
2
3These notes cover the creation of a server, hosted with Linode, running Debian
4Linux, offering the following services:
5
6 - Mail server
7
8 - SMTP via postfix
9
10 - IMAP via dovecot
11
12 - Realtime blacklists for SPAM rejection
13
14 - MySQL for virtual domain and user management.
15
16 - Web server
17
18 - Apache as central HTTP server with multiple vhosts.
19
562bd187 20 - [CMless](http://git.subgeniuskitty.com/cmless/.git) as CGI for content management
4ccc706c
AT
21
22 - ACME for automated SSL certificate management
23
24 - Git server
25
26 - SSH-based, authenticated read-write access to all git repositories
27
28 - Anonymous read-only access to a subset of git repositories via:
29
6bce4d40
AT
30 - Customized [gitweb](http://git.subgeniuskitty.com/gitweb-sgk/.git) for
31 GUI git browsing with syntax highlighting, diffs, etc
4ccc706c
AT
32
33 - Git-daemon for cloning repositories via the `git://` protocol
34
35These notes are a high-level checklist for my reference rather than a
36step-by-step installation guide for the public. That means they make no attempt
37to explain all options at each step, rather that they mention only the options
38I use on my servers. It also means they use my domains, my file system paths,
39etc in the examples.
40
41
42# TODO List #
43
4ccc706c
AT
44 - Take a snapshot on Linode's backup service once the basic services are
45 operational.
46
7c21ac6b 47 - Migrate mail server. Delete old linode vserver after downloading a disk image.
4ccc706c
AT
48
49 - Finish this documentation.
50
51 - Improve CSS on gitweb, especially for displaying READMEs.
52
6bce4d40
AT
53 - Add some form of web logfile viewing.
54
4ccc706c
AT
55
56# Basic Configuration #
57
58
59## General Information ##
60
61**Name:** SGK-Main-2020
62
63**OS:** Debian 10
64
65**Creation Date:** 2020-11-01
66
67**Filesystem Points of Interest:**
68
69 - `/srv/apache_vhosts`: Contains websites hosted by Apache2. See
70 `/etc/apache2/sites_available` for vhost configurations.
71
72 - `/srv/git`: Master location for bare git repositories. All are private.
73
74 - `/srv/gitweb_cache`: Contains checked out copies of git repositories from
75 `/srv/git`, publicly visible via `gitweb` and cloneable via `git-daemon`.
76
77
78## Preparation ##
79
80Setup DNS entries for `subgeniuskitty.com` and `logicavalanche.com` through
81Linode. Remember to do IPv4 and IPv6 entries for the bare domain, `www`,
82`mail`, and `git`.
83
84Create a new Debian 10 on Linode and update the system with `apt-get update &&
85apt-get upgrade`.
86
87Add a user via `adduser ataylor` and following the prompts. Edit
88`/etc/ssh/sshd_config` to set `PermitRootLogin: no` and restart SSH. For both
89the `ataylor` user and `root`, add the line `set mouse=` to `~/.vimrc` in order
90to disable mouse support in `vim`, allowing normal mark-and-paste in the
91terminal.
92
93Install useful packages:
94
95 apt-get install net-tools screen bzip2 zip
96
97
98# Web Server #
99
100
101## HTTP: Apache2 ##
102
103Install Apache2.
104
105 apt-get install apache2
106
b989fcd3
AT
107If not already defined elsewhere, add a `ServerName 127.0.0.1` entry to the
108bottom of `/etc/apache2/apache2.conf`, or whatever is appropriate.
2fbf9e1e 109
4ccc706c
AT
110Since we use `/srv` instead of `/var/www`, edit `/etc/apache2/apache2.conf` to
111comment out the `<Directory ...>` entry for `/var/www` and replace it with
112this:
113
114 <Directory /srv/>
115 Options Indexes FollowSymLinks
116 AllowOverride None
117 Require all granted
118 </Directory>
119
120Make and edit the file `/srv/apache_vhosts/default/index.html` with (rewritten)
121contents:
122
123 <p>Invalid VHost</p>
124 <p>Contact user @at@ domain .dot. com</p>
125
126Ensure everything under `/srv/apache_vhosts`, including that directory itself,
127is owned recursively by `www-data:www-data`.
128
129Edit `/etc/apache2/sites-available/000-default.conf` and
130`/etc/apache2/sites-available/000-default.conf`, changing references for the
131default sites from `/var/www/...` to `/srv/apache_vhosts/...` as necessary.
132
133Reload Apache2 with `systemctl reload apache2` and check status with `systemctl
134status apache2`.
135
136
137### SSL ###
138
6bce4d40
AT
139Install certbot and generate a key for its use.
140
141 apt-get install certbot
142 openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048
4ccc706c 143
6bce4d40
AT
144Create `/etc/apache2/conf-available/ssl-params.conf` with the following
145contents.
146
147 SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
148 SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
149 SSLHonorCipherOrder off
150 SSLSessionTickets off
151
152 SSLUseStapling On
153 SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"
154
155 Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
156 Header always set X-Frame-Options SAMEORIGIN
157 Header always set X-Content-Type-Options nosniff
158
159 SSLOpenSSLConfCmd DHParameters "/etc/ssl/certs/dhparam.pem"
4ccc706c 160
6bce4d40 161Enable the new configuration and required mods, then restart Apache2.
4ccc706c 162
6bce4d40
AT
163 a2enconf ssl-params
164 a2enmod ssl
165 a2enmod headers
166 systemctl restart apache2
167
168Retrieve an initial certificate with the following command, modified to match
169the desired webroot and server names.
170
171<http://subgeniuskitty.com> and <http://logicavalanche.com>:
172
173 certbot certonly --agree-tos --email webmaster@subgeniuskitty.com --webroot -w /srv/apache_vhosts/subgeniuskitty.com/site/data/ -d subgeniuskitty.com -d www.subgeniuskitty.com
174
175<http://archive.subgeniuskitty.com> and <http://git.subgeniuskitty.com>:
176
177 certbot certonly --agree-tos --email webmaster@subgeniuskitty.com --webroot -w /srv/apache_vhosts/archive.subgeniuskitty.com/ -d archive.subgeniuskitty.com
178
179Edit `/etc/apache2/sites-available/subgeniuskitty.com`, adding the following
180`VirtualHost` definition that mostly copies the non-SSL entry.
181
182 <VirtualHost *:443>
183 SSLEngine on
184 SSLCertificateFile /etc/letsencrypt/live/subgeniuskitty.com/fullchain.pem
185 SSLCertificateKeyFile /etc/letsencrypt/live/subgeniuskitty.com/privkey.pem
186
187 ...copy of vhost definition for host *:80...
188 </VirtualHost>
4ccc706c 189
6bce4d40
AT
190Edit `/etc/cron.d/certbot` and append `--renew-hook "systemctl reload apache2"`
191to the certbot invokation.
4ccc706c 192
6bce4d40 193Test with `certbot renew --dry-run`.
4ccc706c 194
6bce4d40 195Repeat the process for any other sites hosted on this server.
4ccc706c 196
6bce4d40 197Backup the `/etc/letsencrypt` folder off-server periodically.
4ccc706c
AT
198
199
200## Basic Website ##
201
202Using <http://archive.subgeniuskitty.com> as an example of a basic website,
203create an Apache2 vhost configuration file at
204`/etc/apache2/sites-available/archive.subgeniuskitty.com.conf`.
205
206 <VirtualHost *:80>
207 DocumentRoot "/srv/apache_vhosts/archive.subgeniuskitty.com"
208 ServerName archive.subgeniuskitty.com
209 ServerAdmin webmaster@subgeniuskitty.com
210 ErrorLog /var/log/apache2/error_log.archive.subgeniuskitty.com
211 CustomLog /var/log/apache2/access_log.archive.subgeniuskitty.com combined
212 <Directory "/srv/apache_vhosts/archive.subgeniuskitty.com">
213 Options +FollowSymLinks
214 AllowOverride None
215 Require all granted
216 </Directory>
217 <Directory "/srv/apache_vhosts/archive.subgeniuskitty.com/sites">
218 Options +FollowSymLinks +Indexes
219 AllowOverride None
220 Require all granted
221 </Directory>
222 </VirtualHost>
223
224Make the directory `/srv/apache_vhosts/archive.subgeniuskitty.com`, move your
225data into it, and ensure everything is owned by `www-data:www-data`.
226
227Enable the vhost with `a2ensite archive.subgeniuskitty.com` and `systemctl
228reload apache2`.
229
230
231## CMless Website ##
232
233Enable `mod_rewrite` and either `mod_cgi` or `mod_cgid` as appropriate with
234these commands.
235
236 a2enmod rewrite
237 a2enmod cgid
238
239Install `discount` to convert Markdown to HTML.
240
241 apt-get install discount
242
243Create `/etc/apache2/sites-available/subgeniuskitty.com.conf`.
244
245 <VirtualHost *:80>
246 DocumentRoot "/srv/apache_vhosts/subgeniuskitty.com"
247 ServerName subgeniuskitty.com
248 ServerAlias www.subgeniuskitty.com
249 ServerAdmin webmaster@subgeniuskitty.com
250 ErrorLog /var/log/apache2/error_log.subgeniuskitty.com
251 CustomLog /var/log/apache2/access_log.subgeniuskitty.com combined
252 AddHandler cgi-script .py
253 <Directory "/srv/apache_vhosts/subgeniuskitty.com">
254 Options -ExecCGI -Indexes
255 AllowOverride None
256 Require all granted
257 </Directory>
258 <Directory "/srv/apache_vhosts/subgeniuskitty.com/bin">
259 Options ExecCGI
260 AllowOverride None
261 Require all granted
262 </Directory>
263 RewriteEngine On
264 RewriteRule (.*) /srv/apache_vhosts/subgeniuskitty.com/site/data/$1
265 RewriteCond %{REQUEST_FILENAME} !-f
266 RewriteRule .* /srv/apache_vhosts/subgeniuskitty.com/bin/cmless.py
267 </VirtualHost>
268
269Enable the site with `a2ensite subgeniuskitty.com`.
270
562bd187 271Clone a copy of [CMless](http://git.subgeniuskitty.com/cmless/.git) into
4ccc706c
AT
272`/srv/apache_vhosts/subgeniuskitty.com` and ensure everything is owned by
273`www-data:www-data`.
274
275Clone a copy of [the website
562bd187 276data](http://git.subgeniuskitty.com/website_subgeniuskitty.com/.git) into
4ccc706c
AT
277`/srv/apache_vhosts/subgeniuskitty.com/site`. Verify it is owned by
278`ataylor:ataylor` (but still readable by all) so we can update the site
279remotely with a simple script like this:
280
281 #!/usr/local/bin/bash
282 #
283 # Usage: No cmdline arguments
284 # Update content of www.subgeniuskitty.com to the latest version.
285
286 ssh ataylor@git.subgeniuskitty.com "cd /srv/apache_vhosts/subgeniuskitty.com/site && git pull"
287
288Reload Apache's configuration with `systemctl reload apache2` and test access
289to the website.
290
291Repeat this process for <http://logicavalanche.com>.
292
293
294# Git Server #
295
296The git server provides read-write access to a private collection of bare
297repositories located at `/srv/git` via SSH. It also provides read-only access
298to a public collection of normal repositories located at `/srv/gitweb_cache`
299via <http://git.subgeniuskitty.com/repo_name> through gitweb and via
300[git://git.subgeniuskitty.com/repo_name](git://git.subgeniuskitty.com/repo_name)
301through `git-daemon`.
302
303
304## Read/Write: SSH ##
305
306Install git with `apt-get install git`.
307
308On my workstation, generate an SSH key with `ssh-keygen -t rsa`.
309
310On the server, as user `ataylor`:
311
312 mkdir ~/.ssh
6bce4d40
AT
313 chmod 700 ~/.ssh
314 touch ~/.ssh/authorized_keys
315 chmod 600 ~/.ssh/authorized_keys
4ccc706c
AT
316
317Then `cat` the public SSH key from the workstation to the server, appending it
318onto `~/.ssh/authorized_keys`.
319
320Verify ability to login using new certificate.
321
322Create a directory `/srv/git` owned by `ataylor:ataylor`. This will hold bare
323git repositories and act as the central private store for SGK git repos.
324
325From the workstation, we can create a new bare repository on the server. For
326example, packed up in a simple script:
327
328 #!/usr/local/bin/bash
329 #
330 # Usage: sgkgit-new-repo project_name
331 # Setup a repository on the SGK git server.
332
333 if [ "$#" -ne 1 ]; then
334 echo "Must specify repo name as only parameter."
335 exit 2
336 fi
337
338 ssh ataylor@git.subgeniuskitty.com "git init --bare /srv/git/$@"
339
340We then set the remote of an existing repository to the new bare repository.
341Again as a script:
342
343 #!/usr/local/bin/bash
344 #
345 # Usage: sgkgit-set-origin project_name
346 # Sets remote to the correct path for 'project_name' on the SGK git server.
347
348 if [ "$#" -ne 1 ]; then
349 echo "Must specify repo name as only parameter."
350 exit 2
351 fi
352
353 git remote remove origin
354 git remote add origin ataylor@git.subgeniuskitty.com:/srv/git/$@
355 echo "Remember to make the first push with \"git push --set-upstream origin master\"."
356
357After the first push to the bare repository on the server, simply `git push`
358and `git pull` as normal.
359
360You can also list the repositories currently on the server with simple SSH commands.
361
362 #!/usr/local/bin/bash
363 #
364 # Usage: No cmdline arguments.
365 # List all remote repos available on SGK git server.
366
367 ssh ataylor@git.subgeniuskitty.com "ls -lt /srv/git/"
368
369Clone one of these repositories, with remote correspondingly pre-set.
370
371 #!/usr/local/bin/bash
372 #
373 # Usage: sgkgit-checkout project_name
374 # Clone a local copy of repo "project_name" from SGK git server.
375
376 if [ "$#" -ne 1 ]; then
377 echo "Must specify repo name as only parameter."
378 exit 2
379 fi
380
381 git clone ataylor@git.subgeniuskitty.com:/srv/git/$@
382
383
384## Read-Only: Gitweb ##
385
386Enable `mod_rewrite` and either `mod_cgi` or `mod_cgid` as appropriate with
387these commands.
388
389 a2enmod rewrite
390 a2enmod cgid
391
392Install `discount` to convert Markdown to HTML, `highlight` for syntax
393highlighting, and `gitweb` to pull in any dependencies.
394
395 apt-get install discount highlight gitweb
396
397Create `/etc/apache2/sites-available/git.subgeniuskitty.com.conf`.
398
399 <VirtualHost *:80>
400 ServerName git.subgeniuskitty.com
401 ServerAdmin webmaster@subgeniuskitty.com
402
403 DocumentRoot "/srv/apache_vhosts/git.subgeniuskitty.com"
404
405 ErrorLog /var/log/apache2/error_log.git.subgeniuskitty.com
406 CustomLog /var/log/apache2/access_log.git.subgeniuskitty.com combined
407
408 <Directory "/srv/apache_vhosts/git.subgeniuskitty.com">
409 Options +FollowSymLinks +ExecCGI
410 AllowOverride None
411 Require all granted
412 AddHandler cgi-script .cgi
413 DirectoryIndex gitweb.cgi
414 RewriteEngine On
415 RewriteCond %{REQUEST_FILENAME} !-f
416 RewriteCond %{REQUEST_FILENAME} !-d
417 RewriteRule ^.* /gitweb.cgi/$0 [L,PT]
418 </Directory>
419 </VirtualHost>
420
421Enable the site with `a2ensite git.subgeniuskitty.com`.
422
562bd187 423Clone a copy of [the SGK gitweb fork](http://git.subgeniuskitty.com/gitweb-sgk/.git)
4ccc706c
AT
424into `/srv/apache_vhosts/git.subgeniuskitty.com` and ensure everything is owned
425by `ataylor:ataylor` (but world-readable!) so we can update via SSH with a script like this.
426
427 #!/usr/local/bin/bash
428 #
429 # Usage: No cmdline arguments
430 # Update git.subgeniuskitty.com to the latest version of forked gitweb repo.
431
432 ssh ataylor@git.subgeniuskitty.com "cd /srv/apache_vhosts/git.subgeniuskitty.com && git pull"
433
434This fork makes a few changes to gitweb, displaying READMEs by default, etc.
435Read the gitweb project's `README.md` for more details.
436
437Create a gitweb config file at `/etc/gitweb.conf`. Note that we are adding a
438`clone url` to the toolbar with the address of our `git-daemon` server.
439Remember to set that up.
440
441 $site_name = "git.subgeniuskitty.com";
442 @git_base_url_list = ("git://git.subgeniuskitty.com");
443 $projectroot = "/srv/gitweb_cache";
444 $git_temp = "/tmp";
445
446 @stylesheets = ("static/gitweb.css");
447 $javascript = "static/gitweb.js";
448 $logo = "static/sgk-logo.png";
449 $favicon = "static/git-favicon.png";
450
451 # git-diff-tree(1) options to use for generated patches
452 @diff_opts = ();
453
454 # Enable PATH_INFO so the server can produce URLs of the
455 # form: http://git.hokietux.net/project.git/xxx/xxx
456 # This allows for pretty URLs *within* the Git repository,
457 # also needs the Apache rewrite rules for full effect.
458 $feature{'pathinfo'}{'default'} = [1];
459
460 # HTML text to include as home page header.
461 $home_text = "indextext.html";
462
463 # Add a toolbar option with the 'git clone url'.
464 $feature{'actions'}{'default'} = [('clone url', 'git://git.subgeniuskitty.com/%n', 'summary')];
465
466 # Category name is read from .git/category, in the same manner as .git/description.
467 $projects_list_group_categories = 1;
468 $project_list_default_category = "misc";
469
470 # Needed for displaying README files.
471 $prevent_xss = 0;
472
473 # Enable syntax highlighting.
474 $feature{'highlight'}{'default'} = [1];
475
476 ################################################################################
477
478 # Enable blame, pickaxe search, snapshop, search, and grep
479 # support, but still allow individual projects to turn them off.
480 # These are features that users can use to interact with your Git trees. They
481 # consume some CPU whenever a user uses them, so you can turn them off if you
482 # need to. Note that the 'override' option means that you can override the
483 # setting on a per-repository basis.
484 $feature{'blame'}{'default'} = [1];
485 $feature{'blame'}{'override'} = [1];
486
487 $feature{'pickaxe'}{'default'} = [1];
488 $feature{'pickaxe'}{'override'} = [1];
489
490 $feature{'snapshot'}{'default'} = [1];
491 $feature{'snapshot'}{'override'} = [1];
492
493 $feature{'search'}{'default'} = [1];
494
495 $feature{'grep'}{'default'} = [1];
496 $feature{'grep'}{'override'} = [1];
497
498Create the directory `/srv/gitweb_cache`. It should be readable by the web
499server (`www-data`), but owned by `ataylor:ataylor` so that simple scripts like
500the following can make repositories public/private.
501
502 #!/usr/local/bin/bash
503 #
504 # Usage: sgkgit-make-public project_name
505 # Make a repo accessible through gitweb and git-daemon.
506
507 if [ "$#" -ne 1 ]; then
508 echo "Must specify repo name as only parameter."
509 exit 2
510 fi
511
512 read -p "Enter a category name: " category
513 read -p "Enter a description: " description
514
515 echo "You entered:"
516 printf "\tCategory: $category \n"
517 printf "\tDescription: $description \n"
518 read -p "Confirm (y/n)?: " confirm
519
520 if [ "$confirm" == "y" ]; then
521 ssh ataylor@git.subgeniuskitty.com "cd /srv/gitweb_cache && rm -rf $@ && git clone /srv/git/$@"
522 ssh ataylor@git.subgeniuskitty.com "cd /srv/git && \
523 printf '#!/usr/bin/bash\ncd /srv/gitweb_cache/$@\ngit --git-dir=.git pull\n' > \
524 /srv/git/$@/hooks/post-update && chmod +x /srv/git/$@/hooks/post-update"
525 ssh ataylor@git.subgeniuskitty.com "echo \"$category\" > /srv/gitweb_cache/$@/.git/category"
526 ssh ataylor@git.subgeniuskitty.com "echo \"$description\" > /srv/gitweb_cache/$@/.git/description"
527 fi
528
529In addition to cloning the git repo into `/srv/gitweb_cache` from the private,
530SSH-only collection of repositories in `/srv/git`, this also creates a
531`post-update` hook in the bare repo in `/srv/git` to ensure that gitweb's cache
532is updated every time a push is made via SSH to the private repo.
533
534This script also sets the `.git/description` and `.git/category` files needed
535by gitweb for displays like the project summary page. These are not under
536version control and may be directly edited to change what is displayed in
537gitweb.
538
539We can remove the public cache, making the repo private-only as shown in this
540script.
541
542 #!/usr/local/bin/bash
543 #
544 # Usage: sgkgit-make-private project_name
545 # Make a repo inaccessible through gitweb and git-daemon.
546
547 if [ "$#" -ne 1 ]; then
548 echo "Must specify repo name as only parameter."
549 exit 2
550 fi
551
552 ssh ataylor@git.subgeniuskitty.com "rm -rf /srv/gitweb_cache/$@ && rm /srv/git/$@/hooks/post-update"
553
554Any repos made private/public via this method are immediately reflected in
555`gitweb` and `git-daemon`.
556
557
558## Read-Only: Git Daemon ##
559
560Since `git` was already installed, simply create a new service profile in
561`/etc/systemd/system/git-daemon.service`.
562
563 [Unit]
564 Description=Start Git Daemon
565
566 [Service]
567 ExecStart=/usr/bin/git daemon --export-all --reuseaddr --base-path=/srv/gitweb_cache/ /srv/gitweb_cache/
568
569 Restart=always
570 RestartSec=500ms
571
572 StandardOutput=syslog
573 StandardError=syslog
574 SyslogIdentifier=git-daemon
575
576 User=ataylor
577 Group=ataylor
578
579 [Install]
580 WantedBy=multi-user.target
581
582Then start the service with `systemctl daemon-reload` and `systemctl start
583git-daemon`. Verify functionality before enabling daemon-autostart with
584`systemctl enable git-daemon`.
585
586The `--export-all` flag tells `git-daemon` to export all repositories located
587at the specified path, regardless of the presence (or lack) of the file
588`.git/git-daemon-export-ok` in the individual repository. We do this because
589only public repos are checked out to this folder. All private repos are kept
590entirely elsewhere.
591
592The directory `/srv/gitweb_cache` should have already been created when
593installing gitweb. If not, go read those instructions.
594
595A repository located at `/srv/gitweb_cache/repo_name` may be cloned through
596`git-daemon` at
597[git://git.subgeniuskitty.com/repo_name](git://git.subgeniuskitty.com/repo_name)
598with `git clone`.
599
600
601# Mail Server #
602
603TODO
604
605TODO
606
607TODO
608
609TODO
610
611TODO
612
613TODO
614
615TODO
616
617TODO
618