Compare commits
8 Commits
afe7826d58
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| ec5e77f796 | |||
| 3be9586238 | |||
| 4a5fa4415c | |||
| d30d2efa4e | |||
| 05c04b209c | |||
| 13fe22a804 | |||
| dedfbfce64 | |||
| d3d720b3bc |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -3,6 +3,10 @@ result
|
|||||||
result-*
|
result-*
|
||||||
.direnv/
|
.direnv/
|
||||||
|
|
||||||
|
# Compiled binaries
|
||||||
|
pal
|
||||||
|
*_test
|
||||||
|
|
||||||
# Secrets (NEVER commit unencrypted secrets)
|
# Secrets (NEVER commit unencrypted secrets)
|
||||||
secrets/*.yaml
|
secrets/*.yaml
|
||||||
!secrets/secrets.yaml.example
|
!secrets/secrets.yaml.example
|
||||||
|
|||||||
68
README.md
68
README.md
@@ -1,6 +1,6 @@
|
|||||||
# Ultimate Notetaking, Sync & Backup System
|
# Ultimate Notetaking, Sync & Backup System
|
||||||
|
|
||||||
A NixOS-based system for managing the three types of data in a computer:
|
A NixOS-based system for managing the three types of data across devices:
|
||||||
|
|
||||||
| Tier | Type | Examples | Sync Model |
|
| Tier | Type | Examples | Sync Model |
|
||||||
|------|------|----------|------------|
|
|------|------|----------|------------|
|
||||||
@@ -12,11 +12,11 @@ A NixOS-based system for managing the three types of data in a computer:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# One-command setup (public repo, no SSH key needed)
|
# One-command setup (public repo, no SSH key needed)
|
||||||
nix run 'git+https://git.qrty.ink/blu/grapho.git'
|
nix run 'git+https://git.qrty.ink/blu/pal.git'
|
||||||
|
|
||||||
# Or clone first, then run
|
# Or clone first, then run
|
||||||
git clone https://git.qrty.ink/blu/grapho.git
|
git clone https://git.qrty.ink/blu/pal.git
|
||||||
cd grapho
|
cd pal
|
||||||
nix run .
|
nix run .
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -26,8 +26,8 @@ nix run .
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Clone the repo
|
# 1. Clone the repo
|
||||||
git clone https://git.qrty.ink/blu/grapho.git
|
git clone https://git.qrty.ink/blu/pal.git
|
||||||
cd grapho
|
cd pal
|
||||||
|
|
||||||
# 2. Run setup (one command - includes all dependencies)
|
# 2. Run setup (one command - includes all dependencies)
|
||||||
nix run .
|
nix run .
|
||||||
@@ -39,18 +39,18 @@ nix run .
|
|||||||
# 4. (Optional) Set up SSH for push access
|
# 4. (Optional) Set up SSH for push access
|
||||||
# Add your SSH key to Gitea: https://git.qrty.ink/user/settings/keys
|
# Add your SSH key to Gitea: https://git.qrty.ink/user/settings/keys
|
||||||
# Then switch to SSH remote:
|
# Then switch to SSH remote:
|
||||||
git remote set-url origin ssh://git@git.qrty.ink:2222/blu/grapho.git
|
git remote set-url origin ssh://git@git.qrty.ink:2222/blu/pal.git
|
||||||
```
|
```
|
||||||
|
|
||||||
### Additional Computers (Joining)
|
### Additional Computers (Joining)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# One command (no SSH key needed for public repo)
|
# One command (no SSH key needed for public repo)
|
||||||
nix run 'git+https://git.qrty.ink/blu/grapho.git'
|
nix run 'git+https://git.qrty.ink/blu/pal.git'
|
||||||
# Choose option [2], enter your config git URL and age key
|
# Choose option [2], enter your config git URL and age key
|
||||||
|
|
||||||
# Or clone first:
|
# Or clone first:
|
||||||
git clone https://git.qrty.ink/blu/grapho.git && cd grapho
|
git clone https://git.qrty.ink/blu/pal.git && cd pal
|
||||||
nix run . -- <config-git-url> <your-age-key>
|
nix run . -- <config-git-url> <your-age-key>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -78,10 +78,10 @@ Add to your flake.nix inputs, then import the module:
|
|||||||
|
|
||||||
```nix
|
```nix
|
||||||
{
|
{
|
||||||
inputs.grapho.url = "git+https://git.qrty.ink/blu/grapho.git";
|
inputs.pal.url = "git+https://git.qrty.ink/blu/pal.git";
|
||||||
|
|
||||||
# In your configuration:
|
# In your configuration:
|
||||||
imports = [ inputs.grapho.nixosModules.default ];
|
imports = [ inputs.pal.nixosModules.default ];
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -306,19 +306,22 @@ No. See [our research](./docs/research/sync-tools-comparison.md). Common issues
|
|||||||
|
|
||||||
But cloud is fine too. This system works with GitHub, Backblaze B2, etc.
|
But cloud is fine too. This system works with GitHub, Backblaze B2, etc.
|
||||||
|
|
||||||
## Directory Structure
|
## Repository Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
.
|
.
|
||||||
├── flake.nix # Entry point
|
├── flake.nix # Entry point
|
||||||
├── flake.lock
|
├── flake.lock
|
||||||
├── README.md
|
├── README.md
|
||||||
|
├── cli/
|
||||||
|
│ └── pal.lux # CLI source (Lux language)
|
||||||
├── docs/
|
├── docs/
|
||||||
│ ├── research/
|
│ ├── research/
|
||||||
│ │ └── sync-tools-comparison.md
|
│ │ └── sync-tools-comparison.md
|
||||||
│ ├── ARCHITECTURE.md
|
│ ├── ARCHITECTURE.md
|
||||||
│ └── LLM-CONTEXT.md # For AI assistants
|
│ └── LLM-CONTEXT.md # For AI assistants
|
||||||
├── modules/
|
├── modules/
|
||||||
|
│ ├── pal.nix # Core pal NixOS module
|
||||||
│ ├── nb.nix
|
│ ├── nb.nix
|
||||||
│ ├── syncthing.nix
|
│ ├── syncthing.nix
|
||||||
│ ├── neovim.nix
|
│ ├── neovim.nix
|
||||||
@@ -342,6 +345,47 @@ But cloud is fine too. This system works with GitHub, Backblaze B2, etc.
|
|||||||
└── ustatus
|
└── 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
|
## Contributing
|
||||||
|
|
||||||
PRs welcome! Please read [ARCHITECTURE.md](./docs/ARCHITECTURE.md) first.
|
PRs welcome! Please read [ARCHITECTURE.md](./docs/ARCHITECTURE.md) first.
|
||||||
|
|||||||
1091
cli/grapho.lux
1091
cli/grapho.lux
File diff suppressed because it is too large
Load Diff
1722
cli/pal.lux
Normal file
1722
cli/pal.lux
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,218 +0,0 @@
|
|||||||
# Lux Language Limitations (grapho CLI)
|
|
||||||
|
|
||||||
This document tracks limitations encountered while developing the grapho CLI in Lux, to help improve the language.
|
|
||||||
|
|
||||||
## Fixed Issues
|
|
||||||
|
|
||||||
### 1. Double Execution Bug (FIXED)
|
|
||||||
**Severity:** Critical
|
|
||||||
**Status:** Fixed in Lux c_backend.rs
|
|
||||||
|
|
||||||
When using `let result = run main() with {}` to invoke the main function, the entire program was executing twice.
|
|
||||||
|
|
||||||
**Root Cause:** In `c_backend.rs:3878-3907`, the generated C code was:
|
|
||||||
1. Executing all `run` expressions (including `run main() with {}`)
|
|
||||||
2. Then ALSO calling `main_lux()` separately because `has_main` was true
|
|
||||||
|
|
||||||
**Fix:** Added tracking of whether main was already called via a `run` expression, and skip the separate `main_lux()` call if so.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## String Handling Issues
|
|
||||||
|
|
||||||
### 2. No Escape Sequences in String Literals
|
|
||||||
**Severity:** High
|
|
||||||
**Status:** Confirmed
|
|
||||||
|
|
||||||
Lux does not support backslash escape sequences like `\"`, `\n`, `\t` in string literals.
|
|
||||||
|
|
||||||
```lux
|
|
||||||
// This FAILS - backslash causes parse error
|
|
||||||
Console.print("Hello \"World\"") // ERROR: Unexpected character: '\'
|
|
||||||
|
|
||||||
// This FAILS
|
|
||||||
Console.print("Line1\nLine2") // ERROR: Unexpected character: '\'
|
|
||||||
```
|
|
||||||
|
|
||||||
**Impact:** Cannot include quotes in strings, cannot create multi-line strings, cannot output JSON with proper formatting.
|
|
||||||
|
|
||||||
**Workaround:**
|
|
||||||
- Use shell commands via `Process.exec` to generate quoted output
|
|
||||||
- Use `String.fromChar('"')` for quotes (but this had issues too)
|
|
||||||
- For JSON output, use key=value format instead
|
|
||||||
|
|
||||||
### 3. Dollar Sign in Strings Causes Parse Error
|
|
||||||
**Severity:** Medium
|
|
||||||
**Status:** Confirmed
|
|
||||||
|
|
||||||
The `$` character in strings triggers the string interpolation lexer, even inside shell command strings.
|
|
||||||
|
|
||||||
```lux
|
|
||||||
// This FAILS
|
|
||||||
execQuiet("jq -n --arg x '$foo' ...") // ERROR: Unexpected character: '$'
|
|
||||||
```
|
|
||||||
|
|
||||||
**Impact:** Cannot use shell variable syntax or jq arguments in command strings.
|
|
||||||
|
|
||||||
**Workaround:** Avoid `$` in strings, or construct commands differently.
|
|
||||||
|
|
||||||
### 4. String.fromChar Returns Int, Not String
|
|
||||||
**Severity:** Medium
|
|
||||||
**Status:** Bug
|
|
||||||
|
|
||||||
`String.fromChar('"')` appears to return an Int instead of a String, causing C compilation errors.
|
|
||||||
|
|
||||||
```lux
|
|
||||||
let q = String.fromChar('"') // Compiles but C code is wrong
|
|
||||||
Console.print(q + "hello") // C error: int + string
|
|
||||||
```
|
|
||||||
|
|
||||||
**Impact:** Cannot use character literals to build strings.
|
|
||||||
|
|
||||||
**Workaround:** Use `execQuiet("printf '%s' '\"'")` to get a quote character.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Type System Issues
|
|
||||||
|
|
||||||
### 5. Record Type Definitions Don't Work as Expected
|
|
||||||
**Severity:** Medium
|
|
||||||
**Status:** Needs Investigation
|
|
||||||
|
|
||||||
Defining a record type and then creating values of that type doesn't work:
|
|
||||||
|
|
||||||
```lux
|
|
||||||
type ComponentStatus = {
|
|
||||||
name: String,
|
|
||||||
status: HealthStatus,
|
|
||||||
message: String,
|
|
||||||
fix: String
|
|
||||||
}
|
|
||||||
|
|
||||||
fn checkNb(): ComponentStatus with {Process} = {
|
|
||||||
// ...
|
|
||||||
{ name: "nb", status: Healthy, message: "ok", fix: "" }
|
|
||||||
// ERROR: Cannot unify { name: String, ... } with ComponentStatus
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Impact:** Cannot use structured types for cleaner code organization.
|
|
||||||
|
|
||||||
**Workaround:** Avoid record types, use multiple return values via tuples or restructure code.
|
|
||||||
|
|
||||||
### 6. Int.parse Doesn't Exist or Has Wrong Signature
|
|
||||||
**Severity:** Low
|
|
||||||
**Status:** Confirmed
|
|
||||||
|
|
||||||
There's no obvious way to parse a string to an integer.
|
|
||||||
|
|
||||||
```lux
|
|
||||||
let count = Int.parse(someString) // ERROR: Unknown effect operation
|
|
||||||
```
|
|
||||||
|
|
||||||
**Impact:** Cannot convert string output from shell commands to numbers.
|
|
||||||
|
|
||||||
**Workaround:** Keep numbers as strings, use shell for numeric comparisons.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## C Backend Issues
|
|
||||||
|
|
||||||
### 7. String Equality Comparison Generates Incorrect C Code
|
|
||||||
**Severity:** High
|
|
||||||
**Status:** Bug
|
|
||||||
|
|
||||||
Using `==` to compare strings generates C code that compares pointers instead of string contents.
|
|
||||||
|
|
||||||
```lux
|
|
||||||
let result = execQuiet("echo yes")
|
|
||||||
if result == "yes" then ... // C code: (result == "yes") - pointer comparison!
|
|
||||||
```
|
|
||||||
|
|
||||||
**Impact:** String comparisons fail in compiled binaries.
|
|
||||||
|
|
||||||
**Workaround:** Use `String.contains` for comparison:
|
|
||||||
```lux
|
|
||||||
fn isYes(s: String): Bool = String.contains(s, "yes")
|
|
||||||
if result |> isYes then ...
|
|
||||||
```
|
|
||||||
|
|
||||||
### 8. String.startsWith Not Available in C Backend
|
|
||||||
**Severity:** Medium
|
|
||||||
**Status:** Bug
|
|
||||||
|
|
||||||
`String.startsWith` works in interpreter but generates undefined function calls in C.
|
|
||||||
|
|
||||||
```lux
|
|
||||||
String.startsWith(s, "prefix") // C error: lux_string__startsWith undefined
|
|
||||||
```
|
|
||||||
|
|
||||||
**Workaround:** Use `String.contains` instead.
|
|
||||||
|
|
||||||
### 9. `let _ = expr` Pattern Not Supported
|
|
||||||
**Severity:** Low
|
|
||||||
**Status:** Bug
|
|
||||||
|
|
||||||
The underscore wildcard pattern for discarding results doesn't work.
|
|
||||||
|
|
||||||
```lux
|
|
||||||
let _ = Process.exec("...") // ERROR: Expected identifier
|
|
||||||
```
|
|
||||||
|
|
||||||
**Workaround:** Use a named binding:
|
|
||||||
```lux
|
|
||||||
let ignore = Process.exec("...")
|
|
||||||
```
|
|
||||||
|
|
||||||
### 10. List Literals and Recursion Cause Segfaults
|
|
||||||
**Severity:** High
|
|
||||||
**Status:** Bug
|
|
||||||
|
|
||||||
Combining list literals with recursive functions can cause segmentation faults in compiled binaries while working fine in interpreter.
|
|
||||||
|
|
||||||
```lux
|
|
||||||
// This crashes when compiled:
|
|
||||||
let dirs = ["a", "b", "c"]
|
|
||||||
fn processDirs(dirs: List<String>): Unit =
|
|
||||||
match List.head(dirs) {
|
|
||||||
Some(d) => { ...; match List.tail(dirs) { Some(rest) => processDirs(rest), ... } }
|
|
||||||
None => ()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Workaround:** Avoid list literals with recursive processing. Inline the operations:
|
|
||||||
```lux
|
|
||||||
fn processA(): Unit = ...
|
|
||||||
fn processB(): Unit = ...
|
|
||||||
fn processC(): Unit = ...
|
|
||||||
// Call each individually
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Suggestions for Lux
|
|
||||||
|
|
||||||
1. **Add escape sequence support** - At minimum `\"`, `\\`, `\n`, `\t`
|
|
||||||
2. **Fix String.fromChar** to return String, not Int
|
|
||||||
3. **Add raw string literals** - Something like `r"..."` or `'''...'''` for shell commands
|
|
||||||
4. **Fix the double execution bug** in the runtime (DONE)
|
|
||||||
5. **Support record type literals** matching their declared type
|
|
||||||
6. **Add Int.parse and Float.parse** for string-to-number conversion
|
|
||||||
7. **Consider a heredoc syntax** for multi-line strings with special characters
|
|
||||||
8. **Fix string equality** - Use strcmp in C backend for string ==
|
|
||||||
9. **Support `let _ = `** - Allow underscore as discard binding
|
|
||||||
10. **Fix String.startsWith** in C backend
|
|
||||||
11. **Fix list literals with recursion** causing segfaults
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Current Workarounds in grapho CLI
|
|
||||||
|
|
||||||
1. **Double output:** FIXED in Lux c_backend.rs
|
|
||||||
2. **JSON output:** Using key=value format instead of proper JSON
|
|
||||||
3. **Quotes in output:** Avoided entirely or generated via shell
|
|
||||||
4. **Structured types:** Using individual variables instead of records
|
|
||||||
5. **Numeric parsing:** Keeping counts as strings throughout
|
|
||||||
6. **String comparison:** Using `String.contains` with helper functions instead of `==`
|
|
||||||
7. **Discarding results:** Using `let ignore = ...` instead of `let _ = ...`
|
|
||||||
8. **Lists with recursion:** Replaced with individual function calls
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Markdown Editors for grapho
|
# Markdown Editors for pal
|
||||||
|
|
||||||
This document covers recommended markdown editors for use with grapho across desktop and mobile platforms.
|
This document covers recommended markdown editors for use with pal across desktop and mobile platforms.
|
||||||
|
|
||||||
## Recommended: md (PWA)
|
## Recommended: md (PWA)
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ A lightweight, browser-based markdown editor that works on both desktop and mobi
|
|||||||
- Syntax highlighting for code blocks
|
- Syntax highlighting for code blocks
|
||||||
- Keyboard shortcuts (Ctrl+S to download, Ctrl+B/I for formatting)
|
- Keyboard shortcuts (Ctrl+S to download, Ctrl+B/I for formatting)
|
||||||
|
|
||||||
### Why It's Good for grapho
|
### Why It's Good for pal
|
||||||
- Works on any device with a browser
|
- Works on any device with a browser
|
||||||
- Can be installed as a PWA on mobile home screen
|
- Can be installed as a PWA on mobile home screen
|
||||||
- No account required
|
- No account required
|
||||||
@@ -156,7 +156,7 @@ The best open-source markdown editor for Android.
|
|||||||
- Supports markdown, todo.txt, and more
|
- Supports markdown, todo.txt, and more
|
||||||
- Offline-first
|
- Offline-first
|
||||||
|
|
||||||
**Best for:** grapho users on Android.
|
**Best for:** pal users on Android.
|
||||||
|
|
||||||
### iA Writer (iOS/Android)
|
### iA Writer (iOS/Android)
|
||||||
**Paid** | [Website](https://ia.net/writer)
|
**Paid** | [Website](https://ia.net/writer)
|
||||||
@@ -187,12 +187,12 @@ Mobile companion to Obsidian desktop.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Recommendation for grapho Users
|
## Recommendation for pal Users
|
||||||
|
|
||||||
### Simple Setup (Recommended)
|
### Simple Setup (Recommended)
|
||||||
1. **Desktop:** MarkText or VS Code
|
1. **Desktop:** MarkText or VS Code
|
||||||
2. **Mobile:** md PWA (https://md-ashy.vercel.app) or Markor (Android)
|
2. **Mobile:** md PWA (https://md-ashy.vercel.app) or Markor (Android)
|
||||||
3. **Sync:** Syncthing (already part of grapho)
|
3. **Sync:** Syncthing (already part of pal)
|
||||||
|
|
||||||
### Power User Setup
|
### Power User Setup
|
||||||
1. **Desktop:** Obsidian with Syncthing sync
|
1. **Desktop:** Obsidian with Syncthing sync
|
||||||
@@ -206,7 +206,7 @@ Mobile companion to Obsidian desktop.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Integration with grapho
|
## Integration with pal
|
||||||
|
|
||||||
All recommended editors work with plain markdown files, which means:
|
All recommended editors work with plain markdown files, which means:
|
||||||
|
|
||||||
@@ -225,7 +225,7 @@ marktext ~/.nb/home/meeting-notes.md
|
|||||||
|
|
||||||
# Or on mobile, open the same file via Syncthing folder
|
# Or on mobile, open the same file via Syncthing folder
|
||||||
# Sync happens automatically
|
# Sync happens automatically
|
||||||
grapho sync
|
pal sync
|
||||||
```
|
```
|
||||||
|
|
||||||
## Sources
|
## Sources
|
||||||
|
|||||||
8
flake.lock
generated
8
flake.lock
generated
@@ -47,13 +47,13 @@
|
|||||||
"rust-overlay": "rust-overlay"
|
"rust-overlay": "rust-overlay"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1771221263,
|
"lastModified": 1771638380,
|
||||||
"narHash": "sha256-Av4s4pelV+ueIMSY61aHuT8KjKZ6ekXtJsnjVc89gtQ=",
|
"narHash": "sha256-RLGfahDSlYi8ec50DtmfOZn9q8JpF2xBTcUb8K2ZQ3Q=",
|
||||||
"path": "/home/blu/src/lux",
|
"path": "/home/blu/src/lux/lang",
|
||||||
"type": "path"
|
"type": "path"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"path": "/home/blu/src/lux",
|
"path": "/home/blu/src/lux/lang",
|
||||||
"type": "path"
|
"type": "path"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
32
flake.nix
32
flake.nix
@@ -15,7 +15,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
lux = {
|
lux = {
|
||||||
url = "path:/home/blu/src/lux";
|
url = "path:/home/blu/src/lux/lang";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
# Shared modules for all hosts
|
# Shared modules for all hosts
|
||||||
sharedModules = [
|
sharedModules = [
|
||||||
./modules/grapho.nix
|
./modules/pal.nix
|
||||||
./modules/nb.nix
|
./modules/nb.nix
|
||||||
./modules/syncthing.nix
|
./modules/syncthing.nix
|
||||||
./modules/backup.nix
|
./modules/backup.nix
|
||||||
@@ -66,36 +66,36 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
in {
|
in {
|
||||||
# Grapho CLI package
|
# Pal CLI package
|
||||||
packages = forAllSystems (system:
|
packages = forAllSystems (system:
|
||||||
let
|
let
|
||||||
pkgs = nixpkgsFor.${system};
|
pkgs = nixpkgsFor.${system};
|
||||||
luxPkg = lux.packages.${system}.default;
|
luxPkg = lux.packages.${system}.default;
|
||||||
in {
|
in {
|
||||||
grapho = pkgs.stdenv.mkDerivation {
|
pal = pkgs.stdenv.mkDerivation {
|
||||||
pname = "grapho";
|
pname = "pal";
|
||||||
version = "0.1.0";
|
version = "0.1.0";
|
||||||
src = ./cli;
|
src = ./cli;
|
||||||
|
|
||||||
nativeBuildInputs = [ luxPkg pkgs.gcc ];
|
nativeBuildInputs = [ luxPkg pkgs.gcc ];
|
||||||
|
|
||||||
buildPhase = ''
|
buildPhase = ''
|
||||||
${luxPkg}/bin/lux compile grapho.lux -o grapho
|
${luxPkg}/bin/lux compile pal.lux -o pal
|
||||||
'';
|
'';
|
||||||
|
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
mkdir -p $out/bin
|
mkdir -p $out/bin
|
||||||
cp grapho $out/bin/
|
cp pal $out/bin/
|
||||||
'';
|
'';
|
||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
description = "Personal data infrastructure CLI";
|
description = "Personal data infrastructure CLI";
|
||||||
homepage = "https://github.com/user/grapho";
|
homepage = "https://github.com/user/pal";
|
||||||
license = pkgs.lib.licenses.mit;
|
license = pkgs.lib.licenses.mit;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
default = self.packages.${system}.grapho;
|
default = self.packages.${system}.pal;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
else
|
else
|
||||||
echo "Error: Cannot find setup script"
|
echo "Error: Cannot find setup script"
|
||||||
echo "Run from the repo directory, or clone it first:"
|
echo "Run from the repo directory, or clone it first:"
|
||||||
echo " git clone ssh://git@your-server:2222/you/grapho.git"
|
echo " git clone ssh://git@your-server:2222/you/pal.git"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -173,6 +173,7 @@
|
|||||||
|
|
||||||
packages = [
|
packages = [
|
||||||
lux.packages.${system}.default
|
lux.packages.${system}.default
|
||||||
|
self.packages.${system}.pal
|
||||||
] ++ (with pkgs; [
|
] ++ (with pkgs; [
|
||||||
# Tier 2: Notes & Sync
|
# Tier 2: Notes & Sync
|
||||||
nb # Notebook CLI
|
nb # Notebook CLI
|
||||||
@@ -203,12 +204,11 @@
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
printf '\033[1m%s\033[0m\n' "Ultimate Notetaking, Sync & Backup System"
|
printf '\033[1m%s\033[0m\n' "pal - Your personal data, everywhere"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Get started: ./setup"
|
echo "Get started: pal onboard"
|
||||||
echo "Pair mobile: ./setup mobile"
|
echo "Status: pal status"
|
||||||
echo "Sync: ./scripts/usync"
|
echo "Help: pal help"
|
||||||
echo "Status: ./scripts/ustatus"
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -216,7 +216,7 @@
|
|||||||
|
|
||||||
# Export modules for use in other flakes
|
# Export modules for use in other flakes
|
||||||
nixosModules = {
|
nixosModules = {
|
||||||
grapho = import ./modules/grapho.nix;
|
pal = import ./modules/pal.nix;
|
||||||
nb = import ./modules/nb.nix;
|
nb = import ./modules/nb.nix;
|
||||||
syncthing = import ./modules/syncthing.nix;
|
syncthing = import ./modules/syncthing.nix;
|
||||||
backup = import ./modules/backup.nix;
|
backup = import ./modules/backup.nix;
|
||||||
|
|||||||
@@ -1,39 +1,39 @@
|
|||||||
# Grapho Module
|
# Pal Module
|
||||||
#
|
#
|
||||||
# Unified personal data infrastructure module for NixOS.
|
# Unified personal data infrastructure module for NixOS.
|
||||||
# Sets up Syncthing (isolated) + Restic backup + directory structure.
|
# Sets up Syncthing (isolated) + Restic backup + directory structure.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# services.grapho.enable = true;
|
# services.pal.enable = true;
|
||||||
# services.grapho.user = "youruser";
|
# services.pal.user = "youruser";
|
||||||
#
|
#
|
||||||
# This creates:
|
# This creates:
|
||||||
# - ~/.config/grapho/ directory structure
|
# - ~/.config/pal/ directory structure
|
||||||
# - Isolated Syncthing on port 8385 (separate from system Syncthing)
|
# - Isolated Syncthing on port 8385 (separate from system Syncthing)
|
||||||
# - Restic backup timer for grapho data
|
# - Restic backup timer for pal data
|
||||||
|
|
||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
with lib;
|
with lib;
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.services.grapho;
|
cfg = config.services.pal;
|
||||||
home = config.users.users.${cfg.user}.home;
|
home = config.users.users.${cfg.user}.home;
|
||||||
graphoDir = "${home}/.config/grapho";
|
palDir = "${home}/.config/pal";
|
||||||
in {
|
in {
|
||||||
options.services.grapho = {
|
options.services.pal = {
|
||||||
enable = mkEnableOption "grapho personal data infrastructure";
|
enable = mkEnableOption "pal personal data infrastructure";
|
||||||
|
|
||||||
user = mkOption {
|
user = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
description = "User to run grapho services as.";
|
description = "User to run pal services as.";
|
||||||
example = "alice";
|
example = "alice";
|
||||||
};
|
};
|
||||||
|
|
||||||
group = mkOption {
|
group = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
default = "users";
|
default = "users";
|
||||||
description = "Group to run grapho services as.";
|
description = "Group to run pal services as.";
|
||||||
};
|
};
|
||||||
|
|
||||||
# Syncthing options
|
# Syncthing options
|
||||||
@@ -41,7 +41,7 @@ in {
|
|||||||
enable = mkOption {
|
enable = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = true;
|
default = true;
|
||||||
description = "Enable grapho's isolated Syncthing instance.";
|
description = "Enable pal's isolated Syncthing instance.";
|
||||||
};
|
};
|
||||||
|
|
||||||
guiPort = mkOption {
|
guiPort = mkOption {
|
||||||
@@ -65,7 +65,7 @@ in {
|
|||||||
openFirewall = mkOption {
|
openFirewall = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = true;
|
default = true;
|
||||||
description = "Open firewall for grapho's Syncthing ports.";
|
description = "Open firewall for pal's Syncthing ports.";
|
||||||
};
|
};
|
||||||
|
|
||||||
devices = mkOption {
|
devices = mkOption {
|
||||||
@@ -120,14 +120,14 @@ in {
|
|||||||
enable = mkOption {
|
enable = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = false;
|
default = false;
|
||||||
description = "Enable restic backup of grapho data.";
|
description = "Enable restic backup of pal data.";
|
||||||
};
|
};
|
||||||
|
|
||||||
repository = mkOption {
|
repository = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
default = "";
|
default = "";
|
||||||
description = "Restic repository location (e.g., 'sftp:server:/backups/grapho').";
|
description = "Restic repository location (e.g., 'sftp:server:/backups/pal').";
|
||||||
example = "sftp:backup-server:/backups/grapho";
|
example = "sftp:backup-server:/backups/pal";
|
||||||
};
|
};
|
||||||
|
|
||||||
passwordFile = mkOption {
|
passwordFile = mkOption {
|
||||||
@@ -195,27 +195,27 @@ in {
|
|||||||
isNormalUser = true;
|
isNormalUser = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
# Create grapho directory structure
|
# Create pal directory structure
|
||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
"d ${graphoDir} 0755 ${cfg.user} ${cfg.group} -"
|
"d ${palDir} 0755 ${cfg.user} ${cfg.group} -"
|
||||||
"d ${graphoDir}/config-repo 0755 ${cfg.user} ${cfg.group} -"
|
"d ${palDir}/config-repo 0755 ${cfg.user} ${cfg.group} -"
|
||||||
"d ${graphoDir}/syncthing/config 0755 ${cfg.user} ${cfg.group} -"
|
"d ${palDir}/syncthing/config 0755 ${cfg.user} ${cfg.group} -"
|
||||||
"d ${graphoDir}/syncthing/db 0755 ${cfg.user} ${cfg.group} -"
|
"d ${palDir}/syncthing/db 0755 ${cfg.user} ${cfg.group} -"
|
||||||
"d ${graphoDir}/sync 0755 ${cfg.user} ${cfg.group} -"
|
"d ${palDir}/sync 0755 ${cfg.user} ${cfg.group} -"
|
||||||
"d ${graphoDir}/sync/notes 0755 ${cfg.user} ${cfg.group} -"
|
"d ${palDir}/sync/notes 0755 ${cfg.user} ${cfg.group} -"
|
||||||
"d ${graphoDir}/sync/documents 0755 ${cfg.user} ${cfg.group} -"
|
"d ${palDir}/sync/documents 0755 ${cfg.user} ${cfg.group} -"
|
||||||
"d ${graphoDir}/sync/dotfiles 0755 ${cfg.user} ${cfg.group} -"
|
"d ${palDir}/sync/dotfiles 0755 ${cfg.user} ${cfg.group} -"
|
||||||
"d ${graphoDir}/restic/cache 0755 ${cfg.user} ${cfg.group} -"
|
"d ${palDir}/restic/cache 0755 ${cfg.user} ${cfg.group} -"
|
||||||
"d ${graphoDir}/server 0755 ${cfg.user} ${cfg.group} -"
|
"d ${palDir}/server 0755 ${cfg.user} ${cfg.group} -"
|
||||||
];
|
];
|
||||||
|
|
||||||
# Isolated Syncthing for grapho
|
# Isolated Syncthing for pal
|
||||||
services.syncthing = mkIf cfg.syncthing.enable {
|
services.syncthing = mkIf cfg.syncthing.enable {
|
||||||
enable = true;
|
enable = true;
|
||||||
user = cfg.user;
|
user = cfg.user;
|
||||||
group = cfg.group;
|
group = cfg.group;
|
||||||
dataDir = "${graphoDir}/sync";
|
dataDir = "${palDir}/sync";
|
||||||
configDir = "${graphoDir}/syncthing/config";
|
configDir = "${palDir}/syncthing/config";
|
||||||
guiAddress = "127.0.0.1:${toString cfg.syncthing.guiPort}";
|
guiAddress = "127.0.0.1:${toString cfg.syncthing.guiPort}";
|
||||||
|
|
||||||
overrideDevices = true;
|
overrideDevices = true;
|
||||||
@@ -228,24 +228,24 @@ in {
|
|||||||
}) cfg.syncthing.devices;
|
}) cfg.syncthing.devices;
|
||||||
|
|
||||||
folders = {
|
folders = {
|
||||||
# Default grapho folders
|
# Default pal folders
|
||||||
"grapho-notes" = {
|
"pal-notes" = {
|
||||||
path = "${graphoDir}/sync/notes";
|
path = "${palDir}/sync/notes";
|
||||||
id = "grapho-notes";
|
id = "pal-notes";
|
||||||
devices = attrNames cfg.syncthing.devices;
|
devices = attrNames cfg.syncthing.devices;
|
||||||
type = "sendreceive";
|
type = "sendreceive";
|
||||||
fsWatcherEnabled = true;
|
fsWatcherEnabled = true;
|
||||||
};
|
};
|
||||||
"grapho-documents" = {
|
"pal-documents" = {
|
||||||
path = "${graphoDir}/sync/documents";
|
path = "${palDir}/sync/documents";
|
||||||
id = "grapho-documents";
|
id = "pal-documents";
|
||||||
devices = attrNames cfg.syncthing.devices;
|
devices = attrNames cfg.syncthing.devices;
|
||||||
type = "sendreceive";
|
type = "sendreceive";
|
||||||
fsWatcherEnabled = true;
|
fsWatcherEnabled = true;
|
||||||
};
|
};
|
||||||
"grapho-dotfiles" = {
|
"pal-dotfiles" = {
|
||||||
path = "${graphoDir}/sync/dotfiles";
|
path = "${palDir}/sync/dotfiles";
|
||||||
id = "grapho-dotfiles";
|
id = "pal-dotfiles";
|
||||||
devices = attrNames cfg.syncthing.devices;
|
devices = attrNames cfg.syncthing.devices;
|
||||||
type = "sendreceive";
|
type = "sendreceive";
|
||||||
fsWatcherEnabled = true;
|
fsWatcherEnabled = true;
|
||||||
@@ -271,15 +271,15 @@ in {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# Firewall for grapho Syncthing
|
# Firewall for pal Syncthing
|
||||||
networking.firewall = mkIf (cfg.syncthing.enable && cfg.syncthing.openFirewall) {
|
networking.firewall = mkIf (cfg.syncthing.enable && cfg.syncthing.openFirewall) {
|
||||||
allowedTCPPorts = [ cfg.syncthing.syncPort ];
|
allowedTCPPorts = [ cfg.syncthing.syncPort ];
|
||||||
allowedUDPPorts = [ cfg.syncthing.syncPort cfg.syncthing.discoveryPort ];
|
allowedUDPPorts = [ cfg.syncthing.syncPort cfg.syncthing.discoveryPort ];
|
||||||
};
|
};
|
||||||
|
|
||||||
# Restic backup service
|
# Restic backup service
|
||||||
systemd.services.grapho-backup = mkIf (cfg.backup.enable && cfg.backup.repository != "") {
|
systemd.services.pal-backup = mkIf (cfg.backup.enable && cfg.backup.repository != "") {
|
||||||
description = "Grapho data backup";
|
description = "Pal data backup";
|
||||||
wants = [ "network-online.target" ];
|
wants = [ "network-online.target" ];
|
||||||
after = [ "network-online.target" ];
|
after = [ "network-online.target" ];
|
||||||
|
|
||||||
@@ -288,18 +288,18 @@ in {
|
|||||||
User = cfg.user;
|
User = cfg.user;
|
||||||
Group = cfg.group;
|
Group = cfg.group;
|
||||||
ExecStart = let
|
ExecStart = let
|
||||||
paths = [ "${graphoDir}/sync" ] ++ cfg.backup.extraPaths;
|
paths = [ "${palDir}/sync" ] ++ cfg.backup.extraPaths;
|
||||||
pathArgs = concatMapStringsSep " " (p: "'${p}'") paths;
|
pathArgs = concatMapStringsSep " " (p: "'${p}'") paths;
|
||||||
in ''
|
in ''
|
||||||
${pkgs.restic}/bin/restic backup \
|
${pkgs.restic}/bin/restic backup \
|
||||||
--cache-dir ${graphoDir}/restic/cache \
|
--cache-dir ${palDir}/restic/cache \
|
||||||
${optionalString (cfg.backup.passwordFile != null) "--password-file ${cfg.backup.passwordFile}"} \
|
${optionalString (cfg.backup.passwordFile != null) "--password-file ${cfg.backup.passwordFile}"} \
|
||||||
-r ${cfg.backup.repository} \
|
-r ${cfg.backup.repository} \
|
||||||
${pathArgs}
|
${pathArgs}
|
||||||
'';
|
'';
|
||||||
ExecStartPost = ''
|
ExecStartPost = ''
|
||||||
${pkgs.restic}/bin/restic forget \
|
${pkgs.restic}/bin/restic forget \
|
||||||
--cache-dir ${graphoDir}/restic/cache \
|
--cache-dir ${palDir}/restic/cache \
|
||||||
${optionalString (cfg.backup.passwordFile != null) "--password-file ${cfg.backup.passwordFile}"} \
|
${optionalString (cfg.backup.passwordFile != null) "--password-file ${cfg.backup.passwordFile}"} \
|
||||||
-r ${cfg.backup.repository} \
|
-r ${cfg.backup.repository} \
|
||||||
${concatStringsSep " " cfg.backup.pruneOpts}
|
${concatStringsSep " " cfg.backup.pruneOpts}
|
||||||
@@ -307,8 +307,8 @@ in {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.timers.grapho-backup = mkIf (cfg.backup.enable && cfg.backup.repository != "") {
|
systemd.timers.pal-backup = mkIf (cfg.backup.enable && cfg.backup.repository != "") {
|
||||||
description = "Grapho backup timer";
|
description = "Pal backup timer";
|
||||||
wantedBy = [ "timers.target" ];
|
wantedBy = [ "timers.target" ];
|
||||||
timerConfig = {
|
timerConfig = {
|
||||||
OnCalendar = cfg.backup.schedule;
|
OnCalendar = cfg.backup.schedule;
|
||||||
@@ -318,7 +318,7 @@ in {
|
|||||||
};
|
};
|
||||||
|
|
||||||
# Server mount (NFS)
|
# Server mount (NFS)
|
||||||
fileSystems."${graphoDir}/server" = mkIf (cfg.server.enable && cfg.server.type == "nfs" && cfg.server.host != "") {
|
fileSystems."${palDir}/server" = mkIf (cfg.server.enable && cfg.server.type == "nfs" && cfg.server.host != "") {
|
||||||
device = "${cfg.server.host}:${cfg.server.remotePath}";
|
device = "${cfg.server.host}:${cfg.server.remotePath}";
|
||||||
fsType = "nfs";
|
fsType = "nfs";
|
||||||
options = [
|
options = [
|
||||||
@@ -330,7 +330,7 @@ in {
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
# Install grapho CLI and dependencies
|
# Install pal CLI and dependencies
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
syncthing
|
syncthing
|
||||||
restic
|
restic
|
||||||
2
setup
2
setup
@@ -133,7 +133,7 @@ EOF
|
|||||||
age_key=$(grep 'AGE-SECRET-KEY' "$AGE_KEY_FILE")
|
age_key=$(grep 'AGE-SECRET-KEY' "$AGE_KEY_FILE")
|
||||||
|
|
||||||
# Build join command for other devices
|
# Build join command for other devices
|
||||||
local join_cmd="nix run '<grapho-flake>' -- '${config_url}' '${age_key}'"
|
local join_cmd="nix run '<pal-flake>' -- '${config_url}' '${age_key}'"
|
||||||
|
|
||||||
# Summary
|
# Summary
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
Reference in New Issue
Block a user