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
4 changes: 2 additions & 2 deletions .devcontainer/dead-reckoning_beginner/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
}
}
},
"forwardPorts": [3000, 7007, 30110],
"forwardPorts": [3000, 7007, 30112],
"portsAttributes": {
"3000": {
"label": "Backstage",
Expand All @@ -30,7 +30,7 @@
"label": "Backstage Backend",
"onAutoForward": "silent"
},
"30110": {
"30112": {
"label": "Gitea",
"onAutoForward": "notify"
}
Expand Down
2 changes: 1 addition & 1 deletion .devcontainer/dead-reckoning_beginner/post-create.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ track_container_created
# The scaffolder's publish:gitea action can only create repos under an existing
# organization, so this must exist before the commissioning template runs.
echo "✨ Creating Gitea organization 'fleet'"
GITEA_URL="http://localhost:30110"
GITEA_URL="http://localhost:30112"
until curl -sf "$GITEA_URL/api/v1/version" >/dev/null 2>&1; do sleep 3; done
# Idempotent: a re-run returns 422 (org already exists), which we ignore.
curl -sf -X POST "$GITEA_URL/api/v1/orgs" \
Expand Down
2 changes: 1 addition & 1 deletion .devcontainer/dead-reckoning_beginner/post-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ cat <<'EOF'
✨ Dead Reckoning — 🟢 Beginner (Laying the Keel)

The platform is ready:
• Gitea (the archives) is running in the cluster on port 30110
• Gitea (the archives) is running in the cluster on port 30112
• The vessel commissioning template is loaded into Backstage

▶ Start Backstage (the commission office):
Expand Down
47 changes: 47 additions & 0 deletions .devcontainer/dead-reckoning_intermediate/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "🧭 Dead Reckoning | 🟡 Intermediate (Sea Trial)",
"image": "mcr.microsoft.com/devcontainers/base:bullseye",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}/adventures/dead-reckoning/intermediate",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
"ghcr.io/devcontainers/features/node:1": {
"version": "22"
}
},
"postCreateCommand": "bash /workspaces/${localWorkspaceFolderBasename}/.devcontainer/dead-reckoning_intermediate/post-create.sh",
"postStartCommand": "bash /workspaces/${localWorkspaceFolderBasename}/.devcontainer/dead-reckoning_intermediate/post-start.sh",
"customizations": {
"codespaces": {
"openFiles": [],
"permissions": {
"codespaces": "write"
}
}
},
"forwardPorts": [3000, 7007, 30112, 30100, 30113],
"portsAttributes": {
"3000": {
"label": "Backstage",
"onAutoForward": "notify"
},
"7007": {
"label": "Backstage Backend",
"onAutoForward": "silent"
},
"30112": {
"label": "Gitea",
"onAutoForward": "notify"
},
"30100": {
"label": "Argo CD",
"onAutoForward": "silent"
},
"30113": {
"label": "Argo Workflows",
"onAutoForward": "silent"
}
},
"otherPortsAttributes": {
"onAutoForward": "ignore"
}
}
61 changes: 61 additions & 0 deletions .devcontainer/dead-reckoning_intermediate/post-create.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
set -e

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
CHALLENGE_DIR="$REPO_ROOT/adventures/dead-reckoning/intermediate"

# shellcheck disable=SC1091
source "$REPO_ROOT/lib/scripts/tracker.sh"
set_tracking_context "dead-reckoning" "intermediate" "06" "07" "2026"
track_container_created

"$REPO_ROOT/lib/shared/init.sh" --version v0.17.0

"$REPO_ROOT/lib/github-cli/init.sh" --version v2.96.0 # https://github.com/cli/cli/releases

"$REPO_ROOT/lib/kubernetes/init.sh" \
--kind-version v0.32.0 \
--kubectl-version v1.36.2 \
--kubens-version v0.11.0 \
--k9s-version v0.51.0 \
--helm-version v4.2.3

"$REPO_ROOT/lib/gitea/init.sh" --version 12.6.0

"$REPO_ROOT/lib/argo-workflows/init.sh" --version 1.0.19 # https://artifacthub.io/packages/helm/argo/argo-workflows (app v4.0.7)

"$REPO_ROOT/lib/argo-events/init.sh" --version 2.4.22 # https://artifacthub.io/packages/helm/argo/argo-events (app v1.9.10)

"$REPO_ROOT/lib/argocd/init.sh" --read-only --version v3.4.5 # https://github.com/argoproj/argo-cd/releases

# Create the Gitea organization that vessel repositories are published into.
# The scaffolder's publish:gitea action can only create repos under an existing
# organization, so this must exist before the commissioning template runs.
echo "✨ Creating Gitea organization 'fleet'"
GITEA_URL="http://localhost:30112"
until curl -sf "$GITEA_URL/api/v1/version" >/dev/null 2>&1; do sleep 3; done
# Idempotent: a re-run returns 422 (org already exists), which we ignore.
curl -sf -X POST "$GITEA_URL/api/v1/orgs" \
-H "Content-Type: application/json" \
-u "admin:a-super-secure-password" \
-d '{"username": "fleet", "visibility": "public"}' >/dev/null || true

# Kaniko needs registry credentials to push built images to Gitea's built-in
# container registry, which authenticates with the same admin credentials as
# the Gitea API.
echo "✨ Creating kaniko registry credentials secret"
docker_config_json=$(printf '{"auths":{"localhost:30112":{"auth":"%s"}}}' \
"$(printf 'admin:a-super-secure-password' | base64 | tr -d '\n')")
kubectl create secret generic kaniko-docker-config \
--namespace argo-workflows \
--from-literal=config.json="$docker_config_json" \
--dry-run=client -o yaml | kubectl apply -f -

echo "✨ Installing Backstage dependencies"
# Disable corepack's interactive "download Yarn x.y.z?" prompt. Without this the
# first yarn invocation blocks post-create waiting for stdin that never comes.
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
corepack enable
(cd "$CHALLENGE_DIR/backstage" && yarn install --immutable)

echo "✅ Post-create complete"
63 changes: 63 additions & 0 deletions .devcontainer/dead-reckoning_intermediate/post-start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
set -e

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
CHALLENGE_DIR="$REPO_ROOT/adventures/dead-reckoning/intermediate"
GITEA_URL="http://localhost:30112"
GITEA_AUTH="admin:a-super-secure-password"

# Bring up the trigger path (Gitea push -> Argo Events -> Argo Workflow) and
# the delivery workflow itself (clone -> build -> update-deploy).
echo "✨ Applying the Argo Events trigger path"
kubectl apply -f "$CHALLENGE_DIR/platform/argo-events/"
echo "✨ Applying the delivery workflow"
kubectl apply -f "$CHALLENGE_DIR/platform/argo-workflows/"
echo "✨ Applying the Argo CD ApplicationSet"
kubectl apply -f "$CHALLENGE_DIR/platform/argocd/"

# The EventSource controller creates gitea-webhook-eventsource-svc once it
# reconciles the EventSource applied above; the org webhook can't be
# registered until that Service (its DNS name is the webhook target) exists.
echo "✨ Waiting for the gitea-webhook EventSource to be ready"
until kubectl get svc gitea-webhook-eventsource-svc -n argo-events >/dev/null 2>&1; do sleep 3; done

echo "✨ Registering the fleet org webhook"
until curl -sf "$GITEA_URL/api/v1/version" >/dev/null 2>&1; do sleep 3; done
# Idempotent: a re-run returns 422 (an identical hook already exists), which
# we ignore. Every repo pushed to the fleet org fires this webhook; the
# Sensor (platform/argo-events/sensor.yaml) is what filters out "-deploy"
# pushes, not the webhook itself.
curl -sf -X POST "$GITEA_URL/api/v1/orgs/fleet/hooks" \
-H "Content-Type: application/json" \
-u "$GITEA_AUTH" \
-d '{
"type": "gitea",
"config": {
"url": "http://gitea-webhook-eventsource-svc.argo-events.svc.cluster.local:12000/push",
"content_type": "json"
},
"events": ["push"],
"active": true
}' >/dev/null || true

cat <<'EOF'

✨ Dead Reckoning — 🟡 Intermediate (Sea Trial)

The platform is up:
• Gitea (the archives) → port 30112
• Argo Workflows (the shipyard) → port 30113
• Argo CD (the harbor master) → port 30100

▶ Start Backstage (the commission office):
make backstage

✅ When you think you've fixed the pipeline, verify your work:
make verify

EOF

# shellcheck disable=SC1091
source "$REPO_ROOT/lib/scripts/tracker.sh"
set_tracking_context "dead-reckoning" "intermediate" "06" "07" "2026"
track_container_initialized
17 changes: 17 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,23 @@ policy exists.
that players open their Codespace and the problem is immediately visible — not a blank
slate. The broken state should be the first thing they see.

### Ports (NodePorts)

NodePorts are a single, cluster-wide namespace. `lib/kubernetes/config.yaml` (shared by
every adventure) maps each one to the host, so a port number must mean the same tool
everywhere. Treat that file as the authoritative port registry.

- **One tool, one port, globally.** Never reuse a NodePort for a different tool. If two
challenges each grabbed, say, 30110 for different services, a future challenge that needs
both tools at once could not run. Gitea is always 30112, Falcosidekick always 30111, and
so on.
- **Reuse across levels is fine for the same tool** (the Dead Reckoning beginner and
intermediate both serve Gitea on 30112). Reuse across *different* tools is not.
- **Adding a new tool:** claim the next free `hostPort`/`containerPort` in
`lib/kubernetes/config.yaml`, label it with the tool's name in a comment, and forward it
in the level's `devcontainer.json`. If the tool is already listed there, reuse its
existing port instead of allocating a new one.

### Makefile

Keep it minimal. For a typical challenge level:
Expand Down
2 changes: 1 addition & 1 deletion adventures/dead-reckoning/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

The Grand Fleet's commission office is buried in complaints. Manifests are filed but nothing comes of them. Vessels that do sail arrive at port with the wrong cargo, and no one along the route can explain why. As the fleet's engineer, your mission is to restore order from keel to quayside and find out what the records are hiding.

**Technologies:** Backstage, Gitea, Argo Workflows, Argo CD, OpenTelemetry, Jaeger, Kubernetes
**Technologies:** Backstage, Gitea, Argo Workflows, Argo CD, Kubernetes

The entire **infrastructure is pre-provisioned in your Codespace**
**You don't need to set up anything locally. Just focus on solving the problem.**
Expand Down
6 changes: 3 additions & 3 deletions adventures/dead-reckoning/beginner/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ help:

# Foreground start so you get live logs and can restart with Ctrl-C + make backstage.
# In a Codespace two ports must be public: 7007 so the browser SPA can reach the
# backend, and 30110 so both the browser and the scaffolder backend can reach Gitea
# at its forwarded URL. 30110 (Gitea) is already up, so it flips immediately; 7007
# backend, and 30112 so both the browser and the scaffolder backend can reach Gitea
# at its forwarded URL. 30112 (Gitea) is already up, so it flips immediately; 7007
# only forwards once Backstage is listening, so that one retries. We also layer the
# Codespaces config overlay so the baseUrls point at forwarded URLs.
backstage:
@echo "⚓ Starting Backstage. First start compiles for ~30-60s, then open port 3000."
@configs="--config $(CURDIR)/backstage/app-config.yaml"; \
if [ -n "$$CODESPACE_NAME" ]; then \
configs="$$configs --config $(CURDIR)/backstage/app-config.codespaces.yaml"; \
( until gh codespace ports visibility 30110:public -c "$$CODESPACE_NAME" >/dev/null 2>&1; do sleep 3; done; \
( until gh codespace ports visibility 30112:public -c "$$CODESPACE_NAME" >/dev/null 2>&1; do sleep 3; done; \
until gh codespace ports visibility 7007:public -c "$$CODESPACE_NAME" >/dev/null 2>&1; do sleep 3; done ) & \
fi; \
cd backstage && COREPACK_ENABLE_DOWNLOAD_PROMPT=0 yarn start $$configs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,23 @@ backend:
cors:
origin: https://${CODESPACE_NAME}-3000.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}

# In a Codespace, localhost:30110 is not reachable from the browser, so every
# In a Codespace, localhost:30112 is not reachable from the browser, so every
# Gitea URL Backstage generates (repo links, catalog source location) must use
# the public forwarded URL instead. The base app-config.yaml keeps localhost, so
# local dev is unaffected. Requires port 30110 to be public (make backstage does that).
# local dev is unaffected. Requires port 30112 to be public (make backstage does that).
#
# Two entries are needed because Backstage keys integrations by host:
# 1. host localhost:30110 matches the repoUrl the template builds, so publish:gitea
# 1. host localhost:30112 matches the repoUrl the template builds, so publish:gitea
# finds it. Its forwarded baseUrl makes publish emit forwarded repo/content URLs.
# 2. host <forwarded-domain> matches those forwarded URLs, so catalog:register (which
# looks up the integration by the repoContentsUrl host) and the URL reader resolve.
integrations:
gitea:
- host: localhost:30110
baseUrl: https://${CODESPACE_NAME}-30110.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}
- host: localhost:30112
baseUrl: https://${CODESPACE_NAME}-30112.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}
username: admin
password: a-super-secure-password
- host: ${CODESPACE_NAME}-30110.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}
baseUrl: https://${CODESPACE_NAME}-30110.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}
- host: ${CODESPACE_NAME}-30112.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}
baseUrl: https://${CODESPACE_NAME}-30112.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}
username: admin
password: a-super-secure-password
4 changes: 2 additions & 2 deletions adventures/dead-reckoning/beginner/backstage/app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ integrations:
# baseUrl must be set explicitly: without it the integration defaults to
# https://<host>, but Gitea serves plain HTTP here (ERR_SSL_WRONG_VERSION_NUMBER).
gitea:
- host: localhost:30110
baseUrl: http://localhost:30110
- host: localhost:30112
baseUrl: http://localhost:30112
username: admin
password: a-super-secure-password

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ spec:
ui:field: RepoUrlPicker
ui:options:
allowedHosts:
- localhost:30110
- localhost:30112
allowedOwners:
- fleet

Expand Down
4 changes: 2 additions & 2 deletions adventures/dead-reckoning/beginner/verify.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ TEMPLATE_FILE="$SCRIPT_DIR/backstage/templates/vessel-commissioning/template.yam
BACKSTAGE_URL="http://localhost:7007"
TEMPLATE_REF="template:default/vessel-commissioning"

GITEA_URL="http://localhost:30110"
GITEA_URL="http://localhost:30112"
GITEA_AUTH="admin:a-super-secure-password"
GITEA_ORG="fleet"

# A unique, disposable vessel so repeated runs never collide, and it is obvious
# in Gitea/the catalog that it came from verification.
VESSEL_NAME="hms-verify-$(date +%s)"
REPO_URL="localhost:30110?owner=${GITEA_ORG}&repo=${VESSEL_NAME}"
REPO_URL="localhost:30112?owner=${GITEA_ORG}&repo=${VESSEL_NAME}"

print_header \
'Dead Reckoning' \
Expand Down
8 changes: 6 additions & 2 deletions adventures/dead-reckoning/docs/beginner.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ community_url: "" # TODO: add community thread URL once the adventure is live

summary: "Fix a broken Backstage software template so the commission office can register new vessels for service."

topics:
- Backstage
- Gitea

audience: >-
Platform engineers, developers, and anyone curious about internal developer platforms and self-service
scaffolding. No prior Backstage experience is needed, but familiarity with YAML and basic Git concepts will help.
Expand Down Expand Up @@ -56,7 +60,7 @@ architecture:
misconfigured, the whole commission fails.
- >-
All infrastructure is pre-provisioned, with nothing to install. **Gitea** (the archives) runs in a Kubernetes
cluster on port 30110; **Backstage** (the commission office) runs alongside as a standalone instance on port 3000,
cluster on port 30112; **Backstage** (the commission office) runs alongside as a standalone instance on port 3000,
already wired to Gitea.
- >-
Good news: you can trust the platform. The Backstage app and its configuration, Gitea, and the cluster are all
Expand Down Expand Up @@ -97,7 +101,7 @@ how_to_play:
templates, letting you browse the scaffolder's available actions and edit a template with a
live preview and a safe dry-run. Poke around and see what each one does.

Open **Gitea** on port 30110 (the archives) and the Backstage **catalog** to see what does,
Open **Gitea** on port 30112 (the archives) and the Backstage **catalog** to see what does,
and doesn't, make it through when you run the template.
- id: fix
title: "Repair the Template"
Expand Down
Loading
Loading