Fossil

Diff
Login

Differences From Artifact [37a864ffeb]:

To Artifact [bc99ba2b28]:


248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
  TCC += -DWITHOUT_ICONV
  TCC += -Dsocketlen_t=int
  TCC += -DSQLITE_MAX_MMAP_SIZE=0
</pre></blockquote>
</ul>


<h2 id="docker">5.0 Building a Docker Container</h2>

Fossil ships a <tt>Dockerfile</tt> at the top of its source tree which
you can build like so:

<pre><code>  $ docker build -t fossil .</code></pre>

If the image built successfully, you can create a container from it and
test that it runs:

<pre><code>  $ docker run --name fossil -p 9999:8080/tcp fossil</code></pre>

This shows us remapping the internal TCP listening port as 9999 on the
host. As a rule, there's little point to using the "<tt>fossil server
--port</tt>" feature inside a Docker container. Let it default to 8080
internally, then remap it to wherever you want it on the host instead.

Our stock <tt>Dockerfile</tt> configures Fossil with the default feature
set, so you may wish to modify the <tt>Dockerfile</tt> to add
configuration options, add APK packages to support those options, and so
forth. It also strips out all but the default and darkmode skins to save
executable space.

There are two convenience targets for you, <tt>make container-image</tt>
and <tt>make container-run</tt>. The first creates a versioned container
image, and the second does that and then launches a fresh container
based on that image. You can pass extra arguments to the first command
via the Makefile's <tt>DBFLAGS</tt> variable and to the second with the
<tt>DRFLAGS</tt> variable. To get the custom port setting as in second
command above, say:

<pre><code>  $ make container-run DRFLAGS='-p 9999:8080/tcp'</code></pre>

Contrast the raw "<tt>docker</tt>" commands above, which create an
<i>unversioned</i> image called <tt>fossil:latest</tt> and from that a
container simply called <tt>fossil</tt>. The unversioned names are more
convenient for interactive use, while the versioned ones are good for
CI/CD type applications since they avoid a conflict with past versions;
it lets you keep old containers around for quick roll-backs while
replacing them with fresh ones.


<h3 id="docker-storage">5.1 Repository Storage Options</h3>

If you want the container to serve an existing repository, there are at
least two right ways to do it.

The wrong way is to use the <tt>Dockerfile COPY</tt> command to bake it
into the image at build time. It will become one of the image's base
layers, so that each time you build a container from that image, the
repo gets reset to its build-time state. Worse, restarting the container
will do the same thing, since the base image layers are immutable in
Docker. This is almost certainly not what you want.

The correct ways put the repo into the <i>container</i> created from the
<i>image</i>, not in the image itself.


<h4>5.1.1 Storing the Repo Inside the Container</h4>

The simplest method is to stop the container if it was running,
then say:

<pre><code>  $ docker cp /path/to/my-project.fossil fossil:/jail/museum/repo.fossil
  $ docker start fossil
  $ docker exec fossil chown -R 499 /jail/museum</code></pre>

That copies the local Fossil repo into the container where the server
expects to find it, so that the "start" command causes it to serve from
that copied-in file instead. Since it lives atop the immutable base layers, it
persists as part of the container proper, surviving restarts.

Notice that the copy command changes the name of the repository database.
The container configuration expects it to be called
<tt>repo.fossil</tt>, which it almost certainly was not out on the host
system. This is because there is only one repository inside this
container, so we don't have to name it after the project it contains, as
is traditional. A generic name lets us hard-code the server start
command.

If you skip the "chown" command above and put "http://localhost:9999/" into
your browser, expecting to see the copied-in repo's home page, you will
get an opaque "Not Found" error. This is because the user and
group ID of the file will be that of your local user on the container's
host machine, which is unlikely to map to anything in the container's
<tt>/etc/passwd</tt> and <tt>/etc/group</tt> files, effectively
preventing the server from reading the copied-in repository file.
499 is the default "fossil" user ID inside the container, causing Fossil to run
with that user's privileges after it enters the chroot.
(See [#docker-args | below] for how to change this default.)
You don't have to restart the server after fixing this with
<tt>chmod</tt>: simply reload the browser, and Fossil will try again.


<h4 id="docker-bind-mount">5.1.2 Storing the Repo Outside the Container</h4>

The simple storage method above has a problem: Docker containers are designed to be
killed off at the slightest cause, rebuilt, and redeployed. If you do
that with the repo inside the container, it gets destroyed, too. The
solution is to replace the "run" command above with the following:

<pre><code>  $ docker run \
    --name fossil-bind-mount -p 9999:8080 \
    -v ~/museum:/jail/museum fossil
</code></pre>

Because this bind mount maps a host-side directory (<tt>~/museum</tt>)
into the container, you don't need to <tt>docker cp</tt> the repo into
the container at all.  It still expects to find the repository as
<tt>repo.fossil</tt> under that directory, but now both the host and the
container can see that file.  (Beware: This may create a
[https://www.sqlite.org/howtocorrupt.html | risk of data corruption] due
to SQLite locking issues if you try to modify the DB from both sides at
once.)

Instead of a bind mount, you could instead set up
a separate [https://docs.docker.com/storage/volumes/ | Docker volume],
at which point you <i>would</i> need to <tt>docker cp</tt> the repo file
into the container.

Either way, files in these mounted directories have a lifetime independent
of the container(s) they're mounted into.  When you need to
rebuild the container or its underlying image — such as to upgrade to a newer version of Fossil
— the external directory remains behind and gets remapped into the new container
when you recreate it with <tt>-v</tt>.


<h3 id="docker-security">5.2 Security</h3>

<h4 id="docker-chroot">5.2.1 Why Chroot?</h4>

A potentially surprising feature of this container is that it runs
Fossil as root. Since that causes [./chroot.md | Fossil's chroot jail
feature] to kick in, and a Docker container is a type of über-jail
already, you may be wondering why we bother. Instead, why not either:

  #  run <tt>fossil server --nojail</tt> to skip the internal chroot; or
  #  set "<tt>USER fossil</tt>" in the Dockerfile so it starts Fossil as
     that user instead

The reason is, although this container is quite stripped-down by today's
standards, it's based on the [https://www.busybox.net/BusyBox.html |
surprisingly powerful Busybox project].  (This author made a living for
years in the early 1990s using Unix systems that were less powerful than
this container.) If someone ever figured out how to make a Fossil binary
execute arbitrary commands on the host or to open up a remote shell,
the power available to them at that point would make it likely that
they'd be able to island-hop from there into the rest of your network.
That power is there for you as the system administrator, to let you
inspect the container's runtime behavior, change things on the fly, and
so forth. Fossil proper doesn't need that power; if we take it away via
this cute double-jail dance, we keep any potential attacker from making
use of it should they ever get in.

Having said this, know that
we deem this risk low since a) it's never happened, that we know of;
and b) we haven't enabled any of the risky features of Fossil such as
[https://fossil-scm.org/forum/forumpost/42e0c16544 | TH1 docs].
Nevertheless, we believe in defense-in-depth.

If you say something like "<tt>docker exec fossil ps</tt>" while the
system is idle, it's likely to report a single <tt>fossil</tt> process
running as <tt>root</tt> even though the chroot feature is documented as
causing Fossil to drop its privileges in favor of the owner of the
repository database or its containing folder.  If the repo file is owned
by the in-container user "<tt>fossil</tt>", why is the server still
running as root?

It's because you're seeing only the parent
process, which assumes it's running on bare metal or a VM and thus
may need to do rootly things like listening on port 80 or
443 before forking off any children to handle HTTP hits.
Fossil's chroot feature only takes effect in these child processes.
This is why you can fix broken
permissions with <tt>chown</tt> after the container is already running,
without restarting it: each hit reevaluates the repository file
permissions when deciding what user to become when dropping root
privileges.


<h4 id="docker-caps">5.2.2 Dropping Unnecessary Capabilities</h4>

The example commands given in this section create the container with
[https://docs.docker.com/engine/security/#linux-kernel-capabilities | a
default set of Linux kernel capabilities]. Although Docker strips almost
all of the traditional root capabilities away by default, and Fossil
doesn't need any of those it does take away, Docker does leave some
enabled that Fossil doesn't actually need. You can tighten the scope of
capabilities by adding a "<tt>--cap-drop LIST</tt>" option to your
container creation commands. Specifically:

  *  <b><tt>AUDIT_WRITE</tt></b>: Fossil doesn't write to the kernel's
     auditing log, and we can't see any reason you'd want to be able to
     do that as an administrator shelled into the container, either.
     Auditing is something done on the host, not from inside each
     individual container.<p>
  *  <b><tt>CHOWN</tt></b>: The Fossil server never even calls
     <tt>chown(2)</tt>, and our image build process sets up all file
     ownership properly, to the extent that this is possible under the
     limitations of our automation.<p>
     Curiously, stripping this capability doesn't affect your ability to
     run commands like "<tt>chown -R fossil:fossil /jail/museum</tt>"
     when you're using bind mounts or external volumes — as we recommend
     [#docker-bind-mount | above] — because it's the host OS's kernel
     capabilities that affect the underlying <tt>chown(2)</tt> call in
     that case, not those of the container.<p>
     If for some reason you did have to change file ownership of
     in-container files, it's best to do that by changing the
     <tt>Dockerfile</tt> to suit, then rebuilding the container, since
     that bakes the need for the change into your reproducible build
     process. If you had to do it without rebuilding the container,
     [https://stackoverflow.com/a/45752205/142454 | there's a
     workaround] for the fact that capabilities are a create-time
     change, baked semi-indelibly into the container configuration.<p>
  *  <b><tt>FSETID</tt></b>: Fossil doesn't use the SUID and SGID bits
     itself, and our build process doesn't set those flags on any of the
     files.  Although the second fact means we can't see any harm from
     leaving this enabled, we also can't see any good reason to allow
     it, so we strip it.<p>
  *  <b><tt>KILL</tt></b>: The only place Fossil calls <tt>kill(2)</tt>
     is in the [./backoffice.md | backoffice], and then only for
     processes it created on earlier runs; it doesn't need the ability
     to kill processes created by other users. You might wish for this
     ability as an administrator shelled into the container, but you can
     pass the "<tt>docker exec --user</tt>" option to run commands
     within your container as the legitimate owner of the process,
     removing the need for this capability.<p>
  *  <b><tt>MKNOD</tt></b>: All device nodes are created at build time
     and are never changed at run time. Realize that the virtualized
     device nodes inside the container get mapped onto real devices on
     the host, so if an attacker ever got a root shell on the container,
     they might be able to do actual damage to the host if we didn't
     preemptively strip this capability away.<p>
  *  <b><tt>NET_BIND_SERVICE</tt></b>: With containerized deployment,
     Fossil never needs the ability to bind the server to low-numbered
     TCP ports, not even if you're running the server in production with
     TLS enabled and want the service bound to port 443. It's perfectly
     fine to let the Fossil instance inside the container bind to its
     default port (8080) because you can rebind it on the host with the
     "<tt>docker create --publish 443:8080</tt>" option. It's the
     container's <i>host</i> that needs this ability, not the container
     itself.<p> (Even the container runtime might not need that
     capability if you're [./ssl.wiki#server | terminating TLS with a
     front-end proxy]. You're more likely to say something like "<tt>-p
     localhost:12345:8080</tt>", then configure the reverse proxy to
     translate external HTTPS calls into HTTP directed at this internal
     port 12345.)<p>
  *  <b><tt>NET_RAW</tt></b>: Fossil itself doesn't use raw sockets, and
     our build process leaves out all the Busybox utilities that require
     them. Although that set includes common tools like <tt>ping</tt>,
     we foresee no compelling reason to use that or any of these other
     elided utilities — <tt>ether-wake</tt>, <tt>netstat</tt>,
     <tt>traceroute</tt>, and <tt>udhcp</tt> — inside the container. If
     you need to ping something, do it on the host.<p>
     If we did not take this hard-line stance, an attacker that broke
     into the container and gained root privileges might use raw sockets
     to do a wide array of bad things to any network the container is
     bound to.<p>
  *  <b><tt>SETFCAP, SETPCAP</tt></b>: There isn't much call for file
     permission granularity beyond the classic Unix ones inside the
     container, so we drop root's ability to change them.

All together, we recommend adding the following options to your
"<tt>docker run</tt>" commands, as well as to any "<tt>docker
create</tt>" command that will be followed by "<tt>docker start</tt>":

<pre><code>  --cap-drop AUDIT_WRITE \
  --cap-drop CHOWN \
  --cap-drop FSETID \
  --cap-drop KILL \
  --cap-drop MKNOD \
  --cap-drop NET_BIND_SERVICE \
  --cap-drop NET_RAW \
  --cap-drop SETFCAP \
  --cap-drop SETPCAP
</code></pre>

In the next section, we'll show a case where you create a container
without ever running it, making these options pointless.



<h3 id="docker-static">5.3 Extracting a Static Binary</h3>

Our 2-stage build process uses Alpine Linux only as a build host. Once
we've got everything reduced to the two key static binaries — Fossil and
Busybox — we throw all the rest of it away.

A secondary benefit falls out of this process for free: it's arguably
the easiest way to build a purely static Fossil binary for Linux. Most
modern Linux distros make this surprisingly difficult, but Alpine's
back-to-basics nature makes static builds work the way they used to,
back in the day. If that's what you're after, you can skip the "run"
command above and create a temporary container from the image, then
extract the executable from it instead:

<pre><code>  $ docker create --name fossil-static-tmp fossil
  $ docker cp fossil-static-tmp:/jail/bin/fossil .
  $ docker container rm fossil-static-tmp
</code></pre>

The resulting binary is the single largest file inside that container,
at about 6 MiB. (It's built stripped.)


<h3 id="docker-args">5.4 Container Build Arguments</h3>

<h4>5.4.1 Package Versions</h4>

You can override the default versions of Fossil and BusyBox that get
fetched in the build step.  To get the latest-and-greatest of
everything, you could say:

<pre><code>  $ docker build -t fossil \
    --build-arg FSLVER=trunk \
    --build-arg BBXVER=master .</code></pre>

(But don't, for reasons we will get to.)

Because the BusyBox configuration file we ship was created with and
tested against a specific stable release, that's the version we pull by
default. It does try to merge the defaults for any new configuration
settings into the stock set, but since it's possible this will fail, we
don't blindly update the BusyBox version merely because a new release
came out.  Someone needs to get around to vetting it against our stock
configuration first.

As for Fossil, it defaults to fetching the same version as the checkout
you're running the build command from, based on checkin ID. The most
common reason to override this is to get a release version:

<pre><code>  $ docker build -t fossil \
    --build-arg FSLVER=version-2.19 .</code></pre>

It's best to use a specific version number rather than the generic
"release" tag because Docker caches downloaded files and tries to reuse
them across builds. If you ask for "release" before a new version is
tagged and then immediately after, you expect to get two different
tarballs, but because the URL hasn't changed, if you have an old release
tarball in your Docker cache, you'll get the old version even if you
pass the "docker build --no-cache" option.

This is why we default to a checkin ID rather than a generic tag like
"trunk": so the URL will change each time you update your Fossil source
tree, forcing Docker to pull a fresh tarball.

<h4>5.4.2 User & Group IDs</h4>

The "fossil" user and group IDs inside the container default to 499.
Why?  Regular user IDs start at 500 or 1000 on most Unix type systems,
leaving those below it for "system" users like this Fossil daemon owner.
Since standard OS system users start at 0 and go upward, we started at
500 and went down one to reduce the chance of a conflict to as close to
zero as we can manage.

To change it to something else, say:

<pre><code>  $ docker build -t fossil --build-arg UID=501 .</code></pre>

This is particularly useful if you're putting your repository on a
Docker volume since the IDs "leak" out into the host environment via
file permissions.  You may therefore wish them to mean something on both
sides of the container barrier rather than have "499" appear on the host
in "<tt>ls -l</tt>" output.


<h2>6.0 Building on/for Android</h2>

<h3>6.1 Cross-compiling from Linux</h3>

The following instructions for building Fossil for Android via Linux,







|

<
<
|
<
|
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
<
|
<
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







248
249
250
251
252
253
254
255
256


257

258


259




















































































































































































260






































































261



262










263























































































264
265
266
267
268
269
270
  TCC += -DWITHOUT_ICONV
  TCC += -Dsocketlen_t=int
  TCC += -DSQLITE_MAX_MMAP_SIZE=0
</pre></blockquote>
</ul>


<h2 id="docker" name="oci">5.0 Building a Docker Container</h2>



The information on building Fossil inside an

[https://opencontainers.org/ | OCI container] is now in


[./containers.md | a separate document].



























































































































































































































































This includes the instructions on using the OCI container as an



expedient intermediary for building a statically-linked Fossil binary on










modern Linux platforms, which otherwise make this difficult.

























































































<h2>6.0 Building on/for Android</h2>

<h3>6.1 Cross-compiling from Linux</h3>

The following instructions for building Fossil for Android via Linux,