# Mako 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 encrypted markdown files. One small binary, no database, no dependencies. You sign in with **Authentik** and nothing else: there are no passwords in Mako. Named for the mako shark, the fastest in the sea, because a notebook should keep up with you. ## What it does - **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. - **Live, Edit or View.** Live is the default: every line shows rendered except the one you are on, which shows its markdown, as in Obsidian's live preview. Edit shows the whole page as plain markdown, and View shows the whole page rendered, with tasks you can tick and links you can follow. Click a page in View to start writing. Ctrl+E switches between editing and View. - **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/mako` 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, Mako 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. - **A vault for what needs more.** Pages in the Vault open only with a passphrase of their own, on top of signing in. The server cannot read them until you unlock it, and they are in no list, search or download of your ordinary notebook. It locks itself after 15 minutes idle, when you press Lock, and when you sign out. See Encryption below. Press To vault on any page, journal days included, and Move out on a vault page to bring it back. A page that links to a vaulted page shows Locked Page in its place until you unlock the vault, and the vaulted page's title is kept only in the vault. - **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 Mako 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 Mako at), and keep the default scopes (openid, profile, email). 2. **Applications → Create**: name it Mako 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/mako/`. 4. Give Mako those four things (see below), then run `mako check`. It tells you whether Authentik answers and what redirect URI to allow. Mako 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 Mako turns anyone else away even if Authentik lets them through. ## Installing on Linux (systemd) ```sh curl -fsSL https://git.veillax.com/Veillax/mako/raw/branch/main/deploy/install.sh | sudo sh ``` This downloads the latest release (checking its SHA-256), installs `/usr/local/bin/mako`, creates a `mako` user, `/etc/mako/` (settings) and `/var/lib/mako/` (your notes), and writes the systemd service and `/etc/mako/mako.env`. Then: ```sh sudoedit /etc/mako/mako.env # fill in the four Authentik lines at the top sudo systemctl start mako journalctl -u mako -f # it says at once if a setting is missing or wrong ``` Mako 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 `MAKO_PUBLIC_URL`. Authentik will only send people back to an address you allowed, and cookies are marked Secure when that address is https. 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 or in Docker ```sh export MAKO_PUBLIC_URL=https://notes.example.com export MAKO_OIDC_ISSUER=https://auth.example.com/application/o/mako/ export MAKO_OIDC_CLIENT_ID=... export MAKO_OIDC_CLIENT_SECRET=... ./mako check # optional: test the settings ./mako serve ``` Docker: see [`Dockerfile`](Dockerfile) and [`deploy/docker-compose.yml`](deploy/docker-compose.yml). ### Commands ``` mako serve [-config mako.json] [-listen addr] [-data dir] mako check [-config mako.json] test the Authentik settings without starting mako version ``` ## Settings Everything can be set with environment variables, which win over the settings file and are never written to it: | Setting | Variable | | | --- | --- | --- | | `public_url` | `MAKO_PUBLIC_URL` | where you open Mako, no path | | `oidc.issuer` | `MAKO_OIDC_ISSUER` | the provider's issuer | | `oidc.client_id` | `MAKO_OIDC_CLIENT_ID` | | | `oidc.client_secret` | `MAKO_OIDC_CLIENT_SECRET` | | | `allowed` | `MAKO_ALLOWED` | optional; emails, usernames or Authentik ids, comma separated | | `key` | `MAKO_KEY` | encrypts your pages; made for you, see Encryption below | | `listen` | `MAKO_LISTEN` | default `127.0.0.1:8490` | | `data_dir` | `MAKO_DATA` | default `data` | `mako.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. ## Encryption Every page is encrypted on disk with AES-256-GCM. Each person has a key of their own, made from the `key` in `mako.json` together with who they are in Authentik (the issuer and their subject id). So one person's files cannot be opened with another's key, and a file copied over another page's name stops opening. Pages are decrypted in the server's memory while it runs, and your downloads are plain markdown. - **The key is made on first run** and written to `mako.json` (mode `0600`), or set `MAKO_KEY` (at least 32 characters, for example `openssl rand -base64 32`) to keep it out of the file. - **Back the key up, apart from the notes.** Without it the pages cannot be read, and Mako refuses to start a notebook it cannot decrypt rather than overwrite it. Do not change it. Unlike `secret`, changing `key` does not just sign people out. - **What it protects:** a stolen disk or backup of `data/`, and anyone who can read the notes folder but not the settings. **What it does not:** someone who controls the running server, or who can read both `mako.json` and `data/`. Who you are in Authentik is not secret, so it cannot be the key by itself. - **The Vault** adds a second lock. Its pages are encrypted with a key made from your passphrase (PBKDF2, 600,000 rounds) together with the server's key, so someone with the server's key still cannot read them without the passphrase. The server holds the unlocked key in memory only, for as long as the vault stays open. **There is no way to reset the passphrase**; forget it and those pages are gone. Five wrong guesses in ten minutes lock out further tries for a while. - **Upgrading from 0.1.0:** existing plain files are read and saved again encrypted the first time that person signs in, and a `key` is added to your settings file. ## Where your notes are ``` data/users// notes/20261031-143205-a1b2c3 one encrypted file per page (named .md) notes/daily-2026-10-31 the journal page for a day trash/ deleted pages, until the trash is emptied ``` Inside, a page is markdown with a small header (shown here decrypted, and what the zip export holds): ``` --- title: "Trip to Lisbon" parent: 20261030-091200-ffee11 created: 2026-10-31T14:32:05.123Z updated: 2026-10-31T14:35:00.456Z pinned: true --- Flights and hotels, see [[Packing list]] #travel ``` A plain `.md` file dropped into `notes/` is picked up when that person's notebook next opens, with its file time as its date, and is then encrypted (name it with lower-case letters, numbers and dashes). ## Security 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 Mako 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 encrypted on disk (see Encryption), with a key per person. The server itself, while it runs, can read them: this is not end-to-end encryption. ## Honest limits - 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. - Pages are only reloaded from disk when Mako starts. - A page in the Vault cannot link to your ordinary pages or use their tags, and the vault is not in the zip download. Moving a page in takes its subpages up a level but does not move them with it, and a journal day moved in leaves that day empty, so starting it again makes a new page which cannot be moved in while the first is there. You cannot change its passphrase yet. - Live preview works a line at a time (a code block counts as one), so a table or a quote spread over several lines shows rendered line by line. View shows the whole page as one. Selecting across lines (Ctrl+A, Shift+Up or Down, Shift+click, or dragging) opens those whole lines as raw markdown, so a selection that starts or ends partway through a line takes in the whole line. ## Design 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/mako serve # with the MAKO_* variables set ``` Releases are built by the Gitea workflow when a `v*` tag is pushed. Licensed under the AGPL-3.0 (see [LICENSE](LICENSE)).