diff --git a/README.md b/README.md index d866df0..ea606d3 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,14 @@ docker run --rm -p 8080:8080 \ ghcr.io/savioruz/memayu:latest ``` +For a persistent, restart-on-failure deployment with a data volume and a +readiness healthcheck, see [`contrib/docker-compose.yml`](contrib/docker-compose.yml): + +```bash +docker compose up -d +docker compose logs -f +``` + The server exposes an unauthenticated readiness endpoint at `GET /api/health`: @@ -88,6 +96,42 @@ ExecStartPost=/bin/sh -c 'for i in $(seq 1 30); do \ sleep 1; done; exit 1' ``` +### Running as a service (systemd) + +To keep `memayu serve` running persistently (and restart it automatically after +a reboot or crash), ship the example unit file: + +```bash +sudo cp contrib/systemd/memayu.service /etc/systemd/system/memayu.service +``` + +Read the comments in that file first — it runs as an unprivileged `memayu` user +and expects a `config.toml` in that user's config directory. Create the service +user, data directory, and a valid `config.toml` (e.g. from `memayu setup` run as +that user), then: + +```bash +sudo systemctl daemon-reload +sudo systemctl enable --now memayu +``` + +Useful commands: + +```bash +systemctl status memayu # status plus the last few log lines +journalctl -u memayu -f # follow the service logs +systemctl restart memayu # apply changes after editing config.toml +systemctl disable memayu # stop auto-starting on boot +``` + +The service reads the same on-disk `config.toml` that `memayu serve` uses +(`$MEMAYU_CONFIG`, else `$XDG_CONFIG_HOME/memayu/config.toml`, else +`~/.config/memayu/config.toml`) — no `.env` file is required. If you prefer env +overrides, add them with `Environment=` lines in the unit or a systemd +`EnvironmentFile=`. + +For help diagnosing installs, see [TROUBLESHOOTING.md](TROUBLESHOOTING.md). + ## Usage ### CLI diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md new file mode 100644 index 0000000..3c1d395 --- /dev/null +++ b/TROUBLESHOOTING.md @@ -0,0 +1,137 @@ +# Troubleshooting + +Common issues when running memayu self-hosted, and how to diagnose them. The +quickest first step for almost any "it's not working" problem is `memayu doctor`, +which checks config validity, storage, and LLM/embedder connectivity in one +command: + +```bash +memayu doctor +``` + +> **Missing config?** `memayu doctor` exits nonzero if the config can't load at +> all. Run `memayu config check` / `memayu config show` (below) to see what's +> wrong. + +## Does the config load? + +`memayu` reads an on-disk `config.toml` (created by `memayu setup`), then applies +any `MEMAYU_*` environment overrides. The file is resolved in this order: + +1. `$MEMAYU_CONFIG` +2. `$XDG_CONFIG_HOME/memayu/config.toml` +3. `~/.config/memayu/config.toml` + +If you run memayu from a service (systemd), make sure the service user can read +that file. + +```bash +memayu config show # effective config (secrets redacted) +memayu config check # validates; exits 1 listing any problems +``` + +If `memayu config check` fails, fix the reported fields (e.g. a missing +`llm.base_url` or `embedder.model`) in `config.toml` or via `MEMAYU_*` env. + +A missing/empty model id for a local embedder, or no dimension, produces errors +like: + +``` +no embedding dimension available for storage; run `memayu setup` or set MEMAYU_EMBEDDER_DIM +``` + +Set `MEMAYU_EMBEDDER_DIM` (or run `memayu setup` for a remote embedder) to record +the dimension. + +## Is the server actually up and usable? + +`memayu serve` exposes an unauthenticated readiness endpoint: + +```bash +curl -s http://localhost:18080/api/health +``` + +- `{"status":"setup_required"}` — the process is listening but first-run setup is + incomplete (no admin account and/or no provider config). Complete setup first. +- `{"status":"ready"}` — up and usable. + +If `curl` can't connect, the server isn't listening on that address/port. Check +the bind address and port in `config.toml` (`[server]`), and that no firewall or +another process is blocking it. + +## Provider problems (LLM / embedder reachable through a proxy) + +`memayu doctor` verifies providers with a **real test call** (a completion for the +LLM, an embedding for the embedder), not a `/models` listing. So a provider behind +a proxy that never exposes `/models`, or renames models, still reports healthy as +long as the real call works. + +If doctor reports a provider error, it prints the HTTP status or failure reason: + +- **401** — the API key was rejected by the provider. Check `MEMAYU_*_API_KEY` / + `api_key` in `config.toml`, and that the key is valid for the configured + `base_url`. +- **500 / 5xx** — the provider (or proxy) returned a server error. Check the + provider's own logs and that the endpoint/model combination is valid. +- **connection / timeout** — host unreachable, TLS error, or the URL is wrong. + Confirm `base_url` is an `http(s)://` URL reachable from the memayu host, and + that a proxy/gateway in front of the provider is up. + +A dimension mismatch (configured `MEMAYU_EMBEDDER_DIM` differs from what's stored +or produced) fails doctor and will error on writes: + +``` +configured dimension 8 differs from stored 3 +``` + +Set the correct consistent dimension everywhere. + +## Port already in use + +```bash +lsof -i :18080 # macOS / Linux +ss -tlnp | grep 18080 # Linux +``` + +Find and stop the process, or change `port` in the `[server]` section (and any +Docker `-p` mapping). After editing config, restart memayu. + +## Reset a forgotten admin password + +The web dashboard has an **Account** page (`/accounts`) for changing the password +and email when you're logged in. If the admin password is forgotten entirely and +you can't log in, reset it from the terminal (this works for both libsql and +Postgres backends): + +```bash +memayu reset-password 'NewHorse-Staple-99!' +``` + +This bypasses the login gate, validates the new password against the same policy +the UI enforces, and writes a fresh password hash. Log in with the new password +and rotate it from `/accounts` if you like. + +## API key / 401 on API calls + +API routes require either an **`x-api-key`** header or session cookie. If a call +returns 401: + +- Confirm the API key is valid. Generate one from the dashboard's **API Keys** + page or via `memayu setup`. Keys are stored as a hash; the raw `mmyu_…` value is + shown once. +- Confirm you're sending it as `x-api-key: mmyu_...`. +- `GET /api/health` is the one endpoint that needs **no** auth. + +## Where are logs? + +- **Terminal:** `memayu` prints diagnostics (config, embedder dimension, listening + address) to stdout/stderr. +- **systemd:** `journalctl -u memayu -f`. +- **Docker:** `docker logs `. +- Per-request HTTP logging appears in the dashboard's **Requests** page. + +## Still stuck + +Open an issue at https://github.com/savioruz/memayu/issues and include: +the output of `memayu doctor`, `memayu config show`, your OS/arch/build, and — for +a service — `systemctl status memayu` plus recent `journalctl -u memayu` lines. \ No newline at end of file diff --git a/contrib/docker-compose.yml b/contrib/docker-compose.yml new file mode 100644 index 0000000..580a4f1 --- /dev/null +++ b/contrib/docker-compose.yml @@ -0,0 +1,60 @@ +# Memayu — Docker Compose example +# +# Runs memayu serve in a container with restart-on-failure/reboot and a +# non-destructive `/api/health` healthcheck, mirroring the Dockerfile and the +# README "Docker" example. +# +# docker compose up -d +# docker compose logs -f +# docker compose ps +# +# The container stores its database at /data/memayu.db (see the Dockerfile's +# MEMAYU_LIBSQL_PATH), which is kept in the `memayu-data` named volume below so +# the store survives container recreation and upgrades. +# +# Configure via the `environment:` block. Everything is optional except the +# provider keys/model you plan to use; the README uses OpenAI-compatible +# defaults. For a local (on-device) embedder set instead: +# +# MEMAYU_EMBEDDER_BACKEND: local +# MEMAYU_EMBEDDER_MODEL: sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 +# MEMAYU_EMBEDDER_DIM: "768" +# +# The port on the left of `8080:8080` is the host port; change it if occupied. +# +# Healthcheck: the image is alpine, so `wget` (busybox) is available. The service +# is marked healthy only once /api/health returns {"status":"ready"} (i.e. first- +# run setup has completed). Before that it reports "unhealthy" — complete setup +# at http://localhost:8080/setup, then the container transitions to healthy. + +services: + memayu: + image: ghcr.io/savioruz/memayu:latest + container_name: memayu + restart: unless-stopped + ports: + - "8080:8080" + environment: + # Storage (libsql embedded database under the named volume below). + MEMAYU_STORAGE_BACKEND: libsql + MEMAYU_LIBSQL_PATH: /data/memayu.db + # Provider config (OpenAI-compatible; adjust to your provider/proxy). + # MEMAYU_LLM_BASE_URL: https://api.openai.com/v1 + # MEMAYU_LLM_API_KEY: sk-... + # MEMAYU_LLM_MODEL: gpt-4o-mini + # MEMAYU_EMBEDDER_BASE_URL: https://api.openai.com/v1 + # MEMAYU_EMBEDDER_API_KEY: sk-... + # MEMAYU_EMBEDDER_MODEL: text-embedding-3-small + volumes: + - memayu-data:/data + healthcheck: + test: + - CMD-SHELL + - wget -q -O /dev/stdout http://127.0.0.1:8080/api/health | grep -q '"status":"ready"' + interval: 10s + timeout: 3s + start_period: 10s + retries: 5 + +volumes: + memayu-data: diff --git a/contrib/systemd/memayu.service b/contrib/systemd/memayu.service new file mode 100644 index 0000000..4615a81 --- /dev/null +++ b/contrib/systemd/memayu.service @@ -0,0 +1,58 @@ +# +# Memayu memory server — systemd unit example +# +# Place this file at /etc/systemd/system/memayu.service, adjust the paths and +# user to match how you installed memayu, then enable it: +# +# sudo systemctl daemon-reload +# sudo systemctl enable --now memayu +# +# Useful commands: +# systemctl status memayu # status incl. the last few log lines +# journalctl -u memayu -f # follow the service logs +# systemctl restart memayu # restart after editing config +# systemctl disable memayu # stop auto-starting on boot +# +# Configuration comes from memayu's on-disk config.toml, exactly as when you run +# `memayu serve` in a shell. memayu resolves it in this order (see +# memayu-config::config_path): +# 1. $MEMAYU_CONFIG (an explicit path, if set below) +# 2. $XDG_CONFIG_HOME/memayu/config.toml +# 3. ~/.config/memayu/config.toml +# Run `memayu setup` once as the service user (or copy a config.toml into place) +# so a valid file exists before the service starts. +# +# This unit runs as an unprivileged `memayu` user and stores its database under +# /var/lib/memayu. Create that directory, give the user ownership, and make sure +# the config.toml is readable by them: +# +# sudo mkdir -p /var/lib/memayu +# sudo useradd --system --home /var/lib/memayu memayu (if not already present) +# sudo chown -R memayu:memayu /var/lib/memayu +# sudo -u memayu mkdir -p /home/memayu/.config/memayu +# sudo -u memayu cp ./memayu-config.toml /home/memayu/.config/memayu/config.toml +# sudo -u memayu chmod 600 /home/memayu/.config/memayu/config.toml +# (or copy your existing machine's config.toml there) + +[Unit] +Description=Memayu memory server +After=network.target +Wants=network-online.target + +[Service] +Type=simple +# Adjust this path if you installed memayu somewhere other than /usr/local/bin. +ExecStart=/usr/local/bin/memayu serve +Restart=on-failure +RestartSec=5 +User=memayu +Group=memayu +WorkingDirectory=/var/lib/memayu +# Optional: point at an explicit config.toml instead of the XDG default above. +#Environment=MEMAYU_CONFIG=/var/lib/memayu/config.toml +# Absolute libsql path so the database always lands in the working dir. Note that +# this overrides whatever libsql_path your config.toml sets. +Environment=MEMAYU_LIBSQL_PATH=/var/lib/memayu/memayu.db + +[Install] +WantedBy=multi-user.target