diff --git a/README.md b/README.md index 4a187fd..33977b1 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,64 @@ -tilde.club -========== +# tilde.club -This is the set of code and utilities for running and managing the -tilde.club server. +This repo documents how to build and run a shared Unix machine like +[tilde.club](https://tilde.club). It also keeps the older site utilities at their +existing paths. -# What is tilde.club? -- [https://tilde.club](https://tilde.club) is one unmodified unix server on the Internet - - Several thousand people have accounts - - It was launched at the end of September 2014 -- Tilde.Club is *also* an emerging, mutually supportive community of dozens hundreds thousands of people launching similar servers. - - These people enjoy working in the tool-rich, very social Unix programming environment. - - Many of these people want to help new users use these open, free tools to empower themselves. - - Even new users who are scared of command-lines. - - Especially new users who want to learn but don't like being laughed at. - - _We won't laugh at you because we know how hard it is to learn about technology._ - - _If this is confusing we will try to help._ -- [Here is the story of how tilde.club started](https://medium.com/message/tilde-club-i-had-a-couple-drinks-and-woke-up-with-1-000-nerds-a8904f0a2ebf). Basically, one person had a few too many drinks and launched a cheap computer in the cloud. Github user @ftrain is the initiator and recently Github user @mbuchholz81 has taken over tilde.club from @ftrain. -- [Here is the FAQ](https://tilde.club/wiki/faq.html) which is being developed here at this GitHub repository. -- No particular entity controls tilde.club. It's emerging as sort of an open, basically free mini-Internet for people who need some refuge from the regular Internet. It was founded with the rule, "no drama," a rule directly inspired by the Mary J. Blige song "No More Drama." So far there's only been a little drama. It's only been three weeks, though. If we're honest with ourselves there is going to be drama. +Members get a shell account, space for their own pages, and tools for writing, +programming, chatting, and making whatever interests them. You do not need to +know all of Unix before joining. Helping people learn is part of running the +place. -It's all moving very fast. +## Build your own -# Getting started: run a tilde and help users build on it +Start with [Build a tilde-style machine](docs/how-to-set-up-a-tilde.md). +It follows a Fedora/nginx setup from a new host through account creation, +publishing, limits, backups, and launch checks. -If your goal is to launch your own tilde-style host (or improve an existing one), start here: +The [current system record](docs/current-system.md) describes what was confirmed +on tilde.club on October 22, 2026. The [example configs](examples/README.md) are +separate starting points for a new host. Applying this repo patch does not +install them or change a running server. -- [Build your own tilde-style server](docs/how-to-set-up-a-tilde.md) -- [Shell server setup notes](docs/shellserver.md) -- [SSH key onboarding guide](docs/ssh.md) -- [Current `/etc/skel` permissions reference](docs/etc-skel-permissions.md) -- [User and session accounting quick reference](docs/accounting.md) +| Topic | Guide | +| --- | --- | +| Base installation | [Build a tilde-style machine](docs/how-to-set-up-a-tilde.md) | +| Accounts and onboarding | [Account creation and first login](docs/accounts.md) | +| Login and recovery | [SSH keys and MFA](docs/ssh.md) | +| Home defaults | [Skeleton and permissions](docs/etc-skel-permissions.md) | +| Publishing | [nginx, PHP-FPM, and CGI](docs/web.md) | +| Mail | [Postfix, Dovecot 2.4, submission, and filtering](docs/mail.md) | +| Other publishing | [Gopher, Gemini, and finger](docs/publishing.md) | +| Chat | [IRC operations](docs/ircserver.md) and [connection details](docs/ircserver-connections.md) | +| News | [INN and local newsgroups](docs/usenet.md) | +| Daily operation | [Quotas, resource limits, firewall, and maintenance](docs/shellserver.md) | +| Recovery | [Borg/borgmatic backups and restore](docs/backups.md) | +| Usage records | [Login and process accounting](docs/accounting.md) | +| Website and apps | [Sources, signups, wiki, and community tools](docs/community.md) | -These documents are focused on practical operations, onboarding, and adding community functionality in safe increments. +The older [server.org](docs/server.org) path remains as a pointer. The +[API notes](api/README.org), [recent-page utility notes](api/recently-updated/README.md), +and [connection-list notes](pages/list-html-connections/README.md) explain the +retained programs and their limitations. They are not an unattended installer. -# Help Wanted -Tilde.club is moving very quickly and is 100% volunteer led. +## About tilde.club -## 1. optimal: pull requests and bug reports -- There is a lot of great work going on around config, FAQ, basic docs. -- Jump in if you want. Literally everyone here said "I am ready to help, what can I do." -- We need non-coders (writers, editors, communicators, artists, project managers) as much as we need tech folks. -- Most of the coding work is done for us by the Unix operating system. We're celebrating that. -- A lot of the basic docs are in the [wiki](https://github.com/tildeclub/tilde.club/wiki) which you (yes, you) can edit. +Tilde.club started in September 2014. Paul Ford wrote about +[how it began](https://medium.com/message/tilde-club-i-had-a-couple-drinks-and-woke-up-with-1-000-nerds-a8904f0a2ebf). +The [website](https://tilde.club) and [user wiki](https://tilde.club/wiki/) cover +joining and using the server. This repo covers operating one. -## 2. suboptimal: open ended feature requests and broad discussion -- We love philosophy, just not in our issue tracker! +[Preguntas frecuentes en español](faq-esp.md). + +## Contributing + +Corrections, tested configs, and missing instructions are welcome. Include the +OS and software versions you used, what you changed, and how you checked the +result. Documentation fixes are useful contributions too. + +Use example domains and accounts. Leave passwords, private keys, MFA secrets, +and members' personal information out of commits and issue attachments. Keep +production-specific settings distinct from advice for a new installation. + +Existing utilities are licensed under the terms retained in [LICENSE](LICENSE) +and their source notices. This update preserves those files and attributions. diff --git a/api/README.org b/api/README.org index 7b523cb..29167b6 100644 --- a/api/README.org +++ b/api/README.org @@ -1,7 +1,24 @@ -Gather up the code that people are writing and put it here -- chat tools -- user listing -- user # -- list of sites -- river -- +#+TITLE: Site utilities + +* Scope + +The files under this directory are retained utility implementations, not the +current server configuration or an automated installation system. Start with +[[file:../docs/how-to-set-up-a-tilde.md][the installation guide]] and [[file:../docs/community.md][community applications]]. + +* Recently updated pages + +See [[file:recently-updated/README.md][the implementation notes]] before using these scripts. +A separate public repo also exists at https://github.com/tildeclub/recently-updated. +Inspect the chosen version; do not infer production deployment from a repo name. + +Run indexing as an unprivileged service identity with only intended public-read +access, stage output outside the served tree, validate it, and publish atomically. +Do not run a broad scan of homes as root from a web request. + +* Adding an endpoint + +Document its input, output schema, account visibility, schedule, data retention, +error behavior, and owner. Escape HTML and serialize JSON with a real serializer. +A local account name or filesystem filename is data, not trusted markup. +Keep private account fields and credentials out of generated public output. diff --git a/api/recently-updated/README.md b/api/recently-updated/README.md index 11a1e9e..f8d15e4 100644 --- a/api/recently-updated/README.md +++ b/api/recently-updated/README.md @@ -1,40 +1,46 @@ -# Recently-Updated Page Shell Scripts +# Recently updated pages: retained implementations -As soon as [~ford](http://tilde.club/~ford) granted me entrance to tilde.Club, I knew that I wanted to keep tabs on what cool things folks were doing there. And thinking back to the mid-90s, what was the best way to know what was new on a site? A recently-updated page! So I set about making one for tilde.club. +These scripts are kept so existing users do not lose the programs or old paths. +They are historical implementations, not a tested production installation +recipe. The maintained deployment source must be chosen and reviewed separately; +there is also [tildeclub/recently-updated](https://github.com/tildeclub/recently-updated). -There's no question at all that this is a rudimentary hack, but it's the *exact* sort of hack that was The Way To Do Things on the mid-90s Internet. (Note that that's the mid-90s-capital-I-Internet, not the [21st-century-lowercase-i-internet](http://www.nytimes.com/2002/12/29/weekinreview/29SCHW.html).) And I'm using tried-and-true command-line Unix filesystem commands to grab, parse, and sort the list of pages, so I feel like I'm treading in the footsteps of thousands before me. The result is [the list of all tilde.club pages updated in the last 24 hours](http://tilde.club/~delfuego/tilde.24h.html), which is generated from the ```findmod.sh``` shell script once a minute from a crontab job. +## Files and interfaces -Of course, while the HTML version of the recently-updated list is firmly mid-90s (well, if you view source you'll see that it's 21st-century HTML5, but still), enabling cooler 21st-century functionality required making the same list available in a more easily-parsed format. Enter JSON, and the [API version of the recently-updated list](http://tilde.club/~delfuego/tilde.24h.json), generated by the ```findmod-json.sh``` shell script once a minute via crontab as well. +| File | Behavior | +| --- | --- | +| `findmod.sh` | Writes HTML to stdout for recently changed top-level `index.html` files. | +| `findmod-json.sh` | Writes a JSON-like `pagelist` response to stdout for the same index scan. | +| `findmod2.pl` | Scans each public subtree and writes both HTML and JSON output files. | +| `header.tpl`, `footer.tpl` | HTML fragments used by the original shell implementation's external template paths. | -Hope these are useful to someone! +The original shell script hard-codes template paths in another user's home and +an HTTP domain. Configure a reviewed copy for your own domain and deployment +path rather than creating that person's home layout on a new host. -Jason Levine, aka [~delfuego](http://tilde.club/~delfuego) +The Perl program accepts `--domain`, `--root`, `--since-hours`, `--destdir`, +`--update-every`, `--verbose`, and `--help`. Its default window is 24 hours and +its output names are `tilde.24h.html` and `tilde.24h.json` (with the chosen hour +count substituted). A positive update period makes it keep rescanning. +## Limitations to fix before deployment -# Recently-Updated Page Perl Script +The retained implementations concatenate strings instead of safely serializing +all output. The Perl JSON writer includes a trailing comma, so its nonempty JSON +output is invalid. HTML escaping and URL encoding also need review. Filesystem +names must not be trusted as markup, and timestamps labeled UTC must actually +be generated in UTC. The shell regex also restricts which usernames it finds. -`findmod2.pl` is a pure-Perl5 reimplementation of ~delfuego's scripts. -It's self-contained, readable, well-documented and will search a -user's entire `public_html` subtree for the most recent file rather -than just using the date of the `index.html` file. +The tree scanner can traverse large user-controlled public trees. Define bounds, +symlink handling, public-file eligibility, error handling, and exclusion policy. +Run with read access only to intended public content; running it as root can +publish information about files the web server itself cannot read. -It accepts the following command-lines options: +For a deployment, validate JSON with `python3 -m json.tool`, check HTML and URLs, +write to a private temporary file on the destination filesystem, and rename the +validated output into place. Do not truncate the live page at the start of a +scan or repeatedly publish partial files from overlapping jobs. -* --domain *name* -- Domain name for the site. Guesses if omitted. -* --root *url* -- Root URL to use in links. Derived from domain by default. -* --since-hours *hours* -- Number of hours considered recent. Default = 24. -* --destdir *path* -- Destination directory for output files. -* --update-every *seconds* -- Regenerate the lists every *seconds*. -* --verbose -- Print more information as the program runs. -* --help -- Print a simple help message and exit. - -If `--update-every` is given with a positive number, `findmod2.pl` -will loop forever, waiting the given number of seconds before -rescanning and regenerating the list. Otherwise, it will scan once -and exit. - -The output files are named `tilde.XXh.json` and `tilde.XXh.html`, -where *XX* is replaced by number of hours considered recent (24 or the -argument given with `--since-hours`). - -Chris Reuter, [~suetanvil](http://totallynuclear.club/~suetanvil). +The original scripts were contributed by Jason Levine (~delfuego) and Chris +Reuter (~suetanvil). Their interfaces and code are preserved by this docs update; +no claim is made that they have been repaired or replaced here. diff --git a/docs/accounting.md b/docs/accounting.md index 9a7381b..c3e4221 100644 --- a/docs/accounting.md +++ b/docs/accounting.md @@ -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). diff --git a/docs/accounts.md b/docs/accounts.md new file mode 100644 index 0000000..21e3f64 --- /dev/null +++ b/docs/accounts.md @@ -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. diff --git a/docs/backups.md b/docs/backups.md new file mode 100644 index 0000000..9c0b66b --- /dev/null +++ b/docs/backups.md @@ -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. diff --git a/docs/community.md b/docs/community.md new file mode 100644 index 0000000..0d7c20d --- /dev/null +++ b/docs/community.md @@ -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). diff --git a/docs/current-system.md b/docs/current-system.md new file mode 100644 index 0000000..c95619c --- /dev/null +++ b/docs/current-system.md @@ -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. diff --git a/docs/etc-skel-permissions.md b/docs/etc-skel-permissions.md index 93d52e4..0c08a51 100644 --- a/docs/etc-skel-permissions.md +++ b/docs/etc-skel-permissions.md @@ -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 + + + + +My tilde page +

It works.

+

Edit ~/public_html/index.html to make this page your own.

+ +``` -./.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. diff --git a/docs/how-to-set-up-a-tilde.md b/docs/how-to-set-up-a-tilde.md index cf5f118..193c0bd 100644 --- a/docs/how-to-set-up-a-tilde.md +++ b/docs/how-to-set-up-a-tilde.md @@ -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 - - - AllowOverride All - Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec - Require method GET POST OPTIONS - -``` - -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' - - - testuser page -

hello from testuser

- -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' - - - Welcome - -

It works!

-

Edit this file to publish your page.

- - -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. diff --git a/docs/ircserver-connections.md b/docs/ircserver-connections.md index 53debcc..e6edb4b 100644 --- a/docs/ircserver-connections.md +++ b/docs/ircserver-connections.md @@ -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 | | -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. diff --git a/docs/ircserver.md b/docs/ircserver.md index f0c2d36..235cc2d 100644 --- a/docs/ircserver.md +++ b/docs/ircserver.md @@ -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. diff --git a/docs/mail.md b/docs/mail.md new file mode 100644 index 0000000..9d1eba6 --- /dev/null +++ b/docs/mail.md @@ -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. diff --git a/docs/publishing.md b/docs/publishing.md new file mode 100644 index 0000000..52d9db2 --- /dev/null +++ b/docs/publishing.md @@ -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. diff --git a/docs/server.org b/docs/server.org index b13be32..93e4cee 100644 --- a/docs/server.org +++ b/docs/server.org @@ -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. diff --git a/docs/shellserver.md b/docs/shellserver.md index 7c8d303..9c5ab03 100644 --- a/docs/shellserver.md +++ b/docs/shellserver.md @@ -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. diff --git a/docs/ssh.md b/docs/ssh.md index cbd92b9..e071657 100644 --- a/docs/ssh.md +++ b/docs/ssh.md @@ -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: -
-mkdir -m 700 -p ~/.ssh                          # Create your .ssh directory
-ssh-keygen -t rsa -b 2048 -f ~/.ssh/tilde.club  # Create your keys
-
-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). diff --git a/docs/usenet.md b/docs/usenet.md index fd9a9d5..b655ee2 100644 --- a/docs/usenet.md +++ b/docs/usenet.md @@ -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: "" +} +access "shell-local" { + users: "" + 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: - } +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: - 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). diff --git a/docs/web.md b/docs/web.md new file mode 100644 index 0000000..c7439d6 --- /dev/null +++ b/docs/web.md @@ -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)$ { + # try_files changes $uri; retain the original public script URL for PHP. + set $member_php_uri $uri; + root /home/member/public_html; + disable_symlinks on; + try_files /$member_php_script =404; + fastcgi_param QUERY_STRING $query_string; + fastcgi_param REQUEST_METHOD $request_method; + fastcgi_param CONTENT_TYPE $content_type; + fastcgi_param CONTENT_LENGTH $content_length; + fastcgi_param SCRIPT_FILENAME /home/member/public_html/$member_php_script; + fastcgi_param SCRIPT_NAME $member_php_uri; + fastcgi_param REQUEST_URI $request_uri; + fastcgi_param DOCUMENT_URI $member_php_uri; + fastcgi_param DOCUMENT_ROOT /home/member/public_html; + fastcgi_param SERVER_PROTOCOL $server_protocol; + fastcgi_param REQUEST_SCHEME $scheme; + fastcgi_param HTTPS $https if_not_empty; + fastcgi_param GATEWAY_INTERFACE CGI/1.1; + fastcgi_param SERVER_SOFTWARE nginx; + fastcgi_param REMOTE_ADDR $remote_addr; + fastcgi_param REMOTE_PORT $remote_port; + fastcgi_param REMOTE_USER $remote_user; + fastcgi_param SERVER_ADDR $server_addr; + fastcgi_param SERVER_PORT $server_port; + fastcgi_param SERVER_NAME $server_name; + fastcgi_param HTTP_PROXY ""; + fastcgi_pass unix:/run/php-fpm/tilde-member.sock; +} diff --git a/examples/nginx/tilde-static.conf b/examples/nginx/tilde-static.conf new file mode 100644 index 0000000..0b54143 --- /dev/null +++ b/examples/nginx/tilde-static.conf @@ -0,0 +1,39 @@ +# HTTP bootstrap for a new host. Replace the domain, then add TLS. +# This file belongs inside nginx's http context, e.g. /etc/nginx/conf.d/. +server { + listen 80; + listen [::]:80; + server_name tilde.example.net; + root /usr/share/nginx/html; + index index.html; + + add_header X-Content-Type-Options nosniff always; + add_header Referrer-Policy strict-origin-when-cross-origin always; + + location ^~ /.well-known/acme-challenge/ { + root /var/lib/letsencrypt; + default_type text/plain; + try_files $uri =404; + } + + # These precede the userdir regex so private files are denied there too. + location ~ (^|/)\. { + return 404; + } + + # A static-only host must not download executable script source as text. + location ~* \.(?:php[0-9]*|phtml|phar|cgi|pl|py|sh|lua)(?:/|$) { + return 404; + } + + location ~ "^/(?:~|u/)(?[a-z_][a-z0-9_-]{0,31})(?/.*)?$" { + alias /home/$tilde_user/public_html$tilde_path; + index index.html index.htm; + autoindex off; + disable_symlinks on; + } + + location / { + try_files $uri $uri/ =404; + } +} diff --git a/examples/php-fpm/member.conf b/examples/php-fpm/member.conf new file mode 100644 index 0000000..400e609 --- /dev/null +++ b/examples/php-fpm/member.conf @@ -0,0 +1,18 @@ +[tilde-member] +user = member +group = member +listen = /run/php-fpm/tilde-member.sock +listen.owner = nginx +listen.group = nginx +listen.mode = 0660 +pm = ondemand +pm.max_children = 3 +pm.process_idle_timeout = 10s +pm.max_requests = 500 +request_terminate_timeout = 60s +clear_env = yes +security.limit_extensions = .php +php_admin_value[memory_limit] = 128M +php_admin_flag[display_errors] = off +php_admin_flag[log_errors] = on +php_admin_value[session.save_path] = /home/member/.local/state/php/sessions diff --git a/examples/postfix/main.cf.fragment b/examples/postfix/main.cf.fragment new file mode 100644 index 0000000..e3f6a32 --- /dev/null +++ b/examples/postfix/main.cf.fragment @@ -0,0 +1,20 @@ +# New-host fragment. Merge into main.cf; retain distro defaults not shown here. +myhostname = tilde.example.net +mydomain = tilde.example.net +myorigin = $mydomain +mydestination = $myhostname, localhost.$mydomain, localhost +inet_interfaces = all +inet_protocols = all +mynetworks = 127.0.0.0/8 [::1]/128 +relayhost = +home_mailbox = .mail/ +mailbox_transport = lmtp:unix:private/dovecot-lmtp +smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination +smtpd_sasl_type = dovecot +smtpd_sasl_path = private/auth +smtpd_sasl_auth_enable = no +smtpd_tls_auth_only = yes +smtpd_tls_security_level = may +smtp_tls_security_level = may +smtpd_tls_cert_file = /etc/letsencrypt/live/tilde.example.net/fullchain.pem +smtpd_tls_key_file = /etc/letsencrypt/live/tilde.example.net/privkey.pem diff --git a/examples/postfix/master.cf.fragment b/examples/postfix/master.cf.fragment new file mode 100644 index 0000000..2719456 --- /dev/null +++ b/examples/postfix/master.cf.fragment @@ -0,0 +1,14 @@ +# Merge these named services into the existing master.cf, replacing duplicates. +# Keep the distribution's smtp, local, cleanup, qmgr, and other required services. +submission inet n - n - - smtpd + -o syslog_name=postfix/submission + -o smtpd_tls_security_level=encrypt + -o smtpd_sasl_auth_enable=yes + -o smtpd_relay_restrictions=permit_sasl_authenticated,reject + -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject +smtps inet n - n - - smtpd + -o syslog_name=postfix/smtps + -o smtpd_tls_wrappermode=yes + -o smtpd_sasl_auth_enable=yes + -o smtpd_relay_restrictions=permit_sasl_authenticated,reject + -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject diff --git a/examples/ssh/00-tilde.conf b/examples/ssh/00-tilde.conf new file mode 100644 index 0000000..37f4e4c --- /dev/null +++ b/examples/ssh/00-tilde.conf @@ -0,0 +1,20 @@ +# New-host baseline. Install only after key login and sudo work. +# The actual main sshd config must include this file at global scope. +# Check first-value precedence and verify sshd -T before reloading. +PermitRootLogin no +PubkeyAuthentication yes +PasswordAuthentication no +KbdInteractiveAuthentication no +GSSAPIAuthentication no +HostbasedAuthentication no +UsePAM yes +AuthenticationMethods publickey +PermitEmptyPasswords no +PermitUserEnvironment no +X11Forwarding no +AllowAgentForwarding no +AllowTcpForwarding no +PermitTunnel no + +# Optional MFA belongs in a later Match block in the main configuration. +# See docs/ssh.md. Leave the distribution's subsystem configuration in place. diff --git a/faq-esp.md b/faq-esp.md index b09ab91..eec7e59 100644 --- a/faq-esp.md +++ b/faq-esp.md @@ -1,49 +1,39 @@ -"based on revison https://github.com/tildeclub/tilde.club/commit/6a35a61621150a9547fb63201524c95ab513177b " -# FAQ EN ESPAÑOL +# Preguntas frecuentes - ________________________________________ - / BIENVENIDO A TILDE.CLUB Un lugar para \ - \ las páginas web / - ---------------------------------------- - \ ^__^ - \ (oo)\_______ - (__)\ )\/\ - ||----w | - || || +## ¿Qué es tilde.club? -## ¿Quieres unirte? ¡Estaremos encantados de conocerte! +Es una máquina Unix compartida. Cada miembro tiene una cuenta de shell y espacio +para publicar páginas, aprender y trabajar en sus propios proyectos. -Tenemos algún que otro problema de capacidad y organización que solucionar, pero estamos trabajando activamente en ello. ¡[Sigue la conversación aquí](https://github.com/tildeclub/tilde.club/issues/18) y únete a la lista de espera [aquí](http://goo.gl/forms/gRMRT1YBU4)! +## ¿Cómo solicito una cuenta? -[Varios clubs alternativos han surgido](http://tilde.club/%7Epfhawkins/othertildes.html) y carecen de lista de espera. Únete a alguno de ellos mientras trabajamos en la forma de conectarnos en una feliz e hipervinculada red de intereses comunes. +Consulta el formulario y las instrucciones actuales en [tilde.club](https://tilde.club). +La aceptación y el tiempo de respuesta dependen de los operadores. Los enlaces +antiguos a listas de espera ya no son las instrucciones de esta página. -## ¿Cómo surgió esta cosa accidentalmente increíble? +Necesitas una clave pública SSH. Conserva la clave privada en tu propio equipo; +no la envíes al formulario ni por correo. La [guía SSH](docs/ssh.md) explica cómo +generar una clave y conectarte desde Linux, macOS o Windows. -[Paul ha escrito una explicacion en Medium](https://medium.com/message/tilde-club-i-had-a-couple-drinks-and-woke-up-with-1-000-nerds-a8904f0a2ebf) y puedes leer su ideas [en su blog en tilde](http://tilde.club/~ford/). +## ¿Qué hago después de entrar? -## Ya estoy en Tilde, ¿y ahora qué? +Lee las instrucciones de bienvenida, configura la autenticación indicada por el +operador y consulta la [wiki](https://tilde.club/wiki/). Tus páginas web se editan +en `~/public_html`. Gopher y Gemini usan directorios distintos si están habilitados. -Tilde cuenta con un gran espíritu de communidad y estamos intentando por todos los medios que se mantenga así. Estamos redactando unas normas de etiqueta para los recién llegados. Mientras tanto… +Respeta a los demás usuarios, sus archivos y los recursos compartidos. Si algo +no funciona, pregunta; no necesitas conocer todos los comandos antes de empezar. -- ¡NO PIRATEES EL GIBSON! -- Nada de dramas. Se respetuoso. Diviértete. Nos estamos esforzando y estamos todos juntos en esto. +## ¿Puedo montar una máquina parecida? -## Lo he pillado, no seas un capullo. Pero cómo, ehhh, ¿hago cosas? +Sí. Empieza por la [guía de instalación](docs/how-to-set-up-a-tilde.md) y el +[estado documentado del servidor](docs/current-system.md). Los ejemplos usan +dominios y cuentas de prueba. Debes adaptar la configuración, mantener los +servicios y comprobar las copias de seguridad y su restauración. -[~anthonydpaul te tiene cubierto con el tutorial oficial no oficial introductorio](http://tilde.club/~anthonydpaul/primer.html). - -## Otros enlaces - -- [Github](https://github.com/tildeclub/tilde.club) -- [Webring](http://tilde.club/~harper/link.html?action=join) -- [Skillswap](http://goo.gl/forms/LT2bDgtmwH) - -Estamos deseando conocerte. - - - - __ .' '. - _/__) . . . - (8|)_}}- . . . - `\__) '. . ' ' . . ' +## ¿Dónde están las instrucciones completas? +- [Sitio y solicitudes de cuenta](https://tilde.club) +- [Wiki para usuarios](https://tilde.club/wiki/) +- [Documentación de administración](README.md) +- [Código y correcciones](https://github.com/tildeclub/tilde.club) diff --git a/pages/list-html-connections/README.md b/pages/list-html-connections/README.md new file mode 100644 index 0000000..7e9351f --- /dev/null +++ b/pages/list-html-connections/README.md @@ -0,0 +1,20 @@ +# Connection-list utility + +`social.sh` and `ack2list.pl` are retained historical utilities. The shell +pipeline uses `ack` to find local tilde links, then the Perl script generates an +HTML table. `css/social.css` supplies the stylesheet. + +The original pipeline assumes a working directory containing `ack2list.pl` and +an output directory named `public_html`. It scans `/home`, has hard-coded +exclusions, and does not safely escape all filesystem/link values as HTML. +It is not an appropriate root job or a web-request handler on a new shared host. + +Before using a reviewed copy, restrict scanning to intended public HTML, +handle permissions and symlinks, bound the scan, validate/escape the output, +and publish atomically from staging. Decide whether indexing is opt-in and +how members exclude a page. Preserve other people's private files and do not +interpret local links as shell commands. + +The docs refresh keeps the utility and stylesheet intact for existing users. +See [community tooling](../../docs/community.md) for the current deployment +approach and [the setup guide](../../docs/how-to-set-up-a-tilde.md) for the host.