# 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]`, using headings after the marker and nesting them by level. - 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. Its header can toggle source line numbers and expand the document to the full browser width. 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.