Process.exec uses popen(cmd, "r") which creates a read-only pipe where the subprocess stdin is disconnected from the terminal. Console.readLine reads directly from the parent process stdin via fgets, fixing all interactive prompts (askConfirm, askInput, choice menus). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ultimate Notetaking, Sync & Backup System
A NixOS-based system for managing the three types of data across devices:
| Tier | Type | Examples | Sync Model |
|---|---|---|---|
| 1 | Configuration | flake.nix, dotfiles | Git (public, shareable) |
| 2 | Syncable Data | Notes, documents | Git (nb) + Syncthing |
| 3 | Large Data | Photos, videos, repos | Central backup, on-demand |
Quick Start
# One-command setup (public repo, no SSH key needed)
nix run 'git+https://git.qrty.ink/blu/pal.git'
# Or clone first, then run
git clone https://git.qrty.ink/blu/pal.git
cd pal
nix run .
Full Setup Guide
First Computer (Initial Setup)
# 1. Clone the repo
git clone https://git.qrty.ink/blu/pal.git
cd pal
# 2. Run setup (one command - includes all dependencies)
nix run .
# Or: nix develop && ./setup
# 3. Choose option [1] "New setup (first device)"
# This generates an age encryption key - SAVE IT!
# 4. (Optional) Set up SSH for push access
# Add your SSH key to Gitea: https://git.qrty.ink/user/settings/keys
# Then switch to SSH remote:
git remote set-url origin ssh://git@git.qrty.ink:2222/blu/pal.git
Additional Computers (Joining)
# One command (no SSH key needed for public repo)
nix run 'git+https://git.qrty.ink/blu/pal.git'
# Choose option [2], enter your config git URL and age key
# Or clone first:
git clone https://git.qrty.ink/blu/pal.git && cd pal
nix run . -- <config-git-url> <your-age-key>
Mobile Device (Phone/Tablet)
# On any computer that's already set up:
nix run . -- mobile
# Or: ./setup mobile
This shows a QR code for Syncthing pairing. You can also manually paste device IDs.
Just Try the Tools (No Setup)
nix develop
# Provides: nb, syncthing, restic, rclone, age, sops, etc.
Apply as NixOS Module
Add to your flake.nix inputs, then import the module:
{
inputs.pal.url = "git+https://git.qrty.ink/blu/pal.git";
# In your configuration:
imports = [ inputs.pal.nixosModules.default ];
}
Philosophy
- Config is code: Your system configuration should be version-controlled and shareable
- Notes deserve versioning: Use git-backed tools (nb) for notes, not just file sync
- Sync ≠ Backup: Syncthing syncs; restic backs up. Use both.
- Self-host when practical: Forgejo, Immich, Jellyfin over proprietary clouds
- Open source only: Every tool in this stack is FOSS
Architecture
┌─────────────────────────────────────────────────────────────┐
│ YOUR MACHINES │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Desktop │ │ Laptop │ │ Phone │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ └──────────────┼──────────────┘ │
│ │ │
│ ┌───────▼───────┐ │
│ │ TIER 2 │ │
│ │ nb (notes) │◄── git push/pull │
│ │ Syncthing │◄── P2P sync (optional) │
│ └───────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ YOUR SERVER │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Forgejo │ │ Immich │ │ Jellyfin │ │
│ │ (git/nb) │ │ (photos) │ │ (media) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ └────────────────┼────────────────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ restic │ │
│ │ (backup) │ │
│ └──────┬──────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ B2 / NAS │ │
│ │ (offsite) │ │
│ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
What's Included
NixOS Modules
modules/nb.nix- nb notebook CLI installation and configurationmodules/syncthing.nix- Declarative Syncthing setupmodules/neovim.nix- Neovim with nb integrationmodules/backup.nix- restic backup timersmodules/server/- Forgejo, Immich, Jellyfin configurations
Development Shell
nix develop
Provides: nb, syncthing, restic, rclone, jq, and helper scripts.
Helper Scripts
usync- Unified sync command (syncs nb + triggers Syncthing scan)ubackup- Run restic backup manuallyustatus- Show sync/backup status across all tiers
Usage
Tier 1: Configuration
Your system config lives in this repo. Fork it, customize it, push to your own GitHub.
# Rebuild your system
sudo nixos-rebuild switch --flake .#your-hostname
# Update flake inputs
nix flake update
Tier 2: Notes with nb
# Create a note
nb add "My note title"
# Edit with neovim
nb edit 1
# Sync to remote
nb sync
# Search notes
nb search "keyword"
# List notebooks
nb notebooks
Set up git remote for nb:
nb remote set git@your-forgejo:you/notes.git
Tier 2: Documents with Syncthing
Documents in ~/Documents/ sync automatically via Syncthing.
# Check Syncthing status
syncthing cli show system
# Force rescan
syncthing cli scan --folder documents
Tier 3: Large Data
Photos upload to Immich (mobile app or web). Media is served via Jellyfin. Manual files can be uploaded with rclone:
# Upload to your server
rclone copy ~/Videos/project server:archive/videos/
# List remote files
rclone ls server:archive/
Customization
Adding Your Own Notebooks
Edit modules/nb.nix:
{
programs.nb = {
notebooks = {
personal = {
remote = "git@forgejo:you/personal-notes.git";
};
work = {
remote = "git@forgejo:you/work-notes.git";
};
};
};
}
Syncthing Folders
Edit modules/syncthing.nix:
{
services.syncthing.settings.folders = {
"documents" = {
path = "~/Documents";
devices = [ "laptop" "desktop" ];
};
"music" = {
path = "~/Music";
devices = [ "laptop" "desktop" "server" ];
};
};
}
Backup Paths
Edit modules/backup.nix:
{
services.restic.backups.default = {
paths = [
"/home/you/Documents"
"/home/you/notes"
"/var/lib/important"
];
};
}
FAQ
Why nb instead of Obsidian/Notion/etc?
- Git-native: Full version history, proper merges
- Plain text: Markdown files, no vendor lock-in
- CLI-first: Works over SSH, in tmux, everywhere
- Scriptable: Integrates with shell workflows
- Encrypted option: Built-in GPG encryption
Why not just Syncthing for everything?
Syncthing is great for binary files but poor for text conflicts. When you edit notes on multiple devices, you want git-style merges, not .sync-conflict files scattered everywhere.
Is Syncthing buggy?
No. See our research. Common issues are:
- Treating it as a backup (it's not)
- Android battery killing the app (use Syncthing-Fork)
- Expecting cloud-style behavior from P2P sync
Why self-host?
- Control: Your data on your hardware
- Privacy: No third-party access
- Cost: One-time hardware vs. monthly subscriptions
- Learning: Valuable sysadmin experience
But cloud is fine too. This system works with GitHub, Backblaze B2, etc.
Repository Structure
.
├── flake.nix # Entry point
├── flake.lock
├── README.md
├── cli/
│ └── pal.lux # CLI source (Lux language)
├── docs/
│ ├── research/
│ │ └── sync-tools-comparison.md
│ ├── ARCHITECTURE.md
│ └── LLM-CONTEXT.md # For AI assistants
├── modules/
│ ├── pal.nix # Core pal NixOS module
│ ├── nb.nix
│ ├── syncthing.nix
│ ├── neovim.nix
│ ├── backup.nix
│ └── server/
│ ├── forgejo.nix
│ ├── immich.nix
│ └── jellyfin.nix
├── hosts/
│ ├── desktop/
│ │ └── configuration.nix
│ ├── laptop/
│ │ └── configuration.nix
│ └── server/
│ └── configuration.nix
├── home/
│ └── default.nix # home-manager config
└── scripts/
├── usync
├── ubackup
└── ustatus
Data Directory (~/.config/pal/)
When you run pal onboard or pal setup, this directory is created to hold all your data and configuration. Everything is human-readable unless noted.
~/.config/pal/
│
├── pal.toml # Main config (TOML) — device name, ports, schedule
├── age-key.txt # Age encryption private key
├── state.db # Event history (SQLite)
│
├── config-repo/ # Your system config (git-managed)
│ └── .git/
│
├── sync/ # Syncthing-managed data (syncs across devices)
│ ├── notes/ # Your notes
│ ├── documents/ # Your documents
│ └── dotfiles/ # Your dotfiles
│
├── syncthing/ # Syncthing runtime (auto-generated)
│ ├── config/ # config.xml, TLS certs, keys
│ └── db/ # Syncthing index database
│
├── restic/ # Backup settings
│ ├── password # Repository password (auto-generated, plaintext)
│ ├── repository # Repository URL/path (plaintext)
│ └── cache/ # Local cache for faster operations
│
├── backups/ # Restic repository (if backing up locally)
│ ├── config # ⚠ Encrypted — restic internal, not human-readable
│ ├── data/ # Encrypted, deduplicated backup chunks
│ ├── index/ # Backup index
│ ├── keys/ # Repository encryption keys
│ ├── locks/ # Lock files
│ └── snapshots/ # Snapshot metadata
│
└── server/ # Mount point for remote server storage
What's human-readable? pal.toml, restic/password, restic/repository, and everything in sync/ and config-repo/. The syncthing/config/ directory is auto-generated XML. The backups/ directory is a restic repository where everything is encrypted by design — use pal backup list to inspect snapshots.
Contributing
PRs welcome! Please read ARCHITECTURE.md first.
License
MIT
Built with Nix, because reproducibility matters.