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:
1 parent
b548faa258
commit
98f7730913
19 files changed
+3291
-1578
No files matched your search
@@ -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.
|
||||
|
||||
Reference in new issue
Block a user