Basking: sign in with Authentik only, pages that nest and link

Sign-in is now OpenID Connect against Authentik (authorization code flow
with PKCE, state and nonce). The password, API tokens and the add command
are gone; each person who signs in gets a private notebook in a folder
named from a hash of who they are. An optional allowed list is checked on
every request.

Notes become pages with titles, a tree of subpages, [[links]] with
backlinks that follow renames, a Ctrl+K finder, slash commands, list
continuation, and Write and Read modes. Autosave keeps the text through a
dropped connection or an expired session. Export opens as an Obsidian vault.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
VeillaxandClaude Sonnet 5.5 committed 2026-10-31 22:51:26 -06:00
1 parent b548faa258
commit 98f7730913
19 files changed
+3291 -1578

No files matched your search

+115 -83
View File
@@ -1,30 +1,59 @@
# Basking
A place to dump thoughts, jot notes and keep a journal. Named for the basking shark, which swims
along with its mouth open and takes in whatever drifts by. One small binary, no database, no
dependencies.
A private notebook in the spirit of Obsidian and Notion, for people who would rather run their own.
Pages that nest inside each other, `[[links]]` between them with backlinks, a journal page for each
day, tags, and quick search, all kept as plain markdown files. One small binary, no database, no
dependencies. You sign in with **Authentik** and nothing else: there are no passwords in Basking.
Your notes are plain markdown files in a folder you own. Basking is just a fast, comfortable way to
write them and find them again, from a browser on your phone or your desk.
Named for the basking shark, which swims along with its mouth open and takes in whatever drifts by.
## What it does
- **Dump first, sort later.** The box at the top of Notes is always ready. Type, press Ctrl+Enter,
keep typing. An unsaved draft survives a closed tab.
- **Tags by typing.** Write `#errands` or `#projects/basking` anywhere in a note and it becomes a tag.
The sidebar lists them. Numbers such as `#12` and anything inside code are left alone.
- **A page for each day.** Today opens the day's journal page, with an Insert time button to stamp
`20:15 ` where you are typing. Move between days, or read back over the Journal.
- **Search** across everything as you type: every word must match, and `#tag` words filter by tag.
- **Markdown**, rendered safely: headings, lists, checkboxes you can tick in place, quotes, code,
links and bold and italic.
- **Pin** what matters. **Delete** goes to a trash first, with an Undo, and nothing is lost until you
empty it.
- **Autosave** while you edit, and a warning (not a silent overwrite) if the same note was changed on
another device.
- **Send notes from anywhere.** Make an API token and post text from a script, a phone shortcut or the
`basking add` command.
- **Take it with you.** Download everything as a zip of `.md` files, or just copy the folder.
- **Pages in a tree.** Give a page a title and it appears in the sidebar. Add a subpage with the `+`
next to it, or move a page with Move. Delete moves a page to the trash with an Undo, and any
subpages move up a level so nothing is lost with it.
- **Link pages with `[[`.** Type `[[` and pick a page, or type a new title to create it. A dashed link
is a page that does not exist yet; click it and it is made. Each page lists what links to it.
Rename a page and every `[[link]]` to it is updated.
- **Type `/` for commands**: headings, to-do lists, quotes, code blocks, dividers, today's date, the
time. Lists continue when you press Enter and indent with Tab. Ctrl+B and Ctrl+I work as you expect.
- **Write or Read.** Write shows the markdown; Read shows the page as it will look, with tasks you can
tick and links you can follow. Click a page in Read to start writing (Ctrl+E switches).
- **Quick find with Ctrl+K** searches titles and text as you type, and makes a new page if there is no
match.
- **A journal.** Today opens the page for the day, with an Insert time button. Days link to each
other: `[[2026-10-31]]` goes to that day.
- **Quick notes.** The box on Home takes a thought in two keystrokes (`n`, then Ctrl+Enter). They stay
out of the sidebar until you give them a title.
- **Tags by typing.** Write `#errands` or `#projects/basking` anywhere. Numbers such as `#12` and
anything in code are left alone.
- **Autosave that does not lose things.** Changes save as you type. If the connection drops, Basking
keeps your text and retries. If your Authentik session ends mid-sentence, it keeps the text, offers
to sign in again in another tab, and saves when you are back. If a page changed on another device,
it asks which version to keep instead of overwriting.
- **Take it with you.** Download everything as a zip that opens as a vault in Obsidian: file names are
titles, so the links keep working, and journal days sit in a Journal folder.
## Signing in with Authentik
Basking never sees a password. Who may open it is decided in Authentik, and each person gets a
notebook that only they can read.
1. In Authentik, go to **Applications → Providers → Create** and choose **OAuth2/OpenID Provider**.
Set the client type to **Confidential**, add the redirect URI (strict)
`https://notes.example.com/auth/callback` (use the address you will open Basking at), and keep the
default scopes (openid, profile, email).
2. **Applications → Create**: name it Basking and pick that provider. To limit who can open it, bind a
user or group to the application.
3. Copy the provider's **Client ID** and **Client Secret**, and note its **OpenID Configuration
Issuer**, which looks like `https://auth.example.com/application/o/basking/`.
4. Give Basking those four things (see below), then run `basking check`. It tells you whether
Authentik answers and what redirect URI to allow.
Basking asks for the `openid profile email` scopes and uses the authorization code flow with PKCE, a
`state` and a `nonce`. Whoever Authentik lets into the application can sign in; to be stricter, list
people under `allowed` (emails or usernames), and Basking turns anyone else away even if Authentik
lets them through.
## Installing on Linux (systemd)
@@ -32,123 +61,126 @@ write them and find them again, from a browser on your phone or your desk.
curl -fsSL https://git.veillax.com/Veillax/basking/raw/branch/main/deploy/install.sh | sudo sh
```
This downloads the latest release (checking its SHA-256), installs `/usr/local/bin/basking`, creates
a `basking` user, `/etc/basking/` (settings) and `/var/lib/basking/` (your notes), and writes the
systemd service and `/etc/basking/basking.env`. It then tells you what to do next:
This downloads the latest release (checking its SHA-256), installs `/usr/local/bin/basking`, creates a
`basking` user, `/etc/basking/` (settings) and `/var/lib/basking/` (your notes), and writes the
systemd service and `/etc/basking/basking.env`. Then:
```sh
sudoedit /etc/basking/basking.env # optional: the address and where notes are kept
sudoedit /etc/basking/basking.env # fill in the four Authentik lines at the top
sudo systemctl start basking
journalctl -u basking -f # the first start prints your password here, once
journalctl -u basking -f # it says at once if a setting is missing or wrong
```
Run it again to upgrade; your settings and notes are kept and a running service is restarted.
Basking listens on `127.0.0.1:8490`. Put an HTTPS reverse proxy (or
[Hammerhead](https://git.veillax.com/Veillax/hammerhead)) in front of it and open the address you gave
as `BASKING_PUBLIC_URL`. Authentik will only send people back to an address you allowed, and cookies
are marked Secure when that address is https.
Options: `--start` (start the service straight away), `--version v0.1.0`, `--listen 0.0.0.0:8490`,
`--source` (build from the repository with Go instead), and `--uninstall` (add `--purge` to delete
settings and notes). Download the script and read it first if you prefer: it is
Run the installer again to upgrade; your settings and notes are kept and a running service is
restarted. Options: `--start`, `--version v0.1.0`, `--listen 127.0.0.1:8490`, `--source` (build with Go
instead) and `--uninstall` (add `--purge` to delete settings and notes). Read it first if you like:
[`deploy/install.sh`](deploy/install.sh).
## Running it by hand
## Running it by hand or in Docker
```sh
# download a release, or: go build ./cmd/basking
export BASKING_PUBLIC_URL=https://notes.example.com
export BASKING_OIDC_ISSUER=https://auth.example.com/application/o/basking/
export BASKING_OIDC_CLIENT_ID=...
export BASKING_OIDC_CLIENT_SECRET=...
./basking check # optional: test the settings
./basking serve
```
On first run it writes `basking.json` and prints a password **once** in the log. Open
http://127.0.0.1:8490 and sign in. Notes go in `./data/notes/`.
By default it only listens on `127.0.0.1`. To reach it from your phone, put it behind an HTTPS
reverse proxy, or expose it through [Hammerhead](https://git.veillax.com/Veillax/hammerhead), and set
`"cookie_secure": true` and `"trust_proxy": true` in `basking.json`. Don't put it on the open
internet over plain HTTP: it holds your private thoughts.
Docker: see [`Dockerfile`](Dockerfile) and [`deploy/docker-compose.yml`](deploy/docker-compose.yml).
### Commands
```
basking serve [-config basking.json] [-listen addr] [-data dir]
basking password [-config ...] set a new password (BASKING_PASSWORD or stdin); restart afterwards
basking token [-config ...] NAME make an API token (printed once)
basking add [TEXT...] send a note to a running Basking (needs BASKING_URL and BASKING_TOKEN)
basking serve [-config basking.json] [-listen addr] [-data dir]
basking check [-config basking.json] test the Authentik settings without starting
basking version
```
## Settings
`basking.json` holds `listen` (default `127.0.0.1:8490`), `data_dir` (default `data`), the password
hash, API token hashes, a signing secret, and `cookie_secure` and `trust_proxy` for use behind a
proxy. The environment variables `BASKING_CONFIG`, `BASKING_LISTEN` and `BASKING_DATA` override the
file location, address and notes folder.
Everything can be set with environment variables, which win over the settings file and are never
written to it:
| Setting | Variable | |
| --- | --- | --- |
| `public_url` | `BASKING_PUBLIC_URL` | where you open Basking, no path |
| `oidc.issuer` | `BASKING_OIDC_ISSUER` | the provider's issuer |
| `oidc.client_id` | `BASKING_OIDC_CLIENT_ID` | |
| `oidc.client_secret` | `BASKING_OIDC_CLIENT_SECRET` | |
| `allowed` | `BASKING_ALLOWED` | optional; emails, usernames or Authentik ids, comma separated |
| `listen` | `BASKING_LISTEN` | default `127.0.0.1:8490` |
| `data_dir` | `BASKING_DATA` | default `data` |
`basking.json` also holds a `secret` that signs session cookies. It is made on first run and written
with mode `0600`. Changing it signs everyone out.
## Where your notes are
```
data/
notes/20261031-143205-a1b2c3.md one file per note
data/users/<hash of who you are>/
owner.txt your name and email, so the folder can be matched to a person
notes/20261031-143205-a1b2c3.md one file per page
notes/daily-2026-10-31.md the journal page for a day
trash/ deleted notes, until the trash is emptied
trash/ deleted pages, until the trash is emptied
```
A note file looks like this, so you can edit it with anything:
A page file looks like this, so you can edit it with anything:
```
---
title: "Trip to Lisbon"
parent: 20261030-091200-ffee11
created: 2026-10-31T14:32:05.123Z
updated: 2026-10-31T14:35:00.456Z
pinned: true
---
Call the dentist #errands
Flights and hotels, see [[Packing list]] #travel
```
A plain `.md` file you drop into `notes/` is picked up too, with its file time as its date (name it
with lower-case letters, numbers and dashes). Restart Basking to see files added by hand.
## API
Sign in from a browser, or send `Authorization: Bearer bsk_...`.
```sh
curl -H "Authorization: Bearer $TOKEN" -H "Content-Type: text/plain" \
-d "Call the dentist #errands" http://127.0.0.1:8490/api/notes
```
`GET /api/notes?q=&tag=&view=all|pinned|daily|trash&offset=&limit=`, `POST /api/notes` (JSON
`{"body": "..."}` or plain text), `GET|PUT|DELETE /api/notes/{id}`, `POST /api/notes/{id}/restore`,
`DELETE /api/trash`, `POST /api/daily` (`{"date": "2026-10-31"}`), `GET /api/summary`,
`GET /api/export`.
A plain `.md` file dropped into `notes/` is picked up too, with its file time as its date (name it with
lower-case letters, numbers and dashes). Restart Basking to see files added by hand.
## Security notes
- The password is stored as a PBKDF2-SHA256 hash and API tokens as SHA-256 hashes. `basking.json` is
written `0600`. Failed sign-ins are rate-limited per address.
- Sessions are `HttpOnly`, `SameSite=Strict` cookies that last 30 days, signed with a secret from the
settings file. They are not kept on the server, so a restart does not sign you out, and signing out
only clears this browser's cookie. Changing the password signs out every browser.
- Every browser change needs a header a page on another site can't send. The page has a strict
Content-Security-Policy and notes are never rendered as HTML.
- Notes are **not encrypted** on disk. Anyone who can read the folder can read your notes.
- Sign-in is Authentik only: the authorization code flow with PKCE, `state` and `nonce`, over https. The
ID token comes straight from Authentik's token endpoint, and its issuer, audience, expiry and nonce
are checked. Authentik and Basking must be reachable over https unless both are on the same machine.
- Sessions are `HttpOnly`, `SameSite=Lax` cookies that last 14 days, signed with the secret in the
settings file. Nothing is kept on the server, so a restart does not sign anyone out. The `allowed`
list is checked on every request.
- Each person's folder is named from a hash of who they are, never from anything they typed. One
person's requests can only ever reach their own folder.
- Every change from the browser needs a header a page on another site can't send. The page has a strict
Content-Security-Policy, and pages are never rendered as HTML.
- Pages are **not encrypted** on disk. Anyone who can read the server's disk can read them.
## Honest limits
- One person, one password. There are no accounts, sharing or real-time collaboration.
- No attachments or images, and no offline mode: it needs the server to be reachable.
- Searching holds every note in memory, which is comfortable for tens of thousands of notes and not
- No sharing or real-time collaboration: each notebook has one reader.
- No attachments or images, and no offline mode; it needs the server to be reachable.
- No scripts or tokens for posting notes from elsewhere: Authentik is the only way in.
- Searching holds every page in memory, which is comfortable for tens of thousands of pages and not
built for millions.
- Notes are only reloaded from disk when Basking starts.
- Pages are only reloaded from disk when Basking starts.
- Write mode is plain markdown, not a block editor. Read mode shows how it looks.
## Design
The dashboard follows the Lightwell design language, the same one Hammerhead and Megalodon use. See
The interface follows the Lightwell design language, the same one Hammerhead and Megalodon use. See
[docs/design.md](docs/design.md). `node scripts/lint-design.mjs` checks the rules a machine can check.
## Development
```sh
go test ./...
go run ./cmd/basking serve
go run ./cmd/basking serve # with the BASKING_* variables set
```
Releases are built by the Gitea workflow when a `v*` tag is pushed.