rewrite API #3

Merged
Sirherobrine23 merged 6 commits from v2APIs into main 2022-12-24 02:00:38 +00:00
17 changed files with 1001 additions and 1116 deletions

6
.dockerignore Normal file
View File

@ -0,0 +1,6 @@
node_modules/
.devcontainer/
.vscode/
.github/
*.d.ts
*.js

2
.vscode/launch.json vendored
View File

@ -12,7 +12,7 @@
"skipFiles": ["<node_internals>/**", "node_modules/**"],
"cwd": "${workspaceRoot}",
"runtimeExecutable": "ts-node",
"args": ["src/index.ts"]
"args": ["src/index.ts", "server"]
}
]
}

9
Dockerfile Normal file
View 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" ]

View File

@ -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
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)
- 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
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/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
* `GET` /pool/:package_name/:version/:arch.deb - `Download .deb file`
* `GET` /pool/:package_name/:version/:arch - `Config package if exists`
* `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`
* `GET` /pool and / - `Get packages registred to packages registry`

View File

@ -1,8 +1,8 @@
FROM debian:latest
ARG DEBIAN_FRONTEND="noninteractive"
RUN apt update && \
cd /tmp && mkdir debs && cd 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 apt update && apt install -y jq curl
WORKDIR /tmp/debs
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
COPY --from=0 /tmp/debs/*.deb /

194
package-lock.json generated
View File

@ -1,22 +1,28 @@
{
"name": "node-apt",
"name": "apt-stream",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "node-apt",
"name": "apt-stream",
"version": "1.0.0",
"license": "GPL-2.0",
"dependencies": {
"@sirherobrine23/coreutils": "^2.1.6",
"@sirherobrine23/coreutils": "^2.2.1",
"cron": "^2.1.0",
"express": "^4.18.2",
"lzma-native": "^8.0.6",
"openpgp": "^5.5.0",
"tar": "^6.1.13",
"yaml": "^2.1.3",
"yaml": "^2.2.0",
"yargs": "^17.6.2"
},
"bin": {
"apt-stream": "src/index.js"
},
"devDependencies": {
"@types/cron": "^2.0.0",
"@types/express": "^4.17.15",
"@types/lzma-native": "^4.0.1",
"@types/node": "^18.11.17",
@ -198,15 +204,16 @@
}
},
"node_modules/@sirherobrine23/coreutils": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/@sirherobrine23/coreutils/-/coreutils-2.1.6.tgz",
"integrity": "sha512-Te/4gJZqjWcEocDMH2ObHiY6muIwrWUZe7ZGQHob0HTRQ4J0bPcfNsLBW9L/UFCjUdTRzjcLyZ0/pssCQD2Zlg==",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@sirherobrine23/coreutils/-/coreutils-2.2.1.tgz",
"integrity": "sha512-IDm39eYtVrSkOkviS6Yny0WcUHzOxAWXywGvubdAImdAfWeqbOCMEJh6/TFdDa+g9OEQVEVBEybL+LjbLD0Dcg==",
"dependencies": {
"@actions/github": "^5.1.1",
"adm-zip": "^0.5.9",
"debug": "^4.3.4",
"got": "^12.5.3",
"jsdom": "^20.0.3",
"lzma-native": "^8.0.6",
"tar": "^6.1.12"
}
},
@ -272,6 +279,16 @@
"@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": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.15.tgz",
@ -295,10 +312,11 @@
"@types/range-parser": "*"
}
},
"node_modules/@types/http-cache-semantics": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
"integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ=="
"node_modules/@types/luxon": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.1.0.tgz",
"integrity": "sha512-gCd/HcCgjqSxfMrgtqxCgYk/22NBQfypwFUG7ZAyG/4pqs51WLTcUzVp1hqTbieDYeHS3WoVEh2Yv/2l+7B0Vg==",
"dev": true
},
"node_modules/@types/lzma-native": {
"version": "4.0.1",
@ -414,9 +432,9 @@
}
},
"node_modules/adm-zip": {
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz",
"integrity": "sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg==",
"version": "0.5.10",
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz",
"integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==",
"engines": {
"node": ">=6.0"
}
@ -465,6 +483,17 @@
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"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": {
"version": "0.4.0",
"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",
"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": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
@ -528,11 +562,10 @@
}
},
"node_modules/cacheable-request": {
"version": "10.2.3",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.3.tgz",
"integrity": "sha512-6BehRBOs7iurNjAYN9iPazTwFDaMQavJO8W1MEm3s2pH8q/tkPTtLDRUZaweWK87WFGf2Y5wLAlaCJlR5kOz3w==",
"version": "10.2.4",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.4.tgz",
"integrity": "sha512-IWIea8ei1Ht4dBqvlvh7Gs7EYlMyBhlJybLDUB9sadEqHqftmdNieMLIR5ia3vs8gbjj9t8hXLBpUVg3vcQNbg==",
"dependencies": {
"@types/http-cache-semantics": "^4.0.1",
"get-stream": "^6.0.1",
"http-cache-semantics": "^4.1.0",
"keyv": "^4.5.2",
@ -642,6 +675,14 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"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": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
@ -1297,6 +1338,14 @@
"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": {
"version": "8.0.6",
"resolved": "https://registry.npmjs.org/lzma-native/-/lzma-native-8.0.6.tgz",
@ -1382,6 +1431,11 @@
"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": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
@ -1525,6 +1579,17 @@
"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": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
@ -2206,9 +2271,9 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/yaml": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz",
"integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.0.tgz",
"integrity": "sha512-auf7Gi6QwO7HW//GA9seGvTXVGWl1CM/ADWh1+RxtXr6XOxnT65ovDl9fTi4e0monEyJxCHqDpF6QnFDXmJE4g==",
"engines": {
"node": ">= 14"
}
@ -2400,15 +2465,16 @@
"integrity": "sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw=="
},
"@sirherobrine23/coreutils": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/@sirherobrine23/coreutils/-/coreutils-2.1.6.tgz",
"integrity": "sha512-Te/4gJZqjWcEocDMH2ObHiY6muIwrWUZe7ZGQHob0HTRQ4J0bPcfNsLBW9L/UFCjUdTRzjcLyZ0/pssCQD2Zlg==",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@sirherobrine23/coreutils/-/coreutils-2.2.1.tgz",
"integrity": "sha512-IDm39eYtVrSkOkviS6Yny0WcUHzOxAWXywGvubdAImdAfWeqbOCMEJh6/TFdDa+g9OEQVEVBEybL+LjbLD0Dcg==",
"requires": {
"@actions/github": "^5.1.1",
"adm-zip": "^0.5.9",
"debug": "^4.3.4",
"got": "^12.5.3",
"jsdom": "^20.0.3",
"lzma-native": "^8.0.6",
"tar": "^6.1.12"
}
},
@ -2468,6 +2534,16 @@
"@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": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.15.tgz",
@ -2491,10 +2567,11 @@
"@types/range-parser": "*"
}
},
"@types/http-cache-semantics": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
"integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ=="
"@types/luxon": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.1.0.tgz",
"integrity": "sha512-gCd/HcCgjqSxfMrgtqxCgYk/22NBQfypwFUG7ZAyG/4pqs51WLTcUzVp1hqTbieDYeHS3WoVEh2Yv/2l+7B0Vg==",
"dev": true
},
"@types/lzma-native": {
"version": "4.0.1",
@ -2598,9 +2675,9 @@
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA=="
},
"adm-zip": {
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz",
"integrity": "sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg=="
"version": "0.5.10",
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz",
"integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ=="
},
"agent-base": {
"version": "6.0.2",
@ -2634,6 +2711,17 @@
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"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": {
"version": "0.4.0",
"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",
"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": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
@ -2689,11 +2782,10 @@
"integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w=="
},
"cacheable-request": {
"version": "10.2.3",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.3.tgz",
"integrity": "sha512-6BehRBOs7iurNjAYN9iPazTwFDaMQavJO8W1MEm3s2pH8q/tkPTtLDRUZaweWK87WFGf2Y5wLAlaCJlR5kOz3w==",
"version": "10.2.4",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.4.tgz",
"integrity": "sha512-IWIea8ei1Ht4dBqvlvh7Gs7EYlMyBhlJybLDUB9sadEqHqftmdNieMLIR5ia3vs8gbjj9t8hXLBpUVg3vcQNbg==",
"requires": {
"@types/http-cache-semantics": "^4.0.1",
"get-stream": "^6.0.1",
"http-cache-semantics": "^4.1.0",
"keyv": "^4.5.2",
@ -2776,6 +2868,14 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"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": {
"version": "0.5.0",
"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",
"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": {
"version": "8.0.6",
"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",
"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": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
@ -3418,6 +3528,14 @@
"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": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
@ -3906,9 +4024,9 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"yaml": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz",
"integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg=="
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.0.tgz",
"integrity": "sha512-auf7Gi6QwO7HW//GA9seGvTXVGWl1CM/ADWh1+RxtXr6XOxnT65ovDl9fTi4e0monEyJxCHqDpF6QnFDXmJE4g=="
},
"yargs": {
"version": "17.6.2",

View File

@ -1,21 +1,21 @@
{
"name": "node-apt",
"name": "apt-stream",
"version": "1.0.0",
"description": "Replace",
"main": "src/index.js",
"types": "./src/index.d.ts",
"private": false,
"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/)",
"license": "GPL-2.0",
"repository": {
"type": "git",
"url": "git+https://github.com/Sirherobrine23/node-apt.git"
"url": "git+https://github.com/Sirherobrine23/apt-stream.git"
},
"keywords": [],
"bugs": {
"url": "https://github.com/Sirherobrine23/node-apt/issues"
"url": "https://github.com/Sirherobrine23/apt-stream/issues"
},
"sponsor": {
"url": "https://github.com/sponsors/Sirherobrine23"
@ -26,10 +26,15 @@
"engines": {
"node": ">=14.0.0"
},
"bin": {
"apt-stream": "./src/index.js"
},
"scripts": {
"start": "ts-node src/index.ts"
"start": "ts-node src/index.ts",
"build": "tsc"
},
"devDependencies": {
"@types/cron": "^2.0.0",
"@types/express": "^4.17.15",
"@types/lzma-native": "^4.0.1",
"@types/node": "^18.11.17",
@ -39,11 +44,13 @@
"typescript": "^4.9.4"
},
"dependencies": {
"@sirherobrine23/coreutils": "^2.1.6",
"@sirherobrine23/coreutils": "^2.2.1",
"cron": "^2.1.0",
"express": "^4.18.2",
"lzma-native": "^8.0.6",
"openpgp": "^5.5.0",
"tar": "^6.1.13",
"yaml": "^2.1.3",
"yaml": "^2.2.0",
"yargs": "^17.6.2"
}
}

View File

@ -1,13 +1,180 @@
version: 1
repos:
- repo: ghcr.io/sirherobrine23/nodeaptexample:sha256:c7b3eb9435f67aa08a2e98fdaa5c4548bb8e410fc804ed352bb03dcb212dde96
from: oci
apt-config:
portListen: 3000
origin: "Sirherobrine23"
label: Sirherobrine23-repo
enableHash: true
sourcesHost: http://localhost:3000
pgpKey:
private: |
-----BEGIN PGP PRIVATE KEY BLOCK-----
# - repo: Sirherobrine23/ghcrAptRepository
# from: release
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
=8o1a
-----END PGP PRIVATE KEY BLOCK-----
public: |
-----BEGIN PGP PUBLIC KEY BLOCK-----
# - repo: The-Bds-Maneger/BedrockFetch
# from: release
# - repo: cli/cli
# from: release
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
iKy5h0djTyX95owar8j3Lg==
=Xjxj
-----END PGP PUBLIC KEY BLOCK-----
repositories:
- from: github_release
repository: cli
owner: cli

View File

@ -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
View 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
View File

@ -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;
}

View File

@ -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);
};
}

View File

@ -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);
}
}

View File

@ -1,15 +1,51 @@
#!/usr/bin/env node
import yargs from "yargs";
import { createAPI } from "./aptRepo.js";
yargs(process.argv.slice(2)).wrap(null).strict().help().option("cofig-path", {
import repo from "./apt_repo_v2.js";
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",
default: process.cwd()+"/repoconfig.yml",
}).option("port", {
type: "number",
default: 3000,
}).parseAsync().then(options => {
return createAPI({
configPath: options["cofig-path"],
portListen: options.port,
});
});
}).command("config", "maneger basics configs", async yargs => {
const options = yargs.option("generate-keys", {
type: "boolean",
default: false,
alias: "g",
}).option("passphrase", {
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();

View File

@ -1,12 +1,9 @@
import { localRegistryManeger, parseDebControl } from "./aptRepo.js";
import { DockerRegistry, extendsCrypto } from "@sirherobrine23/coreutils";
import { createExtract } from "./ar.js";
import { DockerRegistry, DebianPackage } from "@sirherobrine23/coreutils";
import { Readable } from "stream";
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);
await registry.layersStream((data) => {
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 data.stream.pipe(tar.list({
onentry(entry) {
async onentry(entry) {
if (!entry.path.endsWith(".deb")) return null;
let fileSize = 0;
entry.on("data", (chunk) => fileSize += chunk.length);
const signs = extendsCrypto.createSHA256_MD5(entry as any, "both", new Promise(done => entry.once("end", done)));
return entry.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(controlEntry) {
if (!controlEntry.path.endsWith("control")) return null;
let controlFile: Buffer;
controlEntry.on("data", chunck => controlFile = (!controlFile)?chunck:Buffer.concat([controlFile, chunck])).once("end", async () => {
const sign = await signs;
const control = parseDebControl(controlFile);
entry.on("end", () => {
packageManeger.registerPackage({
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);
const control = await DebianPackage.extractControl(entry as any);
return fn({
...control,
getStream: async () => {
return new Promise<Readable>((done, reject) => registry.blobLayerStream(data.layer.digest).then(stream => {
stream.on("error", reject);
stream.pipe(tar.list({
onentry(getEntry) {
if (getEntry.path !== entry.path) return null;
return done(getEntry as any);
}
// @ts-ignore
}).on("error", reject));
}).catch(reject));
},
});
},
}));
});

View File

@ -2,45 +2,88 @@ import coreUtils, { DockerRegistry } from "@sirherobrine23/coreutils";
import * as yaml from "yaml";
import fs from "node:fs/promises";
export type configV1 = {
version: 1,
repos: (({
from: "release",
repo: string|{
owner: string,
repo: string
},
}|{
from: "oci",
repo: string,
ociConfig?: DockerRegistry.Manifest.platfomTarget,
}) & {
auth?: {
username?: string,
password?: string
}
})[]
export type apt_config = {
origin?: string,
label?: string,
codename?: string,
suite?: string[],
enableHash?: boolean,
sourcesHost?: string
};
export async function getConfig(filePath: string): Promise<configV1> {
if (!await coreUtils.extendFs.exists(filePath)) throw new Error("file not exists");
const configData: configV1 = yaml.parse(await fs.readFile(filePath, "utf8"));
return {
version: 1,
repos: (configData?.repos ?? []).map(data => {
if (data.from === "oci" && typeof data.repo === "string") {
return {
repo: data.repo,
from: "oci",
auth: data.auth,
ociConfig: data.ociConfig
};
}
return {
repo: (typeof data.repo === "string")?data.repo:{owner: data.repo.owner, repo: data.repo.repo},
from: "release",
auth: data.auth,
}
})
};
export type repository = ({
from: "oci",
image: string,
platfom_target?: DockerRegistry.Manifest.platfomTarget
auth?: {
username?: string,
password?: string
},
"apt-config"?: apt_config,
}|{
from: "github_release",
repository: string,
owner?: string,
tags?: string[],
takeUpTo?: number,
token?: string,
"apt-config"?: apt_config,
}) & {
/** 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;
}

View File

@ -88,11 +88,109 @@
"colId": "history",
"containerId": "",
"name": "localhost:3000/dists/gh/Release",
"url": "localhost:3000/",
"url": "localhost:3000/dists/gh/Release",
"method": "GET",
"sortNum": 0,
"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": [],
"params": [],
"tests": []