Skip to content

Commit cedc565

Browse files
authored
Merge pull request #140 from blooop/fix/ghcr-package-visibility-docs
Document GHCR package visibility and how to make public
2 parents a13dbe1 + 37688f0 commit cedc565

File tree

4 files changed

+176
-16
lines changed

4 files changed

+176
-16
lines changed

.devcontainer/devcontainer.json

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,20 @@
11
{
22
"name": "python_template",
33

4-
// Prebuilt image from GHCR (built by .github/workflows/devcontainer.yml)
5-
// Features (including claude-code) are baked into this image.
6-
"image": "ghcr.io/blooop/python_template/devcontainer:latest",
4+
"build": {
5+
"dockerfile": "Dockerfile",
6+
"context": ".."
7+
},
8+
"features": {
9+
"./claude-code": {},
10+
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
11+
"ghcr.io/devcontainers/features/common-utils:2": {}
12+
},
713

8-
// To build locally instead of using the prebuilt image, comment out "image" above
9-
// and uncomment the "build" and "features" blocks below:
10-
// "build": {
11-
// "dockerfile": "Dockerfile",
12-
// "context": ".."
13-
// },
14-
// "features": {
15-
// "./claude-code": {}
16-
// "ghcr.io/devcontainers/features/docker-in-docker:2": {}
17-
// "ghcr.io/devcontainers/features/common-utils:2": {},
18-
// "ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {},
19-
// "ghcr.io/jsburckhardt/devcontainer-features/codex:latest": {}
20-
// },
14+
// To use a prebuilt image instead of building locally (faster startup),
15+
// comment out "build" and "features" above, uncomment "image" below,
16+
// and ensure the GHCR package is public (see README).
17+
// "image": "ghcr.io/blooop/python_template/devcontainer:latest",
2118

2219
"initializeCommand": ".devcontainer/claude-code/init-host.sh",
2320
"customizations": {

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,50 @@ source .claude/activate.sh
5757

5858
See [.claude/README.md](.claude/README.md) for detailed information about the Claude Code configuration.
5959

60+
# Devcontainer
61+
62+
By default, the devcontainer builds locally from `.devcontainer/Dockerfile`. This works out of the box for all users, including forks and projects created from this template.
63+
64+
## Switching to a prebuilt image (optional)
65+
66+
A CI workflow (`.github/workflows/devcontainer.yml`) automatically builds and pushes a devcontainer image to GHCR whenever `.devcontainer/` files change on `main`. The image is published as `ghcr.io/<owner>/<repo>/devcontainer:latest`, where `<owner>` is your GitHub username or organization and `<repo>` is the repository name (e.g. `ghcr.io/myuser/myproject/devcontainer:latest`).
67+
68+
You can migrate automatically with:
69+
70+
```bash
71+
pixi run dev-use-prebuilt
72+
```
73+
74+
Or manually:
75+
76+
1. Edit `.devcontainer/devcontainer.json`: comment out the `"build"` and `"features"` blocks, uncomment the `"image"` line
77+
2. Update the image reference to match your repo: `ghcr.io/<owner>/<repo>/devcontainer:latest`
78+
3. Push to `main` and wait for the CI workflow to complete. For new repos or forks where the workflow hasn't run yet, you can trigger it manually from the Actions tab or via `gh workflow run devcontainer.yml`
79+
4. **Make the GHCR package public** (see below) — GHCR packages are private by default and will fail with `MANIFEST_UNKNOWN` otherwise
80+
81+
### Making the GHCR package public
82+
83+
**Option 1: GitHub Web UI**
84+
85+
1. Go to your repository's package settings:
86+
- Personal repos: `https://github.com/users/<username>/packages/container/<repo>%2Fdevcontainer/settings`
87+
- Organization repos: `https://github.com/orgs/<org>/packages/container/<repo>%2Fdevcontainer/settings`
88+
2. Under "Danger Zone", click **Change visibility**
89+
3. Select **Public** and confirm
90+
91+
**Option 2: GitHub CLI**
92+
93+
```bash
94+
# Ensure your token has the write:packages scope
95+
gh auth refresh -s write:packages
96+
97+
# For personal repos:
98+
gh api --method PATCH /user/packages/container/<repo>%2Fdevcontainer -f visibility=public
99+
100+
# For organization repos:
101+
gh api --method PATCH /orgs/<org>/packages/container/<repo>%2Fdevcontainer -f visibility=public
102+
```
103+
60104
# Github setup
61105

62106
There are github workflows for CI, codecov and automated pypi publishing in `ci.yml` and `publish.yml`.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ ci-push = { depends-on = ["format", "ruff-lint", "update-lock", "ci", "push"] }
9999
clear-pixi = "rm -rf .pixi pixi.lock"
100100
setup-git-merge-driver = "git config merge.ourslock.driver true"
101101
update-from-template-repo = "./scripts/update_from_template.sh"
102+
dev-use-prebuilt = "./scripts/devcontainer_use_prebuilt.sh"
102103

103104
[tool.pylint]
104105
extension-pkg-whitelist = ["numpy"]
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
# Migrate devcontainer.json from local builds to a prebuilt GHCR image.
5+
# Also attempts to make the GHCR package public.
6+
7+
DEVCONTAINER_JSON=".devcontainer/devcontainer.json"
8+
9+
# --- Check prerequisites ---
10+
if ! command -v gh >/dev/null 2>&1; then
11+
echo "WARNING: GitHub CLI (gh) not found. Will skip making the package public." >&2
12+
GH_AVAILABLE=false
13+
else
14+
GH_AVAILABLE=true
15+
fi
16+
17+
# --- Resolve GitHub owner/repo from git remote ---
18+
REMOTE_URL=$(git remote get-url origin 2>/dev/null) || {
19+
echo "ERROR: No git remote 'origin' found." >&2
20+
exit 1
21+
}
22+
23+
# Handle common GitHub remote URL formats
24+
REPO=$(echo "$REMOTE_URL" | sed -E 's#(ssh://git@github\.com/|git@github\.com:|https?://github\.com/|git://github\.com/)##; s/\.git$//')
25+
26+
if [[ -z "$REPO" || "$REPO" != */* ]]; then
27+
echo "ERROR: Could not parse owner/repo from remote URL: $REMOTE_URL" >&2
28+
exit 1
29+
fi
30+
31+
IMAGE="ghcr.io/${REPO}/devcontainer:latest"
32+
echo "Repository: $REPO"
33+
echo "Image: $IMAGE"
34+
35+
# --- Check current state: look for an uncommented "image": line ---
36+
if grep -q '^[[:space:]]*"image"[[:space:]]*:' "$DEVCONTAINER_JSON"; then
37+
echo "Already using a prebuilt image. Nothing to do."
38+
exit 0
39+
fi
40+
41+
# --- Replace the file using awk for reliable block manipulation ---
42+
awk -v image="$IMAGE" '
43+
# Comment out uncommented "build" block (4-space indent open/close).
44+
# NOTE: assumes no nested {} within these blocks.
45+
/^ "build": \{/ { in_build=1 }
46+
in_build {
47+
sub(/^ /, " // ")
48+
if (/^ \/\/ \}/) in_build=0
49+
print; next
50+
}
51+
52+
# Comment out uncommented "features" block (4-space indent open/close).
53+
# NOTE: assumes no nested {} within these blocks.
54+
/^ "features": \{/ { in_features=1 }
55+
in_features {
56+
sub(/^ /, " // ")
57+
if (/^ \/\/ \}/) in_features=0
58+
print; next
59+
}
60+
61+
# Uncomment the image line and set the correct reference
62+
/^ \/\/ "image":/ {
63+
printf " \"image\": \"%s\",\n", image
64+
next
65+
}
66+
67+
{ print }
68+
' "$DEVCONTAINER_JSON" > "${DEVCONTAINER_JSON}.tmp" && mv "${DEVCONTAINER_JSON}.tmp" "$DEVCONTAINER_JSON"
69+
70+
echo ""
71+
echo "Updated $DEVCONTAINER_JSON to use prebuilt image."
72+
73+
# --- Try to make the GHCR package public ---
74+
echo ""
75+
echo "Attempting to make GHCR package public..."
76+
77+
OWNER=$(echo "$REPO" | cut -d'/' -f1)
78+
PACKAGE_NAME=$(echo "$REPO" | cut -d'/' -f2)
79+
ENCODED_PACKAGE="${PACKAGE_NAME}%2Fdevcontainer"
80+
81+
if ! $GH_AVAILABLE; then
82+
echo "Skipping: gh CLI not available."
83+
echo ""
84+
echo "To make the package public, install gh and run:"
85+
echo " gh auth refresh -s write:packages"
86+
echo " gh api --method PATCH /user/packages/container/${ENCODED_PACKAGE} -f visibility=public"
87+
exit 0
88+
fi
89+
90+
# Determine if owner is an org or a user
91+
IS_ORG=false
92+
if gh api "/orgs/${OWNER}" >/dev/null 2>&1; then
93+
IS_ORG=true
94+
fi
95+
96+
if $IS_ORG; then
97+
API_PATH="/orgs/${OWNER}/packages/container/${ENCODED_PACKAGE}"
98+
SETTINGS_URL="https://github.com/orgs/${OWNER}/packages/container/${ENCODED_PACKAGE}/settings"
99+
else
100+
API_PATH="/user/packages/container/${ENCODED_PACKAGE}"
101+
SETTINGS_URL="https://github.com/users/${OWNER}/packages/container/${ENCODED_PACKAGE}/settings"
102+
fi
103+
104+
if API_OUTPUT=$(gh api --method PATCH "$API_PATH" -f visibility=public 2>&1); then
105+
echo "GHCR package is now public."
106+
else
107+
echo "Could not set package visibility automatically."
108+
echo "API response: $API_OUTPUT"
109+
echo ""
110+
echo "To make the package public manually, either:"
111+
echo ""
112+
echo " 1. Visit: $SETTINGS_URL"
113+
echo " -> Danger Zone -> Change visibility -> Public"
114+
echo ""
115+
echo " 2. Run:"
116+
echo " gh auth refresh -s write:packages"
117+
echo " gh api --method PATCH $API_PATH -f visibility=public"
118+
fi

0 commit comments

Comments
 (0)