Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@ on:
pull_request:
branches: [ "main", "api-v2" ]

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Use Node.js 24.x LTS
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '24.x'
cache: 'npm'
Expand All @@ -31,10 +34,10 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Use Node.js 24.x LTS
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '24.x'
cache: 'npm'
Expand All @@ -45,6 +48,11 @@ jobs:
cd nest
npm ci

- name: Audit Production Dependencies
run: |
cd nest
npm audit --omit=dev --audit-level=critical

- name: Lint
run: |
cd nest
Expand Down
24 changes: 17 additions & 7 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ services:
privileged: true

sscan-ipfs:
image: ipfs/kubo:v0.39.0
image: ipfs/kubo:v0.40.1@sha256:9c70a3dba0b5f362bf99317a02384a194f5c91cf5388abdb8fe7d64d83dd20bb
container_name: ${BRANCH_NAME:-main}_sscan-ipfs
restart: always
environment:
Expand All @@ -35,13 +35,13 @@ services:
- ./docker-data/ipfs:/ipfsdata
- ./ipfs/001-configure.sh:/container-init.d/001-configure.sh:ro
healthcheck:
test: ["CMD-SHELL", "ipfs swarm peers | head -1"]
test: ["CMD", "ipfs", "id", "--format=<id>"]
interval: 10s
timeout: 5s
retries: 5

sscan-nginx:
image: nginx:1.27
image: nginx:1.30.3-alpine-slim@sha256:d5b51cfc7d55fc7a7bcf4d1d577b9c3738331df56d68f0b1d8ac9795b9470a5a
container_name: ${BRANCH_NAME:-main}_sscan-nginx
restart: always
ports:
Expand All @@ -56,13 +56,23 @@ services:
environment:
- NGINX_PORT=${NGINX_PORT}
- NEST_PORT=${NEST_PORT}
- GATE_PORT=${GATE_PORT}
- IPFS_PORT=${IPFS_PORT}
- GATE_PORT=8080
- IPFS_PORT=5001
- IPFS_ADMIN_USER=${IPFS_ADMIN_USER}
- IPFS_ADMIN_PASS=${IPFS_ADMIN_PASS}
healthcheck:
test: ["CMD-SHELL", "wget --spider --quiet http://127.0.0.1:$${NGINX_PORT}/"]
interval: 15s
timeout: 5s
start_period: 10s
retries: 3
depends_on:
- sscan-nest
- sscan-ipfs
sscan-nest:
condition: service_healthy
restart: true
sscan-ipfs:
condition: service_healthy
restart: true

networks:
nwk:
Expand Down
7 changes: 6 additions & 1 deletion nest/.dockerignore
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
node_modules
.git
**/._*
coverage
dist
node_modules
npm-debug.log*
100 changes: 61 additions & 39 deletions nest/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,70 +1,92 @@
# Build stage
FROM node@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14 AS build
# syntax=docker/dockerfile:1.7

WORKDIR /app
ARG NEAR_CLI_RS_VERSION=0.27.0

# Keep exact tags for reviewability and digests for reproducibility.
FROM node:24.17.0-alpine3.24@sha256:156b55f92e98ccd5ef49578a8cea0df4679826564bad1c9d4ef04462b9f0ded6 AS node-build

RUN apk add --update python3 make g++\
&& rm -rf /var/cache/apk/*
WORKDIR /app

# Copy configuration files
COPY eslint.config.mjs ./
COPY tsconfig*.json ./
COPY package*.json ./

# Install dependencies from package-lock.json
RUN npm ci
# Install dependencies from package-lock.json.
RUN --mount=type=cache,target=/root/.npm,sharing=locked npm ci

# Copy application sources (.ts, .tsx, js)
COPY src/ src/

# Build application (produces dist/ folder)
RUN npm run build

# Runtime (production) layer
FROM docker@sha256:7d85d0eda291f1a7ab6df4a9d1802b5ad4cf9145a088bd11188c78dcb5c7392b AS production
# Keep only dependencies needed by the production application.
FROM node-build AS node-production

RUN npm prune --omit=dev

# Build near-cli-rs separately so Rust and compiler tooling never reach runtime.
FROM rust:1.96.1-alpine3.24@sha256:a41f7740f8b45d45795624eec13a8b42263cc700f19f7e4e86e04d3dda08a479 AS near-cli-build

ARG NEAR_CLI_RS_VERSION

# Install dependencies for Rust, build tools, and HIDAPI
RUN apk add --no-cache \
python3 \
git \
curl \
perl \
protobuf \
eudev-dev \
build-base \
pkgconfig \
eudev-dev \
linux-headers \
bash
perl \
pkgconf \
protobuf

RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \
--mount=type=cache,target=/usr/local/cargo/git,sharing=locked \
--mount=type=cache,target=/tmp/cargo-target,sharing=locked \
CARGO_TARGET_DIR=/tmp/cargo-target \
cargo install near-cli-rs --version "${NEAR_CLI_RS_VERSION}" --locked

# Install Rust
ENV RUSTUP_HOME=/usr/local/rustup
ENV CARGO_HOME=/usr/local/cargo
RUN test "$(near --version)" = "near-cli-rs ${NEAR_CLI_RS_VERSION}"

# Runtime (production) layer
FROM docker:29.6.1-dind@sha256:66d292e5c26bd33a6f6f61cacb880de2186339a524ecba1ce098dbbaceed6515 AS production

RUN mkdir -p $RUSTUP_HOME $CARGO_HOME \
&& curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain stable -y \
&& ln -s $RUSTUP_HOME /root/.rustup \
&& ln -s $CARGO_HOME /root/.cargo
# Add Rust to the PATH environment variable for all subsequent commands
ENV PATH="/usr/local/cargo/bin:$PATH"
ARG NEAR_CLI_RS_VERSION

# Install near-cli-rs for contract verification
RUN cargo install near-cli-rs --locked
LABEL dev.sourcescan.near-cli-rs.version="${NEAR_CLI_RS_VERSION}"

# Set environment variable for near-cli-rs to run in non-interactive mode
ENV NEAR_CI=1
# Git requires libcurl, while Compose and Buildx are host-side tools here.
RUN apk upgrade --no-cache libcurl \
&& rm -f \
/usr/local/libexec/docker/cli-plugins/docker-buildx \
/usr/local/libexec/docker/cli-plugins/docker-compose

# Keep near-cli-rs non-interactive and point child processes at the embedded daemon.
ENV NEAR_CI=1 \
DOCKER_HOST=unix:///var/run/docker.sock

WORKDIR /app

# Copy node and npm from build stage
COPY --from=build /usr/local /usr/local
# Copy Node and the pinned near CLI into the runtime image.
COPY --from=node-build /usr/lib/libgcc_s.so.1 /usr/lib/
COPY --from=node-build /usr/lib/libstdc++.so.6* /usr/lib/
COPY --from=node-build /usr/local/bin/node /usr/local/bin/node
COPY --from=near-cli-build /usr/local/cargo/bin/near /usr/local/bin/near

# Copy dependencies files
# Copy package metadata and production dependencies.
COPY package*.json ./

# Install runtime dependencies (without dev/test dependencies)
RUN npm ci --omit=dev
COPY --from=node-production /app/node_modules/ ./node_modules/
COPY docker-healthcheck.js ./

# Copy production build
COPY --from=build /app/dist/ ./dist/
COPY --from=node-build /app/dist/ ./dist/

# Fail the image build if any required runtime binary is unusable.
RUN node --version \
&& near --version \
&& docker --version

HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 \
CMD ["node", "docker-healthcheck.js"]

# Start the application when dockerd is ready
CMD ["sh", "-ec", "rm -f /var/run/docker.pid /var/run/docker.sock /var/run/docker/containerd/containerd.pid /var/run/docker/containerd/containerd.sock /var/run/docker/containerd/containerd.sock.ttrpc /var/run/docker/containerd/containerd-debug.sock; dockerd & until docker info >/dev/null 2>&1; do sleep 1; done; exec node dist/main.js"]
CMD ["sh", "-ec", "rm -f /var/run/docker.pid /var/run/docker.sock /var/run/docker/containerd/containerd.pid /var/run/docker/containerd/containerd.sock /var/run/docker/containerd/containerd.sock.ttrpc /var/run/docker/containerd/containerd-debug.sock; dockerd & until node docker-healthcheck.js --docker-only >/dev/null 2>&1; do sleep 1; done; exec node dist/main.js"]
Comment thread
Canvinus marked this conversation as resolved.
44 changes: 44 additions & 0 deletions nest/docker-healthcheck.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use strict';

const http = require('node:http');

function request(options, label) {
return new Promise((resolve, reject) => {
const req = http.get(options, (response) => {
response.resume();

if (response.statusCode >= 200 && response.statusCode < 300) {
resolve();
return;
}

reject(new Error(`${label} returned HTTP ${response.statusCode}`));
});

req.setTimeout(3000, () => req.destroy(new Error(`${label} timed out`)));
req.on('error', reject);
});
}

async function main() {
await request(
{ socketPath: '/var/run/docker.sock', path: '/_ping' },
'Docker daemon',
);

if (process.argv.includes('--docker-only')) {
return;
}

const port = Number(process.env.NEST_PORT);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new Error('NEST_PORT must be a valid TCP port');
}

await request({ host: '127.0.0.1', port, path: '/' }, 'Verifier API');
}

main().catch((error) => {
console.error(error.message);
process.exit(1);
});
6 changes: 5 additions & 1 deletion nginx/scripts/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ set -e
# Generate htpasswd from environment variables
if [ -n "$IPFS_ADMIN_USER" ] && [ -n "$IPFS_ADMIN_PASS" ]; then
echo "Generating htpasswd for user: $IPFS_ADMIN_USER"
echo "$IPFS_ADMIN_USER:$(openssl passwd -apr1 $IPFS_ADMIN_PASS)" > /etc/nginx/.htpasswd
password_hash=$(printf '%s\n' "$IPFS_ADMIN_PASS" | mkpasswd -P 0 -m sha512)
printf '%s:%s\n' "$IPFS_ADMIN_USER" "$password_hash" > /etc/nginx/.htpasswd
else
echo "Warning: IPFS_ADMIN_USER or IPFS_ADMIN_PASS not set, creating empty htpasswd"
touch /etc/nginx/.htpasswd
fi

chown root:nginx /etc/nginx/.htpasswd
chmod 640 /etc/nginx/.htpasswd

# Start nginx
exec /docker-entrypoint.sh "$@"
Loading