Initial commit: Ultimate Notetaking, Sync & Backup System

A NixOS-based system for managing personal data across three tiers:
- Tier 1: Configuration (shareable via git)
- Tier 2: Syncable data (nb + Syncthing)
- Tier 3: Large data (self-hosted services + backup)

Includes:
- NixOS modules for nb, Syncthing, backup (restic)
- Server modules for Forgejo, Immich, Jellyfin
- Helper scripts (usync, ustatus)
- Comprehensive documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 01:44:00 -05:00
commit b40ac99524
17 changed files with 3151 additions and 0 deletions

227
scripts/ustatus Executable file
View File

@@ -0,0 +1,227 @@
#!/usr/bin/env bash
#
# ustatus - Status dashboard for the Ultimate Notetaking System
#
# Shows status of all sync and backup systems.
#
# Usage:
# ustatus - Show full status
# ustatus brief - Show brief status
set -euo pipefail
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
BOLD='\033[1m'
NC='\033[0m'
status_ok() {
echo -e "${GREEN}●${NC} $1"
}
status_warn() {
echo -e "${YELLOW}●${NC} $1"
}
status_error() {
echo -e "${RED}●${NC} $1"
}
header() {
echo -e "\n${BOLD}═══ $1 ═══${NC}"
}
# Check if a command exists
has_cmd() {
command -v "$1" &> /dev/null
}
brief_status() {
echo -e "${BOLD}Ultimate Notetaking System Status${NC}"
echo ""
# nb
if has_cmd nb; then
notebook_count=$(nb notebooks --names 2>/dev/null | wc -l || echo 0)
status_ok "nb: $notebook_count notebooks"
else
status_error "nb: not installed"
fi
# Syncthing
if has_cmd syncthing && syncthing cli show system &> /dev/null 2>&1; then
status_ok "Syncthing: running"
elif has_cmd syncthing; then
status_warn "Syncthing: not running"
else
status_error "Syncthing: not installed"
fi
# Backup
if systemctl is-active restic-backup.timer &> /dev/null; then
status_ok "Backup: timer active"
elif has_cmd restic; then
status_warn "Backup: timer not active"
else
status_error "Backup: restic not installed"
fi
}
full_status() {
echo -e "${BOLD}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}║ Ultimate Notetaking, Sync & Backup System ║${NC}"
echo -e "${BOLD}╚════════════════════════════════════════════════════════════╝${NC}"
# ==================== TIER 1: Configuration ====================
header "TIER 1: Configuration"
# Nix/NixOS
if has_cmd nixos-rebuild; then
generation=$(nixos-rebuild list-generations 2>/dev/null | head -1 | awk '{print $1}' || echo "?")
status_ok "NixOS generation: $generation"
fi
if has_cmd nix; then
nix_version=$(nix --version | head -1)
status_ok "Nix: $nix_version"
fi
# Git
if has_cmd git; then
status_ok "Git: $(git --version | cut -d' ' -f3)"
fi
# ==================== TIER 2: Notes ====================
header "TIER 2: Notes (nb)"
if has_cmd nb; then
nb_version=$(nb version 2>/dev/null || echo "unknown")
status_ok "nb version: $nb_version"
echo ""
echo "Notebooks:"
nb_dir="${NB_DIR:-$HOME/.nb}"
for notebook in $(nb notebooks --names 2>/dev/null || true); do
if [ -d "$nb_dir/$notebook" ]; then
note_count=$(find "$nb_dir/$notebook" -name "*.md" 2>/dev/null | wc -l)
# Check if has remote
if [ -d "$nb_dir/$notebook/.git" ]; then
remote=$(git -C "$nb_dir/$notebook" remote get-url origin 2>/dev/null || echo "")
if [ -n "$remote" ]; then
last_sync=$(git -C "$nb_dir/$notebook" log -1 --format="%ar" 2>/dev/null || echo "never")
echo -e " ${GREEN}●${NC} $notebook: $note_count notes, last sync: $last_sync"
else
echo -e " ${YELLOW}●${NC} $notebook: $note_count notes (no remote)"
fi
else
echo -e " ${YELLOW}●${NC} $notebook: $note_count notes (not git repo)"
fi
fi
done
else
status_error "nb not installed"
fi
# ==================== TIER 2: Syncthing ====================
header "TIER 2: File Sync (Syncthing)"
if has_cmd syncthing; then
if syncthing cli show system &> /dev/null 2>&1; then
uptime=$(syncthing cli show system 2>/dev/null | jq -r '.uptime // 0' | awk '{printf "%dd %dh %dm", $1/86400, ($1%86400)/3600, ($1%3600)/60}')
status_ok "Syncthing running (uptime: $uptime)"
echo ""
echo "Folders:"
syncthing cli show config 2>/dev/null | jq -r '.folders[] | "\(.label // .id)|\(.path)"' 2>/dev/null | while IFS='|' read -r label path; do
if [ -d "$path" ]; then
file_count=$(find "$path" -type f 2>/dev/null | wc -l)
echo -e " ${GREEN}●${NC} $label: $path ($file_count files)"
else
echo -e " ${YELLOW}●${NC} $label: $path (not found)"
fi
done || echo " Unable to list folders"
echo ""
echo "Devices:"
syncthing cli show config 2>/dev/null | jq -r '.devices[] | "\(.name // .deviceID[:8])"' 2>/dev/null | while read -r device; do
echo " ● $device"
done || echo " Unable to list devices"
else
status_warn "Syncthing installed but not running"
fi
else
status_error "Syncthing not installed"
fi
# ==================== TIER 3: Backup ====================
header "TIER 3: Backup (restic)"
if has_cmd restic; then
status_ok "restic: $(restic version | head -1)"
# Check systemd timer
if systemctl is-active restic-backup.timer &> /dev/null; then
next_run=$(systemctl show restic-backup.timer --property=NextElapseUSecRealtime 2>/dev/null | cut -d= -f2)
status_ok "Backup timer active (next: $next_run)"
else
status_warn "Backup timer not active"
fi
# Try to show recent snapshots (requires RESTIC_REPOSITORY to be set)
if [ -n "${RESTIC_REPOSITORY:-}" ]; then
echo ""
echo "Recent snapshots:"
restic snapshots --last 3 2>/dev/null | tail -n +3 | head -5 || echo " Unable to list snapshots"
fi
else
status_error "restic not installed"
fi
# ==================== TIER 3: Services ====================
header "TIER 3: Services"
# Check common services
for service in forgejo immich jellyfin; do
if systemctl is-active "$service" &> /dev/null 2>&1; then
status_ok "$service: running"
elif systemctl is-enabled "$service" &> /dev/null 2>&1; then
status_warn "$service: enabled but not running"
else
echo -e " ${BLUE}○${NC} $service: not configured"
fi
done
# ==================== Summary ====================
header "Quick Actions"
echo " usync - Sync all (nb + Syncthing)"
echo " ubackup now - Run backup immediately"
echo " nb add - Create new note"
echo ""
}
# Main
case "${1:-full}" in
brief|short|b)
brief_status
;;
full|"")
full_status
;;
-h|--help|help)
echo "ustatus - System status dashboard"
echo ""
echo "Usage:"
echo " ustatus Show full status"
echo " ustatus brief Show brief status"
echo " ustatus help Show this help"
;;
*)
echo "Unknown option: $1"
echo "Run 'ustatus help' for usage"
exit 1
;;
esac

156
scripts/usync Executable file
View File

@@ -0,0 +1,156 @@
#!/usr/bin/env bash
#
# usync - Unified sync command for the Ultimate Notetaking System
#
# Syncs both nb notebooks and triggers Syncthing scans.
#
# Usage:
# usync - Sync everything
# usync nb - Sync only nb notebooks
# usync st - Sync only Syncthing folders
# usync status - Show sync status
set -euo pipefail
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[OK]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
sync_nb() {
log_info "Syncing nb notebooks..."
if ! command -v nb &> /dev/null; then
log_error "nb not found in PATH"
return 1
fi
# Get list of notebooks
notebooks=$(nb notebooks --names 2>/dev/null || echo "")
if [ -z "$notebooks" ]; then
log_warn "No nb notebooks found"
return 0
fi
# Sync each notebook
for notebook in $notebooks; do
log_info " Syncing notebook: $notebook"
if nb "$notebook:sync" 2>/dev/null; then
log_success " $notebook synced"
else
log_warn " $notebook sync failed (no remote?)"
fi
done
}
sync_syncthing() {
log_info "Triggering Syncthing scan..."
if ! command -v syncthing &> /dev/null; then
log_error "syncthing not found in PATH"
return 1
fi
# Check if Syncthing is running
if ! syncthing cli show system &> /dev/null; then
log_warn "Syncthing is not running"
return 1
fi
# Trigger scan on all folders
if syncthing cli scan --all 2>/dev/null; then
log_success "Syncthing scan triggered"
else
log_error "Failed to trigger Syncthing scan"
return 1
fi
}
show_status() {
echo "=== Sync Status ==="
echo ""
# nb status
echo "--- nb Notebooks ---"
if command -v nb &> /dev/null; then
nb notebooks 2>/dev/null || echo " No notebooks"
echo ""
echo "Last sync times:"
for notebook in $(nb notebooks --names 2>/dev/null); do
nb_dir="${NB_DIR:-$HOME/.nb}"
if [ -d "$nb_dir/$notebook/.git" ]; then
last_commit=$(git -C "$nb_dir/$notebook" log -1 --format="%ar" 2>/dev/null || echo "never")
echo " $notebook: $last_commit"
fi
done
else
echo " nb not installed"
fi
echo ""
# Syncthing status
echo "--- Syncthing ---"
if command -v syncthing &> /dev/null && syncthing cli show system &> /dev/null; then
syncthing cli show system 2>/dev/null | grep -E "(myID|startTime)" || true
echo ""
echo "Folders:"
syncthing cli show config 2>/dev/null | jq -r '.folders[] | " \(.label // .id): \(.path)"' 2>/dev/null || echo " Unable to get folder info"
else
echo " Syncthing not running"
fi
}
# Main
case "${1:-all}" in
nb)
sync_nb
;;
st|syncthing)
sync_syncthing
;;
status)
show_status
;;
all|"")
sync_nb
echo ""
sync_syncthing
echo ""
log_success "All sync operations complete"
;;
-h|--help|help)
echo "usync - Unified sync command"
echo ""
echo "Usage:"
echo " usync Sync everything (nb + Syncthing)"
echo " usync nb Sync only nb notebooks"
echo " usync st Trigger Syncthing scan"
echo " usync status Show sync status"
echo " usync help Show this help"
;;
*)
log_error "Unknown command: $1"
echo "Run 'usync help' for usage"
exit 1
;;
esac