Skip to content

Commit efef1f4

Browse files
authored
Merge branch 'master' into master-preserve-install-order
2 parents 489c5fc + 5513791 commit efef1f4

11 files changed

Lines changed: 290 additions & 57 deletions

File tree

.github/workflows/shellcheck.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: ShellCheck
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize]
6+
push:
7+
branches:
8+
- master
9+
- dev
10+
- staging
11+
12+
permissions:
13+
contents: read
14+
15+
jobs:
16+
shellcheck:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- name: Checkout code
20+
uses: actions/checkout@v4
21+
22+
- name: Run ShellCheck
23+
uses: ludeeus/action-shellcheck@master
24+
with:
25+
scandir: '.'
26+
format: gcc
27+
severity: style
28+

.shellcheckrc

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# ShellCheck configuration file
2+
# See https://github.com/koalaman/shellcheck/wiki/Directives for more information
3+
4+
# Enable all optional checks
5+
enable=all
6+
7+
# Disable specific warnings that may be acceptable in this project
8+
# SC1090: Can't follow non-constant source. Use a path to a file that can be checked
9+
# (disabled because we use dynamic sourcing with script_dir)
10+
disable=SC1090
11+
12+
# SC1091: Not following: <file> was not specified as input
13+
# (disabled because lib.sh is sourced dynamically)
14+
disable=SC1091
15+
16+
# SC2310: Function invoked in && condition (set -e disabled)
17+
# (acceptable pattern for conditional execution)
18+
disable=SC2310
19+
20+
# SC2311: Bash implicitly disabled set -e in command substitution
21+
# (acceptable pattern - we want to capture output even if function fails)
22+
disable=SC2311
23+
24+
# SC2312: Consider invoking command separately to avoid masking return value
25+
# (many of these are acceptable patterns in command substitutions)
26+
disable=SC2312
27+
28+
# Exclude external files that we don't control
29+
exclude-dir=.git
30+
exclude-dir=testlogs
31+

README.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,84 @@ For more context and information see [issue #57](https://github.com/awalsh128/ca
154154
### Cache Limits
155155

156156
A repository can have up to 5GB of caches. Once the 5GB limit is reached, older caches will be evicted based on when the cache was last accessed. Caches that are not accessed within the last week will also be evicted. To get more information on how to access and manage your actions's caches, see [GitHub Actions / Using workflows / Cache dependencies](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#viewing-cache-entries).
157+
158+
## Development
159+
160+
### Prerequisites
161+
162+
- **Go 1.20+** (for building the `apt_query` binary) - version specified in `go.mod`
163+
- Install from [golang.org](https://golang.org/dl/) or via package manager
164+
- Verify installation: `go version`
165+
- **ShellCheck** (for linting shell scripts) - install via:
166+
- macOS: `brew install shellcheck`
167+
- Linux: `sudo apt-get install shellcheck` or see [shellcheck installation guide](https://github.com/koalaman/shellcheck#installing)
168+
- Windows: Available via [scoop](https://scoop.sh/) or [chocolatey](https://chocolatey.org/)
169+
170+
### Building
171+
172+
The project includes Go binaries (`apt_query-arm64` and `apt_query-x86`) that are used by the shell scripts to query APT package information.
173+
174+
**Build all packages:**
175+
```bash
176+
go build -v ./...
177+
```
178+
179+
**Build for specific architecture:**
180+
```bash
181+
# For ARM64 (Apple Silicon, ARM servers)
182+
GOARCH=arm64 go build -o apt_query-arm64 ./src/cmd/apt_query
183+
184+
# For x86_64 (Intel/AMD)
185+
GOARCH=amd64 go build -o apt_query-x86 ./src/cmd/apt_query
186+
```
187+
188+
**Run tests:**
189+
```bash
190+
go test -v ./...
191+
```
192+
193+
### Linting
194+
195+
This project uses [ShellCheck](https://github.com/koalaman/shellcheck) to ensure shell script quality and catch common errors. The configuration is stored in `.shellcheckrc`.
196+
197+
**Run ShellCheck locally:**
198+
```bash
199+
shellcheck *.sh
200+
```
201+
202+
**IDE Integration:**
203+
204+
Many IDEs and editors can automatically run ShellCheck:
205+
206+
- **VS Code**: Install the [ShellCheck extension](https://marketplace.visualstudio.com/items?itemName=timonwong.shellcheck)
207+
- **Vim/Neovim**: Use [ALE](https://github.com/dense-analysis/ale) or [coc-shellcheck](https://github.com/josa42/coc-shellcheck)
208+
- **IntelliJ/CLion**: ShellCheck is integrated in recent versions
209+
- **Sublime Text**: Install [SublimeLinter-shellcheck](https://github.com/SublimeLinter/SublimeLinter-shellcheck)
210+
211+
**Go Linting:**
212+
213+
This project uses [golangci-lint](https://golangci-lint.run/) for Go code quality checks.
214+
215+
**Run golangci-lint locally:**
216+
```bash
217+
# Install golangci-lint (if not already installed)
218+
# macOS: brew install golangci-lint
219+
# Linux: See https://golangci-lint.run/usage/install/
220+
221+
golangci-lint run
222+
```
223+
224+
**IDE Integration:**
225+
226+
- **VS Code**: Install the [Go extension](https://marketplace.visualstudio.com/items?itemName=golang.go) for syntax highlighting, auto-completion, and built-in linting
227+
- **IntelliJ/GoLand**: Built-in Go support with linting and formatting
228+
- **Vim/Neovim**: Use [vim-go](https://github.com/fatih/vim-go) for Go development
229+
230+
### CI/CD
231+
232+
The GitHub Actions workflows will automatically:
233+
- **Build and test** Go code on pull requests
234+
- **Run ShellCheck** on shell scripts (blocks PRs on failures)
235+
- **Run golangci-lint** on Go code (blocks PRs on failures)
236+
237+
All checks run on pull requests and pushes to `master`, `dev`, and `staging` branches.

install_and_cache_pkgs.sh

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ cache_dir="${1}"
1919
add_repository="${3}"
2020

2121
# List of the packages to use.
22-
input_packages="${@:4}"
22+
input_packages="${*:4}"
2323

2424
if ! apt-fast --version > /dev/null 2>&1; then
2525
log "Installing apt-fast for optimized installs..."
@@ -31,7 +31,7 @@ if ! apt-fast --version > /dev/null 2>&1; then
3131
fi
3232

3333
# Add custom repositories if specified
34-
if [ -n "${add_repository}" ]; then
34+
if [[ -n "${add_repository}" ]]; then
3535
log "Adding custom repositories..."
3636
for repository in ${add_repository}; do
3737
log "- Adding repository: ${repository}"
@@ -71,7 +71,10 @@ install_log_filepath="${cache_dir}/install.log"
7171

7272
log "Clean installing ${package_count} packages..."
7373
# Zero interaction while installing or upgrading the system via apt.
74-
sudo DEBIAN_FRONTEND=noninteractive apt-fast --yes install ${packages} > "${install_log_filepath}"
74+
# Note: sudo doesn't affect redirects, but we want the output in the file anyway
75+
# shellcheck disable=SC2024
76+
# We intentionally redirect output here; the redirect happens as the current user which is fine
77+
sudo DEBIAN_FRONTEND=noninteractive apt-fast --yes install "${packages}" > "${install_log_filepath}"
7578
log "done"
7679
log "Installation log written to ${install_log_filepath}"
7780

@@ -81,7 +84,7 @@ installed_packages=$(get_installed_packages "${install_log_filepath}")
8184
log "Installed package list:"
8285
for installed_package in ${installed_packages}; do
8386
# Reformat for human friendly reading.
84-
log "- $(echo ${installed_package} | awk -F\= '{print $1" ("$2")"}')"
87+
log "- $(echo "${installed_package}" | awk -F= '{print $1" ("$2")"}')"
8588
done
8689

8790
log_empty_line
@@ -93,7 +96,7 @@ for installed_package in ${installed_packages}; do
9396

9497
# Sanity test in case APT enumerates duplicates.
9598
if test ! -f "${cache_filepath}"; then
96-
read package_name package_ver < <(get_package_name_ver "${installed_package}")
99+
read -r package_name package_ver < <(get_package_name_ver "${installed_package}")
97100
log " * Caching ${package_name} to ${cache_filepath}..."
98101

99102
# Pipe all package files (no folders), including symlinks, their targets, and installation control data to Tar.
@@ -104,9 +107,9 @@ for installed_package in ${installed_packages}; do
104107
while IFS= read -r f; do
105108
if test -f "${f}" -o -L "${f}"; then
106109
get_tar_relpath "${f}"
107-
if [ -L "${f}" ]; then
110+
if [[ -L "${f}" ]]; then
108111
target="$(readlink -f "${f}")"
109-
if [ -f "${target}" ]; then
112+
if [[ -f "${target}" ]]; then
110113
get_tar_relpath "${target}"
111114
fi
112115
fi
@@ -120,7 +123,7 @@ for installed_package in ${installed_packages}; do
120123
# Comma delimited name:ver pairs in the all packages manifest.
121124
manifest_all="${manifest_all}${package_name}=${package_ver},"
122125
done
123-
log "done (total cache size $(du -h ${cache_dir} | tail -1 | awk '{print $1}'))"
126+
log "done (total cache size $(du -h "${cache_dir}" | tail -1 | awk '{print $1}'))"
124127

125128
log_empty_line
126129

lib.sh

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,16 @@ set +e
1717
# Filepath of the install script, otherwise an empty string.
1818
###############################################################################
1919
function execute_install_script {
20-
local package_name=$(basename ${2} | awk -F\= '{print $1}')
21-
local install_script_filepath=$(\
20+
local package_name
21+
package_name=$(basename "${2}" | awk -F= '{print $1}')
22+
local install_script_filepath
23+
install_script_filepath=$(\
2224
get_install_script_filepath "${1}" "${package_name}" "${3}")
2325
if test ! -z "${install_script_filepath}"; then
2426
log "- Executing ${install_script_filepath}..."
2527
# Don't abort on errors; dpkg-trigger will error normally since it is
2628
# outside its run environment.
27-
sudo sh -x ${install_script_filepath} ${4} || true
29+
sudo sh -x "${install_script_filepath}" "${4}" || true
2830
log " done"
2931
fi
3032
}
@@ -40,9 +42,17 @@ function execute_install_script {
4042
###############################################################################
4143
function get_install_script_filepath {
4244
# Filename includes arch (e.g. amd64).
43-
local filepath="$(\
44-
ls -1 ${1}var/lib/dpkg/info/${2}*.${3} 2> /dev/null \
45-
| grep -E ${2}'(:.*)?.'${3} | head -1 || true)"
45+
local filepath
46+
# Use glob expansion instead of ls|grep for better handling of non-alphanumeric filenames
47+
# Use nullglob to prevent literal match when no files found
48+
shopt -s nullglob
49+
for f in "${1}"var/lib/dpkg/info/"${2}"*."${3}"; do
50+
if [[ -f "${f}" ]] && [[ "${f}" =~ ${2}'(:.*)?.'"${3}" ]]; then
51+
filepath="${f}"
52+
break
53+
fi
54+
done
55+
shopt -u nullglob
4656
test "${filepath}" && echo "${filepath}"
4757
}
4858

@@ -66,7 +76,7 @@ function get_installed_packages {
6676
log_err "Unable to parse package name and version from \"${line}\""
6777
exit 2
6878
fi
69-
done < <(grep "^Unpacking " ${install_log_filepath})
79+
done < <(grep "^Unpacking " "${install_log_filepath}")
7080
if test -n "${dep_packages}"; then
7181
echo "${dep_packages:0:-1}" # Removing trailing space.
7282
else
@@ -83,13 +93,13 @@ function get_installed_packages {
8393
###############################################################################
8494
function get_package_name_ver {
8595
local ORIG_IFS="${IFS}"
86-
IFS=\= read name ver <<< "${1}"
96+
IFS='=' read -r name ver <<< "${1}"
8797
IFS="${ORIG_IFS}"
8898
# If version not found in the fully qualified package value.
8999
if test -z "${ver}"; then
90100
# This is a fallback and should not be used any more as its slow.
91101
log_err "Unexpected version resolution for package '${name}'"
92-
ver="$(apt-cache show ${name} | grep '^Version:' | awk '{print $2}')"
102+
ver="$(apt-cache show "${name}" | grep '^Version:' | awk '{print $2}')"
93103
fi
94104
echo "${name}" "${ver}"
95105
}
@@ -105,16 +115,19 @@ function get_normalized_package_list {
105115
# Remove commas, and block scalar folded backslashes,
106116
# extraneous spaces at the middle, beginning and end
107117
# then sort.
108-
local packages=$(echo "${1}" \
118+
local packages
119+
packages=$(echo "${1}" \
109120
| sed 's/[,\]/ /g; s/\s\+/ /g; s/^\s\+//g; s/\s\+$//g' \
110121
| sort -t' ')
111-
local script_dir="$(dirname -- "$(realpath -- "${0}")")"
122+
local script_dir
123+
script_dir="$(dirname -- "$(realpath -- "${0}")")"
112124

113-
local architecture=$(dpkg --print-architecture)
114-
if [ "${architecture}" == "arm64" ]; then
115-
${script_dir}/apt_query-arm64 normalized-list ${packages}
125+
local architecture
126+
architecture=$(dpkg --print-architecture)
127+
if [[ "${architecture}" == "arm64" ]]; then
128+
"${script_dir}"/apt_query-arm64 normalized-list "${packages}"
116129
else
117-
${script_dir}/apt_query-x86 normalized-list ${packages}
130+
"${script_dir}"/apt_query-x86 normalized-list "${packages}"
118131
fi
119132
}
120133

@@ -127,8 +140,8 @@ function get_normalized_package_list {
127140
# The relative filepath to archive.
128141
###############################################################################
129142
function get_tar_relpath {
130-
local filepath=${1}
131-
if test ${filepath:0:1} = "/"; then
143+
local filepath="${1}"
144+
if test "${filepath:0:1}" = "/"; then
132145
echo "${filepath:1}"
133146
else
134147
echo "${filepath}"
@@ -183,7 +196,7 @@ function validate_bool {
183196
if test "${1}" != "true" -a "${1}" != "false"; then
184197
log "aborted"
185198
log "${2} value '${1}' must be either true or false (case sensitive)."
186-
exit ${3}
199+
exit "${3}"
187200
fi
188201
}
189202

@@ -197,7 +210,7 @@ function validate_bool {
197210
# Log lines from write.
198211
###############################################################################
199212
function write_manifest {
200-
if [ ${#2} -eq 0 ]; then
213+
if [[ ${#2} -eq 0 ]]; then
201214
log "Skipped ${1} manifest write. No packages to install."
202215
else
203216
log "Writing ${1} packages manifest to ${3}..."

post_cache_action.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ test "${debug}" = "true" && set -x
2929
add_repository="${6}"
3030

3131
# List of the packages to use.
32-
packages="${@:7}"
32+
packages="${*:7}"
3333

3434
if test "${cache_hit}" = "true"; then
35-
${script_dir}/restore_pkgs.sh "${cache_dir}" "${cache_restore_root}" "${execute_install_scripts}" "${debug}"
35+
"${script_dir}"/restore_pkgs.sh "${cache_dir}" "${cache_restore_root}" "${execute_install_scripts}" "${debug}"
3636
else
37-
${script_dir}/install_and_cache_pkgs.sh "${cache_dir}" "${debug}" "${add_repository}" ${packages}
37+
"${script_dir}"/install_and_cache_pkgs.sh "${cache_dir}" "${debug}" "${add_repository}" "${packages}"
3838
fi
3939

4040
log_empty_line

0 commit comments

Comments
 (0)