| 1 |
# mnw-cli |
| 2 |
|
| 3 |
SSH-based CLI and TUI for the Makenotwork creator platform. Authenticates via SSH key fingerprint, provides an interactive terminal UI for managing projects, and supports non-interactive commands for scripting. |
| 4 |
|
| 5 |
## Prerequisites |
| 6 |
|
| 7 |
- **Rust** (stable toolchain, 2024 edition) |
| 8 |
- **Cross-compilation** (for deployment): `zig`, `cargo-zigbuild`, `x86_64-unknown-linux-gnu` target |
| 9 |
|
| 10 |
## Build and Run |
| 11 |
|
| 12 |
```sh |
| 13 |
# Local development (needs MNW server running on localhost:3000) |
| 14 |
cargo run |
| 15 |
|
| 16 |
# Run with custom port |
| 17 |
SSH_PORT=2222 MNW_API_URL=http://localhost:3000 MNW_SERVICE_TOKEN=<token> cargo run |
| 18 |
|
| 19 |
# Cross-compile for production |
| 20 |
cargo zigbuild --release --target x86_64-unknown-linux-gnu |
| 21 |
``` |
| 22 |
|
| 23 |
## Architecture |
| 24 |
|
| 25 |
mnw-cli is an SSH server built on [russh](https://docs.rs/russh). Each connection spawns an independent handler that authenticates via SSH key fingerprint lookup against the MNW API, then dispatches to either the interactive TUI or a non-interactive command. |
| 26 |
|
| 27 |
|
| 28 |
|
| 29 |
| SSH | `ssh/handler.rs`, `ssh/mod.rs` | Connection handling, public key auth, channel dispatch | |
| 30 |
| TUI | `tui/mod.rs` + 9 screen modules | Interactive terminal UI via [ratatui](https://ratatui.rs/) | |
| 31 |
| Commands | `commands.rs` | Non-interactive text output for scripting | |
| 32 |
| API | `api.rs` | HTTP client for MNW internal API (50+ methods) | |
| 33 |
| SFTP | `ssh/sftp.rs` | File upload handling with per-user staging | |
| 34 |
| Git | `ssh/git.rs` | Git proxy (upload-pack, receive-pack) via subprocess | |
| 35 |
| Staging | `staging.rs` | 1 GB per-user upload quota, 24h TTL auto-cleanup | |
| 36 |
|
| 37 |
### Authentication |
| 38 |
|
| 39 |
1. Client presents SSH public key |
| 40 |
2. Server computes SHA-256 fingerprint |
| 41 |
3. Calls MNW internal API to look up the fingerprint |
| 42 |
4. Returns user info (username, creator tier, suspended status) |
| 43 |
5. Suspended users are rejected at auth stage |
| 44 |
|
| 45 |
## Interactive TUI |
| 46 |
|
| 47 |
Connect via `ssh cli.makenot.work` for a full terminal interface with these screens: |
| 48 |
|
| 49 |
|
| 50 |
|
| 51 |
| Home | Project list, revenue/sales/follower stats with period comparison | |
| 52 |
| Project | Items in a project, publish/unpublish, navigation | |
| 53 |
| Upload | SFTP staged files, metadata editor, S3 presigned upload flow | |
| 54 |
| Item | Item details, versions, edit fields, delete | |
| 55 |
| Blog | Blog posts, create with markdown, publish/draft toggle | |
| 56 |
| Promo | Promo codes, create with discount %, delete | |
| 57 |
| Keys | License keys, generate, revoke | |
| 58 |
| Analytics | Timeseries revenue, period comparison, top projects | |
| 59 |
| Settings | SSH keys, storage usage, profile info | |
| 60 |
|
| 61 |
Navigation: vim keys (h/j/k/l), Enter to select, q/Esc to go back, Tab to switch sections. |
| 62 |
|
| 63 |
## Non-Interactive Commands |
| 64 |
|
| 65 |
```sh |
| 66 |
ssh cli.makenot.work projects # List projects (table format) |
| 67 |
ssh cli.makenot.work projects --json # JSON output for scripting |
| 68 |
ssh cli.makenot.work analytics # Revenue stats (7d default) |
| 69 |
ssh cli.makenot.work analytics --range=30 # 30-day analytics |
| 70 |
ssh cli.makenot.work transactions # Recent transactions |
| 71 |
ssh cli.makenot.work export sales # Export sales as CSV |
| 72 |
ssh cli.makenot.work promo list # List promo codes |
| 73 |
ssh cli.makenot.work promo create CODE 20 # Create 20% discount code |
| 74 |
ssh cli.makenot.work blog list SLUG # List blog posts for a project |
| 75 |
ssh cli.makenot.work help # Show all commands |
| 76 |
``` |
| 77 |
|
| 78 |
All commands support `--json` for machine-readable output. |
| 79 |
|
| 80 |
## File Upload Flow |
| 81 |
|
| 82 |
1. Upload file via SFTP to the SSH server |
| 83 |
2. File lands in per-user staging directory (1 GB quota) |
| 84 |
3. TUI shows staged files with type classification |
| 85 |
4. Fill in metadata (title, project, price) |
| 86 |
5. Server gets presigned S3 URL, uploads file, confirms with MNW API |
| 87 |
6. Staging directory auto-cleaned on 24-hour TTL |
| 88 |
|
| 89 |
## Configuration |
| 90 |
|
| 91 |
|
| 92 |
|
| 93 |
| `SSH_PORT` | 2222 | SSH listen port | |
| 94 |
| `MNW_API_URL` | `http://localhost:3000` | MNW server base URL | |
| 95 |
| `MNW_SERVICE_TOKEN` | *(required)* | Bearer token for internal API | |
| 96 |
| `SSH_HOST_KEY` | `host_ed25519` | Path to host key (auto-generated if missing) | |
| 97 |
| `STAGING_DIR` | `/var/lib/mnw-cli/staging` | Per-user upload staging | |
| 98 |
| `GIT_SUDO_USER` | `git` | System user for git subprocess ops | |
| 99 |
|
| 100 |
## Deployment |
| 101 |
|
| 102 |
```sh |
| 103 |
./deploy/deploy.sh # Full: build + upload + config + restart |
| 104 |
./deploy/deploy.sh --quick # Build + binary + restart |
| 105 |
./deploy/deploy.sh --config # Config files only |
| 106 |
``` |
| 107 |
|
| 108 |
Deploys to hetzner (`100.120.174.96`) as a systemd service with security hardening (ProtectSystem=strict, PrivateTmp, NoNewPrivileges). |
| 109 |
|
| 110 |
## Key Paths |
| 111 |
|
| 112 |
|
| 113 |
|
| 114 |
| SSH server + auth | `src/ssh/handler.rs` | |
| 115 |
| TUI app state + event loop | `src/tui/mod.rs` | |
| 116 |
| API client (50+ methods) | `src/api.rs` | |
| 117 |
| Non-interactive commands | `src/commands.rs` | |
| 118 |
| SFTP + staging | `src/ssh/sftp.rs`, `src/staging.rs` | |
| 119 |
| Git proxy | `src/ssh/git.rs` | |
| 120 |
| Deploy script | `deploy/deploy.sh` | |
| 121 |
| systemd unit | `deploy/mnw-cli.service` | |
| 122 |
|
| 123 |
## License |
| 124 |
|
| 125 |
PolyForm Noncommercial 1.0.0 |
| 126 |
|