Skip to content

Commit ee679d2

Browse files
Initial release v0.0.1
- phonepod start: run Solid + Nostr + Git - phonepod tunnel: setup SSH tunnel to relay - phonepod status: show config
0 parents  commit ee679d2

3 files changed

Lines changed: 337 additions & 0 deletions

File tree

README.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# PhonePod
2+
3+
Solid pod on your phone. One command install for Android/Termux.
4+
5+
## Features
6+
7+
- **Solid Pod** - Full LDP server with WebID, WAC
8+
- **Nostr Relay** - Built-in NIP-01 relay
9+
- **Git Server** - Clone and push via HTTP
10+
- **~100MB RAM** - Lighter than a browser tab
11+
12+
## Quick Install (Termux)
13+
14+
```bash
15+
pkg install nodejs-lts
16+
npm install -g phonepod
17+
phonepod start
18+
```
19+
20+
## Usage
21+
22+
```bash
23+
phonepod start # Start the pod
24+
phonepod start --port 3000 # Custom port
25+
phonepod tunnel user@host # Setup SSH tunnel for public access
26+
phonepod status # Show configuration
27+
```
28+
29+
## Public Access
30+
31+
To make your pod publicly accessible, you need a relay server:
32+
33+
```bash
34+
# Setup tunnel (generates SSH key, creates tunnel script)
35+
phonepod tunnel ubuntu@relay.example.com
36+
37+
# Add the printed SSH key to your relay server
38+
# Then start tunnel with PM2:
39+
pm2 start ~/.phonepod/tunnel.sh --name tunnel
40+
pm2 save
41+
```
42+
43+
## Boot Persistence
44+
45+
Install Termux:Boot from F-Droid, then:
46+
47+
```bash
48+
mkdir -p ~/.termux/boot
49+
echo '#!/bin/bash
50+
termux-wake-lock
51+
pm2 resurrect' > ~/.termux/boot/start.sh
52+
chmod +x ~/.termux/boot/start.sh
53+
54+
# Save current processes
55+
pm2 start phonepod -- start
56+
pm2 save
57+
```
58+
59+
## Endpoints
60+
61+
| Endpoint | Description |
62+
|----------|-------------|
63+
| `http://localhost:8080/` | Solid pod root |
64+
| `ws://localhost:8080/relay` | Nostr relay |
65+
| `http://localhost:8080/relay/info` | NIP-11 relay info |
66+
| `git clone http://localhost:8080/` | Git clone |
67+
68+
## Configuration
69+
70+
Config stored in `~/.phonepod/config.json`:
71+
72+
```json
73+
{
74+
"port": 8080,
75+
"nostr": true,
76+
"git": true
77+
}
78+
```
79+
80+
## Requirements
81+
82+
- Android with Termux
83+
- Node.js 18+
84+
- For public access: relay server with SSH access
85+
86+
## License
87+
88+
MIT

bin/phonepod.js

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* PhonePod CLI
5+
*
6+
* Solid pod on your phone - Android/Termux
7+
*
8+
* Usage:
9+
* phonepod start [options] Start the pod
10+
* phonepod setup Interactive setup
11+
* phonepod tunnel <host> Setup SSH tunnel to relay
12+
* phonepod status Show status
13+
*/
14+
15+
import { spawn } from 'child_process';
16+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
17+
import { join } from 'path';
18+
import { homedir } from 'os';
19+
import { createInterface } from 'readline';
20+
21+
const args = process.argv.slice(2);
22+
const command = args[0];
23+
24+
// Config directory
25+
const CONFIG_DIR = join(homedir(), '.phonepod');
26+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
27+
28+
// Ensure config dir exists
29+
if (!existsSync(CONFIG_DIR)) {
30+
mkdirSync(CONFIG_DIR, { recursive: true });
31+
}
32+
33+
// Load or create config
34+
function loadConfig() {
35+
if (existsSync(CONFIG_FILE)) {
36+
return JSON.parse(readFileSync(CONFIG_FILE, 'utf8'));
37+
}
38+
return { port: 8080, nostr: true, git: true };
39+
}
40+
41+
function saveConfig(config) {
42+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
43+
}
44+
45+
// Run JSS with phonepod defaults
46+
function startPod(options = {}) {
47+
const config = loadConfig();
48+
const jssArgs = [
49+
'start',
50+
'--port', String(options.port || config.port || 8080),
51+
];
52+
53+
if (options.nostr ?? config.nostr) jssArgs.push('--nostr');
54+
if (options.git ?? config.git) jssArgs.push('--git');
55+
if (options.root) jssArgs.push('--root', options.root);
56+
57+
console.log('Starting PhonePod...');
58+
console.log(` Port: ${options.port || config.port || 8080}`);
59+
console.log(` Nostr: ${(options.nostr ?? config.nostr) ? 'enabled' : 'disabled'}`);
60+
console.log(` Git: ${(options.git ?? config.git) ? 'enabled' : 'disabled'}`);
61+
console.log('');
62+
63+
// Find jss binary
64+
const jss = spawn('jss', jssArgs, { stdio: 'inherit' });
65+
66+
jss.on('error', (err) => {
67+
if (err.code === 'ENOENT') {
68+
console.error('Error: jss not found. Run: npm install -g javascript-solid-server');
69+
} else {
70+
console.error('Error:', err.message);
71+
}
72+
process.exit(1);
73+
});
74+
75+
jss.on('exit', (code) => process.exit(code || 0));
76+
}
77+
78+
// Setup tunnel
79+
async function setupTunnel(relayHost) {
80+
if (!relayHost) {
81+
console.error('Usage: phonepod tunnel <relay-host>');
82+
console.error('Example: phonepod tunnel relay.example.com');
83+
process.exit(1);
84+
}
85+
86+
const config = loadConfig();
87+
const port = config.port || 8080;
88+
89+
console.log('');
90+
console.log('Setting up SSH tunnel to', relayHost);
91+
console.log('');
92+
93+
// Check for SSH key
94+
const sshKeyPath = join(homedir(), '.ssh', 'id_ed25519');
95+
const sshPubKeyPath = sshKeyPath + '.pub';
96+
97+
if (!existsSync(sshKeyPath)) {
98+
console.log('Generating SSH key...');
99+
const keygen = spawn('ssh-keygen', ['-t', 'ed25519', '-f', sshKeyPath, '-N', ''], { stdio: 'inherit' });
100+
await new Promise(resolve => keygen.on('exit', resolve));
101+
}
102+
103+
// Show public key
104+
if (existsSync(sshPubKeyPath)) {
105+
const pubKey = readFileSync(sshPubKeyPath, 'utf8').trim();
106+
console.log('');
107+
console.log('Add this SSH key to your relay server:');
108+
console.log('');
109+
console.log(pubKey);
110+
console.log('');
111+
}
112+
113+
// Create tunnel script
114+
const tunnelScript = join(CONFIG_DIR, 'tunnel.sh');
115+
writeFileSync(tunnelScript, `#!/bin/bash
116+
exec autossh -M 0 -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -N -R ${port}:localhost:${port} ${relayHost}
117+
`);
118+
spawn('chmod', ['+x', tunnelScript]);
119+
120+
console.log(`Tunnel script created: ${tunnelScript}`);
121+
console.log('');
122+
console.log('To start tunnel manually:');
123+
console.log(` ${tunnelScript}`);
124+
console.log('');
125+
console.log('To add to PM2:');
126+
console.log(` pm2 start ${tunnelScript} --name tunnel`);
127+
console.log(' pm2 save');
128+
console.log('');
129+
130+
// Save relay host to config
131+
config.relayHost = relayHost;
132+
saveConfig(config);
133+
}
134+
135+
// Show status
136+
function showStatus() {
137+
const config = loadConfig();
138+
console.log('');
139+
console.log('PhonePod Configuration:');
140+
console.log('');
141+
console.log(` Port: ${config.port || 8080}`);
142+
console.log(` Nostr: ${config.nostr ? 'enabled' : 'disabled'}`);
143+
console.log(` Git: ${config.git ? 'enabled' : 'disabled'}`);
144+
if (config.relayHost) {
145+
console.log(` Relay: ${config.relayHost}`);
146+
}
147+
console.log('');
148+
console.log(` Config: ${CONFIG_FILE}`);
149+
console.log('');
150+
}
151+
152+
// Help
153+
function showHelp() {
154+
console.log(`
155+
PhonePod - Solid pod on your phone
156+
157+
Usage:
158+
phonepod start [options] Start the pod
159+
phonepod tunnel <host> Setup SSH tunnel to relay
160+
phonepod status Show configuration
161+
phonepod help Show this help
162+
163+
Start Options:
164+
--port <n> Port to listen on (default: 8080)
165+
--no-nostr Disable Nostr relay
166+
--no-git Disable Git server
167+
168+
Examples:
169+
phonepod start
170+
phonepod start --port 3000
171+
phonepod tunnel ubuntu@relay.example.com
172+
`);
173+
}
174+
175+
// Parse start options
176+
function parseStartOptions() {
177+
const options = {};
178+
for (let i = 1; i < args.length; i++) {
179+
if (args[i] === '--port' && args[i + 1]) {
180+
options.port = parseInt(args[++i], 10);
181+
} else if (args[i] === '--no-nostr') {
182+
options.nostr = false;
183+
} else if (args[i] === '--no-git') {
184+
options.git = false;
185+
} else if (args[i] === '--root' && args[i + 1]) {
186+
options.root = args[++i];
187+
}
188+
}
189+
return options;
190+
}
191+
192+
// Main
193+
switch (command) {
194+
case 'start':
195+
startPod(parseStartOptions());
196+
break;
197+
case 'tunnel':
198+
setupTunnel(args[1]);
199+
break;
200+
case 'status':
201+
showStatus();
202+
break;
203+
case 'help':
204+
case '--help':
205+
case '-h':
206+
showHelp();
207+
break;
208+
default:
209+
if (command) {
210+
console.error(`Unknown command: ${command}`);
211+
}
212+
showHelp();
213+
process.exit(command ? 1 : 0);
214+
}

package.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "phonepod",
3+
"version": "0.0.1",
4+
"description": "Solid pod on your phone - one command install for Android/Termux",
5+
"main": "index.js",
6+
"type": "module",
7+
"bin": {
8+
"phonepod": "./bin/phonepod.js"
9+
},
10+
"scripts": {
11+
"test": "echo \"No tests yet\""
12+
},
13+
"repository": {
14+
"type": "git",
15+
"url": "git+https://github.com/JavaScriptSolidServer/phonepod.git"
16+
},
17+
"keywords": [
18+
"solid",
19+
"pod",
20+
"phone",
21+
"android",
22+
"termux",
23+
"nostr",
24+
"decentralized"
25+
],
26+
"author": "",
27+
"license": "MIT",
28+
"bugs": {
29+
"url": "https://github.com/JavaScriptSolidServer/phonepod/issues"
30+
},
31+
"homepage": "https://github.com/JavaScriptSolidServer/phonepod#readme",
32+
"dependencies": {
33+
"javascript-solid-server": "^0.0.60"
34+
}
35+
}

0 commit comments

Comments
 (0)