yakmesh 1.3.1 β 1.4.0
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/CHANGELOG.md +20 -0
- package/README.md +6 -0
- package/announcements/discord-v1.3.1.md +55 -0
- package/content/api.js +348 -0
- package/content/index.js +19 -0
- package/content/store.js +683 -0
- package/deploy-packages/README.md +114 -0
- package/deploy-packages/build-packages.ps1 +205 -0
- package/deploy-packages/yakmesh-full/README.md +122 -0
- package/deploy-packages/yakmesh-full/config/Caddyfile +61 -0
- package/deploy-packages/yakmesh-full/config/php.ini +55 -0
- package/deploy-packages/yakmesh-full/config/yakmesh.config.js +100 -0
- package/deploy-packages/yakmesh-full/start.ps1 +193 -0
- package/deploy-packages/yakmesh-full/stop.ps1 +35 -0
- package/deploy-packages/yakmesh-minimal/README.md +69 -0
- package/deploy-packages/yakmesh-minimal/config/Caddyfile +46 -0
- package/deploy-packages/yakmesh-minimal/config/yakmesh.config.js +79 -0
- package/deploy-packages/yakmesh-minimal/start.ps1 +165 -0
- package/deploy-packages/yakmesh-minimal/stop.ps1 +29 -0
- package/discord.md +13 -58
- package/gossip/protocol.js +13 -2
- package/mesh/annex.js +743 -0
- package/mesh/beacon-broadcast.js +4 -5
- package/mesh/echo-ranging.js +3 -4
- package/mesh/phantom-routing.js +3 -4
- package/mesh/pulse-sync.js +3 -4
- package/mesh/temporal-encoder.js +3 -3
- package/package.json +1 -1
- package/server/index.js +64 -3
- package/yakmesh.config.js +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to YAKMESH will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.3.2] - 2026-01-17
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **Public Content Delivery API** - Content-addressed storage for decentralized website hosting
|
|
9
|
+
- `GET /content` - List available content with stats
|
|
10
|
+
- `GET /content/:hash` - Fetch content by hash with optional proof
|
|
11
|
+
- `POST /content` - Publish content with consensus verification
|
|
12
|
+
- Content gossip via mesh for cross-node synchronization
|
|
13
|
+
- Consensus proof system for verified content
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Gossip protocol method calls (use `spreadRumor()` instead of `broadcast()`)
|
|
17
|
+
- Direct messaging via mesh instead of non-existent gossip.sendTo()
|
|
18
|
+
|
|
19
|
+
### Community
|
|
20
|
+
- Added social links: Discord, Telegram, X (Twitter)
|
|
21
|
+
- Created Discord announcement template
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
5
25
|
## [1.3.1] - 2026-01-16
|
|
6
26
|
|
|
7
27
|
### Security
|
package/README.md
CHANGED
|
@@ -179,6 +179,12 @@ See [TRADEMARK.md](TRADEMARK.md) for trademark usage policy.
|
|
|
179
179
|
<br><br>
|
|
180
180
|
<strong><a href="https://yakmesh.dev">yakmesh.dev</a></strong>
|
|
181
181
|
<br><br>
|
|
182
|
+
<p>
|
|
183
|
+
<a href="https://discord.gg/E62tAE2wGh">π¬ Discord</a> β’
|
|
184
|
+
<a href="https://t.me/yakmesh">π± Telegram</a> β’
|
|
185
|
+
<a href="https://x.com/yakmesh_dev">π Twitter</a>
|
|
186
|
+
</p>
|
|
187
|
+
<br>
|
|
182
188
|
<sub>Β© 2026 YAKMESHβ’ Project. Sturdy & Secure.</sub>
|
|
183
189
|
<br>
|
|
184
190
|
<sub>YAKMESHβ’ is a trademark of PeerQuanta, application pending (Serial No. 99594620).</sub>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# 𦬠YAKMESH v1.3.1 β Public Content Delivery + Mesh Peering Confirmed!
|
|
2
|
+
|
|
3
|
+
Hey everyone! Big update today:
|
|
4
|
+
|
|
5
|
+
## β
What's New
|
|
6
|
+
|
|
7
|
+
### π Public Content Delivery API
|
|
8
|
+
We've added a complete **content-addressed storage system** with public delivery:
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
GET /content/:hash β Fetch any content by its hash
|
|
12
|
+
GET /content/:hash/proof β Get consensus proof for verification
|
|
13
|
+
POST /content/publish β Store and gossip content to mesh
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Key features:**
|
|
17
|
+
- Content addressed by SHA3-256 hash (trustless verification)
|
|
18
|
+
- Consensus proofs for light client verification
|
|
19
|
+
- LRU caching for instant edge delivery
|
|
20
|
+
- Automatic mesh sync via gossip protocol
|
|
21
|
+
|
|
22
|
+
### π First Successful LAN Mesh Peering
|
|
23
|
+
Tested and confirmed: **two Yakmesh nodes successfully peered** with matching network fingerprints. The Code Proof Protocol verified both were running identical codebases before allowing the connection.
|
|
24
|
+
|
|
25
|
+
**Connection is as simple as:**
|
|
26
|
+
```powershell
|
|
27
|
+
POST http://localhost:3000/connect
|
|
28
|
+
{ "address": "ws://192.168.1.178:9001" }
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### π± New Social Channels
|
|
32
|
+
We're now on:
|
|
33
|
+
- π¬ **Discord**: https://discord.gg/E62tAE2wGh
|
|
34
|
+
- π± **Telegram**: https://t.me/yakmesh
|
|
35
|
+
- π **Twitter**: https://x.com/yakmesh
|
|
36
|
+
|
|
37
|
+
## π¦ Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install yakmesh@1.3.1
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## π Links
|
|
44
|
+
- π Website: https://yakmesh.dev
|
|
45
|
+
- π GitHub: https://github.com/yakmesh/yakmesh
|
|
46
|
+
- π¦ npm: https://npmjs.com/package/yakmesh
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
**What's next?**
|
|
51
|
+
- Multi-node cluster testing
|
|
52
|
+
- Production deployment
|
|
53
|
+
- Website/webapp hosting demos
|
|
54
|
+
|
|
55
|
+
Questions? Drop them here! π¦¬
|
package/content/api.js
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YAKMESHβ’ Public Content API
|
|
3
|
+
* HTTP endpoints for public content delivery
|
|
4
|
+
*
|
|
5
|
+
* Public (no auth required):
|
|
6
|
+
* - GET /content/:hash - Fetch content by hash
|
|
7
|
+
* - GET /content/:hash/meta - Fetch metadata only
|
|
8
|
+
* - GET /content/:hash/proof - Fetch consensus proof
|
|
9
|
+
* - GET /content/list - List available content
|
|
10
|
+
*
|
|
11
|
+
* Authenticated (rate limited):
|
|
12
|
+
* - POST /content/publish - Publish new content
|
|
13
|
+
* - DELETE /content/:hash - Remove content (owner only)
|
|
14
|
+
*
|
|
15
|
+
* @module content/api
|
|
16
|
+
* @license MIT
|
|
17
|
+
* @copyright 2026 YAKMESH Contributors
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { Router } from 'express';
|
|
21
|
+
import { ContentStore, ContentType, ContentStatus, computeContentHash } from './store.js';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Create content API router
|
|
25
|
+
*/
|
|
26
|
+
export function createContentAPI(contentStore, options = {}) {
|
|
27
|
+
const router = Router();
|
|
28
|
+
|
|
29
|
+
const {
|
|
30
|
+
writeLimiter,
|
|
31
|
+
readLimiter,
|
|
32
|
+
validateString,
|
|
33
|
+
} = options;
|
|
34
|
+
|
|
35
|
+
// =========================================
|
|
36
|
+
// PUBLIC READ ENDPOINTS (No Auth)
|
|
37
|
+
// =========================================
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* GET /content/:hash
|
|
41
|
+
* Fetch content by hash with optional proof
|
|
42
|
+
*
|
|
43
|
+
* Query params:
|
|
44
|
+
* - proof=1 : Include consensus proof in response headers
|
|
45
|
+
* - download=1 : Force download (Content-Disposition)
|
|
46
|
+
*/
|
|
47
|
+
router.get('/:hash', readLimiter, (req, res) => {
|
|
48
|
+
const { hash } = req.params;
|
|
49
|
+
const includeProof = req.query.proof === '1';
|
|
50
|
+
const download = req.query.download === '1';
|
|
51
|
+
|
|
52
|
+
// Get content with metadata
|
|
53
|
+
const result = contentStore.getWithProof(hash);
|
|
54
|
+
|
|
55
|
+
if (!result) {
|
|
56
|
+
return res.status(404).json({
|
|
57
|
+
error: 'Content not found',
|
|
58
|
+
hash,
|
|
59
|
+
hint: 'Content may not have synced yet. Try again later.',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Set content type
|
|
64
|
+
res.setHeader('Content-Type', result.meta?.contentType || 'application/octet-stream');
|
|
65
|
+
res.setHeader('Content-Length', result.meta?.size || result.content.length);
|
|
66
|
+
res.setHeader('X-Content-Hash', result.hash);
|
|
67
|
+
res.setHeader('X-Content-Status', result.meta?.status || 'unknown');
|
|
68
|
+
|
|
69
|
+
// Cache headers (immutable content = cache forever)
|
|
70
|
+
if (result.verified) {
|
|
71
|
+
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
|
|
72
|
+
} else {
|
|
73
|
+
res.setHeader('Cache-Control', 'public, max-age=60');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Include proof in headers if requested
|
|
77
|
+
if (includeProof && result.proof) {
|
|
78
|
+
res.setHeader('X-Consensus-Proof', JSON.stringify(result.proof));
|
|
79
|
+
res.setHeader('X-Verified', result.verified ? 'true' : 'false');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Download disposition
|
|
83
|
+
if (download && result.meta?.name) {
|
|
84
|
+
res.setHeader('Content-Disposition', `attachment; filename="${result.meta.name}"`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Send content
|
|
88
|
+
res.send(result.content);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* GET /content/:hash/meta
|
|
93
|
+
* Fetch metadata only (no content body)
|
|
94
|
+
*/
|
|
95
|
+
router.get('/:hash/meta', readLimiter, (req, res) => {
|
|
96
|
+
const { hash } = req.params;
|
|
97
|
+
const meta = contentStore.getMeta(hash);
|
|
98
|
+
|
|
99
|
+
if (!meta) {
|
|
100
|
+
return res.status(404).json({ error: 'Content not found', hash });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
res.json(meta.toJSON ? meta.toJSON() : meta);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* GET /content/:hash/proof
|
|
108
|
+
* Fetch consensus proof for light client verification
|
|
109
|
+
*/
|
|
110
|
+
router.get('/:hash/proof', readLimiter, (req, res) => {
|
|
111
|
+
const { hash } = req.params;
|
|
112
|
+
const meta = contentStore.getMeta(hash);
|
|
113
|
+
|
|
114
|
+
if (!meta) {
|
|
115
|
+
return res.status(404).json({ error: 'Content not found', hash });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!meta.consensusProof) {
|
|
119
|
+
return res.status(404).json({
|
|
120
|
+
error: 'No consensus proof yet',
|
|
121
|
+
hash,
|
|
122
|
+
status: meta.status,
|
|
123
|
+
hint: 'Content may still be pending consensus.',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
res.json({
|
|
128
|
+
hash,
|
|
129
|
+
verified: meta.status === ContentStatus.VERIFIED,
|
|
130
|
+
proof: meta.consensusProof.toJSON ? meta.consensusProof.toJSON() : meta.consensusProof,
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* GET /content/list
|
|
136
|
+
* List available content
|
|
137
|
+
*
|
|
138
|
+
* Query params:
|
|
139
|
+
* - tag=<tag> : Filter by tag
|
|
140
|
+
* - status=<status> : Filter by status (local, pending, verified)
|
|
141
|
+
* - limit=<n> : Max results (default 100)
|
|
142
|
+
* - offset=<n> : Pagination offset
|
|
143
|
+
*/
|
|
144
|
+
router.get('/', readLimiter, (req, res) => {
|
|
145
|
+
const { tag, status, limit = 100, offset = 0 } = req.query;
|
|
146
|
+
|
|
147
|
+
const items = contentStore.list({
|
|
148
|
+
tag,
|
|
149
|
+
status,
|
|
150
|
+
limit: Math.min(parseInt(limit) || 100, 1000),
|
|
151
|
+
offset: parseInt(offset) || 0,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
res.json({
|
|
155
|
+
items,
|
|
156
|
+
count: items.length,
|
|
157
|
+
stats: contentStore.getStats(),
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* HEAD /content/:hash
|
|
163
|
+
* Check if content exists (useful for CDN/cache validation)
|
|
164
|
+
*/
|
|
165
|
+
router.head('/:hash', readLimiter, (req, res) => {
|
|
166
|
+
const { hash } = req.params;
|
|
167
|
+
|
|
168
|
+
if (contentStore.has(hash)) {
|
|
169
|
+
const meta = contentStore.getMeta(hash);
|
|
170
|
+
res.setHeader('Content-Type', meta?.contentType || 'application/octet-stream');
|
|
171
|
+
res.setHeader('Content-Length', meta?.size || 0);
|
|
172
|
+
res.setHeader('X-Content-Hash', hash);
|
|
173
|
+
res.setHeader('X-Content-Status', meta?.status || 'unknown');
|
|
174
|
+
res.status(200).end();
|
|
175
|
+
} else {
|
|
176
|
+
res.status(404).end();
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// =========================================
|
|
181
|
+
// AUTHENTICATED WRITE ENDPOINTS
|
|
182
|
+
// =========================================
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* POST /content/publish
|
|
186
|
+
* Publish new content to the mesh
|
|
187
|
+
*
|
|
188
|
+
* Body (JSON):
|
|
189
|
+
* {
|
|
190
|
+
* content: <string|object>,
|
|
191
|
+
* contentType?: <mime-type>,
|
|
192
|
+
* name?: <human-readable-name>,
|
|
193
|
+
* tags?: [<tag>, ...],
|
|
194
|
+
* ttl?: <seconds>
|
|
195
|
+
* }
|
|
196
|
+
*
|
|
197
|
+
* Body (multipart/form-data):
|
|
198
|
+
* - file: uploaded file
|
|
199
|
+
* - name: optional name
|
|
200
|
+
* - tags: comma-separated tags
|
|
201
|
+
*/
|
|
202
|
+
router.post('/publish', writeLimiter, async (req, res) => {
|
|
203
|
+
try {
|
|
204
|
+
let content;
|
|
205
|
+
let options = {};
|
|
206
|
+
|
|
207
|
+
// Handle JSON body
|
|
208
|
+
if (req.is('application/json')) {
|
|
209
|
+
if (!req.body.content) {
|
|
210
|
+
return res.status(400).json({ error: 'Content required' });
|
|
211
|
+
}
|
|
212
|
+
content = req.body.content;
|
|
213
|
+
options = {
|
|
214
|
+
contentType: req.body.contentType,
|
|
215
|
+
name: req.body.name,
|
|
216
|
+
tags: req.body.tags || [],
|
|
217
|
+
ttl: req.body.ttl || 0,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
// Handle raw body
|
|
221
|
+
else if (req.body && Buffer.isBuffer(req.body)) {
|
|
222
|
+
content = req.body;
|
|
223
|
+
options = {
|
|
224
|
+
contentType: req.headers['content-type'] || 'application/octet-stream',
|
|
225
|
+
name: req.headers['x-content-name'],
|
|
226
|
+
tags: req.headers['x-content-tags']?.split(',') || [],
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
// Handle form data (basic - for full multipart use multer)
|
|
230
|
+
else if (req.body?.content) {
|
|
231
|
+
content = req.body.content;
|
|
232
|
+
options = {
|
|
233
|
+
contentType: req.body.contentType,
|
|
234
|
+
name: req.body.name,
|
|
235
|
+
tags: typeof req.body.tags === 'string' ? req.body.tags.split(',') : req.body.tags,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
return res.status(400).json({ error: 'Content required in body' });
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Store and publish
|
|
243
|
+
const result = await contentStore.store(content, options);
|
|
244
|
+
|
|
245
|
+
res.status(201).json({
|
|
246
|
+
success: true,
|
|
247
|
+
hash: result.hash,
|
|
248
|
+
status: result.status,
|
|
249
|
+
meta: result.meta?.toJSON ? result.meta.toJSON() : result.meta,
|
|
250
|
+
url: `/content/${result.hash}`,
|
|
251
|
+
});
|
|
252
|
+
} catch (error) {
|
|
253
|
+
res.status(500).json({ error: error.message });
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* POST /content/request
|
|
259
|
+
* Request content from mesh (if not locally available)
|
|
260
|
+
*/
|
|
261
|
+
router.post('/request', writeLimiter, async (req, res) => {
|
|
262
|
+
const { hash } = req.body;
|
|
263
|
+
|
|
264
|
+
if (!hash) {
|
|
265
|
+
return res.status(400).json({ error: 'Hash required' });
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
// Check local first
|
|
270
|
+
if (contentStore.has(hash)) {
|
|
271
|
+
return res.json({
|
|
272
|
+
found: true,
|
|
273
|
+
local: true,
|
|
274
|
+
hash,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Request from mesh
|
|
279
|
+
const result = await contentStore.request(hash);
|
|
280
|
+
|
|
281
|
+
res.json({
|
|
282
|
+
found: true,
|
|
283
|
+
local: false,
|
|
284
|
+
hash: result.hash,
|
|
285
|
+
meta: result.meta,
|
|
286
|
+
});
|
|
287
|
+
} catch (error) {
|
|
288
|
+
res.status(404).json({
|
|
289
|
+
found: false,
|
|
290
|
+
hash,
|
|
291
|
+
error: error.message,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* DELETE /content/:hash
|
|
298
|
+
* Remove content (local only - cannot remove from mesh)
|
|
299
|
+
*/
|
|
300
|
+
router.delete('/:hash', writeLimiter, (req, res) => {
|
|
301
|
+
const { hash } = req.params;
|
|
302
|
+
|
|
303
|
+
if (!contentStore.has(hash)) {
|
|
304
|
+
return res.status(404).json({ error: 'Content not found', hash });
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Note: This only removes locally - content may still exist on other nodes
|
|
308
|
+
contentStore.delete(hash);
|
|
309
|
+
|
|
310
|
+
res.json({
|
|
311
|
+
deleted: true,
|
|
312
|
+
hash,
|
|
313
|
+
note: 'Content removed locally. Other mesh nodes may still have copies.',
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* GET /content/stats
|
|
319
|
+
* Get content store statistics
|
|
320
|
+
*/
|
|
321
|
+
router.get('/stats', readLimiter, (req, res) => {
|
|
322
|
+
res.json(contentStore.getStats());
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* POST /content/verify
|
|
327
|
+
* Compute hash for content without storing
|
|
328
|
+
* Useful for clients to verify content integrity
|
|
329
|
+
*/
|
|
330
|
+
router.post('/verify', readLimiter, (req, res) => {
|
|
331
|
+
const content = req.body.content || req.body;
|
|
332
|
+
|
|
333
|
+
if (!content) {
|
|
334
|
+
return res.status(400).json({ error: 'Content required' });
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const hash = computeContentHash(content);
|
|
338
|
+
|
|
339
|
+
res.json({
|
|
340
|
+
hash,
|
|
341
|
+
exists: contentStore.has(hash),
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
return router;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export default createContentAPI;
|
package/content/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YAKMESHβ’ Content Module
|
|
3
|
+
* Content-addressed storage with public delivery
|
|
4
|
+
*
|
|
5
|
+
* @module content
|
|
6
|
+
* @license MIT
|
|
7
|
+
* @copyright 2026 YAKMESH Contributors
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
ContentStore,
|
|
12
|
+
ContentType,
|
|
13
|
+
ContentStatus,
|
|
14
|
+
ContentMetadata,
|
|
15
|
+
ConsensusProof,
|
|
16
|
+
computeContentHash,
|
|
17
|
+
} from './store.js';
|
|
18
|
+
|
|
19
|
+
export { createContentAPI } from './api.js';
|