rewrite API #3
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
node_modules/
|
||||||
|
.devcontainer/
|
||||||
|
.vscode/
|
||||||
|
.github/
|
||||||
|
*.d.ts
|
||||||
|
*.js
|
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@ -12,7 +12,7 @@
|
|||||||
"skipFiles": ["<node_internals>/**", "node_modules/**"],
|
"skipFiles": ["<node_internals>/**", "node_modules/**"],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"runtimeExecutable": "ts-node",
|
"runtimeExecutable": "ts-node",
|
||||||
"args": ["src/index.ts"]
|
"args": ["src/index.ts", "server"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
9
Dockerfile
Normal file
9
Dockerfile
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
FROM nodejs:latest
|
||||||
|
VOLUME [ "/data" ]
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
EXPOSE 3000
|
||||||
|
ENTRYPOINT [ "node", "src/index.js", "--port", "3000", "--config-path", "/data/.apt_stream.yml" ]
|
49
README.md
49
README.md
@ -1,18 +1,52 @@
|
|||||||
# node-apt
|
# apt-stream
|
||||||
|
|
||||||
Crie seu repositorio apt com o nodejs sem precisas salvar arquivos ou mesmo cuidar do armazenamento.
|
Create your apt repository with nodejs without having to save files or even take care of storage.
|
||||||
|
|
||||||
## Storages
|
## Storages
|
||||||
|
|
||||||
Como esse projeto voçe hospedar um repositorio do apt rapida com os seguintes storages:
|
You can host an apt rapida repository with the following storages:
|
||||||
|
|
||||||
- Docker and OCI images (find `.deb` files in diff's)
|
- Docker and OCI images (find `.deb` files in diff's)
|
||||||
- Github Releases
|
- Github Releases
|
||||||
- Local folders
|
|
||||||
|
## Config file
|
||||||
|
|
||||||
|
Estou ainda mexendo com o arquivo de configuração dos repositorios, e do servidor por enquanto está com está até eu poder mexer direito nele.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Global apt config
|
||||||
|
apt-config:
|
||||||
|
origin: ""
|
||||||
|
enableHash: true # if it is enabled, it may freeze the request a little because I have to wait for the hashes of the "Packages" files
|
||||||
|
sourcesHost: http://localhost:3000
|
||||||
|
# If you want to use a custom sources.list
|
||||||
|
sourcesList: deb [trusted=yes] %s://%s %s main
|
||||||
|
|
||||||
|
repositories:
|
||||||
|
# Example to docker and OCI image
|
||||||
|
- from: oci
|
||||||
|
image: ghcr.io/sirherobrine23/nodeaptexample
|
||||||
|
# Release endpoint config
|
||||||
|
apt-config:
|
||||||
|
origin: github.com/cli/cli
|
||||||
|
lebel: github-cli
|
||||||
|
description: |
|
||||||
|
This is example
|
||||||
|
is this second line of description.
|
||||||
|
|
||||||
|
# Example to github release
|
||||||
|
- from: github_release
|
||||||
|
repository: cli/cli
|
||||||
|
|
||||||
|
- from: github_release
|
||||||
|
owner: cli
|
||||||
|
repository: cli
|
||||||
|
token: ""
|
||||||
|
```
|
||||||
|
|
||||||
## Endpoints
|
## Endpoints
|
||||||
|
|
||||||
Esse projeto foi feito com base nas rotas do `archive.ubuntu.com` e do `ftp.debian.org`.
|
This project was made based on the `archive.ubuntu.com` and `ftp.debian.org` routes.
|
||||||
|
|
||||||
* `GET` /dists/:package-name/Release
|
* `GET` /dists/:package-name/Release
|
||||||
* `GET` /dists/:package-name/main/binary-:arch/Packages
|
* `GET` /dists/:package-name/main/binary-:arch/Packages
|
||||||
@ -22,7 +56,4 @@ Esse projeto foi feito com base nas rotas do `archive.ubuntu.com` e do `ftp.debi
|
|||||||
### Download .deb and package info
|
### Download .deb and package info
|
||||||
|
|
||||||
* `GET` /pool/:package_name/:version/:arch.deb - `Download .deb file`
|
* `GET` /pool/:package_name/:version/:arch.deb - `Download .deb file`
|
||||||
* `GET` /pool/:package_name/:version/:arch - `Config package if exists`
|
* `GET` /pool and / - `Get packages registred to packages registry`
|
||||||
* `GET` /pool/:package_name/:version - `Get version with config`
|
|
||||||
* `GET` /pool/:package_name - `Get all versions with config`
|
|
||||||
* `GET` /pool - `Get packages registred to packages registry`
|
|
@ -1,8 +1,8 @@
|
|||||||
FROM debian:latest
|
FROM debian:latest
|
||||||
ARG DEBIAN_FRONTEND="noninteractive"
|
ARG DEBIAN_FRONTEND="noninteractive"
|
||||||
RUN apt update && \
|
RUN apt update && apt install -y jq curl
|
||||||
cd /tmp && mkdir debs && cd debs && \
|
WORKDIR /tmp/debs
|
||||||
apt-cache depends --recurse --no-recommends --no-suggests --no-conflicts --no-breaks --no-replaces --no-enhances curl wget | grep "^\w" | sort -u | xargs apt download
|
RUN curl -Ssq https://api.github.com/repos/cli/cli/releases | grep "browser_download_url.*deb" | cut -d '"' -f 4 | xargs -n 1 curl -SsL -O
|
||||||
|
|
||||||
FROM scratch
|
FROM scratch
|
||||||
COPY --from=0 /tmp/debs/*.deb /
|
COPY --from=0 /tmp/debs/*.deb /
|
194
package-lock.json
generated
194
package-lock.json
generated
@ -1,22 +1,28 @@
|
|||||||
{
|
{
|
||||||
"name": "node-apt",
|
"name": "apt-stream",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "node-apt",
|
"name": "apt-stream",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "GPL-2.0",
|
"license": "GPL-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sirherobrine23/coreutils": "^2.1.6",
|
"@sirherobrine23/coreutils": "^2.2.1",
|
||||||
|
"cron": "^2.1.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"lzma-native": "^8.0.6",
|
"lzma-native": "^8.0.6",
|
||||||
|
"openpgp": "^5.5.0",
|
||||||
"tar": "^6.1.13",
|
"tar": "^6.1.13",
|
||||||
"yaml": "^2.1.3",
|
"yaml": "^2.2.0",
|
||||||
"yargs": "^17.6.2"
|
"yargs": "^17.6.2"
|
||||||
},
|
},
|
||||||
|
"bin": {
|
||||||
|
"apt-stream": "src/index.js"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/cron": "^2.0.0",
|
||||||
"@types/express": "^4.17.15",
|
"@types/express": "^4.17.15",
|
||||||
"@types/lzma-native": "^4.0.1",
|
"@types/lzma-native": "^4.0.1",
|
||||||
"@types/node": "^18.11.17",
|
"@types/node": "^18.11.17",
|
||||||
@ -198,15 +204,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sirherobrine23/coreutils": {
|
"node_modules/@sirherobrine23/coreutils": {
|
||||||
"version": "2.1.6",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@sirherobrine23/coreutils/-/coreutils-2.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@sirherobrine23/coreutils/-/coreutils-2.2.1.tgz",
|
||||||
"integrity": "sha512-Te/4gJZqjWcEocDMH2ObHiY6muIwrWUZe7ZGQHob0HTRQ4J0bPcfNsLBW9L/UFCjUdTRzjcLyZ0/pssCQD2Zlg==",
|
"integrity": "sha512-IDm39eYtVrSkOkviS6Yny0WcUHzOxAWXywGvubdAImdAfWeqbOCMEJh6/TFdDa+g9OEQVEVBEybL+LjbLD0Dcg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/github": "^5.1.1",
|
"@actions/github": "^5.1.1",
|
||||||
"adm-zip": "^0.5.9",
|
"adm-zip": "^0.5.9",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"got": "^12.5.3",
|
"got": "^12.5.3",
|
||||||
"jsdom": "^20.0.3",
|
"jsdom": "^20.0.3",
|
||||||
|
"lzma-native": "^8.0.6",
|
||||||
"tar": "^6.1.12"
|
"tar": "^6.1.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -272,6 +279,16 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/cron": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cron/-/cron-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-xZM08fqvwIXgghtPVkSPKNgC+JoMQ2OHazEvyTKnNf7aWu1aB6/4lBbQFrb03Td2cUGG7ITzMv3mFYnMu6xRaQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/luxon": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/express": {
|
"node_modules/@types/express": {
|
||||||
"version": "4.17.15",
|
"version": "4.17.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.15.tgz",
|
||||||
@ -295,10 +312,11 @@
|
|||||||
"@types/range-parser": "*"
|
"@types/range-parser": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/http-cache-semantics": {
|
"node_modules/@types/luxon": {
|
||||||
"version": "4.0.1",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.1.0.tgz",
|
||||||
"integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ=="
|
"integrity": "sha512-gCd/HcCgjqSxfMrgtqxCgYk/22NBQfypwFUG7ZAyG/4pqs51WLTcUzVp1hqTbieDYeHS3WoVEh2Yv/2l+7B0Vg==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/lzma-native": {
|
"node_modules/@types/lzma-native": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
@ -414,9 +432,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/adm-zip": {
|
"node_modules/adm-zip": {
|
||||||
"version": "0.5.9",
|
"version": "0.5.10",
|
||||||
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz",
|
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz",
|
||||||
"integrity": "sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg==",
|
"integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0"
|
"node": ">=6.0"
|
||||||
}
|
}
|
||||||
@ -465,6 +483,17 @@
|
|||||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/asn1.js": {
|
||||||
|
"version": "5.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
|
||||||
|
"integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
|
||||||
|
"dependencies": {
|
||||||
|
"bn.js": "^4.0.0",
|
||||||
|
"inherits": "^2.0.1",
|
||||||
|
"minimalistic-assert": "^1.0.0",
|
||||||
|
"safer-buffer": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/asynckit": {
|
"node_modules/asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
@ -475,6 +504,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
|
||||||
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="
|
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/bn.js": {
|
||||||
|
"version": "4.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||||
|
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||||
|
},
|
||||||
"node_modules/body-parser": {
|
"node_modules/body-parser": {
|
||||||
"version": "1.20.1",
|
"version": "1.20.1",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
||||||
@ -528,11 +562,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cacheable-request": {
|
"node_modules/cacheable-request": {
|
||||||
"version": "10.2.3",
|
"version": "10.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.4.tgz",
|
||||||
"integrity": "sha512-6BehRBOs7iurNjAYN9iPazTwFDaMQavJO8W1MEm3s2pH8q/tkPTtLDRUZaweWK87WFGf2Y5wLAlaCJlR5kOz3w==",
|
"integrity": "sha512-IWIea8ei1Ht4dBqvlvh7Gs7EYlMyBhlJybLDUB9sadEqHqftmdNieMLIR5ia3vs8gbjj9t8hXLBpUVg3vcQNbg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/http-cache-semantics": "^4.0.1",
|
|
||||||
"get-stream": "^6.0.1",
|
"get-stream": "^6.0.1",
|
||||||
"http-cache-semantics": "^4.1.0",
|
"http-cache-semantics": "^4.1.0",
|
||||||
"keyv": "^4.5.2",
|
"keyv": "^4.5.2",
|
||||||
@ -642,6 +675,14 @@
|
|||||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/cron": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cron/-/cron-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-Hq7u3P8y7UWYvsZbSKHHJDVG0VO9O7tp2qljxzTScelcTODBfCme8AIhnZsFwmQ9NchZ3hr2uNr+s3DSms7q6w==",
|
||||||
|
"dependencies": {
|
||||||
|
"luxon": "^1.23.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cssom": {
|
"node_modules/cssom": {
|
||||||
"version": "0.5.0",
|
"version": "0.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
|
||||||
@ -1297,6 +1338,14 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/luxon": {
|
||||||
|
"version": "1.28.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz",
|
||||||
|
"integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lzma-native": {
|
"node_modules/lzma-native": {
|
||||||
"version": "8.0.6",
|
"version": "8.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/lzma-native/-/lzma-native-8.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/lzma-native/-/lzma-native-8.0.6.tgz",
|
||||||
@ -1382,6 +1431,11 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/minimalistic-assert": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
|
||||||
|
},
|
||||||
"node_modules/minipass": {
|
"node_modules/minipass": {
|
||||||
"version": "3.3.6",
|
"version": "3.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||||
@ -1525,6 +1579,17 @@
|
|||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/openpgp": {
|
||||||
|
"version": "5.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/openpgp/-/openpgp-5.5.0.tgz",
|
||||||
|
"integrity": "sha512-SpwcJnxrK9Y0HRM6KxSFqkAEOSWEabCH/c8dII/+y2e5f6KvuDG5ZE7JXaPBaVJNE4VUZZeTphxXDoZD0KOHrw==",
|
||||||
|
"dependencies": {
|
||||||
|
"asn1.js": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/optionator": {
|
"node_modules/optionator": {
|
||||||
"version": "0.8.3",
|
"version": "0.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
|
||||||
@ -2206,9 +2271,9 @@
|
|||||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||||
},
|
},
|
||||||
"node_modules/yaml": {
|
"node_modules/yaml": {
|
||||||
"version": "2.1.3",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.0.tgz",
|
||||||
"integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==",
|
"integrity": "sha512-auf7Gi6QwO7HW//GA9seGvTXVGWl1CM/ADWh1+RxtXr6XOxnT65ovDl9fTi4e0monEyJxCHqDpF6QnFDXmJE4g==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
@ -2400,15 +2465,16 @@
|
|||||||
"integrity": "sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw=="
|
"integrity": "sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw=="
|
||||||
},
|
},
|
||||||
"@sirherobrine23/coreutils": {
|
"@sirherobrine23/coreutils": {
|
||||||
"version": "2.1.6",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@sirherobrine23/coreutils/-/coreutils-2.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@sirherobrine23/coreutils/-/coreutils-2.2.1.tgz",
|
||||||
"integrity": "sha512-Te/4gJZqjWcEocDMH2ObHiY6muIwrWUZe7ZGQHob0HTRQ4J0bPcfNsLBW9L/UFCjUdTRzjcLyZ0/pssCQD2Zlg==",
|
"integrity": "sha512-IDm39eYtVrSkOkviS6Yny0WcUHzOxAWXywGvubdAImdAfWeqbOCMEJh6/TFdDa+g9OEQVEVBEybL+LjbLD0Dcg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@actions/github": "^5.1.1",
|
"@actions/github": "^5.1.1",
|
||||||
"adm-zip": "^0.5.9",
|
"adm-zip": "^0.5.9",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"got": "^12.5.3",
|
"got": "^12.5.3",
|
||||||
"jsdom": "^20.0.3",
|
"jsdom": "^20.0.3",
|
||||||
|
"lzma-native": "^8.0.6",
|
||||||
"tar": "^6.1.12"
|
"tar": "^6.1.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2468,6 +2534,16 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/cron": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cron/-/cron-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-xZM08fqvwIXgghtPVkSPKNgC+JoMQ2OHazEvyTKnNf7aWu1aB6/4lBbQFrb03Td2cUGG7ITzMv3mFYnMu6xRaQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/luxon": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/express": {
|
"@types/express": {
|
||||||
"version": "4.17.15",
|
"version": "4.17.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.15.tgz",
|
||||||
@ -2491,10 +2567,11 @@
|
|||||||
"@types/range-parser": "*"
|
"@types/range-parser": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/http-cache-semantics": {
|
"@types/luxon": {
|
||||||
"version": "4.0.1",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.1.0.tgz",
|
||||||
"integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ=="
|
"integrity": "sha512-gCd/HcCgjqSxfMrgtqxCgYk/22NBQfypwFUG7ZAyG/4pqs51WLTcUzVp1hqTbieDYeHS3WoVEh2Yv/2l+7B0Vg==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/lzma-native": {
|
"@types/lzma-native": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
@ -2598,9 +2675,9 @@
|
|||||||
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA=="
|
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA=="
|
||||||
},
|
},
|
||||||
"adm-zip": {
|
"adm-zip": {
|
||||||
"version": "0.5.9",
|
"version": "0.5.10",
|
||||||
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz",
|
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz",
|
||||||
"integrity": "sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg=="
|
"integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ=="
|
||||||
},
|
},
|
||||||
"agent-base": {
|
"agent-base": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
@ -2634,6 +2711,17 @@
|
|||||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
||||||
},
|
},
|
||||||
|
"asn1.js": {
|
||||||
|
"version": "5.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
|
||||||
|
"integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
|
||||||
|
"requires": {
|
||||||
|
"bn.js": "^4.0.0",
|
||||||
|
"inherits": "^2.0.1",
|
||||||
|
"minimalistic-assert": "^1.0.0",
|
||||||
|
"safer-buffer": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"asynckit": {
|
"asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
@ -2644,6 +2732,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
|
||||||
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="
|
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="
|
||||||
},
|
},
|
||||||
|
"bn.js": {
|
||||||
|
"version": "4.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||||
|
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||||
|
},
|
||||||
"body-parser": {
|
"body-parser": {
|
||||||
"version": "1.20.1",
|
"version": "1.20.1",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
||||||
@ -2689,11 +2782,10 @@
|
|||||||
"integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w=="
|
"integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w=="
|
||||||
},
|
},
|
||||||
"cacheable-request": {
|
"cacheable-request": {
|
||||||
"version": "10.2.3",
|
"version": "10.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.4.tgz",
|
||||||
"integrity": "sha512-6BehRBOs7iurNjAYN9iPazTwFDaMQavJO8W1MEm3s2pH8q/tkPTtLDRUZaweWK87WFGf2Y5wLAlaCJlR5kOz3w==",
|
"integrity": "sha512-IWIea8ei1Ht4dBqvlvh7Gs7EYlMyBhlJybLDUB9sadEqHqftmdNieMLIR5ia3vs8gbjj9t8hXLBpUVg3vcQNbg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/http-cache-semantics": "^4.0.1",
|
|
||||||
"get-stream": "^6.0.1",
|
"get-stream": "^6.0.1",
|
||||||
"http-cache-semantics": "^4.1.0",
|
"http-cache-semantics": "^4.1.0",
|
||||||
"keyv": "^4.5.2",
|
"keyv": "^4.5.2",
|
||||||
@ -2776,6 +2868,14 @@
|
|||||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"cron": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cron/-/cron-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-Hq7u3P8y7UWYvsZbSKHHJDVG0VO9O7tp2qljxzTScelcTODBfCme8AIhnZsFwmQ9NchZ3hr2uNr+s3DSms7q6w==",
|
||||||
|
"requires": {
|
||||||
|
"luxon": "^1.23.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"cssom": {
|
"cssom": {
|
||||||
"version": "0.5.0",
|
"version": "0.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
|
||||||
@ -3262,6 +3362,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz",
|
||||||
"integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ=="
|
"integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ=="
|
||||||
},
|
},
|
||||||
|
"luxon": {
|
||||||
|
"version": "1.28.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz",
|
||||||
|
"integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ=="
|
||||||
|
},
|
||||||
"lzma-native": {
|
"lzma-native": {
|
||||||
"version": "8.0.6",
|
"version": "8.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/lzma-native/-/lzma-native-8.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/lzma-native/-/lzma-native-8.0.6.tgz",
|
||||||
@ -3316,6 +3421,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz",
|
||||||
"integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg=="
|
"integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg=="
|
||||||
},
|
},
|
||||||
|
"minimalistic-assert": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
|
||||||
|
},
|
||||||
"minipass": {
|
"minipass": {
|
||||||
"version": "3.3.6",
|
"version": "3.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||||
@ -3418,6 +3528,14 @@
|
|||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"openpgp": {
|
||||||
|
"version": "5.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/openpgp/-/openpgp-5.5.0.tgz",
|
||||||
|
"integrity": "sha512-SpwcJnxrK9Y0HRM6KxSFqkAEOSWEabCH/c8dII/+y2e5f6KvuDG5ZE7JXaPBaVJNE4VUZZeTphxXDoZD0KOHrw==",
|
||||||
|
"requires": {
|
||||||
|
"asn1.js": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"optionator": {
|
"optionator": {
|
||||||
"version": "0.8.3",
|
"version": "0.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
|
||||||
@ -3906,9 +4024,9 @@
|
|||||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||||
},
|
},
|
||||||
"yaml": {
|
"yaml": {
|
||||||
"version": "2.1.3",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.0.tgz",
|
||||||
"integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg=="
|
"integrity": "sha512-auf7Gi6QwO7HW//GA9seGvTXVGWl1CM/ADWh1+RxtXr6XOxnT65ovDl9fTi4e0monEyJxCHqDpF6QnFDXmJE4g=="
|
||||||
},
|
},
|
||||||
"yargs": {
|
"yargs": {
|
||||||
"version": "17.6.2",
|
"version": "17.6.2",
|
||||||
|
21
package.json
21
package.json
@ -1,21 +1,21 @@
|
|||||||
{
|
{
|
||||||
"name": "node-apt",
|
"name": "apt-stream",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Replace",
|
"description": "Replace",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"types": "./src/index.d.ts",
|
"types": "./src/index.d.ts",
|
||||||
"private": false,
|
"private": false,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"homepage": "https://github.com/Sirherobrine23/node-apt#readme",
|
"homepage": "https://github.com/Sirherobrine23/apt-stream#readme",
|
||||||
"author": "Matheus Sampaio Queiroga <srherobrine20@gmail.com> (https://sirherobrine23.org/)",
|
"author": "Matheus Sampaio Queiroga <srherobrine20@gmail.com> (https://sirherobrine23.org/)",
|
||||||
"license": "GPL-2.0",
|
"license": "GPL-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/Sirherobrine23/node-apt.git"
|
"url": "git+https://github.com/Sirherobrine23/apt-stream.git"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/Sirherobrine23/node-apt/issues"
|
"url": "https://github.com/Sirherobrine23/apt-stream/issues"
|
||||||
},
|
},
|
||||||
"sponsor": {
|
"sponsor": {
|
||||||
"url": "https://github.com/sponsors/Sirherobrine23"
|
"url": "https://github.com/sponsors/Sirherobrine23"
|
||||||
@ -26,10 +26,15 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
},
|
},
|
||||||
|
"bin": {
|
||||||
|
"apt-stream": "./src/index.js"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "ts-node src/index.ts"
|
"start": "ts-node src/index.ts",
|
||||||
|
"build": "tsc"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/cron": "^2.0.0",
|
||||||
"@types/express": "^4.17.15",
|
"@types/express": "^4.17.15",
|
||||||
"@types/lzma-native": "^4.0.1",
|
"@types/lzma-native": "^4.0.1",
|
||||||
"@types/node": "^18.11.17",
|
"@types/node": "^18.11.17",
|
||||||
@ -39,11 +44,13 @@
|
|||||||
"typescript": "^4.9.4"
|
"typescript": "^4.9.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sirherobrine23/coreutils": "^2.1.6",
|
"@sirherobrine23/coreutils": "^2.2.1",
|
||||||
|
"cron": "^2.1.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"lzma-native": "^8.0.6",
|
"lzma-native": "^8.0.6",
|
||||||
|
"openpgp": "^5.5.0",
|
||||||
"tar": "^6.1.13",
|
"tar": "^6.1.13",
|
||||||
"yaml": "^2.1.3",
|
"yaml": "^2.2.0",
|
||||||
"yargs": "^17.6.2"
|
"yargs": "^17.6.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
189
repoconfig.yml
189
repoconfig.yml
@ -1,13 +1,180 @@
|
|||||||
version: 1
|
apt-config:
|
||||||
repos:
|
portListen: 3000
|
||||||
- repo: ghcr.io/sirherobrine23/nodeaptexample:sha256:c7b3eb9435f67aa08a2e98fdaa5c4548bb8e410fc804ed352bb03dcb212dde96
|
origin: "Sirherobrine23"
|
||||||
from: oci
|
label: Sirherobrine23-repo
|
||||||
|
enableHash: true
|
||||||
|
sourcesHost: http://localhost:3000
|
||||||
|
pgpKey:
|
||||||
|
private: |
|
||||||
|
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||||
|
|
||||||
# - repo: Sirherobrine23/ghcrAptRepository
|
xcZYBGOmMPoBEACN8tvJkwhyRagB68x0/tkHM39Wy7aMAVQpNrPZNn2vuppP
|
||||||
# from: release
|
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
|
||||||
|
=8o1a
|
||||||
|
-----END PGP PRIVATE KEY BLOCK-----
|
||||||
|
public: |
|
||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
# - repo: The-Bds-Maneger/BedrockFetch
|
xsFNBGOmMPoBEACN8tvJkwhyRagB68x0/tkHM39Wy7aMAVQpNrPZNn2vuppP
|
||||||
# from: release
|
Uol5SBUxUBo1NaxlqVHf8RGrppBT6m7Jjv4EzhoK/lGOoZ4CDcQDVrqYLnZ1
|
||||||
|
4+dB7GsgFK4c5QpRhQOv/Y6ffGl7XTnuV6UBIKSfJr6BAL8/wOlw9LYw4RgY
|
||||||
# - repo: cli/cli
|
7QsEnVc8IEHsbVSyydtd4WeKC8toPvrGrdvyGgOPOgrZ3m/0l41Gg6Awyvs8
|
||||||
# from: release
|
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
|
||||||
|
iKy5h0djTyX95owar8j3Lg==
|
||||||
|
=Xjxj
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
|
repositories:
|
||||||
|
- from: github_release
|
||||||
|
repository: cli
|
||||||
|
owner: cli
|
||||||
|
335
src/aptRepo.ts
335
src/aptRepo.ts
@ -1,335 +0,0 @@
|
|||||||
import { format } from "node:util";
|
|
||||||
import { WriteStream } from "node:fs";
|
|
||||||
import { PassThrough, Readable, Writable } from "node:stream";
|
|
||||||
import { getConfig } from "./repoConfig.js";
|
|
||||||
import * as ghcr from "./oci_registry.js";
|
|
||||||
import * as release from "./githubRelease.js";
|
|
||||||
import zlib from "node:zlib";
|
|
||||||
import express from "express";
|
|
||||||
import coreUtils from "@sirherobrine23/coreutils";
|
|
||||||
|
|
||||||
export type packagesObject = {
|
|
||||||
Package: string
|
|
||||||
Version: string,
|
|
||||||
/** endpoint folder file */
|
|
||||||
Filename: string,
|
|
||||||
"Installed-Size": number,
|
|
||||||
Maintainer: string,
|
|
||||||
Architecture: string,
|
|
||||||
Depends?: string,
|
|
||||||
Homepage?: string,
|
|
||||||
Section?: string,
|
|
||||||
Priority?: string,
|
|
||||||
Size: number,
|
|
||||||
MD5sum: string,
|
|
||||||
SHA256: string,
|
|
||||||
Description?: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ReleaseOptions = {
|
|
||||||
Origin?: string,
|
|
||||||
Suite?: string,
|
|
||||||
Archive?: string,
|
|
||||||
lebel?: string,
|
|
||||||
Codename?: string,
|
|
||||||
Architectures: string[],
|
|
||||||
Components: string[],
|
|
||||||
Description?: string,
|
|
||||||
sha256?: {sha256: string, size: number, file: string}[]
|
|
||||||
};
|
|
||||||
|
|
||||||
export function parseDebControl(control: string|Buffer) {
|
|
||||||
if (Buffer.isBuffer(control)) control = control.toString();
|
|
||||||
const controlObject: {[key: string]: string} = {};
|
|
||||||
for (const line of control.split(/\r?\n/)) {
|
|
||||||
if (/^[\w\S]+:/.test(line)) {
|
|
||||||
const [, key, value] = line.match(/^([\w\S]+):(.*)$/);
|
|
||||||
controlObject[key.trim()] = value.trim();
|
|
||||||
} else {
|
|
||||||
controlObject[Object.keys(controlObject).at(-1)] += line;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return controlObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function mountRelease(repo: ReleaseOptions) {
|
|
||||||
let data = [`Lebel: ${repo.lebel||repo.Origin}`];
|
|
||||||
data.push(`Date: ${(new Date()).toUTCString()}`);
|
|
||||||
if (repo.Origin) data.push(`Origin: ${repo.Origin}`);
|
|
||||||
if (repo.Suite) data.push(`Suite: ${repo.Suite}`);
|
|
||||||
else if (repo.Archive) data.push(`Archive: ${repo.Archive}`);
|
|
||||||
if (repo.Codename) data.push(`Codename: ${repo.Codename}`);
|
|
||||||
data.push(`Architectures: ${repo.Architectures.join(" ")}\nComponents: ${repo.Components.join(" ")}`);
|
|
||||||
if (repo.Description) data.push(`Description: ${repo.Description}`);
|
|
||||||
if (repo.sha256 && repo.sha256?.length > 0) {
|
|
||||||
data.push("SHA256:");
|
|
||||||
for (const file of repo.sha256) {
|
|
||||||
data.push(` ${file.sha256} ${file.size} ${file.file}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data.join("\n")+"\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
export type registryPackageData = {
|
|
||||||
name: string,
|
|
||||||
getStrem: () => Promise<Readable>,
|
|
||||||
version: string,
|
|
||||||
arch: string,
|
|
||||||
size?: number,
|
|
||||||
from?: string,
|
|
||||||
signature?: {
|
|
||||||
sha256: string,
|
|
||||||
md5: string,
|
|
||||||
},
|
|
||||||
packageConfig?: {
|
|
||||||
[key: string]: string;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
type localRegister = {
|
|
||||||
[name: string]: {
|
|
||||||
[version: string]: {
|
|
||||||
[arch: string]: {
|
|
||||||
getStream: registryPackageData["getStrem"],
|
|
||||||
config?: registryPackageData["packageConfig"],
|
|
||||||
signature?: registryPackageData["signature"],
|
|
||||||
size?: registryPackageData["size"],
|
|
||||||
from?: registryPackageData["from"],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export class localRegistryManeger {
|
|
||||||
public packageRegister: localRegister = {};
|
|
||||||
prettyPackages() {
|
|
||||||
const packagePretty = {};
|
|
||||||
for (const name in this.packageRegister) {
|
|
||||||
if (!packagePretty[name]) packagePretty[name] = [];
|
|
||||||
for (const version in this.packageRegister[name]) {
|
|
||||||
for (const arch in this.packageRegister[name][version]) {
|
|
||||||
packagePretty[name].push({
|
|
||||||
version,
|
|
||||||
arch,
|
|
||||||
config: this.packageRegister[name][version][arch].config,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return packagePretty;
|
|
||||||
}
|
|
||||||
|
|
||||||
public registerPackage(packageConfig: registryPackageData) {
|
|
||||||
packageConfig.name = packageConfig.name?.toLowerCase()?.trim();
|
|
||||||
packageConfig.version = packageConfig?.version?.trim();
|
|
||||||
if (!this.packageRegister) this.packageRegister = {};
|
|
||||||
if (!this.packageRegister[packageConfig.name]) this.packageRegister[packageConfig.name] = {};
|
|
||||||
if (!this.packageRegister[packageConfig.name][packageConfig.version]) this.packageRegister[packageConfig.name][packageConfig.version] = {};
|
|
||||||
console.log("[Internal package maneger]: Registry %s with version %s and arch %s", packageConfig.name, packageConfig.version, packageConfig.arch);
|
|
||||||
this.packageRegister[packageConfig.name][packageConfig.version][packageConfig.arch] = {
|
|
||||||
getStream: packageConfig.getStrem,
|
|
||||||
config: packageConfig.packageConfig,
|
|
||||||
signature: packageConfig.signature,
|
|
||||||
size: packageConfig.size,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async createPackage(compress: boolean, name: string, arch: string, res: WriteStream|Writable|PassThrough = new PassThrough({read(){}, write(){}})) {
|
|
||||||
const sign: {sha256?: {hash: string, size: number}, md5?: {hash: string, size: number}, size: number} = {size: 0};
|
|
||||||
const Packages: packagesObject[] = [];
|
|
||||||
if (!this.packageRegister[name]) throw new Error(format("%s not found", name));
|
|
||||||
for (const version in this.packageRegister[name]) {
|
|
||||||
const packageData = this.packageRegister[name][version][arch];
|
|
||||||
if (!packageData) continue;
|
|
||||||
Packages.push({
|
|
||||||
Package: name,
|
|
||||||
Version: version,
|
|
||||||
Filename: format("pool/%s/%s/%s.deb", name, version, arch),
|
|
||||||
"Installed-Size": parseInt(packageData.config?.["Installed-Size"]||"0"),
|
|
||||||
Maintainer: packageData.config?.Maintainer||"node-apt",
|
|
||||||
Architecture: packageData.config?.Architecture||"all",
|
|
||||||
Depends: packageData.config?.Depends,
|
|
||||||
Homepage: packageData.config?.Homepage,
|
|
||||||
Section: packageData.config?.Section,
|
|
||||||
Priority: packageData.config?.Priority,
|
|
||||||
Size: packageData.size||0,
|
|
||||||
MD5sum: packageData.signature?.md5,
|
|
||||||
SHA256: packageData.signature?.sha256,
|
|
||||||
Description: packageData.config?.Description,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let vsize = 0;
|
|
||||||
const rawStream = new PassThrough({read(){}, write(){}});
|
|
||||||
if (compress) {
|
|
||||||
const ReadStream = new PassThrough({read(){}, write(){}});
|
|
||||||
const gzip = ReadStream.pipe(zlib.createGzip());
|
|
||||||
gzip.pipe(res);
|
|
||||||
gzip.pipe(rawStream);
|
|
||||||
gzip.on("data", (chunk) => vsize += chunk.length);
|
|
||||||
res = ReadStream;
|
|
||||||
} else rawStream.on("data", (chunk) => vsize += chunk.length);
|
|
||||||
let waitHashs = coreUtils.extendsCrypto.createSHA256_MD5(rawStream, "both", new Promise(done => {
|
|
||||||
rawStream.on("end", () => setTimeout(done, 500));
|
|
||||||
rawStream.on("close", () => setTimeout(done, 500));
|
|
||||||
})).then(data => {
|
|
||||||
sign.sha256 = {hash: data.sha256, size: vsize};
|
|
||||||
sign.md5 = {hash: data.md5, size: vsize};
|
|
||||||
});
|
|
||||||
for (const packageInfo of Packages) {
|
|
||||||
let packageData = [];
|
|
||||||
for (let i in packageInfo) packageData.push(`${i}: ${packageInfo[i]||""}`);
|
|
||||||
const configLine = packageData.join("\n")+"\n\n";
|
|
||||||
sign.size += configLine.length;
|
|
||||||
rawStream.push(Buffer.from(configLine, "utf8"));
|
|
||||||
rawStream.write(Buffer.from(configLine, "utf8"));
|
|
||||||
if (res instanceof PassThrough) res.push(configLine);
|
|
||||||
else res.write(configLine);
|
|
||||||
}
|
|
||||||
res.end();
|
|
||||||
res.destroy();
|
|
||||||
rawStream.end();
|
|
||||||
rawStream.destroy();
|
|
||||||
await waitHashs;
|
|
||||||
return sign;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function mainConfig(configPath: string) {
|
|
||||||
const config = await getConfig(configPath);
|
|
||||||
const packageReg = new localRegistryManeger();
|
|
||||||
Promise.all(config.repos.map(async repo => {
|
|
||||||
if (repo.from === "release") return release.fullConfig({config: repo.repo, githubToken: repo?.auth?.password}, packageReg).catch(console.error);
|
|
||||||
if (repo.from === "oci") return ghcr.fullConfig({image: repo.repo, targetInfo: repo.ociConfig}, packageReg).catch(console.error);
|
|
||||||
}));
|
|
||||||
return packageReg;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type apiConfig = {
|
|
||||||
configPath: string,
|
|
||||||
portListen?: number,
|
|
||||||
callback?: (port: number) => void,
|
|
||||||
repositoryOptions?: {
|
|
||||||
Origin?: string,
|
|
||||||
lebel?: string,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
export async function createAPI(apiConfig: apiConfig) {
|
|
||||||
const mainRegister = await mainConfig(apiConfig.configPath);
|
|
||||||
const app = express();
|
|
||||||
app.use(express.json());
|
|
||||||
app.use(express.urlencoded({extended: true}));
|
|
||||||
app.use((_req, res, next) => {
|
|
||||||
res.json = (data) => res.setHeader("Content-Type", "application/json").send(JSON.stringify(data, null, 2));
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Request log
|
|
||||||
app.use((req, _res, next) => {
|
|
||||||
next();
|
|
||||||
console.log("[%s]: From: %s, path: %s", req.protocol, req.ip, req.path);
|
|
||||||
});
|
|
||||||
|
|
||||||
// /dists
|
|
||||||
// Signed Release with gpg
|
|
||||||
// app.get("/dists/:suite/InRelease", (req, res) => {});
|
|
||||||
// app.get("/dists/:package/Release.gpg", (req, res) => {});
|
|
||||||
|
|
||||||
// Root release
|
|
||||||
app.get("/dists/:package/Release", async (req, res) => {
|
|
||||||
if (!mainRegister.packageRegister[req.params.package]) return res.status(404).json({error: "Package not registred"});
|
|
||||||
const Archs: string[] = [];
|
|
||||||
Object.keys(mainRegister.packageRegister[req.params.package]).forEach(version => Object.keys(mainRegister.packageRegister[req.params.package][version]).forEach(arch => (!Archs.includes(arch.toLowerCase()))?Archs.push(arch.toLowerCase()):null));
|
|
||||||
const shas: ReleaseOptions["sha256"] = [];
|
|
||||||
for (const arch of Archs) {
|
|
||||||
const Packagegz = await mainRegister.createPackage(true, req.params.package, arch);
|
|
||||||
const Package = await mainRegister.createPackage(false, req.params.package, arch);
|
|
||||||
if (Packagegz?.sha256?.hash) shas.push({file: format("main/binary-%s/Packages.gz", arch), sha256: Packagegz.sha256.hash, size: Packagegz.sha256.size});
|
|
||||||
if (Package?.sha256?.hash) shas.push({file: format("main/binary-%s/Packages", arch), sha256: Package.sha256.hash, size: Package.sha256.size});
|
|
||||||
}
|
|
||||||
const data = mountRelease({
|
|
||||||
Origin: apiConfig.repositoryOptions?.Origin||"node-apt",
|
|
||||||
lebel: apiConfig.repositoryOptions?.lebel,
|
|
||||||
Suite: req.params.package,
|
|
||||||
Components: ["main"],
|
|
||||||
Architectures: Archs,
|
|
||||||
sha256: shas
|
|
||||||
});
|
|
||||||
res.setHeader("Content-Type", "text/plain");
|
|
||||||
res.setHeader("Content-Length", data.length);
|
|
||||||
return res.send(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Binary release
|
|
||||||
app.get("/dists/:package/main/binary-:arch/Release", (req, res) => {
|
|
||||||
const Archs: string[] = [];
|
|
||||||
Object.keys(mainRegister.packageRegister[req.params.package]).forEach(version => Object.keys(mainRegister.packageRegister[req.params.package][version]).forEach(arch => (!Archs.includes(arch.toLowerCase()))?Archs.push(arch.toLowerCase()):null));
|
|
||||||
if (!Archs.includes(req.params.arch.toLowerCase())) return res.status(404).json({error: "Package arch registred"});
|
|
||||||
res.setHeader("Content-Type", "text/plain");
|
|
||||||
return res.send(mountRelease({
|
|
||||||
Origin: apiConfig.repositoryOptions?.Origin||"node-apt",
|
|
||||||
lebel: apiConfig.repositoryOptions?.lebel,
|
|
||||||
Archive: req.params.package,
|
|
||||||
Components: ["main"],
|
|
||||||
Architectures: [req.params.arch.toLowerCase()]
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
// binary packages
|
|
||||||
app.get("/dists/:package/main/binary-:arch/Packages(.gz|)", (req, res, next) => mainRegister.createPackage(req.path.endsWith(".gz"), req.params.package, req.params.arch, res.writeHead(200, {"Content-Type": req.path.endsWith(".gz") ? "application/x-gzip" : "text/plain"})).catch(next));
|
|
||||||
|
|
||||||
// source.list
|
|
||||||
app.get("/*.list", (req, res) => {
|
|
||||||
res.setHeader("Content-type", "text/plain");
|
|
||||||
let config = "";
|
|
||||||
Object.keys(mainRegister.packageRegister).forEach(packageName => config += format("deb [trusted=yes] %s://%s %s main\n", req.protocol, req.headers.host, packageName));
|
|
||||||
res.send(config+"\n");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Pool
|
|
||||||
app.get(["/", "/pool"], (_req, res) => res.json(mainRegister.prettyPackages()));
|
|
||||||
app.get("/pool/:package_name", (req, res) => {
|
|
||||||
const {package_name} = req.params;
|
|
||||||
const info = mainRegister.packageRegister[package_name];
|
|
||||||
if (!info) return res.status(404).json({error: "Package not registred"});
|
|
||||||
return res.json(info);
|
|
||||||
});
|
|
||||||
app.get("/pool/:package_name/:version", (req, res) => {
|
|
||||||
const {package_name, version} = req.params;
|
|
||||||
const info = mainRegister.packageRegister[package_name];
|
|
||||||
if (!info) return res.status(404).json({error: "Package not registred"});
|
|
||||||
const ver = info?.[version];
|
|
||||||
if (!ver) return res.status(404).json({error: "version not registred"});
|
|
||||||
return res.json(ver);
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get("/pool/:package_name/:version/:arch.deb", (req, res) => {
|
|
||||||
const {package_name, arch, version} = req.params;
|
|
||||||
const info = mainRegister.packageRegister[package_name];
|
|
||||||
if (!info) return res.status(404).json({error: "Package not registred"});
|
|
||||||
const ver = info?.[version];
|
|
||||||
if (!ver) return res.status(404).json({error: "version not registred"});
|
|
||||||
const archInfo = ver?.[arch];
|
|
||||||
if (!archInfo) return res.status(404).json({error: "arch not registred"});
|
|
||||||
const stream = archInfo?.getStream;
|
|
||||||
if (!stream) return res.status(404).json({error: "Package not registred"});
|
|
||||||
res.writeHead(200, {"Content-Type": "application/x-debian-package"});
|
|
||||||
return Promise.resolve(stream()).then(stream => stream.pipe(res));
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get("/pool/:package_name/:version/:arch", (req, res) => {
|
|
||||||
const {package_name, arch, version} = req.params;
|
|
||||||
const info = mainRegister.packageRegister[package_name];
|
|
||||||
if (!info) return res.status(404).json({error: "Package not registred"});
|
|
||||||
const ver = info?.[version];
|
|
||||||
if (!ver) return res.status(404).json({error: "version not registred"});
|
|
||||||
const archInfo = ver?.[arch];
|
|
||||||
if (!archInfo) return res.status(404).json({error: "arch not registred"});
|
|
||||||
return res.json(archInfo);
|
|
||||||
});
|
|
||||||
|
|
||||||
app.listen(apiConfig.portListen||0, function listen() {
|
|
||||||
if (typeof apiConfig.callback !== "function") apiConfig.callback = (port) => console.log("API listen on port %s", port);
|
|
||||||
apiConfig.callback(this.address().port);
|
|
||||||
});
|
|
||||||
return app;
|
|
||||||
}
|
|
345
src/apt_repo_v2.ts
Normal file
345
src/apt_repo_v2.ts
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
import { DebianPackage, httpRequestGithub, extendsCrypto, httpRequest } from "@sirherobrine23/coreutils";
|
||||||
|
import { Compressor as lzmaCompressor } from "lzma-native";
|
||||||
|
import { getConfig, repository } from "./repoConfig.js";
|
||||||
|
import { Readable, Writable } from "node:stream";
|
||||||
|
import { createGzip } from "node:zlib";
|
||||||
|
import { CronJob } from "cron";
|
||||||
|
import { watchFile } from "node:fs";
|
||||||
|
import express from "express";
|
||||||
|
import openpgp from "openpgp";
|
||||||
|
import { format } from "node:util";
|
||||||
|
|
||||||
|
type packageRegistry = {
|
||||||
|
[packageName: string]: {
|
||||||
|
repositoryConfig: repository,
|
||||||
|
arch: {
|
||||||
|
[arch: string]: {
|
||||||
|
[version: string]: {
|
||||||
|
getStream: () => Promise<Readable>,
|
||||||
|
control: DebianPackage.debianControl,
|
||||||
|
suite?: string,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function main(configPath: string) {
|
||||||
|
const packInfos: packageRegistry = {};
|
||||||
|
let repositoryConfig = await getConfig(configPath);
|
||||||
|
// watch config file changes
|
||||||
|
watchFile(configPath, async () => repositoryConfig = await getConfig(configPath));
|
||||||
|
const app = express();
|
||||||
|
app.disable("x-powered-by").disable("etag").use(express.json()).use(express.urlencoded({ extended: true })).use((req, _res, next) => {
|
||||||
|
next();
|
||||||
|
return console.log("%s %s %s", req.method, req.ip, req.path);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Public key
|
||||||
|
app.get("/public_key", async ({res}) => {
|
||||||
|
const Key = repositoryConfig["apt-config"]?.pgpKey;
|
||||||
|
if (!Key) return res.status(400).json({error: "This repository no sign Packages files"});
|
||||||
|
const pubKey = (await openpgp.readKey({ armoredKey: Key.public })).armor();
|
||||||
|
return res.setHeader("Content-Type", "application/pgp-keys").send(pubKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sources list
|
||||||
|
app.get("/source_list", (req, res) => {
|
||||||
|
let source = "";
|
||||||
|
const host = repositoryConfig["apt-config"]?.sourcesHost ?? `${req.protocol}://${req.hostname}:${req.socket.localPort}`;
|
||||||
|
for (const repo in packInfos) {
|
||||||
|
if (source) source += "\n";
|
||||||
|
const suites = packInfos[repo].repositoryConfig?.["apt-config"]?.suite ?? ["main"];
|
||||||
|
if (suites.length === 0) suites.push("main");
|
||||||
|
let extraConfig = "";
|
||||||
|
if (!repositoryConfig["apt-config"]?.pgpKey) extraConfig += " [trusted=yes]";
|
||||||
|
source += format("deb%s %s %s", extraConfig ? " "+extraConfig:"", host, repo, suites.join(""));
|
||||||
|
}
|
||||||
|
source += "\n";
|
||||||
|
return res.setHeader("Content-Type", "text/plain").send(source);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get(["/", "/pool"], (_req, res) => {
|
||||||
|
const packages = Object.keys(packInfos);
|
||||||
|
const packagesVersions = packages.map((packageName) => {
|
||||||
|
const arch = Object.keys(packInfos[packageName].arch);
|
||||||
|
const versions = [...(new Set((arch.map((arch) => Object.keys(packInfos[packageName].arch[arch]))).flat()))];
|
||||||
|
return {packageName, arch, versions};
|
||||||
|
});
|
||||||
|
return res.json(packagesVersions);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Download
|
||||||
|
app.get("/pool/:packageName/:arch/:version/download.deb", async (req, res) => {
|
||||||
|
const packageobject = packInfos[req.params.packageName];
|
||||||
|
if (!packageobject) return res.status(400).json({error: "Package not found"});
|
||||||
|
const arch = packageobject.arch[req.params.arch];
|
||||||
|
if (!arch) return res.status(400).json({error: "Arch not found"});
|
||||||
|
const version = arch[req.params.version];
|
||||||
|
if (!version) return res.status(400).json({error: "Version not found"});
|
||||||
|
const {getStream} = version;
|
||||||
|
if (!getStream) return res.status(400).json({error: "Stream not found"});
|
||||||
|
return getStream().then(stream => stream.pipe(res.writeHead(200, {
|
||||||
|
"Content-Type": "application/vnd.debian.binary-package",
|
||||||
|
"Content-Length": version.control.Size,
|
||||||
|
"Content-MD5": version.control.MD5sum,
|
||||||
|
"Content-SHA1": version.control.SHA1,
|
||||||
|
"Content-SHA256": version.control.SHA256,
|
||||||
|
})));
|
||||||
|
});
|
||||||
|
app.get("/pool/:packageName/:arch/:version", async (req, res) => {
|
||||||
|
const packageobject = packInfos[req.params.packageName];
|
||||||
|
if (!packageobject) return res.status(400).json({error: "Package not found"});
|
||||||
|
const arch = packageobject.arch[req.params.arch];
|
||||||
|
if (!arch) return res.status(400).json({error: "Arch not found"});
|
||||||
|
const version = arch[req.params.version];
|
||||||
|
if (!version) return res.status(400).json({error: "Version not found"});
|
||||||
|
return res.json(version);
|
||||||
|
});
|
||||||
|
app.get("/pool/:packageName/:arch", async (req, res) => {
|
||||||
|
const packageobject = packInfos[req.params.packageName];
|
||||||
|
if (!packageobject) return res.status(400).json({error: "Package not found"});
|
||||||
|
const arch = packageobject.arch[req.params.arch];
|
||||||
|
if (!arch) return res.status(400).json({error: "Arch not found"});
|
||||||
|
return res.json(arch);
|
||||||
|
});
|
||||||
|
app.get("/pool/:packageName", async (req, res) => {
|
||||||
|
const packageobject = packInfos[req.params.packageName];
|
||||||
|
if (!packageobject) return res.status(400).json({error: "Package not found"});
|
||||||
|
return res.json(packageobject.arch);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function createPackages(compress?: "gzip" | "xz", options?: {writeStream?: Writable, package?: string, arch?: string, suite?: string}) {
|
||||||
|
const rawWrite = new Readable({read(){}});
|
||||||
|
let size = 0;
|
||||||
|
let hash: ReturnType<typeof extendsCrypto.createHashAsync>|undefined;
|
||||||
|
if (compress === "gzip") {
|
||||||
|
const gzip = rawWrite.pipe(createGzip({level: 9}));
|
||||||
|
if (options?.writeStream) gzip.pipe(options.writeStream);
|
||||||
|
hash = extendsCrypto.createHashAsync("all", gzip);
|
||||||
|
gzip.on("data", (chunk) => size += chunk.length);
|
||||||
|
} else if (compress === "xz") {
|
||||||
|
const lzma = rawWrite.pipe(lzmaCompressor());
|
||||||
|
if (options?.writeStream) lzma.pipe(options.writeStream);
|
||||||
|
hash = extendsCrypto.createHashAsync("all", lzma);
|
||||||
|
lzma.on("data", (chunk) => size += chunk.length);
|
||||||
|
} else {
|
||||||
|
if (options?.writeStream) rawWrite.pipe(options.writeStream);
|
||||||
|
hash = extendsCrypto.createHashAsync("all", rawWrite);
|
||||||
|
rawWrite.on("data", (chunk) => size += chunk.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
let addbreak = false;
|
||||||
|
for (const packageName in packInfos) {
|
||||||
|
if (options?.package && packageName !== options.package) continue;
|
||||||
|
const packageobject = packInfos[packageName];
|
||||||
|
for (const arch in packageobject.arch) {
|
||||||
|
if (options?.arch && arch !== options.arch) continue;
|
||||||
|
for (const version in packageobject.arch[arch]) {
|
||||||
|
if (addbreak) rawWrite.push("\n\n");
|
||||||
|
addbreak = true;
|
||||||
|
const {control} = packageobject.arch[arch][version];
|
||||||
|
/*
|
||||||
|
Package: hello-world
|
||||||
|
Version: 0.0.1
|
||||||
|
Architecture: amd64
|
||||||
|
Maintainer: example <example@example.com>
|
||||||
|
Depends: libc6
|
||||||
|
Filename: pool/main/hello-world_0.0.1-1_amd64.deb
|
||||||
|
Size: 2832
|
||||||
|
MD5sum: 3eba602abba5d6ea2a924854d014f4a7
|
||||||
|
SHA1: e300cabc138ac16b64884c9c832da4f811ea40fb
|
||||||
|
SHA256: 6e314acd7e1e97e11865c11593362c65db9616345e1e34e309314528c5ef19a6
|
||||||
|
Homepage: http://example.com
|
||||||
|
Description: A program that prints hello
|
||||||
|
*/
|
||||||
|
const Data = [
|
||||||
|
`Package: ${packageName}`,
|
||||||
|
`Version: ${version}`,
|
||||||
|
`Architecture: ${arch}`,
|
||||||
|
`Maintainer: ${control.Maintainer}`,
|
||||||
|
`Depends: ${control.Depends}`,
|
||||||
|
`Size: ${control.Size}`,
|
||||||
|
`MD5sum: ${control.MD5sum}`,
|
||||||
|
`SHA1: ${control.SHA1}`,
|
||||||
|
`SHA256: ${control.SHA256}`,
|
||||||
|
];
|
||||||
|
|
||||||
|
Data.push(`Filename: pool/${packageName}/${arch}/${version}/download.deb`);
|
||||||
|
if (control.Homepage) Data.push(`Homepage: ${control.Homepage}`);
|
||||||
|
if (control.Description) Data.push(`Description: ${control.Description}`);
|
||||||
|
|
||||||
|
// Register
|
||||||
|
rawWrite.push(Data.join("\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rawWrite.push(null);
|
||||||
|
if (hash) return hash.then(hash => ({...hash, size}));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get("/dists/:dist/:suite/binary-:arch/Packages(.(xz|gz)|)", (req, res) => {
|
||||||
|
if (req.path.endsWith(".gz")) {
|
||||||
|
createPackages("gzip", {
|
||||||
|
package: req.params.dist,
|
||||||
|
arch: req.params.arch,
|
||||||
|
suite: req.params.suite,
|
||||||
|
writeStream: res.writeHead(200, {
|
||||||
|
"Content-Encoding": "gzip",
|
||||||
|
"Content-Type": "application/x-gzip"
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} else if (req.path.endsWith(".xz")) {
|
||||||
|
createPackages("xz", {
|
||||||
|
package: req.params.dist,
|
||||||
|
arch: req.params.arch,
|
||||||
|
suite: req.params.suite,
|
||||||
|
writeStream: res.writeHead(200, {
|
||||||
|
"Content-Encoding": "xz",
|
||||||
|
"Content-Type": "application/x-xz"
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
createPackages(undefined, {
|
||||||
|
package: req.params.dist,
|
||||||
|
arch: req.params.arch,
|
||||||
|
suite: req.params.suite,
|
||||||
|
writeStream: res.writeHead(200, {
|
||||||
|
"Content-Type": "text/plain"
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Release
|
||||||
|
async function createReleaseV1(dist: string) {
|
||||||
|
const packageobject = packInfos[dist];
|
||||||
|
if (!packageobject) throw new Error("Dist not found");
|
||||||
|
const ReleaseLines = [];
|
||||||
|
|
||||||
|
// Origin
|
||||||
|
const Origin = packageobject.repositoryConfig["apt-config"]?.origin || repositoryConfig["apt-config"]?.origin || "apt-stream";
|
||||||
|
ReleaseLines.push(`Origin: ${Origin}`);
|
||||||
|
|
||||||
|
// Lebel
|
||||||
|
const Label = packageobject.repositoryConfig["apt-config"]?.label || repositoryConfig["apt-config"]?.label || "apt-stream";
|
||||||
|
ReleaseLines.push(`Label: ${Label}`);
|
||||||
|
|
||||||
|
// Suite
|
||||||
|
const Suites = packageobject.repositoryConfig["apt-config"]?.suite || repositoryConfig["apt-config"]?.suite || ["main"];
|
||||||
|
if (Suites.length === 0) Suites.push("main");
|
||||||
|
|
||||||
|
// Codename if exists
|
||||||
|
const codename = packageobject.repositoryConfig["apt-config"]?.codename || repositoryConfig["apt-config"]?.codename;
|
||||||
|
if (codename) ReleaseLines.push(`Codename: ${codename}`);
|
||||||
|
|
||||||
|
// Date
|
||||||
|
ReleaseLines.push(`Date: ${new Date().toUTCString()}`);
|
||||||
|
|
||||||
|
// Architectures
|
||||||
|
const archs = Object.keys(packageobject.arch);
|
||||||
|
ReleaseLines.push(`Architectures: ${archs.join(" ")}`);
|
||||||
|
|
||||||
|
const createPackagesHash = packageobject.repositoryConfig["apt-config"]?.enableHash ?? repositoryConfig["apt-config"]?.enableHash ?? true;
|
||||||
|
if (createPackagesHash) {
|
||||||
|
const sha256: {file: string, size: number, hash: string}[] = [];
|
||||||
|
const sha1: {file: string, size: number, hash: string}[] = [];
|
||||||
|
const md5: {file: string, size: number, hash: string}[] = [];
|
||||||
|
|
||||||
|
for (const arch of archs) {
|
||||||
|
for (const Suite of Suites) {
|
||||||
|
const gzip = await createPackages("gzip", {package: dist, arch, suite: Suite});
|
||||||
|
if (gzip) {
|
||||||
|
sha256.push({file: `${Suite}/binary-${arch}/Packages.gz`, size: gzip.size, hash: gzip.sha256});
|
||||||
|
sha1.push({file: `${Suite}/binary-${arch}/Packages.gz`, size: gzip.size, hash: gzip.sha1});
|
||||||
|
md5.push({file: `${Suite}/binary-${arch}/Packages.gz`, size: gzip.size, hash: gzip.md5});
|
||||||
|
}
|
||||||
|
const xz = await createPackages("xz", {package: dist, arch, suite: Suite});
|
||||||
|
if (xz) {
|
||||||
|
sha256.push({file: `${Suite}/binary-${arch}/Packages.xz`, size: xz.size, hash: xz.sha256});
|
||||||
|
sha1.push({file: `${Suite}/binary-${arch}/Packages.xz`, size: xz.size, hash: xz.sha1});
|
||||||
|
md5.push({file: `${Suite}/binary-${arch}/Packages.xz`, size: xz.size, hash: xz.md5});
|
||||||
|
}
|
||||||
|
const raw = await createPackages(undefined, {package: dist, arch, suite: Suite});
|
||||||
|
if (raw) {
|
||||||
|
sha256.push({file: `${Suite}/binary-${arch}/Packages`, size: raw.size, hash: raw.sha256});
|
||||||
|
sha1.push({file: `${Suite}/binary-${arch}/Packages`, size: raw.size, hash: raw.sha1});
|
||||||
|
md5.push({file: `${Suite}/binary-${arch}/Packages`, size: raw.size, hash: raw.md5});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sha256.length > 0) ReleaseLines.push(`SHA256:${sha256.map((hash) => `\n ${hash.hash} ${hash.size} ${hash.file}`).join("")}`);
|
||||||
|
if (sha1.length > 0) ReleaseLines.push(`SHA1:${sha1.map((hash) => `\n ${hash.hash} ${hash.size} ${hash.file}`).join("")}`);
|
||||||
|
if (md5.length > 0) ReleaseLines.push(`MD5Sum:${md5.map((hash) => `\n ${hash.hash} ${hash.size} ${hash.file}`).join("")}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ReleaseLines.join("\n");
|
||||||
|
}
|
||||||
|
app.get("/dists/:dist/Release", (req, res, next) => createReleaseV1(req.params.dist).then((data) => res.setHeader("Content-Type", "text/plain").send(data)).catch(next));
|
||||||
|
app.get("/dists/:dist/InRelease", (req, res, next) => {
|
||||||
|
const Key = repositoryConfig["apt-config"]?.pgpKey;
|
||||||
|
if (!Key) return res.status(404).json({error: "No PGP key found"});
|
||||||
|
return Promise.resolve().then(async () => {
|
||||||
|
const privateKey = Key.passphrase ? await openpgp.decryptKey({privateKey: await openpgp.readPrivateKey({ armoredKey: Key.private }), passphrase: Key.passphrase}) : await openpgp.readPrivateKey({ armoredKey: Key.private });
|
||||||
|
const Release = await createReleaseV1(req.params.dist);
|
||||||
|
return res.setHeader("Content-Type", "text/plain").send(await openpgp.sign({
|
||||||
|
signingKeys: privateKey,
|
||||||
|
format: "armored",
|
||||||
|
message: await openpgp.createCleartextMessage({text: Release}),
|
||||||
|
}));
|
||||||
|
}).catch(next);
|
||||||
|
});
|
||||||
|
app.get("/dists/:dist/Release.gpg", (req, res, next) => {
|
||||||
|
const Key = repositoryConfig["apt-config"]?.pgpKey;
|
||||||
|
if (!Key) return res.status(404).json({error: "No PGP key found"});
|
||||||
|
return Promise.resolve().then(async () => {
|
||||||
|
const privateKey = Key.passphrase ? await openpgp.decryptKey({privateKey: await openpgp.readPrivateKey({ armoredKey: Key.private }), passphrase: Key.passphrase}) : await openpgp.readPrivateKey({ armoredKey: Key.private });
|
||||||
|
const Release = await createReleaseV1(req.params.dist);
|
||||||
|
return res.setHeader("Content-Type", "text/plain").send(await openpgp.sign({
|
||||||
|
signingKeys: privateKey,
|
||||||
|
message: await openpgp.createMessage({text: Release}),
|
||||||
|
}));
|
||||||
|
}).catch(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Error handler
|
||||||
|
app.use((err, _req, res, _next) => {
|
||||||
|
res.status(500).json({error: err?.message||err});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen HTTP server
|
||||||
|
app.listen(repositoryConfig["apt-config"].portListen, function () {return console.log(`apt-repo listening at http://localhost:${this.address().port}`);});
|
||||||
|
|
||||||
|
// Loading and update packages
|
||||||
|
for (const repository of repositoryConfig.repositories) {
|
||||||
|
try {
|
||||||
|
if (repository.from === "github_release") {
|
||||||
|
const update = async () => {
|
||||||
|
const relData = repository.tags ? await Promise.all(repository.tags.map(async (tag) => httpRequestGithub.GithubRelease({repository: repository.repository, owner: repository.owner, token: repository.token, releaseTag: tag}))) : await httpRequestGithub.GithubRelease({repository: repository.repository, owner: repository.owner, token: repository.token});
|
||||||
|
const assets = relData.flat().map((rel) => rel.assets).flat().filter((asset) => asset.name.endsWith(".deb")).flat();
|
||||||
|
|
||||||
|
for (const asset of assets) {
|
||||||
|
const getStream = () => httpRequest.pipeFetch(asset.browser_download_url);
|
||||||
|
const control = await DebianPackage.extractControl(await getStream());
|
||||||
|
if (!packInfos[control.Package]) packInfos[control.Package] = {repositoryConfig: repository, arch: {}};
|
||||||
|
if (!packInfos[control.Package].arch[control.Architecture]) packInfos[control.Package].arch[control.Architecture] = {}
|
||||||
|
packInfos[control.Package].arch[control.Architecture][control.Version] = {control, getStream};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await update();
|
||||||
|
(repository.cronRefresh ?? []).map((cron) => {
|
||||||
|
const job = new CronJob(cron, update);
|
||||||
|
job.start();
|
||||||
|
return job;
|
||||||
|
});
|
||||||
|
} else if (repository.from === "oci") {
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
100
src/ar.ts
100
src/ar.ts
@ -1,100 +0,0 @@
|
|||||||
import { Readable, Writable } from "node:stream";
|
|
||||||
|
|
||||||
type fileInfo = {
|
|
||||||
name: string,
|
|
||||||
time: Date,
|
|
||||||
owner: number,
|
|
||||||
group: number,
|
|
||||||
mode: number,
|
|
||||||
size: number
|
|
||||||
};
|
|
||||||
|
|
||||||
export function createExtract(fn: (info: fileInfo, stream: Readable) => void) {
|
|
||||||
const __writed = new Writable();
|
|
||||||
let __locked = false;
|
|
||||||
let entryStream: Readable;
|
|
||||||
let size = 0;
|
|
||||||
function check_new_file(chunk: Buffer) {
|
|
||||||
return !!(chunk.subarray(0, 60).toString().replace(/\s+\`(\n)?$/, "").trim().match(/^([\w\s\S]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)$/));
|
|
||||||
}
|
|
||||||
function _final(callback: (error?: Error) => void): void {
|
|
||||||
if (entryStream) {
|
|
||||||
entryStream.push(null);
|
|
||||||
entryStream = undefined;
|
|
||||||
}
|
|
||||||
return callback();
|
|
||||||
}
|
|
||||||
function _destroy(error: Error, callback: (error?: Error) => void): void {
|
|
||||||
if (entryStream) {
|
|
||||||
entryStream.push(null);
|
|
||||||
entryStream = undefined;
|
|
||||||
}
|
|
||||||
return callback(error);
|
|
||||||
}
|
|
||||||
async function __push(chunk: Buffer, callback?: (error?: Error | null) => void) {
|
|
||||||
if (0 < size) {
|
|
||||||
if (check_new_file(chunk.subarray(size))) {
|
|
||||||
// console.log("[Ar]: Nextfile");
|
|
||||||
const silpChuck = chunk.subarray(0, size);
|
|
||||||
chunk = chunk.subarray(size);
|
|
||||||
// console.log("[Ar]: Nextfile: %f", chunk.length);
|
|
||||||
entryStream.push(silpChuck, "binary");
|
|
||||||
entryStream.push(null);
|
|
||||||
entryStream = undefined;
|
|
||||||
size = 0;
|
|
||||||
return __writed._write(chunk, "binary", callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
size -= chunk.length;
|
|
||||||
entryStream.push(chunk, "binary");
|
|
||||||
return callback();
|
|
||||||
}
|
|
||||||
let waitMore: Buffer;
|
|
||||||
__writed._write = (chunkRemote, encoding, callback) => {
|
|
||||||
if (!Buffer.isBuffer(chunkRemote)) chunkRemote = Buffer.from(chunkRemote, encoding);
|
|
||||||
let chunk = Buffer.from(chunkRemote);
|
|
||||||
if (__locked === false) {
|
|
||||||
// console.log("[Ar]: Fist chunk length: %f", chunk.length);
|
|
||||||
if (waitMore) {
|
|
||||||
chunk = Buffer.concat([waitMore, chunk]);
|
|
||||||
waitMore = undefined;
|
|
||||||
}
|
|
||||||
if (chunk.length < 70) {
|
|
||||||
waitMore = chunk;
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
if (!chunk.subarray(0, 8).toString().trim().startsWith("!<arch>")) {
|
|
||||||
this.destroy();
|
|
||||||
return callback(new Error("Not an ar file"));
|
|
||||||
}
|
|
||||||
__locked = true;
|
|
||||||
chunk = chunk.subarray(8);
|
|
||||||
}
|
|
||||||
if (entryStream) return __push(chunk, callback);
|
|
||||||
const info = chunk.subarray(0, 60).toString().replace(/\s+\`(\n)?$/, "").trim();
|
|
||||||
chunk = chunk.subarray(60);
|
|
||||||
// debian-binary 1668505722 0 0 100644 4
|
|
||||||
const dataMathc = info.match(/^([\w\s\S]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)$/);
|
|
||||||
if (!dataMathc) {
|
|
||||||
size = chunk.length;
|
|
||||||
return __push(chunk, callback);
|
|
||||||
}
|
|
||||||
const [, name, time, owner, group, mode, sizeM] = dataMathc;
|
|
||||||
const data: fileInfo = {
|
|
||||||
name: name.trim(),
|
|
||||||
time: new Date(parseInt(time)*1000),
|
|
||||||
owner: parseInt(owner),
|
|
||||||
group: parseInt(group),
|
|
||||||
mode: parseInt(mode),
|
|
||||||
size: parseInt(sizeM)
|
|
||||||
};
|
|
||||||
size = data.size;
|
|
||||||
entryStream = new Readable({read() {}});
|
|
||||||
fn(data, entryStream);
|
|
||||||
return __push(chunk, callback);
|
|
||||||
// process.exit(1);
|
|
||||||
}
|
|
||||||
__writed._final = (callback) => {return _final.call(this, callback);};
|
|
||||||
__writed._destroy = (error, callback) => {return _destroy.call(this, error, callback);};
|
|
||||||
return __writed;
|
|
||||||
}
|
|
@ -1,448 +0,0 @@
|
|||||||
import { EventEmitter} from "node:events";
|
|
||||||
import fs from "node:fs";
|
|
||||||
import path from "node:path";
|
|
||||||
import { Readable } from "node:stream";
|
|
||||||
// import util from "node:util";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given something of size *size* bytes that needs to be aligned by *alignment*
|
|
||||||
* bytes, returns the total number of padding bytes that need to be appended to
|
|
||||||
* the end of the data.
|
|
||||||
*/
|
|
||||||
function getPaddingBytes(size, alignment) {
|
|
||||||
return (alignment - (size % alignment)) % alignment;
|
|
||||||
}
|
|
||||||
|
|
||||||
function padWhitespace(str, width) {
|
|
||||||
while(str.length<width) {
|
|
||||||
str += " ";
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
function padLF(width) {
|
|
||||||
var str = "";
|
|
||||||
while(str.length<width) {
|
|
||||||
str += "\n";
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
function strictWidthField(str, width) {
|
|
||||||
if(str.length>width) {
|
|
||||||
return str.substring(0, width);
|
|
||||||
} else {
|
|
||||||
return padWhitespace(str, width);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trims trailing whitespace from the given string (both ends, although we
|
|
||||||
* only really need the RHS).
|
|
||||||
*/
|
|
||||||
function trimWhitespace(str: string) {
|
|
||||||
return String.prototype.trim ? str.trim() : str.replace(/^\s+|\s+$/gm, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trims trailing NULL characters.
|
|
||||||
*/
|
|
||||||
function trimNulls(str) {
|
|
||||||
return str.replace(/\0/g, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildHeader(name, ts, uid, gid, mode, size) {
|
|
||||||
var header = strictWidthField(name, 16)
|
|
||||||
+ strictWidthField(ts, 12)
|
|
||||||
+ strictWidthField(uid, 6)
|
|
||||||
+ strictWidthField(gid, 6)
|
|
||||||
+ strictWidthField(mode, 8)
|
|
||||||
+ strictWidthField(size, 10)
|
|
||||||
+ "`\n";
|
|
||||||
return Buffer.from(header, "ascii");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All archive variants share this header before files, but the variants differ
|
|
||||||
* in how they handle odd cases (e.g. files with spaces, long filenames, etc).
|
|
||||||
*
|
|
||||||
* char ar_name[16]; File name
|
|
||||||
* char ar_date[12]; file member date
|
|
||||||
* char ar_uid[6] file member user identification
|
|
||||||
* char ar_gid[6] file member group identification
|
|
||||||
* char ar_mode[8] file member mode (octal)
|
|
||||||
* char ar_size[10]; file member size
|
|
||||||
* char ar_fmag[2]; header trailer string
|
|
||||||
*/
|
|
||||||
class ArEntry {
|
|
||||||
public header: Buffer;
|
|
||||||
public archive: ArReader|ArWriter;
|
|
||||||
public bsd: boolean;
|
|
||||||
public bsdName: string;
|
|
||||||
public data?: Buffer;
|
|
||||||
public streamParam?: {file: string, start: number, end: number};
|
|
||||||
public stream = new Readable();
|
|
||||||
constructor(header: Buffer, archive: ArReader|ArWriter) {
|
|
||||||
this.stream._read = () => {};
|
|
||||||
this.header = header;
|
|
||||||
this.archive = archive;
|
|
||||||
if(this.fmag() !== "`\n") {
|
|
||||||
throw new Error("Record is missing header trailer string; instead, it has: " + this.fmag());
|
|
||||||
}
|
|
||||||
this.bsd = this.name().slice(0, 3) === "#1/";
|
|
||||||
}
|
|
||||||
name(): string {
|
|
||||||
// The name field is padded by whitespace, so trim any lingering whitespace.
|
|
||||||
return trimWhitespace(this.header.toString('utf8', 0, 16));
|
|
||||||
};
|
|
||||||
realName(): string {
|
|
||||||
var name = this.name();
|
|
||||||
if(this.bsd) {
|
|
||||||
this.nameSizeBSD();
|
|
||||||
// Unfortunately, even though they give us the *explicit length*, they add
|
|
||||||
// NULL bytes and include that in the length, so we must strip them out.
|
|
||||||
name = this.bsdName;
|
|
||||||
} else if(this.archive && this.archive.isGNU() && name.indexOf("/")===0) {
|
|
||||||
name = this.archive.resolveNameGNU(name);
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Returns the number of bytes that the resolved BSD-style name takes up in the
|
|
||||||
* content section.
|
|
||||||
*/
|
|
||||||
nameSizeBSD() {
|
|
||||||
if (this.bsd) {
|
|
||||||
return parseInt(this.name().substr(3), 10);
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fileName() {
|
|
||||||
var n = this.realName();
|
|
||||||
if(n.lastIndexOf("/")==n.length-1) {
|
|
||||||
n = n.substring(0, n.length-1);
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
};
|
|
||||||
date() {
|
|
||||||
return new Date(parseInt(this.header.toString('ascii', 16, 28), 10));
|
|
||||||
};
|
|
||||||
uid() {
|
|
||||||
return parseInt(this.header.toString('ascii', 28, 34), 10);
|
|
||||||
};
|
|
||||||
gid() {
|
|
||||||
return parseInt(this.header.toString('ascii', 34, 40), 10);
|
|
||||||
};
|
|
||||||
mode() {
|
|
||||||
return parseInt(this.header.toString('ascii', 40, 48), 8);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Total size of the data section in the record. Does not include padding bytes.
|
|
||||||
*/
|
|
||||||
dataSize() {
|
|
||||||
return parseInt(this.header.toString('ascii', 48, 58), 10);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Total size of the *file* data in the data section of the record. This is
|
|
||||||
* not always equal to dataSize.
|
|
||||||
*/
|
|
||||||
fileSize() {
|
|
||||||
if(this.bsd) {
|
|
||||||
return this.dataSize() - this.nameSizeBSD();
|
|
||||||
} else {
|
|
||||||
return this.dataSize();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fmag() {
|
|
||||||
return this.header.toString('ascii', 58, 60);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Total size of the header, including padding bytes.
|
|
||||||
*/
|
|
||||||
headerSize() {
|
|
||||||
// The common header is already two-byte aligned.
|
|
||||||
return 60;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Total size of this file record (header + header padding + file data +
|
|
||||||
* padding before next archive member).
|
|
||||||
*/
|
|
||||||
totalSize() {
|
|
||||||
var headerSize = this.headerSize(), dataSize = this.dataSize();
|
|
||||||
|
|
||||||
// All archive members are 2-byte aligned, so there's padding bytes after
|
|
||||||
// the data section.
|
|
||||||
return headerSize + dataSize + getPaddingBytes(dataSize, 2);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare interface ArReader {
|
|
||||||
on(act: "entry", fn: (entry: ArEntry, next?: () => void) => void): this;
|
|
||||||
once(act: "entry", fn: (entry: ArEntry, next?: () => void) => void): this;
|
|
||||||
|
|
||||||
on(act: "end"|"close", fn: () => void): this;
|
|
||||||
once(act: "end"|"close", fn: () => void): this;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ArReader extends EventEmitter {
|
|
||||||
private file: string;
|
|
||||||
public size: number;
|
|
||||||
public fd: number;
|
|
||||||
private gnuEntry?: ArEntry;
|
|
||||||
constructor(file: string) {
|
|
||||||
super({captureRejections: true});
|
|
||||||
this.file = file;
|
|
||||||
fs.stat(this.file, (sErr, stats) => {
|
|
||||||
if(sErr) this.emit("error", sErr);
|
|
||||||
else {
|
|
||||||
this.size = stats.size;
|
|
||||||
fs.open(this.file, "r", (oErr, fd) => {
|
|
||||||
if(oErr) this.emit("error", oErr);
|
|
||||||
else {
|
|
||||||
this.emit("open");
|
|
||||||
this.fd = fd;
|
|
||||||
/*
|
|
||||||
fix return cb buffer return
|
|
||||||
*/
|
|
||||||
var readChunks = (buf: Buffer, off: number, pos: number, left: number, cb: (data?: Buffer) => void, stream?: Readable) => {
|
|
||||||
if(pos >= this.size && left > 0) cb();
|
|
||||||
else if(left <= 0) cb(buf);
|
|
||||||
else {
|
|
||||||
var chunkSize = Math.max(Math.min(left, 1024), 0);
|
|
||||||
fs.read(fd, buf, off, chunkSize, pos, (rErr, read, b) => {
|
|
||||||
if(rErr) {
|
|
||||||
this.emit("error", rErr);
|
|
||||||
return cb();
|
|
||||||
}
|
|
||||||
readChunks(buf, off+read, pos+read, left-read, cb);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var readEntry = (offset: number) => {
|
|
||||||
readChunks(Buffer.alloc(60), 0, offset, 60, (header) => {
|
|
||||||
if(!header) {
|
|
||||||
this.emit("end");
|
|
||||||
fs.close(fd, (cErr) => {
|
|
||||||
if(cErr) this.emit("error", cErr);
|
|
||||||
this.fd = undefined;
|
|
||||||
this.emit("close");
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
var entry = new ArEntry(header, this);
|
|
||||||
var bsdNameSize = entry.nameSizeBSD();
|
|
||||||
readChunks(Buffer.alloc(bsdNameSize), 0, offset+60, bsdNameSize, (bsdNameData) => {
|
|
||||||
if(bsdNameData) {
|
|
||||||
entry.bsdName = trimNulls(bsdNameData.toString('utf8', 0, bsdNameSize));
|
|
||||||
var nextOffset = entry.totalSize()+offset;
|
|
||||||
var nexted = false;
|
|
||||||
var next = () => {
|
|
||||||
if(!nexted) { //prevent repeat calls
|
|
||||||
entry = undefined;
|
|
||||||
readEntry(nextOffset);
|
|
||||||
nexted = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if(entry.name()==="//") {
|
|
||||||
this.gnuEntry = entry;
|
|
||||||
var size = entry.fileSize();
|
|
||||||
readChunks(Buffer.alloc(size), 0, offset+60+bsdNameSize, size, (gnuData) => {
|
|
||||||
this.gnuEntry.data = gnuData;
|
|
||||||
console.log("gnuData", gnuData);
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
entry.streamParam = {
|
|
||||||
file: this.file,
|
|
||||||
start: offset+60+bsdNameSize,
|
|
||||||
end: offset+60+entry.dataSize()-1
|
|
||||||
};
|
|
||||||
this.emit("entry", entry, next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
readEntry(8);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
isGNU() {
|
|
||||||
return (this.gnuEntry!==undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
resolveNameGNU(shortName: string): string|void {
|
|
||||||
if(this.isGNU()) {
|
|
||||||
try {
|
|
||||||
var start = parseInt(shortName.replace("/", ""), 10);
|
|
||||||
var resolved = this.gnuEntry.data.toString('utf8', start);
|
|
||||||
return resolved.substring(0, resolved.indexOf("\n"));
|
|
||||||
} catch(e) {
|
|
||||||
return shortName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ArWriterOptions = {
|
|
||||||
variant?: "bsd"|"gnu",
|
|
||||||
uid?: number,
|
|
||||||
gid?: number,
|
|
||||||
mode?: number
|
|
||||||
};
|
|
||||||
export class ArWriter extends EventEmitter {
|
|
||||||
public file: string;
|
|
||||||
public uid?: number;
|
|
||||||
public gid?: number;
|
|
||||||
public mode?: number;
|
|
||||||
public gnu: boolean;
|
|
||||||
public bsd: boolean;
|
|
||||||
public data: Buffer;
|
|
||||||
public gnuMap?: {[key: string]: string};
|
|
||||||
public gnuEntry?: ArEntry
|
|
||||||
constructor(file: string, opts?: ArWriterOptions) {
|
|
||||||
super({captureRejections: true});
|
|
||||||
this.file = file;
|
|
||||||
if(opts) {
|
|
||||||
if(opts.uid) this.uid = opts.uid;
|
|
||||||
if(opts.gid) this.gid = opts.gid;
|
|
||||||
if(opts.mode) this.mode = opts.mode;
|
|
||||||
if(opts.variant) {
|
|
||||||
if(opts.variant.toLowerCase() === "bsd") this.bsd = true;
|
|
||||||
else if(opts.variant.toLowerCase() === "gnu") this.gnu = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(fs.existsSync(this.file)) {
|
|
||||||
fs.unlinkSync(this.file);
|
|
||||||
}
|
|
||||||
fs.open(this.file, "w", (oErr, fd) => {
|
|
||||||
if(oErr) {
|
|
||||||
this.emit("error", oErr);
|
|
||||||
} else {
|
|
||||||
this.emit("open");
|
|
||||||
fs.write(fd, Buffer.from("!<arch>\n", "ascii"), 0, 8, null, (archErr, writ, b) => {
|
|
||||||
if(archErr) {
|
|
||||||
this.emit("error", archErr);
|
|
||||||
} else {
|
|
||||||
var writeEntry = (entry: ArEntry, off: number, cb: (data?: number) => void) => {
|
|
||||||
fs.write(fd, entry.header, 0, entry.headerSize(), null, (wErr1, w, b) => {
|
|
||||||
if(wErr1) {
|
|
||||||
this.emit("error", wErr1);
|
|
||||||
} else {
|
|
||||||
var dataSize = entry.dataSize();
|
|
||||||
var paddedData = entry.data;
|
|
||||||
var paddSize = getPaddingBytes(dataSize, 2);
|
|
||||||
if(paddSize>0) {
|
|
||||||
paddedData = Buffer.concat([entry.data, Buffer.from(padLF(paddSize), "ascii")], dataSize+paddSize);
|
|
||||||
}
|
|
||||||
fs.write(fd, paddedData, 0, dataSize+paddSize, null, (wErr2, w2, b2) => {
|
|
||||||
if(wErr2) {
|
|
||||||
this.emit("error", wErr2);
|
|
||||||
} else {
|
|
||||||
var total = entry.totalSize();
|
|
||||||
entry = undefined;
|
|
||||||
cb(off+total);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
var processFile = (fList: any[], off: number, cb: (data?: Buffer) => void) => {
|
|
||||||
if(fList.length <= 0) cb();
|
|
||||||
else {
|
|
||||||
var curr = fList.shift();
|
|
||||||
fs.stat(curr, (statErr, currStat) => {
|
|
||||||
if(statErr) this.emit("error", statErr);
|
|
||||||
else {
|
|
||||||
fs.readFile(curr, (rfErr, data) => {
|
|
||||||
if(rfErr) this.emit("error", rfErr);
|
|
||||||
else {
|
|
||||||
var currName = path.basename(curr) + "/";
|
|
||||||
var currSize = currStat.size;
|
|
||||||
if(this.gnu && this.gnuMap[currName]) {
|
|
||||||
currName = this.gnuMap[currName];
|
|
||||||
} else if(this.bsd && currName.length>16) {
|
|
||||||
currSize += currName.length;
|
|
||||||
data = Buffer.concat([Buffer.from(currName, "ascii"), data], currSize);
|
|
||||||
currName = "#1/" + currName.length;
|
|
||||||
}
|
|
||||||
var currHeader = buildHeader(currName,
|
|
||||||
(currStat.mtime.getTime()/1000) + "",
|
|
||||||
((this.uid!==undefined) ? this.uid : currStat.uid) + "",
|
|
||||||
((this.gid!==undefined) ? this.gid : currStat.gid) + "",
|
|
||||||
((this.mode!==undefined) ? this.mode : currStat.mode).toString(8),
|
|
||||||
currSize + "");
|
|
||||||
var arEntry = new ArEntry(currHeader, this);
|
|
||||||
arEntry.data = data;
|
|
||||||
writeEntry(arEntry, off, (newOff) => {
|
|
||||||
this.emit("entry", arEntry);
|
|
||||||
arEntry = undefined;
|
|
||||||
processFile(fList, newOff, cb);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var finished = () => {
|
|
||||||
fs.close(fd, (cwErr) => {
|
|
||||||
if(cwErr) {
|
|
||||||
this.emit("error", cwErr);
|
|
||||||
} else {
|
|
||||||
this.emit("finish");
|
|
||||||
// callback && callback();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
if(this.gnu) {
|
|
||||||
this.gnuMap = {};
|
|
||||||
var gnuContent = "";
|
|
||||||
var entries = entries;
|
|
||||||
for(var i=0; i<entries.length; i++) {
|
|
||||||
var base = path.basename(entries[i]) + "/";
|
|
||||||
if(base.length>16) {
|
|
||||||
this.gnuMap[base] = "/" + gnuContent.length;
|
|
||||||
gnuContent += base + "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(Object.keys(this.gnuMap).length>0) {
|
|
||||||
var gnuHeader = buildHeader("//", "", "", "", "", gnuContent.length + "");
|
|
||||||
this.gnuEntry = new ArEntry(gnuHeader, this);
|
|
||||||
this.gnuEntry.data = Buffer.from(gnuContent);
|
|
||||||
writeEntry(this.gnuEntry, 8, (newOffset) => {
|
|
||||||
processFile(entries, newOffset, finished);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
processFile(entries, 8, finished);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
processFile(entries, 8, finished);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
isGNU() {
|
|
||||||
return this.gnu;
|
|
||||||
};
|
|
||||||
|
|
||||||
isBSD() {
|
|
||||||
return this.bsd;
|
|
||||||
};
|
|
||||||
|
|
||||||
resolveNameGNU(shortName: string) {
|
|
||||||
return ArReader.prototype.resolveNameGNU.call(this, shortName);
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
import { parseDebControl } from "./aptRepo.js";
|
|
||||||
import { createExtract } from "./ar.js";
|
|
||||||
import coreUtils, { extendsCrypto } from "@sirherobrine23/coreutils";
|
|
||||||
import tar from "tar";
|
|
||||||
import { localRegistryManeger } from "./aptRepo.js";
|
|
||||||
import { format } from "util";
|
|
||||||
import { Decompressor } from "lzma-native";
|
|
||||||
|
|
||||||
export type baseOptions<T extends {} = {}> = {
|
|
||||||
repo: string,
|
|
||||||
owner: string
|
|
||||||
} & T;
|
|
||||||
|
|
||||||
export async function list(config: string|baseOptions<{releaseTag?: string}>, githubToken?: string) {
|
|
||||||
if (typeof config === "string") {
|
|
||||||
const [owner, repo] = config.split("/");
|
|
||||||
config = {
|
|
||||||
owner,
|
|
||||||
repo
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: baseOptions<{releaseTag?: string}> = config;
|
|
||||||
const releases = (await coreUtils.httpRequestGithub.GithubRelease(options.owner, options.repo)).slice(0, 10).filter(data => data.assets.some(file => file.name.endsWith(".deb"))).map(data => {
|
|
||||||
return {
|
|
||||||
tag: data.tag_name,
|
|
||||||
assets: data.assets.filter(data => data.name.endsWith(".deb")).map(({name, browser_download_url}) => ({name, download: browser_download_url}))
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return releases.filter(({assets}) => assets?.length > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fullConfig(config: {config: string|baseOptions<{releaseTag?: string}>, githubToken?: string}, packageManeger: localRegistryManeger) {
|
|
||||||
const releases = await list(config.config, config.githubToken);
|
|
||||||
for (const {assets, tag} of releases ?? []) for (const {download} of assets ?? []) {
|
|
||||||
let size = 0;
|
|
||||||
const request = (await coreUtils.httpRequest.pipeFetch(download)).on("data", (chunk) => size += chunk.length);
|
|
||||||
const signs = extendsCrypto.createSHA256_MD5(request, "both", new Promise(done => request.on("end", done)));
|
|
||||||
request.pipe(createExtract((info, stream) => {
|
|
||||||
if (!(info.name.endsWith("control.tar.gz")||info.name.endsWith("control.tar.xz"))) return;
|
|
||||||
(info.name.endsWith("tar.gz")?stream:stream.pipe(Decompressor())).pipe(tar.list({
|
|
||||||
onentry: (tarEntry) => {
|
|
||||||
if (!tarEntry.path.endsWith("control")) return;
|
|
||||||
let controlBuffer: Buffer;
|
|
||||||
tarEntry.on("data", (chunk) => {
|
|
||||||
if (!controlBuffer) controlBuffer = chunk;
|
|
||||||
else controlBuffer = Buffer.concat([controlBuffer, chunk]);
|
|
||||||
}).on("error", console.log);
|
|
||||||
request.on("end", async () => {
|
|
||||||
const debConfig = parseDebControl(controlBuffer);
|
|
||||||
if (!(debConfig.Package && debConfig.Version && debConfig.Architecture)) return;
|
|
||||||
const sigs = await signs;
|
|
||||||
packageManeger.registerPackage({
|
|
||||||
name: debConfig.Package,
|
|
||||||
version: debConfig.Version,
|
|
||||||
arch: debConfig.Architecture,
|
|
||||||
packageConfig: debConfig,
|
|
||||||
signature: sigs,
|
|
||||||
size,
|
|
||||||
from: format("github release, tag: %s, repo: %s", tag, config.config),
|
|
||||||
getStrem: async () => coreUtils.httpRequest.pipeFetch(download),
|
|
||||||
});
|
|
||||||
}).on("error", console.log);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
})).on("error", console.log);
|
|
||||||
}
|
|
||||||
}
|
|
58
src/index.ts
58
src/index.ts
@ -1,15 +1,51 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
import yargs from "yargs";
|
import yargs from "yargs";
|
||||||
import { createAPI } from "./aptRepo.js";
|
import repo from "./apt_repo_v2.js";
|
||||||
yargs(process.argv.slice(2)).wrap(null).strict().help().option("cofig-path", {
|
import openpgp from "openpgp";
|
||||||
|
import { getConfig, saveConfig } from "./repoConfig.js";
|
||||||
|
|
||||||
|
yargs(process.argv.slice(2)).wrap(null).strict().help().strictCommands().option("cofig-path", {
|
||||||
type: "string",
|
type: "string",
|
||||||
default: process.cwd()+"/repoconfig.yml",
|
default: process.cwd()+"/repoconfig.yml",
|
||||||
}).option("port", {
|
}).command("config", "maneger basics configs", async yargs => {
|
||||||
type: "number",
|
const options = yargs.option("generate-keys", {
|
||||||
default: 3000,
|
type: "boolean",
|
||||||
}).parseAsync().then(options => {
|
default: false,
|
||||||
return createAPI({
|
alias: "g",
|
||||||
configPath: options["cofig-path"],
|
}).option("passphrase", {
|
||||||
portListen: options.port,
|
type: "string",
|
||||||
});
|
default: "",
|
||||||
});
|
alias: "p",
|
||||||
|
}).option("name", {
|
||||||
|
type: "string",
|
||||||
|
default: "",
|
||||||
|
alias: "n",
|
||||||
|
}).option("email", {
|
||||||
|
type: "string",
|
||||||
|
default: "",
|
||||||
|
alias: "e",
|
||||||
|
}).parseSync();
|
||||||
|
|
||||||
|
const config = await getConfig(options.cofigPath);
|
||||||
|
if (options.generateKeys) {
|
||||||
|
if (!options.email) throw new Error("email is required");
|
||||||
|
if (!options.name) throw new Error("name is required");
|
||||||
|
if (!options.passphrase) options.passphrase = undefined;
|
||||||
|
const keys = await openpgp.generateKey({
|
||||||
|
type: "rsa",
|
||||||
|
rsaBits: 4096,
|
||||||
|
userIDs: [{ name: options.name, email: options.email }],
|
||||||
|
passphrase: options.passphrase
|
||||||
|
});
|
||||||
|
if (!config["apt-config"]) config["apt-config"] = {};
|
||||||
|
config["apt-config"].pgpKey = {
|
||||||
|
private: keys.privateKey,
|
||||||
|
public: keys.publicKey,
|
||||||
|
passphrase: options.passphrase,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await saveConfig(options.cofigPath, config);
|
||||||
|
}).command("server", "Run HTTP serber", yargs => {
|
||||||
|
const options = yargs.parseSync();
|
||||||
|
return repo(options.cofigPath);
|
||||||
|
}).parseAsync();
|
@ -1,12 +1,9 @@
|
|||||||
import { localRegistryManeger, parseDebControl } from "./aptRepo.js";
|
import { DockerRegistry, DebianPackage } from "@sirherobrine23/coreutils";
|
||||||
import { DockerRegistry, extendsCrypto } from "@sirherobrine23/coreutils";
|
|
||||||
import { createExtract } from "./ar.js";
|
|
||||||
import { Readable } from "stream";
|
import { Readable } from "stream";
|
||||||
import tar from "tar";
|
import tar from "tar";
|
||||||
import { Decompressor } from "lzma-native";
|
|
||||||
import { format } from "util";
|
|
||||||
|
|
||||||
export async function fullConfig(imageInfo: {image: string, targetInfo?: DockerRegistry.Manifest.platfomTarget}, packageManeger: localRegistryManeger) {
|
export default fullConfig;
|
||||||
|
export async function fullConfig(imageInfo: {image: string, targetInfo?: DockerRegistry.Manifest.platfomTarget}, fn: (data: DebianPackage.debianControl & {getStream: () => Promise<Readable>}) => void) {
|
||||||
const registry = await DockerRegistry.Manifest.Manifest(imageInfo.image, imageInfo.targetInfo);
|
const registry = await DockerRegistry.Manifest.Manifest(imageInfo.image, imageInfo.targetInfo);
|
||||||
await registry.layersStream((data) => {
|
await registry.layersStream((data) => {
|
||||||
if (!(["gzip", "gz", "tar"]).some(ends => data.layer.mediaType.endsWith(ends))) {
|
if (!(["gzip", "gz", "tar"]).some(ends => data.layer.mediaType.endsWith(ends))) {
|
||||||
@ -14,44 +11,24 @@ export async function fullConfig(imageInfo: {image: string, targetInfo?: DockerR
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return data.stream.pipe(tar.list({
|
return data.stream.pipe(tar.list({
|
||||||
onentry(entry) {
|
async onentry(entry) {
|
||||||
if (!entry.path.endsWith(".deb")) return null;
|
if (!entry.path.endsWith(".deb")) return null;
|
||||||
let fileSize = 0;
|
const control = await DebianPackage.extractControl(entry as any);
|
||||||
entry.on("data", (chunk) => fileSize += chunk.length);
|
return fn({
|
||||||
const signs = extendsCrypto.createSHA256_MD5(entry as any, "both", new Promise(done => entry.once("end", done)));
|
...control,
|
||||||
return entry.pipe(createExtract((info, stream) => {
|
getStream: async () => {
|
||||||
if (!(info.name.endsWith("control.tar.gz")||info.name.endsWith("control.tar.xz"))) return;
|
return new Promise<Readable>((done, reject) => registry.blobLayerStream(data.layer.digest).then(stream => {
|
||||||
(info.name.endsWith("tar.gz")?stream:stream.pipe(Decompressor())).pipe(tar.list({
|
stream.on("error", reject);
|
||||||
onentry(controlEntry) {
|
stream.pipe(tar.list({
|
||||||
if (!controlEntry.path.endsWith("control")) return null;
|
onentry(getEntry) {
|
||||||
let controlFile: Buffer;
|
if (getEntry.path !== entry.path) return null;
|
||||||
controlEntry.on("data", chunck => controlFile = (!controlFile)?chunck:Buffer.concat([controlFile, chunck])).once("end", async () => {
|
return done(getEntry as any);
|
||||||
const sign = await signs;
|
}
|
||||||
const control = parseDebControl(controlFile);
|
// @ts-ignore
|
||||||
entry.on("end", () => {
|
}).on("error", reject));
|
||||||
packageManeger.registerPackage({
|
}).catch(reject));
|
||||||
name: control.Package,
|
},
|
||||||
version: control.Version,
|
});
|
||||||
arch: control.Architecture,
|
|
||||||
packageConfig: control,
|
|
||||||
size: fileSize,
|
|
||||||
signature: sign,
|
|
||||||
from: format("oci registry, image: %s, layer: %s", imageInfo.image, data.layer.digest),
|
|
||||||
getStrem: () => new Promise<Readable>(done => {
|
|
||||||
registry.blobLayerStream(data.layer.digest).then((stream) => stream.pipe(tar.list({
|
|
||||||
onentry(getEntry) {
|
|
||||||
if (getEntry.path !== entry.path) return;
|
|
||||||
done(getEntry as any);
|
|
||||||
}
|
|
||||||
})));
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}).on("error", console.error);
|
|
||||||
},
|
|
||||||
// @ts-ignore
|
|
||||||
})).on("error", console.error);
|
|
||||||
})).on("error", console.log);
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
@ -2,45 +2,88 @@ import coreUtils, { DockerRegistry } from "@sirherobrine23/coreutils";
|
|||||||
import * as yaml from "yaml";
|
import * as yaml from "yaml";
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
|
|
||||||
export type configV1 = {
|
export type apt_config = {
|
||||||
version: 1,
|
origin?: string,
|
||||||
repos: (({
|
label?: string,
|
||||||
from: "release",
|
codename?: string,
|
||||||
repo: string|{
|
suite?: string[],
|
||||||
owner: string,
|
enableHash?: boolean,
|
||||||
repo: string
|
sourcesHost?: string
|
||||||
},
|
|
||||||
}|{
|
|
||||||
from: "oci",
|
|
||||||
repo: string,
|
|
||||||
ociConfig?: DockerRegistry.Manifest.platfomTarget,
|
|
||||||
}) & {
|
|
||||||
auth?: {
|
|
||||||
username?: string,
|
|
||||||
password?: string
|
|
||||||
}
|
|
||||||
})[]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function getConfig(filePath: string): Promise<configV1> {
|
export type repository = ({
|
||||||
if (!await coreUtils.extendFs.exists(filePath)) throw new Error("file not exists");
|
from: "oci",
|
||||||
const configData: configV1 = yaml.parse(await fs.readFile(filePath, "utf8"));
|
image: string,
|
||||||
return {
|
platfom_target?: DockerRegistry.Manifest.platfomTarget
|
||||||
version: 1,
|
auth?: {
|
||||||
repos: (configData?.repos ?? []).map(data => {
|
username?: string,
|
||||||
if (data.from === "oci" && typeof data.repo === "string") {
|
password?: string
|
||||||
return {
|
},
|
||||||
repo: data.repo,
|
"apt-config"?: apt_config,
|
||||||
from: "oci",
|
}|{
|
||||||
auth: data.auth,
|
from: "github_release",
|
||||||
ociConfig: data.ociConfig
|
repository: string,
|
||||||
};
|
owner?: string,
|
||||||
}
|
tags?: string[],
|
||||||
return {
|
takeUpTo?: number,
|
||||||
repo: (typeof data.repo === "string")?data.repo:{owner: data.repo.owner, repo: data.repo.repo},
|
token?: string,
|
||||||
from: "release",
|
"apt-config"?: apt_config,
|
||||||
auth: data.auth,
|
}) & {
|
||||||
}
|
/** cron range: https://github.com/kelektiv/node-cron#cron-ranges */
|
||||||
})
|
cronRefresh?: string[],
|
||||||
};
|
}
|
||||||
|
|
||||||
|
export type backendConfig = Partial<{
|
||||||
|
"apt-config"?: apt_config & {
|
||||||
|
portListen?: number,
|
||||||
|
pgpKey?: {
|
||||||
|
private: string,
|
||||||
|
public: string,
|
||||||
|
passphrase?: string
|
||||||
|
}
|
||||||
|
},
|
||||||
|
all?: apt_config,
|
||||||
|
repositories: repository[]
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export async function saveConfig(filePath: string, config: backendConfig) {
|
||||||
|
await fs.writeFile(filePath, yaml.stringify(config));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getConfig(filePath: string) {
|
||||||
|
if (!await coreUtils.extendFs.exists(filePath)) throw new Error("config File not exists");
|
||||||
|
const fixedConfig: backendConfig = {};
|
||||||
|
const configData: backendConfig = yaml.parse(await fs.readFile(filePath, "utf8"));
|
||||||
|
fixedConfig["apt-config"] = configData["apt-config"] ?? {enableHash: true, label: "apt-stream"};
|
||||||
|
fixedConfig.repositories = configData.repositories ?? [];
|
||||||
|
fixedConfig.repositories = (fixedConfig.repositories ?? []).map((repo) => {
|
||||||
|
if (repo.from === "oci") {
|
||||||
|
if (!repo.image) throw new Error("oci repository must have image field");
|
||||||
|
const repoFix: repository = {from: "oci", image: repo.image};
|
||||||
|
if (repo.platfom_target) repoFix.platfom_target = repo.platfom_target;
|
||||||
|
if (repo.auth) repoFix.auth = repo.auth;
|
||||||
|
if (repo["apt-config"]) repoFix["apt-config"] = repo["apt-config"];
|
||||||
|
if (repo.cronRefresh) repoFix.cronRefresh = repo.cronRefresh;
|
||||||
|
return repoFix;
|
||||||
|
} else if (repo.from === "github_release") {
|
||||||
|
if (!repo.repository) throw new Error("github_release repository must have repository field");
|
||||||
|
else if (typeof repo.repository !== "string") throw new Error("github_release repository must be string");
|
||||||
|
const repoFix: repository = {from: "github_release", repository: repo.repository};
|
||||||
|
if (repo.owner) repoFix.owner = repo.owner;
|
||||||
|
else {
|
||||||
|
const [owner, ...repository] = repo.repository.split("/");
|
||||||
|
if (!owner) throw new Error("github_release repository must have owner field");
|
||||||
|
if (repository.length === 0) throw new Error("github_release repository must have repository field");
|
||||||
|
repoFix.owner = owner;
|
||||||
|
repoFix.repository = repository.join("/");
|
||||||
|
}
|
||||||
|
if (repo.token) repoFix.token = repo.token;
|
||||||
|
if (repo["apt-config"]) repoFix["apt-config"] = repo["apt-config"];
|
||||||
|
if (repo.cronRefresh) repoFix.cronRefresh = repo.cronRefresh;
|
||||||
|
return repoFix;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}).filter(a => !!a);
|
||||||
|
return fixedConfig;
|
||||||
}
|
}
|
||||||
|
@ -88,11 +88,109 @@
|
|||||||
"colId": "history",
|
"colId": "history",
|
||||||
"containerId": "",
|
"containerId": "",
|
||||||
"name": "localhost:3000/dists/gh/Release",
|
"name": "localhost:3000/dists/gh/Release",
|
||||||
"url": "localhost:3000/",
|
"url": "localhost:3000/dists/gh/Release",
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"sortNum": 0,
|
"sortNum": 0,
|
||||||
"created": "2022-12-16T12:04:29.843Z",
|
"created": "2022-12-16T12:04:29.843Z",
|
||||||
"modified": "2022-12-16T15:33:07.174Z",
|
"modified": "2022-12-23T21:44:46.078Z",
|
||||||
|
"headers": [],
|
||||||
|
"params": [],
|
||||||
|
"tests": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "d22e6c9a-65a2-42c5-8d9f-f5f593d82dd5",
|
||||||
|
"colId": "history",
|
||||||
|
"containerId": "",
|
||||||
|
"name": "http://localhost:3000/pool/",
|
||||||
|
"url": "http://localhost:3000/pool/",
|
||||||
|
"method": "GET",
|
||||||
|
"sortNum": 0,
|
||||||
|
"created": "2022-12-18T23:11:34.556Z",
|
||||||
|
"modified": "2022-12-18T23:11:34.556Z",
|
||||||
|
"headers": [],
|
||||||
|
"params": [],
|
||||||
|
"tests": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "091394e2-824e-40a0-a4ee-435c940186df",
|
||||||
|
"colId": "history",
|
||||||
|
"containerId": "",
|
||||||
|
"name": "http://localhost:3000/pool/a",
|
||||||
|
"url": "http://localhost:3000/pool/a",
|
||||||
|
"method": "GET",
|
||||||
|
"sortNum": 0,
|
||||||
|
"created": "2022-12-18T23:11:43.135Z",
|
||||||
|
"modified": "2022-12-18T23:11:43.135Z",
|
||||||
|
"headers": [],
|
||||||
|
"params": [],
|
||||||
|
"tests": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "d9daac8a-8c88-42e5-bed1-fe140c911c8a",
|
||||||
|
"colId": "history",
|
||||||
|
"containerId": "",
|
||||||
|
"name": "http://localhost:3000/pool/a/b",
|
||||||
|
"url": "http://localhost:3000/pool/a/b",
|
||||||
|
"method": "GET",
|
||||||
|
"sortNum": 0,
|
||||||
|
"created": "2022-12-18T23:13:15.489Z",
|
||||||
|
"modified": "2022-12-18T23:13:15.489Z",
|
||||||
|
"headers": [],
|
||||||
|
"params": [],
|
||||||
|
"tests": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "7a618a4f-2f2e-4943-9120-ab1bebd6ebc9",
|
||||||
|
"colId": "history",
|
||||||
|
"containerId": "",
|
||||||
|
"name": "http://localhost:3000/pool/gh",
|
||||||
|
"url": "http://localhost:3000/pool/gh",
|
||||||
|
"method": "GET",
|
||||||
|
"sortNum": 0,
|
||||||
|
"created": "2022-12-18T23:18:38.398Z",
|
||||||
|
"modified": "2022-12-18T23:18:38.398Z",
|
||||||
|
"headers": [],
|
||||||
|
"params": [],
|
||||||
|
"tests": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "453956ca-3122-4220-99ec-7a7533bfcd70",
|
||||||
|
"colId": "history",
|
||||||
|
"containerId": "",
|
||||||
|
"name": "http://localhost:3000/pool/gh/2.10.0",
|
||||||
|
"url": "http://localhost:3000/pool/gh/2.10.0",
|
||||||
|
"method": "GET",
|
||||||
|
"sortNum": 0,
|
||||||
|
"created": "2022-12-18T23:20:15.271Z",
|
||||||
|
"modified": "2022-12-18T23:20:15.271Z",
|
||||||
|
"headers": [],
|
||||||
|
"params": [],
|
||||||
|
"tests": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "37a29f1f-25cf-4f9e-9246-71056ec87e1d",
|
||||||
|
"colId": "history",
|
||||||
|
"containerId": "",
|
||||||
|
"name": "http://localhost:3000/pool/gh/2.10.0.deb",
|
||||||
|
"url": "http://localhost:3000/pool/gh/2.10.0.deb",
|
||||||
|
"method": "GET",
|
||||||
|
"sortNum": 0,
|
||||||
|
"created": "2022-12-18T23:20:21.809Z",
|
||||||
|
"modified": "2022-12-18T23:20:21.809Z",
|
||||||
|
"headers": [],
|
||||||
|
"params": [],
|
||||||
|
"tests": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "85562af1-6352-46ee-9b7f-10efed2e0782",
|
||||||
|
"colId": "history",
|
||||||
|
"containerId": "",
|
||||||
|
"name": "http://localhost:3000/pool/gh/amd64/2.10.0.deb",
|
||||||
|
"url": "http://localhost:3000/pool/gh/amd64/2.10.0.deb",
|
||||||
|
"method": "GET",
|
||||||
|
"sortNum": 0,
|
||||||
|
"created": "2022-12-18T23:23:22.999Z",
|
||||||
|
"modified": "2022-12-18T23:23:22.999Z",
|
||||||
"headers": [],
|
"headers": [],
|
||||||
"params": [],
|
"params": [],
|
||||||
"tests": []
|
"tests": []
|
||||||
|
Reference in New Issue
Block a user