Added instructions to sysadmin notes for placing a 'tags' button on the gitweb toolbar.
[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";
adccee6a
AT
462
463 # Add a toolbar option with the 'git clone url' and an
464 # option to display all tags.
465 $feature{'actions'}{'default'} = [
466 ('clone url', 'git://git.subgeniuskitty.com/%n', 'summary'),
467 ('tags', 'https://git.subgeniuskitty.com/%n/tags', 'summary')
468 ];
4ccc706c
AT
469
470 # Category name is read from .git/category, in the same manner as .git/description.
471 $projects_list_group_categories = 1;
472 $project_list_default_category = "misc";
473
474 # Needed for displaying README files.
475 $prevent_xss = 0;
476
477 # Enable syntax highlighting.
478 $feature{'highlight'}{'default'} = [1];
479
480 ################################################################################
481
482 # Enable blame, pickaxe search, snapshop, search, and grep
483 # support, but still allow individual projects to turn them off.
484 # These are features that users can use to interact with your Git trees. They
485 # consume some CPU whenever a user uses them, so you can turn them off if you
486 # need to. Note that the 'override' option means that you can override the
487 # setting on a per-repository basis.
488 $feature{'blame'}{'default'} = [1];
489 $feature{'blame'}{'override'} = [1];
490
491 $feature{'pickaxe'}{'default'} = [1];
492 $feature{'pickaxe'}{'override'} = [1];
493
494 $feature{'snapshot'}{'default'} = [1];
495 $feature{'snapshot'}{'override'} = [1];
496
497 $feature{'search'}{'default'} = [1];
498
499 $feature{'grep'}{'default'} = [1];
500 $feature{'grep'}{'override'} = [1];
501
502Create the directory `/srv/gitweb_cache`. It should be readable by the web
503server (`www-data`), but owned by `ataylor:ataylor` so that simple scripts like
504the following can make repositories public/private.
505
506 #!/usr/local/bin/bash
507 #
508 # Usage: sgkgit-make-public project_name
509 # Make a repo accessible through gitweb and git-daemon.
510
511 if [ "$#" -ne 1 ]; then
512 echo "Must specify repo name as only parameter."
513 exit 2
514 fi
515
516 read -p "Enter a category name: " category
517 read -p "Enter a description: " description
518
519 echo "You entered:"
520 printf "\tCategory: $category \n"
521 printf "\tDescription: $description \n"
522 read -p "Confirm (y/n)?: " confirm
523
524 if [ "$confirm" == "y" ]; then
525 ssh ataylor@git.subgeniuskitty.com "cd /srv/gitweb_cache && rm -rf $@ && git clone /srv/git/$@"
526 ssh ataylor@git.subgeniuskitty.com "cd /srv/git && \
527 printf '#!/usr/bin/bash\ncd /srv/gitweb_cache/$@\ngit --git-dir=.git pull\n' > \
528 /srv/git/$@/hooks/post-update && chmod +x /srv/git/$@/hooks/post-update"
529 ssh ataylor@git.subgeniuskitty.com "echo \"$category\" > /srv/gitweb_cache/$@/.git/category"
530 ssh ataylor@git.subgeniuskitty.com "echo \"$description\" > /srv/gitweb_cache/$@/.git/description"
531 fi
532
533In addition to cloning the git repo into `/srv/gitweb_cache` from the private,
534SSH-only collection of repositories in `/srv/git`, this also creates a
535`post-update` hook in the bare repo in `/srv/git` to ensure that gitweb's cache
536is updated every time a push is made via SSH to the private repo.
537
538This script also sets the `.git/description` and `.git/category` files needed
539by gitweb for displays like the project summary page. These are not under
540version control and may be directly edited to change what is displayed in
541gitweb.
542
543We can remove the public cache, making the repo private-only as shown in this
544script.
545
546 #!/usr/local/bin/bash
547 #
548 # Usage: sgkgit-make-private project_name
549 # Make a repo inaccessible through gitweb and git-daemon.
550
551 if [ "$#" -ne 1 ]; then
552 echo "Must specify repo name as only parameter."
553 exit 2
554 fi
555
556 ssh ataylor@git.subgeniuskitty.com "rm -rf /srv/gitweb_cache/$@ && rm /srv/git/$@/hooks/post-update"
557
558Any repos made private/public via this method are immediately reflected in
559`gitweb` and `git-daemon`.
560
561
562## Read-Only: Git Daemon ##
563
564Since `git` was already installed, simply create a new service profile in
565`/etc/systemd/system/git-daemon.service`.
566
567 [Unit]
568 Description=Start Git Daemon
569
570 [Service]
571 ExecStart=/usr/bin/git daemon --export-all --reuseaddr --base-path=/srv/gitweb_cache/ /srv/gitweb_cache/
572
573 Restart=always
574 RestartSec=500ms
575
576 StandardOutput=syslog
577 StandardError=syslog
578 SyslogIdentifier=git-daemon
579
580 User=ataylor
581 Group=ataylor
582
583 [Install]
584 WantedBy=multi-user.target
585
586Then start the service with `systemctl daemon-reload` and `systemctl start
587git-daemon`. Verify functionality before enabling daemon-autostart with
588`systemctl enable git-daemon`.
589
590The `--export-all` flag tells `git-daemon` to export all repositories located
591at the specified path, regardless of the presence (or lack) of the file
592`.git/git-daemon-export-ok` in the individual repository. We do this because
593only public repos are checked out to this folder. All private repos are kept
594entirely elsewhere.
595
596The directory `/srv/gitweb_cache` should have already been created when
597installing gitweb. If not, go read those instructions.
598
599A repository located at `/srv/gitweb_cache/repo_name` may be cloned through
600`git-daemon` at
601[git://git.subgeniuskitty.com/repo_name](git://git.subgeniuskitty.com/repo_name)
602with `git clone`.
603
604
605# Mail Server #
606
607TODO
608
609TODO
610
611TODO
612
613TODO
614
615TODO
616
617TODO
618
619TODO
620
621TODO
622