File size: 4,707 Bytes
e92be04
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import archiver from 'archiver';
import crypto from 'crypto';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const PUBLIC_DIR = path.join(__dirname, 'public');
const BACKUP_DIR = path.join(PUBLIC_DIR, 'backups');
const SYSTEM_DIR = path.join(__dirname, 'source_mirror');

// Ensure directories exist
if (!fs.existsSync(BACKUP_DIR)) {
    fs.mkdirSync(BACKUP_DIR, { recursive: true });
}

export const Archivist = {
    /**
     * Creates a zip snapshot of all provided papers + system source code.
     * @param {Array} papers - Array of paper objects { id, title, content, ... }
     * @returns {Promise<Object>} - Metadata { zipUrl, ed2kLink, size, date }
     */
    async createSnapshot(papers) {
        const dateStr = new Date().toISOString().split('T')[0];
        const zipName = `p2pclaw_full_system_${dateStr}.zip`;
        const zipPath = path.join(BACKUP_DIR, zipName);
        const relativeZipUrl = `/backups/${zipName}`;

        console.log(`[Archivist] Starting snapshot generation: ${zipName}`);

        // 1. Create ZIP (Papers + System Source)
        await new Promise((resolve, reject) => {
            const output = fs.createWriteStream(zipPath);
            const archive = archiver('zip', { zlib: { level: 9 } });

            output.on('close', resolve);
            archive.on('error', reject);

            archive.pipe(output);

            // Add Manifesto
            archive.append(
                `P2PCLAW Hive Mind - Full System Snapshot ${dateStr}\n\n` +
                `This archive contains:\n` +
                `1. The complete Research Library (Markdown)\n` +
                `2. The Source Code for the P2P Node (system/index.html)\n\n` +
                `INSTRUCTIONS:\n` +
                `- To run the node: Open 'system/index.html' in any browser.\n` +
                `- To help the network: Keep this file shared to ensure redundancy.\n`,
                { name: 'README.txt' }
            );

            // Add Metadata Index
            archive.append(JSON.stringify(papers, null, 2), { name: 'library_index.json' });

            // Add Papers
            papers.forEach(p => {
                const safeTitle = (p.title || 'untitled').replace(/[^a-z0-9]/gi, '_').substring(0, 50);
                const content = p.content || '';
                const meta = `---
title: ${p.title}
author: ${p.author || 'Collective'}
date: ${new Date(p.timestamp || Date.now()).toISOString()}
id: ${p.id}
tags: ${(p.tags || []).join(', ')}
---

${content}`;
                archive.append(meta, { name: `papers/${safeTitle}.md` });
            });

            // Add System Source Code
            if (fs.existsSync(path.join(SYSTEM_DIR, 'index.html'))) {
                archive.file(path.join(SYSTEM_DIR, 'index.html'), { name: 'system/index.html' });
            }
            if (fs.existsSync(path.join(SYSTEM_DIR, 'PROTOCOL.md'))) {
                archive.file(path.join(SYSTEM_DIR, 'PROTOCOL.md'), { name: 'system/PROTOCOL.md' });
            }

            archive.finalize();
        });

        const zipBuffer = fs.readFileSync(zipPath);
        const zipSize = zipBuffer.length;
        console.log(`[Archivist] ZIP created: ${zipSize} bytes`);

        // 2. Seeding Disabled (Phase 55: Torrenting is Banned on Railway)
        // We rely on static ZIP downloads and IPFS redundancy instead.
        const torrentData = { magnetURI: null };

        // 3. Create 'latest' references
        try {
            fs.copyFileSync(zipPath, path.join(BACKUP_DIR, 'latest.zip'));
            console.log(`[Archivist] 'latest' snapshot updated.`);
        } catch (symErr) {
            console.error(`[Archivist] Failed to update 'latest' files:`, symErr);
        }
        
        // 4. Generate eD2K Link (eMule) - Handled with MD4 fallback
        let ed2kLink = '';
        try {
            const md4 = crypto.createHash('md4');
            md4.update(zipBuffer);
            const ed2kHash = md4.digest('hex').toLowerCase();
            ed2kLink = `ed2k://|file|${zipName}|${zipSize}|${ed2kHash}|/`;
        } catch {
            const fallbackHash = crypto.createHash('sha256').update(zipBuffer).digest('hex').slice(0, 32);
            ed2kLink = `ed2k://|file|${zipName}|${zipSize}|${fallbackHash}|/`;
        }

        return {
            filename: zipName,
            size: (zipSize / 1024 / 1024).toFixed(2) + ' MB',
            date: dateStr,
            downloadUrl: relativeZipUrl,
            latestZipUrl: `/backups/latest.zip`,
            magnetLink: null, // Disabled on Railway
            ed2kLink: ed2kLink
        };
    }
};