Compare commits

..
Author SHA1 Message Date
zramsay 1e3eff0914 Update stack_orchestrator/data/container-build/cerc-nextjs-base/scripts/start-serving-app.sh
Lint Checks / Run linter (push) Successful in 31s
Lint Checks / Run linter (pull_request) Successful in 31s
Deploy Test / Run deploy test suite (pull_request) Successful in 4m33s
K8s Deploy Test / Run deploy test suite on kind/k8s (pull_request) Successful in 7m36s
K8s Deployment Control Test / Run deployment control suite on kind/k8s (pull_request) Successful in 5m25s
Webapp Test / Run webapp test suite (pull_request) Successful in 4m45s
Smoke Test / Run basic test suite (pull_request) Successful in 3m37s
2024-11-25 17:03:59 +00:00
23 changed files with 61 additions and 391 deletions
@@ -1,47 +0,0 @@
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:
@@ -26,14 +26,8 @@ fi
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
WORK_DIR="${1:-/app}"
if [ -f "${WORK_DIR}/build-webapp.sh" ]; then
echo "Building webapp with ${WORK_DIR}/build-webapp.sh ..."
cd "${WORK_DIR}" || exit 1
./build-webapp.sh || exit 1
exit 0
fi
if [ -f "next.config.mjs" ]; then
NEXT_CONFIG_JS="next.config.mjs"
IMPORT_OR_REQUIRE="import"
@@ -5,7 +5,7 @@ fi
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
CERC_MAX_GENERATE_TIME=${CERC_MAX_GENERATE_TIME:-120}
CERC_MAX_GENERATE_TIME=${CERC_MAX_GENERATE_TIME:-600}
tpid=""
ctrl_c() {
@@ -30,44 +30,36 @@ fi
CERC_WEBAPP_FILES_DIR="${CERC_WEBAPP_FILES_DIR:-/app}"
cd "$CERC_WEBAPP_FILES_DIR"
if [ -f "./run-webapp.sh" ]; then
echo "Running webapp with run-webapp.sh ..."
cd "${WORK_DIR}" || exit 1
./run-webapp.sh &
tpid=$!
wait $tpid
else
"$SCRIPT_DIR/apply-runtime-env.sh" "`pwd`" .next .next-r
mv .next .next.old
mv .next-r/.next .
"$SCRIPT_DIR/apply-runtime-env.sh" "`pwd`" .next .next-r
mv .next .next.old
mv .next-r/.next .
if [ "$CERC_NEXTJS_SKIP_GENERATE" != "true" ]; then
jq -e '.scripts.cerc_generate' package.json >/dev/null
if [ $? -eq 0 ]; then
npm run cerc_generate > gen.out 2>&1 &
tail -f gen.out &
tpid=$!
if [ "$CERC_NEXTJS_SKIP_GENERATE" != "true" ]; then
jq -e '.scripts.cerc_generate' package.json >/dev/null
if [ $? -eq 0 ]; then
npm run cerc_generate > gen.out 2>&1 &
tail -f gen.out &
tpid=$!
count=0
generate_done="false"
while [ $count -lt $CERC_MAX_GENERATE_TIME ] && [ "$generate_done" == "false" ]; do
sleep 1
count=$((count + 1))
grep 'rendered as static' gen.out > /dev/null
if [ $? -eq 0 ]; then
generate_done="true"
fi
done
if [ $generate_done != "true" ]; then
echo "ERROR: 'npm run cerc_generate' not successful within CERC_MAX_GENERATE_TIME" 1>&2
exit 1
count=0
generate_done="false"
while [ $count -lt $CERC_MAX_GENERATE_TIME ] && [ "$generate_done" == "false" ]; do
sleep 1
count=$((count + 1))
grep 'rendered as static' gen.out > /dev/null
if [ $? -eq 0 ]; then
generate_done="true"
fi
done
kill $tpid $(ps -ef | grep node | grep next | grep generate | awk '{print $2}') 2>/dev/null
tpid=""
if [ $generate_done != "true" ]; then
echo "ERROR: 'npm run cerc_generate' not successful within CERC_MAX_GENERATE_TIME" 1>&2
exit 1
fi
fi
$CERC_BUILD_TOOL start . -- -p ${CERC_LISTEN_PORT:-80}
kill $tpid $(ps -ef | grep node | grep next | grep generate | awk '{print $2}') 2>/dev/null
tpid=""
fi
fi
$CERC_BUILD_TOOL start . -- -p ${CERC_LISTEN_PORT:-80}
@@ -1,10 +0,0 @@
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"]
@@ -1,59 +0,0 @@
# 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
@@ -1,21 +0,0 @@
#!/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}
@@ -1,44 +0,0 @@
#!/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,4 +60,3 @@ cerc/nitro-rpc-client
cerc/watcher-merkl-sushiswap-v3
cerc/watcher-sushiswap-v3
cerc/uniswap-interface
cerc/trashscan-explorer
-1
View File
@@ -45,4 +45,3 @@ ponder
ipld-eth-server-payments
merkl-sushiswap-v3
sushiswap-v3
trashscan-explorer
@@ -50,4 +50,3 @@ 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
@@ -14,6 +14,7 @@
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
from stack_orchestrator.deploy.deployment_context import DeploymentContext
from ruamel.yaml import YAML
def create(context: DeploymentContext, extra_args):
@@ -22,12 +23,17 @@ def create(context: DeploymentContext, extra_args):
# deterministic-deployment-proxy contract, which itself is a prereq for Optimism contract deployment
fixturenet_eth_compose_file = context.deployment_dir.joinpath('compose', 'docker-compose-fixturenet-eth.yml')
with open(fixturenet_eth_compose_file, 'r') as yaml_file:
yaml = YAML()
yaml_data = yaml.load(yaml_file)
new_script = '../config/fixturenet-optimism/run-geth.sh:/opt/testnet/run.sh'
def add_geth_volume(yaml_data):
if new_script not in yaml_data['services']['fixturenet-eth-geth-1']['volumes']:
yaml_data['services']['fixturenet-eth-geth-1']['volumes'].append(new_script)
if new_script not in yaml_data['services']['fixturenet-eth-geth-1']['volumes']:
yaml_data['services']['fixturenet-eth-geth-1']['volumes'].append(new_script)
context.modify_yaml(fixturenet_eth_compose_file, add_geth_volume)
with open(fixturenet_eth_compose_file, 'w') as yaml_file:
yaml = YAML()
yaml.dump(yaml_data, yaml_file)
return None
@@ -2,6 +2,7 @@ version: "1.0"
name: test
description: "A test stack"
repos:
- git.vdb.to/cerc-io/laconicd
- git.vdb.to/cerc-io/test-project@test-branch
containers:
- cerc/test-container
@@ -1,99 +0,0 @@
# 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.
@@ -1,9 +0,0 @@
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
@@ -45,22 +45,20 @@ class DeploymentContext:
def get_compose_dir(self):
return self.deployment_dir.joinpath(constants.compose_dir_name)
def get_compose_file(self, name: str):
return self.get_compose_dir() / f"docker-compose-{name}.yml"
def get_cluster_id(self):
return self.id
def init(self, dir: Path):
self.deployment_dir = dir.absolute()
def init(self, dir):
self.deployment_dir = dir
self.spec = Spec()
self.spec.init_from_file(self.get_spec_file())
self.stack = Stack(self.spec.obj["stack"])
self.stack.init_from_file(self.get_stack_file())
deployment_file_path = self.get_deployment_file()
if deployment_file_path.exists():
obj = get_yaml().load(open(deployment_file_path, "r"))
self.id = obj[constants.cluster_id_key]
with deployment_file_path:
obj = get_yaml().load(open(deployment_file_path, "r"))
self.id = obj[constants.cluster_id_key]
# Handle the case of a legacy deployment with no file
# Code below is intended to match the output from _make_default_cluster_name()
# TODO: remove when we no longer need to support legacy deployments
@@ -69,19 +67,3 @@ class DeploymentContext:
unique_cluster_descriptor = f"{path},{self.get_stack_file()},None,None"
hash = hashlib.md5(unique_cluster_descriptor.encode()).hexdigest()[:16]
self.id = f"{constants.cluster_name_prefix}{hash}"
def modify_yaml(self, file_path: Path, modifier_func):
"""
Load a YAML from the deployment, apply a modification function, and write it back.
"""
if not file_path.absolute().is_relative_to(self.deployment_dir):
raise ValueError(f"File is not inside deployment directory: {file_path}")
yaml = get_yaml()
with open(file_path, 'r') as f:
yaml_data = yaml.load(f)
modifier_func(yaml_data)
with open(file_path, 'w') as f:
yaml.dump(yaml_data, f)
@@ -443,16 +443,18 @@ def _check_volume_definitions(spec):
@click.command()
@click.option("--spec-file", required=True, help="Spec file to use to create this deployment")
@click.option("--deployment-dir", help="Create deployment files in this directory")
@click.argument('extra_args', nargs=-1, type=click.UNPROCESSED)
# TODO: Hack
@click.option("--network-dir", help="Network configuration supplied in this directory")
@click.option("--initial-peers", help="Initial set of persistent peers")
@click.pass_context
def create(ctx, spec_file, deployment_dir, extra_args):
def create(ctx, spec_file, deployment_dir, network_dir, initial_peers):
deployment_command_context = ctx.obj
return create_operation(deployment_command_context, spec_file, deployment_dir, extra_args)
return create_operation(deployment_command_context, spec_file, deployment_dir, network_dir, initial_peers)
# The init command's implementation is in a separate function so that we can
# call it from other commands, bypassing the click decoration stuff
def create_operation(deployment_command_context, spec_file, deployment_dir, extra_args):
def create_operation(deployment_command_context, spec_file, deployment_dir, network_dir, initial_peers):
parsed_spec = Spec(os.path.abspath(spec_file), get_parsed_deployment_spec(spec_file))
_check_volume_definitions(parsed_spec)
stack_name = parsed_spec["stack"]
@@ -539,7 +541,7 @@ def create_operation(deployment_command_context, spec_file, deployment_dir, extr
deployer_config_generator = getDeployerConfigGenerator(deployment_type, deployment_context)
# TODO: make deployment_dir_path a Path above
deployer_config_generator.generate(deployment_dir_path)
call_stack_deploy_create(deployment_context, extra_args)
call_stack_deploy_create(deployment_context, [network_dir, initial_peers, deployment_command_context])
# TODO: this code should be in the stack .py files but
+3 -2
View File
@@ -92,8 +92,9 @@ class Spec:
return self.obj.get(item, default)
def init_from_file(self, file_path: Path):
self.obj = get_yaml().load(open(file_path, "r"))
self.file_path = file_path
with file_path:
self.obj = get_yaml().load(open(file_path, "r"))
self.file_path = file_path
def get_image_registry(self):
return self.obj.get(constants.image_registry_key)
+2 -1
View File
@@ -27,4 +27,5 @@ class Stack:
self.name = name
def init_from_file(self, file_path: Path):
self.obj = get_yaml().load(open(file_path, "r"))
with file_path:
self.obj = get_yaml().load(open(file_path, "r"))
@@ -92,6 +92,7 @@ def create_deployment(ctx, deployment_dir, image, url, kube_config, image_regist
spec_file_name,
deployment_dir,
None,
None
)
# Fix up the container tag inside the deployment compose file
_fixup_container_tag(deployment_dir, image)
@@ -54,7 +54,6 @@ def process_app_deployment_request(
deployment_record_namespace,
dns_record_namespace,
default_dns_suffix,
dns_value,
deployment_parent_dir,
kube_config,
image_registry,
@@ -252,7 +251,6 @@ def process_app_deployment_request(
dns_record,
dns_lrn,
deployment_dir,
dns_value,
app_deployment_request,
webapp_deployer_record,
logger,
@@ -306,7 +304,6 @@ def dump_known_requests(filename, requests, status="SEEN"):
help="How to handle requests with an FQDN: prohibit, allow, preexisting",
default="prohibit",
)
@click.option("--ip", help="IP address of the k8s deployment (to be set in DNS record)", default=None)
@click.option("--record-namespace-dns", help="eg, lrn://laconic/dns", required=True)
@click.option(
"--record-namespace-deployments",
@@ -384,7 +381,6 @@ def command( # noqa: C901
only_update_state,
dns_suffix,
fqdn_policy,
ip,
record_namespace_dns,
record_namespace_deployments,
dry_run,
@@ -433,13 +429,6 @@ def command( # noqa: C901
)
sys.exit(2)
if fqdn_policy == "allow" and not ip:
print(
"--ip is required with 'allow' fqdn-policy",
file=sys.stderr,
)
sys.exit(2)
tempdir = tempfile.mkdtemp()
gpg = gnupg.GPG(gnupghome=tempdir)
@@ -676,7 +665,6 @@ def command( # noqa: C901
record_namespace_deployments,
record_namespace_dns,
dns_suffix,
ip,
os.path.abspath(deployment_parent_dir),
kube_config,
image_registry,
-3
View File
@@ -689,7 +689,6 @@ def publish_deployment(
dns_record,
dns_lrn,
deployment_dir,
dns_value=None,
app_deployment_request=None,
webapp_deployer_record=None,
logger=None,
@@ -722,8 +721,6 @@ def publish_deployment(
}
if app_deployment_request:
new_dns_record["record"]["request"] = app_deployment_request.id
if dns_value:
new_dns_record["record"]["value"] = dns_value
if logger:
logger.log("Publishing DnsRecord.")
+3 -1
View File
@@ -180,7 +180,9 @@ def get_k8s_dir():
def get_parsed_deployment_spec(spec_file):
spec_file_path = Path(spec_file)
try:
return get_yaml().load(open(spec_file_path, "r"))
with spec_file_path:
deploy_spec = get_yaml().load(open(spec_file_path, "r"))
return deploy_spec
except FileNotFoundError as error:
# We try here to generate a useful diagnostic error
print(f"Error: spec file: {spec_file_path} does not exist")
+2 -7
View File
@@ -14,13 +14,8 @@ delete_cluster_exit () {
# Test basic stack-orchestrator deploy
echo "Running stack-orchestrator deploy test"
if [ "$1" == "from-path" ]; then
TEST_TARGET_SO="laconic-so"
else
TEST_TARGET_SO=$( ls -t1 ./package/laconic-so* | head -1 )
fi
# Bit of a hack, test the most recent package
TEST_TARGET_SO=$( ls -t1 ./package/laconic-so* | head -1 )
# Set a non-default repo dir
export CERC_REPO_BASE_DIR=~/stack-orchestrator-test/repo-base-dir
echo "Testing this package: $TEST_TARGET_SO"