{"id":49557924,"url":"https://github.com/thezupzup/nexanote","last_synced_at":"2026-05-03T06:14:02.074Z","repository":{"id":350694775,"uuid":"1207910505","full_name":"TheZupZup/NexaNote","owner":"TheZupZup","description":"Self-hosted note-taking built for handwriting, stylus input, and privacy.","archived":false,"fork":false,"pushed_at":"2026-04-27T12:27:45.000Z","size":225,"stargazers_count":0,"open_issues_count":18,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-27T14:17:44.637Z","etag":null,"topics":["cross-platform","drawing","evernote-alternative","handwriting","joplin-alternative","linux","nas","note-taking","notes","obsidian-alternative","open-source","privacy","productivity","python","self-hosted","stylus","sync","tablet"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TheZupZup.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-11T15:12:29.000Z","updated_at":"2026-04-27T12:27:49.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/TheZupZup/NexaNote","commit_stats":null,"previous_names":["thezupzup/nexanote"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/TheZupZup/NexaNote","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheZupZup%2FNexaNote","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheZupZup%2FNexaNote/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheZupZup%2FNexaNote/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheZupZup%2FNexaNote/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TheZupZup","download_url":"https://codeload.github.com/TheZupZup/NexaNote/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheZupZup%2FNexaNote/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32559753,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-03T03:21:47.309Z","status":"ssl_error","status_checked_at":"2026-05-03T03:21:43.884Z","response_time":103,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cross-platform","drawing","evernote-alternative","handwriting","joplin-alternative","linux","nas","note-taking","notes","obsidian-alternative","open-source","privacy","productivity","python","self-hosted","stylus","sync","tablet"],"created_at":"2026-05-03T06:14:01.352Z","updated_at":"2026-05-03T06:14:02.067Z","avatar_url":"https://github.com/TheZupZup.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# NexaNote\n\nOpen-source, privacy-friendly, self-hostable note-taking app with stylus support.\n\nAn alternative to Samsung Notes, OneNote and GoodNotes that respects your data.\n\n---\n## About development\n\nThis project is actively developed by me, with the help of AI tools for productivity and experimentation.\nAll design decisions and direction are fully human-driven.\n\n---\n## What works today\n\n- Typed notes stored as plain Markdown files (Obsidian-style)\n- Handwritten notes with stylus or mouse (pen, highlighter, eraser, pressure sensitivity)\n- Notebooks to organize your notes\n- WebDAV sync with your NAS, Nextcloud, or any WebDAV server\n- Offline-first — works without internet\n- Conflict resolution when editing the same note on multiple devices\n- Search by title\n- Linux desktop app (Flutter)\n- Python backend with REST API and WebDAV server\n\n## What's coming\n\n- Android app\n- PDF export\n- Handwriting OCR\n- Page templates (lined, grid, dotted)\n- End-to-end encryption\n\nSee [docs/android-roadmap.md](docs/android-roadmap.md) for the full Android \u0026 S26 Ultra plan.\n\n---\n\n## Architecture\n\nNexaNote uses two components that work together:\n\n- **Python backend** — handles storage (file-based, Markdown), REST API, and WebDAV sync server\n- **Flutter app** — the interface, runs on Linux desktop and Android (coming soon)\n\n```\nNexaNote/\n├── main.py                  # Start the backend\n├── requirements.txt\n├── nexanote/                # Python backend\n│   ├── models/              # Data models\n│   ├── storage/             # File-based store (Markdown + JSON) + SQLite migration\n│   ├── sync/                # WebDAV server + sync engine + conflict resolution\n│   └── api/                 # REST API (FastAPI)\n├── app/                     # Flutter app\n│   └── lib/\n│       ├── screens/         # UI screens\n│       ├── widgets/         # Reusable widgets (ink canvas, notes list...)\n│       └── services/        # API client, app state\n└── tests/                   # 100+ tests\n```\n\n### Storage layout (v1.0.0+)\n\nNotes are plain files on disk, Obsidian-style:\n\n```\n\u003cdata_dir\u003e/\n├── notebooks/\u003cnotebook_id\u003e.yaml      # Notebook metadata (YAML)\n├── notes/\u003cnote_id\u003e.md                # Markdown body + YAML frontmatter\n└── drawings/\u003cnote_id\u003e.json           # Stylus strokes (one file per note)\n```\n\nEach note's frontmatter carries `title`, `tags`, `created_at`, `updated_at`,\nplus the metadata the app needs (id, notebook_id, page list, sync_status).\nSingle-page notes have a clean body with no NexaNote-specific markers, so\nthey can be opened/edited directly in Obsidian or any Markdown editor.\n\nA pre-v1.0.0 SQLite database (`nexanote.db`) is detected on first startup\nand migrated automatically — the original DB is renamed to\n`nexanote.db.legacy_backup` and kept in place. See\n[`CHANGELOG.md`](CHANGELOG.md) for details.\n\n---\n\n## Getting started\n\n### Requirements\n\n- Python 3.10+\n- Flutter 3.10+\n\n### Backend\n\n```bash\ncd NexaNote\npython -m venv venv\nsource venv/bin/activate\npip install -r requirements.txt\npython main.py\n```\n\nThe backend starts two servers:\n\n| Service | URL | Purpose |\n|---------|-----|---------|\n| REST API | http://127.0.0.1:8766 | Used by the Flutter app |\n| WebDAV | http://127.0.0.1:8765 | Connect your NAS or Nextcloud |\n| API docs | http://127.0.0.1:8766/docs | Interactive Swagger UI |\n\nThe two ports work directly out of the box. If you'd rather expose the\nbackend behind a single hostname (one URL to type into the app, one\ncertificate to manage, one firewall rule), see\n[Single backend URL](#single-backend-url) below.\n\n### Flutter app\n\n```bash\ncd NexaNote/app\nflutter pub get\nflutter run -d linux\n```\n\n### Launch everything at once\n\n```bash\nbash ~/NexaNote/nexanote.sh\n```\n\nThis script starts the backend and the app automatically.\n\n### Run the backend with Docker\n\nPrefer to run the backend persistently on a NAS or always-on machine?\n\nFrom source:\n\n```bash\ndocker compose up -d --build\n```\n\nOr pull the prebuilt image from Docker Hub (no clone needed):\n\n```bash\ndocker run -d \\\n  --name nexanote-backend \\\n  -p 8766:8766 -p 8765:8765 \\\n  -v /path/on/host/nexanote-data:/data \\\n  --restart unless-stopped \\\n  thezupzup/nexanote-backend:latest\n```\n\nThis starts the backend on ports `8766` (API) and `8765` (WebDAV), with data\npersisted in the mounted volume. The image is published as a multi-arch\nmanifest (`linux/amd64` + `linux/arm64`), so x86 servers and ARM NAS units\n(Ugreen DXP, Raspberry Pi, etc.) both pull the right variant automatically.\n\nSee [docs/docker.md](docs/docker.md) for the full guide, including a NAS\n(Synology / Ugreen) compose example and multi-arch buildx instructions.\n\n---\n\n## Single backend URL\n\nBy default the backend exposes the REST API on `:8766` and the WebDAV server\non `:8765` — both still work directly and unchanged. To make life easier on\nmobile (one URL to type, one TLS cert, one firewall rule), put a reverse\nproxy in front and route:\n\n| Path on the public host | Backend target | Purpose |\n|-------------------------|----------------|---------|\n| `/`                     | `127.0.0.1:8766` | REST API |\n| `/webdav`               | `127.0.0.1:8765` | WebDAV |\n\nThe Flutter app then accepts a single base URL (e.g.\n`https://nexanote.example.com`) and derives the WebDAV URL by appending\n`/webdav`. Advanced users who run the legacy two-port deployment can still\nset the WebDAV URL explicitly in Settings → Backend; this overrides the\nderivation and is preserved across upgrades.\n\n### Nginx\n\n```nginx\nserver {\n    listen 443 ssl http2;\n    server_name nexanote.example.com;\n\n    # TLS certificates (e.g. Let's Encrypt)\n    ssl_certificate     /etc/letsencrypt/live/nexanote.example.com/fullchain.pem;\n    ssl_certificate_key /etc/letsencrypt/live/nexanote.example.com/privkey.pem;\n\n    # WebDAV — strip the /webdav prefix before forwarding so wsgidav\n    # serves its root at \"/\" as it expects.\n    location /webdav/ {\n        proxy_pass         http://127.0.0.1:8765/;\n        proxy_set_header   Host              $host;\n        proxy_set_header   X-Forwarded-For   $remote_addr;\n        proxy_set_header   X-Forwarded-Proto $scheme;\n        proxy_redirect     off;\n        client_max_body_size 0;        # allow large uploads\n        proxy_request_buffering off;   # stream PUTs straight through\n    }\n\n    # REST API — everything else.\n    location / {\n        proxy_pass         http://127.0.0.1:8766;\n        proxy_set_header   Host              $host;\n        proxy_set_header   X-Forwarded-For   $remote_addr;\n        proxy_set_header   X-Forwarded-Proto $scheme;\n    }\n}\n```\n\n### Caddy\n\n```\nnexanote.example.com {\n    handle_path /webdav/* {\n        reverse_proxy 127.0.0.1:8765\n    }\n    reverse_proxy 127.0.0.1:8766\n}\n```\n\n### Cloudflare Tunnel\n\nCloudflared forwards the request path to the origin unchanged, so the\ncleanest setup is to run a local reverse proxy (Nginx or Caddy from above)\non `127.0.0.1:80` and point the tunnel at it:\n\n```yaml\n# ~/.cloudflared/config.yml\ntunnel: \u003cyour-tunnel-id\u003e\ncredentials-file: /etc/cloudflared/\u003cyour-tunnel-id\u003e.json\n\ningress:\n  - hostname: nexanote.example.com\n    service: http://127.0.0.1:80\n  - service: http_status:404\n```\n\nIf you'd rather skip the local proxy, use two subdomains and configure the\nWebDAV URL explicitly in the app's advanced settings:\n\n```yaml\ningress:\n  - hostname: nexanote.example.com\n    service: http://127.0.0.1:8766\n  - hostname: webdav.nexanote.example.com\n    service: http://127.0.0.1:8765\n  - service: http_status:404\n```\n\nAfter configuring the proxy, point the Flutter app at\n`https://nexanote.example.com` and you're done — no second URL required\n(unless you took the two-subdomain route, in which case set the WebDAV URL\noverride in Settings → Backend).\n\n---\n\n## Sync with your NAS\n\nOnce your NAS has WebDAV enabled, open Settings in the app and enter your NAS URL and credentials. NexaNote will sync your notes automatically.\n\nTested with Ugreen NAS (UGOS Pro). Should work with any WebDAV-compatible server including Nextcloud.\n\n---\n\n## Running tests\n\n```bash\npython -m pytest tests/ -v\n```\n\n100+ tests covering models, file-based storage, SQLite → file migration, WebDAV provider, conflict resolution, and REST API.\n\n---\n\n## Contributing\n\nThe project is in early development. Contributions are welcome.\n\n| Task | Difficulty |\n|------|-----------|\n| Android app (Flutter) | Hard |\n| PDF export | Medium |\n| Page templates | Easy |\n| Handwriting OCR | Hard |\n| End-to-end encryption | Hard |\n\n1. Fork the repo\n2. Create a branch (`git checkout -b feature/my-feature`)\n3. Commit your changes\n4. Open a pull request\n\n---\n\n## License\n\n[MPL 2.0](https://www.mozilla.org/en-US/MPL/2.0/) — modifications must remain open-source, commercial use is allowed.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthezupzup%2Fnexanote","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthezupzup%2Fnexanote","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthezupzup%2Fnexanote/lists"}