Fossil SCM

fossil-scm / www / server / openbsd / fastcgi.md
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

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button