|
1
|
# Serving via httpd on OpenBSD |
|
2
|
|
|
3
|
[`httpd`][httpd] is the default web server that is included in the base |
|
4
|
install on OpenBSD. It's minimal and lightweight but secure and capable, |
|
5
|
and provides a clean interface for setting up a Fossil server using |
|
6
|
FastCGI. |
|
7
|
|
|
8
|
This article will detail the steps required to setup a TLS-enabled |
|
9
|
`httpd` configuration that serves multiple Fossil repositories out of |
|
10
|
a single directory within a chroot, and allow `ssh` access to create |
|
11
|
new repositories remotely. |
|
12
|
|
|
13
|
**NOTE:** The following instructions assume an OpenBSD 6.7 installation. |
|
14
|
|
|
15
|
[httpd]: https://www.openbsd.org/papers/httpd-asiabsdcon2015.pdf |
|
16
|
|
|
17
|
## <a id="fslinstall"></a>Install Fossil |
|
18
|
|
|
19
|
Use the OpenBSD package manager `pkg_add` to install Fossil, making sure |
|
20
|
to select the statically linked binary. |
|
21
|
|
|
22
|
```console |
|
23
|
$ doas pkg_add fossil |
|
24
|
quirks-3.325 signed on 2020-06-12T06:24:53Z |
|
25
|
Ambiguous: choose package for fossil |
|
26
|
0: <None> |
|
27
|
1: fossil-2.10v0 |
|
28
|
2: fossil-2.10v0-static |
|
29
|
Your choice: 2 |
|
30
|
fossil-2.10v0-static: ok |
|
31
|
``` |
|
32
|
|
|
33
|
This installs Fossil into the chroot. To facilitate local use, create a |
|
34
|
symbolic link of the fossil executable into `/usr/local/bin`. |
|
35
|
|
|
36
|
```console |
|
37
|
$ doas ln -s /var/www/bin/fossil /usr/local/bin/fossil |
|
38
|
``` |
|
39
|
|
|
40
|
As a privileged user, create the file `/var/www/cgi-bin/scm` with the |
|
41
|
following contents to make the CGI script that `httpd` will execute in |
|
42
|
response to `fsl.domain.tld` requests; all paths are relative to the |
|
43
|
`/var/www` chroot. |
|
44
|
|
|
45
|
```sh |
|
46
|
#!/bin/fossil |
|
47
|
directory: /htdocs/fsl.domain.tld |
|
48
|
notfound: https://domain.tld |
|
49
|
repolist |
|
50
|
errorlog: /logs/fossil.log |
|
51
|
``` |
|
52
|
|
|
53
|
The `directory` directive instructs Fossil to serve all repositories |
|
54
|
found in `/var/www/htdocs/fsl.domain.tld`, while `errorlog` sets logging |
|
55
|
to be saved to `/var/www/logs/fossil.log`; create the repository |
|
56
|
directory and log file—making the latter owned by the `www` user, and |
|
57
|
the script executable. |
|
58
|
|
|
59
|
```console |
|
60
|
$ doas mkdir /var/www/htdocs/fsl.domain.tld |
|
61
|
$ doas touch /var/www/logs/fossil.log |
|
62
|
$ doas chown www /var/www/logs/fossil.log |
|
63
|
$ doas chmod 660 /var/www/logs/fossil.log |
|
64
|
$ doas chmod 755 /var/www/cgi-bin/scm |
|
65
|
``` |
|
66
|
|
|
67
|
## <a id="chroot"></a>Setup chroot |
|
68
|
|
|
69
|
Fossil needs both `/dev/random` and `/dev/null`, which aren't accessible |
|
70
|
from within the chroot, so need to be constructed; `/var`, however, is |
|
71
|
mounted with the `nodev` option. Rather than removing this default |
|
72
|
setting, create a small memory filesystem and then mount it on to |
|
73
|
`/var/www/dev` with [`mount_mfs(8)`][mfs] so that the `random` and |
|
74
|
`null` device files can be created. In order to avoid necessitating a |
|
75
|
startup script to recreate the device files at boot, create a template |
|
76
|
of the needed ``/dev`` tree to automatically populate the memory |
|
77
|
filesystem. |
|
78
|
|
|
79
|
```console |
|
80
|
$ doas mkdir /var/www/dev |
|
81
|
$ doas install -d -g daemon /template/dev |
|
82
|
$ cd /template/dev |
|
83
|
$ doas /dev/MAKEDEV urandom |
|
84
|
$ doas mknod -m 666 null c 2 2 |
|
85
|
$ doas mount_mfs -s 1M -P /template/dev /dev/sd0b /var/www/dev |
|
86
|
$ ls -l |
|
87
|
total 0 |
|
88
|
crw-rw-rw- 1 root daemon 2, 2 Jun 20 08:56 null |
|
89
|
lrwxr-xr-x 1 root daemon 7 Jun 18 06:30 random@ -> urandom |
|
90
|
crw-r--r-- 1 root wheel 45, 0 Jun 18 06:30 urandom |
|
91
|
``` |
|
92
|
|
|
93
|
[mfs]: https://man.openbsd.org/mount_mfs.8 |
|
94
|
|
|
95
|
To make the mountable memory filesystem permanent, open `/etc/fstab` as |
|
96
|
a privileged user and add the following line to automate creation of the |
|
97
|
filesystem at startup: |
|
98
|
|
|
99
|
```console |
|
100
|
swap /var/www/dev mfs rw,-s=1048576,-P=/template/dev 0 0 |
|
101
|
``` |
|
102
|
|
|
103
|
The same user that executes the fossil binary must have writable access |
|
104
|
to the repository directory that resides within the chroot; on OpenBSD |
|
105
|
this is `www`. In addition, grant repository directory ownership to the |
|
106
|
user who will push to, pull from, and create repositories. |
|
107
|
|
|
108
|
```console |
|
109
|
$ doas chown -R user:www /var/www/htdocs/fsl.domain.tld |
|
110
|
$ doas chmod 770 /var/www/htdocs/fsl.domain.tld |
|
111
|
``` |
|
112
|
|
|
113
|
## <a id="httpdconfig"></a>Configure httpd |
|
114
|
|
|
115
|
On OpenBSD, [httpd.conf(5)][httpd] is the configuration file for |
|
116
|
`httpd`. To setup the server to serve all Fossil repositores within the |
|
117
|
directory specified in the CGI script, and automatically redirect |
|
118
|
standard HTTP requests to HTTPS—apart from [Let's Encrypt][LE] |
|
119
|
challenges issued in response to [acme-client(1)][acme] certificate |
|
120
|
requests—create `/etc/httpd.conf` as a privileged user with the |
|
121
|
following contents. |
|
122
|
|
|
123
|
[LE]: https://letsencrypt.org |
|
124
|
[acme]: https://man.openbsd.org/acme-client.1 |
|
125
|
[httpd.conf(5)]: https://man.openbsd.org/httpd.conf.5 |
|
126
|
|
|
127
|
```apache |
|
128
|
server "fsl.domain.tld" { |
|
129
|
listen on * port http |
|
130
|
root "/htdocs/fsl.domain.tld" |
|
131
|
location "/.well-known/acme-challenge/*" { |
|
132
|
root "/acme" |
|
133
|
request strip 2 |
|
134
|
} |
|
135
|
location * { |
|
136
|
block return 301 "https://$HTTP_HOST$REQUEST_URI" |
|
137
|
} |
|
138
|
location "/*" { |
|
139
|
fastcgi { param SCRIPT_FILENAME "/cgi-bin/scm" } |
|
140
|
} |
|
141
|
} |
|
142
|
|
|
143
|
server "fsl.domain.tld" { |
|
144
|
listen on * tls port https |
|
145
|
root "/htdocs/fsl.domain.tld" |
|
146
|
tls { |
|
147
|
certificate "/etc/ssl/domain.tld.fullchain.pem" |
|
148
|
key "/etc/ssl/private/domain.tld.key" |
|
149
|
} |
|
150
|
hsts { |
|
151
|
max-age 15768000 |
|
152
|
preload |
|
153
|
subdomains |
|
154
|
} |
|
155
|
connection max request body 104857600 |
|
156
|
location "/*" { |
|
157
|
fastcgi { param SCRIPT_FILENAME "/cgi-bin/scm" } |
|
158
|
} |
|
159
|
location "/.well-known/acme-challenge/*" { |
|
160
|
root "/acme" |
|
161
|
request strip 2 |
|
162
|
} |
|
163
|
} |
|
164
|
``` |
|
165
|
|
|
166
|
[The default limit][dlim] for HTTP messages in OpenBSD’s `httpd` server |
|
167
|
is 1 MiB. Fossil chunks its sync protocol such that this is not |
|
168
|
normally a problem, but when sending [unversioned content][uv], it uses |
|
169
|
a single message for the entire file. Therefore, if you will be storing |
|
170
|
files larger than this limit as unversioned content, you need to raise |
|
171
|
the limit as we’ve done above with the “`connection max request body`” |
|
172
|
setting, raising the limit to 100 MiB. |
|
173
|
|
|
174
|
[dlim]: https://man.openbsd.org/httpd.conf.5#connection |
|
175
|
[uv]: ../../unvers.wiki |
|
176
|
|
|
177
|
**NOTE:** If not already in possession of an HTTPS certificate, comment |
|
178
|
out the `https` server block and proceed to securing a free |
|
179
|
[Let's Encrypt Certificate](#letsencrypt); otherwise skip to |
|
180
|
[Start `httpd`](#starthttpd). |
|
181
|
|
|
182
|
|
|
183
|
## <a id="letsencrypt"></a>Let's Encrypt Certificate |
|
184
|
|
|
185
|
In order for `httpd` to serve HTTPS, secure a free certificate from |
|
186
|
Let's Encrypt using `acme-client`. Before issuing the request, however, |
|
187
|
ensure you have a zone record for the subdomain with your registrar or |
|
188
|
nameserver. Then open `/etc/acme-client.conf` as a privileged user to |
|
189
|
configure the request. |
|
190
|
|
|
191
|
```dosini |
|
192
|
authority letsencrypt { |
|
193
|
api url "https://acme-v02.api.letsencrypt.org/directory" |
|
194
|
account key "/etc/acme/letsencrypt-privkey.pem" |
|
195
|
} |
|
196
|
|
|
197
|
authority letsencrypt-staging { |
|
198
|
api url "https://acme-staging.api.letsencrypt.org/directory" |
|
199
|
account key "/etc/acme/letsencrypt-staging-privkey.pem" |
|
200
|
} |
|
201
|
|
|
202
|
domain domain.tld { |
|
203
|
alternative names { www.domain.tld fsl.domain.tld } |
|
204
|
domain key "/etc/ssl/private/domain.tld.key" |
|
205
|
domain certificate "/etc/ssl/domain.tld.crt" |
|
206
|
domain full chain certificate "/etc/ssl/domain.tld.fullchain.pem" |
|
207
|
sign with letsencrypt |
|
208
|
} |
|
209
|
``` |
|
210
|
|
|
211
|
Start `httpd` with the new configuration file, and issue the certificate |
|
212
|
request. |
|
213
|
|
|
214
|
```console |
|
215
|
$ doas rcctl start httpd |
|
216
|
$ doas acme-client -vv domain.tld |
|
217
|
acme-client: /etc/acme/letsencrypt-privkey.pem: account key exists (not creating) |
|
218
|
acme-client: /etc/acme/letsencrypt-privkey.pem: loaded RSA account key |
|
219
|
acme-client: /etc/ssl/private/domain.tld.key: generated RSA domain key |
|
220
|
acme-client: https://acme-v01.api.letsencrypt.org/directory: directories |
|
221
|
acme-client: acme-v01.api.letsencrypt.org: DNS: 172.65.32.248 |
|
222
|
... |
|
223
|
N(Q????Z???j?j?>W#????b???? H????eb??T??*? DNosz(???n{L}???D???4[?B] (1174 bytes) |
|
224
|
acme-client: /etc/ssl/domain.tld.crt: created |
|
225
|
acme-client: /etc/ssl/domain.tld.fullchain.pem: created |
|
226
|
``` |
|
227
|
|
|
228
|
A successful result will output the public certificate, full chain of |
|
229
|
trust, and private key into the `/etc/ssl` directory as specified in |
|
230
|
`acme-client.conf`. |
|
231
|
|
|
232
|
```console |
|
233
|
$ doas ls -lR /etc/ssl |
|
234
|
-r--r--r-- 1 root wheel 2.3K Mar 2 01:31:03 2018 domain.tld.crt |
|
235
|
-r--r--r-- 1 root wheel 3.9K Mar 2 01:31:03 2018 domain.tld.fullchain.pem |
|
236
|
|
|
237
|
/etc/ssl/private: |
|
238
|
-r-------- 1 root wheel 3.2K Mar 2 01:31:03 2018 domain.tld.key |
|
239
|
``` |
|
240
|
|
|
241
|
Make sure to reopen `/etc/httpd.conf` to uncomment the second server |
|
242
|
block responsible for serving HTTPS requests before proceeding. |
|
243
|
|
|
244
|
## <a id="starthttpd"></a>Start `httpd` |
|
245
|
|
|
246
|
With `httpd` configured to serve Fossil repositories out of |
|
247
|
`/var/www/htdocs/fsl.domain.tld`, and the certificates and key in place, |
|
248
|
enable and start `slowcgi`—OpenBSD's FastCGI wrapper server that will |
|
249
|
execute the above Fossil CGI script—before checking that the syntax of |
|
250
|
the `httpd.conf` configuration file is correct, and (re)starting the |
|
251
|
server (if still running from requesting a Let's Encrypt certificate). |
|
252
|
|
|
253
|
```console |
|
254
|
$ doas rcctl enable slowcgi |
|
255
|
$ doas rcctl start slowcgi |
|
256
|
slowcgi(ok) |
|
257
|
$ doas httpd -vnf /etc/httpd.conf |
|
258
|
configuration OK |
|
259
|
$ doas rcctl start httpd |
|
260
|
httpd(ok) |
|
261
|
``` |
|
262
|
|
|
263
|
## <a id="clientconfig"></a>Configure Client |
|
264
|
|
|
265
|
To facilitate creating new repositories and pushing them to the server, |
|
266
|
add the following function to your `~/.cshrc` or `~/.zprofile` or the |
|
267
|
config file for whichever shell you are using on your development box. |
|
268
|
|
|
269
|
```sh |
|
270
|
finit() { |
|
271
|
fossil init $1.fossil && \ |
|
272
|
chmod 664 $1.fossil && \ |
|
273
|
fossil open $1.fossil && \ |
|
274
|
fossil user password $USER $PASSWD && \ |
|
275
|
fossil remote-url https://$USER:[email protected]/$1 && \ |
|
276
|
rsync --perms $1.fossil [email protected]:/var/www/htdocs/fsl.domain.tld/ >/dev/null && \ |
|
277
|
chmod 644 $1.fossil && \ |
|
278
|
fossil ui |
|
279
|
} |
|
280
|
``` |
|
281
|
|
|
282
|
This enables a new repository to be made with `finit repo`, which will |
|
283
|
create the fossil repository file `repo.fossil` in the current working |
|
284
|
directory; by default, the repository user is set to the environment |
|
285
|
variable `$USER`. It then opens the repository and sets the user |
|
286
|
password to the `$PASSWD` environment variable (which you can either set |
|
287
|
with `export PASSWD 'password'` on the command line or add to a |
|
288
|
*secured* shell environment file), and the `remote-url` to |
|
289
|
`https://fsl.domain.tld/repo` with the credentials of `$USER` who is |
|
290
|
authenticated with `$PASSWD`. Finally, it `rsync`'s the file to the |
|
291
|
server before opening the local repository in your browser where you can |
|
292
|
adjust settings such as anonymous user access, and set pertinent |
|
293
|
repository details. Thereafter, you can add files with `fossil add`, and |
|
294
|
commit with `fossil ci -m 'commit message'` where Fossil, by default, |
|
295
|
will push to the `remote-url`. It's suggested you read the |
|
296
|
[Fossil documentation][documentation]; with a sane and consistent |
|
297
|
development model, the system is much more efficient and cohesive than |
|
298
|
`git`—so the learning curve is not steep at all. |
|
299
|
|
|
300
|
[documentation]: https://fossil-scm.org/home/doc/trunk/www/permutedindex.html |
|
301
|
|
|
302
|
*[Return to the top-level Fossil server article.](../)* |
|
303
|
|