fedbox 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -27
- package/lib/profile-server.js +37 -7
- package/lib/server.js +23 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,44 +26,118 @@ That's it. You're on the Fediverse.
|
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
28
|
# Setup
|
|
29
|
-
fedbox init
|
|
30
|
-
fedbox start
|
|
31
|
-
fedbox
|
|
29
|
+
fedbox init # Set up your identity
|
|
30
|
+
fedbox start # Start full ActivityPub server
|
|
31
|
+
fedbox profile [port] # Start profile-only server (for editing)
|
|
32
|
+
fedbox status # Show your profile info
|
|
32
33
|
|
|
33
34
|
# Social
|
|
34
|
-
fedbox post "text"
|
|
35
|
+
fedbox post "text" # Post a message to followers
|
|
35
36
|
fedbox follow @user@domain # Follow someone
|
|
36
|
-
fedbox timeline
|
|
37
|
+
fedbox timeline # View posts from people you follow
|
|
37
38
|
fedbox reply <url> "text" # Reply to a post
|
|
38
|
-
fedbox posts
|
|
39
|
+
fedbox posts # View your own posts
|
|
39
40
|
|
|
40
|
-
#
|
|
41
|
-
fedbox
|
|
41
|
+
# Maintenance
|
|
42
|
+
fedbox clean # Remove database
|
|
43
|
+
fedbox clean --all # Remove database and config
|
|
44
|
+
fedbox help # Show all commands
|
|
42
45
|
```
|
|
43
46
|
|
|
47
|
+
## Profile Editing (Web UI)
|
|
48
|
+
|
|
49
|
+
Visit your profile page and click **Edit** to change:
|
|
50
|
+
- Display name
|
|
51
|
+
- Bio
|
|
52
|
+
- Avatar (upload image)
|
|
53
|
+
- Nostr pubkey (64-char hex)
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
fedbox profile
|
|
57
|
+
# Visit http://localhost:3000/
|
|
58
|
+
# Click "Edit" to modify your profile
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Nostr Identity
|
|
62
|
+
|
|
63
|
+
Link your Nostr identity by adding your pubkey (64-char hex):
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"nostrPubkey": "124c0fa99407182ece5a24fad9b7f6674902fc422843d3128d38a0afbee0fdd2"
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Your actor will include:
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"alsoKnownAs": ["did:nostr:124c0fa99407182ece5a24fad9b7f6674902fc422843d3128d38a0afbee0fdd2"]
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
This follows the [did:nostr spec](https://nostrcg.github.io/did-nostr/).
|
|
79
|
+
|
|
80
|
+
## Solid-Compatible URIs
|
|
81
|
+
|
|
82
|
+
Fedbox uses Solid-style WebID URIs:
|
|
83
|
+
|
|
84
|
+
- `/alice` — Profile document (HTML + JSON-LD)
|
|
85
|
+
- `/alice#me` — WebID (the Person/Actor)
|
|
86
|
+
|
|
87
|
+
This makes profiles compatible with both ActivityPub and Solid ecosystems.
|
|
88
|
+
|
|
89
|
+
## Separated Architecture
|
|
90
|
+
|
|
91
|
+
Fedbox supports separating **identity** (profile) from **federation** (AP server):
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
Profile Server (static host) AP Server (fedbox)
|
|
95
|
+
─────────────────────────── ─────────────────
|
|
96
|
+
https://me.example.com https://fedbox.example.com
|
|
97
|
+
/alice /alice/inbox
|
|
98
|
+
/alice#me ──points to──► /alice/outbox
|
|
99
|
+
/alice/followers
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Profile-only config** (`fedbox.json`):
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"username": "alice",
|
|
106
|
+
"displayName": "Alice",
|
|
107
|
+
"summary": "My bio",
|
|
108
|
+
"apServer": "https://fedbox.example.com",
|
|
109
|
+
"nostrPubkey": "124c0fa9..."
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The profile can be hosted anywhere (GitHub Pages, S3, any static server). The AP server handles federation.
|
|
114
|
+
|
|
44
115
|
## Federation (so Mastodon can find you)
|
|
45
116
|
|
|
46
|
-
To federate with the wider Fediverse, you need a public HTTPS URL
|
|
117
|
+
To federate with the wider Fediverse, you need a public HTTPS URL:
|
|
47
118
|
|
|
48
119
|
```bash
|
|
49
120
|
# In another terminal
|
|
50
121
|
ngrok http 3000
|
|
51
122
|
```
|
|
52
123
|
|
|
53
|
-
Copy your ngrok URL
|
|
124
|
+
Copy your ngrok URL and add it to `fedbox.json`:
|
|
54
125
|
|
|
55
126
|
```json
|
|
56
127
|
{
|
|
57
|
-
"domain": "abc123.ngrok.io"
|
|
58
|
-
...
|
|
128
|
+
"domain": "abc123.ngrok.io"
|
|
59
129
|
}
|
|
60
130
|
```
|
|
61
131
|
|
|
62
|
-
Restart your server
|
|
132
|
+
Restart your server. Search for `@yourname@abc123.ngrok.io` on Mastodon.
|
|
63
133
|
|
|
64
134
|
## What You Get
|
|
65
135
|
|
|
66
136
|
- **Your own identity** — `@you@yourdomain.com`
|
|
137
|
+
- **Web UI editing** — Edit profile in browser
|
|
138
|
+
- **Nostr identity** — Link via `did:nostr`
|
|
139
|
+
- **Solid-compatible** — WebID at `/username#me`
|
|
140
|
+
- **Separated architecture** — Profile and AP server can be separate
|
|
67
141
|
- **Post from CLI** — `fedbox post "Hello world"`
|
|
68
142
|
- **Follow anyone** — `fedbox follow @user@mastodon.social`
|
|
69
143
|
- **View timeline** — `fedbox timeline`
|
|
@@ -72,19 +146,6 @@ Restart your server, and you're federated! Search for `@yourname@abc123.ngrok.io
|
|
|
72
146
|
- **HTTP Signature verification** — Secure federation
|
|
73
147
|
- **Rate limiting** — Protection against abuse
|
|
74
148
|
- **Persistent storage** — SQLite database
|
|
75
|
-
- **Beautiful profile page** — Dark theme, shows your posts
|
|
76
|
-
|
|
77
|
-
## How It Works
|
|
78
|
-
|
|
79
|
-
Fedbox uses [microfed](https://github.com/micro-fed/microfed.org) for ActivityPub primitives:
|
|
80
|
-
|
|
81
|
-
- **Profile** — Your actor/identity
|
|
82
|
-
- **Inbox** — Receive follows, likes, boosts, posts
|
|
83
|
-
- **Outbox** — Your posts
|
|
84
|
-
- **WebFinger** — So others can find you
|
|
85
|
-
- **HTTP Signatures** — Secure signed requests
|
|
86
|
-
|
|
87
|
-
Data is stored in SQLite (`data/fedbox.db`).
|
|
88
149
|
|
|
89
150
|
## Configuration
|
|
90
151
|
|
|
@@ -97,12 +158,33 @@ After `fedbox init`, you'll have a `fedbox.json`:
|
|
|
97
158
|
"summary": "Hello, Fediverse!",
|
|
98
159
|
"port": 3000,
|
|
99
160
|
"domain": null,
|
|
161
|
+
"apServer": null,
|
|
162
|
+
"nostrPubkey": null,
|
|
163
|
+
"avatar": null,
|
|
100
164
|
"publicKey": "...",
|
|
101
165
|
"privateKey": "..."
|
|
102
166
|
}
|
|
103
167
|
```
|
|
104
168
|
|
|
105
|
-
|
|
169
|
+
| Field | Description |
|
|
170
|
+
|-------|-------------|
|
|
171
|
+
| `domain` | Your public domain (for federation) |
|
|
172
|
+
| `apServer` | External AP server URL (for separated mode) |
|
|
173
|
+
| `nostrPubkey` | 64-char hex Nostr pubkey |
|
|
174
|
+
| `avatar` | Avatar filename in `public/` |
|
|
175
|
+
|
|
176
|
+
## How It Works
|
|
177
|
+
|
|
178
|
+
Fedbox uses [microfed](https://github.com/micro-fed/microfed.org) for ActivityPub primitives:
|
|
179
|
+
|
|
180
|
+
- **Profile** — Your actor/identity with JSON-LD
|
|
181
|
+
- **Inbox** — Receive follows, likes, boosts, posts
|
|
182
|
+
- **Outbox** — Your posts
|
|
183
|
+
- **WebFinger** — So others can find you
|
|
184
|
+
- **HTTP Signatures** — Secure signed requests
|
|
185
|
+
- **Nodeinfo** — Server discovery
|
|
186
|
+
|
|
187
|
+
Data is stored in SQLite (`data/fedbox.db`).
|
|
106
188
|
|
|
107
189
|
## Requirements
|
|
108
190
|
|
package/lib/profile-server.js
CHANGED
|
@@ -38,6 +38,13 @@ function buildActor(baseUrl) {
|
|
|
38
38
|
const profileUrl = `${baseUrl}/${config.username}`
|
|
39
39
|
const actorId = `${profileUrl}#me`
|
|
40
40
|
|
|
41
|
+
// If apServer is configured, point inbox/outbox there
|
|
42
|
+
// Otherwise, use local URLs (for standalone mode)
|
|
43
|
+
const apBase = config.apServer || baseUrl
|
|
44
|
+
const apProfileUrl = config.apServer
|
|
45
|
+
? `${config.apServer}/${config.username}`
|
|
46
|
+
: profileUrl
|
|
47
|
+
|
|
41
48
|
const actor = {
|
|
42
49
|
'@context': [
|
|
43
50
|
'https://www.w3.org/ns/activitystreams',
|
|
@@ -49,20 +56,24 @@ function buildActor(baseUrl) {
|
|
|
49
56
|
preferredUsername: config.username,
|
|
50
57
|
name: config.displayName,
|
|
51
58
|
summary: config.summary ? `<p>${config.summary}</p>` : '',
|
|
52
|
-
inbox: `${
|
|
53
|
-
outbox: `${
|
|
54
|
-
followers: `${
|
|
55
|
-
following: `${
|
|
59
|
+
inbox: `${apProfileUrl}/inbox`,
|
|
60
|
+
outbox: `${apProfileUrl}/outbox`,
|
|
61
|
+
followers: `${apProfileUrl}/followers`,
|
|
62
|
+
following: `${apProfileUrl}/following`,
|
|
56
63
|
endpoints: {
|
|
57
|
-
sharedInbox: `${
|
|
64
|
+
sharedInbox: `${apBase}/inbox`
|
|
58
65
|
},
|
|
59
|
-
publicKey
|
|
66
|
+
// publicKey lives on the profile (identity), not AP server
|
|
67
|
+
publicKey: config.publicKey ? {
|
|
60
68
|
id: `${profileUrl}#main-key`,
|
|
61
69
|
owner: actorId,
|
|
62
70
|
publicKeyPem: config.publicKey
|
|
63
|
-
}
|
|
71
|
+
} : undefined
|
|
64
72
|
}
|
|
65
73
|
|
|
74
|
+
// Remove publicKey if not set (profile-only mode)
|
|
75
|
+
if (!actor.publicKey) delete actor.publicKey
|
|
76
|
+
|
|
66
77
|
if (config.avatar) {
|
|
67
78
|
actor.icon = {
|
|
68
79
|
type: 'Image',
|
|
@@ -72,6 +83,15 @@ function buildActor(baseUrl) {
|
|
|
72
83
|
}
|
|
73
84
|
}
|
|
74
85
|
|
|
86
|
+
// Add alsoKnownAs for identity linking (Nostr, etc.)
|
|
87
|
+
const alsoKnownAs = []
|
|
88
|
+
if (config.nostrPubkey) {
|
|
89
|
+
alsoKnownAs.push(`did:nostr:${config.nostrPubkey}`)
|
|
90
|
+
}
|
|
91
|
+
if (alsoKnownAs.length > 0) {
|
|
92
|
+
actor.alsoKnownAs = alsoKnownAs
|
|
93
|
+
}
|
|
94
|
+
|
|
75
95
|
return actor
|
|
76
96
|
}
|
|
77
97
|
|
|
@@ -224,6 +244,11 @@ ${JSON.stringify(actor, null, 2)}
|
|
|
224
244
|
<textarea class="edit-textarea" id="edit-summary" placeholder="Write a short bio...">${config.summary || ''}</textarea>
|
|
225
245
|
</div>
|
|
226
246
|
|
|
247
|
+
${config.nostrPubkey ? `<p class="view-mode" style="margin-bottom:1rem"><a href="nostr:${config.nostrPubkey}" style="color:#667eea;font-size:0.85rem">did:nostr:${config.nostrPubkey.slice(0,8)}...</a></p>` : ''}
|
|
248
|
+
<div class="edit-mode" style="margin-bottom:1rem">
|
|
249
|
+
<input type="text" class="edit-input" id="edit-nostr" value="${config.nostrPubkey || ''}" placeholder="Nostr pubkey (64-char hex)" style="font-size:0.85rem;max-width:400px">
|
|
250
|
+
</div>
|
|
251
|
+
|
|
227
252
|
<div class="edit-actions">
|
|
228
253
|
<button class="save-btn" onclick="saveProfile()">Save</button>
|
|
229
254
|
<button class="cancel-btn" onclick="toggleEdit()">Cancel</button>
|
|
@@ -253,6 +278,7 @@ ${JSON.stringify(actor, null, 2)}
|
|
|
253
278
|
const formData = new FormData();
|
|
254
279
|
formData.append('displayName', document.getElementById('edit-name').value);
|
|
255
280
|
formData.append('summary', document.getElementById('edit-summary').value);
|
|
281
|
+
formData.append('nostrPubkey', document.getElementById('edit-nostr').value);
|
|
256
282
|
if (avatarFile) formData.append('avatar', avatarFile);
|
|
257
283
|
try {
|
|
258
284
|
const res = await fetch('/edit', { method: 'POST', body: formData });
|
|
@@ -330,6 +356,10 @@ async function handleEdit(req, res) {
|
|
|
330
356
|
config.summary = parts.summary
|
|
331
357
|
updated = true
|
|
332
358
|
}
|
|
359
|
+
if (parts.nostrPubkey !== undefined) {
|
|
360
|
+
config.nostrPubkey = parts.nostrPubkey || undefined
|
|
361
|
+
updated = true
|
|
362
|
+
}
|
|
333
363
|
if (parts.avatar && parts.avatar.data && parts.avatar.data.length > 0) {
|
|
334
364
|
if (!existsSync('public')) mkdirSync('public', { recursive: true })
|
|
335
365
|
const ext = parts.avatar.contentType?.includes('png') ? 'png' :
|
package/lib/server.js
CHANGED
|
@@ -112,6 +112,16 @@ function buildActor() {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
// Add alsoKnownAs for identity linking (Nostr, etc.)
|
|
116
|
+
const alsoKnownAs = []
|
|
117
|
+
if (config.nostrPubkey) {
|
|
118
|
+
// Format as did:nostr per https://nostrcg.github.io/did-nostr/
|
|
119
|
+
alsoKnownAs.push(`did:nostr:${config.nostrPubkey}`)
|
|
120
|
+
}
|
|
121
|
+
if (alsoKnownAs.length > 0) {
|
|
122
|
+
actor.alsoKnownAs = alsoKnownAs
|
|
123
|
+
}
|
|
124
|
+
|
|
115
125
|
return actor
|
|
116
126
|
}
|
|
117
127
|
|
|
@@ -396,7 +406,7 @@ async function handleRequest(req, res) {
|
|
|
396
406
|
version: '2.1',
|
|
397
407
|
software: {
|
|
398
408
|
name: 'fedbox',
|
|
399
|
-
version: '0.0.
|
|
409
|
+
version: '0.0.9',
|
|
400
410
|
repository: 'https://github.com/micro-fed/fedbox'
|
|
401
411
|
},
|
|
402
412
|
protocols: ['activitypub'],
|
|
@@ -629,6 +639,12 @@ async function handleProfileEdit(req, res) {
|
|
|
629
639
|
updated = true
|
|
630
640
|
}
|
|
631
641
|
|
|
642
|
+
// Update nostr pubkey
|
|
643
|
+
if (parts.nostrPubkey !== undefined) {
|
|
644
|
+
config.nostrPubkey = parts.nostrPubkey || undefined
|
|
645
|
+
updated = true
|
|
646
|
+
}
|
|
647
|
+
|
|
632
648
|
// Handle avatar upload
|
|
633
649
|
if (parts.avatar && parts.avatar.data && parts.avatar.data.length > 0) {
|
|
634
650
|
// Ensure public directory exists
|
|
@@ -904,6 +920,11 @@ ${JSON.stringify(actor, null, 2)}
|
|
|
904
920
|
<textarea class="edit-textarea" id="edit-summary" placeholder="Write a short bio...">${config.summary || ''}</textarea>
|
|
905
921
|
</div>
|
|
906
922
|
|
|
923
|
+
${config.nostrPubkey ? `<p class="nostr-link view-mode" style="margin-bottom:1rem"><a href="nostr:${config.nostrPubkey}" style="color:#667eea;font-size:0.85rem">did:nostr:${config.nostrPubkey.slice(0,8)}...</a></p>` : ''}
|
|
924
|
+
<div class="edit-mode" style="margin-bottom:1rem">
|
|
925
|
+
<input type="text" class="edit-input" id="edit-nostr" value="${config.nostrPubkey || ''}" placeholder="Nostr pubkey (64-char hex)" style="font-size:0.85rem;max-width:400px">
|
|
926
|
+
</div>
|
|
927
|
+
|
|
907
928
|
<div class="stats">
|
|
908
929
|
<div class="stat">
|
|
909
930
|
<div class="stat-num">${followers}</div>
|
|
@@ -964,6 +985,7 @@ ${JSON.stringify(actor, null, 2)}
|
|
|
964
985
|
const formData = new FormData();
|
|
965
986
|
formData.append('displayName', document.getElementById('edit-name').value);
|
|
966
987
|
formData.append('summary', document.getElementById('edit-summary').value);
|
|
988
|
+
formData.append('nostrPubkey', document.getElementById('edit-nostr').value);
|
|
967
989
|
if (avatarFile) {
|
|
968
990
|
formData.append('avatar', avatarFile);
|
|
969
991
|
}
|