Skip to content
Merged
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
21 changes: 21 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# The Demo Codespace image: the system packages ABOM needs, and the user the Demo runs as. Versioned
# tools are installed by on-create.sh, at the versions the checkout names.
# Pinned as tag@digest so Dependabot can follow the tag. Codespaces' Dev Containers CLI 0.83.3 logs
# "Could not parse image name" for that form, then pulls and reads the image anyway.
FROM mcr.microsoft.com/devcontainers/base:ubuntu-24.04@sha256:d94c97dd9cacf183d0a6fd12a8e87b526e9e928307674ae9c94139139c0c6eae

RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
graphviz iptables sqlite3 unzip \
&& rm -rf /var/lib/apt/lists/*

# Demo runs as its own user, so the firewall can confine it to loopback without confining the
# editor. A Codespace runs this container on its VM's network, where that rule matches the user ID
# across the VM, so the ID is one outside the range the VM's own services take. The editor's user
# joins its group to read and edit the practice query pack, and may run commands as it: the base
# image lets vscode run commands as root alone.
RUN useradd --uid 4999 --user-group --create-home --shell /usr/sbin/nologin abom-demo \
&& usermod -aG abom-demo vscode \
&& echo 'vscode ALL=(abom-demo) NOPASSWD: ALL' >/etc/sudoers.d/abom-demo \
&& chmod 0440 /etc/sudoers.d/abom-demo \
&& visudo --check --quiet
102 changes: 102 additions & 0 deletions .devcontainer/copilot-login.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#!/usr/bin/env bash
# Give the Demo's Copilot authoring sessions your own GitHub sign-in.
#
# copilot-login.sh sign in with gh, through a one-time code in your browser, unless
# already signed in, and give that sign-in to the Demo
# copilot-login.sh --token read a fine-grained token with the Copilot Requests permission
# copilot-login.sh --if-signed-in give the Demo an existing sign-in, never asking (each start runs this)
#
# The Codespace's own GITHUB_TOKEN is never used: it belongs to this repository, not to you. The
# credential is kept in the Demo's private directory, readable only by the Demo's user, and handed to
# no process but the Copilot CLI.
set -euo pipefail
root=${ABOM_DEMO_ROOT:-/tmp/abom-demo}

die() {
printf 'copilot-login: %s\n' "$*" >&2
exit 1
}

# gh without any exported token, so it answers with the sign-in `gh auth login` stored.
no_tokens=(env -u GITHUB_TOKEN -u GH_TOKEN -u GITHUB_ENTERPRISE_TOKEN -u GH_ENTERPRISE_TOKEN)
user_gh() { "${no_tokens[@]}" "$@"; }

# sign_in signs gh in with a one-time code, and opens GitHub's page for it itself: gh opens a browser
# only where it may also prompt, and its prompts offer to replace the Codespace's own Git credentials.
# A Codespace's terminal names VS Code's $BROWSER, which opens the page in the browser running it.
# It is a subshell, so gh and what gh wrote are gone when it returns, however it ends.
sign_in() (
gh_pid=""
said=$(mktemp "${TMPDIR:-/tmp}/copilot-login.XXXXXX")
trap '[ -z "$gh_pid" ] || kill "$gh_pid" 2>/dev/null || :; rm -f "$said"' EXIT
# macOS's bash otherwise waits through Ctrl-C for a background command, with only EXIT trapped.
trap 'exit 130' INT
# Started directly, not through user_gh: a function in the background is a shell of its own, and
# stopping that shell leaves gh waiting for its code.
"${no_tokens[@]}" GH_PROMPT_DISABLED=1 gh auth login --hostname github.com --web --clipboard=false \
--git-protocol https --skip-ssh-key </dev/null >"$said" 2>&1 &
gh_pid=$!
# Read after asking whether gh is running, so what an exited gh wrote is read once more.
code="" page=""
while :; do
running=0
! kill -0 "$gh_pid" 2>/dev/null || running=1
code=$(grep -i 'code' "$said" | grep -oE '[A-Z0-9]{4}-[A-Z0-9]{4}' | head -n 1) || :
page=$(grep -oE 'https://[^[:space:]]+/login/device' "$said" | head -n 1) || :
[ -n "$code" ] && [ -n "$page" ] && break
[ "$running" = 1 ] || break
sleep 0.2
done
if [ -n "$code" ] && [ -n "$page" ]; then
printf 'Sign in to GitHub with this one-time code:\n\n %s\n\n' "$code"
if [ -n "${BROWSER:-}" ]; then
read -rp "Press Enter to open $page in your browser, then enter the code there. " _ || :
"$BROWSER" "$page" >/dev/null 2>&1 || echo "Open $page in your browser, and enter the code there."
else
echo "Open $page in your browser, and enter the code there."
fi
echo "GitHub then asks you to authorize GitHub CLI, whose sign-in the Demo uses. Waiting for you..."
fi
status=0
wait "$gh_pid" || status=$?
# Waited for, so its ID may already name another process.
gh_pid=""
if [ "$status" != 0 ]; then
cat "$said" >&2
die "you were not signed in, so nothing was saved"
fi
who=$(sed -n 's/.*Logged in as \([^[:space:]]*\).*/\1/p' "$said" | head -n 1)
[ -z "$who" ] || echo "copilot-login: signed in to GitHub as $who"
)

[ -f "$root/demo.json" ] || die "No Demo is running in this Codespace; start one with .devcontainer/start-demo.sh"

case "${1:-}" in
--token)
if [ -t 0 ]; then
echo "Paste a fine-grained personal access token with the Copilot Requests permission."
read -rsp "Token: " token
echo
else
read -r token || true
fi
;;
--if-signed-in)
token=$(user_gh gh auth token --hostname github.com 2>/dev/null) || exit 0
;;
"")
if ! token=$(user_gh gh auth token --hostname github.com 2>/dev/null); then
sign_in
token=$(user_gh gh auth token --hostname github.com)
fi
;;
*) die "usage: copilot-login.sh [--token | --if-signed-in]" ;;
esac

token=$(printf '%s' "${token:-}" | tr -d '[:space:]')
case "$token" in
"") die "no credential was entered, so none was saved" ;;
ghp_*) die "Copilot does not accept classic personal access tokens (ghp_); sign in with gh, or use a fine-grained token" ;;
esac
printf '%s\n' "$token" | sudo -u abom-demo sh -c 'umask 077; cat >"$1"' sh "$root/secrets/copilot-token"
echo "copilot-login: saved. The next authoring session uses it, and each start of this Codespace gives it to the new Demo."
21 changes: 0 additions & 21 deletions .devcontainer/demo/Dockerfile

This file was deleted.

19 changes: 0 additions & 19 deletions .devcontainer/demo/copilot-login.sh

This file was deleted.

18 changes: 0 additions & 18 deletions .devcontainer/demo/on-create.sh

This file was deleted.

5 changes: 0 additions & 5 deletions .devcontainer/demo/post-start.sh

This file was deleted.

96 changes: 0 additions & 96 deletions .devcontainer/demo/start-demo.sh

This file was deleted.

22 changes: 0 additions & 22 deletions .devcontainer/demo/update-content.sh

This file was deleted.

24 changes: 24 additions & 0 deletions .devcontainer/devcontainer-lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"features": {
"ghcr.io/devcontainers/features/dotnet:2": {
"version": "2.5.0",
"resolved": "ghcr.io/devcontainers/features/dotnet@sha256:0fc16547ed4db6d7ff2a9f5981d2b93eb314e568affb9958029ad794f1f9a093",
"integrity": "sha256:0fc16547ed4db6d7ff2a9f5981d2b93eb314e568affb9958029ad794f1f9a093"
},
"ghcr.io/devcontainers/features/github-cli:1": {
"version": "1.1.3",
"resolved": "ghcr.io/devcontainers/features/github-cli@sha256:bd7ab48a832228f633239277552c30b353867fef2e5b037e064b4e64f0b843f2",
"integrity": "sha256:bd7ab48a832228f633239277552c30b353867fef2e5b037e064b4e64f0b843f2"
},
"ghcr.io/devcontainers/features/go:1": {
"version": "1.4.0",
"resolved": "ghcr.io/devcontainers/features/go@sha256:3b2c7acfdb24de88292283d10eb7517ccc45f60ae22a97030f1cf4fbd03634f3",
"integrity": "sha256:3b2c7acfdb24de88292283d10eb7517ccc45f60ae22a97030f1cf4fbd03634f3"
},
"ghcr.io/devcontainers/features/node:1": {
"version": "1.7.1",
"resolved": "ghcr.io/devcontainers/features/node@sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6",
"integrity": "sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@
"name": "ABOM Demo: fictional repositories on a simulated GitHub",
"build": { "dockerfile": "Dockerfile" },
"features": {
"ghcr.io/devcontainers/features/go:1": { "version": "1.26" },
"ghcr.io/devcontainers/features/go:1": { "version": "1.26", "installGoTools": false },
"ghcr.io/devcontainers/features/dotnet:2": { "version": "10.0" },
"ghcr.io/devcontainers/features/node:1": { "version": "24.21.0" }
"ghcr.io/devcontainers/features/node:1": { "version": "24.21.0", "pnpmVersion": "none" },
"ghcr.io/devcontainers/features/github-cli:1": {}
},
"capAdd": ["NET_ADMIN"],
"remoteUser": "vscode",
"onCreateCommand": "bash .devcontainer/demo/on-create.sh",
"updateContentCommand": "bash .devcontainer/demo/update-content.sh",
"postStartCommand": "bash .devcontainer/demo/post-start.sh",
"onCreateCommand": "bash .devcontainer/on-create.sh",
"updateContentCommand": "bash .devcontainer/update-content.sh",
"postStartCommand": "bash .devcontainer/post-start.sh",
"postAttachCommand": "bash .devcontainer/status.sh",
"forwardPorts": [8099],
"portsAttributes": {
"8099": { "label": "ABOM Demo", "onAutoForward": "openBrowserOnce" }
Expand Down
15 changes: 15 additions & 0 deletions .devcontainer/lib.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# shellcheck shell=bash
# Sourced by the lifecycle scripts that reach the network while a Codespace is created.

# retry runs a step that reaches the network, up to four times. In a Codespace a lifecycle command
# that fails leaves a recovery container holding none of these tools, so one dropped connection
# would cost the whole Codespace.
retry() {
local attempt
for attempt in 1 2 3 4; do
"$@" && return 0
printf '%s: attempt %s of 4 failed: %s\n' "$(basename "$0" .sh)" "$attempt" "$*" >&2
[ "$attempt" = 4 ] || sleep $((attempt * ${ABOM_RETRY_DELAY:-10}))
done
return 1
}
Loading
Loading