bring on more tildes hopefully

This commit is contained in:
deepend committed 2026-10-22 10:10:41 -06:00
1 parent c4d72ea94b
commit 109dda05c6
32 files changed
+2239 -1159

No files matched your search

+41 -33
View File
@@ -1,49 +1,57 @@
# Accounting
# Login and process accounting
This page collects simple command-line checks for understanding shell usage on a multi-user host.
Accounting is useful for understanding use and investigating a problem. It is
not a reliable count of people and it is not proof of what someone intended.
Tell members what you retain and restrict access to logs containing addresses
and commands.
## Longest cumulative login time (per user)
The historical one-liner below is still useful, but this version is easier to read and less dependent on shell backticks:
```bash
users | tr ' ' '\n' | sort -u | while read -r user; do
ac "$user" | awk -v u="$user" '{print $1, u}'
done | sort -n
```
### Notes
- `ac` reads connection accounting data (typically from `/var/log/wtmp`).
- Results are cumulative and depend on log retention.
- On some systems, log rotation or reboots can make totals look lower than expected.
## Who is currently online
## Current sessions
```bash
who
w
loginctl list-sessions
```
If you only need usernames:
These commands answer different questions. A persistent tmux session, a user
service, and a current terminal login are not the same thing. Some terminal
programs also update traditional login records differently.
## Login history
On systems with traditional wtmp records:
```bash
who | awk '{print $1}' | sort -u
last
ac -p
```
## Most recent login for each user
```bash
lastlog
```
This is useful for finding dormant accounts and checking recent activity.
## Total command usage summary (process accounting)
If process accounting is enabled, you can summarize command usage:
`ac -p` summarizes the retained connection records for all recorded users.
Do not start with `users` if the goal is to count everybody: that limits the
input to people logged in at the time you run the command. Rotated or missing
records change the totals, and concurrent sessions are not unique human time.
Check which tools and storage the OS actually uses. Newer Fedora installations
may use lastlog2 and different login-record implementations. Use the installed
`lastlog2 --help` and service documentation rather than assuming the old
`lastlog` file is populated. A missing record does not establish that an account
has never been used.
## Process accounting
The collected host has `psacct.service` active. On a new Fedora host, install
and enable it only after deciding the logging and retention policy:
```bash
dnf install psacct
systemctl enable --now psacct
sa
```
Not all systems enable this by default; if `sa` has no data, verify process accounting configuration first.
`sa` summarizes completed-process accounting records. It does not replace
systemd resource limits, live process inspection, or application logs. Check
package log rotation so accounting data does not consume the filesystem.
For a service problem, start with that service's journal and resource counters.
For a user resource problem, inspect the relevant user slice as described in
[shell operations](shellserver.md#process-memory-and-cpu-limits).
+101
View File
@@ -0,0 +1,101 @@
# Accounts and first login
These notes describe the scripts collected from tilde.club on October 22, 2026.
They explain the dependencies and side effects that a replacement installation
must account for. Do not run the production scripts on a new machine before
changing the domains, paths, and external service integrations.
## What account creation currently does
`/usr/local/bin/makeuser` accepts a username, contact email, and SSH public key.
For a new username, it performs these operations in order:
1. Generate a 20-character password with `pwgen`.
2. Create a matching primary group if needed.
3. Create a Bash account and home, adding supplementary group ID 100.
4. Set the account password with `chpasswd`.
5. Render `/usr/local/bin/welcome-email.tmpl` and send it through `sendmail`.
The message is also copied to the new local user and the root mailbox.
6. Write the supplied public key to the new home's `.ssh/authorized_keys`.
7. Announce the account through the local `toot` command.
8. Remove matching entries from `/var/signups_current`.
9. Remove the skeleton's `.git` directory and README from the new home.
10. Sort the password database with `pwck -s`.
11. Request an XFS block quota of 1 GiB soft and 3 GiB hard.
12. Create a ZNC account with the generated password and a three-network limit.
13. Add the contact address to the local helpdesk.
The mailing-list subscription function is present but its call is commented
out. PHP pool creation is not called by this copy of `makeuser`.
If the username already exists, the script exits successfully without doing
the remaining work. That is important after an interrupted run: invoking it
again does not complete a partially provisioned account. Check each dependent
service before treating that account as ready.
## Dependencies to resolve on another host
The common group must exist before account creation. Group ID 100 is a local
choice; check its meaning on the target instead of assuming that ID names the
same group everywhere.
The skeleton must supply `.ssh` with mode 0700 and `authorized_keys` with mode
0600. The script writes into that file; it does not create the parent directory
or independently repair its ownership and permissions.
The production script also needs Python 3, `pwgen`, working outbound mail, its
welcome template, XFS quota support, and these local integrations:
- `/usr/local/bin/toot`
- `/usr/local/bin/znccreate.py`
- `/usr/local/bin/helpdesk_admin.sh`
These are separate components. Merely copying `makeuser` does not install them.
The production command changes external services as well as the Unix account,
so an account test must use a test configuration for those services too.
## First-login wizard
`/usr/local/bin/firstlogin` is a Ruby program using `tty-prompt` and `tty-screen`.
It also uses `figlet`, `passwd`, `chsh`, `/etc/shells`, `/etc/editors`, `tzselect`,
and optionally Byobu. Its sequence is:
1. Change the account password using the temporary password from the welcome
message.
2. Choose a login shell from an allowlist of names in `/etc/shells`.
3. Choose an editor and append `EDITOR` to `.bash_profile`.
4. Offer to enable Byobu on login.
5. Choose a timezone; write it to `.zshrc` for Zsh or `.bash_profile` otherwise.
6. Offer mail forwarding through `.forward`.
7. Offer to run `setup-2fa`.
8. Save pronouns in `.pronouns`.
9. Remove `.new_user`; launch Byobu if selected.
The wizard makes enrollment optional. The collected `setup-2fa` simply invokes
Google Authenticator, and the main SSH config accepts key-only login separately
from its PAM password/TOTP path. See [SSH and MFA](ssh.md) for the observed policy
and an explicit new-host enforcement example. The `.new_user` marker and shell
startup hook do not prevent someone from using another login path.
The collected wizard writes the editor setting to `.bash_profile` regardless
of the selected shell, and handles timezone setup specially only for Zsh. Test
those settings after selecting Fish or another shell before documenting them
as working for every choice.
## Checks for a newly created account
On a test installation, verify all of the following before sending an account
ready message:
- Primary and supplementary groups match the intended policy.
- The home, `.ssh`, and key file have the intended owner and permissions.
- A supplied key can log in, and an unrelated key cannot.
- First-login setup runs from each supported login path.
- Enrollment and subsequent SSH authentication behave as documented.
- The user's starter page works at the published URL.
- Mail delivery and any forwarding work.
- Quotas are enforced on the filesystem containing the home.
- ZNC and helpdesk provisioning completed, if those integrations are enabled.
Use a separate connection for login tests. Keep the existing admin connection
and provider console available while changing authentication.
+169
View File
@@ -0,0 +1,169 @@
# Backup and recovery
The collected host runs Borg 1.4.5. Root's crontab invokes
`/usr/bin/borgmatic` at 03:00 daily; that executable reports 2.1.9. A separate
`/usr/local/bin/borgmatic` reports 1.5.21 and appears earlier in the collector's
PATH. Use the absolute path from the actual job when checking versions,
validating configs, or reproducing a run.
The production backup configuration and credentials were not collected. The
example here is a new-host plan, not a claim about its repository, retention,
or current source coverage. Historical backup logs are also kept on the
operator's NAS; missing local journal history is not proof a backup did not run.
## Decide what must be recoverable
Include these in the plan:
| Data | What to capture |
| --- | --- |
| Accounts | `/etc`, homes, UIDs/GIDs, keys, shell defaults, quotas, and group memberships. |
| Member publishing and mail | Public directories, private mailbox data, forwarding and Sieve files. |
| Local code | `/usr/local`, source revisions, application lockfiles, service definitions, and build notes. |
| Operator web apps | Code, uploaded content, config, and a consistent database backup. |
| Service state | ZNC, INN spool/history, Gemini/Gopher content, and other app-specific state. |
| Secrets | Restricted encrypted copies of signing keys, service credentials, and recovery material. |
| Backup recovery | Repository key, passphrase, SSH credentials, destination, and restore instructions held off the host. |
Do not assume all state lives in `/home`. Inspect service units and application
configs. A Postfix queue restore needs its own policy because re-delivering
queued mail can duplicate messages. Reinstalling a database package and copying
its live data directory is not a substitute for a supported database restore.
## Prepare a remote repository
Install the maintained `borgbackup` and `borgmatic` packages. Configure a
dedicated backup account/key on the destination and verify the host key through
a trusted channel. Restrict the remote key to the intended repository and
operations using the destination's supported Borg setup.
Keep the repository passphrase in a root-only file outside the repo. Create
`/root/.config/borg` at 0700 and the passphrase file at 0600 using a root editor
or password-manager procedure; do not put the passphrase in a shell command,
git commit, or public config archive.
For the Borg **1.x** example, initialize a new repository from a root Bash shell:
```bash
export BORG_PASSCOMMAND='cat /root/.config/borg/passphrase'
repository='ssh://backup@backup.example.net/./tilde'
/usr/bin/borg init --encryption=repokey-blake2 "$repository"
/usr/bin/borg key export "$repository" /root/tilde-borg-key-export
```
Store the export and the passphrase off the source host through a protected
channel. Test access from a recovery machine. Do not initialize a repository
that is already in use, and do not substitute Borg 2 commands or formats into a
Borg 1 backup without planning that migration.
## Configure borgmatic
Review [config.yaml](../examples/borgmatic/config.yaml). Put the adjusted file
in `/etc/borgmatic/config.yaml` with root ownership and mode 0600. It uses the
flat borgmatic 2.x schema, not the old 1.5 `location/storage/retention` sections.
Create the database-dump directory at mode 0700 before validation:
```bash
install -d -o root -g root -m 0700 /var/backups/tilde-databases
/usr/bin/borgmatic config validate
```
Add existing service-state directories to `source_directories`. Keep the
missing-source check enabled so a typo or missing mount fails visibly. If a
required data mount can disappear while leaving an empty mountpoint behind,
add a mount-identity check too: merely testing that a directory exists is not
sufficient.
The example keeps daily, weekly, and monthly restore points. Choose retention
based on available storage and the longest period a bad change might go
unnoticed. A live filesystem backup is not a simultaneous snapshot of every
file. Quiesce services or use consistent snapshots where that matters.
## Consistent databases
Use borgmatic's supported database hooks for your installed version, or prepare
logical backups before running it. Here is a small wrapper for a host with a
local MariaDB instance. Save it as `/usr/local/sbin/backup-tilde`, root-owned and
mode 0700, and adjust the database section if MariaDB is not part of your host:
```bash
#!/usr/bin/env bash
set -euo pipefail
export PATH=/usr/sbin:/usr/bin:/sbin:/bin
umask 077
dump_dir=/var/backups/tilde-databases
install -d -m 0700 "$dump_dir"
dump_file=$(mktemp "$dump_dir/.mariadb.XXXXXXXX.sql")
trap 'rm -f -- "$dump_file"' EXIT
mariadb-dump --all-databases --single-transaction --routines --events \
--triggers --hex-blob > "$dump_file"
mv -f -- "$dump_file" "$dump_dir/mariadb.sql"
/usr/bin/borgmatic create prune compact check --verbosity 1
```
This assumes a local root socket login or a separately protected client config;
it does not embed a database password. `--single-transaction` gives a consistent
view of transactional tables, not arbitrary nontransactional tables or concurrent
schema changes. Check engines and use a suitable lock, snapshot, or maintenance
window where needed. A failed dump must stop the backup rather than silently
reuse yesterday's SQL.
For SQLite, use its backup API or the CLI `.backup` operation while the app is
running. Do not copy a database file while ignoring an active WAL. For other
databases, use their supported consistent export/snapshot method.
## Schedule and monitor
Run the wrapper manually once and inspect the resulting archive. For a
cron-based setup, a root crontab entry can be:
```cron
0 3 * * * /usr/bin/flock -n /run/tilde-backup.lock /usr/local/sbin/backup-tilde >> /var/log/tilde-backup.log 2>&1
```
Create the log at mode 0600 and configure root-only log rotation. This example
runs at 03:00 in cron's configured timezone; record that timezone. Do not enable
a second timer for the same job without intending two schedules. A skipped
lock acquisition is not a successful backup and needs to be visible in monitoring.
Alert when there is no successful archive within the expected interval, not
only when the scheduler launches a command. Check off-host capacity, archive
age, consistency checks, repository access, database exports, and restore tests.
Store monitoring/notification credentials separately from the public docs.
## Restore an account in isolation
On a recovery machine or isolated directory, use the matching Borg version and
repository key. With `BORG_PASSCOMMAND` set as above:
```bash
repository='ssh://backup@backup.example.net/./tilde'
/usr/bin/borg list "$repository"
install -d -m 0700 /root/tilde-restore-test
cd /root/tilde-restore-test
/usr/bin/borg extract --dry-run "$repository::ARCHIVE" home/member
/usr/bin/borg extract "$repository::ARCHIVE" home/member
```
Replace `ARCHIVE` with the exact archive name returned by the list command.
Check the stored path names first. Extract relative paths into the test
directory; do not start a restore by extracting over `/home` or `/etc`.
Verify file contents, private modes, ownership, symlinks, mail, public pages,
and database exports. Preserve numeric UIDs/GIDs or deliberately map them before
returning restored data to service. Reapply SELinux labels and re-create quota
limits on the restored filesystem. Test login on an isolated host, not by
announcing a restored production account before it has been checked.
For a whole-host restore, reinstall a supported OS, restore account identity
and data deliberately, restore databases using their matching tools, restore
service secrets, then enable services in dependency order. Keep outbound mail,
IRC bots, signup announcements, and scheduled jobs disabled until duplicate
external actions have been ruled out. Change DNS or reopen public access only
after the replacement has passed its service checks.
Record the last successful restore date, what was restored, and what failed.
A recovery key kept only inside the encrypted backup cannot recover that backup.
+119
View File
@@ -0,0 +1,119 @@
# Website, signups, and community tools
A working shell and a place to publish are enough to start. Add community tools
when someone will maintain them. Each operator application needs its own code,
service identity, writable data, update procedure, and backup entry.
## Source repositories
These public repos provide parts of the tilde.club setup. Review the code and
its revision before deployment; a public checkout is not necessarily identical
to a local production copy.
| Component | Source |
| --- | --- |
| Website and wiki | [tildeclub/site](https://github.com/tildeclub/site) |
| Home skeleton | [tildeclub/tilde.etcskel](https://github.com/tildeclub/tilde.etcskel) |
| Account creation | [tildeclub/makeuser](https://github.com/tildeclub/makeuser); the collected live script has local integrations. |
| First-login wizard | [tildeclub/welcome](https://github.com/tildeclub/welcome) |
| Gopher daemon | [tildeclub/Fossor](https://github.com/tildeclub/Fossor) |
| Gopher root content | [tildeclub/gopherhole](https://github.com/tildeclub/gopherhole) |
| Gemini root content | [tildeclub/geminipage](https://github.com/tildeclub/geminipage) |
| MOTD | [tildeclub/motd](https://github.com/tildeclub/motd) |
| Terminal wiki helper | [tildeclub/wiki-cli](https://github.com/tildeclub/wiki-cli) |
| Recent-page listing | [tildeclub/recently-updated](https://github.com/tildeclub/recently-updated) |
| Botany | [tildeclub/botany](https://github.com/tildeclub/botany) |
| Terminal blogging | [tildeclub/ttbp](https://github.com/tildeclub/ttbp) |
| Forum client | [tildeclub/ttrv](https://github.com/tildeclub/ttrv) |
| Helpdesk | [tildeclub/helpdesk](https://github.com/tildeclub/helpdesk) |
| File sharing | [tildeclub/share](https://github.com/tildeclub/share) |
## Deploy the website deliberately
Stage `tildeclub/site` outside the web root, select and record a revision, then
review domains, contact addresses, signup behavior, JSON data, helper scripts,
and web dependencies. The collected host's main root is
`/usr/share/nginx/html`; configure its own PHP handler before deploying a PHP
front page. The static bootstrap example deliberately does not execute PHP.
Keep `.git`, credentials, local environment files, Composer's development
artifacts, and diagnostics out of the served copy. In particular, the reviewed
site checkout has a signup `_probe.php` mail diagnostic; do not deploy that as
a public endpoint. Install each application's declared dependencies using its
lockfile in a build/staging location and check its supported PHP version.
Do not infer a database requirement merely because MariaDB runs on the machine.
Make code root-owned and grant the backend write access only to the files it
must update. Avoid making the entire document root writable by nginx or giving
an operator application the same writable identity as every member's PHP.
Test error pages, forms, mail submission, and file ownership on the target host.
The wiki has its own `source/` Markdown and Makefile in the site repo. Review
that build file, install its declared tools, and build in staging. Check that
generated pages, local links, feeds, and navigation are present before syncing
the output. A successful checkout is not a completed wiki deployment.
## Signup requests are data
The reviewed website sends a request to the operator and records pending
usernames and a suggested `makeuser` command in files beneath `/var`. The
collected account script later removes the processed pending entry. Neither
step is a transaction across the Unix account, mail, ZNC, helpdesk, and signup
queue.
For a new host, reserve service/admin names, validate the username and contact
address, parse the supplied SSH public key with OpenSSH, and review the request.
A useful key-file check is:
```bash
ssh-keygen -lf /root/pending/member.pub
```
Keep that pending directory private. Construct privileged invocations from
validated arguments or structured records. Do not paste an emailed shell
command into a root terminal without inspecting its quoting and values, and do
not execute a web-writable queue as a shell script.
The public form should handle duplicate requests, concurrent writes, invalid
keys, rate limiting, CSRF where applicable, and mail failures. Trust forwarded
client-address headers only from a configured trusted proxy. Keep request data
and contact addresses out of the document root and define their retention.
Running a signup form is not a reason to give the web process passwordless sudo.
## First contact and support
Give the member a short welcome with:
- The hostname, username, SSH instructions, and host-key verification path.
- Where their pages live and which publishing services are enabled.
- First-login/MFA instructions and a recovery contact.
- Chat, mail, quotas, rules, and how to request help or packages.
- What account data is public and how to request removal.
The existing script emails a generated password and copies it to local mailboxes.
That is its recorded behavior, not a requirement for a new installation. Choose
an enrollment process consistent with your credential and mailbox-retention
policy, and ensure temporary credentials are changed before normal use.
Keep MOTD concise. Put longer help in the wiki or a local `help` command, and
publish an independent status/contact path for when the host is unavailable.
Respect the difference between a maintenance notice and a public announcement
of a new member's account; make the announcement policy clear.
## Optional apps
Install apps under separate operator-managed paths and identities. Record the
revision and dependency lockfile, local socket or port, reverse-proxy route,
backup data, and a restart/restore test. If an app requires secrets, load them
from protected config or the service environment, never from a public checkout.
For cgit, only expose repositories deliberately marked public, validate the user
and repository mapping, and test path traversal. For Botany and similar shared
apps, test whose files the backend can read and write. Keep IRC bot rate limits
and reconnection behavior within the network's policy. Treat browser terminals
and relay sockets as authenticated services, not harmless static content.
The older utilities retained under `api/` and `pages/` are not used as an
automatic install system. Their current limitations and safer deployment rules
are documented in [the API notes](../api/README.org) and
[connection-list notes](../pages/list-html-connections/README.md).
+76
View File
@@ -0,0 +1,76 @@
# Current system
This records the operator's inventories and selected configs collected on
October 22, 2026. It describes the existing shell host. The
[installation guide](how-to-set-up-a-tilde.md) and [examples](../examples/README.md)
make separate choices for a new host; they do not silently claim those choices
are already deployed here.
## Machine
- Fedora Linux 43, x86-64, 8 logical CPUs, about 29 GiB usable RAM.
- About 1 TiB on the root XFS filesystem; `/home` and `/var` are on that mount.
- Root mount options include user and group quotas.
- SELinux disabled on the recorded host; CSF is the active firewall manager.
These are the resources on the existing machine, not minimum requirements.
Exact user inventories, addresses, private keys, credentials, and raw config
dumps are deliberately not part of this public record.
## Services
| Purpose | Confirmed configuration |
| --- | --- |
| Shell access | OpenSSH main service; separate restricted legacy service on 2222. |
| Authentication | Key login, or PAM password/TOTP path; the collected main config does not require both paths together. |
| Onboarding | Local `makeuser`, Ruby `firstlogin`, skeleton marker, Google Authenticator wrapper. |
| Web | nginx, root `/usr/share/nginx/html`, `/~USER/` and `/u/USER/` publishing. |
| PHP | Shared `php-user:club` pool for user pages; `nginx:nginx` pool for main site applications. |
| CGI | fcgiwrap instance with a local override running as `cgiwrap`. |
| Mail | Postfix 3.10.14, Dovecot/Pigeonhole 2.4.5, Maildir at `~/.mail`. |
| Mail filtering | OpenDKIM and Rspamd on configured local milter endpoints; pyspf-milter also running. |
| Webmail | nginx vhost points to SnappyMail. |
| Usenet | INN 2.7.3 running on the shell host. |
| Gopher | Fossor socket activation; dedicated `gopher` service identity. |
| Gemini | gemserv, running from `/srv/gemini/gemserv`, as `gemini`. |
| Finger | Local Python `fingerd.py`, service identity `efingerd`. |
| IRC support | oidentd and ZNC running. Network-server location/daemon was not established by this collection. |
| Database/cache | MariaDB and Valkey running. |
| Accounting | psacct active. |
| Certificates | Custom twice-daily standalone Certbot timer that stops/starts nginx. |
| Backups | Root cron at 03:00, explicit `/usr/bin/borgmatic` 2.1.9 with Borg 1.4.5. |
The compiled services and private application integration code need their own
version/update records. Installed RPMs are not an installation manifest: the
host has accumulated optional tools and old builds over time.
## Details worth keeping straight
`/usr/local/bin/borgmatic` is a different, older installation (1.5.21). Manual
invocation through PATH is not necessarily the program run by the backup job.
`/home` is not an independent mount here. Quota commands need the containing
mountpoint `/`; the collector's `/home` quota query printed an error.
The per-user FPM pool generator exists, but the collected main nginx route uses
the shared socket. A generated per-user pool alone does not route a member's
requests to it.
The supplied main SSH config does not include `sshd_config.d`; collecting those
files does not establish that the daemon reads them. The effective global
`AuthenticationMethods` was `any`. The SSH chapter explains the key and PAM
paths, with an explicit MFA example for a new installation.
The aggregate `user.slice` reported no finite memory or task limit. Individual
user slices and PAM limits were not comprehensively collected, so no complete
production resource policy is claimed here.
The Gopher service reads options from `/etc/default/fossor` and
`/etc/sysconfig/fossor`; those files were not supplied. Gemini's service uses
`/srv/gemini/gemserv/config.toml`, which was also not supplied. The publishing
chapter identifies the runtime layout and points to version-matched upstream
configuration rather than inventing live option values.
The backup repository, complete source list, retention, and restore history
were not supplied. The backup chapter's example is explicitly separate from the
confirmed scheduler and executable versions.
+78 -87
View File
@@ -1,99 +1,90 @@
The [tilde.etcskel repository](https://github.com/tildeclub/tilde.etcskel) contains the contents of the `/etc/skel/` directory, but since git repositories can't reproduce permissions on the files, here are the current permissions for those contents:
# Home skeleton and permissions
`useradd --create-home` copies `/etc/skel` into a new home. Get it right before
creating accounts. Changes to the skeleton do not update existing homes.
The reference source is [tilde.etcskel](https://github.com/tildeclub/tilde.etcskel).
Review its startup commands, domains, IRC settings, and first-login dependencies
before deploying it. Stage changes outside `/etc/skel`; do not clone over a live
skeleton or copy a repository's `.git` directory into every new account.
## Minimal new-host skeleton
Keep the distribution's shell files, then add the public page and an empty
SSH key file. On a new skeleton only:
```bash
install -d -o root -g root -m 0700 /etc/skel/.ssh
install -o root -g root -m 0600 /dev/null /etc/skel/.ssh/authorized_keys
install -d -o root -g root -m 0755 /etc/skel/public_html
```
root@tilde /e/skel (master)# ls -alR
.:
total 63
drwxr-xr-x. 10 root root 4096 Jul 17 2024 ./
drwxr-xr-x. 164 root root 12288 Jan 27 19:40 ../
-rw-r--r--. 1 root root 29 Sep 15 2019 .bash_logout
-rw-r--r-- 1 root root 144 Aug 12 00:00 .bash_profile
-rw-r--r-- 1 root root 3272 Dec 13 2021 .bashrc
drwxr-xr-x 3 root root 303 Jan 27 19:43 .byobu/
lrwxrwxrwx 1 root root 25 Jul 17 2024 .dovecot.sieve -> sieve/rainloop.user.sieve
-rw-r--r--. 1 root root 500 Sep 15 2019 .emacs
drwxr-xr-x. 2 root root 35 Mar 27 2023 .irssi/
-rw-r--r--. 1 root root 11 Sep 15 2019 .logout
-rw-r--r-- 1 root root 124 Jul 18 2024 .mkshrc
-rw-r--r-- 1 root root 0 Mar 27 2020 .new_user
-rw-r--r-- 1 root root 816 Mar 27 2020 .profile
drwxr-xr-x 2 root root 23 Nov 26 2021 public_gemini/
drwxr-xr-x. 2 root root 23 Jan 27 19:43 public_gopher/
drwxr-xr-x. 2 root root 24 Mar 27 2023 public_html/
-rw-r--r-- 1 root root 790 Dec 23 2022 README.md
-rw-r--r--. 1 root root 12 Sep 15 2019 .scrollzrc
drwxr-xr-x 3 root root 44 Jul 17 2024 sieve/
drwx------. 2 root root 29 Sep 15 2019 .ssh/
-rw-r--r-- 1 root root 299 Jul 20 2024 .zprofile
-rw-r--r-- 1 root root 658 Jul 20 2024 .zshrc
./.byobu:
total 56
drwxr-xr-x 3 root root 303 Jan 27 19:43 ./
drwxr-xr-x. 10 root root 4096 Jul 17 2024 ../
-rw-r--r-- 1 root root 19 Aug 23 2018 backend
drwxr-xr-x 2 root root 6 Aug 23 2018 bin/
-rw-r--r-- 1 root root 38 Aug 23 2018 color
-rw-r--r-- 1 root root 96 Aug 23 2018 color.tmux
-rw-r--r-- 1 root root 45 Aug 23 2018 datetime.tmux
-rw-r--r-- 1 root root 52 Aug 23 2018 keybindings
-rw-r--r-- 1 root root 73 Aug 23 2018 keybindings.tmux
-rw-r--r-- 1 root root 0 Aug 23 2018 .metadata_available
-rw-r--r-- 1 root root 49 Aug 23 2018 profile
-rw-r--r-- 1 root root 47 Aug 23 2018 profile.tmux
-rw-r--r-- 1 root root 95 Aug 24 2018 prompt
-rw-r--r-- 1 root root 0 Aug 23 2018 .screenrc
-rw-r--r-- 1 root root 2833 Aug 23 2018 status
-rw-r--r-- 1 root root 2558 Oct 8 2019 statusrc
-rw-r--r-- 1 root root 103 Jan 27 19:43 .tmux.conf
-rw-r--r-- 1 root root 0 Aug 23 2018 windows
-rw-r--r-- 1 root root 8 Aug 23 2018 windows.tmux
The `install ... /dev/null` command empties its destination. Do not use it on
an existing account's key file. Add a starter `index.html` at mode 0644, for
example:
./.byobu/bin:
total 0
drwxr-xr-x 2 root root 6 Aug 23 2018 ./
drwxr-xr-x 3 root root 303 Jan 27 19:43 ../
```html
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My tilde page</title>
<h1>It works.</h1>
<p>Edit ~/public_html/index.html to make this page your own.</p>
</html>
```
./.irssi:
total 16
drwxr-xr-x. 2 root root 35 Mar 27 2023 ./
drwxr-xr-x. 10 root root 4096 Jul 17 2024 ../
-rw-r--r-- 1 root root 4977 Mar 27 2023 config
-rw-r--r-- 1 root root 15 Mar 27 2023 startup
Add a short README covering SSH, editing the page, mail if offered, chat,
acceptable use, quotas, and how to reach an operator. Use your own domain and
contact details. Keep private defaults out of publicly served directories.
./public_gemini:
total 8
drwxr-xr-x 2 root root 23 Nov 26 2021 ./
drwxr-xr-x. 10 root root 4096 Jul 17 2024 ../
-rw-r--r-- 1 root root 96 Nov 26 2021 index.gmi
## Modes
./public_gopher:
total 8
drwxr-xr-x. 2 root root 23 Jan 27 19:43 ./
drwxr-xr-x. 10 root root 4096 Jul 17 2024 ../
-rw-r--r-- 1 root root 149 Jan 27 19:43 gophermap
| Path | Mode | Owner in the skeleton | Purpose |
| --- | --- | --- | --- |
| `/etc/skel` | 0755 | root:root | Template directory. |
| `.ssh` | 0700 | root:root | Private SSH configuration directory. |
| `.ssh/authorized_keys` | 0600 | root:root | Empty placeholder; never a shared key. |
| `public_html` | 0755 | root:root | Public web directory. |
| `public_html/index.html` | 0644 | root:root | Public starter page. |
| `public_gopher`, `public_gemini` | 0755 | root:root | Only if those services are offered. |
| Public text and menu files | 0644 | root:root | Readable publishing content. |
| Private mail/sieve directories | 0700 where appropriate | root:root | Not web content. |
| Private sieve files | 0600 | root:root | User-specific filtering state. |
./public_html:
total 8
drwxr-xr-x. 2 root root 24 Mar 27 2023 ./
drwxr-xr-x. 10 root root 4096 Jul 17 2024 ../
-rw-r--r-- 1 root root 2145 Mar 27 2023 index.html
`useradd` assigns the copied files to the new account. Check that ownership
rather than recursively changing an established home. Git records executable
bits but not arbitrary file modes or ownership, so a checkout alone is not a
permissions deployment procedure.
./sieve:
total 4
drwxr-xr-x 3 root root 44 Jul 17 2024 ./
drwxr-xr-x. 10 root root 4096 Jul 17 2024 ../
-rw------- 1 root root 0 Jul 17 2024 rainloop.user.sieve
drwx------ 2 root root 6 Jul 17 2024 tmp/
The collected production skeleton also has Byobu, Irssi, `.gnupg`, Sieve files,
a `.dovecot.sieve` symlink, and a `.new_user` marker. Its `.ssh` and `.gnupg`
directories are 0700, the empty key file and personal Sieve file are 0600,
public directories are 0755, and public starter files are 0644. A symlink must
be checked at its destination as well; do not use recursive chmod across it.
./sieve/tmp:
total 0
drwx------ 2 root root 6 Jul 17 2024 ./
drwxr-xr-x 3 root root 44 Jul 17 2024 ../
## First-login hook
./.ssh:
total 4
drwx------. 2 root root 29 Sep 15 2019 ./
drwxr-xr-x. 10 root root 4096 Jul 17 2024 ../
-rw-------. 1 root root 0 Sep 15 2019 authorized_keys
On the collected host, `.bash_profile` sources `.bashrc`, and interactive Bash
runs `firstlogin` when `.new_user` exists. Noninteractive Bash returns before
that hook. The collected Zsh startup files do not contain the same hook.
The Ruby wizard removes the marker at the end. Test interruption, failure, and
re-entry. Keep it out of noninteractive SCP/SFTP or command sessions, and do
not rely on the marker to enforce MFA or block an account.
## Test a fresh copy
Create a disposable account with the real account-creation method. Check:
```bash
namei -l /home/member/.ssh/authorized_keys
namei -l /home/member/public_html/index.html
stat -c '%a %U:%G %n' /home/member /home/member/.ssh \
/home/member/.ssh/authorized_keys /home/member/public_html
```
Use the home traversal mode explained in [the setup guide](how-to-set-up-a-tilde.md).
Check SSH and web access as the actual member and service users, including
SELinux labels when enforcing. Never repair a 403 by making the entire home
readable to everyone.
+197 -501
View File
@@ -1,575 +1,271 @@
## Build your own tilde-style server (beginner-friendly, step-by-step)
# Build a tilde-style machine
This guide is intentionally detailed for first-time operators.
A tilde is a shared Unix machine where people get a shell account and space to
publish things. The machine needs an operator, a backup, and some clear rules.
It does not need every service on the existing tilde.club host on the first day.
If you have never run a server before, follow this in order and do not skip ahead.
This guide uses Fedora Server and nginx. The production reference was collected
from tilde.club on October 22, 2026, running Fedora 43. Use a supported release
and its maintained packages. See [the system record](current-system.md) for
what was actually observed; examples below use `tilde.example.net` and a test
account named `member`.
---
The commands in this guide are Bash. If your interactive shell is Fish, start
`bash` before using examples that assign variables or use Bash loops. Run
administrative commands from a root shell obtained with `sudo -i`, unless the
text says to run them on your own computer or as a regular user.
## What you are building
## 1. Plan the machine
A "tilde-style" host usually provides:
For a small initial group, 2 CPUs, 4 GiB RAM, and 60 GiB of SSD storage are a
reasonable starting allocation. Compilers, persistent bots, mail, and games can
change that quickly. Monitor actual use before increasing limits or buying more
capacity. The existing server's size is not a minimum requirement.
- A Linux shell account for each user
- Personal web publishing from `~/public_html`
- Shared Unix tools for learning, writing, coding, and socializing
- Community norms and moderation
You need:
In short: a small, friendly, multi-user Unix community.
- A domain you control and a host with a stable public address.
- Provider console or recovery access independent of SSH.
- An off-host backup destination and a place to keep recovery keys.
- An admin account, a second person or recovery process for emergencies, and a
published way to contact the operators.
- A decision about signups, acceptable use, account removal, and disk limits.
---
Use separate service accounts for the web server, mail, Gopher, and other
services. Ordinary shell accounts do not belong in `wheel` or a container-engine
administration group. Shell access lets people run programs: PHP restrictions
and a blocked SSH forwarding option do not turn a shell account into a sandbox.
## 0) Before you touch a server
## 2. Install and update Fedora
### 0.1 Buy or prepare these things first
1. A domain name (example: `domain.club`)
2. One Linux server (VPS is fine)
3. SSH client on your laptop
4. A text editor you can use comfortably
### 0.2 Keep this safety rule in mind
Never close your current SSH session until you have confirmed a **new** SSH session works with your latest config changes.
This one habit prevents most accidental lockouts.
---
## 1) Provision your Linux server
You can use AWS, Hetzner, Linode, DigitalOcean, or any other provider.
### Recommended minimum for a small starter community
- 2 vCPU
- 4 GB RAM
- 40 GB SSD
- Ubuntu LTS or Debian stable (easiest for beginners)
### 1.1 Point DNS at your server
At your DNS provider:
- Create an `A` record for `domain.club` -> your server IPv4
- Optionally create `AAAA` for IPv6
DNS can take time to propagate.
### 1.2 First login
From your local machine:
Install Fedora Server with SSH access and time synchronization enabled. Keep
SELinux enforcing on a new installation. The production host currently has it
disabled; that is not a prerequisite for running a tilde.
```bash
ssh root@domain.club
dnf upgrade --refresh
dnf install sudo openssh-server git curl rsync tmux vim-enhanced nano jq \
acl ripgrep policycoreutils-python-utils firewalld chrony
hostnamectl set-hostname tilde.example.net
systemctl enable --now sshd chronyd
```
If your provider uses a default admin user (for example `ubuntu`), use that user and `sudo`.
Reboot if the update requires it and verify a fresh login. Record the OS version
and package sources. Do not add random repositories to recover an old program
name from a 2014 package list.
---
## 3. Set DNS and the firewall
## 2) Base system setup (packages, hostname, firewall)
Create an A record for `tilde.example.net`. Add an AAAA record only if IPv6 works
through the host and provider firewalls. Test both address families from outside
the server. Reverse DNS is especially important if you plan to send mail directly.
This section includes both Debian/Ubuntu and Red Hat-family commands.
### 2.1 Update the system
**Debian / Ubuntu**
On a new host using firewalld, first check which zone owns the network interface:
```bash
apt update
apt -y upgrade
firewall-cmd --get-active-zones
```
**RHEL / Rocky / Alma / Fedora**
In the example below, replace `public` with that zone. Confirm any custom SSH
port is allowed before changing a firewall remotely.
```bash
dnf -y upgrade
```
### 2.2 Install baseline tools
**Debian / Ubuntu**
```bash
apt -y install sudo git curl wget rsync tmux htop vim nano tree jq ufw
```
**RHEL / Rocky / Alma / Fedora**
```bash
dnf -y install sudo git curl wget rsync tmux htop vim nano tree jq
```
### 2.3 Set hostname
```bash
hostnamectl set-hostname domain.club
```
Check:
```bash
hostnamectl
```
### 2.4 Configure a basic firewall
If using `ufw` (common on Ubuntu):
```bash
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
ufw status verbose
```
If using `firewalld` (common on RHEL-family):
```bash
systemctl enable --now firewalld
firewall-cmd --permanent --add-service=ssh
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
systemctl enable firewalld
firewall-cmd --permanent --zone=public --add-service=ssh
firewall-cmd --permanent --zone=public --add-service=http
firewall-cmd --permanent --zone=public --add-service=https
firewall-cmd --reload
firewall-cmd --list-services
firewall-cmd --zone=public --list-all
```
---
These commands assume firewalld is already running. If it is stopped, prepare
the persistent zone with `firewall-offline-cmd` before starting it, including
your actual SSH port and the intended network-zone assignment. Then start it
and verify a new connection. Provider firewalls are independent.
## 3) Create a non-root admin account
The existing tilde.club host uses CSF. Do not enable a second firewall manager
on that machine as part of following this guide. See [ports and firewall
policy](shellserver.md#ports-and-firewall-policy) before adding optional services.
Running daily admin tasks as `root` is risky.
## 4. Establish an admin account
### 3.1 Create admin user
Create a private primary group and home. Set a password for sudo, then add the
account to `wheel`:
```bash
adduser admin
useradd --create-home --user-group --shell /bin/bash operator
passwd operator
usermod --append --groups wheel operator
install -d -o operator -g operator -m 0700 /home/operator/.ssh
```
On some distros:
Copy the admin's **public** key to `/home/operator/.ssh/authorized_keys`, without
replacing an existing key file. Set its owner to `operator:operator` and mode
0600, and run `restorecon -RF /home/operator/.ssh` if SELinux is enabled.
From a second terminal on your own computer, verify key login and `sudo -v`.
Keep the first connection open. Do not disable the only working login method
before its replacement has been tested.
Follow [SSH access and MFA](ssh.md) to make the key-only baseline explicit.
That chapter also covers enrollment before enforcing MFA and a separate legacy
SSH service. Do not copy the production host's account-specific exceptions.
## 5. Build the home skeleton
Follow [the skeleton instructions](etc-skel-permissions.md). At a minimum, create
an empty private SSH directory, a public HTML directory, and a useful starter
page. Do this before creating the test account.
The public [tilde.etcskel repository](https://github.com/tildeclub/tilde.etcskel)
is a reference to review, not a directory to copy over your live `/etc/skel`
without checking it. It contains host-specific defaults and first-login hooks.
## 6. Install the web service
```bash
useradd -m -s /bin/bash admin
passwd admin
dnf install nginx
install -d -o root -g root -m 0755 /usr/share/nginx/html
```
### 3.2 Give sudo privileges
Use [web publishing](web.md) and the
[static nginx example](../examples/nginx/tilde-static.conf). Start with static
pages. That makes it possible to test path mapping and private-file access
before adding PHP or CGI.
**Debian / Ubuntu**
The example belongs in `/etc/nginx/conf.d/tilde.conf`, which Fedora's normal
nginx configuration includes inside `http`. Check that include on your machine.
Remove or adjust a conflicting packaged default server block; do not keep two
servers both claiming to be the default on the same address and port.
Replace the example domain, then validate and start the service:
```bash
usermod -aG sudo admin
nginx -t
systemctl enable --now nginx
```
**RHEL-family**
The example is HTTP-only so it can be used before obtaining a certificate. Set
up TLS before accepting account passwords or enabling web applications.
## 7. Create one test member
The following is the independent Unix-account baseline. It does not announce an
account, send welcome mail, create ZNC accounts, or update the production signup
queue. Those are integrations described in [accounts](accounts.md).
```bash
usermod -aG wheel admin
useradd --create-home --user-group --shell /bin/bash member
passwd member
chmod 0711 /home/member
install -d -o member -g member -m 0700 /home/member/.ssh
install -d -o member -g member -m 0755 /home/member/public_html
```
### 3.3 Test sudo
`useradd` should copy the empty key file from the prepared skeleton. Put the
member's public key there, then set its owner to `member:member` and mode 0600.
Never put private keys in the skeleton or generate a private key for a member
and email it to them.
Keep the home at 0711 for this layout. That permits traversal to public content
without allowing a directory listing of the home. Private directories and files
still need their own modes: use 0700 for private directories and 0600 for private
files. A 0711 home alone does not hide a readable file at a known path.
With SELinux enabled, apply and test the publishing policy described in the web
chapter, then restore the account's labels. Do not recursively make a home
world-readable just to fix a 403 response.
From your own computer, test SSH, an unrelated key that should fail, and:
```bash
su - admin
sudo whoami
curl --fail http://tilde.example.net/~member/
curl --fail http://tilde.example.net/u/member/
```
Expected output: `root`
Create a second disposable member and run the cross-account checks in the web
chapter before enabling executable user content.
---
## 8. Add TLS
## 4) SSH hardening (safe order, no lockouts)
Do this carefully.
### 4.1 Set up admin SSH key directory and file
The static example exposes ACME challenges beneath `/var/lib/letsencrypt`.
Create that directory and install the packaged Certbot client:
```bash
install -d -m 700 /home/admin/.ssh
install -m 600 /dev/null /home/admin/.ssh/authorized_keys
chown -R admin:admin /home/admin/.ssh
dnf install certbot
install -d -o root -g root -m 0755 /var/lib/letsencrypt/.well-known/acme-challenge
certbot certonly --webroot -w /var/lib/letsencrypt -d tilde.example.net
```
### 4.2 Add your public key
Verify that the returned certificate paths exist. Add the HTTPS server block
from the web chapter, run `nginx -t`, and reload nginx. Install a deploy hook to
validate and reload nginx when a renewed certificate is deployed. If the same
certificate is used by mail, reload those consumers as well. Confirm the
packaged renewal timer is enabled and perform `certbot renew --dry-run`.
On your local machine, show your public key:
This webroot setup can renew without stopping nginx. The existing server uses
a separate standalone renewal service that stops and starts nginx; its details
are recorded in the web chapter rather than imposed on a new host.
## 9. Give people useful tools
Install a small set first; expand it in response to actual requests:
```bash
cat ~/.ssh/id_ed25519.pub
dnf install fish zsh emacs irssi weechat alpine lynx links \
git gcc make python3 ruby perl figlet
```
Copy that line and paste it into:
Use `dnf info NAME` before adding optional packages. Packages, module streams,
and third-party builds change. Keep a record of manual installations and their
update procedure. Do not chmod system compilers to root-only as a substitute
for resource management; programming is part of the point of the machine.
`/home/admin/.ssh/authorized_keys`
Choose the shells you support in `/etc/shells`, and make sure each has working
startup files. Add [first-login setup](accounts.md#first-login-wizard) only after
its dependencies and exit behavior have been tested with those shells.
Example on server:
## 10. Set quotas and resource limits
```bash
printf '%s\n' 'ssh-ed25519 AAAA... your-key-comment' >> /home/admin/.ssh/authorized_keys
chown admin:admin /home/admin/.ssh/authorized_keys
chmod 600 /home/admin/.ssh/authorized_keys
```
Use [the shell operations chapter](shellserver.md). For XFS, discover the mount
containing `/home` before running quota commands. On the collected machine that
mount is `/`, not `/home`.
### 4.3 Keep passwords ON while you test keys
A disk quota does not limit CPU or processes. Set and verify those policies
separately, with enough room for normal compiles, editors, mail, and chat clients.
Edit `/etc/ssh/sshd_config`:
## 11. Back up and restore before opening signups
```text
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication yes
ChallengeResponseAuthentication no
UsePAM yes
```
Follow [backup and recovery](backups.md). Include account data, mail, service
configs, local programs, web applications, and consistent database dumps. Keep
the repository and its recovery keys off the host. Restore a test account into
an isolated directory and verify the result.
Reload:
A successful backup job alone is not a completed restore test.
```bash
systemctl reload sshd
```
## 12. Add the services you intend to operate
From a **second local terminal**, test login:
- [Mail](mail.md): local delivery, IMAP, submission, DNS, filtering, and abuse checks.
- [IRC](ircserver.md): clients, persistent sessions, ident, ZNC, and optional network hosting.
- [Gopher and Gemini](publishing.md): separate public directories and service users.
- [Usenet](usenet.md): local groups before opening reader access or negotiating peers.
- [Website and community tools](community.md): signup handling, the wiki, and optional apps.
```bash
ssh admin@domain.club
```
Each service adds an update job, a backup requirement, and a support obligation.
Write down who owns it and how to stop or restore it.
If this fails, fix it now. Do **not** continue.
## Before accepting members
### 4.4 Disable password auth only after key login works
- Test admin and member login from outside the host, including IPv6 if published.
- Verify MFA enrollment, account recovery, and any allowed automation accounts.
- Test static publishing, private-file denial, and executable content separately.
- Confirm disk quotas and process/memory limits with the test accounts.
- Complete an off-host backup and isolated restore.
- Publish rules, a contact address, maintenance notices, and an account-removal policy.
- Review every enabled network listener against the intended firewall policy.
- Remove diagnostic PHP pages, temporary archives, and test credentials from web roots.
Edit `/etc/ssh/sshd_config`:
```text
PasswordAuthentication no
```
Reload and test again from a second terminal:
```bash
systemctl reload sshd
ssh admin@domain.club
```
Only after success should you end your old session.
---
## 5) Install and configure Apache for user pages
This is the baseline many tilde hosts use for `~username` pages.
### 5.1 Install Apache
**Debian / Ubuntu**
```bash
apt -y install apache2
systemctl enable --now apache2
```
**RHEL-family**
```bash
dnf -y install httpd
systemctl enable --now httpd
```
Check service status:
```bash
systemctl status apache2 --no-pager
# or
systemctl status httpd --no-pager
```
### 5.2 Enable user directories (`~username` URLs)
#### Debian / Ubuntu
```bash
a2enmod userdir
systemctl restart apache2
```
By default, this serves `/home/USERNAME/public_html` as:
`http://domain.club/~USERNAME/`
#### RHEL-family
Edit Apache config (often `/etc/httpd/conf/httpd.conf`) and ensure:
```apache
UserDir public_html
<Directory /home/*/public_html>
AllowOverride All
Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
Require method GET POST OPTIONS
</Directory>
```
Then restart:
```bash
systemctl restart httpd
```
### 5.3 Create a test web user and page
```bash
useradd -m -s /bin/bash testuser
mkdir -p /home/testuser/public_html
cat > /home/testuser/public_html/index.html <<'HTML'
<!doctype html>
<html>
<head><meta charset="utf-8"><title>testuser page</title></head>
<body><h1>hello from testuser</h1></body>
</html>
HTML
chown -R testuser:testuser /home/testuser
chmod 755 /home/testuser
chmod 755 /home/testuser/public_html
chmod 644 /home/testuser/public_html/index.html
```
Now test locally on the server:
```bash
curl -I http://localhost/~testuser/
curl http://localhost/~testuser/
```
Then test from your laptop:
```bash
curl -I http://domain.club/~testuser/
```
If it fails, check Apache logs:
**Debian / Ubuntu**
```bash
tail -n 100 /var/log/apache2/error.log
```
**RHEL-family**
```bash
tail -n 100 /var/log/httpd/error_log
```
---
## 6) Prepare `/etc/skel` before inviting users
`/etc/skel` is copied into every new account. Set it up early.
### 6.1 Minimum recommended contents
- `.bashrc` and/or `.zshrc` with helpful comments
- `public_html/index.html` starter page
- A README with first commands and local rules
- Optional `public_gemini/` and `public_gopher/`
### 6.2 Example starter files
```bash
install -d -m 755 /etc/skel/public_html
cat > /etc/skel/public_html/index.html <<'HTML'
<!doctype html>
<html>
<head><meta charset="utf-8"><title>Welcome</title></head>
<body>
<h1>It works!</h1>
<p>Edit this file to publish your page.</p>
</body>
</html>
HTML
```
```bash
cat > /etc/skel/README-FIRST.txt <<'TXT'
Welcome to the server.
Useful first commands:
- pwd
- ls -la
- nano ~/public_html/index.html
Your web page lives at:
http://domain.club/~YOURUSERNAME/
TXT
```
See also: `docs/etc-skel-permissions.md`.
---
## 7) Install baseline user tools
Give new users a capable, friendly default toolbox.
**Debian / Ubuntu**
```bash
apt -y install \
zsh fish \
emacs vim nano \
irssi weechat \
mutt alpine \
lynx w3m links \
git build-essential python3 nodejs npm
```
**RHEL-family**
```bash
dnf -y install \
zsh fish \
emacs vim nano \
irssi weechat \
mutt alpine \
lynx links \
git gcc make python3 nodejs npm
```
Add or remove packages based on your community.
---
## 8) Add users safely and consistently
Use a repeatable checklist every time.
### 8.1 Account creation checklist
```bash
useradd -m -s /bin/bash USERNAME
passwd -l USERNAME
install -d -m 700 /home/USERNAME/.ssh
install -m 600 /dev/null /home/USERNAME/.ssh/authorized_keys
install -d -m 755 /home/USERNAME/public_html
chown -R USERNAME:USERNAME /home/USERNAME
```
### 8.2 Add the user's public key
```bash
printf '%s\n' 'ssh-ed25519 AAAA... user@device' >> /home/USERNAME/.ssh/authorized_keys
chown USERNAME:USERNAME /home/USERNAME/.ssh/authorized_keys
chmod 600 /home/USERNAME/.ssh/authorized_keys
```
### 8.3 Verify login and web publishing
```bash
ssh USERNAME@domain.club
curl -I http://domain.club/~USERNAME/
```
---
## 9) Add "tilde functionality" in manageable layers
Do not launch everything on day one.
### Layer A: personal publishing
- User web pages in `public_html`
- Basic HTML templates
- Optional Gemini and Gopher directories
### Layer B: communication
- IRC client docs
- Local mail (postfix + local delivery)
- Server bulletin/MOTD updates
### Layer C: collaboration
- Shared Git repos
- Local pastebin/snippet service
- Community docs/wiki process
### Layer D: culture and learning
- New-user orientation checklist
- Mentoring or office-hours in chat
- Monthly "show your tilde" events
---
## 10) Operations, backup, and recovery
### 10.1 Back up the important data
At minimum, back up:
- `/home`
- `/etc`
- Web server config (`/etc/apache2` or `/etc/httpd`)
- Mail config if used
### 10.2 Example nightly backup script
Create `/usr/local/sbin/backup-tilde.sh`:
```bash
#!/usr/bin/env bash
set -euo pipefail
DEST=/var/backups/tilde
DATE=$(date +%F)
mkdir -p "$DEST/$DATE"
tar -czf "$DEST/$DATE/home.tgz" /home
tar -czf "$DEST/$DATE/etc.tgz" /etc
```
Make executable:
```bash
chmod 700 /usr/local/sbin/backup-tilde.sh
```
Run once manually:
```bash
/usr/local/sbin/backup-tilde.sh
```
Then automate with cron or systemd timers.
### 10.3 Test restore (critical)
A backup is not real until you test restoring at least one file.
---
## 11) First-week launch checklist
- [ ] DNS points to server
- [ ] Firewall allows only intended ports
- [ ] Admin key login works from a second terminal
- [ ] Password auth disabled only after key validation
- [ ] Apache running and `~testuser` page reachable
- [ ] `/etc/skel` tested by creating a new account
- [ ] Backup job ran and one restore test passed
- [ ] Rules/moderation/contact info published
- [ ] At least one backup admin has emergency access
---
## 12) Where to continue in this repository
- `docs/shellserver.md` for shell host operational notes
- `docs/etc-skel-permissions.md` for current skeleton permissions
- `docs/ssh.md` for SSH key onboarding details
- `docs/server.org` for historical package/setup notes
Keep your local configuration changes under version control without secrets.
Record the version or commit used for every manually installed service. The
[upstream references](sources.md) explain the software-specific behavior behind
these examples.
+19 -4
View File
@@ -1,7 +1,22 @@
### Newnet IRC Network
# Connecting to IRC
## How to connect
The endpoint recorded for NewNet in this repo is:
You can connect to irc.newnet.net on port 6697 with ssl
| Setting | Value |
| --- | --- |
| Server | `irc.newnet.net` |
| TLS port | `6697` |
| Browser client | <https://web.newnet.net/?join=club> |
irc.newnet.net is a round-robin host that will return any of the available servers.
Use TLS and keep certificate validation enabled. `irc.newnet.net` is a network
name that can direct clients to different servers; it is not proof that the
IRCd runs on the shell host.
For a new community, put your own network/channel choice in the member README,
MOTD, and client defaults. Check the network's connection and registration
policy before making hundreds of accounts connect from one host. Do not ship
member passwords or a privileged bot credential in the skeleton.
See [IRC operations](ircserver.md) for clients, ident, ZNC, and separate network
hosting. Endpoint details should be rechecked when the operator changes the
network or connection policy.
+54 -95
View File
@@ -1,112 +1,71 @@
# Setting up the tilde.club IRC server
# IRC on a shared shell host
We chose to go with `charybdis`, mostly because it's in the Debian/Ubuntu package repository (universe), so we can update it without having to go through the `configure`/`make`/`make install` process.
Members can use IRC without running an IRC network. Start with clients and a
clear place to meet. [Connection details](ircserver-connections.md) describe
the endpoint recorded in this repo.
> FYI, we've done a bit of testing, and aren't sure that `charybdis` will interconnect server-to-server with `ngircd`, another open-source IRC daemon that's available out there. So if you're setting up a potential IRC peer of the tilde.club IRC server, this might be an important bit of information to know.
The host inventory confirms oidentd and ZNC. It does not establish where the
network servers run or which daemon they currently use. The old EC2/Charybdis
instructions and private address map have been removed because they were not a
current deployment record.
## System setup
## Clients and persistent sessions
The IRC server is an Amazon EC2 instance running Ubuntu. Initial system setup on the IRC server included:
Install maintained client packages such as Irssi or WeeChat. Provide a small
example using TLS with certificate validation. Let members keep their own
nickname/account credentials private; do not put a shared NickServ password in
`/etc/skel`.
* Setting the hostname (`sudo hostname irc`) (and perhaps also needing to edit the `/etc/hostname` file -- unclear)
* updating the `/etc/hosts` file (add `172.30.0.216 irc.tilde.club irc` and `172.30.0.176 tilde.club tilde`)
* setting the EC2 security group to allow TCP traffic on ports 6667 and 6697 from the `tilde.club` shell server only
A client inside tmux or Byobu can remain connected while the member disconnects
from SSH. A bouncer such as ZNC is a separate service that keeps IRC connections
and can replay buffered messages to a remote client. Document which option you
support instead of promising that every account includes both.
### Setup changes on the tilde.club shell server to access the IRC server
## Ident
The `tilde.club` shell server needed a few tweaks as well:
A shared host can send many connections to the same IRC network from one IP.
Ident can help an IRC network identify individual local accounts, but it is not
a replacement for IRC account authentication. Agree on connection limits and
ident policy with the network's operators before inviting a large group.
* updating `/etc/hosts` (add `172.30.0.216 irc.tilde.club irc` so users can reach the IRC server by hostname)
* setting the EC2 security group to allow TCP traffic on port 113 (`identd`) from the IRC server only
On a new Fedora host, inspect the maintained `oidentd` package and its config,
then enable the intended service/socket. Restrict inbound TCP 113 to the IRC
servers that need it where practical. Decide whether users may override their
ident response, and verify IPv4 and IPv6 independently. Do not open a privileged
service just because the old guide had a `chkconfig` command for it.
## Installing charybdis
## ZNC
First we needed to add the "universe" repository to the `apt` repository list:
The production `makeuser` invokes `znccreate.py` to create a ZNC account with a
three-network limit. It passes the generated password on stdin. The provisioning
script's existence does not establish the current ZNC listener, module set, or
account-recovery policy; those need their own config and tests.
```
sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc) universe"
```
For a new host, run ZNC under a dedicated service identity, with its writable
state outside the web root. Use the maintained package or a reviewed build,
configure a TLS listener, protect credentials and buffers, and set reasonable
network/buffer limits. Bind any administrative listener locally or restrict it
to the intended admin path. Test reconnects and account removal as well as the
initial login. Back up its config and state consistently.
Once that was added, `charybdis` could be installed:
Never hand a signup form permission to execute arbitrary root commands in
order to create a bouncer account. Keep the member-facing request and privileged
provisioning steps separate, with validated inputs and an auditable result.
```
sudo apt-get install charybdis
```
See the [ZNC administration documentation](https://wiki.znc.in/ZNC).
## Configuring charybdis
## If you want to run an IRC server
We first needed to set up SSL certs, which are good for local users who want to use SSL, but also will be important if and when we allow linked tilde.club IRC servers.
Choose the daemon based on the network you intend to operate or join. IRC
client protocol compatibility does not imply server-link compatibility between
daemons. Linking needs agreement with that network's operators.
```
sudo /usr/bin/charybdis-genssl
```
For a separate server deployment, document its supported version, source/package,
service user, hostname, TLS certificate renewal, listener policy, account services,
operator access, connection/flood limits, and backup/restore procedure. Keep
server-link secrets and operator credentials outside this public repo. Test the
config with the daemon's own checker and test a client before opening the port.
I'm not sure if it's critical, but I set the cert's common name to the same name I chose for the IRC server (`irc.tilde.club`); I answered the rest of the questions logically (`US`, `DC`, `Washington`, `tilde.club` for the org name, `delfuego@tilde.club` for the contact email).
The next step was to edit the `/etc/charybdis/ircd.conf` file. There are [a bunch of settings](http://www.stack.nl/~jilles/irc/charybdis-oper-guide/index.htm) here, organized into sections; these are the important ones.
### serverinfo section
* **name**: the same name you used above for the common name of the SSL cert [`irc.tilde.club`]
* **sid**: a unique identifier, two digits and a character; these distinguish networked servers from each other
* **description**: simple
* **network_name**: I actually don't think this matters [`tilde.club`]
* **network_desc**: I also don't think this matters [`your very own network!`]
* **hub**: determines whether servers can connect at all [`yes`]
* **ssl_private_key**: location of the private key you generated above [`/etc/charybdis/ssl.key`]
* **ssl_cert**: location of the SSL cert [`/etc/charybdis/ssl.cert`]
* **ssl_dh_params**: location of the SSL Diffie-Hellman parameters file [`/etc/charybdis/dh.pem`]
### admin section
This info is reported when an IRC user issues the `/INFO` command, so you might want it to be accurate.
### log section
The defaults here are meaningless, since they're relative paths and I can't figure out what they're relative *to*. So I changed all these to absolute paths, such as:
* **fname_userlog**: `/var/log/charybdis/userlog`
Another important note: it appears that the file has to exist and be writeable by charybdis in order for it to be used, so for each log file you enable, the following needs to be done:
```
sudo touch /var/log/charybdis/userlog
sudo chown charybdis.charybdis /var/log/charybdis/userlog
sudo chmod 644 /var/log/charybdis/userlog
```
Finally, the `serverlog` file is the relevant one for debugging server-to-server connections, so that was an important one to enable.
### listen section
* **port**: port 6667 is the typical IRC server port [`5000, 6665 .. 6669`]
* **sslport**: port 6697 is the typical IRC SSL server port [`6697`]
### auth section
There can be multiple `auth` sections, but there can't be sections that have identical `user` entries.
* **user**: the mask for users allowed to connect [`*@172.30.0.0/16`]
* **class**: the class that matching users are put into [`users`]
### operator section
Any users who should be able to elevate with `/OPER` need a section here; these sections are different in that the section head includes the IRC nickname of the user (e.g., `operator "ubuntu" {`).
* *header*: see above, you need to include the nickname in the header
* **user**: the full hostmask of the user [`delfuego@172.30.0.176`]
* **password**: the password the user needs to use after issuing `/OPER`; it can be encrypted using `mkpasswd` or left unencrypted
* **flags**: either `encrypted` if the password is encrypted, or `~encrypted` if it isn't [`encrypted`]
### connect section
This section is only needed if you're allowing other servers to connect and/or connecting to other servers. It's like the `operator` section in that the header includes an additional string, the name of the server to which you're connecting.
* *header*: see above, this needs to be the exact name -- as in, the `name` in the `serverinfo` section -- of the server you're connecting to (or is connecting to you)
* **host**: the IP address of the remote server
* **send_password**: this is the password you'll send to the remote server (configured as that server's `accept_password`)
* **accept_password**: this is the password you'll accept from the remote server (configured as that server's `send_password`)
* **port**: the port you'll connect to on the remote server
* **hub_mask**: the mask of hosts that a hub can introduce to each other [`*`]
* **class**: [`server`]
* **flags**: various flags for server-to-server behaviors [`compressed, topicburst, ssl`] *There's also an `autoconn` flag, and one of the two servers should have it enabled; I think it's good practice for us to expect the remote server to autoconnect to us, not vice-versa.*
An independently administered IRC service should not need write access to the
shell host's homes or account database. The shell host's firewall only needs
outbound client access and whatever ident/bouncer policy you deliberately offer.
+181
View File
@@ -0,0 +1,181 @@
# Mail
Mail is part of the current tilde.club host, not a localhost-only experiment.
The collected Postfix config accepts mail for tilde.club and several related
domains, uses `.mail/`, and has no configured `relayhost`. Dovecot 2.4 serves
Maildir mail from `~/.mail`, authenticates through PAM, and exposes a Postfix
authentication socket. The first-login wizard can create `.forward`.
OpenDKIM and Rspamd are connected through milters on localhost ports 8891 and
11332. A pyspf-milter service is also running; its presence alone does not show
how it is connected. The guide does not reconstruct unpublished filter rules.
## Decide what service you are offering
There are three useful starting points:
- Local-only mail between shell accounts.
- Internet mail with direct inbound and outbound delivery.
- Inbound mail with outbound delivery through an authenticated relay.
The production reference matches the second configuration at the Postfix
`relayhost` setting. Provider-side routing was not inspected. For a new direct
mail host, verify that TCP 25 is permitted, the address has working reverse DNS,
and the provider allows mail service. Do not discover a port-25 block after
opening accounts.
Use a separate mail domain in examples if that is your intended policy. Here,
mail addresses are `member@tilde.example.net` and the SMTP host is also
`tilde.example.net`.
## DNS before launch
Publish and verify:
- A/AAAA records for the SMTP host; publish IPv6 only if SMTP and reverse DNS work there.
- An MX for the mail domain pointing to that hostname, not to an IP literal.
- A PTR for each sending address, matching the sending hostname with forward resolution.
- SPF authorizing the actual sending service, including any outbound relay.
- A DKIM public key for the signing selector you configure.
- DMARC with a monitored report address and a policy you can operate. Start with
observation and verify alignment before enforcing rejection.
Do not copy tilde.club's IP addresses, signing keys, DNS selectors, or domain
aliases. Forwarded mail has additional SPF/DMARC consequences; test it against
actual recipients rather than assuming `.forward` solves deliverability.
## Install the base services
```bash
dnf install postfix dovecot dovecot-pigeonhole swaks
```
Back up existing configs first. The examples are for a new host. Merge
[main.cf.fragment](../examples/postfix/main.cf.fragment) into Postfix's config,
replacing conflicting settings rather than appending duplicate guesses. Keep
the distribution's default local-recipient checks, alias handling, and master
services. Point `root` and `postmaster` aliases to a mailbox an operator reads,
then run `newaliases`.
The example uses Dovecot LMTP for local mailbox delivery so the Sieve plugin can
run. This is a deliberate new-host choice: the collected production config has
an empty `mailbox_transport` and a local delivery transport. A configured Sieve
plugin alone does not prove that every live delivery goes through it.
For a local-only host, keep Postfix listening on loopback, do not publish an MX
or open SMTP externally, and explicitly reject nonlocal delivery with
`default_transport = error: external mail is not offered`. Do not apply that
setting to a host that promises Internet mail.
## Dovecot 2.4
Use [tilde.conf](../examples/dovecot/tilde.conf) as a standalone starting config
for system accounts with private primary groups. Replace the domain and TLS
paths. Do not include another conflicting authentication config alongside it.
Retain the distro's `/etc/pam.d/dovecot` authentication/account policy; it is a
separate PAM service from SSH and should not inherit SSH-specific TOTP prompts.
This uses Dovecot 2.4 syntax. In particular, it uses `mail_driver`, `mail_path`,
protocol/plugin blocks, and current TLS settings. Do not paste a Dovecot 2.3
`mail_location` tutorial into it.
The example enables IMAP and LMTP, requires TLS, uses a minimum of TLS 1.2, and
places LMTP and authentication sockets inside Postfix's private spool. The
current host also enables POP3 and ManageSieve; enable those on a new host only
if they are part of the service you intend to support.
The recorded host has `ssl_min_protocol = TLSv1`. That is a live compatibility
setting, not the minimum used by this example. Check client compatibility before
changing a running system.
Ensure each mailbox and Sieve directory is owned by its member and is not
readable by other members or the shared web group. Test SELinux access on a new
enforcing host. Inspect `doveadm user member` for the actual resolved UID, group,
home, and mail paths.
```bash
doveconf -n
postfix check
systemctl enable --now dovecot postfix
```
Check the journal and actual socket ownership. A parser check cannot prove
that PAM login or delivery works. Do not add `doveconf -P` when sharing output:
it reveals values that the default display hides.
## Submission
Merge the named services from
[master.cf.fragment](../examples/postfix/master.cf.fragment). Do not replace the
rest of `master.cf`. The example offers authenticated STARTTLS on 587 and
implicit TLS on 465, with authentication enabled on those services and TLS
required before accepting credentials. Delivery on 25 remains available without
mandatory TLS so remote mail servers can reach you.
Keep relay restrictions explicit. Test that an outside, unauthenticated client
cannot relay to a third-party domain. Also test that an authenticated member
can submit a message and that the expected envelope/header sender policy is
enforced. If you want one-account/one-sender enforcement, maintain
`smtpd_sender_login_maps` for the addresses and aliases you actually allow and
then apply the matching sender restrictions to submission. Do not enable a
sender-ownership rule against an empty map.
A shared host's localhost clients are not all trusted operators. Local shell
users can invoke mail tools directly, so address-spoofing controls, outbound
volume policy, abuse response, and logging need to cover local submission too.
Use authenticated submission or an identity-aware local submission policy where
account attribution is required.
## Filtering and signing
The base examples establish mail transport, not a finished spam policy. Before
opening Internet mail, configure maintained filtering and signing software.
The production host's two milter endpoints are recorded above, but its private
rules and signing material are not included here.
If using OpenDKIM, install the maintained package and generate a new key under
`/etc/opendkim/keys/tilde.example.net`. Configure a signing table for your domain,
a key table pointing to that key, and a localhost-only milter socket. Publish
the generated public TXT record. Keep the private key restricted to the signing
service. In Rspamd, configure the proxy/milter and controller according to its
installed version, keep the controller private, and choose which service owns
DKIM signing. Avoid accidentally signing twice with conflicting policies.
Add the tested milter endpoints to `smtpd_milters` and `non_smtpd_milters` only
after verifying them. Choose and document what happens if filtering is down:
accepting unfiltered mail and temporarily rejecting it are different policies.
Add spam-folder handling only after confirming messages travel through the
Sieve-enabled delivery path. Do not enable Sieve external-program extensions
just because they appear in a production dump.
See [OpenDKIM](https://www.opendkim.org/) and
[Rspamd's MTA integration guide](https://docs.rspamd.com/tutorials/integration/).
Both need their own update, key rotation, and failure checks.
## Verification
Use disposable mailboxes and clients you control:
1. Deliver locally between two accounts and verify the mailbox path and ownership.
2. Receive from an outside sender and send to an outside recipient.
3. Verify the received SPF, DKIM, and DMARC results and inspect the queue/logs.
4. Check IMAP login with a valid account, a wrong password, and an ineligible account.
5. Check submission over TLS and reject an unauthenticated external relay attempt.
6. Test aliases, `.forward`, invalid recipients, a full quota, Sieve, and service restart.
7. Renew the certificate in a test run and confirm each mail service uses the new one.
For TLS inspection without exposing a password in command history:
```bash
openssl s_client -connect tilde.example.net:993 -servername tilde.example.net
openssl s_client -starttls smtp -connect tilde.example.net:587 -servername tilde.example.net
postqueue -p
journalctl -u postfix -u dovecot --since today
```
Do not submit a password until the client verifies the certificate and expected
hostname. Do not paste authentication transcripts into public logs.
Back up mailbox data, Sieve files, aliases, transport/filter configs, and signing
keys using [the recovery plan](backups.md). A key or MFA reset for SSH does not
by itself revoke an existing webmail session, mail password, or application token.
+174
View File
@@ -0,0 +1,174 @@
# 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](https://github.com/tildeclub/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:
```bash
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:
```bash
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:
```bash
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:
```ini
[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:
```bash
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:
```text
/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](https://git.sr.ht/~int80h/gemserv), 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](../examples/gemserv/config.toml) 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:
```bash
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:
```bash
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](../examples/gemserv/gemserv.service) 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:
```bash
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.
+17 -131
View File
@@ -1,137 +1,23 @@
#+TITLE: Server Configuration
#+TITLE: Server setup reference
* Preface
* Where the current guide lives
This is a literate shell program written. The formatting is pretty
straightforward. You can have documentation and code mixed up and make
nice PDFs but also spit out code. It's good for public projects. It
looks okay on Github.
Use [[file:how-to-set-up-a-tilde.md][Build a tilde-style machine]] for installation and
[[file:current-system.md][Current system]] for the recorded tilde.club configuration.
This file remains at its old path so links to it still lead somewhere useful.
Let's set up a tilde.club server!
The old package list mixed Fedora, CentOS and Ubuntu instructions, init scripts,
and manual builds. It was not a reproducible description of the current host.
It has been replaced by a Fedora/nginx guide and separate service chapters.
* Base machine
- A standard unix server, Ubuntu or Fedora or Centos.
- [TK explain servers and server math]
* Related instructions
* TODO Getting started
- [ ] Add apropos descriptions for each application?
- [[file:ssh.md][SSH and MFA]]
- [[file:etc-skel-permissions.md][Home skeleton and permissions]]
- [[file:accounts.md][Account provisioning and first login]]
- [[file:web.md][Web publishing]]
- [[file:mail.md][Mail]]
- [[file:backups.md][Backups and recovery]]
- [[file:sources.md][Upstream documentation and source repositories]]
We need to start by installing some basics.
First we automatically upgrade the system
#+begin_src bash
echo "Installing basics"
dnf update
dnf upgrade
reboot
#+end_src
And we remove Java; it's a beast and it takes a lot of memory and is
generally a pretty bad citizen on machines that many people share.
#+begin_src bash
echo "Removing java"
dnf uninstall java
#+end_src
* Shells
#+begin_src bash
dnf install zsh
dnf install csh
dnf install fish
#+end_src
* Data
#+begin_src bash
dnf install rrdtool
dnf install jq
#+end_src
* Games
#+begin_src bash
dnf install frotz
dnf install mlmmj
#+end_src
* Editors
#+begin_src bash
dnf install mg vile zile nano vim
#+end_src
* Usenet
#+begin_src bash
dnf install slang slang-devel slang-static slang-slsh
#+end_src
Then download slrn and do a manual install
#+begin_src bash
dnf install slrn
#+end_src
Same with tin
#+begin_src bash
dnf install tin
#+end_src
* Servers
Now we install servers, which allow client software to connect and
issue commands. Each server is like a small computer unto itself.
The first server we fetch is Webmin, which is a systems administration
tool that you can use from the web.
#+begin_src bash
echo "Installing servers"
echo "2) nginx"
dnf install nginx
echo "3) Unix Talk"
dnf install talk-server
echo "4) postfix mail server"
dnf install postfix
echo "5) dovecot IMAP server"
dnf install dovecot
#+end_src
* Applications
#+begin_src bash
dnf install emacs
dnf install tmux
dnf install htop
dnf install elinks
dnf install nail # [?why did I did this]
dnf install lynx
dnf install links
dnf install figlet
dnf install ImageMagick
dnf --enablerepo=epel install -y mosh
# Irc clients
dnf install ScrollZ
dnf install irssi
dnf install alpine
dnf install pico
dnf install readline
dnf install tig
dnf install sbcl
dnf install fortune-mod
dnf install tidy
dnf install jq
dnf install git-core
dnf --enablerepo=epel install nodejs
dnf --enablerepo=epel install npm
#+end_src
* Development Tools
#+begin_src bash
dnf groupinstall "Development Tools"
dnf install gcc
chmod 700 /usr/bin/gcc*
chmod 700 /usr/bin/cc*
#+end_src
* Add other things
- Get the right racket for the box at http://racket-lang.org/download/
No blocks in this file need to be tangled or run as a setup program.
+153 -35
View File
@@ -1,47 +1,165 @@
# Setting up the tilde.club shell server (user host)
# Running the shell host
We want to document the ins and outs of setting up the server so others who are interested can learn (and help!).
Use [the setup guide](how-to-set-up-a-tilde.md) for a new machine. This chapter
covers the work after people start using it.
## System setup
## Accounts and groups
Start with [Build your own tilde-style server](./how-to-set-up-a-tilde.md) for a modern baseline. Historical package notes still live in [docs/server.org](./server.org).
Give each person a private primary group. Use supplementary groups for specific
shared resources. The production `makeuser` also adds a common group by numeric
ID; resolve that ID locally before copying the policy to another host.
Keep the list of privileged groups short. Membership in `wheel`, Docker's
administration group, or a service configuration group can be equivalent to
root access. Shared web backends need their own identities and narrowly scoped
permissions; see [web publishing](web.md).
## Quick priorities for new operators
To suspend a compromised account, disable its actual login paths. Locking the
Unix password alone does not necessarily prevent public-key SSH or existing
sessions. Check keys, SSH account rules, active sessions, cron, user services,
ZNC, web applications, and application-specific credentials. Preserve the data
needed to understand the incident before deleting anything.
If you are bringing up a new host, focus on these first:
Use [account provisioning](accounts.md) as a checklist. Provisioning a Unix
account and provisioning its external services are separate operations.
1. Lock down SSH and require keys.
2. Get `/etc/skel` right before creating many users.
3. Verify `~/public_html` publishing works.
4. Document onboarding, moderation, and backup/restore workflows.
## Disk quotas
These four steps prevent many common early-stage tilde problems.
The collected host uses XFS with `usrquota,grpquota` on the root filesystem.
`/home` is a directory on that filesystem. The collector's attempt to query
`xfs_quota` with `/home` as a mount path failed even though the command returned
zero, so inspect its output as well as its exit status.
## /etc/skel directory
This is the directory that's used as the basis for all newly-created users' home directories, so it's good to get it right before adding new users to a shell server. We've created [a separate repo for the contents of the directory itself](https://github.com/tildeclub/tilde.etcskel), but since it's impossible to check in the appropriate file and directory permissions, [they're documented here](https://github.com/tildeclub/tilde.club/blob/master/docs/etc-skel-permissions.md).
## Email
The default MTA on CentOS is `postfix`. Our goal was to have a `localhost`-only mail service, which required that we configure `postfix` to listen only to `localhost`, and to bounce any email which local users try to send off-server. Both configuration changes are handled in `/etc/postfix/main.cf`.
* the `inet_interfaces` value should just be `localhost` (`inet_interfaces = localhost`)
* the `default_transport` parameter should be the bounce message we want (so add `default_transport = error: outside mail is not deliverable` to the bottom of the file)
### pine
Pine is sort of brain-dead about creating its `.addressbook` file in a user's home directory with `744` permissions; there doesn't appear to be an option to fix this. Instead, it's probably important to work around it before adding any new users:
1. Add `~/mail/` to `/etc/skel` with permissions `700` so that there's a user-accessible-only place for the file to live.
2. Create an `/etc/pine.conf` file that includes the config directive `address-book=mail/.addressbook` to put that file into this new home.
## identd
Users will connect from their shell account to an IRC server, so it is *very* handy to have an `identd` server. For us that just meant installing the standard CentOS `identd` server and configuring it to start automatically:
First discover the mount:
```bash
findmnt --target /home --output TARGET,FSTYPE,OPTIONS
```
sudo yum install oidentd
sudo /etc/init.d/oidentd start
sudo chkconfig oidentd on
On the recorded layout, query and set limits using `/`:
```bash
xfs_quota -x -c 'state' /
xfs_quota -x -c 'report -h -u' /
xfs_quota -x -c 'limit -u bsoft=1G bhard=3G member' /
xfs_quota -x -c 'quota -h -u member' /
```
These match the block limits requested by the current provisioning script.
The soft limit can be exceeded during a grace period; the hard limit cannot.
Check the grace policy and add inode limits if small-file counts are a concern.
If `/home` is a separate XFS mount on your host, use that mount instead.
Enabling XFS quotas can require mount or boot configuration work. Follow the
filesystem's documentation, take a backup, and arrange a maintenance window.
Do not remount a busy filesystem using an unreviewed command copied from here.
For ext4 or another filesystem, use its own quota tools and activation procedure.
Mail and user applications can live in the same quota as the home. Decide what
happens when someone reaches the hard limit: mail delivery and saving an editor
buffer are both affected.
## Process, memory, and CPU limits
The system record did not establish per-user limits. It reported an unlimited
aggregate `user.slice`, which alone says nothing about individual user slices
or PAM limits.
For a new host using systemd user slices, a starting example is
`/etc/systemd/system/user-.slice.d/50-tilde-limits.conf`:
```ini
[Slice]
TasksMax=512
MemoryHigh=1G
MemoryMax=2G
CPUQuota=200%
```
Those values are examples to test, not the existing server's measured policy.
The drop-in applies to `user-UID.slice` units. Apply configuration changes with
`systemctl daemon-reload`, then check a fresh test session:
```bash
id -u member
systemctl show user-UID.slice -p TasksMax -p MemoryHigh -p MemoryMax -p CPUQuotaPerSecUSec
loginctl user-status member
```
Replace `UID` with the number printed by `id`. Verify SSH sessions are actually
placed in that slice. A hard memory limit can terminate a user's work. Tell
people what the limits are and watch for repeated limit hits before tightening
them. PAM `limits.d` settings are useful for session limits but are not a
replacement for aggregate cgroup limits.
Run long-lived community daemons as separate service users with their own
limits. Enable user lingering only where persistent user services are intended.
Do not quietly turn every interactive account into an unrestricted boot-time
service account.
## Ports and firewall policy
The existing host uses CSF, with IPv4 and IPv6 rules and a number of historical
exceptions. Its broad port ranges are not a minimum firewall template. A new
host should allow the services it actually serves.
| Service | Typical inbound port | When it is needed |
| --- | --- | --- |
| SSH | TCP 22 | Member and admin shell access. |
| HTTP and HTTPS | TCP 80, 443 | Publishing and certificate validation. |
| SMTP delivery | TCP 25 | Receiving Internet mail. |
| Authenticated submission | TCP 587 or 465 | Sending mail from remote clients with TLS. |
| IMAPS | TCP 993 | Remote mailbox access. |
| ManageSieve | TCP 4190 | Remote filter editing, if enabled with appropriate TLS/auth. |
| Gopher | TCP 70 | Public Gopher service. |
| Gemini | TCP 1965 | Public Gemini service. |
| Finger | TCP 79 | A deliberately enabled finger service. |
| Ident | TCP 113 | Trusted IRC servers querying user identity. |
| NNTP/NNTP over TLS | TCP 119, 563 | Remote newsreaders, if intentionally allowed. |
| Mosh | UDP chosen from 60000–61000 | An explicit Mosh port allocation; the range is not a universal default. |
IRC client connections are outbound. You do not need an inbound IRC daemon port
just because members run an IRC client. Database, cache, PHP, CGI, and mail
filter sockets should remain local unless there is a separately designed reason
for remote access.
Check `ss -lntup`, the configured bind addresses, host rules, provider rules,
and an outside connection test. A listening socket is not proof of public
reachability, and a firewall listing is not proof of a working service.
## Updates and local programs
Install supported OS updates, read service migration notes, and schedule reboots.
For a shared host, announce downtime through a public status/contact path and
inside the shell. People may have a compiler or editor running when you reboot.
Keep an inventory of programs in `/usr/local`, services built from source,
Python virtual environments, Ruby gems, and application dependency lockfiles.
An RPM update will not update a different executable earlier in PATH. The
recorded borgmatic installation is a concrete example: cron uses
`/usr/bin/borgmatic` 2.1.9 while `/usr/local/bin/borgmatic` reports 1.5.21.
Do not update every language ecosystem globally as root on behalf of all users.
Separate OS packages, operator applications, and user-managed dependencies.
Keep root-owned service code outside user-writable directories.
## Routine checks
```bash
systemctl --failed
systemctl list-timers --all
ss -lntup
df -h
df -i
journalctl -p err --since today
```
Review mail queues, backup completion, certificate expiry, disk and inode use,
resource-limit events, and pending account requests. Keep logs rotated and
retain enough history to investigate failures without logging passwords or
private user content unnecessarily. See [accounting](accounting.md) for what
session and process accounting can actually tell you.
Document how to recover from a failed update, restore an account, rotate a
credential, handle an abuse report, and reach someone when the host is down.
See [backup and recovery](backups.md) for the restore procedure.
+146 -50
View File
@@ -1,78 +1,174 @@
[tilde.club](http://tilde.club) uses SSH public-key authentication.
# SSH access and MFA
If you want access to [tilde.club](http://tilde.club), you'll need to create a new SSH key pair and then send the **public** key to the admins; they will then associate it with your account and let you know when things are ready for you to log in.
## Member keys
## Mac
Use an SSH client already supplied with Linux, macOS, or Windows OpenSSH. On
Windows, PowerShell can run the same `ssh` and `ssh-keygen` commands. PuTTY is
also fine if the user prefers it; submit the OpenSSH-format public-key line,
not a `.ppk` private key.
### Generating your key pair
On your own computer, generate a separate key if you do not already have one
you want to use. Choose a new filename rather than overwriting an existing key:
1. Open **Terminal** (in `/Applications/Utilities/`).
1. In the **Terminal** window, paste the following:
<pre>
mkdir -m 700 -p ~/.ssh # Create your .ssh directory
ssh-keygen -t rsa -b 2048 -f ~/.ssh/tilde.club # Create your keys
</pre>
1. The `ssh-keygen` program will generate two new keys (private and public), and prompt you for a password. Please set a good password (at least 9 characters, and not just letters or numbers), and make a note of it somewhere safe.
1. Now, in **Terminal**, type: `open ~/.ssh` to open a Finder window showing your `.ssh` directory.
1. Take the `tilde.club.pub` file and attach it to an email to ford@ (our friendly host). **Do not attach the file `tilde.club`** -- that is your *private* key, which you should never ever **ever** share with anyone.
```bash
ssh-keygen -t ed25519 -a 64 -f ~/.ssh/tilde_ed25519
```
### Using your key pair
Use a passphrase. Send only `tilde_ed25519.pub` to the operator or signup form.
The file without `.pub` is private and stays on your computer. A hardware-backed
key can also be used when both the client and host support it.
1. Open **Terminal**.
1. Type `ssh -i ~/.ssh/tilde.club username@tilde.club` (substituting your own username).
Connect with:
Note that it's possible to save an SSH configuration so you don't have to type this whole long thing every time; [here's a good primer](http://nerderati.com/2011/03/17/simplify-your-life-with-an-ssh-config-file/) on how to do that. If you save your private key passphrase in the OS X Keychain, you shouldn't need to enter it to login.
```bash
ssh -i ~/.ssh/tilde_ed25519 member@tilde.example.net
```
## Linux
A local `~/.ssh/config` entry saves typing:
### Generating your key pair
```sshconfig
Host mytilde
HostName tilde.example.net
User member
IdentityFile ~/.ssh/tilde_ed25519
IdentitiesOnly yes
ForwardAgent no
```
If you're on Linux you can use the same commands as on the Mac to generate and use a `tilde.club` key pair, but attaching `tilde.club.pub` to email is left as an exercise for the reader.
Verify a new host's fingerprint through a separate trusted channel. If it
changes later, ask why before replacing the saved host key. Agent forwarding
is normally unnecessary for using the host; enable it only for a specific
reason you understand.
### Using your key pair
## What the collected host does
At a command prompt, type `ssh -i ~/.ssh/tilde.club username@tilde.club` (substituting your own username).
The main daemon reads `/etc/ssh/sshd_config`. It has public-key support,
`PasswordAuthentication no`, keyboard-interactive authentication enabled, and
`UsePAM yes`. `AuthenticationMethods` is not set to require a combination;
the observed effective value is `any`.
Note that it's possible to save an SSH configuration so that you don't have to type this whole long thing every time; [here's a good primer](http://nerderati.com/2011/03/17/simplify-your-life-with-an-ssh-config-file/) on how to do that.
Its SSH PAM authentication stack requires `pam_unix` and
`pam_google_authenticator`, except for a special help account. That means the
PAM route asks for the Unix password and a TOTP code. It does **not** mean
public-key login must also complete that route: the collected SSH configuration
accepts a valid public key on its own. Existing account-specific exceptions and
a separate restricted help command must be reviewed locally rather than copied
into a new installation.
## Windows
`setup-2fa` is a small wrapper around `/usr/bin/google-authenticator`.
`check_google_authenticator.sh` is not referenced by the collected SSH PAM file;
its printed output is not an enforced authentication policy.
### Generating your key pair
The main config collected from this host does not contain an `Include` for
`sshd_config.d`. The presence of files in that directory does not make them
active. Check the actual service command and configuration before assuming a
drop-in changes anything.
The easiest way to generate a key pair on Windows is to use [PuTTY](http://www.chiark.greenend.org.uk/~sgtatham/putty/), a freely-available SSH client which comes with its own key generator.
## Key-only baseline for a new host
1. Download **PuTTY** [here](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html). (Specifically, get the Windows Installer version; if you download the binaries individually, make sure you get the **PuTTYgen** binary, which is what generates key pairs.)
1. Run **PuTTYgen**.
1. From the main window, click the **Generate** button; you'll be asked to move the mouse around to generate randomness, which you should do until **PuTTYgen** tells you that it's generating the key.
1. Once the key pair is generated, click the **Save public key** button, choose a directory to save it into, and in the "File name" field, type `tilde.club.pub`. Save the key.
1. Halfway down the main window, type a good password into the "Key passphrase" field (at least 9 characters, and not just letters or numbers), and repeat it in the "Confirm passphrase" field.
1. Click the **Save private key** button, choose the same directory as above, type `tilde.club` into the "File name" field, and save the private key.
1. Finally, email the `tilde.club.pub` file to ford@ (our friendly host). **Do not attach the file `tilde.club`** -- that is your *private* key, which you should never ever **ever** share with anyone.
Use [the example drop-in](../examples/ssh/00-tilde.conf) only after admin key
login and sudo have been tested. Put it in a directory that the actual main
config includes at global scope. Fedora's default layout normally provides
`/etc/ssh/sshd_config.d/*.conf`; existing customized configs may not.
### Using your key pair
OpenSSH generally uses the first value it obtains for a setting. A file placed
later in the include order will not necessarily override an earlier value.
Keep `Match` sections at the end and finish a conditional section with
`Match all` before adding unrelated global settings.
1. Run **PuTTY**.
1. In the first window that comes up, you should be in the main "Session" category (in the left-hand column). On the right, in the "Host Name" field, type `tilde.club`.
1. In the left-hand column, find the **SSH** entry underneath the **Connection** section and then click on the small + next to it to open it up; then find and click the **Auth** entry just below it.
1. On the right the last field should be "Private key file for authentication"; click the **Browse** button and find the `tilde.club` file you saved above. Select it and click the **Open** button.
1. Finally, click the **Open** button at the bottom of the main PuTTY window to open your connection. You'll be asked for your username ("login as:"); type it and hit Enter. You will then be asked for your key passphrase; type it and hit enter, and you're in.
Before applying anything, keep a backup of the main file, its includes, the
SSH PAM file, and any unit overrides. Keep a working connection and the provider
console available. Validate the **effective** settings as well as syntax:
Note that you can save these settings from that first page of the main PuTTY window, so you don't have to do this every time. You can also use [Pageant](http://the.earth.li/~sgtatham/putty/0.58/htmldoc/Chapter9.html) (another part of the **PuTTY** kit) to store your keys so you don't need to enter them each time you login.
```bash
sshd -t
sshd -T -C user=member,host=client.example.net,addr=192.0.2.20
systemctl reload sshd
```
## Android
Run connection tests from a second terminal on your own computer. Test the
admin and member separately. Match rules based on source address or local port
also need representative `addr`, `laddr`, and `lport` values in the test context.
A successful syntax check alone does not establish that the intended method
will be accepted.
### Generating your key pair
## Require MFA after enrollment
1. There are several SSH terminals in the Play store. [JuiceSSH](http://juicessh.com) is one of them, try it out, see if you like it.
2. Follow this [tutorial](http://sonelli.freshdesk.com/support/solutions/articles/187068-how-do-i-generate-an-ssh-key-within) for generating a key pair with JuiceSSH.
For a new host, keep the key-only baseline working while enrolling people.
The following is an example policy, not a report of the production host's
current enforcement:
### Using your key pair
1. Install `google-authenticator` and ensure time synchronization is healthy.
2. Create a group named `mfa-ready`.
3. Have the test member run `google-authenticator` interactively in their own
account. Use time-based tokens, save the recovery codes somewhere private
off the host, and complete a code check. Keep `.google_authenticator` private.
4. Keep the Fedora SSH PAM account/session stack. Add
`auth required pam_google_authenticator.so` to `/etc/pam.d/sshd` after its
normal authentication rules. Do not use `nullok` for an account that must
provide a second factor. Do not edit authselect-generated shared files to
make this SSH-only change.
5. Add the following conditional block at the end of the effective SSH config,
after the baseline global settings:
1. For JuiceSSH, follow this [tutorial](http://sonelli.freshdesk.com/support/solutions/articles/178806-how-to-create-define-a-new-ssh) on defining new credentials.
2. Let us know how it goes!
```sshconfig
Match Group mfa-ready
KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactive:pam
Match all
```
## Why go through all this?
With Fedora's password authentication PAM substack retained, this asks for a
key, the Unix password, and the authenticator code. If you want a key plus TOTP
without the Unix password, design and test that SSH-specific PAM stack instead
of removing password checks from the shared system authentication configuration.
One of the weakest links in online account security is your password. If someone were to take over the tilde server (specifically the SSH server, `sshd`), they would be able to capture the passwords of everyone who uses a password to log in. (This isn't theoretical -- [it has actually happened](http://www.apache.org/info/20010519-hack.html).) If users use their SSH keys to log in instead, hackers can't do this anymore; a user's private key never leaves their own computer and can't be captured on the server.
Validate the config, then add only the enrolled test account to the group:
Additionally, if you use an SSH agent, you can login without entering a password every time. On OS X the Apple Keychain provides this functionality. On Linux it's `ssh-agent` (optionally with assistance from [keychain](http://www.funtoo.org/Keychain)). On Windows `Pageant` provides this capability for [PuTTY](http://www.chiark.greenend.org.uk/~sgtatham/putty/).
```bash
groupadd -f mfa-ready
usermod --append --groups mfa-ready member
sshd -t
sshd -T -C user=member,host=client.example.net,addr=192.0.2.20
systemctl reload sshd
```
From the client, test correct and incorrect codes and confirm that key-only
batch authentication fails for the enrolled account:
```bash
ssh -o BatchMode=yes -o PreferredAuthentications=publickey \
-i ~/.ssh/tilde_ed25519 member@tilde.example.net
```
That last command is a negative test and should fail under the enforced policy.
Also verify normal interactive login, SFTP if offered, recovery codes, and admin
recovery. Scripts using SSH need a separately documented policy; silently
exempting all automation defeats a blanket MFA claim.
Only enroll more members after the full test passes. A user-editable startup
file or `.new_user` marker is onboarding state, not an access-control boundary.
## Legacy SSH
The existing host has a second daemon on TCP 2222. Its service reads
`/etc/ssh/sshd_config_legacy`, disables root and password login, restricts its
allowed user, and deliberately enables old RSA/SHA-1 and group1 algorithms.
This is a compatibility exception, not a general access method.
A new tilde does not need this service. If you have a specific legacy client
requirement, isolate it in a separate daemon and source/identity policy, verify
its effective settings with `sshd -T -f PATH`, and record who needs it and when
it can be retired. Do not weaken the normal daemon or the host-wide crypto
policy for every member.
## Recovery
Verify the person's identity before replacing a key or resetting MFA. Remove
lost factors, invalidate unused recovery codes when re-enrolling, and check
whether other services share the same account password. Test that the revoked
key or factor no longer works. Do not ask someone to send a private SSH key or
their TOTP secret in an ordinary support message.
References: [OpenSSH server configuration](https://man.openbsd.org/sshd_config)
and [Google Authenticator PAM](https://github.com/google/google-authenticator-libpam).
+69 -112
View File
@@ -1,130 +1,87 @@
# Usenet in 60 Seconds
# Usenet and local newsgroups
## Installation
The current shell host runs INN 2.7.3. Its reader rules, peer list, and spool
configuration were not supplied. The old Fedora 32 setup and manual history
rebuild have been replaced here with a local-first deployment procedure.
Create Linux machine (This is based on Fedora 32)
## Install and inspect the packaged layout
$ sudo -i
root $ dnf update
```bash
dnf install inn slrn
rpm -ql inn
systemctl cat innd.service
```
Use the config, service account, spool, history, and log paths supplied by that
package. Do not replace `pathnews` with `/usr/local/news` merely because an old
source-install guide used it. Keep a copy of the packaged examples and consult
the installed `inn.conf(5)` and `readers.conf(5)` manuals.
root $ dnf install innd
In `inn.conf`, set your own `organization`, `pathhost`, `domain`, and reader
`server` name. Retain the packaged storage/history layout until you have a
reason to change it. Keep the first deployment reachable only from loopback or
an explicit trusted network, using both the service's bind policy and the
firewall.
root $ vim /etc/hostname # set your hostname to something like `news`
root $ vim /etc/hosts # change local.localdomain to the new hostname
## Local reader policy
# should probably restart the machine to get the new hostname
INN separates reader authentication from reader access. A small example
`readers.conf` for trusted localhost shell clients is:
root $ sudo -u news bash
news $ vim /etc/inn.conf
```text
auth "shell-local" {
hosts: "127.0.0.1,::1"
default: "<shell-local>"
}
access "shell-local" {
users: "<shell-local>"
read: "local.*"
post: "local.*"
}
```
The top of my `inn.conf` looks like:
mta: "/usr/sbin/sendmail -oi -oem %s"
organization: "tilde.club"
ovmethod: tradindexed
hismethod: hisv6
pathhost: news.tilde.club
pathnews: /usr/local/news
This deliberately trusts connections originating on the machine and does not
authenticate an individual shell user. Decide whether that is sufficient for
your community. It is not an appropriate anonymous Internet posting policy.
Review the whole file: another broad auth/access block can grant access you
did not intend, and matching order matters.
#runasuser:
#runasgroup:
Use the package's `inncheck` as the news service user, fix reported errors,
then start `innd`. Locate the packaged admin binaries if they are not on PATH:
# General Settings
```bash
rpm -ql inn | rg '/(inncheck|ctlinnd|news.daily)$'
systemctl enable --now innd
```
domain: news.tilde.club
#innflags:
mailcmd: /usr/libexec/news/innmail
server: news.tilde.club
#syntaxchecks: [ no-laxmid ]
Create a disposable local group with the installed `ctlinnd newgroup` command,
for example `local.test`, as the news service user. Read, post, and retrieve a
test article with a local client. Check the configured spool and logs after a
restart. Test that a client outside the permitted source range is denied.
## Expiry and maintenance
Then you need to configure `readers.conf` to say who can connect, how, and what
kind of things they get when they log in.
Inspect the packaged expiry timer and cron integration before adding another
job. Configure `expire.ctl` for the groups and retention you intend to keep,
then verify `news.daily` actually runs. Monitor spool blocks and inodes, history
size, failures, and feed queues. A job in a document is not evidence that it is
scheduled on the host.
news $ mv /etc/news/readers.conf /etc/news/readers.conf.old
news $ vim /etc/readers.conf
Back up the config and recovery-critical spool/history state according to the
storage method and upstream recovery procedure. Do not run an old `makehistory`
recipe against a live spool without confirming the storage method, paths,
backups, and downtime requirements. Never move guessed `.hash`, `.dir`, or
`.index` files into place because they appeared in a guide for another INN version.
A rudimentary `readers.conf` looks like:
## Remote readers and peering
# Since tilde.club is very open and inviting, I don't mind having
# such a wild rule.
auth tilde.club {
hosts: *
default: <PUBLIC>
}
Keep these separate. TLS/authenticated reader access has a different purpose
from server-to-server article feeds. Before adding a remote peer, agree on
names, source addresses, offered groups, moderation/control policy, volume,
spam handling, and feed authentication. Configure `incoming.conf` and
`newsfeeds` for those peers specifically. Do not use a wildcard peer rule or
open reader posting simply to make a connection test pass.
access default {
users: <PUBLIC>
newsgroups: "*,!admin.*,!control,!control.*,!mode,!junk"
}
access admin {
users: "artem,ftrain@tilde.club"
newsgroups: *
access: "RPA"
perlfilter: false
}
And finally set up the cron job to expire messages. Make sure you add this as
the `news` user cron:
0 6 * * * /usr/libexec/news/news.daily > dev/null
## Running the server
This can be accomplished via systemd.
Here is how to enable it to start automatically:
root $ systemctl enable innd
The following runs the server:
root $ systemctl start innd
And the following stops it:
root $ systemctl stop innd
All errors are logged to `/var/log/news`. `/var/log/errlog` is a good resource for finding
out why your news server isn't running.
## Troubleshooting the server
If you know there are articles on the server and for some reason they do not show when you connect with a client, you can rebuild your history and index's.
root $ systemctl stop innd
root $ sudo -u news bash
news $ cd /var/lib/news/
news $ time /usr/libexec/news/makehistory -b -O -f history.n
news $ awk 'NF == 2 { print }' < history >> history.n
news $ /usr/libexec/news/makedbz -s `wc -l < history` -f history.n
news $ mv history.n history
news $ mv history.n.dir history.dir
news $ mv history.n.hash history.hash
news $ mv history.n.index history.index
news $ exit
root $ systemctl innd start
root $ sudo -u news /usr/libexec/news/ctlinnd renumber ''
Above guide was copied/modified from: https://lists.isc.org/pipermail/inn-workers/2003-September/012007.html
Use the current [INN documentation](https://www.eyrie.org/~eagle/software/inn/docs/),
including [reader access](https://www.eyrie.org/~eagle/software/inn/docs/readers.conf.html)
and [installation](https://www.eyrie.org/~eagle/software/inn/docs/install.html).
+211
View File
@@ -0,0 +1,211 @@
# Web publishing
The collected tilde.club configuration uses nginx, PHP-FPM, and fcgiwrap.
Apache's `mod_userdir`, `.htaccess`, and `a2enmod` do not configure this stack.
## Production layout
The main site's document root is `/usr/share/nginx/html`. Its server block
serves HTTP and HTTPS. Several separate service vhosts redirect HTTP to HTTPS,
but the main host's block serves both. The main nginx config includes
`/etc/nginx/sites-enabled/*`; this is a local layout to reproduce explicitly,
not an assumption about the distribution default.
| Request | Backend or directory |
| --- | --- |
| Main site | `/usr/share/nginx/html` |
| `/~USER/` and `/u/USER/` | `/home/USER/public_html/` |
| User PHP | `/run/php-fpm/php-fpm-php-user.sock`, pool UID `php-user`, group `club` |
| Main site PHP | `/run/php-fpm/www.sock`, pool UID/group `nginx` |
| User CGI | `/run/fcgiwrap/fcgiwrap-cgi.sock`; service runs as `cgiwrap` |
| `/~USER/git` | Local cgit wrapper through fcgiwrap |
| `/~USER/myplant` | Central Botany PHP application |
| WeeChat relay beneath a user page | A socket beneath the user's `.weechat` directory |
| Webmail | SnappyMail in a separate vhost |
The user-page location enables directory listings and SSI, and uses a per-user
HTML 404 page. Account for those features when replacing a running config.
The collected pool generator can create per-user FPM sockets, but the main
user-page route uses the **shared** pool. `makeuser` does not call the pool
generator. Do not describe this as per-user PHP execution.
The CGI unit has UID/GID-changing capabilities. Those capabilities do not, by
themselves, establish that each script is switched to its owner. That needs the
wrapper implementation and a runtime identity test. Likewise, the collected
CGI `SCRIPT_FILENAME` expression prepends a home path to `$request_filename`
inside an alias location. Verify the expanded path before using it in a new
config. The new-host examples below use explicit per-account paths instead.
## Start with static pages
Use [tilde-static.conf](../examples/nginx/tilde-static.conf) inside nginx's
`http` context and replace the example domain. It provides both user URL forms,
blocks dotfiles and script-source downloads, and disables symlinks in public
user content. The example leaves directory listings and SSI off; enable those
features deliberately if your community wants them.
A 0711 home can be traversed without being listed. Public directories need
0755 and public files 0644. Keep the user's other files private. Check every
path component with `namei -l`, not just the page's final mode.
For a new SELinux-enforcing host, use the installed policy's home publishing
support. A common Fedora layout is:
```bash
setsebool -P httpd_enable_homedirs on
semanage fcontext -a -t httpd_user_content_t '/home/[^/]+/public_html(/.*)?'
restorecon -RFv /home/member/public_html
```
If a matching custom rule already exists, review it and use `semanage fcontext
-m` to modify it instead of adding a duplicate. Test access as the service and
inspect AVC denials. Policy versions and service domains can require additional
booleans or labels for writing or connecting to other services; enable only the
access actually needed. The production snapshot with SELinux disabled cannot
validate those policy rules for a new enforcing installation.
Useful checks:
```bash
nginx -t
namei -l /home/member/public_html/index.html
ls -Zd /home/member /home/member/public_html
ausearch -m AVC,USER_AVC -ts recent
journalctl -u nginx --since today
```
The production host sends nginx logs to syslog; do not assume every nginx error
will be in `/var/log/nginx/error.log`. Check the effective `error_log` setting.
## Add HTTPS
After obtaining a certificate, retain the HTTP challenge location and decide
whether other HTTP requests should be served or redirected. For an HTTPS-only
policy, redirect the non-challenge requests to a fixed configured hostname,
rather than constructing a redirect from an unvalidated Host header.
Copy the publishing locations into an HTTPS server block with these settings,
using the certificate paths returned by Certbot:
```nginx
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name tilde.example.net;
ssl_certificate /etc/letsencrypt/live/tilde.example.net/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/tilde.example.net/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
```
Keep shared locations in one root-owned include if both server blocks use them.
Set response headers in the context where they are intended to apply. Test
headers on error responses and nested locations; inheritance is version-specific.
A blanket CSP, frame policy, or cross-origin isolation policy on every member's
page can break the projects you are hosting. Keep the operator application and
member publishing policies separate. Add HSTS only after deciding that all of
its affected names will continue to support HTTPS.
A Certbot deploy hook for nginx can be a root-owned executable file in
`/etc/letsencrypt/renewal-hooks/deploy/`:
```bash
#!/usr/bin/env bash
set -euo pipefail
/usr/sbin/nginx -t
/usr/bin/systemctl reload nginx
```
Enable the packaged renewal timer and test renewal. The collected production
service instead invokes `/usr/local/bin/certbot renew --standalone`, with hooks
to stop and start nginx. Its timer checks at 06:09 and 18:09 in the host's local
time, with `Persistent=true`. That produces a different availability profile
from webroot renewal. Verify all certificate consumers after renewal, including
Dovecot and Postfix if they share the certificate.
## Add PHP with a deliberate execution identity
A new host can give an enabled member a dedicated pool. Install maintained
packages, including the process extension if you use the identity test below:
```bash
dnf install php-fpm php-cli php-process
```
Review [member.conf](../examples/php-fpm/member.conf), then install it as
`/etc/php-fpm.d/member.conf`. The Unix account and group must already exist.
Create the private session directory named in that file:
```bash
sudo -u member -- install -d -m 0700 /home/member/.local/state/php/sessions
php-fpm -t
systemctl enable --now php-fpm
```
If PHP-FPM is already running, reload it after successful validation. Ensure the
nginx worker can connect to the new socket. With SELinux enabled, directory
labels, FPM's domain, and socket policy need to be checked on the target system.
Insert [member-php.inc](../examples/nginx/member-php.inc) into the site's server
block **after** the dotfile-denial location but **before** the generic script
source-denial and static userdir locations. Keep both HTTP and HTTPS policy
consistent. The example supports `.php` endpoints for this named member; it
does not implement PATH_INFO routing for arbitrary frameworks. If you want PHP
index files, add `index.php` to the userdir index list after enabling its handler.
Validate and reload nginx. Use this temporary test in the member's public
directory, then remove it:
```php
<?php
declare(strict_types=1);
header('Content-Type: text/plain; charset=utf-8');
echo 'sapi=', PHP_SAPI, "\n";
echo 'uid=', posix_geteuid(), "\n";
```
Compare the response UID with `id -u member`. Do not use `get_current_user()` as
an execution-identity test: it reports the script file's owner. Also test that
the backend cannot read a second member's private file. FPM running as the member
still has all the access that member has; it is not a container or a separate
security identity from their shell.
Tune `pm.max_children`, process lifetime, memory, uploads, and logging to the
machine. Avoid eagerly starting several workers for every dormant account.
The collected shared pool uses 20 maximum children and a 120-second request
limit; those are production settings for that pool, not universal defaults.
## CGI
A new installation can use Fedora's `fcgiwrap@.service` template with a separate
instance for an enabled member. Inspect the installed unit with `systemctl cat`
and confirm the service runs as that member. Override the matching socket's
owner/group so nginx can connect, keeping the socket at 0660. Do not grant a
shared CGI worker UID-changing capabilities merely to imitate the current unit.
Restrict a new CGI handler to the specific member's `public_html/cgi-bin` path.
Use a fixed `SCRIPT_FILENAME` mapping for that account, verify the target is a
real executable, and test the interpreter, UID, environment, and filesystem
access before enabling it. Keep source-file denial for scripts without an
enabled handler. PATH_INFO and executable index support need their own tests.
The existing host's generalized CGI wrapper was not supplied, so this guide
does not claim it can reproduce its owner-switching behavior.
## Publishing tests
Use two disposable members and check response bodies and filesystem effects,
not just status codes:
- Both user URL forms, nested directories, index files, and trailing-slash redirects.
- Missing users and scripts, dotfiles, percent-encoded paths, and traversal attempts.
- A symlink from public content to a nonpublic readable file.
- PHP source denial before enabling PHP, then the correct runtime UID afterward.
- A script under one account trying to read or write the other account's private data.
- User custom error pages, directory listings, SSI, and optional routes where enabled.
- TLS certificate validation and all response headers on successful and error responses.
Remove diagnostics and downloadable configuration archives from the web root
after use. Keep configs root-owned, validate before reload, and keep the previous
config available for rollback.