# RustPad RustPad is a collaborative Markdown editor with standalone notes and workspaces. ## Development setup ```bash ./dev.sh ``` The script creates `data/db` and `data/files`, refreshes the generated browser libraries, builds the project, and starts it with Cargo. If Cargo is unavailable, it runs `docker compose up --build` instead; the Docker build downloads the libraries in a separate stage. ## Workspace features - Real-time collaborative editing over WebSocket. - Nicknames stored in `localStorage`. - Change authors shown in history. - Line numbering enabled by default, with per-account preferences stored separately for each note or pad. - The formatting toolbar can be collapsed; the state is saved per account and per note or pad. - Signed-in users with read/write access can save personal compact view, line, font, size, authorship, and color preferences; resource-linked rows are removed with the note, pad, or account. - Owner color displayed next to each line. - Image and file uploads to `data/files/pads/_/` or `data/files/notes/_/`. - Compact attachment aliases are inserted after upload: `[file=name.ext,label]`, `[image=name.ext,alt]`, and `[video=name.ext,label]`. Video uploads can be inserted as an embedded player or a forced-download link. - Standalone YouTube links are rendered as responsive privacy-enhanced players. - Markdown and Mermaid diagram rendering. - History with snippets, previews, and version restore. - Alert blocks: `success`, `info`, `warning`, and `danger`. - Table of contents generated with `[TOC]`. - Optional line numbers in fenced code blocks. ## Fenced code blocks and language aliases RustPad recognizes common language names and aliases, including: - JavaScript: `js`, `javascript`, `jsx` - TypeScript: `ts`, `typescript`, `tsx` - Python: `py`, `python` - PHP: `php` - Rust: `rs`, `rust` - Shell: `sh`, `shell`, `bash`, `zsh` - C and C++: `c`, `h`, `cpp`, `c++`, `cxx`, `hpp` - C#: `cs`, `c#`, `csharp` - Java, Kotlin, Go, Swift, Dart, Scala - HTML, XML, SVG, CSS, SCSS, Sass, Less - JSON, YAML, TOML, INI, SQL, GraphQL - Markdown, Dockerfile, Makefile, PowerShell - Lua, Perl, R, MATLAB, Nginx, Apache, Diff, and plain text Standard code block: ````markdown ```python print("Hello") ``` ```` Code block with line numbers starting from line 1: ````markdown ```python= print("Hello") ``` ```` Code block with line numbers starting from a custom value: ````markdown ```python=101 print("Hello") ``` ```` The `=number` suffix is a RustPad extension and may not be supported by other Markdown renderers. ## Data - SQLite database: `data/db/rustpad.db` - Attachments: `data/files/pads/_/` and `data/files/notes/_/` - Public attachment URL: `/f//` In Docker, both directories are located under `/data`. ## Publishing a note as a page Use the **Page** button in the editor. RustPad creates a permanent public `/s/` URL, copies it to the clipboard, and opens it in a new tab. The page displays the current note and renders Markdown, images, video players, YouTube embeds, links, and Mermaid diagrams. Publishing a protected note requires its password, but the generated public page itself is accessible without that password. ## Upload limit Configure the maximum size of a single uploaded file for signed-in users with `UPLOAD_MAX_SIZE_MB` in `.env`, for example: ```env UPLOAD_MAX_SIZE_MB=50 ``` The default limit is 20 MB. Uploads by guests are disabled by default. Enable them deliberately and set their separate per-file limit with: ```env GUEST_UPLOAD_ENABLED=true GUEST_UPLOAD_MAX_SIZE_MB=5 ``` Guest uploads still require read-write access to the note or workspace. Restart the project with `./dev.sh` after changing these values. ## Database selection RustPad selects the database engine through `DATABASE_URL`: - SQLite: `sqlite:///data/db/rustpad.db?mode=rwc&journal_mode=WAL&busy_timeout=5000` - PostgreSQL: `postgres://rustpad:rustpad@postgres:5432/rustpad` - MySQL: `mysql://rustpad:rustpad@mysql:3306/rustpad` SQLite remains the default for development and small installations. WAL mode allows reads during writes, but SQLite still performs only one write at a time. `busy_timeout=5000` waits briefly instead of immediately returning a `database is locked` error. PostgreSQL or MySQL is recommended for many concurrent editors or multiple application instances. Optional databases in Docker Compose: ```bash # PostgreSQL docker compose --profile postgres up -d postgres DATABASE_URL=postgres://rustpad:rustpad@postgres:5432/rustpad docker compose up -d rustpad # MySQL docker compose --profile mysql up -d mysql DATABASE_URL=mysql://rustpad:rustpad@mysql:3306/rustpad docker compose up -d rustpad ``` Migrations are stored in `migrations/sqlite`, `migrations/postgres`, and `migrations/mysql`. Runtime SQL statements are centralized in `src/queries.rs`, while `src/database.rs` selects the driver and configures the connection. ## Optional user accounts and password reset Nicknames can be used anonymously while they remain unregistered. Registering a nickname reserves it and requires a valid login session before it can be used in editor WebSocket connections. Configure `PUBLIC_URL`, `SMTP_HOST`, `SMTP_PORT`, `SMTP_SECURITY`, `SMTP_USERNAME`, `SMTP_PASSWORD`, and `SMTP_FROM` to enable password-reset emails. `SMTP_FROM` accepts both `RustPad ` and a value wrapped in one matching pair of single or double quotes, as may be passed literally by container env-file implementations. `SMTP_SECURITY` accepts `none` (plain SMTP, typically an internal relay on port 25), `starttls`, or `tls` (implicit TLS, commonly port 465). When omitted, it defaults to `tls` for port 465, `starttls` for port 587, and `none` for port 25 or any other port. SMTP authentication is enabled only when both `SMTP_USERNAME` and `SMTP_PASSWORD` are non-empty. Reset links expire after 30 minutes and can be used only once. ## Browser libraries `static/libs/rustpad-player` is project-owned and stays in the repository. Mermaid and Highlight.js are generated locally and ignored by Git. Refresh or restore them with: ```bash python3 scripts/update_browser_libs.py ``` The standard-library-only updater checks the current stable npm releases, verifies tarball integrity, and stores each license beside the generated files. `./dev.sh` runs it before local Cargo development. Docker performs the same download in the `browser-libs` stage; `dev.sh` sets `BROWSER_LIBS_REFRESH` so Docker does not reuse a stale dependency layer. The editor serves all browser libraries through `/assets` and never loads Mermaid or Highlight.js directly from a public CDN. ## Diagnostics and logging Server logs use `tracing`. Configure verbosity with `RUST_LOG`, for example: ```env RUST_LOG=rustpad=debug,tower_http=info ``` Important lifecycle, database, authentication, password-reset, and WebSocket events are logged. Passwords, session tokens, reset tokens, SMTP credentials, and authorization headers are never logged. Browser diagnostics are configured separately with `FRONTEND_LOG_LEVEL`. Supported values are `off`, `error`, `warn`, `info`, and `debug`; the default is `warn`. URL parameters cannot enable diagnostics. Use `debug` only in trusted development environments. Production should normally use `warn` or `error`. ## Registration and SMTP Set `REGISTRATION_ENABLED=true` to enable registration. After an account is created, the application sends an SMTP message containing the nickname and `PUBLIC_URL`. Set `ACCOUNT_CONFIRMATION_REQUIRED=true` to require users to click a confirmation link before signing in. This option is disabled by default and requires SMTP configuration. ## Attachment storage RustPad supports two interchangeable attachment backends selected in `.env`: - `STORAGE_DRIVER=local` stores files under `FILES_DIR` and is the default. - `STORAGE_DRIVER=s3` uses an S3-compatible service such as AWS S3, Garage, Ceph RGW, OpenStack, or MinIO. Stored attachment paths remain `/f/{token}/{filename}` for both backends. RustPad validates access and streams objects through the API, so the bucket does not need to be public and existing database records do not require migration. Set `FILES_PUBLIC_URL=files.note.example.com` to return attachment links through a separate domain. Bare domains are normalized to HTTPS; `http://` can be used explicitly for local deployments. The external domain must serve or proxy the same `/f/{token}/{filename}` paths. Removing the variable immediately restores application-relative `/f/...` links, including for records created while a custom domain was enabled. Set `ASSET_CACHE_MAX_AGE_SECONDS=0` or `FILE_CACHE_MAX_AGE_SECONDS=0` to disable browser caching. RustPad then sends `Cache-Control: no-cache, no-store, must-revalidate`; positive values use `public, max-age=`. For the optional Docker Garage service, configure the S3 variables shown in `.env.example`, use strong unique credentials, and run: ```sh docker compose --profile s3 up -d --build ``` Garage runs as a separate Compose service. Existing PostgreSQL and MySQL profiles remain unchanged. The included single-node setup is intended for local or self-hosted development without redundancy. Production Garage deployments should use a properly designed multi-node configuration. ## Authentication: local, LDAP, LDAPS, or Active Directory Choose exactly one authentication backend: ```env AUTHORIZATION_TYPE=local ``` Supported values: - `local` - built-in registration and password login - `ldap` - OpenLDAP-compatible directory - `ad` - Microsoft Active Directory defaults For `ldap` and `ad`, RustPad searches the directory with the service account, validates the password by binding as the user, and automatically provisions a local account. Existing sessions, ownership, sharing, and other account functions continue to use the existing `users` table. Local registration and guest nickname access are disabled. The nickname is generated as `LDAP_ORGANIZATION/displayName`, for example `example/Mateusz Testowy`. Directory accounts are stored using a stable `entryUUID` (LDAP) or `objectGUID` (AD), while e-mail, nickname, and DN are synchronized after every successful login. ### OpenLDAP ```env AUTHORIZATION_TYPE=ldap LDAP_URL=ldap://10.87.2.6:389 LDAP_STARTTLS=false LDAP_BIND_DN=cn=admin,dc=example,dc=org LDAP_BIND_PASSWORD=admin LDAP_BASE_DN=ou=people,dc=example,dc=org LDAP_ORGANIZATION=example ``` In `ldap` mode the defaults are: ```env LDAP_USER_FILTER=(uid={username}) LDAP_USERNAME_ATTRIBUTE=uid LDAP_EMAIL_ATTRIBUTE=mail LDAP_DISPLAY_NAME_ATTRIBUTE=displayName ``` ### Active Directory ```env AUTHORIZATION_TYPE=ad LDAP_URL=ldaps://ad.example.org:636 LDAP_STARTTLS=false LDAP_BASE_DN=DC=example,DC=org LDAP_BIND_DN=CN=rustpad-bind,OU=Service Accounts,DC=example,DC=org LDAP_BIND_PASSWORD=secret LDAP_ORGANIZATION=example ``` In `ad` mode the defaults are: ```env LDAP_USER_FILTER=(|(sAMAccountName={username})(userPrincipalName={username})) LDAP_USERNAME_ATTRIBUTE=sAMAccountName LDAP_EMAIL_ATTRIBUTE=mail LDAP_DISPLAY_NAME_ATTRIBUTE=displayName ``` All LDAP attributes and filters can still be overridden explicitly. By default TLS certificates are verified. For a trusted internal or test server using a self-signed certificate, `LDAP_TLS_VERIFY=false` keeps LDAPS/StartTLS encryption enabled without requiring a custom CA file. Do not disable verification on untrusted networks. Additional directory options: ```env LDAP_EXTERNAL_ID_ATTRIBUTE=entryUUID LDAP_EMAIL_REQUIRED=true LDAP_LINK_EXISTING_BY_EMAIL=false LDAP_TLS_VERIFY=true LDAP_CONNECT_TIMEOUT_SECONDS=5 LDAP_OPERATION_TIMEOUT_SECONDS=10 ``` For Active Directory, the default external identifier is `objectGUID`. Set `LDAP_LINK_EXISTING_BY_EMAIL=true` only during an intentional migration of existing local or legacy LDAP accounts; otherwise an e-mail collision is rejected. ### Test LDAP on 10.0.0.1 (example) ```bash cd docker/ldap docker compose up -d ``` Test users: - `admin` / `test1234` - `user` / `test1234` phpLDAPadmin: `http://10.0.0.1:8088` Administrator: `cn=admin,dc=example,dc=org` / `admin` ## CLI and YAML configuration RustPad reads `.env` as before and can additionally load a YAML file. Environment variables have higher priority than YAML values. ```bash rustpad --version rustpad --help rustpad --config /etc/rustpad/rustpad.yaml check-config rustpad --config /etc/rustpad/rustpad.yaml migrate rustpad --config /etc/rustpad/rustpad.yaml ``` `check-config` validates YAML syntax, supported keys, value types, required LDAP/S3/SMTP fields, database URL scheme, paths and dependent settings. It does not connect to the database or LDAP server. Example deployment files are in `systemd/`. ### Database query layout SQL is selected explicitly by database engine. Application code uses logical query identifiers from `src/queries/mod.rs`; complete engine-specific statements live in: - `src/queries/sqlite.rs` - `src/queries/postgres.rs` - `src/queries/mysql.rs` PostgreSQL statements use native `$1`, `$2`, ... placeholders. Query text is not rewritten at runtime, and result-shape casts are defined independently for each engine. ## Random API test data `tests/random_data.py` creates data only through RustPad's HTTP API. It logs in, obtains a CSRF token, creates workspaces and notes, and can seed the initial Markdown content from generated text or cached snapshots of random Wikipedia pages with Wikimedia images. ```bash export RUSTPAD_TEST_PASSWORD='test1234' python3 tests/random_data.py \ --ip localhost \ --port 3000 \ --source wikipedia \ --notes 10000 \ --workspaces 10 \ --notes-in-workspaces 1000 \ --user test ``` `--notes-in-workspaces` is applied to every workspace. The example creates 10,000 standalone notes and another 10,000 notes inside 10 workspaces. Wikipedia mode never falls back to generated content. Use `--wikipedia-images`, `--wikipedia-attempts`, `--workers`, `--source-pool-size`, `--scheme https`, `--base-url`, or `--dry-run` as needed. The login value may be a local account e-mail or an LDAP/AD username.