diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a5f0386..0ffa663 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -34,5 +34,6 @@ ], // Mount the parent as /workspaces so we can pip install peers as editable "workspaceMount": "source=${localWorkspaceFolder}/..,target=/workspaces,type=bind", - "initializeCommand": "mkdir -p ${localEnv:HOME}/.config/devcontainer_rc" + "initializeCommand": "mkdir -p ${localEnv:HOME}/.config/devcontainer_rc", + "remoteUser": "root" } \ No newline at end of file diff --git a/.devcontainer/features/bash-config/README.md b/.devcontainer/features/bash-config/README.md index e164739..52a8168 100644 --- a/.devcontainer/features/bash-config/README.md +++ b/.devcontainer/features/bash-config/README.md @@ -2,25 +2,14 @@ ## Defaults -When this feature is first called, onCreateCommand.sh is created and called in `devcontainer-feature`. -This creates and installs a default bash terminal configuration from `feature_settings_rc` which contains opinionated default BASH terminal settings such as: -* A minimalist, blue bash prompt. -* Eternal, shared history between containers using this feature. - -## Customisation - -The user can specify their own terminal preferences in `$CONFIG_STAGING/bashrc`, which defaults to `/devcontainer_staging/bashrc`. -Then the container is rebuilt, if `bashrc` already exists it won't be overwritten, preserving changes. If the container has been built once already, the existing bashrc must be deleted in order for your changes to be applied. +The default, opinionated configuration is defined in +`$HOME/.config/bash-config/bash-config-rc` and includes the following: +- Persistent history across all devcontainers that use this feature +- The MS devcontainer bash prompt with git branch and status +- history search with up/down arrows (.inputrc) +- ctrl-left or right arrow for word navigation (.inputrc) +## Customisation -creates staging folder in container -creates onCreateCommand.sh in the staging folder -inputrc and bashrc are also created in the staging folder, containing incremental history searching and user customisations respectively -feature_settings_rc is created in the staging fodler, which contains a PS1 setup and opinionated terminal features -"onCreateCommand" is then exectuted inside the container during finalisation of container setup. - - This copies the contents of feature_settings_rc into devcontainer_rc - - then copies bashrc and inputrc into the container, unless they exist already to prevent overwriting user edits - - then adds root permissions. -A symbolic link to /root/ is created The items in the CONFIG_FOLDER -our bashrc is called by /root/.bashrc \ No newline at end of file +Can be customized by editing `$HOME/.config/bash-config/bashrc` \ No newline at end of file diff --git a/.devcontainer/features/bash-config/bashrc b/.devcontainer/features/bash-config/bashrc new file mode 100644 index 0000000..b79cb02 --- /dev/null +++ b/.devcontainer/features/bash-config/bashrc @@ -0,0 +1,12 @@ +#!/bin/bash + +# This bashrc will be source from /root/.bashrc upon launch of every terminal +# in a devcontainer that uses the bash-config feature from +# https://github.com/DiamondLightSource/devcontainer-features + +# This file is initialized by bash-config, but you are then free to edit it + +# execute default, opinionated settings - delete this line to remove defaults +source $CONFIG_FOLDER/feature_settings_rc + +# add your personal custom settings below diff --git a/.devcontainer/features/bash-config/devcontainer-feature.json b/.devcontainer/features/bash-config/devcontainer-feature.json index 4cc68e6..aba0978 100644 --- a/.devcontainer/features/bash-config/devcontainer-feature.json +++ b/.devcontainer/features/bash-config/devcontainer-feature.json @@ -2,7 +2,8 @@ "name": "Lightweight and configurable BASH eternal history", //ID *must* match parent folder name *exactly* "id": "bash-config", - "version": "1.0.0", + // a default beta version that is replaced by CI when doing tagged releases + "version": "0.0.1-b1", "description": "Lightweight BASH setup with eternal history and PS1 tweaks. Fully configurable.", "documentationURL": "https://raw.githubusercontent.com/DiamondRC/devcontainer-features/refs/heads/main/.devcontainer/features/bash-config/README.md", "containerEnv": { @@ -17,4 +18,4 @@ } ], "onCreateCommand": "bash /devcontainer_staging/onCreateCommand.sh" -} +} \ No newline at end of file diff --git a/.devcontainer/features/bash-config/feature_settings_rc b/.devcontainer/features/bash-config/feature_settings_rc new file mode 100644 index 0000000..afecc2e --- /dev/null +++ b/.devcontainer/features/bash-config/feature_settings_rc @@ -0,0 +1,14 @@ +#!/bin/bash + +# default opinioned bash configuration + +# set the prompt +export PS1="\[\033[1;34m\]\W \[\033[0m\]# " + +# enable enternal shared history +export HISTCONTROL=ignoreboth:erasedups +export HISTSIZE=-1 +export HISTFILESIZE=-1 +export SAVEHIST=-1 +export HISTFILE=$CONFIG_FOLDER/.bash_eternal_history +PROMPT_COMMAND="history -a; $PROMPT_COMMAND" \ No newline at end of file diff --git a/.devcontainer/features/bash-config/inputrc b/.devcontainer/features/bash-config/inputrc new file mode 100644 index 0000000..e44e17b --- /dev/null +++ b/.devcontainer/features/bash-config/inputrc @@ -0,0 +1,10 @@ +# Readline configuration for bash shell. + +# Incremental history searching with up and down arrows (C-P and C-N for old +# style navigation). +"\e[A": history-search-backward +"\e[B": history-search-forward + +# Control left and right for word movement +"\e[5C": forward-word +"\e[5D": backward-word \ No newline at end of file diff --git a/.devcontainer/features/bash-config/install.sh b/.devcontainer/features/bash-config/install.sh index 95db35a..b01548b 100644 --- a/.devcontainer/features/bash-config/install.sh +++ b/.devcontainer/features/bash-config/install.sh @@ -1,71 +1,25 @@ #!/bin/bash -set -e - -echo "Activating feature 'terminal-history'" -echo "User: ${_REMOTE_USER} User home: ${_REMOTE_USER_HOME}" - -mkdir -p $CONFIG_STAGING - -# ------------------------------------------------------------------------------- -cat > $CONFIG_STAGING/onCreateCommand.sh \ -<< EOF -#!/bin/bash - -# copy in the opinionated default settings from the feature -cp $CONFIG_STAGING/feature_settings_rc $CONFIG_FOLDER/feature_settings_rc - -# copy in the user editable settings unless they already exist -if [[ ! -f $CONFIG_FOLDER/bashrc ]] ; then - cp $CONFIG_STAGING/bashrc $CONFIG_FOLDER - cp $CONFIG_STAGING/inputrc $CONFIG_FOLDER -fi -# hook in the config to the root account -ln -s $CONFIG_FOLDER/inputrc /root/.inputrc -echo "source $CONFIG_FOLDER/bashrc" >> /root/.bashrc -EOF - -# ------------------------------------------------------------------------------- -cat > $CONFIG_STAGING/inputrc \ -<< EOF -# Readline configuration for bash shell. - -# Incremental history searching with up and down arrows (C-P and C-N for old -# style navigation). -"\e[A": history-search-backward -"\e[B": history-search-forward - -# Control left and right for word movement -"\e[5C": forward-word -"\e[5D": backward-word -EOF - -# ------------------------------------------------------------------------------- -cat > $CONFIG_STAGING/bashrc \ -<< EOF -#!/bin/bash +# abort on error +set -e -# execute default opinionated settings - delete this line to remove defaults -source $CONFIG_FOLDER/feature_settings_rc +# discover where the install.sh and peers have been extracted to +this_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# add your personal custom settings below -EOF -# ------------------------------------------------------------------------------- -cat > $CONFIG_STAGING/feature_settings_rc \ -<< EOF -#!/bin/bash +echo "Activating feature 'terminal-history'" +echo "User: ${_REMOTE_USER} User home: ${_REMOTE_USER_HOME}" -# default opinioned bash configuration +# This script is run at container build time. +# +# Here we place all the configuration files in a staging area. +# +# Then at container runtime, when the hosts' config folder is mounted in, we +# can copy from the staging area to the mounted host folder +# +# The runtime copy to host folder is performed once oby onCreateCommand.sh -# set the prompt -export PS1="\[\033[1;34m\]\W \[\033[0m\]# " +mkdir -p "$CONFIG_STAGING" +cp -r "$this_dir"/* "$CONFIG_STAGING" +echo $this_dir > "$CONFIG_STAGING/this_dir" -# enable enternal shared history -export HISTCONTROL=ignoreboth:erasedups -export HISTSIZE=-1 -export HISTFILESIZE=-1 -export SAVEHIST=-1 -export HISTFILE=$CONFIG_FOLDER/.bash_eternal_history -PROMPT_COMMAND="history -a; $PROMPT_COMMAND" -EOF diff --git a/.devcontainer/features/bash-config/onCreateCommand.sh b/.devcontainer/features/bash-config/onCreateCommand.sh new file mode 100644 index 0000000..6b3ead4 --- /dev/null +++ b/.devcontainer/features/bash-config/onCreateCommand.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# this script is run once inside the developer container at creation time + +# copy in the opinionated default settings from the feature +cp $CONFIG_STAGING/feature_settings_rc $CONFIG_FOLDER/feature_settings_rc + +# copy in the user editable settings unless they already exist +if [[ ! -f $CONFIG_FOLDER/bashrc ]] ; then + cp $CONFIG_STAGING/bashrc $CONFIG_FOLDER + cp $CONFIG_STAGING/inputrc $CONFIG_FOLDER +fi + +# hook in the config to the root account +ln -s $CONFIG_FOLDER/inputrc /root/.inputrc +echo "source $CONFIG_FOLDER/bashrc" >> /root/.bashrc \ No newline at end of file diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 8f7c58b..95b27e0 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,10 +1,12 @@ name: "Release dev container features & Generate Documentation" on: workflow_dispatch: + push: + branches: + - main jobs: deploy: - if: ${{ github.ref == 'refs/heads/main' }} runs-on: ubuntu-latest permissions: contents: write @@ -13,13 +15,23 @@ jobs: steps: - uses: actions/checkout@v4 + - name: patch in version tag + # patch the version tag if the version is a valid semver. If not the + # default to the + run: | + if [[ ${{ github.ref_name }} =~ ^[0-9]+\.[0-9]+.+ ]] ; then + sed -i s/0\.0\.0-b1/${{ github.ref_name }}/ $(find .devcontainer -name devcontainer-feature.json) + # dump the modified json for debugging + cat $(find .devcontainer -name devcontainer-feature.json) + fi + - name: "Publish Features" uses: devcontainers/action@v1 with: publish-features: "true" base-path-to-features: "./src" generate-docs: "true" - + env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 80017c1..d988533 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,10 +1,7 @@ name: "CI - Test Features" on: push: - branches: - - main pull_request: - workflow_dispatch: jobs: test-autogenerated: @@ -13,7 +10,7 @@ jobs: strategy: matrix: features: - - terminal-history + - bash-config baseImage: - debian:latest - ubuntu:latest @@ -24,35 +21,7 @@ jobs: run: | npm install -g @devcontainers/cli mkdir -p ~/.config/devcontainer_rc - + - name: "Generating tests for '${{ matrix.features }}' against '${{ matrix.baseImage }}'" run: devcontainer features test --skip-scenarios -f ${{ matrix.features }} -i ${{ matrix.baseImage }} . -# test-scenarios: -# runs-on: ubuntu-latest -# continue-on-error: true -# strategy: -# matrix: -# features: -# - color -# - hello -# steps: -# - uses: actions/checkout@v4 -# -# - name: "Install latest devcontainer CLI" -# run: npm install -g @devcontainers/cli -# -# - name: "Generating tests for '${{ matrix.features }}' scenarios" -# run: devcontainer features test -f ${{ matrix.features }} --skip-autogenerated --skip-duplicated . -# -# test-global: -# runs-on: ubuntu-latest -# continue-on-error: true -# steps: -# - uses: actions/checkout@v4 -# -# - name: "Install latest devcontainer CLI" -# run: npm install -g @devcontainers/cli -# -# - name: "Testing global scenarios" -# run: devcontainer features test --global-scenarios-only . diff --git a/README.md b/README.md index 1a1cea8..8a0a097 100644 --- a/README.md +++ b/README.md @@ -1,188 +1,28 @@ -# Dev Container Features: Self Authoring Template +# Dev Container Features -> This repo provides a starting point and example for creating your own custom [dev container Features](https://containers.dev/implementors/features/), hosted for free on GitHub Container Registry. The example in this repository follows the [dev container Feature distribution specification](https://containers.dev/implementors/features-distribution/). -> -> To provide feedback to the specification, please leave a comment [on spec issue #70](https://github.com/devcontainers/spec/issues/70). For more broad feedback regarding dev container Features, please see [spec issue #61](https://github.com/devcontainers/spec/issues/61). +Some features for use in Python Copier Template development containers. Also intended to be generally useful in any developer container. -## Example Contents +## bash-config -This repository contains a _collection_ of two Features - `hello` and `color`. These Features serve as simple feature implementations. Each sub-section below shows a sample `devcontainer.json` alongside example usage of the Feature. +Adds customizations to the bash shell that can be shared across all developer containers using the feature. -### `hello` +Default setup attempts to be a lightweight replacement for the common-utils feature zsh capabilities, namely: -Running `hello` inside the built container will print the greeting provided to it via its `greeting` option. +- useful prompt +- eternal history (shared across all containers using the feature) +- history search with up/down arrows (.inputrc) +- ctrl-left or right arrow for word navigation (.inputrc) -```jsonc -{ - "image": "mcr.microsoft.com/devcontainers/base:ubuntu", - "features": { - "ghcr.io/devcontainers/feature-starter/hello:1": { - "greeting": "Hello" - } - } -} -``` +## other -```bash -$ hello +Other features may be added here in future. -Hello, user. -``` +## Testing -### `color` +This repo has it's own devcontainer which can be used to do local testing of the features. -Running `color` inside the built container will print your favorite color to standard out. +## Publishing Features -```jsonc -{ - "image": "mcr.microsoft.com/devcontainers/base:ubuntu", - "features": { - "ghcr.io/devcontainers/feature-starter/color:1": { - "favorite": "green" - } - } -} -``` +CI publishes the features defined here as follows:- -```bash -$ color - -my favorite color is green -``` - -## Repo and Feature Structure - -Similar to the [`devcontainers/features`](https://github.com/devcontainers/features) repo, this repository has a `src` folder. Each Feature has its own sub-folder, containing at least a `devcontainer-feature.json` and an entrypoint script `install.sh`. - -``` -├── src -│ ├── hello -│ │ ├── devcontainer-feature.json -│ │ └── install.sh -│ ├── color -│ │ ├── devcontainer-feature.json -│ │ └── install.sh -| ├── ... -│ │ ├── devcontainer-feature.json -│ │ └── install.sh -... -``` - -An [implementing tool](https://containers.dev/supporting#tools) will composite [the documented dev container properties](https://containers.dev/implementors/features/#devcontainer-feature-json-properties) from the feature's `devcontainer-feature.json` file, and execute in the `install.sh` entrypoint script in the container during build time. Implementing tools are also free to process attributes under the `customizations` property as desired. - -### Options - -All available options for a Feature should be declared in the `devcontainer-feature.json`. The syntax for the `options` property can be found in the [devcontainer Feature json properties reference](https://containers.dev/implementors/features/#devcontainer-feature-json-properties). - -For example, the `color` feature provides an enum of three possible options (`red`, `gold`, `green`). If no option is provided in a user's `devcontainer.json`, the value is set to "red". - -```jsonc -{ - // ... - "options": { - "favorite": { - "type": "string", - "enum": [ - "red", - "gold", - "green" - ], - "default": "red", - "description": "Choose your favorite color." - } - } -} -``` - -Options are exported as Feature-scoped environment variables. The option name is captialized and sanitized according to [option resolution](https://containers.dev/implementors/features/#option-resolution). - -```bash -#!/bin/bash - -echo "Activating feature 'color'" -echo "The provided favorite color is: ${FAVORITE}" - -... -``` - -## Distributing Features - -### Versioning - -Features are individually versioned by the `version` attribute in a Feature's `devcontainer-feature.json`. Features are versioned according to the semver specification. More details can be found in [the dev container Feature specification](https://containers.dev/implementors/features/#versioning). - -### Publishing - -> NOTE: The Distribution spec can be [found here](https://containers.dev/implementors/features-distribution/). -> -> While any registry [implementing the OCI Distribution spec](https://github.com/opencontainers/distribution-spec) can be used, this template will leverage GHCR (GitHub Container Registry) as the backing registry. - -Features are meant to be easily sharable units of dev container configuration and installation code. - -This repo contains a **GitHub Action** [workflow](.github/workflows/release.yaml) that will publish each Feature to GHCR. - -*Allow GitHub Actions to create and approve pull requests* should be enabled in the repository's `Settings > Actions > General > Workflow permissions` for auto generation of `src//README.md` per Feature (which merges any existing `src//NOTES.md`). - -By default, each Feature will be prefixed with the `` namespace. For example, the two Features in this repository can be referenced in a `devcontainer.json` with: - -``` -ghcr.io/devcontainers/feature-starter/color:1 -ghcr.io/devcontainers/feature-starter/hello:1 -``` - -The provided GitHub Action will also publish a third "metadata" package with just the namespace, eg: `ghcr.io/devcontainers/feature-starter`. This contains information useful for tools aiding in Feature discovery. - -'`devcontainers/feature-starter`' is known as the feature collection namespace. - -### Marking Feature Public - -Note that by default, GHCR packages are marked as `private`. To stay within the free tier, Features need to be marked as `public`. - -This can be done by navigating to the Feature's "package settings" page in GHCR, and setting the visibility to 'public`. The URL may look something like: - -``` -https://github.com/users//packages/container/%2F/settings -``` - -image - -### Adding Features to the Index - -If you'd like your Features to appear in our [public index](https://containers.dev/features) so that other community members can find them, you can do the following: - -* Go to [github.com/devcontainers/devcontainers.github.io](https://github.com/devcontainers/devcontainers.github.io) - * This is the GitHub repo backing the [containers.dev](https://containers.dev/) spec site -* Open a PR to modify the [collection-index.yml](https://github.com/devcontainers/devcontainers.github.io/blob/gh-pages/_data/collection-index.yml) file - -This index is from where [supporting tools](https://containers.dev/supporting) like [VS Code Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) and [GitHub Codespaces](https://github.com/features/codespaces) surface Features for their dev container creation UI. - -#### Using private Features in Codespaces - -For any Features hosted in GHCR that are kept private, the `GITHUB_TOKEN` access token in your environment will need to have `package:read` and `contents:read` for the associated repository. - -Many implementing tools use a broadly scoped access token and will work automatically. GitHub Codespaces uses repo-scoped tokens, and therefore you'll need to add the permissions in `devcontainer.json` - -An example `devcontainer.json` can be found below. - -```jsonc -{ - "image": "mcr.microsoft.com/devcontainers/base:ubuntu", - "features": { - "ghcr.io/my-org/private-features/hello:1": { - "greeting": "Hello" - } - }, - "customizations": { - "codespaces": { - "repositories": { - "my-org/private-features": { - "permissions": { - "packages": "read", - "contents": "read" - } - } - } - } - } -} -``` +- bash-config: ghcr.io/gilesknap/devcontainer-features/bash-config:latest diff --git a/test/bash-config/scenarios.json b/test/bash-config/scenarios.json new file mode 100644 index 0000000..484e02a --- /dev/null +++ b/test/bash-config/scenarios.json @@ -0,0 +1,8 @@ +{ + "hello": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "bash-config": {} + } + } +} \ No newline at end of file diff --git a/test/bash-config/test.sh b/test/bash-config/test.sh new file mode 100644 index 0000000..2886eb3 --- /dev/null +++ b/test/bash-config/test.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -e + +# Import test library bundled with the devcontainer CLI +# See https://github.com/devcontainers/cli/blob/HEAD/docs/features/test.md#dev-container-features-test-lib +# Provides the 'check' and 'reportResults' commands. +source dev-container-features-test-lib + +# Feature-specific tests +# The 'check' command comes from the dev-container-features-test-lib. Syntax is... +# check