Files
tilde.club/docs/publishing.md
T

7.1 KiB

Gopher, Gemini, and finger

These are separate services with separate public directories. Creating a public_gopher or public_gemini directory does not make it reachable until the corresponding service is configured and tested.

Gopher with Fossor

The current host uses Fossor, with systemd accepting TCP connections on port 70 and starting fossor@.service for each connection. The effective collected settings are:

  • MaxConnections=128, MaxConnectionsPerSource=8 on the socket.
  • Service UID/group gopher, a 300-second runtime limit, and a 10-second stop timeout.
  • Options read from /etc/default/fossor and then /etc/sysconfig/fossor.

The values of those option files were not supplied. The new-host setup below chooses its own root and initially disables executable maps/CGI. A proxy can make every connection appear to come from one address; account for that before copying a per-source connection limit.

Build and install on a new host

The reviewed Fossor checkout provides release v3.1.2. Check the project's release/security notes before installation and record the actual tag/commit:

dnf install gcc make git
git clone --branch v3.1.2 https://github.com/tildeclub/Fossor.git
cd Fossor
./configure --listener=systemd --hostname=tilde.example.net \
    --gopherroot=/srv/gopher --systemd=/etc/systemd/system
make

Review the generated files, then create a dedicated service identity and install from that reviewed checkout:

useradd --system --user-group --no-create-home --shell /usr/sbin/nologin gopher
install -d -o root -g root -m 0755 /srv/gopher
make install

Run the privileged commands as root; do the source checkout and build as an ordinary build account. make install installs the executable, options, and units. It can replace existing Fossor files, so these commands are for a new installation. For migration, use the project's migration notes and preserve existing content and config first.

Set OPTIONS in /etc/sysconfig/fossor to a new-host configuration such as:

OPTIONS="-h tilde.example.net -r /srv/gopher -u public_gopher -nv -nx"

-nx disables executable content; -nv disables Gopher virtual hosting. Keep one authoritative options value: the service reads both option files, with the later environment file taking precedence for repeated variables.

Use systemctl edit fossor@.service for a local override:

[Service]
User=gopher
Group=gopher
RuntimeMaxSec=300
TimeoutStopSec=10

Create a root gophermap and one member's public directory. A menu file uses literal tabs between link fields:

printf '!Our tilde\n~\n' > /srv/gopher/gophermap
install -d -o member -g member -m 0755 /home/member/public_gopher
printf '!My gopher space\nHello from my tilde.\n' > /home/member/public_gopher/gophermap
chown member:member /home/member/public_gopher/gophermap
chmod 0644 /srv/gopher/gophermap /home/member/public_gopher/gophermap
systemctl daemon-reload
systemctl enable --now fossor.socket

Allow TCP 70 in the chosen firewall manager, then test a Gopher client against gopher://tilde.example.net/ and gopher://tilde.example.net/1/~member/. Use world-readable public content with traversable parent directories; Fossor's public-file checks are stricter than merely allowing its UID to read a file. Test that private files and traversal attempts are refused. Keep an executable map test out of the live service until you deliberately enable execution and understand which UID will run it.

Check systemctl status fossor.socket and journalctl -u 'fossor@*'. Keep a version/update record for the manually installed binary; an OS package update will not update it automatically.

Gemini with gemserv

The collected service runs as gemini, with working directory /srv/gemini/gemserv and this command:

/srv/gemini/gemserv/target/release/gemserv config.toml

Its actual config.toml and source revision were not supplied. Do not infer its vhosts, TLS keys, or execution policy from the unit alone.

For a new installation, review the upstream project, its dependency/update status, and the example config shipped with the revision you choose. Build as an ordinary account with the lockfile, using cargo build --release --locked. If the chosen revision supports the static-only feature set, use --no-default-features when that is all you intend to offer. Install the built binary as root-owned /usr/local/sbin/gemserv; record its source revision.

The published gemserv config schema uses interface, [[server]], hostname, dir, cert, key, index, and usrdir for ~/public_gemini. Use the small example only with a revision supporting that schema; it is not a copy of the uncollected live config.

Create a dedicated gemini system user/group, root-owned public content at /srv/gemini/content, and a root-owned config directory at /etc/gemserv. Create the new-host service identity and paths before installing its config:

useradd --system --user-group --no-create-home --shell /usr/sbin/nologin gemini
install -d -o root -g gemini -m 0750 /etc/gemserv
install -d -o root -g root -m 0755 /srv/gemini/content

A self-signed Gemini certificate for the example hostname can be made with:

openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -sha256 \
    -days 365 -noenc -subj '/CN=tilde.example.net' \
    -addext 'subjectAltName=DNS:tilde.example.net' \
    -keyout /etc/gemserv/key.pem -out /etc/gemserv/cert.pem
chown root:gemini /etc/gemserv/key.pem
chmod 0640 /etc/gemserv/key.pem
chmod 0644 /etc/gemserv/cert.pem

Gemini clients commonly keep certificate fingerprints. Record your replacement and renewal policy so a changed certificate can be explained. Do not loosen the permissions on a shared HTTPS private key to make a different service work.

Install the example unit as /etc/systemd/system/gemserv.service, after creating its user and paths. Add index.gmi under the main content root and a member's public_gemini at 0755, with public files at 0644. Load the unit and start it:

systemctl daemon-reload
systemctl enable --now gemserv
journalctl -u gemserv --since today

Open TCP 1965 only when ready. Test the root and the member's URL with a Gemini client, both externally and over IPv6 if advertised. Also test missing paths, private files, symlinks, encoded traversal, and the deliberately disabled CGI policy. Set timeouts and resource controls using the chosen version and the service manager; do not assume a web server's limits apply to Gemini.

Finger

The current host runs a local Python fingerd.py as efingerd, with only the bind-service capability needed to listen on a privileged port. Its source was not part of the collection, so this guide does not invent its response format or access policy.

Finger is optional. Before offering it, decide which account/profile fields are public, how members opt out, what the response says about logged-in users, and how much output or request volume you allow. A public finger response should not disclose private files just because the daemon can read them.