Skip to content

Commit b6bb18f

Browse files
authored
feat(shell): add new feature and tests for default and minimal scenarios (#22)
feat(shell): add robust shell feature supporting Zsh and optional Oh My Zsh setup - Install Zsh by default, optionally install Oh My Zsh, Powerlevel10k theme, and plugins. - Dynamically installs git if missing to ensure cross-platform compatibility. - Handles .zshrc safely and ensures correct behavior for non-root and root users. - Only installs plugins and themes when Oh My Zsh is enabled. - Finalized resilient and modular setup. test(shell): add validation for minimal and full Zsh installation scenarios - Validate basic Zsh installation without Oh My Zsh. - Validate full installation including Oh My Zsh, Powerlevel10k, and plugins. - Ensure .zshrc exists and is correctly modified. test(scenarios): expand scenarios to validate against amazonlinux, debian, ubuntu images - Added Amazon Linux 2023 to feature test matrix. - Created multiple scenarios targeting different base images. chore: polish devcontainer-feature.json and automate documentation - Improve feature metadata descriptions. - Ensure automated documentation follows conventional commit format. --- Signed-off-by: Jonatan Mata <jonmatum@gmail.com>
1 parent c3fb349 commit b6bb18f

File tree

18 files changed

+4205
-13
lines changed

18 files changed

+4205
-13
lines changed

.github/workflows/release.yaml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,23 @@ jobs:
3030
run: |
3131
set -e
3232
echo "Start."
33+
3334
# Configure git and Push updates
3435
git config --global user.email github-actions[bot]@users.noreply.github.com
3536
git config --global user.name github-actions[bot]
3637
git config pull.rebase false
37-
branch=automated-documentation-update-$GITHUB_RUN_ID
38-
git checkout -b $branch
39-
message='Automated documentation update'
38+
39+
branch=automated/documentation-update-$GITHUB_RUN_ID
40+
git checkout -b "$branch"
41+
42+
# Conventional commit message
43+
message='docs: update feature documentation [skip ci]'
44+
4045
# Add / update and commit
4146
git add */**/README.md
42-
git commit -m 'Automated documentation update [skip ci]' || export NO_UPDATES=true
43-
# Push
47+
git commit -m "$message" || export NO_UPDATES=true
48+
49+
# Push and create PR
4450
if [ "$NO_UPDATES" != "true" ] ; then
4551
git push origin "$branch"
4652
gh pr create --title "$message" --body "$message"

.github/workflows/test.yaml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ jobs:
1313
strategy:
1414
matrix:
1515
features:
16-
- color
17-
- hello
16+
- shell
1817
baseImage:
1918
- debian:latest
2019
- ubuntu:latest
2120
- mcr.microsoft.com/devcontainers/base:ubuntu
21+
- amazonlinux:2023
2222
steps:
2323
- uses: actions/checkout@v4
2424

@@ -34,8 +34,7 @@ jobs:
3434
strategy:
3535
matrix:
3636
features:
37-
- color
38-
- hello
37+
- shell
3938
steps:
4039
- uses: actions/checkout@v4
4140

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "Feature Test Sandbox",
3+
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
4+
"features": {
5+
"./features/shell": {
6+
"opinionated": true
7+
}
8+
}
9+
}

.sandbox/.devcontainer/features/shell/assets/p10k.zsh

Lines changed: 1713 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"id": "shell",
3+
"version": "0.0.0",
4+
"name": "Shell Environment",
5+
"description": "Configurable shell setup with optional Zsh, Oh My Zsh, Powerlevel10k, autosuggestions, and syntax highlighting.",
6+
"options": {
7+
"installZsh": {
8+
"type": "boolean",
9+
"default": true,
10+
"description": "Install Zsh and set it as the default shell."
11+
},
12+
"ohMyZsh": {
13+
"type": "boolean",
14+
"default": true,
15+
"description": "Install the Oh My Zsh framework."
16+
},
17+
"powerlevel10k": {
18+
"type": "boolean",
19+
"default": true,
20+
"description": "Install the Powerlevel10k theme for Zsh."
21+
},
22+
"autosuggestions": {
23+
"type": "boolean",
24+
"default": true,
25+
"description": "Enable the zsh-autosuggestions plugin."
26+
},
27+
"syntaxHighlighting": {
28+
"type": "boolean",
29+
"default": true,
30+
"description": "Enable the zsh-syntax-highlighting plugin."
31+
},
32+
"opinionated": {
33+
"type": "boolean",
34+
"default": false,
35+
"description": "Apply an opinionated Powerlevel10k configuration for a highly customized prompt."
36+
}
37+
},
38+
"entrypoint": "install.sh",
39+
"installsAfter": [
40+
"ghcr.io/devcontainers/features/common-utils"
41+
]
42+
}
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# Load feature-utils if available
5+
if [ -f "/usr/local/share/feature-utils.sh" ]; then
6+
. /usr/local/share/feature-utils.sh
7+
else
8+
echo "Warning: feature-utils.sh not found. Continuing without it."
9+
fi
10+
11+
echo "> Starting shell environment setup..."
12+
13+
USERNAME="${_REMOTE_USER:-vscode}"
14+
15+
# Resolve home directory safely
16+
if [ "${USERNAME}" = "root" ]; then
17+
USER_HOME="/root"
18+
elif [ -d "/home/${USERNAME}" ]; then
19+
USER_HOME="/home/${USERNAME}"
20+
else
21+
echo "Warning: User home for '${USERNAME}' not found. Defaulting to /root."
22+
USER_HOME="/root"
23+
fi
24+
25+
ZSHRC="${USER_HOME}/.zshrc"
26+
OMZ_DIR="${USER_HOME}/.oh-my-zsh"
27+
ZSH_CUSTOM="${OMZ_DIR}/custom"
28+
29+
# Feature options
30+
: "${installZsh:=true}"
31+
: "${ohMyZsh:=true}"
32+
: "${powerlevel10k:=true}"
33+
: "${autosuggestions:=true}"
34+
: "${syntaxHighlighting:=true}"
35+
: "${opinionated:=false}"
36+
: "${autosuggestHighlight:=fg=8}"
37+
38+
detect_package_manager() {
39+
if command -v apt-get &>/dev/null; then
40+
echo "apt"
41+
elif command -v dnf &>/dev/null; then
42+
echo "dnf"
43+
elif command -v yum &>/dev/null; then
44+
echo "yum"
45+
elif command -v apk &>/dev/null; then
46+
echo "apk"
47+
else
48+
echo "unsupported"
49+
fi
50+
}
51+
52+
install_package_if_missing() {
53+
local package="$1"
54+
if ! command -v "$package" &>/dev/null; then
55+
echo "Installing missing package: $package"
56+
local pm
57+
pm=$(detect_package_manager)
58+
case "${pm}" in
59+
apt)
60+
apt-get update -y && apt-get install -y --no-install-recommends "$package"
61+
;;
62+
dnf)
63+
dnf install -y --allowerasing "$package"
64+
;;
65+
yum)
66+
yum install -y "$package"
67+
;;
68+
apk)
69+
apk add --no-cache "$package"
70+
;;
71+
*)
72+
echo "Unsupported package manager."
73+
exit 1
74+
;;
75+
esac
76+
fi
77+
}
78+
79+
ensure_common_dependencies() {
80+
echo "Ensuring common system dependencies..."
81+
for pkg in curl git tar bash ca-certificates; do
82+
install_package_if_missing "$pkg"
83+
done
84+
}
85+
86+
install_zsh() {
87+
if ! command -v zsh &>/dev/null; then
88+
install_package_if_missing zsh
89+
# Special case: Amazon Linux needs util-linux-user for chsh
90+
local pm
91+
pm=$(detect_package_manager)
92+
if [[ "$pm" == "dnf" || "$pm" == "yum" ]]; then
93+
install_package_if_missing util-linux-user || echo "Skipping util-linux-user: not needed."
94+
fi
95+
fi
96+
97+
echo "Changing default shell to Zsh for user ${USERNAME}"
98+
chsh -s "$(command -v zsh)" "${USERNAME}" || echo "Warning: Failed to change shell, continuing..."
99+
}
100+
101+
install_oh_my_zsh() {
102+
if [ ! -d "${OMZ_DIR}" ]; then
103+
echo "Installing Oh My Zsh for ${USERNAME}..."
104+
# Set correct HOME manually so installer puts files in the right place
105+
HOME="${USER_HOME}" su - "${USERNAME}" -c "sh -c \"\$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\" --unattended"
106+
107+
# Ensure correct ownership
108+
chown -R "${USERNAME}:${USERNAME}" "${OMZ_DIR}"
109+
else
110+
echo "Oh My Zsh already installed, skipping."
111+
fi
112+
113+
# Make sure .zshrc points to correct OMZ install
114+
if [ ! -f "${ZSHRC}" ]; then
115+
echo "Creating missing .zshrc at ${ZSHRC}"
116+
echo 'export ZSH="$HOME/.oh-my-zsh"' >"${ZSHRC}"
117+
echo 'ZSH_THEME="robbyrussell"' >>"${ZSHRC}"
118+
chown "${USERNAME}:${USERNAME}" "${ZSHRC}"
119+
else
120+
grep -qxF 'export ZSH="$HOME/.oh-my-zsh"' "${ZSHRC}" || echo 'export ZSH="$HOME/.oh-my-zsh"' >>"${ZSHRC}"
121+
grep -qxF 'ZSH_THEME="robbyrussell"' "${ZSHRC}" || echo 'ZSH_THEME="robbyrussell"' >>"${ZSHRC}"
122+
fi
123+
}
124+
125+
install_powerlevel10k() {
126+
local theme_dir="${ZSH_CUSTOM}/themes/powerlevel10k"
127+
128+
if [ ! -d "${theme_dir}" ]; then
129+
echo "Installing Powerlevel10k..."
130+
su - "${USERNAME}" -c "git clone --depth=1 https://github.com/romkatv/powerlevel10k.git '${theme_dir}'"
131+
else
132+
echo "Powerlevel10k already installed, skipping."
133+
fi
134+
135+
# Ensure theme activation
136+
if grep -q '^ZSH_THEME=' "${ZSHRC}"; then
137+
sed -i 's/^ZSH_THEME=.*/ZSH_THEME="powerlevel10k\/powerlevel10k"/' "${ZSHRC}"
138+
else
139+
echo 'ZSH_THEME="powerlevel10k/powerlevel10k"' >>"${ZSHRC}"
140+
fi
141+
}
142+
143+
install_autosuggestions() {
144+
local plugin_dir="${ZSH_CUSTOM}/plugins/zsh-autosuggestions"
145+
if [ ! -d "${plugin_dir}" ]; then
146+
echo "Installing zsh-autosuggestions plugin..."
147+
su - "${USERNAME}" -c "git clone https://github.com/zsh-users/zsh-autosuggestions '${plugin_dir}'"
148+
fi
149+
grep -qxF "source ${plugin_dir}/zsh-autosuggestions.zsh" "${ZSHRC}" || echo "source ${plugin_dir}/zsh-autosuggestions.zsh" >>"${ZSHRC}"
150+
grep -qxF "ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='${autosuggestHighlight}'" "${ZSHRC}" || echo "ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='${autosuggestHighlight}'" >>"${ZSHRC}"
151+
}
152+
153+
install_syntax_highlighting() {
154+
local plugin_dir="${ZSH_CUSTOM}/plugins/zsh-syntax-highlighting"
155+
if [ ! -d "${plugin_dir}" ]; then
156+
echo "Installing zsh-syntax-highlighting plugin..."
157+
su - "${USERNAME}" -c "git clone https://github.com/zsh-users/zsh-syntax-highlighting.git '${plugin_dir}'"
158+
fi
159+
grep -qxF "source ${plugin_dir}/zsh-syntax-highlighting.zsh" "${ZSHRC}" || echo "source ${plugin_dir}/zsh-syntax-highlighting.zsh" >>"${ZSHRC}"
160+
}
161+
162+
fix_permissions() {
163+
echo "Fixing permissions..."
164+
chown -R "${USERNAME}:${USERNAME}" "${USER_HOME}"
165+
}
166+
167+
apply_opinionated_files() {
168+
echo "Applying opinionated config files..."
169+
170+
local assets_dir
171+
assets_dir="$(dirname "$0")/assets"
172+
173+
if [ -d "$assets_dir" ]; then
174+
for file in "$assets_dir"/*; do
175+
base_name="$(basename "$file")"
176+
target_name="${base_name}"
177+
# If the file doesn't start with '.', add one (make it hidden)
178+
[[ "${base_name}" != .* ]] && target_name=".${base_name}"
179+
echo "Copying ${file} to ${USER_HOME}/${target_name}"
180+
cp "$file" "${USER_HOME}/${target_name}"
181+
chown "${USERNAME}:${USERNAME}" "${USER_HOME}/${target_name}"
182+
done
183+
else
184+
echo "Warning: Assets directory not found: $assets_dir"
185+
fi
186+
}
187+
188+
# --- MAIN ---
189+
190+
ensure_common_dependencies
191+
192+
if [[ "${installZsh}" == "true" ]]; then
193+
install_zsh
194+
fi
195+
196+
if [[ "${installZsh}" == "true" && "${ohMyZsh}" == "true" ]]; then
197+
install_oh_my_zsh
198+
fi
199+
200+
if [[ "${powerlevel10k}" == "true" && "${ohMyZsh}" == "true" ]]; then
201+
install_powerlevel10k
202+
fi
203+
204+
if [[ "${autosuggestions}" == "true" && "${ohMyZsh}" == "true" ]]; then
205+
install_autosuggestions
206+
fi
207+
208+
if [[ "${syntaxHighlighting}" == "true" && "${ohMyZsh}" == "true" ]]; then
209+
install_syntax_highlighting
210+
fi
211+
212+
fix_permissions
213+
214+
if [[ "${opinionated}" == "true" ]]; then
215+
apply_opinionated_files
216+
fi
217+
218+
echo "Shell environment setup completed successfully for ${USERNAME}!"

0 commit comments

Comments
 (0)