const fs = require('fs'); const crypto = require('crypto'); const zlib = require('zlib'); const { getAuth } = require('./auth_manager'); const REPO_NAME = 'soc-gateway'; function createTar(files) { const chunks = []; for (const file of files) { const name = file.name; const content = Buffer.isBuffer(file.content) ? file.content : Buffer.from(file.content, 'utf8'); const size = content.length; const header = Buffer.alloc(512); header.write(name, 0, 100, 'utf8'); header.write('0000644\0', 100, 8, 'utf8'); header.write('0000000\0', 108, 8, 'utf8'); header.write('0000000\0', 116, 8, 'utf8'); header.write(size.toString(8).padStart(11, '0') + '\0', 124, 12, 'utf8'); const mtime = Math.floor(Date.now() / 1000); header.write(mtime.toString(8).padStart(11, '0') + '\0', 136, 12, 'utf8'); header.write(' ', 148, 8, 'utf8'); header.write('0', 156, 1, 'utf8'); header.write('ustar\0', 257, 6, 'utf8'); header.write('00', 263, 2, 'utf8'); let sum = 0; for (let i = 0; i < 512; i++) sum += header[i]; header.write(sum.toString(8).padStart(6, '0') + '\0 ', 148, 8, 'utf8'); chunks.push(header); chunks.push(content); const remainder = size % 512; if (remainder > 0) chunks.push(Buffer.alloc(512 - remainder)); } chunks.push(Buffer.alloc(1024)); return Buffer.concat(chunks); } async function uploadBlobToGhaymah(blobBuffer, digest, userId) { let authData = await getAuth(); let auth = Buffer.from(userId + ':' + authData.token).toString('base64'); // Check if blob already exists let checkRes = await fetch(`https://registry.ghaymah.systems/v2/${userId}/${REPO_NAME}/blobs/${digest}`, { method: 'HEAD', headers: { 'Authorization': 'Basic ' + auth } }); if (checkRes.status === 401) { authData = await getAuth(true); auth = Buffer.from(userId + ':' + authData.token).toString('base64'); checkRes = await fetch(`https://registry.ghaymah.systems/v2/${userId}/${REPO_NAME}/blobs/${digest}`, { method: 'HEAD', headers: { 'Authorization': 'Basic ' + auth } }); } if (checkRes.ok) { console.log(`Blob ${digest.substring(0, 19)} already exists on Ghaymah!`); return; } // Initiate upload let initRes = await fetch(`https://registry.ghaymah.systems/v2/${userId}/${REPO_NAME}/blobs/uploads/`, { method: 'POST', headers: { 'Authorization': 'Basic ' + auth } }); if (initRes.status === 401) { authData = await getAuth(true); auth = Buffer.from(userId + ':' + authData.token).toString('base64'); initRes = await fetch(`https://registry.ghaymah.systems/v2/${userId}/${REPO_NAME}/blobs/uploads/`, { method: 'POST', headers: { 'Authorization': 'Basic ' + auth } }); } if (initRes.status !== 202) { throw new Error(`Failed to initiate blob upload: ${initRes.status} ${await initRes.text()}`); } let uploadUrl = initRes.headers.get('location'); if (uploadUrl.startsWith('/')) { uploadUrl = 'https://registry.ghaymah.systems' + uploadUrl; } const separator = uploadUrl.includes('?') ? '&' : '?'; const putUrl = `${uploadUrl}${separator}digest=${digest}`; const putRes = await fetch(putUrl, { method: 'PUT', headers: { 'Authorization': 'Basic ' + auth, 'Content-Type': 'application/octet-stream', 'Content-Length': blobBuffer.length.toString() }, body: blobBuffer }); if (putRes.status !== 201) { throw new Error(`Failed to put blob ${digest}: ${putRes.status} ${await putRes.text()}`); } console.log(`Uploaded blob ${digest.substring(0, 19)} (${blobBuffer.length} bytes)`); } async function main() { const { token, userId } = await getAuth(); const auth = Buffer.from(userId + ':' + token).toString('base64'); console.log('=== Step 1: Getting Docker Hub Token for nginx:alpine ==='); const dAuth = await (await fetch('https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/nginx:pull')).json(); const dToken = dAuth.token; console.log('=== Step 2: Fetching linux/amd64 Manifest ==='); const manListRes = await fetch('https://registry-1.docker.io/v2/library/nginx/manifests/alpine', { headers: { 'Authorization': 'Bearer ' + dToken, 'Accept': 'application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.index.v1+json' } }); const manList = await manListRes.json(); const amd64 = manList.manifests?.find(m => m.platform?.architecture === 'amd64' && m.platform?.os === 'linux'); const manifestDigest = amd64 ? amd64.digest : 'alpine'; const manifestRes = await fetch(`https://registry-1.docker.io/v2/library/nginx/manifests/${manifestDigest}`, { headers: { 'Authorization': 'Bearer ' + dToken, 'Accept': 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json' } }); const manifest = await manifestRes.json(); console.log(`Base image has ${manifest.layers.length} layers, config: ${manifest.config.digest}`); console.log('\n=== Step 3: Copying Base Layers from Docker Hub to Ghaymah Registry ==='); for (let i = 0; i < manifest.layers.length; i++) { const layer = manifest.layers[i]; console.log(`[Layer ${i + 1}/${manifest.layers.length}] Fetching ${layer.digest.substring(0, 19)}...`); const layerRes = await fetch(`https://registry-1.docker.io/v2/library/nginx/blobs/${layer.digest}`, { headers: { 'Authorization': 'Bearer ' + dToken } }); const layerBuf = Buffer.from(await layerRes.arrayBuffer()); await uploadBlobToGhaymah(layerBuf, layer.digest, userId); } console.log('\n=== Step 4: Fetching Base Config ==='); const configRes = await fetch(`https://registry-1.docker.io/v2/library/nginx/blobs/${manifest.config.digest}`, { headers: { 'Authorization': 'Bearer ' + dToken } }); const baseConfig = await configRes.json(); console.log('\n=== Step 5: Packaging SOC Command Center Files ==='); const htmlContent = fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/soc_gateway_ui/index.html', 'utf8'); const nginxConf = `server { listen 80; server_name _; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /proxy/ { proxy_pass http://soc-backend-d1895c70f6b2:4000/; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_read_timeout 10s; } } `; // Create uncompressed tar const tarBuf = createTar([ { name: 'usr/share/nginx/html/index.html', content: htmlContent }, { name: 'etc/nginx/conf.d/default.conf', content: nginxConf } ]); const diffId = 'sha256:' + crypto.createHash('sha256').update(tarBuf).digest('hex'); // Gzip compress const gzipBuf = zlib.gzipSync(tarBuf); const customLayerDigest = 'sha256:' + crypto.createHash('sha256').update(gzipBuf).digest('hex'); const customLayerSize = gzipBuf.length; console.log(`Custom layer: ${customLayerDigest} (size: ${customLayerSize}, diffId: ${diffId})`); await uploadBlobToGhaymah(gzipBuf, customLayerDigest, userId); console.log('\n=== Step 6: Creating and Uploading Custom Image Config ==='); const newConfig = { ...baseConfig }; newConfig.rootfs = { type: 'layers', diff_ids: [...(baseConfig.rootfs?.diff_ids || []), diffId] }; newConfig.history = [ ...(baseConfig.history || []), { created: new Date().toISOString(), created_by: 'ADD soc_gateway_ui /usr/share/nginx/html' } ]; const newConfigBuf = Buffer.from(JSON.stringify(newConfig)); const newConfigDigest = 'sha256:' + crypto.createHash('sha256').update(newConfigBuf).digest('hex'); console.log(`New config digest: ${newConfigDigest}`); await uploadBlobToGhaymah(newConfigBuf, newConfigDigest, userId); console.log('\n=== Step 7: Publishing Image Manifest as latest ==='); const newManifest = { schemaVersion: 2, mediaType: 'application/vnd.docker.distribution.manifest.v2+json', config: { mediaType: 'application/vnd.docker.container.image.v1+json', size: newConfigBuf.length, digest: newConfigDigest }, layers: [ ...manifest.layers.map(l => ({ mediaType: 'application/vnd.docker.image.rootfs.diff.tar.gzip', size: l.size, digest: l.digest })), { mediaType: 'application/vnd.docker.image.rootfs.diff.tar.gzip', size: customLayerSize, digest: customLayerDigest } ] }; let curAuth = await getAuth(); let putAuth = Buffer.from(userId + ':' + curAuth.token).toString('base64'); let putManRes = await fetch(`https://registry.ghaymah.systems/v2/${userId}/${REPO_NAME}/manifests/latest`, { method: 'PUT', headers: { 'Authorization': 'Basic ' + putAuth, 'Content-Type': 'application/vnd.docker.distribution.manifest.v2+json' }, body: JSON.stringify(newManifest) }); if (putManRes.status === 401) { curAuth = await getAuth(true); putAuth = Buffer.from(userId + ':' + curAuth.token).toString('base64'); putManRes = await fetch(`https://registry.ghaymah.systems/v2/${userId}/${REPO_NAME}/manifests/latest`, { method: 'PUT', headers: { 'Authorization': 'Basic ' + putAuth, 'Content-Type': 'application/vnd.docker.distribution.manifest.v2+json' }, body: JSON.stringify(newManifest) }); } console.log('Manifest PUT Status:', putManRes.status, putManRes.statusText); if (!putManRes.ok) { throw new Error('Manifest upload failed: ' + (await putManRes.text())); } console.log('\n>>> SUCCESS! DOCKER IMAGE SUCCESSFULLY PUSHED TO REGISTRY.GHAYMAH.SYSTEMS! <<<'); console.log(`Image: registry.ghaymah.systems/${userId}/${REPO_NAME}:latest`); } main().catch(console.error);