Compare commits

..
Author SHA1 Message Date
Ryan King 026f91535b fix: Add drizzle-kit and schema files for database migrations
Lint Checks / Run linter (pull_request) Successful in 16s
Deploy Test / Run deploy test suite (pull_request) Successful in 2m59s
Webapp Test / Run webapp test suite (pull_request) Failing after 3m42s
K8s Deployment Control Test / Run deployment control suite on kind/k8s (pull_request) Failing after 4m11s
Smoke Test / Run basic test suite (pull_request) Successful in 4m29s
K8s Deploy Test / Run deploy test suite on kind/k8s (pull_request) Successful in 8m56s
2026-01-14 20:40:41 -05:00
Ryan KingandClaude Opus 4.5 ed676c00c9 feat: Add trashscan-explorer stack for Gorbagana blockchain explorer
Add a complete stack for deploying TrashScan-Explorer, a blockchain
explorer for Gorbagana mainnet (Solana fork).

## New Files

Stack definition:
- stacks/trashscan-explorer/stack.yml
- stacks/trashscan-explorer/README.md

Container build:
- container-build/cerc-trashscan-explorer/Dockerfile.base
- container-build/cerc-trashscan-explorer/Dockerfile
- container-build/cerc-trashscan-explorer/build.sh
- container-build/cerc-trashscan-explorer/scripts/start-explorer.sh

Compose:
- compose/docker-compose-trashscan-explorer.yml

## Components

- trashscan-explorer: React/Express blockchain explorer (port 5001)
- trashscan-db: PostgreSQL 14 database

## Usage

```bash
laconic-so --stack trashscan-explorer setup-repositories
laconic-so --stack trashscan-explorer build-containers
laconic-so --stack trashscan-explorer deploy-system up
```

Access at http://localhost:5001

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 17:09:55 -05:00
roysc ccccd9f957 Pass extra args to custom create command (#972)
Lint Checks / Run linter (push) Successful in 17s
Publish / Build and publish (push) Successful in 31s
Smoke Test / Run basic test suite (push) Successful in 1m51s
Webapp Test / Run webapp test suite (push) Successful in 2m7s
Deploy Test / Run deploy test suite (push) Successful in 2m45s
Fixturenet-Laconicd-Test / Run Laconicd fixturenet and Laconic CLI tests (push) Successful in 17m18s
K8s Deploy Test / Run deploy test suite on kind/k8s (push) Successful in 4m44s
Database Test / Run database hosting test on kind/k8s (push) Failing after 2m1s
Container Registry Test / Run contaier registry hosting test on kind/k8s (push) Failing after 1m19s
External Stack Test / Run external stack test suite (push) Successful in 2m42s
This is needed to allow custom deploy commands to handle arbitrary args.

* Adds a `DeploymentContext.modify_yaml` helper
* Removes `laconicd` from test stack to simplify it

Reviewed-on: #972
Reviewed-by: ashwin <ashwin@noreply.git.vdb.to>
2025-11-25 03:05:35 +00:00
12 changed files with 303 additions and 15 deletions
+8 -11
View File
@@ -26,7 +26,6 @@ from decouple import config
import subprocess
import click
from pathlib import Path
from typing import Any
from stack_orchestrator.opts import opts
from stack_orchestrator.util import include_exclude_check, stack_is_external, error_exit
from stack_orchestrator.base import get_npm_registry_url
@@ -43,7 +42,7 @@ def make_container_build_env(dev_root_path: str,
debug: bool,
force_rebuild: bool,
extra_build_args: str):
command_env: dict[str, Any] = {
container_build_env = {
"CERC_NPM_REGISTRY_URL": get_npm_registry_url(),
"CERC_GO_AUTH_TOKEN": config("CERC_GO_AUTH_TOKEN", default=""),
"CERC_NPM_AUTH_TOKEN": config("CERC_NPM_AUTH_TOKEN", default=""),
@@ -53,16 +52,14 @@ def make_container_build_env(dev_root_path: str,
"CERC_HOST_GID": f"{os.getgid()}",
"DOCKER_BUILDKIT": config("DOCKER_BUILDKIT", default="0")
}
command_env.update({"CERC_SCRIPT_DEBUG": "true"} if debug else {})
command_env.update({"CERC_FORCE_REBUILD": "true"} if force_rebuild else {})
command_env.update({"CERC_CONTAINER_EXTRA_BUILD_ARGS": extra_build_args} if extra_build_args else {})
container_build_env.update({"CERC_SCRIPT_DEBUG": "true"} if debug else {})
container_build_env.update({"CERC_FORCE_REBUILD": "true"} if force_rebuild else {})
container_build_env.update({"CERC_CONTAINER_EXTRA_BUILD_ARGS": extra_build_args} if extra_build_args else {})
docker_host_env = os.getenv("DOCKER_HOST")
if docker_host_env:
container_build_env.update({"DOCKER_HOST": docker_host_env})
forwarded_vars = ("DOCKER_HOST", "BUILDKIT_PROGRESS", "http_proxy", "https_proxy")
for var in forwarded_vars:
if value := config(var, default=None):
command_env[var] = value
return command_env
return container_build_env
def process_container(build_context: BuildContext) -> bool:
@@ -0,0 +1,47 @@
services:
trashscan-db:
image: postgres:14-alpine
restart: unless-stopped
environment:
POSTGRES_USER: ${TRASHSCAN_DB_USER:-trashscan}
POSTGRES_PASSWORD: ${TRASHSCAN_DB_PASSWORD:-password}
POSTGRES_DB: ${TRASHSCAN_DB_NAME:-trashscan}
POSTGRES_INITDB_ARGS: "-E UTF8 --locale=C"
volumes:
- trashscan_db_data:/var/lib/postgresql/data
ports:
- "5432"
healthcheck:
test: ["CMD", "pg_isready", "-U", "trashscan"]
interval: 10s
timeout: 5s
retries: 10
start_period: 5s
trashscan-explorer:
image: cerc/trashscan-explorer:local
restart: unless-stopped
depends_on:
trashscan-db:
condition: service_healthy
environment:
NODE_ENV: production
DATABASE_URL: ${DATABASE_URL:-postgres://trashscan:password@trashscan-db:5432/trashscan}
PORT: ${TRASHSCAN_PORT:-5000}
SESSION_SECRET: ${SESSION_SECRET:-change-me-in-production}
RPC_URL: ${RPC_URL:-https://rpc.trashscan.io/}
RUN_MIGRATIONS: ${RUN_MIGRATIONS:-true}
CERC_SCRIPT_DEBUG: ${CERC_SCRIPT_DEBUG}
ports:
- "${TRASHSCAN_HOST_PORT:-5001}:5000"
healthcheck:
test: ["CMD", "nc", "-z", "localhost", "5000"]
interval: 30s
timeout: 10s
retries: 10
start_period: 30s
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
trashscan_db_data:
@@ -0,0 +1,10 @@
FROM cerc/trashscan-explorer-base:local
COPY ./scripts/start-explorer.sh /scripts/
WORKDIR /app
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
CMD nc -z localhost 5000 || exit 1
CMD ["/scripts/start-explorer.sh"]
@@ -0,0 +1,59 @@
# Multi-stage build for TrashScan Explorer - Base image
# Stage 1: Build
FROM node:20-bullseye-slim AS builder
RUN apt-get update && apt-get install -y \
python3 \
make \
g++ \
git \
imagemagick \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
# Create placeholder assets if missing (required for build)
RUN mkdir -p attached_assets && \
cd attached_assets && \
for f in sticker2_1755759744667.webp sticker23_1755237287920.webp gormain_1759315091728.webp; do \
[ ! -f "$f" ] && convert -size 200x200 xc:gray "$f" 2>/dev/null || true; \
done && \
for f in canvas_1758470431079.png 6940a1305f6e525130bf_1762958059343.png icon_1763016116252.png \
image_1763473142686.png image_1763533198459.png trash-coin-with-text_1763537605889.png \
trashbun-logo.png Perps_1755290078134.png e6bf0fb3-0a22-485b-acd2-46e987e98c8f_1755633493962.png \
LendingBorrwing_1755289821275.png LaunchPad_1755289631741.png breakgorbagana_1755290274068.png \
"Gor Swap_1755289631741.png" bridge_1755289631741.png NFTMarketplace_1755289909254.png; do \
[ ! -f "$f" ] && convert -size 200x200 xc:gray "$f" 2>/dev/null || true; \
done && \
for f in gor-incinerator-logo.jpg; do \
[ ! -f "$f" ] && convert -size 200x200 xc:gray "$f" 2>/dev/null || true; \
done && \
[ ! -f "maintenance1_1759314383900.mp3" ] && printf '\xff\xfb\x90\x00' > maintenance1_1759314383900.mp3 || true
RUN npm run build
# Stage 2: Production runtime
FROM node:20-bullseye-slim
RUN apt-get update && apt-get install -y \
netcat-openbsd \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/drizzle.config.ts ./
COPY --from=builder /app/shared ./shared
# Install production deps + vite + drizzle-kit (needed for migrations and server runtime)
RUN npm ci --omit=dev && npm install vite drizzle-kit
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/public ./public
COPY --from=builder /app/attached_assets ./attached_assets
EXPOSE 5000
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
# Build the TrashScan Explorer image
source ${CERC_CONTAINER_BASE_DIR}/build-base.sh
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# Two-stage build: base image from repo, final image with local scripts
docker build -t cerc/trashscan-explorer-base:local \
${build_command_args} \
-f ${SCRIPT_DIR}/Dockerfile.base \
${CERC_REPO_BASE_DIR}/TrashScan-Explorer
if [[ $? -ne 0 ]]; then
echo "FATAL: Base container build failed, exiting"
exit 1
fi
docker build -t cerc/trashscan-explorer:local \
${build_command_args} \
-f ${SCRIPT_DIR}/Dockerfile \
${SCRIPT_DIR}
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
set -e
if [ -n "$CERC_SCRIPT_DEBUG" ]; then
set -x
fi
echo "TrashScan Explorer starting..."
# Wait for database to be ready
if [ -n "$DATABASE_URL" ]; then
echo "Waiting for database to be ready..."
# Parse DATABASE_URL: postgres://user:pass@host:port/db
DB_HOST=$(echo $DATABASE_URL | sed -e 's|.*@||' -e 's|:.*||' -e 's|/.*||')
DB_PORT=$(echo $DATABASE_URL | sed -e 's|.*@[^:]*:||' -e 's|/.*||')
if [ -z "$DB_PORT" ] || [ "$DB_PORT" = "$DB_HOST" ]; then
DB_PORT=5432
fi
timeout=60
counter=0
until nc -z "$DB_HOST" "$DB_PORT" 2>/dev/null; do
counter=$((counter + 1))
if [ $counter -ge $timeout ]; then
echo "Error: Database not available after ${timeout} seconds"
exit 1
fi
echo "Waiting for database at ${DB_HOST}:${DB_PORT}... ($counter/$timeout)"
sleep 1
done
echo "Database is available!"
fi
# Run database migrations if needed
if [ "${RUN_MIGRATIONS:-true}" = "true" ]; then
echo "Running database migrations..."
npx drizzle-kit push --config=drizzle.config.ts 2>&1 || echo "Migration warning (tables may already exist), continuing..."
fi
# Start the application
echo "Starting TrashScan Explorer on port ${PORT:-5000}..."
exec node dist/index.js
@@ -60,3 +60,4 @@ cerc/nitro-rpc-client
cerc/watcher-merkl-sushiswap-v3
cerc/watcher-sushiswap-v3
cerc/uniswap-interface
cerc/trashscan-explorer
+1
View File
@@ -45,3 +45,4 @@ ponder
ipld-eth-server-payments
merkl-sushiswap-v3
sushiswap-v3
trashscan-explorer
@@ -50,3 +50,4 @@ github.com/cerc-io/ponder
github.com/cerc-io/merkl-sushiswap-v3-watcher-ts
github.com/cerc-io/sushiswap-v3-watcher-ts
github.com/cerc-io/uniswap-interface
github.com/gorbagana-dev/TrashScan-Explorer
@@ -0,0 +1,99 @@
# TrashScan Explorer Stack
TrashScan is a blockchain explorer for Gorbagana mainnet (Solana fork).
## Quick Start
```bash
# 1. Setup repositories (clones TrashScan-Explorer to ~/cerc/)
laconic-so --stack trashscan-explorer setup-repositories
# 2. Build containers
laconic-so --stack trashscan-explorer build-containers
# 3. Deploy
laconic-so --stack trashscan-explorer deploy-system up
# 4. Verify
docker ps --filter "name=trashscan"
curl http://localhost:5001/
# 5. View logs
laconic-so --stack trashscan-explorer deploy-system logs -f
# 6. Stop
laconic-so --stack trashscan-explorer deploy-system down
```
## Access
After deployment, access the explorer at: **http://localhost:5001**
Note: Default port is 5001 to avoid conflict with macOS AirPlay Receiver on port 5000.
## Components
| Service | Image | Port | Description |
|---------|-------|------|-------------|
| trashscan-explorer | cerc/trashscan-explorer:local | 5001 | React/Express blockchain explorer |
| trashscan-db | postgres:14-alpine | (internal) | PostgreSQL database |
## Configuration
Environment variables can be set in your deployment configuration:
| Variable | Default | Description |
|----------|---------|-------------|
| NODE_ENV | production | Node environment |
| DATABASE_URL | postgres://trashscan:password@trashscan-db:5432/trashscan | Database connection string |
| TRASHSCAN_HOST_PORT | 5001 | Host port for explorer |
| SESSION_SECRET | change-me-in-production | Express session secret |
| RPC_URL | https://rpc.trashscan.io/ | Gorbagana RPC endpoint |
| RUN_MIGRATIONS | true | Run database migrations on startup |
## External Dependencies
The explorer connects to the Gorbagana RPC at https://rpc.trashscan.io/ by default.
## Files in This Stack
```
stack-orchestrator/stack_orchestrator/data/
├── stacks/trashscan-explorer/
│ ├── stack.yml # Stack definition
│ └── README.md # This file
├── container-build/cerc-trashscan-explorer/
│ ├── Dockerfile.base # Multi-stage build (base)
│ ├── Dockerfile # Final image with scripts
│ ├── build.sh # Build script
│ └── scripts/
│ └── start-explorer.sh # Container startup script
└── compose/
└── docker-compose-trashscan-explorer.yml # Docker Compose
```
## Verification Checklist
After deployment, verify:
- [ ] `docker ps` shows both containers as `(healthy)`
- [ ] `curl http://localhost:5001/` returns HTTP 200
- [ ] Logs show "TrashScan Explorer starting..." and "Database is available!"
## Troubleshooting
### Port 5000 conflict (macOS)
macOS AirPlay Receiver uses port 5000. This stack defaults to 5001.
To use a different port: `export TRASHSCAN_HOST_PORT=8080`
### Missing assets error during build
The upstream TrashScan-Explorer repo may be missing the `attached_assets/` directory.
Create placeholder images if needed:
```bash
cd ~/cerc/TrashScan-Explorer
mkdir -p attached_assets
# Create placeholder images for any missing assets
```
### "Cannot find package 'vite'" error
The Dockerfile.base includes vite in production deps to handle this.
@@ -0,0 +1,9 @@
version: "1.0"
name: trashscan-explorer
description: "TrashScan blockchain explorer for Gorbagana mainnet"
repos:
- github.com/gorbagana-dev/TrashScan-Explorer
containers:
- cerc/trashscan-explorer
pods:
- trashscan-explorer
@@ -422,10 +422,9 @@ def _copy_files_to_directory(file_paths: List[Path], directory: Path):
copy(path, os.path.join(directory, os.path.basename(path)))
def _create_deployment_file(deployment_dir: Path, cluster):
def _create_deployment_file(deployment_dir: Path):
deployment_file_path = deployment_dir.joinpath(constants.deployment_file_name)
if cluster is None:
cluster = f"{constants.cluster_name_prefix}{token_hex(8)}"
cluster = f"{constants.cluster_name_prefix}{token_hex(8)}"
with open(deployment_file_path, "w") as output_file:
output_file.write(f"{constants.cluster_id_key}: {cluster}\n")
@@ -472,7 +471,7 @@ def create_operation(deployment_command_context, spec_file, deployment_dir, extr
# Copy spec file and the stack file into the deployment dir
copyfile(spec_file, deployment_dir_path.joinpath(constants.spec_file_name))
copyfile(stack_file, deployment_dir_path.joinpath(constants.stack_file_name))
_create_deployment_file(deployment_dir_path, deployment_command_context.cluster_context.cluster)
_create_deployment_file(deployment_dir_path)
# Copy any config varibles from the spec file into an env file suitable for compose
_write_config_file(spec_file, deployment_dir_path.joinpath(constants.config_file_name))
# Copy any k8s config file into the deployment dir