thandClaude Opus 4.7 7bce6a3a36 v2.0.12 — support channel: relay + hyprcrd plugin
Central support desk for the Oumta project. Tickets arrive through a
self-hostable support-relay (public form, SMTP out), hyprcrd pulls
them in as cards, replies go back out through the relay, and KB
fragments are plain hyprcrd cards so the board + search + tags
machinery handles them for free.

support-relay/ (standalone PHP/SQLite service)
- Public form + submit endpoint with IP rate-limit, honeypot, and an
  email-matched reply path so users can follow up by typing their
  OUMTA-n issue id — mismatched email falls back to a fresh ticket
  so a hostile submitter can't hijack someone else's thread.
- One-shot setup wizard configures branding, issue-id prefix, public
  URL, mail footer template, SMTP creds (stored AES-256-GCM at rest),
  and generates the X-Support-Key API token shown exactly once.
- Hyprcrd-facing API: GET /api/hyprcrd/tickets?since=<iso>, POST
  …/reply (sends via SMTP, flips status to answered), PATCH
  …/status, DELETE, GET /ping, plus GET/POST /api/hyprcrd/settings
  exposing brand/public_url/mail_footer for post-setup edits.
- Minimal dependency-free SMTP client covering STARTTLS/TLS/none,
  AUTH LOGIN, RFC 2047 subject encoding, dot-stuffing. Extended to
  emit multipart/alternative (plain+HTML) + multipart/related so
  outbound mails carry the branded logo via Content-ID embedding;
  plain-text fallback stays intact for older clients.
- Mail footer template supports {brand}, {issue_id}, {public_url}
  placeholders. Lines with an empty placeholder are dropped, so the
  "Reply via …" line vanishes cleanly when no public URL is set.
- Favicon on all public pages. Logo (SVG by default, PNG preferred
  when both exist) picked up from logo2026col.* at send time.
- Docker: Dockerfile + docker-compose.yml for a stand-alone test
  container, plus a support-relay service in the main compose
  (profile "support") so hyprcrd can reach it at http://support-relay
  without host-networking shenanigans.

public/plugins/support (hyprcrd client)
- Full-screen Support overlay: ticket list (status-filterable),
  active ticket thread, reply composer with KB-fragment search +
  click-to-insert, "Save as fragment" button, "Close ticket" shortcut.
- KB fragments are regular cards in a configurable bucket. The
  composer just adds a fast search-and-insert path on top.
- Sync-tick hook auto-polls the relay alongside the existing CalDAV
  + relay-mail cycle.
- Settings tab in the main overlay: relay URL, API key, inbox / KB
  bucket pickers, connection test, and a "Relay config" subsection
  that round-trips to /api/hyprcrd/settings so the mail footer and
  branding can be edited without touching the relay's DB.
- Local cache tables support_tickets + support_replies mirror the
  relay's authoritative data keyed by relay_id.

Tooling
- dev.sh now opts into both relay + support profiles.
- dev-down.sh wipes support-relay volumes and bind-mount data for a
  clean re-setup. Hyprcrd + relay data is preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 10:41:19 +02:00
bak
2026-04-06 18:09:04 +02:00
bak
2026-04-06 18:09:04 +02:00
bak
2026-04-06 18:09:04 +02:00
bak
2026-04-07 08:20:48 +02:00

hyprcrd

A self-hosted card/note manager with markdown, tags, image galleries, and Obsidian-compatible export.

Requirements

  • PHP 8.3+
  • Apache with mod_rewrite
  • PHP extensions: pdo_sqlite, zip

No external dependencies. All assets are bundled locally.

Quick Start (Docker)

git clone https://git.uesome.de/tanztee/hyprcrd.git
cd hyprcrd
docker build -t hyprcrd .
docker run -d -p 8080:80 -v hyprcrd-data:/var/www/html/data hyprcrd

Open http://localhost:8080 and set your password on first visit.

The -v hyprcrd-data:/var/www/html/data flag persists the database across container restarts.

Via docker compose

If you prefer compose, the bundled docker-compose.yml starts the app alone by default:

docker compose up -d     # just the app on :8088

To also bring up a local relay (for multi-tenant messaging / collab / chat), opt in via the relay profile:

docker compose --profile relay up -d    # app on :8088 + relay on :8081

Most installs talk to a remote relay, so the app-only default keeps the local setup minimal.

Manual Installation

1. Clone

git clone https://git.uesome.de/tanztee/hyprcrd.git
cd hyprcrd

2. Set document root

Point your web server's document root to the public/ directory.

Apache vhost example:

<VirtualHost *:80>
    ServerName hyprcrd.local
    DocumentRoot /path/to/hyprcrd/public

    <Directory /path/to/hyprcrd/public>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

Make sure mod_rewrite is enabled:

a2enmod rewrite
systemctl restart apache2

3. Set permissions

mkdir -p data public/uploads
chmod 755 data
chmod 777 public/uploads

The web server user (e.g. www-data) needs write access to both directories.

4. PHP settings (optional)

For larger image uploads, adjust in php.ini:

upload_max_filesize = 20M
post_max_size = 22M

5. Open and set password

Navigate to your URL. On first visit, you'll be prompted to set a password (min. 4 characters). That's it.

Directory Structure

hyprcrd/
├── config.php              # App constants (auto-configured)
├── Dockerfile
├── docker-entrypoint.sh
├── dist.sh                 # Build distribution ZIP
├── src/
│   ├── bootstrap.php       # Initialization + plugin discovery
│   ├── Database.php        # SQLite schema & migrations
│   ├── PluginManager.php   # Plugin scanner & asset/slot renderer
│   ├── CalDavClient.php    # CalDAV HTTP client + iCal parser
│   └── CalDavSync.php      # Bidirectional CalDAV sync engine
├── public/                 # Document root
│   ├── index.php           # Entry point & API
│   ├── .htaccess           # URL rewriting
│   ├── css/
│   │   ├── style.css       # App styles
│   │   ├── tailwind.out.css # Generated Tailwind utilities
│   │   ├── fonts.css       # Local @font-face rules
│   │   └── vendor/         # Phosphor Icons CSS + fonts
│   ├── fonts/              # JetBrains Mono & Silkscreen
│   ├── js/
│   │   ├── hyprcrd.js      # Plugin API (window.hyprcrd, sync-loaded)
│   │   ├── app.js          # Orchestrator
│   │   ├── modules/        # Core feature modules
│   │   └── vendor/         # marked.js, DOMPurify
│   ├── plugins/            # Optional, drop-in plugin folders
│   │   ├── canvas/         # ASCII art canvas editor
│   │   ├── chat/           # Encrypted relay chat
│   │   ├── collab-buckets/ # Collaborative bucket sync
│   │   ├── email/          # Email overlay
│   │   ├── gitea/          # Gitea issue integration
│   │   ├── logseq-import/  # Logseq markdown importer
│   │   ├── slideshow/      # Presentation mode
│   │   └── tests/          # UI + state test runner
│   └── uploads/            # User media (writable)
├── electron/               # Electron desktop app wrapper
│   ├── main.js             # Spawns PHP, creates BrowserWindow
│   ├── router.php          # PHP built-in server router
│   ├── fetch-php.sh        # Downloads static PHP binaries
│   └── php-bin/            # Platform-specific PHP binaries
├── relay/                  # Relay service (multi-tenant messaging + collab)
│   ├── index.php           # Entry point + API dispatch
│   ├── inc/                # Backend modules (bootstrap, crypto, auth, routes)
│   ├── ui/                 # Frontend (layout.php, style.css, app.js)
│   ├── .htaccess           # URL rewriting + data protection
│   ├── Dockerfile
│   └── dist.sh             # Build relay distribution ZIP
└── data/
    └── hyprcrd.db          # SQLite database (auto-created)

Features

  • Cards with markdown body, color, tags, and position
  • Buckets for organizing cards into groups
  • Tags written as #tagname or hierarchical #parent/child in card body, extracted automatically
  • Markdown with [[Card Title]] wiki-links, ```gallery image galleries, ```tree list views, ```ascii art canvas (via canvas plugin)
  • Search combining text and #tag filters (e.g. #project meeting notes)
  • Assets gallery (images, audio, PDFs) with orphan detection and fullscreen viewer
  • Split view showing tag-related and content-related cards
  • Import/Export as JSON+media ZIP or Obsidian/Logseq-compatible markdown vault
  • Drag & drop card reordering and grouping
  • Calendar with month view, journal entries, appointments, and day popup
  • CalDAV sync with ownCloud/Nextcloud (bidirectional, #termin tagged cards)
  • Inline calculator (?3+5 → 8, 34+56? → 34+56=90)
  • Snippets (?dat, ?time, ?now, ?todo, ?table, ?code, ...)
  • Due dates with overdue notifications and pulsing cards
  • Relay for mobile note capture and card sharing between instances
  • Notification banner for upcoming events, birthdays, overdue tasks
  • Board split view with side-by-side preview
  • Superview compact multi-column list with hover cards
  • Super-buckets — nest buckets one level deep with a dropdown for children
  • Card groups — drag-to-group, join multiple cards, dissolve
  • Context menu — color, tags, copy text, mark done, join cards, make ticket (gitea)
  • Find & replace — in-card (Ctrl+H) and across all cards (Ctrl+Shift+H via search)
  • Arrow key navigation on the board with Enter to open
  • PDF upload in the editor toolbar
  • Keyboard shortcuts: Ctrl+K search, F1 help, ? shortcut reference, arrow keys, 1–9 bucket switch
  • Electron desktop app — standalone AppImage/DMG/NSIS with bundled PHP, no server needed

Plugins

hyprcrd has a drop-in plugin system. A plugin is just a folder under public/plugins/ with a plugin.json manifest. The core scans the directory at startup, loads each plugin's CSS/JS/HTML, and the plugin self-registers against window.hyprcrd. Plugins are entirely optional — removing a plugin folder removes the feature, with no code changes anywhere else.

Bundled plugins

  • canvas/ — ASCII art canvas editor. Adds a toolbar button + ```ascii fenced block. Draw with brush, line, rectangle, ellipse, fill, and text tools using a monospace character grid.
  • chat/ — Encrypted chat between relay tenants. Footer flyout with delivery + read receipts.
  • collab-buckets/ — Collaborative buckets shared between relay tenants with exclusive card locking and E2E encrypted sync.
  • email/ — Email overlay (toggled with M key).
  • gitea/ — Gitea integration: create issues from cards, link to repos.
  • slideshow/ — Slideshow presentation mode for cards with slide-image blocks.
  • logseq-import/ — Import Logseq markdown journals and pages.
  • tests/ — UI + state test runner (40+ test files). Loaded only when Ctrl+Shift+Alt+T is pressed, so production users pay no cost for it.

Plugin layout

public/plugins/myplugin/
├── plugin.json     # Manifest (required)
├── myplugin.js     # Entry point (loaded as ES module)
├── myplugin.css    # Optional styles
├── myplugin.html   # Optional HTML fragment (overlays, modals, etc.)
└── backend.php     # Optional PHP hooks

plugin.json:

{
    "name": "myplugin",
    "version": "1.0.0",
    "description": "What this plugin does",
    "frontend": {
        "js":   ["myplugin.js"],
        "css":  ["myplugin.css"],
        "html": ["myplugin.html"]
    },
    "backend": "backend.php"
}

Plugin API (window.hyprcrd)

Loaded synchronously before any module, so plugins can register handlers immediately:

Namespace Methods Purpose
hyprcrd.editor registerCommand(name, fn), getCommand(name), api.{body, insertMd, updatePreview} Add toolbar commands. The data-md="<name>" button dispatches to the registered handler. api is set after the editor initializes.
hyprcrd.markdown registerFencedBlock(lang, fn), registerPreProcessor(fn), registerPostProcessor(fn) Add ```<lang> block renderers (returns HTML, passes through DOMPurify). Pre/post processors run on the source/HTML around marked.parse().
hyprcrd.hooks on(name, fn), off(name, fn), emit(name, data) Generic event bus. Use for cross-plugin or core→plugin signaling.
hyprcrd.util $(idOrSel), $$(sel), esc(s) Lightweight DOM/escape helpers so plugins don't need to import core modules.
hyprcrd.registerPlugin(meta) — Register the plugin's metadata for introspection.

Minimal plugin example

public/plugins/hello/plugin.json:

{
    "name": "hello",
    "version": "1.0.0",
    "description": "Inserts a greeting",
    "frontend": { "js": ["hello.js"] }
}

public/plugins/hello/hello.js:

const h = window.hyprcrd;

h.editor.registerCommand('hello', () => {
    h.editor.api.insertMd('Hello, world! ', '', '');
});

// Inject a toolbar button
const btn = document.createElement('button');
btn.dataset.md = 'hello';
btn.className = 'toolbar-btn';
btn.title = 'Hello';
btn.textContent = '👋';
document.getElementById('md-toolbar').appendChild(btn);

h.registerPlugin({ name: 'hello', version: '1.0.0' });

That's the entire plugin. Drop the folder in, reload, the button appears.

Backend hooks (PHP)

If a plugin needs server-side code, set "backend": "backend.php" in the manifest. The file is require_once'd during bootstrap. Backend plugins can use PluginManager::instance()->addToSlot('slot-name', '<html>') to inject HTML into named slots in index.php (e.g. md-toolbar).

Removing a plugin

rm -rf public/plugins/canvas

That's it. The toolbar button, overlay, CSS, and markdown handler all disappear together. The core has no awareness of any specific plugin.

Data & Backup

All data lives in two places:

  • data/hyprcrd.db — SQLite database (cards, buckets, settings)
  • public/uploads/ — uploaded images and audio

Back up both to preserve everything. The database auto-migrates on startup.

Export Formats

  • JSON export (export button) — full backup with media, re-importable
  • Markdown vault (vault button) — Obsidian/Logseq-compatible structure:
    Bucket Name/
      Card Title.md      # YAML frontmatter + markdown body
    attachments/
      image.jpg
    

CalDAV Sync (ownCloud / Nextcloud)

hyprcrd can sync appointment cards with any CalDAV server (tested with ownCloud 10.15.3.0).

Setup

  1. Open the calendar (button in header)
  2. Click the gear icon in the calendar header
  3. Enter your CalDAV URL, username, and app password:
    URL: https://cloud.example.com/remote.php/dav/calendars/your-user/personal/
    Username: your-user
    Password: your-app-password
    
  4. Click test to verify the connection
  5. Click save

How it works

  • Cards tagged with #termin are synced as calendar events
  • The appointment card template maps to iCalendar fields:
    Title: 2026-04-15 — Team Meeting
    
    **Zeit:** 14:30-15:30
    **Ort:** Room 42
    **Teilnehmer:** Alice, Bob
    
    ---
    
    **Notizen:**
    Agenda items here
    
    #termin
    
  • Sync is bidirectional: local changes push to the server, remote changes pull to hyprcrd
  • Conflict resolution: last-modified timestamp wins
  • Deleted cards: tracked via tombstones, deleted on the server on next sync
  • Auto-sync: triggers when opening the calendar (max once per minute)
  • Manual sync: click the cloud icon in the calendar header

Security

  • Credentials are encrypted at rest (AES-256-CBC) in the SQLite database
  • All sync operations run server-side (PHP/cURL), no client-side credential exposure
  • Supports HTTPS with certificate verification

Calendar

The built-in calendar shows cards on their dates:

  • Journal cards (title = 2026-04-08) appear on that date
  • Appointment cards (title = 2026-04-08 — Subject) appear on their date
  • Other cards fall back to their creation date
  • Click a day to see all cards in a popup
  • Double-click or use + journal / + termin to create new entries
  • Appointment template includes Zeit, Ort, Teilnehmer, Notizen fields
  • Navigate months with arrow buttons, today jumps to current date

Vault Key (Multi-Instance Encryption)

When running multiple hyprcrd instances that share CalDAV credentials, you need a shared encryption key.

Setup

  1. Go to Settings > Account > Vault Key
  2. Enter the same key (min 8 characters) on all instances
  3. Click save

How it works

  • The vault key encrypts/decrypts CalDAV credentials stored in the database
  • Key derivation: SHA-256(vault_key) → deterministic, same on all instances
  • Without a vault key, encryption falls back to the login password hash (single-instance only)
  • Changing the vault key automatically re-encrypts CalDAV credentials
  • Changing the login password does NOT affect encryption when a vault key is set

Important

  • The vault key is stored in plaintext in each instance's database — it is NOT synced
  • You must enter it manually on each connected instance

Relay

A self-hosted multi-tenant message relay for hyprcrd. Multiple users share one relay instance — each one has their own login, their own client encryption secret, and their own inbox. Cards (and in the future: encrypted chat messages) can be sent between tenants on the same relay.

Full documentation: relay/README.md

Architecture

[hyprcrd alice] ──push──┐                         ┌──poll── [hyprcrd alice]
                        ├──► [Relay] ◄────────────┤
[hyprcrd bob]   ──push──┘   tenants: alice, bob   └──poll── [hyprcrd bob]

      sender encrypts → relay decrypts with sender's secret →
      re-encrypts with shared inter-tenant key → stores →
      on recipient poll: decrypts → re-encrypts with recipient's secret

Deploy

Webspace (any PHP host with mod_rewrite and pdo_sqlite):

cd relay
./dist.sh
# upload hyprcrd-relay.zip and extract into your webspace

Docker:

cd relay
docker build -t hyprcrd-relay .
docker run -d -p 8081:80 -v relay-data:/var/www/html/data hyprcrd-relay

The data/ directory is created on first request and contains the SQLite DB plus the master encryption key (chmod 600, blocked from web access via .htaccess).

Development (both together)

./dev.sh

Starts hyprcrd on http://localhost:8088 and the relay on http://localhost:8081 in the same Docker network. Source files are bind-mounted — changes are live without rebuilding. From inside the hyprcrd container, the relay is reachable at http://relay:80.

Under the hood this runs docker compose --profile relay up --build. The relay service lives behind the relay compose profile, so a plain docker compose up brings up only the app — handy when you're developing against a remote relay.

Setup workflow

  1. Open the relay URL from a desktop browser — the setup wizard is desktop-friendly
  2. Create the first admin account: username + password (UI login)
    • shared secret (client encryption)
  3. As admin, click the users tab and create accounts for any additional users you want to give relay access to
  4. Each user enters their credentials in their own hyprcrd: Settings → Relay → relay URL + username + shared secret
  5. To send a card from one tenant to another, use the relay UI's compose form (recipient picker) — or push cards from hyprcrd

How it works (high level)

  • Per-tenant inboxes: each user only sees their own messages
  • Cross-tenant routing: send a card to another tenant on the same relay; the relay decrypts with the sender's secret, re-encrypts with a shared inter-tenant key for storage, then re-encrypts with the recipient's secret on delivery
  • At-rest encryption: every sensitive value in the DB (tenant secrets, the inter-tenant key, message bodies) is encrypted. The master key lives outside the DB in a chmod-600 file
  • 24-hour ephemeral: messages and attachments auto-delete after a day. Cards extracted in hyprcrd are the long-term store
  • Encrypted chat: tenant-to-tenant chat with delivery + read receipts (via chat plugin). Per-pair E2E key exchange is on the roadmap

Relay Security

  • Per-tenant bcrypt-hashed passwords + secrets
  • Master key encryption for tenant secrets and the inter-tenant key at rest
  • HttpOnly + SameSite=Strict session cookies
  • Rate-limited login (5 attempts/minute per IP)
  • AES-256-CBC for all message encryption
  • Security headers (X-Frame-Options, HSTS, X-Content-Type-Options)
  • data/ directory blocked from direct download via .htaccess
  • Request size limited to 256KB (15MB for attachments)
  • Last-admin protection prevents accidental lockout

For the full security model (including what's NOT protected and the roadmap to strict E2E for chat), see relay/README.md.

Migrating from the old single-tenant relay

When you upgrade an existing relay installation, the new code automatically detects the legacy schema and drops the old messages and attachments tables (the data is 24h-ephemeral anyway). The setup wizard appears on your next visit. You'll need to:

  1. Run through the desktop setup wizard to create the first admin
  2. Recreate any other tenant accounts that need access
  3. On every connected hyprcrd, update Settings → Relay to add the new username field and the new shared secret

The old relay_key was bcrypt-hashed and cannot be recovered, so all client secrets need to be reissued.

Electron Desktop App

hyprcrd can run as a standalone desktop app via Electron — no web server needed.

cd electron
./fetch-php.sh     # download static PHP binary (~20MB)
npm install
npm start          # dev mode
npm run dist       # build AppImage / DMG / NSIS

The app bundles a static PHP binary per platform and stores user data (database + uploads) outside the app bundle:

  • Linux: ~/.config/hyprcrd/data/
  • macOS: ~/Library/Application Support/hyprcrd/data/
  • Windows: %APPDATA%/hyprcrd/data/

See electron/README.md for details.

Rebuilding Tailwind CSS

After changing Tailwind utility classes in HTML or JS files:

# Download standalone CLI (once)
curl -sL https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 -o tailwindcss && chmod +x tailwindcss

# Build (includes plugins so plugin HTML/JS contributes utility classes)
./tailwindcss --input public/css/tailwind.css --output public/css/tailwind.out.css --content "public/index.php,public/js/**/*.js,public/plugins/**/*.{js,html}" --minify

Security

  • Bcrypt password hashing
  • CSRF protection on all mutations
  • Rate-limited login (5 attempts/60s)
  • Upload directory blocks PHP execution
  • DOMPurify sanitizes all rendered markdown
  • Security headers (X-Frame-Options, HSTS, etc.)
  • AES-256-CBC encryption for CalDAV credentials and Relay messages
  • Vault key system for multi-instance CalDAV encryption (independent of login password)
  • Relay: separate password + shared secret, rate-limited, client-side encryption

License

MIT

S
Description
No description provided
Readme
8.7 MiB
0 Stars 3 Watchers 0 Forks
Languages
JavaScript 53%
PHP 30.2%
CSS 11.6%
HTML 4.7%
Shell 0.4%