340 lines
11 KiB
Markdown
340 lines
11 KiB
Markdown
# 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)
|
|
|
|
```bash
|
|
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.
|
|
|
|
## Manual Installation
|
|
|
|
### 1. Clone
|
|
|
|
```bash
|
|
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:**
|
|
|
|
```apache
|
|
<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:
|
|
|
|
```bash
|
|
a2enmod rewrite
|
|
systemctl restart apache2
|
|
```
|
|
|
|
### 3. Set permissions
|
|
|
|
```bash
|
|
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`:
|
|
|
|
```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
|
|
│ ├── Database.php # SQLite schema & migrations
|
|
│ ├── 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/
|
|
│ │ ├── app.js # Orchestrator
|
|
│ │ ├── modules/ # Feature modules
|
|
│ │ └── vendor/ # marked.js, DOMPurify
|
|
│ └── uploads/ # User media (writable)
|
|
├── relay/ # Relay service (mobile inbox + card sharing)
|
|
│ ├── index.php # Single-file API + Mobile UI
|
|
│ ├── .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
|
|
- **Search** combining text and `#tag` filters (e.g. `#project meeting notes`)
|
|
- **Image gallery** 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
|
|
- **Keyboard shortcuts**: `Ctrl+K` search, `F1` help, `Shift+scroll` zoom, arrow keys in autocomplete
|
|
|
|
## 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:
|
|
```markdown
|
|
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 minimal web service for mobile note capture and card sharing between hyprcrd instances.
|
|
|
|
### Architecture
|
|
|
|
```
|
|
[Phone/Browser] → https://relay.example.com → [Relay Service]
|
|
↓ poll
|
|
[hyprcrd (local)]
|
|
|
|
[hyprcrd A] → push card → [Relay] ← poll ← [hyprcrd B]
|
|
```
|
|
|
|
### Deploy to Webspace
|
|
|
|
```bash
|
|
cd relay
|
|
./dist.sh
|
|
```
|
|
|
|
Upload the resulting `hyprcrd-relay.zip` to your webspace and extract it. The SQLite database is created automatically on first request. Open the URL and set a password + shared secret.
|
|
|
|
Requirements: PHP 8.3+ with `pdo_sqlite`, Apache with `mod_rewrite`.
|
|
|
|
### Deploy with Docker
|
|
|
|
```bash
|
|
cd relay
|
|
docker build -t hyprcrd-relay .
|
|
docker run -d -p 8081:80 -v relay-data:/var/www/html/data hyprcrd-relay
|
|
```
|
|
|
|
### Development (both together)
|
|
|
|
```bash
|
|
./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`.
|
|
|
|
### Setup
|
|
|
|
1. Open the relay URL, set a **password** (for UI login) and a **shared secret** (for encryption)
|
|
2. In hyprcrd: **Settings > Relay** — enter the relay URL and the same shared secret
|
|
3. Click **save**
|
|
|
|
### How it works
|
|
|
|
- **Mobile inbox**: type a note on the relay UI, it arrives as a card with `#inbox` tag
|
|
- **Push to relay**: right-click any card → "push to relay" — other instances pick it up on next poll
|
|
- Messages are encrypted client-side (AES-256-CBC) before sending to the relay
|
|
- The relay only stores ciphertext — it cannot read your messages
|
|
- hyprcrd polls the relay every 45 seconds in the background
|
|
- Messages are kept on the relay for 24 hours, then auto-deleted
|
|
- Cards pushed from the same instance are not re-imported
|
|
- Enter to send, Shift+Enter for newline
|
|
|
|
### Relay Security
|
|
|
|
- Separate password (UI login) and shared secret (API + encryption)
|
|
- Bcrypt hashing for both credentials
|
|
- Rate-limited login (5 attempts/minute)
|
|
- AES-256-CBC client-side encryption — relay never sees plaintext
|
|
- Security headers (X-Frame-Options, HSTS, X-Content-Type-Options)
|
|
- SQLite database blocked from direct download via `.htaccess`
|
|
- Request size limited to 256KB
|
|
|
|
## Rebuilding Tailwind CSS
|
|
|
|
After changing Tailwind utility classes in HTML or JS files:
|
|
|
|
```bash
|
|
# Download standalone CLI (once)
|
|
curl -sL https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 -o tailwindcss && chmod +x tailwindcss
|
|
|
|
# Build
|
|
./tailwindcss --input public/css/tailwind.css --output public/css/tailwind.out.css --content "public/index.php,public/js/**/*.js" --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
|