Commit 26312429 authored by Kevin Lyda's avatar Kevin Lyda
Browse files

Add an install file

Also tweak expirations.  We don't need to as aggressively expire messages.
Storage is way cheaper now.
parent 958b2ad3
Loading
Loading
Loading
Loading

INSTALL.md

0 → 100644
+182 −0
Original line number Diff line number Diff line
# Installing BULLETIN

## Prerequisites

  * Go 1.25 or later
  * A Unix user account dedicated to running BULLETIN
  * SSH server with `AuthorizedKeysCommand` support (OpenSSH 6.2+)
  * Root access (for sshd configuration)

## Building

```
make
```

This produces a `bulletin` binary in the current directory.  Install it
somewhere permanent:

```
sudo cp bulletin /usr/local/bin/bulletin
```

## Initial Setup

Run the install command as the dedicated BULLETIN user:

```
bulletin -u SYSTEM -b install
```

This will interactively prompt for:

  * **System name** -- identifies this BULLETIN instance.
  * **Default expiration** -- message lifetime in days, or -1 for permanent
    (recommended).
  * **Expiration limit** -- maximum allowed expiration, or -1 for no limit.
  * **Initial user** -- login, name, and SSH public key for the first user
    (who will be an admin).
  * **Open registration** -- whether unknown SSH keys can self-onboard.
    If enabled, you can optionally add shibboleth challenge questions
    that new users must answer before creating an account.

The install creates the following:

| Path                                  | Purpose                     |
|---------------------------------------|-----------------------------|
| `~/.local/share/BULLETIN/bulletin.db` | SQLite database             |
| `~/.config/BULLETIN/`                 | Command history files       |
| `~/.bulletin-installed`               | Prevents re-running install |

It also installs two crontab entries:

```
@reboot  /usr/local/bin/bulletin -u SYSTEM -b reboot
@daily   /usr/local/bin/bulletin -u SYSTEM -b expire
```

## Configuring SSHD

If you enabled open registration, the install command will print an sshd
configuration block.  If you skipped it, or want to set it up manually,
add the following to `/etc/ssh/sshd_config.d/50-bulletin.conf`:

```
Match User bulletin
    PasswordAuthentication no
    PubkeyAuthentication yes
    PermitTTY yes
    X11Forwarding no
    AllowTcpForwarding no
    PermitTunnel no
    AuthorizedKeysFile none
    ForceCommand /usr/sbin/nologin
    AuthorizedKeysCommand /usr/local/bin/bulletin authorized-keys %u %t %k
    AuthorizedKeysCommandUser bulletin
```

Replace `bulletin` with whatever Unix user runs BULLETIN, and adjust the
path to the binary as needed.

Then reload sshd:

```
sudo systemctl reload sshd
```

With this configuration, sshd delegates key lookup to BULLETIN.  When a
user connects, BULLETIN checks the presented key against its database
and emits the appropriate forced command.  Unknown keys are either
rejected or directed to the onboarding flow, depending on the open
registration setting.

**Without open registration**, you can skip the sshd configuration
entirely.  BULLETIN will use `~/.ssh/authorized_keys` directly, managed
via the `SSH ADD`, `SSH FETCH`, and `SSH DELETE` commands within BULLETIN.

## Adding Users

There are several ways to add users:

### From the BULLETIN prompt (admin)

```
BULLETIN> USER ADD LOGIN "Full Name"
BULLETIN> SSH FETCH LOGIN github github-username
```

### From the command line

```
bulletin -u SYSTEM -b new-user LOGIN github github-username
```

This creates the user (if they don't exist) and fetches their SSH keys
from the specified code forge.  Supported sites: `github`, `gitlab`,
`codeberg`.

### Self-service (open registration)

If open registration is enabled and sshd is configured with
`AuthorizedKeysCommand`, users with unknown SSH keys are presented with
an onboarding flow where they can create their own account or link their
key to an existing account using a link code (generated via `SSH LINK`).

## Migrating from Older Installs

If you have an existing BULLETIN install that stores keys only in
`~/.ssh/authorized_keys`, run:

```
bulletin -u SYSTEM -b migrate-keys
```

This reads the authorized_keys file, imports each BULLETIN key entry
into the database, creates any missing users, and rewrites the file
with the current secure format (`restrict,pty`).

## Connecting

Users connect via SSH:

```
ssh bulletin@your-host
```

They are dropped into the BULLETIN REPL.  Type `HELP` for available
commands.

## Maintenance

The crontab handles routine maintenance automatically:

  * **`@reboot`** -- deletes shutdown messages after a system restart.
  * **`@daily`** -- deletes expired messages and vacuums the database.

### Manual administration

From within BULLETIN, admins can:

  * `USER LIST` -- list all users
  * `USER DISABLE login` / `USER ENABLE login` -- disable or re-enable users
  * `USER DELETE login` -- permanently remove a user
  * `SET DEFAULT_EXPIRE days` -- change default message expiration (-1 = permanent)
  * `SET REGISTRATION OPEN` / `SET REGISTRATION CLOSED` -- toggle self-service onboarding
  * `SET SHIBBOLETH` -- manage challenge questions for onboarding
  * `SHOW SYSTEM` -- display system information and uptime

## File Layout

```
~/.local/share/BULLETIN/
    bulletin.db          SQLite database (all users, folders, messages, keys)

~/.config/BULLETIN/
    LOGIN.history        Command history per user
    .mail.history        Mail sub-app history

~/.ssh/
    authorized_keys      Used when AuthorizedKeysCommand is not configured

~/.bulletin-installed    Touch file from initial install
```
+1 −2
Original line number Diff line number Diff line
@@ -49,5 +49,4 @@ some of the contributors.
The place to get it seems to be the [DECUS archives](http://decuslib.com/).
I tracked it down with help from Kent Brodie who I discovered via
[an old USENET post](https://groups.google.com/forum/#!search/bulletin$20vms/comp.os.vms/rzM2LQMl6Jo/y1BKhO7dv80J)
where he too was trying to track the software down. In 1994.
Where he too was trying to track the software down. In 1994.
+5 −5
Original line number Diff line number Diff line
@@ -107,13 +107,13 @@ the first user.`)
	system.Name, err = ask.GetLine("Enter system name: ")
	system.Name = strings.ToUpper(system.Name)
	ask.CheckErr(err)
	system.DefaultExpire, err = ask.GetInt64("Enter default expiry in days (180): ")
	system.DefaultExpire, err = ask.GetInt64("Enter default expiry in days (-1 = permanent): ")
	if err != nil {
		system.DefaultExpire = 180
		system.DefaultExpire = -1
	}
	system.ExpireLimit, err = ask.GetInt64("Enter expiration limit in days (365): ")
	system.ExpireLimit, err = ask.GetInt64("Enter expiration limit in days (-1 = no limit): ")
	if err != nil {
		system.ExpireLimit = 365
		system.ExpireLimit = -1
	}
	err = q.SetSystem(ctx, storage.SetSystemParams{
		Name:          system.Name,
@@ -161,7 +161,7 @@ the first user.`)
			Subject:    seedMsgs[i].Subject,
			Message:    seedMsgs[i].Message,
			CreateAt:   seedMsgs[i].Date,
			Expiration: time.Now().UTC(),
			Expiration: time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC),
		}))
	}

+9 −3
Original line number Diff line number Diff line
@@ -22,12 +22,18 @@ func CreateMessage(author, subject, message, folder string, permanent, system, s
		}
		if days <= 0 {
			days = sysdef.DefaultExpire
		} else {
		} else if sysdef.ExpireLimit > 0 {
			days = min(days, sysdef.ExpireLimit)
		}
		if days < 0 {
			permanent = 1
			exp := time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC)
			expiration = &exp
		} else {
			exp := time.Now().AddDate(0, 0, int(days)).UTC()
			expiration = &exp
		}
	}
	err := this.Q.CreateMessage(ctx, storage.CreateMessageParams{
		Folder:     folder,
		Author:     author,
+4 −7
Original line number Diff line number Diff line
@@ -265,9 +265,9 @@ BULLCOM.CLD.`,

  Sets the default number of days for messages to expire.

  Default value is 14.`,
  Default value is -1 (permanent).`,
				OptArg:  true,
				Default: "14",
				Default: "-1",
			},
			"/OWNER": {
				Description: `/OWNER=username
@@ -1315,8 +1315,7 @@ privileged qualifier.`,
			},
			"DEFAULT_EXPIRE": {
				Description: `Specifies the number of days the  message is to be retained. The default
is 14 days. The highest limit that can be specified is 30 days. This can
be overridden by a user with privileges.
is permanent (no expiration).

This  also  specifies the default expiration date when adding a message.
If no expiration date is  entered  when  prompted  for  a  date,  or  if
@@ -1326,9 +1325,7 @@ used.
  Format:
    SET DEFAULT_EXPIRE days

If -1 is specified, messages will become permanent.  If 0 is  specified,
no  default expiration date will be present.  The latter should never be
specified for a folder or else the messages will disappear.`,
If -1 is specified, messages will become permanent.`,
				MinArgs: 1,
				MaxArgs: 1,
				Action:  ActionSetDefaultExpire,