Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ Every stack starts from the same [standard setup](documentation/standard-setup.m
| **Memos** | Note-taking app for short Markdown notes, shown on a timeline. | [Details](services/memos) |
| **Nanote** | Note-taking app that stores notes as Markdown files in folders. | [Details](services/nanote) |
| **NextExplorer** | Web file manager for browsing, uploading, and editing files in one folder on your server. | [Details](services/next-explorer) |
| **Open Notebook** | Alternative to NotebookLM for chatting with your sources using AI. | [Details](services/open-notebook) |
| **Open WebUI** | Chat interface for AI models, through Ollama or OpenAI-compatible APIs. | [Details](services/open-webui) |
| **OpenClaw** | Personal AI assistant that connects agents to your chat apps. | [Details](services/openclaw) |
| **OpenCloud** | File sync and sharing platform with desktop and mobile clients. | [Details](services/opencloud) |
Expand Down
32 changes: 32 additions & 0 deletions services/open-notebook/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#URL=https://github.com/tailscale-dev/ScaleTail
# To run a second copy, copy the folder under another name and change SERVICE.

# Service Configuration
# Service name (e.g., adguard). Used as hostname in Tailscale and for container naming.
SERVICE=open-notebook
# Docker image URL from container registry (e.g., adguard/adguardhome:latest).
IMAGE_URL=lfnovo/open_notebook:v1-latest

# Network Configuration
# Port to expose to local network. Uncomment the "ports:" section in compose.yaml to enable.
SERVICEPORT=8502
# Preferred DNS server for Tailscale. Uncomment the "dns:" section in compose.yaml to enable.
DNS_SERVER=9.9.9.9

# Tailscale Configuration
# Auth key from https://console.tailscale.com/admin/settings/keys. See: https://tailscale.com/kb/1085/auth-keys#generate-an-auth-key for instructions.
TS_AUTHKEY=

# Time Zone setting for containers. See: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
TZ=Europe/Amsterdam

# Any Container environment variables are declared below. See https://docs.docker.com/compose/how-tos/environment-variables/

# Required: your Tailnet name, the part between the service name and ".ts.net" in https://open-notebook.<tailnet name>.ts.net, for example tail1234.
TAILNET_NAME=
# Required: encrypts the AI provider keys that the service stores. Generate it with: openssl rand -hex 32. Keep it: a new key cannot decrypt the saved provider keys.
OPEN_NOTEBOOK_ENCRYPTION_KEY=
# Required: password of the database. Generate it with: openssl rand -hex 32
SURREAL_PASSWORD=
# Optional: password for the web interface. Leave it empty to use the service without a login.
OPEN_NOTEBOOK_PASSWORD=
50 changes: 50 additions & 0 deletions services/open-notebook/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Open Notebook

[Open Notebook](https://www.open-notebook.ai/) is an open-source, self-hosted alternative to Google's NotebookLM. You collect sources such as web pages, PDFs, and text in notebooks, then chat with them, search them, and generate notes and podcasts with the AI provider of your choice.

This stack runs Open Notebook with a Tailscale sidecar, as described in [the standard setup](../../documentation/standard-setup.md). The image does not include Tailscale, so the sidecar gives it Tailnet access.

## At a glance

| Item | Value |
| ------------- | ------------------------------------------------------ |
| Web interface | `https://open-notebook.<tailnet>.ts.net` |
| Service port | `8502` |
| Images | `lfnovo/open_notebook:v1-latest` |
| | `surrealdb/surrealdb:v2` |
| Data | `./open-notebook-data/notebook` (uploads and settings) |
| | `./open-notebook-data/surreal` (database) |

## Before you start

Set these values in `.env`. Compose stops with an error if one of them is empty.

- **`TAILNET_NAME`.** Your Tailnet name, the part between the service name and `.ts.net`, for example `tail1234`. `compose.yaml` builds the address `https://<SERVICE>.<TAILNET_NAME>.ts.net` from it.
- **`OPEN_NOTEBOOK_ENCRYPTION_KEY`.** Encrypts the AI provider keys that you save in the web interface. Generate one with `openssl rand -hex 32`. Keep it: with a different key, the saved provider keys can no longer be decrypted and you must enter them again.
- **`SURREAL_PASSWORD`.** The password of the database. Generate one with `openssl rand -hex 32`. The database applies it only when it creates the root user at the first start. If you change it later, Open Notebook can no longer log in to the database and logs `There was a problem with authentication`. Set the old password again, or delete `./open-notebook-data/surreal` to start with an empty database, which removes all notebooks.

## Deviations from the standard setup

- **Extra container.** The stack runs a `db` container with SurrealDB. It uses the network of the `tailscale` container as well, so Open Notebook reaches it at `127.0.0.1`. The database listens only on the loopback address of the device (`SURREAL_BIND`), so other devices on your Tailnet cannot reach it. The image listens on all addresses by default.
- **Root user.** The `db` container runs as root, as upstream requires, to write to the bind-mounted folder. The folder `./open-notebook-data/surreal` is therefore owned by root.
- **API address.** `API_URL` in `compose.yaml` is the Tailnet address of the service. The browser sends its API calls there, and the web interface forwards them to the API on port `5055`. Without it, the browser would call port `5055` of the Tailnet address, which Serve does not publish. If you change `SERVICE` or `TAILNET_NAME`, the address changes with them. For the same reason the optional `ports` block gives a page that loads from your local network, but its API calls go to the Tailnet address, so it works only on a device that is connected to your Tailnet.
- **Health check.** The image defines no health check, so the `application` container calls `/health` of the API on port `5055`.

## First run

Open the web interface. Open Notebook has no login until you set a password, so anyone on your Tailnet can use it. To require a password, set `OPEN_NOTEBOOK_PASSWORD` in `.env` and run `docker compose up -d`.

Then connect an AI provider: open Manage, then Models, add a credential for your provider (a cloud API key, or an Ollama address), test it, add models, and set the default models. Chat and search do not work until the default models are set. The first start can take a minute, because the API runs database migrations.

## Configuration

The Docling engine (OCR and image sources) and a local Crawl4AI browser are not installed by default. To enable them, uncomment `OPEN_NOTEBOOK_ENABLE_DOCLING` or `OPEN_NOTEBOOK_ENABLE_CRAWL4AI` in `compose.yaml` and run `docker compose up -d`. They are downloaded at the next start, which takes several minutes, and are cached in `./open-notebook-data/notebook`.

Open Notebook accepts more settings as environment variables. Add them to the `application` service. The [environment reference](https://github.com/lfnovo/open-notebook/blob/main/docs/5-CONFIGURATION/environment-reference.md) lists them.

## Links

- [Open Notebook documentation](https://github.com/lfnovo/open-notebook/tree/main/docs)
- [Docker Compose installation guide](https://github.com/lfnovo/open-notebook/blob/main/docs/1-INSTALLATION/docker-compose.md)
- [Reverse proxy guide](https://github.com/lfnovo/open-notebook/blob/main/docs/5-CONFIGURATION/reverse-proxy.md)
- [Open Notebook source code](https://github.com/lfnovo/open-notebook)
105 changes: 105 additions & 0 deletions services/open-notebook/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
configs:
ts-serve:
content: |
{"TCP":{"443":{"HTTPS":true}},
"Web":{"$${TS_CERT_DOMAIN}:443":
{"Handlers":{"/":
{"Proxy":"http://127.0.0.1:8502"}}}},
"AllowFunnel":{"$${TS_CERT_DOMAIN}:443":false}}

services:
# Make sure you have updated/checked the .env file with the correct variables.
# Every variable used in this file must be defined there, unless it has a default such as ${VAR:-value}.
# Tailscale Sidecar Configuration
tailscale:
image: tailscale/tailscale:latest # Image to be used
container_name: tailscale-${SERVICE} # Name for local container management
hostname: ${SERVICE} # Name used within your Tailscale environment
environment:
- TS_AUTHKEY=${TS_AUTHKEY}
- TS_STATE_DIR=/var/lib/tailscale
- TS_SERVE_CONFIG=/config/serve.json # Tailscale Serve configuration to expose the web interface on your local Tailnet - remove this line if not required
- TS_USERSPACE=false
- TS_ENABLE_HEALTH_CHECK=true # Enable healthcheck endpoint: "/healthz"
- TS_LOCAL_ADDR_PORT=127.0.0.1:41234 # The <addr>:<port> for the healthz endpoint
#- TS_ACCEPT_DNS=true # Uncomment only if the service must resolve MagicDNS names - this replaces Docker DNS, so Compose service names no longer resolve
- TS_AUTH_ONCE=true
configs:
- source: ts-serve
target: /config/serve.json
volumes:
- ./config:/config # Config folder used to store Tailscale files - you may need to change the path
- ./ts/state:/var/lib/tailscale # Tailscale requirement - you may need to change the path
devices:
- /dev/net/tun:/dev/net/tun # Network configuration for Tailscale to work
cap_add:
- net_admin # Tailscale requirement
#ports:
# - 0.0.0.0:${SERVICEPORT}:${SERVICEPORT} # Binding the service port to the local network - may be removed if only exposure to your Tailnet is required
# If any DNS issues arise, use your preferred DNS provider by uncommenting the config below
#dns:
# - ${DNS_SERVER}
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:41234/healthz"] # Check Tailscale has a Tailnet IP and is operational
interval: 1m # How often to perform the check
timeout: 10s # Time to wait for the check to succeed
retries: 3 # Number of retries before marking as unhealthy
start_period: 10s # Time to wait before starting health checks
restart: always

# Application
application:
image: ${IMAGE_URL} # Image to be used
network_mode: service:tailscale # Sidecar configuration to route the service through Tailscale
container_name: app-${SERVICE} # Name for local container management
environment: # Variables are declared in .env file.
- TZ=${TZ}
- OPEN_NOTEBOOK_ENCRYPTION_KEY=${OPEN_NOTEBOOK_ENCRYPTION_KEY:?Set OPEN_NOTEBOOK_ENCRYPTION_KEY in .env}
- OPEN_NOTEBOOK_PASSWORD=${OPEN_NOTEBOOK_PASSWORD:-}
# The browser sends its API calls to this address, which Serve forwards to the web interface.
- API_URL=https://${SERVICE}.${TAILNET_NAME:?Set TAILNET_NAME in .env}.ts.net
- SURREAL_URL=ws://127.0.0.1:8000/rpc
- SURREAL_USER=root
- SURREAL_PASSWORD=${SURREAL_PASSWORD:?Set SURREAL_PASSWORD in .env}
- SURREAL_NAMESPACE=open_notebook
- SURREAL_DATABASE=open_notebook
# Optional: install the Docling or Crawl4AI extraction runtimes at the next start. See the README.
#- OPEN_NOTEBOOK_ENABLE_DOCLING=true
#- OPEN_NOTEBOOK_ENABLE_CRAWL4AI=true
volumes:
- ./${SERVICE}-data/notebook:/app/data
depends_on:
tailscale:
condition: service_healthy
db:
condition: service_healthy
# Healthcheck: the image defines none, but it ships curl. The check calls the health endpoint of the API.
healthcheck:
test: ["CMD", "curl", "-fsS", "-o", "/dev/null", "http://127.0.0.1:5055/health"] # Check if the service is responding
interval: 1m # How often to perform the check
timeout: 10s # Time to wait for the check to succeed
retries: 3 # Number of retries before marking as unhealthy
start_period: 60s # Time to wait before starting health checks
restart: always

# Database
db:
image: surrealdb/surrealdb:v2
network_mode: service:tailscale # Join the same network namespace to be accessible via localhost
container_name: app-${SERVICE}-db # Name for local container management
command: ["start", "--log", "info", "rocksdb:/mydata/mydatabase.db"]
environment:
- SURREAL_USER=root
- SURREAL_PASS=${SURREAL_PASSWORD:?Set SURREAL_PASSWORD in .env}
- SURREAL_BIND=127.0.0.1:8000 # Listen on localhost only, so the Tailnet cannot reach the database. The image listens on all addresses by default.
user: root # Upstream requires root to write to the bind-mounted folder
volumes:
- ./${SERVICE}-data/surreal:/mydata
# The image has no shell, so the check uses the surreal binary.
healthcheck:
test: ["CMD", "/surreal", "isready", "--endpoint", "http://127.0.0.1:8000"] # Check if the database accepts connections
interval: 10s # How often to perform the check
timeout: 5s # Time to wait for the check to succeed
retries: 5 # Number of retries before marking as unhealthy
start_period: 30s # Time to wait before starting health checks
restart: always
Loading