diff --git a/src/deb/deb-builder.mts b/src/deb/deb-builder.mts index 1464a4b..34c75cf 100644 --- a/src/deb/deb-builder.mts +++ b/src/deb/deb-builder.mts @@ -1,3 +1,4 @@ +import { createHash, randomBytes } from 'crypto'; import { createGzip } from 'zlib'; import * as fs from 'fs'; @@ -23,6 +24,7 @@ const ReleaseFileTemplate = Label: Ubuntu/Debian Architecture: $ARCH Component: $COMPONENT +Acquire-By-Hash: yes Codename: $DISTRIBUTION\n`; function iterateComponents(repo: DebRepo, callback: (distribution: string, component: string, deb: Artifact[]) => void): void { @@ -47,6 +49,24 @@ function iterateDebs(repo: DebRepo, callback: (distribution: string, component: }); } +function sha512(filePath: string): Promise { + return new Promise((resolve, reject) => { + const hash = createHash('sha512'); + fs.createReadStream(filePath) + .once('end', () => resolve(hash.digest('hex'))) + .once('error', () => reject) + .pipe(hash); + }); +} + +async function handleByHash(filePath: string): Promise { + const byHashDir = path.join(path.dirname(filePath), 'by-hash', 'SHA512'); + const byHashFileName = path.resolve(path.join(byHashDir, await sha512(filePath))); + + createDir(byHashDir); + return fs.promises.copyFile(filePath, byHashFileName); +} + export interface Config { out: string, gpgKeyName: string; @@ -102,15 +122,13 @@ export class DebBuilder implements Deployer { .replace('$COMPONENT', component); const distributionPath = path.join(this.distsPath, distribution); - const releaseFilePath = path.join(distributionPath, 'Release'); - const releaseGpgFilePath = path.join(distributionPath, 'Release.gpg'); + const tempReleaseFile = path.join(this.tempPath, `Release-${randomBytes(4).toString('hex')}`); const inReleaseFilePath = path.join(distributionPath, 'InRelease'); - await fs.promises.writeFile(releaseFilePath, releaseContent); + await fs.promises.writeFile(tempReleaseFile, releaseContent); - await execToolToFile('apt-ftparchive', ['release', distributionPath], releaseFilePath, true); - await execToolToFile('gpg', ['--no-tty', '--default-key', this.config.gpgKeyName, '-abs', '-o', releaseGpgFilePath, releaseFilePath]); - await execToolToFile('gpg', ['--no-tty', '--default-key', this.config.gpgKeyName, '--clearsign', '-o', inReleaseFilePath, releaseFilePath]); + await execToolToFile('apt-ftparchive', ['release', distributionPath], tempReleaseFile, true); + await execToolToFile('gpg', ['--no-tty', '--default-key', this.config.gpgKeyName, '--clearsign', '-o', inReleaseFilePath, tempReleaseFile]); } private async dpkgScanpackages(): Promise { @@ -201,7 +219,7 @@ export class DebBuilder implements Deployer { } private async makeRelease(): Promise<{}> { - const compressFile = (filePath: string): Promise => new Promise(resolve => { + const compressFile = (filePath: string): Promise => new Promise(resolve => { const inp = fs.createReadStream(filePath); const out = fs.createWriteStream(`${filePath}.gz`); @@ -209,11 +227,12 @@ export class DebBuilder implements Deployer { inp.pipe(gzip).pipe(out) .on('finish', () => { - resolve(); + resolve(`${filePath}.gz`); }); }); - const compressPromises: Promise[] = []; + const compressPromises: Promise[] = []; + const byHashPromises: Promise[] = []; iterateComponents(this.config.repo, (distribution, component) => { const componentRoot = path.join(this.distsPath, distribution, component); @@ -234,12 +253,18 @@ export class DebBuilder implements Deployer { } fs.writeFileSync(targetPackagesFile, packagesContent); - + byHashPromises.push(handleByHash(targetPackagesFile)); compressPromises.push(compressFile(targetPackagesFile)); }); }); - await Promise.all(compressPromises); + const compressedPackages = await Promise.all(compressPromises); + + compressedPackages.forEach(packagePath => { + byHashPromises.push(handleByHash(packagePath)); + }); + + await Promise.all(byHashPromises); const releasesPromises: Promise[] = [];