Shared Rust code for PlainApp desktop, mobile and command-line hosts.
plain-core/
├── Cargo.toml Builds and tests the four packages together
├── plain-libs/ Business-independent algorithms, formats, files and native primitives
├── plain-rs/ Domain services, application storage and runtime composition
├── plain-server/ GraphQL, HTTP and native transport adapters
├── plain-cli/ The plainapp command-line executable
├── tests/ Tests grouped by package; unit paths mirror src/
├── assets/ Shared Web bundle and canonical file icons
├── schema/ GraphQL and SQLite definitions
└── scripts/ Development tools
Desktop UI and OS adapters live in plain-desktop; mobile UI and JNI/C ABI adapters live in plain-app. Native and HTTP callers use the same main GraphQL schema.
plain-rs/src/
├── lib.rs Public module entry points and compilation gates
├── runtime.rs Core construction and worker lifecycle
├── features/ Product behavior and domain operations
│ ├── chat/ Chat delivery and message operations
│ ├── files/ File operations and resource references
│ ├── media/ Media library, playback and catalog state
│ ├── prefs/ Application preferences and identity
│ └── services/ Runtime operations by domain
│ └── files/ Browsing, previews, indexing and transfers
└── infrastructure/ Application storage, host contracts and protocol policy
├── db/ Product schema, models and repositories
├── host/ Application host commands, scopes and resource streams
├── platform/ Application startup, host facts and system coordination
├── protocols/ Peer framing and product discovery policy
├── transport/ Authenticated peer transport
└── data_paths/ Application data roots and ownership
plain-libs/src/
├── crypto/ Encryption, signatures, hashes and key formats
├── media/ Format parsing, decoding and thumbnails
├── ai_image_search/ Model validation, preprocessing and inference
├── apk/ Android binary XML and resource parsing
├── tls/ Certificates and PKCS#12
├── dlna/ UPnP/DLNA parsing and protocol encoding
├── protocols/mdns/ DNS/mDNS wire codecs and records
├── phone/ Number parsing and offline metadata lookup
├── filesystem/ File operations and native watching
├── kv.rs Generic SQLite key-value storage
├── platform/ Native network, volume and filesystem primitives
└── utils/ Parsing, encoding, time and file utilities
plain-cli/src/
├── bootstrap.rs Process initialization and command dispatch
├── cli/ Command parsing and terminal interaction
├── service.rs Background service startup, shutdown and failure handling
├── host.rs CLI host capabilities and event handling
├── host/linux.rs Linux host command adapters
├── login_socket.rs Local login approval socket server and client
└── storage.rs Startup media sources and mount event wiring
plain-server/
├── src/
│ ├── server.rs Compose core runtime, schemas and transport state
│ ├── http/
│ │ ├── router.rs All HTTP route registrations
│ │ ├── fs.rs /fs: files and previews
│ │ ├── file_response.rs Shared file streaming, Range and response headers
│ │ ├── zip.rs /zip/dir and /zip/files
│ │ ├── upload.rs /upload and /upload_chunk
│ │ ├── managed_proxy.rs /proxy/{peerId}/*: GraphQL, files, uploads and sockets to known devices
│ │ ├── managed_proxy/ Raw relay, attachment, upload and socket proxy transports
│ │ ├── graphql.rs Main, peer and guest GraphQL HTTP adapters
│ │ ├── init.rs /init
│ │ ├── nearby.rs /nearby
│ │ ├── health.rs Health handler
│ │ ├── dlna.rs DLNA description, control, events, media and callbacks
│ │ ├── static_files.rs Web UI fallback and static files
│ │ ├── web_bundle.rs Embedded Web asset access
│ │ ├── ble.rs BLE adapter for the shared transfer routes
│ │ ├── ble/nearby.rs BLE nearby-device request handling
│ │ ├── auth/ HTTP authorization and CORS middleware
│ │ ├── listeners/ HTTP/TLS sockets and listener lifecycle
│ │ └── websocket/ WebSocket entry points and transport
│ │ ├── auth.rs /auth upgrade
│ │ ├── graphql.rs /graphql upgrade and subscriptions
│ │ ├── status.rs /status and peer status connections
│ │ ├── screen.rs /screen upgrade
│ │ ├── image_editor.rs /image_editor upgrade
│ │ ├── runtime.rs Shared auth, screen and image-editor connections
│ │ └── frames.rs WebSocket frame encoding
│ ├── graphql/
│ │ ├── schemas/ Main, peer and guest schema construction
│ │ ├── resolvers/ GraphQL queries and mutations by domain
│ │ │ └── mobile/ Mobile GraphQL adapters (contacts and call log)
│ │ ├── subscriptions/ GraphQL subscriptions
│ │ ├── types/ GraphQL types
│ │ └── executor.rs Shared schema execution
│ ├── host/events.rs Host event dispatch into the core services
│ └── native/ Native API entry points
Requires Rust 1.99+ and a C compiler. Building ONNX Runtime from source requires Python 3.10+.
For builds with prepared ONNX Runtime artifacts, set PLAIN_ONNX_RUNTIME_MANIFEST
to the absolute path of a trusted JSON file mapping Rust target triples to
their library SHA-256 digests. Obtain the manifest independently from the trusted
build record. Place each artifact in a directory named after its target next to
the manifest. The producer prepares each target with the existing recipe:
python3 plain-libs/native/ai_image_search/prepare_onnxruntime.py --target "$target" --output "$artifact_root/$target"Each target directory contains the native library, runtime.json, LICENSE
and ThirdPartyNotices.txt. Cargo uses CMake 3.19+ to verify the selected
artifact and stage it in the usual build output without running Python.
PLAIN_ONNX_RUNTIME_DIR still controls the output directory when set by a consumer.
For a single prepared runtime already in place, set PLAIN_ONNX_RUNTIME_DIR and PLAIN_ONNX_RUNTIME_SHA256 instead.
cargo check --workspace --locked
cargo test --workspace --locked
cargo run -p plainapp -- run --helpUse release builds for performance measurements. Consumer workspaces must configure their own Cargo profiles.
plain-rs/src/features/app_feature.rs owns app feature names. Generate the sibling Desktop and App enums with node scripts/gen-app-features.mjs --write; omit --write to check them.
plain-rs/src/features/capability.rs owns capability names and wire values. Generate the Desktop and App capability enums with yarn node scripts/gen-capabilities.mjs --write; omit --write to check them.
Regenerate the contracts with Node.js and Rust:
node scripts/gen-schema.mjs --writeThis updates schema/database.sql, schema/api/, schema/guest-api.graphqls and schema/peer-api.graphqls. Omit --write to check them.
After API changes, run yarn graphql:codegen in plain-desktop. Mobile reads the Core schema directly.
plainapp embeds the Web UI and runs as a Linux systemd service.
curl -fsSL https://raw.githubusercontent.com/plainhub/plain-core/main/install.sh | bashThe installer downloads the matching release, installs dependencies and starts the service.
First installation requires an admin password entered through a terminal; reinstalling preserves it.
For unattended installation, provision it first with plainapp passwd --stdin using protected standard input.
sudo plainapp passwd # change the admin password without stopping the servicehttp://<host>:8080— HTTPhttps://<host>:8443— HTTPS with a self-signed certificate created on first start forplainapp.localandlocalhost
Set preferences through the running service:
sudo plainapp prefs developer_mode true # enable developerMode
sudo plainapp prefs allow_any_host true # allow cross-origin requestsUse false to disable either setting. plainapp prefs <key> <value> accepts existing preference keys and JSON values and requires a running service.
The service validates, persists and applies each change; changing http_port or https_port restarts its listeners internally while the process stays running.
sudo plainapp status # process status and actual HTTP/HTTPS ports
sudo plainapp prefs http_port 8088
sudo plainapp passwd # hidden password entry with confirmationPassword input is used exactly as entered; passwd --stdin reads one line for automation.
When no service is listening, passwd retains first-password initialization in the selected data directory under its exclusive lock.
prefs never falls back to writing configuration files.
Management uses the existing local Unix socket with a private directory, owner-only socket permissions and peer OS-account verification on both ends.
The installed Linux service runs as root, so management commands require sudo; this does not protect against programs already able to execute arbitrary root commands.
User-run instances trust that OS account, including other programs running as that account.
No management TCP port or shared secret is added.
cargo build --release --locked -p plainapp
install -m 0755 target/release/plainapp /usr/local/bin/plainapp
sudo plainapp install # admin password, ffmpeg and Avahi
sudo systemctl enable --now plainappplainapp install configures the password, dependencies and /etc/systemd/system/plainapp.service.
The unit runs /usr/local/bin/plainapp run; copy the binary and enable the service as shown above.
Listeners require a configured password.
Run bash build.sh in plain-desktop to regenerate assets/web, then commit the assets with the Core changes.
CLI and mobile builds embed this bundle.
To embed another bundle, set PLAIN_WEB_DIST to its absolute directory, or use -PplainWebDist=... for mobile Gradle builds.
The directory must contain index.html. Without assets/web or an override, the build provides APIs only.
For local frontend tests, run --web-root <directory> serves files from disk without rebuilding.
Ports default to 8080 and 8443 and can be changed in user settings.
Linux root services automatically mount eligible unmounted filesystems at stable /mnt/usbX paths and monitor block-device changes.
Without path overrides, Linux CLI commands and the installed service use /var/lib/plainapp. Other operating systems use ~/.plainapp/data for the user running the process. run, prefs, passwd and status share the same directory selection.
App files: <data-dir>/files/ (Linux default: /var/lib/plainapp/files/; other operating systems: ~/.plainapp/data/files/).
<data-dir>/ Persistent data; Linux default: /var/lib/plainapp
├── plain.db SQLite database: application records and indexes
├── plain.db-wal SQLite write-ahead log, when present
├── plain.db-shm SQLite shared-memory file, when present
├── prefs.json Settings, device identity and login credentials
├── tls.pem HTTPS certificate, unless overridden
├── tls.key HTTPS private key, unless overridden
├── tls-identity.json TLS identity metadata
├── files/ App files, including note images and chat attachments
│ └── <hash[0:2]>/
│ └── <hash[2:4]>/
│ └── <sha256>[.<ext>]
├── audio/
│ └── pomodoro_sound.mp3 Custom Pomodoro sound
├── logs/
│ ├── latest.log
│ └── rust.log
├── crash_report.txt
└── crash_log.txt
Default cache directories:
- Linux:
$XDG_CACHE_HOME/plain/<data-dir-hash>, or~/.cache/plain/<data-dir-hash>when no absoluteXDG_CACHE_HOMEis set. - macOS:
~/Library/Caches/plain/<data-dir-hash>. - Windows:
%LOCALAPPDATA%/plain/<data-dir-hash>.
<cache-dir>/ Disposable application cache
├── image_cache/ Image processing cache
├── video_cache/ Video processing cache
├── thumbs/ Generated thumbnails
├── share_open/ Temporary shared-file previews
├── updates/ Downloaded application updates
└── crash_report.txt Temporary crash-report export
Set custom directories with --data-dir and --cache-dir:
plainapp --data-dir /var/lib/plainapp --cache-dir /var/cache/plainapp runUse the service's exact --data-dir for status, prefs and passwd when overriding the default; commands do not search other directories. --cache-dir is used by run and offline password initialization.
Check systemd service paths:
systemctl show plainapp -p ExecStart -p EnvironmentEnvironment overrides: PLAINAPP_DATA_DIR, PLAINAPP_CACHE_DIR, PLAINAPP_TLS_CERT, PLAINAPP_TLS_KEY. Command-line paths take precedence.
plainapp install requires default data/cache paths.
For isolated instances, run also accepts repeated --media-dir and --web-root.
Use <command> --help for options on other commands.
Self-update is not implemented. Replace the executable and restart:
install -m 0755 new/plainapp /usr/local/bin/plainapp
sudo systemctl restart plainappsudo plainapp uninstall removes the unit, the certificate and the data directory. Pass --keep-config and --keep-data to retain them.
A v* tag builds the frontend from plain-desktop, compiles it into plainapp for Linux amd64 and arm64, and attaches the archives to the GitHub release.
Keep plain-core next to plain-desktop or plain-app. In the consumer repository:
./scripts/core local # Use local plain-core
./scripts/core local --off # Stop using local paths
./scripts/core update # Lock this project to the latest remote commit