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 CHANGED
@@ -26,44 +26,118 @@ That's it. You're on the Fediverse.
26
26
 
27
27
  ```bash
28
28
  # Setup
29
- fedbox init # Set up your identity
30
- fedbox start # Start the server
31
- fedbox status # Show your profile info
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" # Post a message to followers
35
+ fedbox post "text" # Post a message to followers
35
36
  fedbox follow @user@domain # Follow someone
36
- fedbox timeline # View posts from people you follow
37
+ fedbox timeline # View posts from people you follow
37
38
  fedbox reply <url> "text" # Reply to a post
38
- fedbox posts # View your own posts
39
+ fedbox posts # View your own posts
39
40
 
40
- # Help
41
- fedbox help # Show all commands
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. The easiest way:
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 (e.g., `abc123.ngrok.io`) and add it to `fedbox.json`:
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, and you're federated! Search for `@yourname@abc123.ngrok.io` on Mastodon.
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
- Add `"domain"` for federation with the wider Fediverse.
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
 
@@ -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: `${profileUrl}/inbox`,
53
- outbox: `${profileUrl}/outbox`,
54
- followers: `${profileUrl}/followers`,
55
- following: `${profileUrl}/following`,
59
+ inbox: `${apProfileUrl}/inbox`,
60
+ outbox: `${apProfileUrl}/outbox`,
61
+ followers: `${apProfileUrl}/followers`,
62
+ following: `${apProfileUrl}/following`,
56
63
  endpoints: {
57
- sharedInbox: `${baseUrl}/inbox`
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.7',
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fedbox",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "Zero to Fediverse in 60 seconds",
5
5
  "type": "module",
6
6
  "main": "lib/server.js",