# Memos Administrator & Developer Guide

> This guide is the complete operational and architectural reference for administrators, DevOps engineers, and contributors deploying and maintaining **Memos v0.26.2** (`bcbcb03`). For day-to-day usage, note creation, and user-facing features, see the companion **User Guide** at [User Guide](/guides/usememos/memos/user-guide/).

---

## 1. Architecture Overview

> [!NOTE]
> Memos is a **monolithic binary** — a single compiled Go executable that simultaneously serves the REST/gRPC API, the React single-page application, and all static assets. There are no separate frontend or backend processes, no message broker, and no cache daemon. This design makes deployment trivially simple but means all components share one process and one port.

### 1.1 High-Level Topology

```
                         ┌───────────────────────────────────────────────┐
                         │              memos binary                      │
                         │                                                │
Internet ──── :8081 ───►│  Echo v5 HTTP Server                          │
  (or Unix sock)         │  ├── /healthz          HealthService           │
                         │  ├── /api/v1/*         gRPC-Gateway → gRPC     │
                         │  ├── /file/attachments  FileServer (HTTP)      │
                         │  ├── /file/users        FileServer (avatars)   │
                         │  ├── /rss/*             RSS feed generator     │
                         │  ├── /mcp               MCP server             │
                         │  └── /*                 SPA (embedded dist/)   │
                         │                                                │
                         │  Background Goroutines                         │
                         │  ├── S3 presign refresher                      │
                         │  └── Memo payload updater                      │
                         │                                                │
                         │  Store Layer                                   │
                         │  ├── In-memory cache                           │
                         │  └── DB driver (SQLite │ MySQL │ PostgreSQL)   │
                         └───────────────────────────────────────────────┘
```

### 1.2 Request Lifecycle

1. An HTTP request arrives at the Echo server.
2. The `Authenticator` middleware resolves the caller's identity from the `Authorization` header (Bearer JWT or PAT) or the `memos_refresh` cookie. Anonymous requests proceed with no identity.
3. The request is dispatched to the matching route:
   - `/api/v1/*` → gRPC-Gateway transcodes JSON↔Protobuf → gRPC service handler.
   - `/file/*` → Native HTTP file server with range-request and thumbnail support.
   - `/mcp` → MCP server with CSRF origin validation.
   - `/rss/*` → RSS generator.
   - Everything else → embedded `index.html` for SPA client-side routing.
4. gRPC handlers call the Store (cache-first, then DB driver).
5. Response returns through Echo (middleware chain is minimal — recovery only; no global rate limiter).

### 1.3 Component Map

| Component | Source Path | Role |
|---|---|---|
| CLI entry | `cmd/memos/main.go` | Cobra commands, flag parsing, startup orchestration |
| Server | `server/server.go` | Echo server creation, route registration, graceful shutdown |
| Auth middleware | `server/auth/` | JWT/PAT validation, refresh token rotation |
| API V1 | `server/router/api/v1/` | All gRPC service handlers |
| ACL config | `server/router/api/v1/acl_config.go` | Method-level role enforcement |
| SSE hub | `server/router/api/v1/sse_hub.go` | Server-Sent Events for live updates |
| File server | `server/router/fileserver/` | Attachment/avatar serving, thumbnail generation |
| Frontend | `server/router/frontend/` | Embedded SPA serving |
| MCP server | `server/router/mcp/` | Model Context Protocol endpoint |
| RSS | `server/router/rss/` | RSS feed generation |
| S3 presign runner | `server/runner/s3presign/` | Background S3 URL refresh |
| Memo payload runner | `server/runner/memopayload/` | Background memo metadata updates |
| Store | `store/` | Data access layer (driver interface + implementations) |
| Store cache | `store/cache/` | In-memory caching wrapper |
| DB drivers | `store/db/sqlite/`, `store/db/mysql/`, `store/db/postgres/` | Database adapters |
| Migrations | `store/migration/{sqlite,mysql,postgres}/` | Versioned schema migrations |
| AI client | `internal/ai/` | OpenAI / Google Gemini transcription |
| Cron | `internal/cron/` | Cron scheduling library |
| Scheduler | `internal/scheduler/` | Job scheduler with middleware |
| Email | `internal/email/` | SMTP email client |
| CEL filter | `internal/filter/` | CEL-based query filter engine |
| Markdown | `internal/markdown/` | Goldmark processor, tag/mention extensions |
| Webhook | `internal/webhook/` | Outbound webhook delivery, SSRF protection |
| HTTP getter | `internal/httpgetter/` | Safe HTML meta extraction and image fetching |
| OAuth2 IDP | `internal/idp/` | Identity provider integration |
| Profile | `internal/profile/` | Server configuration profile struct |
| Version | `internal/version/` | Version parsing and comparison |
| S3 storage | `internal/storage/s3/` | S3 client wrapper |
| Protobuf | `proto/api/v1/` | Service and message definitions |
| Web frontend | `web/` | React 18 SPA (Vite, TypeScript, Tailwind CSS 4) |

### 1.4 Technology Stack

| Layer | Technology | Version |
|---|---|---|
| Language | Go | 1.26.1 |
| HTTP framework | Echo | v5 |
| API protocol | gRPC + gRPC-Gateway + ConnectRPC | — |
| SQLite driver | modernc.org/sqlite | Pure Go, CGO-free |
| MySQL support | go-sql-driver/mysql | — |
| PostgreSQL support | lib/pq | — |
| Frontend framework | React | 18 |
| Frontend build | Vite | — |
| CSS | Tailwind CSS | v4 |
| UI components | Radix UI | — |
| State management | TanStack React Query | — |
| Markdown (backend) | Goldmark | — |
| Markdown (frontend) | react-markdown | — |
| Auth tokens | golang-jwt/jwt | v5 |
| OAuth2 | golang.org/x/oauth2 | — |
| MCP | mcp-go (mark3labs) | — |
| AI providers | OpenAI API, Google Gemini | — |
| Password hashing | bcrypt | — |

### 1.5 Startup Sequence

```
cmd/memos/main.go
  │
  ├─ Parse flags (Cobra + Viper)
  ├─ Build Profile (data dir, addr, port, driver, DSN)
  ├─ Validate Profile (dir writable, driver recognized)
  ├─ Init DB driver (open connection, verify connectivity)
  ├─ Run migrations (store.Migrate())
  ├─ Create Store (driver + cache wrapper)
  ├─ Create Server (server.NewServer())
  │     ├─ Create Echo instance
  │     ├─ Register recovery middleware
  │     ├─ Get/create basic instance setting (secret_key, schema_version)
  │     ├─ Register /healthz
  │     ├─ Register frontend static files (embedded)
  │     ├─ Create gRPC server + register all services
  │     ├─ Create gRPC-Gateway (HTTP/JSON bridge)
  │     ├─ Register file server routes
  │     ├─ Create + register RSS routes
  │     ├─ Register gRPC-Gateway routes (/api/v1/*)
  │     ├─ Register MCP server (/mcp)
  │     └─ Start background runners (S3 presign, memo payload)
  └─ server.Start() → listen + signal handling (SIGTERM/SIGINT)
        └─ Graceful shutdown:
              stop runners → close SSE hub → shutdown HTTP → wait runners → close DB
```

### 1.6 gRPC Services

All gRPC services are defined in `proto/api/v1/` and exposed over HTTP via gRPC-Gateway at `/api/v1/`. The web frontend communicates using ConnectRPC (a browser-compatible gRPC transport).

| Service | Proto file | Key operations |
|---|---|---|
| MemoService | `memo_service.proto` | CRUD memos, list, search, comments, share |
| UserService | `user_service.proto` | CRUD users, settings, stats |
| AuthService | `auth_service.proto` | Sign in, sign up, token refresh |
| AttachmentService | `attachment_service.proto` | CRUD attachments, link to memos |
| InstanceService | `instance_service.proto` | Instance settings, admin operations |
| IDPService | `idp_service.proto` | Identity provider management |
| ShortcutService | `shortcut_service.proto` | Saved search shortcuts |
| AIService | `ai_service.proto` | Audio/image transcription |
| HealthService | `health_service.proto` | Health check, SSE stream |

---

## 2. System Requirements

### 2.1 Minimum Requirements

| Resource | Minimum | Recommended |
|---|---|---|
| CPU | 1 core | 2+ cores |
| RAM | 256 MB | 512 MB–1 GB |
| Disk (binary + data) | 200 MB + data | 1 GB+ (depends on attachments) |
| OS | Linux, macOS, Windows | Linux (amd64/arm64) |
| Go (build only) | 1.26.1 | — |
| Node.js (build only) | 20+ | — |

### 2.2 Database Requirements

| Database | Version | Notes |
|---|---|---|
| SQLite | Built-in (modernc.org/sqlite) | Default; no external server; single-file DB |
| MySQL | 8.0+ | External server required; set `--driver mysql` |
| PostgreSQL | 14+ | External server required; set `--driver postgres` |

> [!NOTE]
> SQLite uses a **pure Go** driver (no CGO). The binary runs on any platform without external libraries. For production workloads with multiple concurrent writers, MySQL or PostgreSQL are recommended.

### 2.3 Network Requirements

| Port | Protocol | Purpose |
|---|---|---|
| 8081 (default) | TCP/HTTP | API + frontend + all routes |
| — | Unix socket | Optional alternative to TCP |

Memos does not open any outbound ports during normal operation. Outbound connections occur only for:
- S3 storage (user-configured endpoint)
- OAuth2 identity providers (user-configured)
- SMTP email (user-configured)
- Webhook delivery (outbound to configured URLs)
- AI providers (OpenAI/Gemini endpoints)

### 2.4 Storage Requirements

| Storage type | Dependency |
|---|---|
| Local filesystem | Data directory writable by the memos process |
| S3-compatible | Bucket with read/write access; credentials in instance settings |
| Database blob | No filesystem; stored in `attachment` table |

---

## 3. Configuration Reference

> [!IMPORTANT]
> Memos has **two configuration layers**: CLI flags (and their environment variable equivalents) set server-level parameters at startup, and **instance settings** (stored in the database) control runtime behavior and can be changed live through the admin UI without restarting the server. CLI flags cannot be changed without a restart.

### 3.1 CLI Flags

All flags can be set as environment variables using the `MEMOS_` prefix and upper-casing the flag name (e.g., `--port` → `MEMOS_PORT`).

| Flag | Env Variable | Default | Description |
|---|---|---|---|
| `--demo` | `MEMOS_DEMO` | `false` | Enable demo mode (pre-populated read-only data) |
| `--addr` | `MEMOS_ADDR` | `""` (all interfaces) | TCP bind address |
| `--port` | `MEMOS_PORT` | `8081` | TCP port |
| `--unix-sock` | `MEMOS_UNIX_SOCK` | `""` | Unix socket path (overrides addr/port when set) |
| `--data` | `MEMOS_DATA` | Platform-dependent | Data directory for DB file, uploads, cache |
| `--driver` | `MEMOS_DRIVER` | `sqlite` | Database driver: `sqlite`, `mysql`, `postgres` |
| `--dsn` | `MEMOS_DSN` | `""` | Database connection string (required for MySQL/PostgreSQL) |
| `--instance-url` | `MEMOS_INSTANCE_URL` | `""` | Public base URL (used in RSS, email links, MCP) |
| `--allow-private-webhooks` | `MEMOS_ALLOW_PRIVATE_WEBHOOKS` | `false` | Allow webhook delivery to private/internal IP addresses |

**Default data directory** by platform:
- Linux: `~/.memos/`
- macOS: `~/Library/Application Support/memos/`
- Windows: `%APPDATA%\memos\`

**DSN formats**:

```bash
# MySQL
--dsn "user:password@tcp(host:3306)/memos?parseTime=true"

# PostgreSQL
--dsn "host=localhost port=5432 user=memos password=secret dbname=memos sslmode=disable"

# SQLite (explicit path)
--dsn "/data/memos.db"
```

### 3.2 Instance Settings

These settings live in the `instance_setting` table and are managed through **Settings → Admin** in the UI or via the `InstanceService` gRPC API. They take effect immediately without restarting the server.

#### 3.2.1 Basic Settings

| Key | Type | Description |
|---|---|---|
| `secret_key` | string | JWT signing secret (auto-generated UUID on first run; rotating this invalidates all existing sessions) |
| `schema_version` | string | Current DB schema version (do not edit manually) |

#### 3.2.2 General Settings

| Key | Type | Default | Description |
|---|---|---|---|
| `disallow_user_registration` | bool | `false` | Prevent new user self-registration |
| `disallow_password_auth` | bool | `false` | Disable username/password login (forces OAuth2-only) |
| `additional_script` | string | `""` | Custom JavaScript injected into every page |
| `additional_style` | string | `""` | Custom CSS injected into every page |
| `custom_profile.title` | string | `""` | Instance title shown on login page |
| `custom_profile.description` | string | `""` | Instance description |
| `custom_profile.logo_url` | string | `""` | URL to custom logo |
| `week_start_day_offset` | int | `0` | Calendar week start (0=Sunday, 1=Monday) |
| `disallow_change_username` | bool | `false` | Prevent users from changing their username |
| `disallow_change_nickname` | bool | `false` | Prevent users from changing their nickname |

#### 3.2.3 Storage Settings

| Key | Type | Default | Description |
|---|---|---|---|
| `storage_type` | enum | `DATABASE` | Where attachments are stored: `DATABASE`, `LOCAL`, `S3` |
| `filepath_template` | string | `{{.year}}/{{.month}}/{{.day}}/{{.filename}}` | Path template for local storage |
| `upload_size_limit_mb` | int | `32` | Maximum attachment upload size in MB |

**S3 configuration** (only active when `storage_type = S3`):

| Key | Description |
|---|---|
| `s3_config.access_key_id` | AWS/S3 access key ID |
| `s3_config.access_key_secret` | AWS/S3 secret access key |
| `s3_config.endpoint` | S3 endpoint URL (for S3-compatible services like MinIO, Cloudflare R2) |
| `s3_config.region` | AWS region (e.g., `us-east-1`) |
| `s3_config.bucket` | Bucket name |
| `s3_config.use_path_style` | `true` for MinIO/path-style; `false` for AWS virtual-hosted style |

#### 3.2.4 Memo Settings

| Key | Type | Default | Description |
|---|---|---|---|
| `display_with_update_time` | bool | `false` | Show memo's last-updated time instead of creation time |
| `content_length_limit` | int | `0` (unlimited) | Maximum memo content length in characters |
| `enable_double_click_edit` | bool | `false` | Enable double-click to edit memo inline |
| `reactions` | string list | Default emoji set | Available emoji reactions |

#### 3.2.5 Tag Settings

Tags can have per-tag metadata stored in the instance settings:

| Key | Description |
|---|---|
| `tag_colors.{tag}.background_color` | Background color for the tag chip |
| `tag_colors.{tag}.blur_content` | Whether to blur memo content tagged with this tag |

#### 3.2.6 Notification (Email) Settings

| Key | Description |
|---|---|
| `email_config.smtp_host` | SMTP server hostname |
| `email_config.smtp_port` | SMTP server port (typically 587 for STARTTLS, 465 for SSL) |
| `email_config.smtp_username` | SMTP authentication username |
| `email_config.smtp_password` | SMTP authentication password |
| `email_config.from_email` | Sender email address |
| `email_config.from_name` | Sender display name |
| `email_config.reply_to` | Reply-To email address |
| `email_config.use_tls` | Enable STARTTLS |
| `email_config.use_ssl` | Enable SSL/TLS (implicit TLS) |

#### 3.2.7 AI Provider Settings

Multiple AI providers can be configured simultaneously:

| Key | Description |
|---|---|
| `ai_provider.id` | Unique provider identifier |
| `ai_provider.title` | Display name |
| `ai_provider.type` | `OPENAI` or `GEMINI` |
| `ai_provider.endpoint` | API base URL (for OpenAI-compatible proxies) |
| `ai_provider.api_key` | Provider API key |

---

## 4. Deployment & Scaling

### 4.1 Docker Deployment (Recommended)

```bash
# Create data directory
mkdir -p /opt/memos

# Run with SQLite (default)
docker run -d \
  --name memos \
  -p 8081:8081 \
  -v /opt/memos:/var/opt/memos \
  -e MEMOS_DATA=/var/opt/memos \
  --restart unless-stopped \
  neosmemo/memos:0.26.2

# Run with PostgreSQL
docker run -d \
  --name memos \
  -p 8081:8081 \
  -e MEMOS_DRIVER=postgres \
  -e MEMOS_DSN="host=db port=5432 user=memos password=secret dbname=memos sslmode=disable" \
  -e MEMOS_INSTANCE_URL="https://memos.example.com" \
  --restart unless-stopped \
  neosmemo/memos:0.26.2
```

The Docker image runs as **non-root uid 10001** for security hardening.

### 4.2 Docker Compose

```yaml
# docker-compose.yml
services:
  memos:
    image: neosmemo/memos:0.26.2
    container_name: memos
    ports:
      - "8081:8081"
    volumes:
      - memos_data:/var/opt/memos
    environment:
      MEMOS_DATA: /var/opt/memos
      MEMOS_INSTANCE_URL: https://memos.example.com
    restart: unless-stopped

  # Optional: PostgreSQL backend
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: memos
      POSTGRES_USER: memos
      POSTGRES_PASSWORD: changeme
    volumes:
      - pg_data:/var/lib/postgresql/data
    restart: unless-stopped

volumes:
  memos_data:
  pg_data:
```

```yaml
# docker-compose.yml with PostgreSQL backend
services:
  memos:
    image: neosmemo/memos:0.26.2
    environment:
      MEMOS_DRIVER: postgres
      MEMOS_DSN: "host=db port=5432 user=memos password=changeme dbname=memos sslmode=disable"
      MEMOS_INSTANCE_URL: https://memos.example.com
    depends_on:
      - db
    ports:
      - "8081:8081"
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: memos
      POSTGRES_USER: memos
      POSTGRES_PASSWORD: changeme
    volumes:
      - pg_data:/var/lib/postgresql/data
    restart: unless-stopped

volumes:
  pg_data:
```

### 4.3 Reverse Proxy (Nginx)

```nginx
# /etc/nginx/sites-available/memos
server {
    listen 80;
    server_name memos.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name memos.example.com;

    ssl_certificate     /etc/letsencrypt/live/memos.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/memos.example.com/privkey.pem;

    # Increase for attachment uploads
    client_max_body_size 64m;

    location / {
        proxy_pass http://127.0.0.1:8081;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # SSE support (HealthService stream)
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 3600s;
    }
}
```

> [!IMPORTANT]
> When running behind a reverse proxy, set `--instance-url` to the public URL (e.g., `https://memos.example.com`). This URL is used in RSS feeds, email notifications, and MCP resource references. Without it, generated links will be incorrect.

### 4.4 Caddy Reverse Proxy

```caddyfile
memos.example.com {
    reverse_proxy localhost:8081 {
        header_up Host {host}
    }
    # Caddy handles TLS automatically
}
```

### 4.5 Binary Deployment (Without Docker)

```bash
# Download release binary
wget https://github.com/usememos/memos/releases/download/v0.26.2/memos_linux_amd64.tar.gz
tar xf memos_linux_amd64.tar.gz

# Run
./memos \
  --data /opt/memos \
  --port 8081 \
  --instance-url https://memos.example.com

# As a systemd service
cat > /etc/systemd/system/memos.service << 'EOF'
[Unit]
Description=Memos - Self-hosted note-taking service
After=network.target

[Service]
Type=simple
User=memos
Group=memos
ExecStart=/usr/local/bin/memos \
  --data /var/opt/memos \
  --port 8081 \
  --instance-url https://memos.example.com
Restart=on-failure
RestartSec=5s
Environment=MEMOS_DATA=/var/opt/memos

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now memos
```

### 4.6 Building from Source

```bash
# Prerequisites: Go 1.26.1+, Node.js 20+

git clone https://github.com/usememos/memos.git
cd memos
git checkout bcbcb03

# Build frontend
cd web
npm install
npm run build
# Output goes to ../server/router/frontend/dist/
cd ..

# Build binary (embeds frontend dist)
go build -o memos ./cmd/memos/main.go

# Cross-compile for Linux arm64
GOOS=linux GOARCH=arm64 go build -o memos-linux-arm64 ./cmd/memos/main.go
```

### 4.7 Unix Socket Deployment

For setups where the reverse proxy is on the same host, Unix socket is slightly more efficient than TCP:

```bash
memos --unix-sock /run/memos/memos.sock --data /var/opt/memos

# Nginx upstream
upstream memos {
    server unix:/run/memos/memos.sock;
}
```

> [!NOTE]
> When `--unix-sock` is set, `--addr` and `--port` are ignored. Ensure the reverse proxy user has read/write permission on the socket file. See GitHub issue #5827 for reported permission edge cases with some configurations.

### 4.8 Scaling Considerations

Memos is designed as a **single-instance application**. Key limitations for horizontal scaling:

| Constraint | Detail |
|---|---|
| In-memory cache | The store cache is process-local; multiple instances have inconsistent caches |
| SSE hub | The SSE connection registry is in-memory; cross-instance live updates don't work |
| Local file storage | Attachments on the local filesystem are not shared between instances |
| No built-in clustering | No leader election, no distributed state |

**Recommended scaling approach**: Vertical scaling (more RAM/CPU on one instance) + offload attachments to S3.

For high availability, a primary/standby setup with a shared PostgreSQL or MySQL backend and shared S3 storage is the most practical approach. Only one instance should be active at a time to avoid cache inconsistency.

---

## 5. Database Administration

### 5.1 Schema Overview

| Table | Purpose |
|---|---|
| `user` | User accounts (id, username, role, email, nickname, password_hash, avatar_url, description, row_status) |
| `memo` | Memos (id, uid, creator_id, content, visibility, pinned, payload JSON, created_ts, updated_ts, row_status) |
| `attachment` | File attachments (id, uid, creator_id, filename, type, size, storage_type, reference, payload, memo_id) |
| `memo_relation` | Memo-to-memo relationships (memo_id, related_memo_id, type) |
| `reaction` | Emoji reactions (id, creator_id, content_id, reaction_type) |
| `idp` | OAuth2 identity providers (id, uid, name, type, identifier_filter, config) |
| `inbox` | User inbox messages (id, sender_id, receiver_id, status, message) |
| `instance_setting` | Key-value store for all instance settings |
| `user_setting` | Per-user key-value settings |
| `memo_share` | Shared memo links (id, memo_id, uid, visibility, expires_at) |

The `row_status` column on `user` and `memo` acts as a soft-delete flag: `NORMAL` (active) or `ARCHIVED`.

### 5.2 Migrations

Migrations run **automatically at startup** via `store.Migrate()`. There is no manual migration step.

- **SQLite**: `store/migration/sqlite/` — plain SQL files versioned 0.2 through 0.27
- **MySQL**: `store/migration/mysql/` — same version range, MySQL-compatible SQL
- **PostgreSQL**: `store/migration/postgres/` — same version range, PostgreSQL-compatible SQL

The current schema version is tracked in the `instance_setting` table under the `schema_version` key. Memos will not downgrade a schema; always back up before upgrading.

### 5.3 SQLite Administration

```bash
# Direct SQLite access (stop memos first for safety)
sqlite3 /var/opt/memos/memos.db

# Backup
sqlite3 /var/opt/memos/memos.db ".backup '/backup/memos_$(date +%Y%m%d).db'"

# Online backup (safe while running, uses SQLite backup API)
sqlite3 /var/opt/memos/memos.db "VACUUM INTO '/backup/memos_$(date +%Y%m%d).db'"

# Check integrity
sqlite3 /var/opt/memos/memos.db "PRAGMA integrity_check;"

# Reclaim space after deletions
sqlite3 /var/opt/memos/memos.db "VACUUM;"

# WAL mode (enabled by Memos automatically)
sqlite3 /var/opt/memos/memos.db "PRAGMA journal_mode;"
```

SQLite is opened with **WAL (Write-Ahead Logging)** mode and **foreign keys enabled**. Do not change these pragmas externally.

### 5.4 PostgreSQL Administration

```bash
# Create database and user
psql -U postgres << 'SQL'
CREATE USER memos WITH PASSWORD 'strongpassword';
CREATE DATABASE memos OWNER memos;
GRANT ALL PRIVILEGES ON DATABASE memos TO memos;
SQL

# Backup
pg_dump -U memos -h localhost memos | gzip > memos_$(date +%Y%m%d).sql.gz

# Restore
gunzip -c memos_20261101.sql.gz | psql -U memos -h localhost memos

# Connection string
MEMOS_DSN="host=localhost port=5432 user=memos password=strongpassword dbname=memos sslmode=require"
```

### 5.5 MySQL Administration

```bash
# Create database and user
mysql -u root -p << 'SQL'
CREATE DATABASE memos CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'memos'@'%' IDENTIFIED BY 'strongpassword';
GRANT ALL PRIVILEGES ON memos.* TO 'memos'@'%';
FLUSH PRIVILEGES;
SQL

# Backup
mysqldump -u memos -p memos | gzip > memos_$(date +%Y%m%d).sql.gz

# Connection string
MEMOS_DSN="memos:strongpassword@tcp(localhost:3306)/memos?parseTime=true&charset=utf8mb4"
```

### 5.6 Database Migration (SQLite → PostgreSQL)

There is no official automated migration tool. The recommended approach:

1. Export data via the API (using PATs or admin credentials)
2. Create a fresh PostgreSQL instance
3. Re-import via the API, or use a data-transfer script against both databases directly

For large instances, a custom Go script that reads from SQLite and writes to PostgreSQL through the Store interface is the most reliable approach.

---

## 6. Operations

> [!NOTE]
> Memos is stateless at the process level — all persistent state lives in the database and (optionally) the filesystem/S3. Restarting the process is always safe. The main operational concern is the data directory and database file.

### 6.1 Health Checks

```bash
# HTTP health check (no auth required)
curl http://localhost:8081/healthz
# Expected response: 200 OK with JSON body

# Container health check (Docker)
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8081/healthz || exit 1
```

### 6.2 Log Management

Memos logs to **stdout** in plain text format. Capture logs via your container runtime or systemd:

```bash
# Docker
docker logs memos --follow

# Systemd
journalctl -u memos -f

# Filter for errors
journalctl -u memos | grep -i "error\|panic\|fatal"
```

There is no built-in log rotation or structured JSON logging. For production, pipe stdout to a log aggregator (Loki, Datadog, CloudWatch, etc.).

### 6.3 Backup Strategy

| Component | Backup method | Frequency |
|---|---|---|
| SQLite DB | `VACUUM INTO` (online) or file copy (stopped) | Daily |
| MySQL/PostgreSQL | `pg_dump` / `mysqldump` | Daily |
| Data directory (local attachments) | rsync / snapshot | Daily |
| Instance settings | Included in DB backup | With DB |

```bash
#!/bin/bash
# Simple SQLite backup script
BACKUP_DIR=/backup/memos
mkdir -p "$BACKUP_DIR"

# Keep 30 days
find "$BACKUP_DIR" -name "*.db" -mtime +30 -delete

sqlite3 /var/opt/memos/memos.db "VACUUM INTO '$BACKUP_DIR/memos_$(date +%Y%m%d_%H%M%S).db'"
echo "Backup complete: $BACKUP_DIR/memos_$(date +%Y%m%d_%H%M%S).db"
```

### 6.4 Upgrade Procedure

```bash
# 1. Back up data
sqlite3 /var/opt/memos/memos.db "VACUUM INTO '/backup/memos_pre_upgrade.db'"

# 2. Pull new image (Docker)
docker pull neosmemo/memos:0.26.2

# 3. Stop old container
docker stop memos && docker rm memos

# 4. Start new container (migrations run automatically)
docker run -d \
  --name memos \
  -p 8081:8081 \
  -v /opt/memos:/var/opt/memos \
  -e MEMOS_DATA=/var/opt/memos \
  --restart unless-stopped \
  neosmemo/memos:0.26.2

# 5. Verify health
curl http://localhost:8081/healthz
```

> [!NOTE]
> Schema migrations run automatically on startup. Downgrading to an older version after a migration is not supported and may corrupt data. Always back up before upgrading.

### 6.5 User Management via API

```bash
# Set API key from user settings → Personal Access Tokens
MEMOS_API_KEY="memos_pat_..."
BASE="https://memos.example.com"

# List users (admin only)
curl -H "Authorization: Bearer $MEMOS_API_KEY" "$BASE/api/v1/users"

# Get account info
curl -H "Authorization: Bearer $MEMOS_API_KEY" "$BASE/api/v1/auth/status"

# Update instance settings (admin only)
curl -X PATCH \
  -H "Authorization: Bearer $MEMOS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"setting": {"general_setting": {"disallow_user_registration": true}}}' \
  "$BASE/api/v1/instance/setting"
```

### 6.6 Background Runners

Two background goroutines run for the lifetime of the server process:

| Runner | Location | Purpose | Interval |
|---|---|---|---|
| S3 presign refresher | `server/runner/s3presign/` | Regenerates expiring S3 presigned URLs for stored attachments | Configurable (checks approaching expiry) |
| Memo payload updater | `server/runner/memopayload/` | Keeps memo metadata (tags, link previews, properties) in sync with content changes | Configurable |

Both runners participate in the graceful shutdown sequence. They are stopped before the HTTP server closes, and the main goroutine waits for them to finish before closing the database.

### 6.7 SSE (Server-Sent Events)

The `sse_hub.go` component maintains a registry of open SSE connections for real-time updates (new memos, changes). During graceful shutdown, all SSE connections are closed before the HTTP server shuts down to prevent client-side errors.

```bash
# SSE endpoint (authenticated)
curl -N \
  -H "Authorization: Bearer $MEMOS_API_KEY" \
  "https://memos.example.com/api/v1/events"
```

### 6.8 Thumbnail Cache

Thumbnails are generated on-demand for image attachments and cached in `.thumbnail_cache/` within the data directory. Generation is semaphore-limited to **3 concurrent** operations to prevent CPU spikes.

```bash
# Clear thumbnail cache (safe; regenerated on demand)
rm -rf /var/opt/memos/.thumbnail_cache/

# Thumbnail specs: max 600px on longest side, JPEG output
```

---

## 7. Security

> [!WARNING]
> Memos does **not** implement rate limiting or request throttling at the application level. If exposed to the public internet, configure rate limiting at the reverse proxy or firewall level to prevent brute-force attacks on the login endpoint.

### 7.1 Authentication System

Memos uses a **dual-token JWT system**:

| Token type | Lifespan | Storage | Purpose |
|---|---|---|---|
| Access token | 15 minutes | Memory / Authorization header | Short-lived API access |
| Refresh token | Configurable (long) | DB + HttpOnly cookie (`memos_refresh`) | Renewing access tokens |
| Personal Access Token (PAT) | No expiry | DB (hash only, prefix `memos_pat_`) | Programmatic API access |

**Access token flow**:
1. User signs in → server returns access token + sets `memos_refresh` cookie
2. Frontend uses access token in `Authorization: Bearer <token>` header
3. When access token expires, frontend calls the token-refresh endpoint
4. Refresh token in cookie is validated against DB → new access token issued

**PAT flow**:
- User generates PAT in settings → shown once, never stored in plaintext
- PAT is hashed (bcrypt) and stored in DB
- API requests include PAT as `Authorization: Bearer memos_pat_...`
- Server hashes incoming PAT and compares to stored hash

**Password hashing**: bcrypt with default cost factor.

**JWT signing**: HMAC-SHA256 with the `secret_key` instance setting (auto-generated UUID on first run).

> [!CAUTION]
> Rotating the `secret_key` in instance settings immediately invalidates **all** existing sessions and refresh tokens. All users will be logged out. PATs are stored separately and are not affected by `secret_key` rotation.

### 7.2 Authorization (ACL)

Role-based access control is enforced at the gRPC method level via `acl_config.go`:

| Role | Capabilities |
|---|---|
| Anonymous | Read PUBLIC memos, RSS feeds, health check |
| USER | Own memos/attachments CRUD, own settings, read PROTECTED memos |
| ADMIN | All USER capabilities + user management, instance settings, IDP management |

The first user registered on a fresh instance is automatically assigned the ADMIN role.

### 7.3 SSRF Protection

> [!WARNING]
> Webhook URLs are validated before delivery using SSRF protection logic in `internal/webhook/`. URLs resolving to reserved/private IP ranges (RFC 1918, loopback, link-local, etc.) are **blocked by default**. Use `--allow-private-webhooks` to allow webhooks to private IPs — only do this in trusted internal networks.

Blocked ranges include:
- `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16` (RFC 1918)
- `127.0.0.0/8` (loopback)
- `169.254.0.0/16` (link-local)
- `::1/128`, `fc00::/7` (IPv6 private)

### 7.4 XSS Prevention

| Attack vector | Mitigation |
|---|---|
| SVG attachments | Served as `application/octet-stream`, not `image/svg+xml` — forces download |
| HTML attachments | Served as `application/octet-stream` — not rendered by browser |
| Custom scripts | Admin-only `additional_script` setting; users cannot inject scripts |
| Markdown | Rendered client-side by react-markdown with default sanitization |

### 7.5 CSRF Protection

MCP endpoints (`/mcp`) validate the `Origin` header to prevent cross-site request forgery. The validation rejects requests whose origin does not match the instance URL.

### 7.6 Input Validation

| Input | Validation |
|---|---|
| UID fields | Format validation (UUID-like pattern) |
| SQL queries | Fully parameterized; no string interpolation |
| File uploads | Size limit enforced (configurable `upload_size_limit_mb`) |
| S3 endpoint | Standard URL parsing; path traversal not applicable |

### 7.7 TLS

Memos does **not** terminate TLS. TLS must be handled by a reverse proxy (Nginx, Caddy, Traefik). For direct TLS (no reverse proxy), wrap memos with a TLS-terminating proxy on the same host.

### 7.8 Docker Security

The official Docker image:
- Runs as **uid 10001** (non-root)
- Does not require `--privileged`
- Needs write access only to the data volume mount point

```bash
# Verify the data directory is writable by uid 10001
chown -R 10001:10001 /opt/memos
```

### 7.9 Secrets Management

| Secret | Where stored | Rotation impact |
|---|---|---|
| `secret_key` (JWT signing) | `instance_setting` table | Invalidates all sessions |
| S3 credentials | `instance_setting` table | Attachments inaccessible until updated |
| SMTP credentials | `instance_setting` table | Email stops until updated |
| AI API keys | `instance_setting` table | Transcription stops until updated |
| OAuth2 client secret | `idp` table | SSO login fails until updated |

There is no dedicated secrets management integration (e.g., Vault, AWS Secrets Manager). All secrets are stored in the database in plaintext. Protect database access accordingly.

---

## 8. Developer Internals

### 8.1 Store Layer

The store layer (`store/`) provides the data access interface. It follows a **driver interface + cache wrapper** pattern:

```
Application code
    ↓
store.Store (interface)
    ↓
store/cache/ (in-memory cache, LRU-style)
    ↓
store/db/{sqlite,mysql,postgres}/ (SQL driver implementation)
    ↓
Database
```

Key design decisions:
- All DB operations use parameterized queries — no raw string interpolation
- WAL mode (SQLite) and FK enforcement are set on connection open
- The cache layer wraps the driver; cache invalidation is manual (called after writes)
- No ORM — raw SQL with `database/sql`

### 8.2 CEL Filter Engine

The filter engine in `internal/filter/` implements Google's Common Expression Language (CEL) for memo queries. CEL expressions are compiled at parse time and evaluated against memo records, enabling expressive and safe server-side filtering without SQL injection risk.

This engine powers:
- The `MemoService.ListMemos` filter parameter
- MCP tool `list_memos` and `search_memos`
- Saved shortcuts

**Example filter expressions** (used internally, exposed via API):
```
visibility == "PUBLIC"
creator == "users/1" && tag in ["work", "project"]
content.contains("meeting notes")
```

### 8.3 Markdown Processing

The backend markdown processor (`internal/markdown/`) extends Goldmark with custom extensions:
- **Tag parser**: extracts `#tag` patterns into structured tag metadata stored in memo payload
- **Mention parser**: extracts `@username` mentions for inbox notifications

The frontend uses `react-markdown` for rendering. The two processors must remain compatible in their interpretation of Memos-specific syntax.

### 8.4 Webhook System

`internal/webhook/` delivers outbound webhooks when memo events occur:
- Events: memo created, updated, deleted
- Payload: JSON with event type + memo data
- SSRF protection: destination URL validated before each delivery
- No retry logic — single-attempt delivery
- Timeout: standard Go HTTP client timeout

### 8.5 AI Integration

`internal/ai/` provides a thin wrapper over external AI providers:

| Provider | Type value | Feature |
|---|---|---|
| OpenAI | `OPENAI` | Audio transcription (Whisper), image-to-text |
| Google Gemini | `GEMINI` | Audio transcription, image-to-text |

The `AIService` gRPC endpoint accepts audio or image attachments and returns transcribed text. Multiple providers can be configured; the client selects by provider ID.

### 8.6 MCP Server

The MCP (Model Context Protocol) server at `/mcp` exposes Memos as a tool-callable data source for AI assistants (Claude, etc.).

**Available tools**:

| Tool | Description |
|---|---|
| `list_memos` | List memos with optional filters |
| `get_memo` | Get a specific memo by UID |
| `search_memos` | Full-text search across memos |
| `create_memo` | Create a new memo |
| `update_memo` | Update an existing memo |
| `delete_memo` | Delete a memo |
| `list_memo_comments` | List comments on a memo |
| `create_memo_comment` | Add a comment to a memo |
| `list_tags` | List all tags |
| `list_attachments` | List attachments |
| `get_attachment` | Get attachment metadata |
| `delete_attachment` | Delete an attachment |
| `link_attachment_to_memo` | Associate an attachment with a memo |
| `list_memo_relations` | List relationships between memos |
| `create_memo_relation` | Create a memo relationship |
| `delete_memo_relation` | Delete a memo relationship |
| `list_reactions` | List reactions on content |
| `upsert_reaction` | Add or update a reaction |
| `delete_reaction` | Remove a reaction |

**Resources**: `memo://memos/{uid}` — individual memo content

**Prompts**: `capture`, `review`, `daily_digest`, `organize`

**Authentication**: MCP requests use the same PAT/JWT auth as the REST API. The MCP server enforces ACL identically to gRPC handlers.

**CSRF**: Origin header validated; requests from untrusted origins are rejected.

### 8.7 RSS Feeds

The RSS service (`server/router/rss/`) generates Atom/RSS feeds for public memos. The instance URL (`--instance-url`) must be set for feed links to be correct.

Feed URLs follow the pattern: `GET /rss/{username}` for per-user public memo feeds.

### 8.8 File Server

`server/router/fileserver/` handles attachment binary serving with these features:

| Feature | Detail |
|---|---|
| Range requests | Supports `Range` header for video/audio streaming (Safari compatibility) |
| Thumbnails | Generated on-demand, cached in `.thumbnail_cache/`, max 600px, 3 concurrent |
| Cache-Control | `public, max-age=3600` |
| SVG/HTML | Served as `application/octet-stream` (XSS prevention) |
| S3 attachments | Served via presigned URLs (refreshed by background runner) |
| Avatar serving | `GET /file/users/{identifier}/avatar` |

### 8.9 OAuth2 Identity Providers

`internal/idp/` implements the OAuth2 authorization code flow for SSO:

1. Admin creates an IDP record via `IDPService` (client ID, secret, auth URL, token URL, scopes)
2. Users click "Sign in with {provider}" → redirected to OAuth2 authorization URL
3. Provider redirects back with authorization code → Memos exchanges for tokens
4. User email/identifier extracted from ID token or userinfo endpoint
5. If `identifier_filter` is set on the IDP, only matching identifiers can sign in
6. Existing user matched by email; new user created if registration is allowed

### 8.10 Scheduler and Cron

`internal/scheduler/` provides a generic job scheduler with middleware support (logging, error recovery). `internal/cron/` is a lightweight cron expression parser. These are used for the background runners but can be extended for future scheduled operations.

### 8.11 gRPC-Gateway and ConnectRPC

Memos uses two HTTP-to-gRPC bridges:
- **gRPC-Gateway**: generates RESTful JSON endpoints at `/api/v1/*` from protobuf service definitions. Used by third-party API clients and curl-based integrations.
- **ConnectRPC**: browser-compatible gRPC transport used by the React frontend. More efficient than gRPC-Gateway for browser clients (avoids base64 encoding overhead).

Both bridges talk to the same Go gRPC service handlers — the logic is written once in gRPC and exposed via both transports.

### 8.12 Version Management

`internal/version/` provides version parsing and comparison utilities. The application version is embedded at build time. Migration files are compared against the stored `schema_version` to determine which migrations need to run.

---

## 9. Integration Points

### 9.1 REST API Reference

All endpoints are served at `https://{instance}/api/v1/`. Authentication via `Authorization: Bearer {token}` (access token or PAT).

| Method | Path | Auth | Description |
|---|---|---|---|
| GET | `/api/v1/auth/status` | Optional | Current user info |
| POST | `/api/v1/auth/signin` | None | Sign in with username/password |
| POST | `/api/v1/auth/signup` | None | Register new account |
| POST | `/api/v1/auth/signout` | Required | Sign out, revoke refresh token |
| GET | `/api/v1/memos` | Optional | List memos (filter, pagination) |
| POST | `/api/v1/memos` | Required | Create memo |
| GET | `/api/v1/memos/{name}` | Optional | Get memo by name |
| PATCH | `/api/v1/memos/{name}` | Required | Update memo |
| DELETE | `/api/v1/memos/{name}` | Required | Delete memo |
| GET | `/api/v1/memos/{name}/comments` | Optional | List memo comments |
| POST | `/api/v1/memos/{name}/comments` | Required | Add comment |
| GET | `/api/v1/users` | Admin | List users |
| POST | `/api/v1/users` | Admin | Create user |
| GET | `/api/v1/users/{name}` | Optional | Get user |
| PATCH | `/api/v1/users/{name}` | Required | Update user |
| DELETE | `/api/v1/users/{name}` | Admin | Delete user |
| GET | `/api/v1/users/{name}/stats` | Optional | User memo statistics |
| GET | `/api/v1/attachments` | Required | List attachments |
| POST | `/api/v1/attachments` | Required | Upload attachment |
| DELETE | `/api/v1/attachments/{name}` | Required | Delete attachment |
| GET | `/api/v1/instance/setting` | Admin | Get instance settings |
| PATCH | `/api/v1/instance/setting` | Admin | Update instance settings |
| GET | `/api/v1/identityProviders` | Admin | List identity providers |
| POST | `/api/v1/identityProviders` | Admin | Create identity provider |
| GET | `/api/v1/healthz` | None | Health check |

> [!NOTE]
> The canonical API definition is the protobuf files in `proto/api/v1/`. The gRPC-Gateway generates HTTP/JSON bindings from them. When in doubt, the proto definition is authoritative.

### 9.2 Personal Access Tokens

PATs are the recommended authentication method for automation and integrations:

```bash
# Generate via UI: Settings → Personal Access Tokens → Create

# Use in API calls
curl -H "Authorization: Bearer memos_pat_..." \
  https://memos.example.com/api/v1/memos

# Use with MCP clients (e.g., Claude Desktop)
# In claude_desktop_config.json:
{
  "mcpServers": {
    "memos": {
      "command": "curl",
      "args": ["-N", "https://memos.example.com/mcp"],
      "headers": {
        "Authorization": "Bearer memos_pat_..."
      }
    }
  }
}
```

### 9.3 MCP Integration

Configure Memos as an MCP data source for AI assistants:

```json
// Claude Desktop MCP configuration
{
  "mcpServers": {
    "memos": {
      "type": "http",
      "url": "https://memos.example.com/mcp",
      "headers": {
        "Authorization": "Bearer memos_pat_your_token_here"
      }
    }
  }
}
```

### 9.4 Webhooks

Configure webhooks in user settings to receive event notifications:

```json
// Webhook payload shape (memo created)
{
  "event": "memo.created",
  "memo": {
    "name": "memos/abc123",
    "content": "Note content...",
    "visibility": "PRIVATE",
    "createTime": "2026-05-07T10:00:00Z"
  }
}
```

> [!WARNING]
> Webhooks are single-attempt with no retry. If the destination is unreliable, implement idempotent handling and use a queuing layer on the receiving end. Private IP destinations are blocked by default (SSRF protection).

### 9.5 RSS Feeds

```
# Per-user public memo feed
GET /rss/{username}

# Example
https://memos.example.com/rss/alice
```

Feeds contain all PUBLIC memos for the specified user. The instance URL must be configured (`--instance-url`) for feed links to resolve correctly.

### 9.6 S3-Compatible Storage Integration

```bash
# MinIO example
# Instance settings (via admin UI or API):
{
  "storage_type": "S3",
  "s3_config": {
    "endpoint": "https://minio.example.com",
    "region": "us-east-1",
    "bucket": "memos-attachments",
    "access_key_id": "minioadmin",
    "access_key_secret": "minioadmin",
    "use_path_style": true
  }
}

# Cloudflare R2 example
{
  "storage_type": "S3",
  "s3_config": {
    "endpoint": "https://<account>.r2.cloudflarestorage.com",
    "region": "auto",
    "bucket": "memos",
    "access_key_id": "<R2 access key>",
    "access_key_secret": "<R2 secret>",
    "use_path_style": false
  }
}
```

### 9.7 OAuth2 SSO Configuration

```bash
# Create IDP via API (admin)
curl -X POST \
  -H "Authorization: Bearer $ADMIN_PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "identityProvider": {
      "name": "Google SSO",
      "type": "OAUTH2",
      "config": {
        "client_id": "xxx.apps.googleusercontent.com",
        "client_secret": "xxx",
        "auth_url": "https://accounts.google.com/o/oauth2/auth",
        "token_url": "https://oauth2.googleapis.com/token",
        "scopes": ["openid", "email", "profile"],
        "field_mapping": {
          "identifier": "email",
          "display_name": "name"
        }
      }
    }
  }' \
  https://memos.example.com/api/v1/identityProviders
```

---

## 10. Troubleshooting

> [!TIP]
> Before investigating application-level issues, verify the health endpoint responds: `curl http://localhost:8081/healthz`. A non-200 response or connection refused means the process is not running or not bound to the expected address.

### 10.1 Common Issues

| Error | Cause | Solution | Prevention |
|---|---|---|---|
| `address already in use` on startup | Port 8081 already occupied | Change `--port` or stop the conflicting process | Use a dedicated port; check with `ss -tlnp \| grep 8081` |
| `permission denied` on data directory | Process user lacks write access | `chown -R <uid>:<gid> /data/dir` | Set ownership before first run; Docker: uid 10001 |
| `database is locked` (SQLite) | Multiple processes opening same SQLite file | Run only one Memos instance per SQLite file | Use PostgreSQL/MySQL for multi-process access |
| `no such file or directory` for DB | Data directory does not exist | Create the directory before starting Memos | Pre-create data dir in Dockerfile/systemd |
| `invalid DSN` | Malformed connection string | Check DSN format for your driver | Test DSN with driver CLI before configuring |
| Login loop after upgrade | `secret_key` changed or refresh token invalidated | Clear `memos_refresh` cookie; log in again | Never manually edit `secret_key` |
| Attachments not loading after storage type change | Old attachments reference old storage type | Migrate attachments manually; do not switch storage types on existing data without migration | Plan storage type before first use |
| S3 presigned URLs expiring | S3 presign runner not running or S3 credentials changed | Check runner logs; update credentials in instance settings | Monitor S3 credential expiry separately |
| Webhooks not delivered to internal URLs | SSRF protection blocking private IPs | Use `--allow-private-webhooks` flag | Only enable on trusted internal networks |
| MCP requests rejected with 403 | Invalid Origin header | Ensure MCP client sends correct `Origin` matching instance URL | Set `--instance-url` correctly |
| iOS PWA empty feed | Known issue #5823 | Check for updates; workaround: reload in browser | — |
| Auto-refresh errors | Known issue #5607 | Check logs for token refresh failures; re-login if persistent | — |
| Unix socket permission denied | Socket file permissions | Ensure reverse proxy user can read/write socket | See GitHub issue #5827 |
| Masonry layout not persisted | Known issue #5807 | Layout preference may reset on page reload | — |
| Calendar past months wrong color | Known issue #5816 | Cosmetic; no data impact | — |

> [!TIP]
> When troubleshooting attachment issues, always check the `storage_type` setting first. A mismatch between stored `storage_type` on individual `attachment` records and the current instance-level setting is the most common cause of broken attachment links.

### 10.2 Database Diagnostics

```bash
# SQLite: check for corruption
sqlite3 /var/opt/memos/memos.db "PRAGMA integrity_check;"

# SQLite: check WAL status
sqlite3 /var/opt/memos/memos.db "PRAGMA journal_mode; PRAGMA wal_checkpoint;"

# Check schema version
sqlite3 /var/opt/memos/memos.db \
  "SELECT value FROM instance_setting WHERE name = 'schema_version';"

# List all users
sqlite3 /var/opt/memos/memos.db \
  "SELECT id, username, role, row_status FROM user;"

# Count memos by visibility
sqlite3 /var/opt/memos/memos.db \
  "SELECT visibility, COUNT(*) FROM memo GROUP BY visibility;"
```

### 10.3 Authentication Diagnostics

```bash
# Decode JWT (for debugging — do not use in production)
# JWT is base64-encoded; inspect payload
echo "eyJ..." | cut -d. -f2 | base64 -d | jq .

# Check refresh token in cookie
# Browser DevTools → Application → Cookies → memos_refresh

# Revoke all sessions for a user (admin — delete refresh tokens from DB)
sqlite3 /var/opt/memos/memos.db \
  "DELETE FROM user_setting WHERE user_id = <uid> AND key = 'refresh_token';"
```

### 10.4 Performance Diagnostics

```bash
# Check memo count
sqlite3 /var/opt/memos/memos.db "SELECT COUNT(*) FROM memo WHERE row_status = 'NORMAL';"

# Check attachment storage usage
sqlite3 /var/opt/memos/memos.db "SELECT storage_type, COUNT(*), SUM(size)/1024/1024 AS total_mb FROM attachment GROUP BY storage_type;"

# Check thumbnail cache size
du -sh /var/opt/memos/.thumbnail_cache/

# Docker resource usage
docker stats memos --no-stream
```

---

## 11. Module Reference

### 11.1 `cmd/memos/main.go`

Entry point. Registers the root Cobra command with all CLI flags. Initializes the profile from flags/env, calls `store.Migrate()`, constructs the Store, constructs the Server, and calls `server.Start()`. Signal handling (SIGTERM/SIGINT) triggers graceful shutdown.

### 11.2 `server/server.go`

Creates and configures the Echo HTTP server. Responsibilities: middleware registration (recovery), health endpoint, static file embedding, gRPC server setup, gRPC-Gateway bridge, file server routes, RSS routes, MCP registration, background runner startup, graceful shutdown coordination.

### 11.3 `server/auth/`

Implements the `Authenticator` middleware. Extracts credentials from `Authorization: Bearer` header (both PATs and JWTs) and the `memos_refresh` HttpOnly cookie. Validates JWTs against the `secret_key`. For PATs: bcrypt hash comparison against DB. Populates the Echo context with the resolved user identity for downstream handlers.

### 11.4 `server/router/api/v1/`

Contains all gRPC service handler implementations. One Go file per service (e.g., `memo_service.go`, `user_service.go`). `acl_config.go` defines which gRPC methods require which roles. `sse_hub.go` manages the in-memory SSE connection registry.

### 11.5 `server/router/fileserver/`

Native HTTP handler for `GET /file/attachments/{uid}/{filename}` and `GET /file/users/{identifier}/avatar`. Implements:
- Content-type detection with SVG/HTML override to `application/octet-stream`
- Range request parsing and response for video/audio streaming
- On-demand thumbnail generation with `.thumbnail_cache/` persistence
- `Cache-Control: public, max-age=3600` header
- S3 presigned URL redirect for S3-stored attachments

### 11.6 `server/router/frontend/`

Serves the embedded React SPA. The frontend build output (`web/dist/`) is embedded into the binary at compile time via Go's `embed` package. All routes not matched by other handlers return `index.html`, enabling client-side routing.

### 11.7 `server/router/mcp/`

Implements the MCP server using the `mcp-go` library. Registers all tools, resources, and prompts. Enforces CSRF via Origin header validation. All tool calls go through the same Store layer as gRPC handlers.

### 11.8 `server/router/rss/`

Generates RSS/Atom XML feeds for public memos. Reads PUBLIC memos from the Store, formats them as feed items, and returns the feed document.

### 11.9 `store/`

The data access interface (`Store`) is defined here. Implementations in `store/db/{sqlite,mysql,postgres}/` provide driver-specific SQL. The cache wrapper in `store/cache/` intercepts reads (returning cached values) and writes (invalidating cache entries). Migrations in `store/migration/` are plain SQL files, one per schema version.

### 11.10 `internal/filter/`

CEL (Common Expression Language) filter engine. Takes a filter string, compiles it to a CEL program, and evaluates it against memo records. Used by `MemoService.ListMemos` and MCP tools to implement server-side memo filtering without ad-hoc SQL construction.

### 11.11 `internal/markdown/`

Goldmark-based markdown processor. Custom extensions:
- **Tag extractor**: parses `#tag` tokens, extracts tag names, stores in memo payload JSON
- **Mention extractor**: parses `@username` tokens, creates inbox notifications

### 11.12 `internal/webhook/`

Outbound webhook delivery. Before making the HTTP request, validates the destination URL against reserved CIDR blocks (SSRF protection). Single-attempt delivery with standard HTTP timeout. No retry queue.

### 11.13 `internal/ai/`

Thin client wrappers for OpenAI and Google Gemini APIs. Used by `AIService` for audio transcription and image-to-text. Provider selection is by configured provider ID. API keys are stored in instance settings.

### 11.14 `internal/email/`

SMTP email client. Supports STARTTLS and implicit SSL/TLS. Used for account notifications. Configuration in instance settings (`email_config.*`). No email queuing — delivery is synchronous.

### 11.15 `internal/idp/`

OAuth2 identity provider client. Implements the authorization code flow: generates auth URL, exchanges code for tokens, fetches user info, applies `identifier_filter`. Supports any standard OAuth2 provider.

### 11.16 `internal/profile/`

Defines the `Profile` struct that holds the resolved runtime configuration (data dir, addr, port, driver, DSN, instance URL, etc.). Populated from CLI flags at startup and passed to all major components.

### 11.17 `internal/storage/s3/`

S3 client wrapper using the AWS SDK. Handles upload, download (presigned URL generation), and deletion. Used by both the file server (for serving) and the S3 presign runner (for URL refresh).

### 11.18 `web/`

React 18 SPA built with Vite and TypeScript. Key libraries: Tailwind CSS 4 (styling), Radix UI (headless components), TanStack React Query (server state), react-markdown (markdown rendering), ConnectRPC (gRPC transport). The build output is embedded into the Go binary at compile time.

---

_Generated May 2026 from usememos/memos at bcbcb03. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
