惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
The GitHub Blog
The GitHub Blog
云风的 BLOG
云风的 BLOG
Engineering at Meta
Engineering at Meta
Google DeepMind News
Google DeepMind News
aimingoo的专栏
aimingoo的专栏
Recent Announcements
Recent Announcements
A
About on SuperTechFans
U
Unit 42
MyScale Blog
MyScale Blog
J
Java Code Geeks
博客园_首页
Blog — PlanetScale
Blog — PlanetScale
D
Docker
Microsoft Azure Blog
Microsoft Azure Blog
博客园 - 司徒正美
量子位
月光博客
月光博客
G
Google Developers Blog
V
V2EX
博客园 - 聂微东
宝玉的分享
宝玉的分享
IT之家
IT之家
Vercel News
Vercel News

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Privacy-first mind mapping app. Part 7: Owning Your Workflow
Kornel Maraz · 2026-05-12 · via DEV Community

Most posts about self‑hosting or indie projects drift into marketing speak. This one doesn’t. This is simply how I build and ship MindMapVault — the real workflow, the real tools, and the real reasons behind them.

This chapter is about ownership. Not in the corporate sense, but in the “I want to understand and control my own tools” sense. If you’re building something solo, or experimenting with automation, or trying to level up your confidence as a developer, this might resonate.

Why I Self‑Host My Git Forge

I run a self‑hosted Forgejo instance on my NAS with a runner. Not because GitHub or GitLab are bad — they’re great — but because I know myself.

Sometimes I move fast.

Sometimes I forget things.

Sometimes I push .env values straight into a repo.

Keeping the forge and runner physically close to me solves that. It gives me:

  • control (no accidental secrets leaving my network)
  • convenience (fast CI, predictable environment)
  • confidence (I can experiment without worrying about leaking something)

And honestly? I just like the project. Forgejo is clean, lightweight, and fits the “local‑first” philosophy behind MindMapVault.

What I use it for

  • another Git remote
  • CI/CD workflows
  • automated checks
  • release builds
  • experiments that I don’t want to run on a public cloud

It’s not about paranoia. It’s about craftsmanship. When you own the forge, you own the workflow.

Automation Makes You a Better Developer (Especially Solo)

When you’re working alone, automation isn’t a luxury — it’s a survival mechanism.

My Forgejo runner handles:

  • type‑checking
  • offline‑parity checks
  • desktop build pipelines
  • release asset uploads
  • version bumping
  • changelog generation

This means I can focus on the actual product instead of the glue.

And here’s the interesting part:
AI tools like GitHub Copilot Agents changed the way I commit.

Not by writing code for me — but by making me more disciplined:

  • commits became larger but more coherent
  • changelogs became more structured
  • documentation became essential, not optional
  • project planning became a real process, not a TODO file

When AI helps you move faster, the meta‑work (planning, documenting, structuring) becomes even more important. Otherwise you drown in your own velocity.

Production Hosting: Simple, Predictable, Mine

I host production on a Czech community VPS: vpsFree.cz. It’s stable, affordable, and gives me the right balance of control and simplicity.

The setup is intentionally minimal:

  • backend (Rust)
  • Postgres
  • MinIO
  • Cloudflare Tunnel
  • Cloudflare Pages for the frontend

All running via a single docker-compose.yml, all credentials in a single .env file. when you are ready zyou just run docker compose up -d and in 6 seconds the new version, fix is up in production.

# ─────────────────────────────────────────────────────────────────────────────
# MindMapVault — local dev compose
# Manages the backend plus the local RustFS and PostgreSQL dependencies.
# Use `docker compose up` / `docker compose up --build`.
# ─────────────────────────────────────────────────────────────────────────────
services:
  minio:
    image: minio/minio:latest
    container_name: mindmapvault-minio
    restart: unless-stopped
    networks:
      - cryptmind
    ulimits:
      nofile:
        soft: 65535
        hard: 65535
    command: server /data --console-address ":9001"
    environment:
      MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:-minioadmin}
      MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY:-minioadmin}
    ports:
      - "${MINIO_API_PORT:-9000}:9000"
      - "${MINIO_CONSOLE_PORT:-9001}:9001"
    volumes:
      - minio-data:/data
    healthcheck:
      test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:9000/minio/health/live >/dev/null || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 12
      start_period: 10s

  postgres:
    image: postgres:16
    container_name: mindmapvault-postgres
    restart: unless-stopped
    networks:
      - cryptmind
    ports:
      - "${POSTGRES_PORT:-5432}:5432"
    environment:
      POSTGRES_USER: ${POSTGRES_USER:-postgres}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
      POSTGRES_DB: ${POSTGRES_DB:-cryptmind}
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-cryptmind}"]
      interval: 10s
      timeout: 5s
      retries: 12
      start_period: 10s

  backend:
    build:
      context: .
      dockerfile: backend/Dockerfile.local
    container_name: mindmapvault-backend
    restart: unless-stopped
    networks:
      - cryptmind
    depends_on:
      minio:
        condition: service_healthy
      mongodb:
        condition: service_healthy
      postgres:
        condition: service_healthy
    ports:
      - "${BACKEND_PORT:-8090}:8090"
    volumes:
      - stoolap-data:/data
    environment:
      HOST: "0.0.0.0"
      PORT: "8090"
      MALLOC_ARENA_MAX: "2"
      MALLOC_TRIM_THRESHOLD_: "131072"
      MALLOC_MMAP_THRESHOLD_: "131072"
      MALLOC_CONF: ${MALLOC_CONF:-background_thread:true,narenas:1,dirty_decay_ms:0,muzzy_decay_ms:0,metadata_thp:disabled}
      DB_ENGINE: ${DB_ENGINE:-mongodb}
      POSTGRES_DSN: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/${POSTGRES_DB:-cryptmind}
      MINIO_ENDPOINT: http://minio:9000
      MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:-minioadmin}
      MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:-minioadmin}
      MINIO_BUCKET: ${MINIO_BUCKET:-mindmapvault-maps}
      MINIO_PUBLIC_ENDPOINT: ${MINIO_PUBLIC_ENDPOINT:-http://127.0.0.1:9000}
      MINIO_REGION: ${MINIO_REGION:-us-east-1}
      MINIO_PRESIGN_EXPIRY_SECS: ${MINIO_PRESIGN_EXPIRY_SECS:-3600}
      JWT_SECRET: ${JWT_SECRET}
      JWT_ACCESS_EXPIRY_SECS: ${JWT_ACCESS_EXPIRY_SECS:-900}
      JWT_REFRESH_EXPIRY_SECS: ${JWT_REFRESH_EXPIRY_SECS:-2592000}
      STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY:-}
      STRIPE_PUBLISHABLE_KEY: ${STRIPE_PUBLISHABLE_KEY:-}
      STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET:-}
      STRIPE_PRICE_PAID_YEARLY_ID: ${STRIPE_PRICE_PAID_YEARLY_ID:-}
      STRIPE_CHECKOUT_SUCCESS_URL: ${STRIPE_CHECKOUT_SUCCESS_URL:-https://mindmapvault.com/vaults}
      STRIPE_CHECKOUT_CANCEL_URL: ${STRIPE_CHECKOUT_CANCEL_URL:-https://mindmapvault.com/vaults}
      STRIPE_PORTAL_RETURN_URL: ${STRIPE_PORTAL_RETURN_URL:-https://mindmapvault.com/vaults}
      DISABLE_APP_LOGIN_TURNSTILE: ${DISABLE_APP_LOGIN_TURNSTILE:-false}
      ENABLE_DIAGNOSTICS_ROUTES: ${ENABLE_DIAGNOSTICS_ROUTES:-false}
      ENABLE_PPROF: ${ENABLE_PPROF:-false}
      PPROF_DURATION_SECS: ${PPROF_DURATION_SECS:-90}
      CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-http://localhost:5173,http://tauri.localhost,https://tauri.localhost}
      RUST_LOG: ${RUST_LOG:-backend=info,tower_http=info}
    healthcheck:
      test: ["CMD-SHELL", "curl -sf http://localhost:8090/health || exit 1"]
      interval: 15s
      timeout: 5s
      retries: 5
      start_period: 10s

cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: mindmapvault-cloudflared
    restart: unless-stopped
    command: tunnel --no-autoupdate run
    depends_on:
      backend:
        condition: service_healthy
    environment:
      TUNNEL_TOKEN: ${TUNNEL_TOKEN}
    networks:
      - mindmapvault-network

volumes:
  minio-data:
    name: cryptmind-minio-data
  postgres-data:
    name: cryptmind-postgres-data

networks:
  cryptmind:
    name: cryptmind-network

Enter fullscreen mode Exit fullscreen mode

then the docker ps looks like this:

CONTAINER ID   NAME                       CPU %     MEM USAGE / LIMIT   MEM %     NET I/O           BLOCK I/O   PIDS
468768ef5c79   mindmapvault-backend       0.00%     31.55MiB / 4GiB     0.77%     26.7MB / 4.73MB   0B / 0B     9
06c14906524a   mindmapvault-postgres      0.00%     31.82MiB / 4GiB     0.78%     2.17MB / 28.7MB   0B / 0B     7
d52f573bcdef   mindmapvault-minio         3.68%     86.56MiB / 4GiB     2.11%     3.65MB / 10.7MB   0B / 0B     13
27fcb47af8c7   mindmapvault-cloudflared   0.19%     16.31MiB / 4GiB     0.40%     132MB / 212MB     0B / 0B     13

Enter fullscreen mode Exit fullscreen mode

No Azure.

No Firebase.

No vendor lock‑in.

Just a clean, reproducible, auditable setup.

Open‑Sourcing the Local‑First Version

And when I have what to show then I pushed the FOSS version of MindMapVault here:

https://github.com/mindmapvault/mindmapvault-foss

It’s a local‑first, privacy‑focused, offline‑capable mind‑mapping desktop app.

  • No cloud.
  • No telemetry.
  • No account.

If you review the code and see weak spots, please comment directly. I welcome criticism — it’s the only way a solo project grows.

GitHub Wiki & Pages — Free Tools Many Developers Underuse

The GitHub Wiki is a perfect home for these research notes once they mature.
It’s Markdown-native, versioned, and acts as a clean documentation hub.
I even use it as a lightweight blog for deeper technical posts.
GitHub Pages hosts the interactive MindMapVault demo — free, fast, and frictionless.
Together, Wiki + Pages + Actions + runners make GitHub a full publishing and documentation platform.

What this gives you for free:

  • Wiki for architecture docs
  • Pages for interactive JS demos
  • Actions for CI
  • Runners for automation
  • A complete developer ecosystem

Research First — Write It Down Before You Build

Before implementing any feature, I create a small Markdown mockup in research/ or notes/.
It’s not a spec — just a quick outline of goals, constraints, and what “good” looks like.
This keeps the project coherent and gives Copilot the right context.
Even a rough sketch prevents future architectural mistakes.
For MindMapVault, every complex feature starts as a simple .md file.

Why this helps:

  • clarifies goals early
  • reduces rework
  • gives Copilot better context
  • documents decisions
  • keeps ideas discoverable
Goals
- Allow arbitrary file attachments (many per map) without transferring plaintext to server in encrypted mode.
- Use per-map buckets or per-map prefixes to keep attachments discoverable and scannable.
- Lazy-download UX: file blobs are fetched only when the user requests/downloads them.
- Provide secure presigned URLs for direct client upload/download while the server maintains metadata, access control, and versioning.

High-level design
- Storage layout (S3/MinIO recommended):
  - Bucket-per-map: `cryptmind-<map-id>` OR
  - Single bucket + prefix: `maps/<map-id>/attachments/<attachment-uuid>` (preferred if many buckets are undesirable)
- Object keys: `attachments/<attachment-uuid>/<sanitized-filename>` or `attachments/<timestamp>-<uuid>-<name>`
- Versioning: enable S3 versioning where possible; store `s3_version_id` in DB on complete.

Security & encryption
- Encrypted mode (server never sees plaintext or DEKs):
  - Client-side: encrypt attachment (AES-GCM or AES-SIV) on the client using the same client-side DEK management as mindmaps.
  - Send `encrypted: true` and minimal `encryption_metadata` (algorithm, non-secret IV/nonce, tag if detached) to server when initiating upload. Do NOT send DEKs.
  - Server returns presigned upload URL or accepts proxied upload; backend stores opaque object and writes metadata to DB.
  - On download, server issues presigned GET; client downloads then decrypts locally.
- Plaintext mode: files uploaded without encryption; server may perform virus scanning or content checks per policy.

Database schema (maybe)
- attachments
  - id: UUID (PK)
  - map_id: UUID (FK)
  - node_id: UUID (nullable) — attach to a node/note if applicable
  - name: TEXT (original filename)
  - sanitized_name: TEXT
  - content_type: TEXT
  - size_bytes: INTEGER
  - s3_key: TEXT
  - s3_version_id: TEXT (nullable)
  - uploaded_by: UUID (user id)
  - uploaded_at: TIMESTAMP
  - encrypted: BOOLEAN
  - encryption_meta: JSON (algorithm, iv, tag — non-secret values only)
  - checksum_sha256: TEXT (optional)
  - status: ENUM('pending','available','deleted')

Enter fullscreen mode Exit fullscreen mode

Example: A Real Changelog Entry

This is what a typical release looks like now — structured, explicit, and automation‑friendly:

**[0.3.27] – 2026‑05‑03**

**Changed**
- Editor UX / Node Icons — Restored full node icon workflow in `MindMapEditor.tsx`:
  - toolbar icon picker
  - context‑menu icon action
  - keyboard shortcut `I`
  - inline icon rendering
  - multi‑select icon toggling
  - help panel + status bar hints
- Editor Components — Added reusable icon picker infrastructure (`MindMapIconPicker.tsx`, `DynamicLucideIcon.tsx`).
- CI / Release Automation — Modernized `.github/workflows/desktop-build.yml`:
  - upgraded actions
  - replaced deprecated upload steps
  - switched to Corepack for `pnpm`
  - kept `$GITHUB_OUTPUT` usage

**Validation**
- `pnpm exec tsc --noEmit` → clean
- `node scripts/check_frontend_offline_parity.mjs` → passed

Enter fullscreen mode Exit fullscreen mode

This is the kind of detail that makes automation reliable and future‑you grateful.