fix: modernize docker publishing

This commit is contained in:
2026-04-29 12:58:06 +00:00
parent 27d4a5d3c1
commit 57f32661f3
11 changed files with 295 additions and 227 deletions
+5
View File
@@ -1 +1,6 @@
.git/
.nogit/
dist/
dist_*/
node_modules/
rust/target/
-66
View File
@@ -1,66 +0,0 @@
name: Default (not tags)
on:
push:
tags-ignore:
- '**'
env:
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
jobs:
security:
runs-on: ubuntu-latest
continue-on-error: true
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Install pnpm and npmci
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
- name: Run npm prepare
run: npmci npm prepare
- name: Audit production dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --prod
continue-on-error: true
- name: Audit development dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --dev
continue-on-error: true
test:
if: ${{ always() }}
needs: security
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Test stable
run: |
npmci node install stable
npmci npm install
npmci npm test
- name: Test build
run: |
npmci node install stable
npmci npm install
npmci npm build
-124
View File
@@ -1,124 +0,0 @@
name: Default (tags)
on:
push:
tags:
- '*'
env:
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
jobs:
security:
runs-on: ubuntu-latest
continue-on-error: true
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Audit production dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --prod
continue-on-error: true
- name: Audit development dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --dev
continue-on-error: true
test:
if: ${{ always() }}
needs: security
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Test stable
run: |
npmci node install stable
npmci npm install
npmci npm test
- name: Test build
run: |
npmci node install stable
npmci npm install
npmci npm build
release:
needs: test
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Release
run: |
npmci node install stable
npmci npm publish
metadata:
needs: test
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
continue-on-error: true
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Code quality
run: |
npmci command npm install -g typescript
npmci npm install
- name: Trigger
run: npmci trigger
- name: Build docs and upload artifacts
run: |
npmci node install stable
npmci npm install
pnpm install -g @git.zone/tsdoc
npmci command tsdoc
continue-on-error: true
+36
View File
@@ -0,0 +1,36 @@
name: Docker (non-tag pushes)
on:
push:
tags-ignore:
- '**'
env:
IMAGE: code.foss.global/host.today/ht-docker-node:szci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
NPMCI_LOGIN_DOCKER_DOCKERREGISTRY: ${{ secrets.NPMCI_LOGIN_DOCKER_DOCKERREGISTRY }}
jobs:
test:
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @git.zone/tsdocker@latest
pnpm config set registry https://verdaccio.lossless.digital
pnpm install
- name: Test
run: pnpm test
- name: Build image
run: tsdocker build
- name: Test image
run: tsdocker test
+42
View File
@@ -0,0 +1,42 @@
name: Docker (tags)
on:
push:
tags:
- '*'
env:
IMAGE: code.foss.global/host.today/ht-docker-node:szci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
NPMCI_LOGIN_DOCKER_DOCKERREGISTRY: ${{ secrets.NPMCI_LOGIN_DOCKER_DOCKERREGISTRY }}
jobs:
release:
runs-on: ubuntu-latest
container:
image: code.foss.global/host.today/ht-docker-dbase:szci
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @git.zone/tsdocker@latest
pnpm config set registry https://verdaccio.lossless.digital
pnpm install
- name: Login to registries
run: tsdocker login
- name: List images
run: tsdocker list
- name: Build images
run: tsdocker build
- name: Test images
run: tsdocker test
- name: Push to code.foss.global
run: tsdocker push code.foss.global
+7
View File
@@ -5,6 +5,13 @@
"linux_arm64"
]
},
"@git.zone/tsdocker": {
"registries": ["code.foss.global"],
"registryRepoMap": {
"code.foss.global": "serve.zone/remoteingress"
},
"platforms": ["linux/amd64", "linux/arm64"]
},
"@git.zone/cli": {
"projectType": "npm",
"module": {
+27 -35
View File
@@ -1,46 +1,38 @@
# gitzone dockerfile_service
## STAGE 1 // BUILD
FROM registry.gitlab.com/hosttoday/ht-docker-node:npmci as node1
COPY ./ /app
WORKDIR /app
ARG NPMCI_TOKEN_NPM2
ENV NPMCI_TOKEN_NPM2 $NPMCI_TOKEN_NPM2
RUN npmci npm prepare
RUN pnpm config set store-dir .pnpm-store
RUN rm -rf node_modules && pnpm install
RUN pnpm run build
FROM code.foss.global/host.today/ht-docker-node:lts AS build
# gitzone dockerfile_service
## STAGE 2 // install production
FROM registry.gitlab.com/hosttoday/ht-docker-node:npmci as node2
WORKDIR /app
COPY --from=node1 /app /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm config set store-dir .pnpm-store
RUN pnpm config set registry https://verdaccio.lossless.digital
RUN pnpm install --frozen-lockfile
COPY . ./
# The npm package builds both Rust targets; each OCI image only needs its native binary.
RUN node -e "const fs=require('node:fs');const p='.smartconfig.json';const c=JSON.parse(fs.readFileSync(p,'utf8'));c['@git.zone/tsrust']={...(c['@git.zone/tsrust']||{}),targets:[]};fs.writeFileSync(p,JSON.stringify(c));" \
&& pnpm exec tsbuild tsfolders --allowimplicitany \
&& pnpm exec tsrust
RUN rm -rf .pnpm-store
ARG NPMCI_TOKEN_NPM2
ENV NPMCI_TOKEN_NPM2 $NPMCI_TOKEN_NPM2
RUN npmci npm prepare
RUN pnpm config set store-dir .pnpm-store
RUN rm -rf node_modules/ && pnpm install --prod
RUN pnpm prune --prod
## STAGE 2 // PRODUCTION
FROM code.foss.global/host.today/ht-docker-node:alpine-node AS production
## STAGE 3 // rebuild dependencies for alpine
FROM registry.gitlab.com/hosttoday/ht-docker-node:alpinenpmci as node3
WORKDIR /app
COPY --from=node2 /app /app
ARG NPMCI_TOKEN_NPM2
ENV NPMCI_TOKEN_NPM2 $NPMCI_TOKEN_NPM2
RUN npmci npm prepare
RUN pnpm config set store-dir .pnpm-store
RUN pnpm rebuild -r
## STAGE 4 // the final production image with all dependencies in place
FROM registry.gitlab.com/hosttoday/ht-docker-node:alpine as node4
WORKDIR /app
COPY --from=node3 /app /app
ENV NODE_ENV=production
### Healthchecks
RUN pnpm install -g @servezone/healthy
HEALTHCHECK --interval=30s --timeout=30s --start-period=30s --retries=3 CMD [ "healthy" ]
COPY --from=build /app/package.json ./package.json
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/cli.js ./cli.js
COPY --from=build /app/dist_ts ./dist_ts
COPY --from=build /app/dist_rust ./dist_rust
EXPOSE 80
CMD ["npm", "start"]
LABEL org.opencontainers.image.title="remoteingress" \
org.opencontainers.image.description="serve.zone edge ingress tunnel" \
org.opencontainers.image.source="https://code.foss.global/serve.zone/remoteingress"
EXPOSE 80 443 8443 53/udp
CMD ["node", "cli.js"]
+1
View File
@@ -9,6 +9,7 @@
"author": "Task Venture Capital GmbH",
"license": "MIT",
"scripts": {
"start": "(node ./cli.js)",
"test": "(pnpm run build && tstest test/ --verbose --logfile --timeout 60)",
"build": "(tsbuild tsfolders --allowimplicitany && tsrust)",
"buildDocs": "(tsdoc)"
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
set -euo pipefail
node --input-type=module <<'NODE'
import fs from 'node:fs';
import { execFileSync } from 'node:child_process';
const readJson = (path) => JSON.parse(fs.readFileSync(path, 'utf8'));
const arch = process.arch === 'x64' ? 'amd64' : process.arch;
const checks = {
packageVersion: readJson('/app/package.json').version,
hasCli: fs.existsSync('/app/cli.js'),
hasRustBinary: fs.existsSync(`/app/dist_rust/remoteingress-bin_linux_${arch}`) || fs.existsSync('/app/dist_rust/remoteingress-bin'),
};
await import('/app/dist_ts/index.js');
execFileSync('node', ['/app/cli.js', '--help'], { stdio: 'pipe' });
if (checks.packageVersion !== '4.17.1') {
throw new Error(`Unexpected remoteingress package version ${checks.packageVersion}`);
}
if (!checks.hasCli) {
throw new Error('Missing cli.js');
}
if (!checks.hasRustBinary) {
throw new Error(`Missing Rust binary for ${arch}`);
}
console.log(JSON.stringify(checks));
NODE
+1 -1
View File
@@ -135,7 +135,7 @@ export interface IUdpStatus {
droppedDatagrams: number;
}
type TAllowedEdge = { id: string; secret: string; listenPorts?: number[]; listenPortsUdp?: number[]; stunIntervalSecs?: number; firewallConfig?: IFirewallConfig; performance?: IPerformanceConfig };
export type TAllowedEdge = { id: string; secret: string; listenPorts?: number[]; listenPortsUdp?: number[]; stunIntervalSecs?: number; firewallConfig?: IFirewallConfig; performance?: IPerformanceConfig };
const MAX_RESTART_ATTEMPTS = 10;
const MAX_RESTART_BACKOFF_MS = 30_000;
+144
View File
@@ -1,3 +1,147 @@
import { RemoteIngressEdge } from './classes.remoteingressedge.js';
import { RemoteIngressHub, type IHubConfig, type TAllowedEdge } from './classes.remoteingresshub.js';
export * from './classes.remoteingresshub.js';
export * from './classes.remoteingressedge.js';
export * from './classes.token.js';
const usage = `remoteingress
Usage:
remoteingress hub [--tunnel-port 8443] [--target-host 127.0.0.1]
remoteingress edge --token <connection-token>
remoteingress edge --hub-host <host> --edge-id <id> --secret <secret> [--hub-port 8443]
Environment:
REMOTEINGRESS_MODE=hub|edge
REMOTEINGRESS_TOKEN=<connection-token>
REMOTEINGRESS_HUB_HOST=<host>
REMOTEINGRESS_HUB_PORT=8443
REMOTEINGRESS_EDGE_ID=<id>
REMOTEINGRESS_SECRET=<secret>
REMOTEINGRESS_TARGET_HOST=127.0.0.1
REMOTEINGRESS_ALLOWED_EDGES_JSON='[{"id":"edge-1","secret":"secret","listenPorts":[80,443]}]'
`;
const readArg = (args: string[], name: string): string | undefined => {
const prefix = `--${name}=`;
const inlineValue = args.find((arg) => arg.startsWith(prefix));
if (inlineValue) {
return inlineValue.slice(prefix.length);
}
const index = args.indexOf(`--${name}`);
if (index >= 0) {
return args[index + 1];
}
return undefined;
};
const readNumber = (value: string | undefined, fallback: number): number => {
if (!value) {
return fallback;
}
const parsed = Number(value);
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
throw new Error(`Invalid port: ${value}`);
}
return parsed;
};
const readJson = <T>(value: string | undefined, fallback: T): T => {
if (!value) {
return fallback;
}
return JSON.parse(value) as T;
};
const waitForever = async (stop: () => Promise<void>) => {
let stopping = false;
const handleStop = async () => {
if (stopping) {
return;
}
stopping = true;
await stop();
process.exit(0);
};
process.once('SIGINT', () => void handleStop());
process.once('SIGTERM', () => void handleStop());
await new Promise(() => {});
};
export const runCli = async () => {
const args = process.argv.slice(2);
if (args.includes('--help') || args.includes('-h')) {
console.log(usage);
return;
}
const positionalMode = args[0]?.startsWith('--') ? undefined : args[0];
const mode = readArg(args, 'mode') ?? positionalMode ?? process.env.REMOTEINGRESS_MODE;
if (mode === 'hub') {
const hub = new RemoteIngressHub();
const config: IHubConfig = {
tunnelPort: readNumber(readArg(args, 'tunnel-port') ?? process.env.REMOTEINGRESS_TUNNEL_PORT, 8443),
targetHost: readArg(args, 'target-host') ?? process.env.REMOTEINGRESS_TARGET_HOST ?? '127.0.0.1',
tls: {
certPem: readArg(args, 'tls-cert-pem') ?? process.env.REMOTEINGRESS_TLS_CERT_PEM,
keyPem: readArg(args, 'tls-key-pem') ?? process.env.REMOTEINGRESS_TLS_KEY_PEM,
},
performance: readJson(readArg(args, 'performance-json') ?? process.env.REMOTEINGRESS_PERFORMANCE_JSON, undefined),
};
await hub.start(config);
const allowedEdges = readJson<TAllowedEdge[]>(
readArg(args, 'allowed-edges-json') ?? process.env.REMOTEINGRESS_ALLOWED_EDGES_JSON,
[],
);
if (allowedEdges.length > 0) {
await hub.updateAllowedEdges(allowedEdges);
}
console.log(`RemoteIngress hub listening on ${config.tunnelPort}`);
await waitForever(() => hub.stop());
return;
}
if (mode === 'edge') {
const edge = new RemoteIngressEdge();
const token = readArg(args, 'token') ?? process.env.REMOTEINGRESS_TOKEN;
if (token) {
await edge.start({ token });
} else {
const hubHost = readArg(args, 'hub-host') ?? process.env.REMOTEINGRESS_HUB_HOST;
const edgeId = readArg(args, 'edge-id') ?? process.env.REMOTEINGRESS_EDGE_ID;
const secret = readArg(args, 'secret') ?? process.env.REMOTEINGRESS_SECRET;
if (!hubHost || !edgeId || !secret) {
throw new Error('Edge mode requires --token or --hub-host, --edge-id, and --secret');
}
await edge.start({
hubHost,
hubPort: readNumber(readArg(args, 'hub-port') ?? process.env.REMOTEINGRESS_HUB_PORT, 8443),
edgeId,
secret,
bindAddress: readArg(args, 'bind-address') ?? process.env.REMOTEINGRESS_BIND_ADDRESS,
transportMode: readArg(args, 'transport-mode') as any,
});
}
console.log('RemoteIngress edge started');
await waitForever(() => edge.stop());
return;
}
console.log(usage);
};