# keepIT FAQ Answers for people running keepIT: what changes when you update, and what to check when something doesn't work. Settings are given as environment variables; the [README](README.md#quick-start) lists them all. On Unraid most are fields in the template; add any other one as a variable. - [Updating](#updating) - [Starting up](#starting-up) - [Password reset and email](#password-reset-and-email) - [HTTPS and reverse proxies](#https-and-reverse-proxies) - [Images](#images) - [Backups, export and import](#backups-export-and-import) - [Logs and security](#logs-and-security) ## Updating ### Do I need to change anything when I update? From 1.0 on, no. Every update keeps your data, and any 1.x app works with any 1.x server, so phones can update before or after the server. What's new is in the [CHANGELOG](CHANGELOG.md). Coming from a version before 1.0? Then [start clean](#updating-to-10). ### Updating to 1.0 > [!CAUTION] > **Export your notes before you update.** keepIT 1.0 starts with a clean database. Getting > there took one breaking change, so data from an earlier version can't come along by itself. > From 1.0 on, every update keeps your data. 1. **Every user exports their notes** under **Settings → Your data**, in the web app or the Android app, while the old version still runs. Open the app on each phone online first, so its latest changes reach the server. 2. **Stop keepIT and delete its data:** - Single container: `docker rm -f keepit && docker volume rm keepit-data`. - Unraid: stop the container and delete everything in `/mnt/user/appdata/keepit`. - Docker Compose: `docker compose down -v`. - Your own PostgreSQL: drop the `keepit` database and create it again, empty, and empty the data folder too. 3. **Install 1.0**, create the accounts again, and **every user imports their file** under **Settings → Your data**. The export brings back notes, checklists, lists, reminders, photos and voice notes. Shared notes, two-factor sign-in and profile pictures are set up again. The Android app without a server isn't affected. ### I updated without exporting first Your notes are still there. Go back to the version you had (for example `richy1989/keepit:0.9.2`), export, and follow the steps above. With PostgreSQL, 1.0 won't start on the old data (the log says `relation "AspNetRoles" already exists`); with the built-in database it starts, but repeating reminders may go off at the wrong time, so set those again after importing. ## Starting up ### keepIT won't start: "App:PublicBaseUrl" The address is malformed, usually because it lacks `https://` (`notes.example.com` instead of `https://notes.example.com`). keepIT stops at startup rather than send broken links. Use the full address, without a path or query. ### Why does my data folder belong to user 1654? keepIT doesn't run as root. The app runs as an unprivileged user, `app` (uid 1654), so a flaw in it wouldn't come with full control of the container. On every start, before the app runs, the container makes everything in the data folder that user's. That's how a folder Docker created as root, such as Unraid's `appdata/keepit`, works with no manual step, and so do files you add later as root, a restored backup for example. On Unraid, `appdata/keepit` shows 1654 as its owner. Backups (CA Appdata Backup and the like) work as usual, and access over SMB is read-only, as for any folder root doesn't own. ### keepIT won't start: "unable to open database file" If the log also says keepIT couldn't give the data folder to the `app` user, the folder is on storage that doesn't allow changing owners, typically a network share (NFS or SMB) with root squashing. keepIT can then only use the folder if user 1654 may write to it. Either make the share writable for uid 1654, or move the data folder to local storage. On Unraid, appdata on the cache, a pool or the array is fine. ### Can I run the container with `--user`, or set PUID/PGID? No. Start it normally. The container already runs the app as the unprivileged user, and it needs root for a moment at startup to hand the data folder over and to let nginx open port 80. Started with `--user`, it stops with a message saying so. ### How do I keep the container log from growing? Docker's log handling can cap it: - On Unraid, make sure **Settings → Docker → Docker LOG rotation** is enabled (the default). - With plain Docker, add `--log-opt max-size=10m --log-opt max-file=3` to `docker run`, or set the same under `logging:` in a Compose file, or once for all containers in Docker's `daemon.json`. Without it, Docker keeps every line. ### The first start on PostgreSQL logs "Failed executing DbCommand" That's expected, once. On a new database, keepIT looks for its list of applied updates before it has created it, and the database library logs the miss as an error. keepIT then sets up the database and starts as usual ("keepIT is ready"). Later starts don't show it. ### What does "healthy" mean? keepIT checks itself every 30 seconds, and `docker ps` shows the result: `healthy` while it can reach its database and its data folder, `unhealthy` when it can't, and the log then says which. An uptime monitor (Uptime Kuma and the like) can ask the same: `/api/health` answers `Healthy`, or `Unhealthy` with a 503. The checks that pass stay out of the log. ## Password reset and email ### Password-reset emails don't arrive keepIT builds the link in a reset email only from `App__PublicBaseUrl`, the address your users open keepIT at (for example `https://notes.example.com`). Without it, no reset email is sent. The server log says so at startup, and the web app's Settings page shows a warning. Set `App__PublicBaseUrl` and restart. [Why keepIT insists on it](#why-does-keepit-insist-on-app__publicbaseurl). ### The test email fails with "doesn't offer STARTTLS" keepIT doesn't send mail unencrypted. Your mail server, on the port you configured, doesn't offer to encrypt the connection. Pick one: - If your provider supports implicit TLS, use port 465 with `Email__UseStartTls=false`. - For a relay on your own network that can't do TLS at all (a local Postfix, say), set `Email__AllowUnencrypted=true`. Only there: anywhere else, anyone on the network path could read your reset links and your SMTP password. Settings shows a warning while it's on. ### Can I use password reset without a mail server? Yes. Leave `Email__SmtpHost` empty and the reset link is written to the server log (`docker logs keepit`, or the log view on Unraid). As the operator, you pass it on to the user. This is deliberate: it's the only place keepIT writes a reset link to a log. ### Which SMTP settings do I need? `Email__SmtpHost`, `Email__From` and usually `Email__SmtpUsername` / `Email__SmtpPassword`, plus `App__PublicBaseUrl`. Port 587 with STARTTLS is the default; for port 465 set `Email__UseStartTls=false`. Then use **Settings → Email → Send test email** in the web app. It shows exactly what went wrong if delivery fails. ### Why does keepIT insist on App__PublicBaseUrl? Anyone can ask for a reset email for any address, and the web address in that request is whatever the requester sends. If keepIT built the link from it, an attacker could have your server send a real reset email to one of your users, with a link pointing at the attacker's site, and take over the account when it's clicked. A configured address is the only safe source. ## HTTPS and reverse proxies ### How do I run keepIT behind Nginx Proxy Manager, SWAG or Traefik? - Point the proxy at the container's port **80**, the one you publish (8080 on the host by default). The app's own port inside the container isn't reachable from outside. - Turn on WebSocket support (in Nginx Proxy Manager: **Websockets Support** on the proxy host). Live sync uses it. - Set `App__ForwardedProxyHops=2`, since your proxy is a second hop in front of keepIT's own. Otherwise every visitor looks like your proxy and they all share one rate limit. - Nothing to do for the sign-in cookie: keepIT marks it HTTPS-only by itself whenever a request arrives over HTTPS. - Raise your proxy's request body limit if you plan to **import** a backup. keepIT accepts an archive up to 256 MB and its own nginx allows that, but yours sits in front and usually caps bodies far lower (Nginx Proxy Manager and SWAG default to 1 MB). In an NPM proxy host that is **Advanced → `client_max_body_size 256m;`**; in SWAG, the same line in your site config. Without it a large restore stops with a 413 that comes from your proxy, not from keepIT. - If your proxy keeps access logs, leave query strings out of them, or at least `token` and `access_token`. The live-sync connection carries a sign-in token in its URL, and a reset link carries its token. keepIT's own logs leave query strings out altogether. ### Should Auth__RefreshCookie__Secure be true or false? Over HTTPS the sign-in cookie is always HTTPS-only, whatever this says. The setting only decides plain http: - `false` (the single container's default): plain http on your LAN keeps working, and access through a TLS proxy is protected anyway. - `true` (Compose's default): plain http can't stay signed in. Choose it if keepIT should only ever be reached over HTTPS. `http://localhost` still works, because browsers treat it as secure. ### I keep getting signed out You get signed out when the browser can't send the sign-in cookie back. Common causes: - `Auth__RefreshCookie__Secure=true` while you use plain http on an address other than `localhost`, such as a LAN IP. Set it to `false`. - The same host name is used over both https and plain http (through a proxy on one port and directly on another). A cookie from the https side can't be replaced from the http side, so plain http can't stay signed in. Use one of the two. ### Live sync doesn't work, changes only show after a reload Usually the proxy in front doesn't pass WebSockets on; turn on WebSocket support (see [behind a reverse proxy](#how-do-i-run-keepit-behind-nginx-proxy-manager-swag-or-traefik)). The web app then falls back to slower methods, which some proxies hold back. The container log shows the live-sync connection as `GET /api/realtime?…`: `101` means it's working, a `502` means the app couldn't be reached. ## Images ### "Image too large" An image may have up to 10 MB and 100 megapixels. The pixel limit exists because decoding an image costs memory by its pixel count, not its file size: a 1 MB file can hold hundreds of megapixels. 100 megapixels is above any phone photo that fits in 10 MB. On a server with little memory you can lower it with `App__Media__MaxImagePixels` (the default is `100000000`). ### iPhone photos are refused (HEIC) keepIT doesn't support HEIC yet. On the iPhone, set **Settings → Camera → Formats → Most Compatible**, or share the photo as JPEG. The Android app saves a photo the server refuses to your gallery (Pictures/keepIT) instead of losing it. ### Where did my photo's location and camera data go? keepIT removes it on upload. Phone photos carry GPS coordinates, and a shared note would otherwise tell everyone you share it with where the photo was taken. The image itself is kept, turned the right way up and at most 2560 pixels on its longest side. GIFs are kept as they are, so animations still play. ## Backups, export and import ### How do I back up my notes? Two ways, and they answer different questions. **Settings → Your data** — in the web app and in the Android app, standalone phones included — gives you a `.zip` of everything you own — notes, checklists, lists, reminders and the images attached to them — which you can read without keepIT and restore into any keepIT account. That is the one to keep off the server. Backing up the **whole server** is the operator's version: it captures every user at once, accounts and settings included, but only restores onto a keepIT server. [How](#how-do-i-back-up-the-whole-server). ### How do I back up the whole server? Everything keepIT keeps is in its data folder (`/data` in the container): the built-in database, photos, voice notes and keepIT's keys. With PostgreSQL the notes are in the database instead, so you back up both. Stop keepIT while you copy: a database copied while keepIT writes to it may not open again. - **Unraid:** the Appdata Backup plugin backs up `appdata/keepit` and stops the container while it copies. Nothing else to do. - **Single container**, in the folder that should hold the backup: ```bash docker stop keepit docker run --rm -v keepit-data:/data -v "$PWD":/backup alpine tar czf /backup/keepit-backup.tar.gz -C /data . docker start keepit ``` - **Docker Compose**, in the folder with `docker-compose.yml`: ```bash docker compose stop api docker compose exec -T db pg_dump -U keepit keepit > keepit-db.sql docker compose run --rm --no-deps --user root -v "$PWD":/backup --entrypoint tar api czf /backup/keepit-files.tar.gz -C /data . docker compose start api ``` - **Single container with your own PostgreSQL:** the single-container steps, plus a `pg_dump` of the `keepit` database while keepIT is stopped. Keep your settings with it too: your `docker run` line, the Unraid template, or Compose's `.env`. ### How do I restore a backup? Onto the same keepIT version or a newer one. A restore replaces everything the server holds now. - **Unraid:** stop keepIT, put the backed-up `appdata/keepit` back (Appdata Backup can restore it), and start keepIT. - **Single container**, in the folder with `keepit-backup.tar.gz`. Then start keepIT with your usual `docker run` line: ```bash docker rm -f keepit && docker volume rm keepit-data docker run --rm -v keepit-data:/data -v "$PWD":/backup alpine tar xzf /backup/keepit-backup.tar.gz -C /data ``` - **Docker Compose**, in the folder with `docker-compose.yml` and both backup files: ```bash docker compose down -v docker compose up -d --wait db docker compose exec -T db psql -q -o /dev/null -U keepit -d keepit < keepit-db.sql docker compose run --rm --no-deps --user root -v "$PWD":/backup --entrypoint tar api xzf /backup/keepit-files.tar.gz -C /data docker compose up -d ``` `down -v` deletes the stack's data, so run it only with both files at hand. - **Single container with your own PostgreSQL:** load the dump into an empty `keepit` database with `psql` before you start keepIT. ### Importing adds my notes twice That is what it is meant to do. Import never overwrites or merges — every note in the archive arrives as a **new** note, so importing the same file twice gives you two of each. It works that way on purpose: the one operation that could destroy your notes is the one that must not be able to. Lists are the exception, since a list you already have is filed into rather than duplicated. Delete the extra copies, or import into a fresh account. ### My import fails, or stops on a big archive Check who is refusing it. A **413** almost always comes from a proxy in front of keepIT rather than from keepIT itself — see the reverse-proxy question above for raising the limit. keepIT's own cap is 256 MB, and it answers an archive it cannot read with a plain message saying what was wrong ("no keepit-export.json inside", "written by a newer version of keepIT"). A **504** on a large archive is also the proxy: every image is re-checked and re-encoded on the way in, which can take a few minutes, so raise its read timeout. Individual images that could not be imported are listed after the import finishes rather than failing the whole thing — one unreadable photo never costs you the rest of the archive. ### My phone has no server — is there any backup at all? Yes. In the Android app, **Settings → Your data → Save my notes** writes the same `.zip` to wherever you choose, built from what is on the phone, images included. It is the only copy a standalone phone has, so put it somewhere that is not the phone. Restoring works the same way, and the restored notes queue up for upload too — connect a server later and they go into your account along with everything else. ### Why can't I record a voice note in the browser? Because browsers only allow microphone access over HTTPS (or on localhost), and keepIT is happy to run over plain http on a LAN — so a record button in the web app would simply not work for a lot of people. Recording lives in the Android app instead. Playback works everywhere: a note recorded on your phone plays in the web app on your desktop. ### Can I import my notes from Google Keep? Not yet. keepIT reads its own export format only; a Google Takeout importer is on the roadmap. ## Logs and security ### What does the container log show? One short, coloured line per request, and a line when keepIT starts, is ready and shuts down: ``` 18:31:04 INF keepIT 0.9.0 starting · SQLite · data in /data 18:31:06 INF POST /api/notes 201 157 ms 18:31:07 INF GET /assets/index-old.js 404 nginx ``` The status is coloured by kind (green for success, amber for something the request got wrong, red for a server error), so a problem stands out. Lines that end in `nginx` are requests that never reached the app: a file that doesn't exist, an upload over the size limit, or the app not answering yet (a `502` or two while the container starts is normal). Times are in the container's time zone; Unraid sets it for you, and with plain Docker you pass `-e TZ=Europe/Berlin` or yours. The colour shows in Unraid's log view and in `docker logs` in a terminal. To turn it off, for a log collector that doesn't understand it, set `NO_COLOR=1`. ### Does keepIT write sign-in tokens to its logs? No. The web app's live-sync connection has to carry its sign-in token in the URL, and a reset link carries its token in the page address. keepIT's logs write the path of a request, never the part after the `?`, so neither token reaches them. The one deliberate exception is a reset link when no mail server is configured ([see above](#can-i-use-password-reset-without-a-mail-server)). Your own proxy's logs are [yours to configure](#how-do-i-run-keepit-behind-nginx-proxy-manager-swag-or-traefik). ### Can other containers reach the app directly, without nginx? No. In the single container the app listens only inside the container, behind nginx. In the Compose setup the app publishes no port; only keepIT's own containers share its network. ### Should I close registration? If keepIT is reachable from the internet: yes, once your accounts exist. Set `App__AllowRegistration=false`. Existing users keep working. Registration would otherwise also tell anyone whether an email address already has an account. ### Someone lost the phone with their authenticator app If they still have a recovery code, they sign in with it (on the code step: *Lost your phone? Use a recovery code*), then set up their new phone under Settings → Security on the web or Settings → Account → Two-factor authentication on Android. Each recovery code works once. Without a recovery code, you, the operator, turn two-factor off for them on the server. It also lifts the 15-minute lockout they may have run into: ```bash # Single container (Unraid, docker run). -u app matters: run it as the user keepIT runs as. docker exec -u app keepit dotnet /app/keepITCore.dll disable-two-factor anna@example.com # Docker Compose docker compose exec api dotnet keepITCore.dll disable-two-factor anna@example.com ``` Use your container's name in place of `keepit`. They can then sign in with their password, and should set up two-factor again. A password reset doesn't turn it off: the reset link only proves access to the mailbox. ### The authenticator code is never accepted The codes depend on the time. Check that the phone sets its clock automatically, and that the server's clock is right (`date` on the host). keepIT accepts a code up to about a minute either side of the right time. ### How is an account deleted? By its owner, from 0.9.2 on: Settings → Security → Delete account on the web, or Settings → Account → Delete account on Android, confirmed with the account's password. The server removes the account and everything it owns, files included; notes other people shared with it stay with them. keepIT has no admin page, so an operator can't delete someone else's account from inside keepIT. Your own backups of the data folder still hold the account until you replace them.