320 lines
14 KiB
Markdown
320 lines
14 KiB
Markdown
# 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/<id>_<token>/` or `data/files/notes/<id>_<token>/`.
|
|
- 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/<id>_<token>/` and `data/files/notes/<id>_<token>/`
|
|
- Public attachment URL: `/f/<token>/<filename>`
|
|
|
|
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/<token>` 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 <no-reply@example.com>` 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=<seconds>`.
|
|
|
|
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.
|