Shorten .env.example; move the long explanations to the readme
76 lines of commentary for five settings was more wall than help. The file now says what to set; the reasoning behind the data directory, and the two ways of putting it under /root, live in the readme where there is room for them. Also brought the deployment section up to date: it still told people to export secrets by hand and run docker-compose, with no word about the first admin, which is now the step that decides whether you can log in at all. INKNOTE_DATA_DIR stays commented out — one .env serves both compose files, so setting it would drag local development off ./data too. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4e93259c04
commit
6dcb77fa73
2 files changed
+71
-75
No files matched your search
+21
-66
@@ -1,76 +1,31 @@
|
||||
# cp .env.example .env — and fill it in. Docker Compose reads .env from this
|
||||
# directory, next to docker-compose.yml; nothing looks for it anywhere else.
|
||||
#
|
||||
# Back the finished .env up together with the database: without ENCRYPTION_KEY
|
||||
# the stored notes cannot be read again, and a backup of one without the other
|
||||
# is worth nothing.
|
||||
# cp .env.example .env — Compose reads it from this directory.
|
||||
# Back the finished .env up with the database: without ENCRYPTION_KEY the
|
||||
# stored notes cannot be read again.
|
||||
|
||||
# Signs session tokens. Changing it logs everyone out.
|
||||
# openssl rand -hex 32
|
||||
# Both: openssl rand -hex 32
|
||||
JWT_SECRET=
|
||||
|
||||
# Encrypts note bodies at rest. LOSING THIS MEANS LOSING THE NOTES.
|
||||
# Must be exactly 32 bytes as hex.
|
||||
# openssl rand -hex 32
|
||||
ENCRYPTION_KEY=
|
||||
|
||||
# The first administrator.
|
||||
#
|
||||
# There is no public sign-up — accounts are created by an admin inside the app
|
||||
# under Account → Administration. These two settle who the first one is:
|
||||
#
|
||||
# empty database → the account is created from both values
|
||||
# existing users, → INKNOTE_ADMIN_USER names which of them becomes admin.
|
||||
# no admin yet Without it, the oldest account is promoted.
|
||||
# an admin exists → both are ignored entirely
|
||||
#
|
||||
# So they can never reset an existing password or take rights away; the worst
|
||||
# they can do on a running instance is nothing.
|
||||
# The first administrator. There is no public sign-up — every other account is
|
||||
# created in the app under Account → Administration. Used only until an admin
|
||||
# exists, then ignored, so they can never reset anything later.
|
||||
INKNOTE_ADMIN_USER=
|
||||
INKNOTE_ADMIN_PASSWORD=
|
||||
|
||||
# ── Where the data lives ──
|
||||
# A plain directory on the Docker host — a bind mount, not a Docker volume, so
|
||||
# that whatever backs up the host also backs up InkNote. Create it first; the
|
||||
# container runs unprivileged and cannot create it for you:
|
||||
#
|
||||
# Where the notes are kept on the host. Defaults to /srv/inknote in production
|
||||
# and ./data for development — leave it commented unless you want another path,
|
||||
# since one .env serves both. Create the directory first, or Docker creates it
|
||||
# as root and the server (uid 1000) cannot write:
|
||||
# mkdir -p /srv/inknote/{db,uploads} && chown -R 1000:1000 /srv/inknote
|
||||
# INKNOTE_DATA_DIR=/srv/inknote
|
||||
#
|
||||
# INKNOTE_DATA_DIR=/srv/inknote # dev compose defaults to ./data
|
||||
# INKNOTE_UID=1000 # must own the directory above
|
||||
# INKNOTE_GID=1000
|
||||
#
|
||||
# The path must start with / or ./ — anything else Docker reads as the name of
|
||||
# a named volume, and a name containing a slash simply fails.
|
||||
#
|
||||
# ── Putting it under /root ──
|
||||
# /root is normally mode 0700, and uid 1000 cannot even pass through it, so the
|
||||
# server would fail to write however the directory itself is owned. Two ways
|
||||
# out; the first keeps the unprivileged container and is the better trade:
|
||||
#
|
||||
# A) let uid 1000 traverse /root, without making it readable:
|
||||
# mkdir -p /root/inknote/{db,uploads}
|
||||
# chown -R 1000:1000 /root/inknote
|
||||
# chmod o+x /root # traverse only — ls /root still refused
|
||||
# INKNOTE_DATA_DIR=/root/inknote
|
||||
#
|
||||
# B) run the container as root instead — no chown, no chmod, but a break-out
|
||||
# lands on root rather than on an unprivileged user:
|
||||
# INKNOTE_DATA_DIR=/root/inknote
|
||||
# INKNOTE_UID=0
|
||||
# INKNOTE_GID=0
|
||||
#
|
||||
# Neither is needed under /srv or /opt, which is why those are the default.
|
||||
# Under /root also run "chmod o+x /root", or the server cannot reach it at all.
|
||||
# That and the alternative: readme.md → Where the data lives.
|
||||
|
||||
# ── Optional tuning ──
|
||||
# Leave unset to keep the defaults shown.
|
||||
# MAX_VERSIONS_PER_NOTE=50 # versions kept per note
|
||||
# VERSION_RETENTION_DAYS=90 # and for how long
|
||||
# VERSION_COALESCE_MINUTES=5 # edits within this window share a version
|
||||
#
|
||||
# Only if the UI is served from a different host than the API. With a reverse
|
||||
# proxy in front, or the bundled nginx, leave it empty — same-origin needs none.
|
||||
# ALLOWED_ORIGINS=https://notes.example.com
|
||||
#
|
||||
# PORT is deliberately not here: the bundled nginx proxies to port 4000 by
|
||||
# name, so changing it would only break that.
|
||||
# ── Optional ── unset keeps the default shown
|
||||
# INKNOTE_UID=1000 # must own INKNOTE_DATA_DIR
|
||||
# INKNOTE_GID=1000
|
||||
# MAX_VERSIONS_PER_NOTE=50
|
||||
# VERSION_RETENTION_DAYS=90
|
||||
# VERSION_COALESCE_MINUTES=5
|
||||
# ALLOWED_ORIGINS= # only if the UI is on another host
|
||||
@@ -85,9 +85,42 @@ $INKNOTE_DATA_DIR/
|
||||
```
|
||||
|
||||
Default is `./data` for development and `/srv/inknote` in production; override
|
||||
with `INKNOTE_DATA_DIR`. The container runs as uid/gid `1000` so the files stay
|
||||
owned by a normal user — set `INKNOTE_UID` / `INKNOTE_GID` if yours differ, and
|
||||
create the directory with matching ownership before the first start.
|
||||
with `INKNOTE_DATA_DIR`. It is a default, not an extra path — set it and the
|
||||
default location is never touched.
|
||||
|
||||
**Create the directory before the first start.** Docker will happily create a
|
||||
missing bind-mount source itself, but it creates it as `root:root`, and the
|
||||
container runs as uid 1000 and then cannot write:
|
||||
|
||||
```bash
|
||||
mkdir -p /srv/inknote/{db,uploads} && chown -R 1000:1000 /srv/inknote
|
||||
```
|
||||
|
||||
The path has to start with `/` or `./`. Anything else Docker reads as the name
|
||||
of a named volume, and a name containing a slash fails outright.
|
||||
|
||||
#### Keeping it under `/root`
|
||||
|
||||
`/root` is mode `0700`, so uid 1000 cannot pass through it — the server fails to
|
||||
write no matter who owns the directory underneath. Two ways out:
|
||||
|
||||
```bash
|
||||
# A) let uid 1000 traverse /root, without making it readable
|
||||
mkdir -p /root/inknote/{db,uploads}
|
||||
chown -R 1000:1000 /root/inknote
|
||||
chmod o+x /root # traverse only — `ls /root` is still refused
|
||||
# .env: INKNOTE_DATA_DIR=/root/inknote
|
||||
|
||||
# B) run the container as root instead — no chown, no chmod, but a break-out
|
||||
# lands on root rather than on an unprivileged user
|
||||
# .env: INKNOTE_DATA_DIR=/root/inknote
|
||||
# INKNOTE_UID=0
|
||||
# INKNOTE_GID=0
|
||||
```
|
||||
|
||||
A is the better trade: it keeps the unprivileged container, which is the layer
|
||||
that matters if the app is ever exploited. Neither is needed under `/srv` or
|
||||
`/opt`, which is why those are the default.
|
||||
|
||||
> Back up the `.env` holding `ENCRYPTION_KEY` too. Note content is encrypted at
|
||||
> rest, so a copy of the database without that key cannot be read.
|
||||
@@ -111,14 +144,22 @@ touches production data.
|
||||
### 2. Docker Deployment
|
||||
|
||||
```bash
|
||||
# Generate a secure encryption key
|
||||
export ENCRYPTION_KEY=$(openssl rand -hex 32)
|
||||
export JWT_SECRET=$(openssl rand -base64 32)
|
||||
|
||||
docker-compose up -d
|
||||
mkdir -p /srv/inknote/{db,uploads} && chown -R 1000:1000 /srv/inknote
|
||||
cp .env.example .env # then fill it in, see below
|
||||
docker login gitea.honeyy.duckdns.org
|
||||
docker compose pull && docker compose up -d
|
||||
docker compose logs inknote-server | head # says which account became admin
|
||||
```
|
||||
|
||||
The server runs on port `4000`, the web UI on port `3000`.
|
||||
`.env` needs `JWT_SECRET`, `ENCRYPTION_KEY` and — on a fresh install —
|
||||
`INKNOTE_ADMIN_USER` plus `INKNOTE_ADMIN_PASSWORD`, since there is no sign-up to
|
||||
create the first account with. On an install that already has accounts,
|
||||
`INKNOTE_ADMIN_USER` alone decides which of them is promoted; without it the
|
||||
oldest one is, which is rarely the one you meant.
|
||||
|
||||
The web UI listens on port `3000`, the API only inside the compose network. If a
|
||||
reverse proxy sits in front, bind the port to localhost (`"127.0.0.1:3000:80"`),
|
||||
otherwise the app is reachable on the host's address without TLS.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in new issue
Block a user