diff --git a/.editorconfig b/.editorconfig index d94dd8b..77da454 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,7 +4,7 @@ # This file is based on The Common EditorConfig Template project # https://github.com/the-common/editorconfig-template # -# Copyright 2021 林博仁(Buo-ren, Lin) +# Copyright 2021 林博仁(Buo-ren, Lin) # SPDX-License-Identifier: WTFPL # This is the top-most EditorConfig file @@ -20,7 +20,7 @@ insert_final_newline = true trim_trailing_whitespace = true # Git configuration files uses tabs as indentation units -[.git*] +[/.git{modules,config}] indent_style = tab # Avoid git patch fail to apply due to stripped unmodified lines that contains only spaces @@ -59,3 +59,8 @@ indent_size = 2 [.*.{yml,yaml}] indent_size = 2 + +# Keep the indentation style of the license text verbatim +[/LICENSES/*] +indent_size = unset +indent_style = unset diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..0e564c0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,24 @@ +# Git path attributes configuration file +# +# References: +# +# * Git - Git Attributes +# https://git-scm.com/book/en/v2/Customizing-Git-Git-Attributes +# * Git - gitattributes Documentation +# https://www.git-scm.com/docs/gitattributes +# +# Copyright 2024 林博仁(Buo-ren Lin) +# SPDX-License-Identifier: CC-BY-SA-4.0 + +# Avoid exporting development files to release archive +/.* export-ignore +/continuous-integration export-ignore + +# Keep editorconfig for ease of editing of product files +/.editorconfig -export-ignore + +# Keep REUSE DEP5 declaration file in the release archive for legal +# conformance +/.reuse/ -export-ignore +/.reuse/* export-ignore +/.reuse/dep5 -export-ignore diff --git a/.github/workflows/check-potential-problems.yml b/.github/workflows/check-potential-problems.yml index a17745a..e5b431f 100644 --- a/.github/workflows/check-potential-problems.yml +++ b/.github/workflows/check-potential-problems.yml @@ -5,11 +5,13 @@ # * Workflow syntax for GitHub Actions - GitHub Docs # https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions # -# Copyright 2022 林博仁(Buo-ren, Lin) +# Copyright 2024 林博仁(Buo-ren Lin) # SPDX-License-Identifier: CC-BY-SA-4.0 name: Check potential problems in the project on: - - push + push: + branches: + - '**' jobs: check-using-precommit: name: Check potential problems using pre-commit @@ -17,24 +19,37 @@ jobs: env: PIP_CACHE_DIR: ${{ github.workspace }}/.cache/pip PRE_COMMIT_HOME: ${{ github.workspace }}/.cache/pre-commit + SHELLCHECK_DIR: ${{ github.workspace }}/.cache/shellcheck-stable steps: - - name: Checking out content from repository + - name: Check out content from the Git repository uses: actions/checkout@v4 - name: Configure PyPI data cache to speed up continuous integration - uses: actions/cache@v3 + uses: actions/cache@v4 with: key: ${{ runner.os }}-pip path: ${{ env.PIP_CACHE_DIR }} - name: >- Configure pre-commit data cache to speed up continuous integration - uses: actions/cache@v3 + uses: actions/cache@v4 with: key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }} path: ${{ env.PRE_COMMIT_HOME }} - - name: Running static analysis program + - name: >- + Configure pre-built ShellCheck cache to speed up continuous integration + uses: actions/cache@v4 + with: + key: ${{ runner.os }}-${{ runner.arch }}-shellcheck + path: ${{ env.SHELLCHECK_DIR }} + + - name: >- + Patch the sudo security policy so that programs run via sudo + will recognize environment variables predefined by GitHub + run: sudo ./continuous-integration/patch-github-actions-sudo-security-policy.sh + + - name: Run the static analysis programs run: | sudo ./continuous-integration/do-static-analysis.install-system-deps.sh ./continuous-integration/do-static-analysis.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1aae685 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,74 @@ +# Release product and their build aritfacts +# +# References: +# +# * Workflow syntax for GitHub Actions - GitHub Docs +# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions +# +# Copyright 2024 林博仁(Buo-ren Lin) +# SPDX-License-Identifier: CC-BY-SA-4.0 +name: Release product and their build aritfacts +on: + push: + tags: + - v*.*.* + +jobs: + release: + name: Release product and their build aritfacts + runs-on: ubuntu-20.04 + steps: + - name: Check out content from the Git repository + uses: actions/checkout@v4 + with: + # Increase fetch depth if you may have more than this amount + # of revisions between releases + fetch-depth: 100 + + # Fetch tags as well to generate detailed changes between two releases + # WORKAROUND: Adding this option triggers actions/checkout#1467 + #fetch-tags: true + + - name: >- + WORKAROUND: Fetch tags that points to the revisions + checked-out(actions/checkout#1467) + run: |- + git fetch \ + --prune \ + --prune-tags \ + --force \ + --depth=100 \ + --no-recurse-submodules + + - name: Determine the project identifier + run: printf "project_id=${GITHUB_REPOSITORY##*/}\\n" >> $GITHUB_ENV + + - name: Determine the name of the Git tag + run: printf "release_tag=${GITHUB_REF##*/}\\n" >> $GITHUB_ENV + + - name: Determine the release version string + run: printf "release_version=${release_tag#v}\\n" >> $GITHUB_ENV + + - name: Determine the release identifier + run: printf "release_id=${project_id}-${release_version}\\n" >> $GITHUB_ENV + + - name: >- + Patch the sudo security policy so that programs run via sudo + will recognize environment variables predefined by GitHub + run: sudo ./continuous-integration/patch-github-actions-sudo-security-policy.sh + + - name: Generate the release archive + run: |- + sudo ./continuous-integration/generate-build-artifacts.install-system-deps.sh + ./continuous-integration/generate-build-artifacts.sh + + - name: Generate the release description + run: ./continuous-integration/generate-release-description.sh + + - name: Publish the release archive to the GitHub Releases + uses: softprops/action-gh-release@v2 + with: + name: ${{ env.project_id }} ${{ env.release_version }} + files: | + ${{ env.release_id }}.tar* + body_path: .detailed_changes diff --git a/.gitignore b/.gitignore index 4647fc3..b26cd36 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ # This file is based on The Common .gitignore Templates # https://github.com/the-common/gitignore-templates # -# Copyright 2022 林博仁(Buo-ren, Lin) +# Copyright 2022 林博仁(Buo-ren, Lin) # SPDX-License-Identifier: CC-BY-SA-4.0 # Don't track regular Unix hidden files @@ -86,5 +86,5 @@ # Don't track GNU gettext machine-readable message catalogs *.mo -# Don't track continuous intregration virtual environments +# Don't track continuous integration virtual environments /continuous-integration/venv/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ef7c83b..ff1cf7a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,48 +5,62 @@ # * `.gitlab-ci.yml` keyword reference | GitLab # https://docs.gitlab.com/ee/ci/yaml/ # -# Copyright 2023 林博仁(Buo-ren, Lin) +# Copyright 2024 林博仁(Buo-ren Lin) # SPDX-License-Identifier: CC-BY-SA-4.0 -generate-build-artifacts: - stage: build - needs: [] - image: ubuntu:22.04 - artifacts: - paths: - - ${CI_PROJECT_NAME}-*.tar* - script: - - ./continuous-integration/generate-build-artifacts.install-system-deps.sh - - ./continuous-integration/generate-build-artifacts.sh - -static-analysis: +do-static-analysis: stage: test + rules: + - if: $CI_COMMIT_TAG == null needs: [] image: ubuntu:22.04 variables: PIP_CACHE_DIR: ${CI_PROJECT_DIR}/.cache/pip PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit + SHELLCHECK_DIR: ${CI_PROJECT_DIR}/.cache/shellcheck-stable cache: # Enable per-job and per-branch caching key: $CI_JOB_NAME-$CI_COMMIT_REF_SLUG paths: - ${PIP_CACHE_DIR} - ${PRE_COMMIT_HOME} + - ${SHELLCHECK_DIR} script: - ./continuous-integration/do-static-analysis.install-system-deps.sh - ./continuous-integration/do-static-analysis.sh -create-release: +generate-build-artifacts: + stage: build + rules: + - if: $CI_COMMIT_TAG + needs: [] + image: ubuntu:22.04 + artifacts: + paths: + - ${CI_PROJECT_NAME}-*.tar* + script: + - ./continuous-integration/generate-build-artifacts.install-system-deps.sh + - ./continuous-integration/generate-build-artifacts.sh + +upload-release-assets: stage: deploy + rules: + - if: $CI_COMMIT_TAG needs: - generate-build-artifacts - image: registry.gitlab.com/gitlab-org/release-cli:latest + image: curlimages/curl:latest + script: + - ./continuous-integration/upload-gitlab-generic-packages.sh + +create-release: + stage: deploy rules: - if: $CI_COMMIT_TAG - # Workaround for gitlab-org/gitlab#223856 + needs: + - generate-build-artifacts + - upload-release-assets + image: registry.gitlab.com/gitlab-org/release-cli:latest script: - - echo 'Creating release...' - release: - tag_name: $CI_COMMIT_TAG - description: 'T.B.A.' - name: $CI_PROJECT_TITLE $CI_COMMIT_TAG + - apk add bash git + - ./continuous-integration/generate-release-description.sh + - ./continuous-integration/create-gitlab-release.sh diff --git a/.gitmodules b/.gitmodules index b130482..8b176a4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,5 +5,5 @@ # * Git - gitmodules Documentation # https://git-scm.com/docs/gitmodules # -# Copyright 2021 林博仁(Buo-ren, Lin) +# Copyright 2024 林博仁(Buo-ren Lin) # SPDX-License-Identifier: CC-BY-SA-4.0 diff --git a/.markdownlint.yml b/.markdownlint.yml index 800a1b2..37df8c7 100644 --- a/.markdownlint.yml +++ b/.markdownlint.yml @@ -1,47 +1,46 @@ # Markdownlint(Node.js variant) configuration file # https://github.com/igorshubovych/markdownlint-cli#configuration # -# This file is based on The Common Markdownlint(Node.js variant) Configuration Templates project -# https://github.com/Lin-Buo-Ren/common-markdownlint-nodejs-config-templates +# This file is based on The Common Markdownlint(Node.js variant) +# Configuration Templates project +# https://github.com/the-common/markdownlint-nodejs-config-templates # -# Copyright 2021 林博仁(Buo-ren, Lin) +# Copyright 2024 林博仁(Buo-ren Lin) # SPDX-License-Identifier: CC-BY-SA-4.0 # Inherit Markdownlint rules default: True -# Only allow consistent un-ordered list bullet style(allow alternations in sub-levels) -MD004: +# Only allow consistent un-ordered list bullet style(allow alternations +# in sub-levels) +ul-style: style: sublist # Only allow 4 spaces as indentation of lists -MD007: +ul-indent: indent: 4 # Only allow 2 spaces as linebreak sequence -MD009: +no-trailing-spaces: br_spaces: 2 # Disable line length limitation(not suitable with CJK context) -MD013: False +line-length: False # Allow missing padding blank line between the heading markup and the context -MD022: False +blanks-around-headings: False # Allow duplicated non-sibling heading text -MD024: +no-duplicate-heading: siblings_only: True # Allow missing padding blank line between a list and its context -MD032: False +blanks-around-lists: False # Allow using raw HTML markups as workarounds of deficiencies of Markdown -MD033: False - -# 佔位字是要被替換掉的,為此規則之特例 -MD036: false +no-inline-html: False # Allow using YAML front matter, while not require the definition of the # `title` property -MD041: +first-line-h1: front_matter_title: '.*' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 266efa5..4bf1d9c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ # This file is based on The Common Pre-commit Framework Configuration Template # https://github.com/Lin-Buo-Ren/common-precommit-config-template # -# Copyright 2021 林博仁(Buo-ren, Lin) +# Copyright 2021 林博仁(Buo-ren, Lin) # SPDX-License-Identifier: CC-BY-SA-4.0 repos: @@ -35,20 +35,41 @@ repos: # Check Markdown documents with Markdownlint(Node.js variant) # https://github.com/DavidAnson/markdownlint - repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.23.2 + rev: v0.34.0 hooks: - id: markdownlint # Check REUSE compliance # https://reuse.software/ - repo: https://github.com/fsfe/reuse-tool - rev: v1.0.0 + rev: v4.0.3 hooks: - id: reuse + # Check shell scripts with ShellCheck + # NOTE: ShellCheck must be available in the command search PATHs + # https://www.shellcheck.net/ + # https://github.com/jumanjihouse/pre-commit-hooks#shellcheck + - repo: https://github.com/jumanjihouse/pre-commit-hooks + rev: 3.0.0 + hooks: + - id: shellcheck + # Check YAML files # https://github.com/adrienverge/yamllint - repo: https://github.com/adrienverge/yamllint rev: v1.30.0 hooks: - id: yamllint + + # Check EditorConfig style compliance + # https://github.com/editorconfig-checker/editorconfig-checker.python + - repo: https://github.com/editorconfig-checker/editorconfig-checker.python + rev: 2.7.3 + hooks: + - id: editorconfig-checker + alias: ec + exclude: | + (?ix)^( + LICENSES/.* + )$ diff --git a/.reuse/dep5 b/.reuse/dep5 deleted file mode 100644 index 419367c..0000000 --- a/.reuse/dep5 +++ /dev/null @@ -1,10 +0,0 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: The Common Project Template -Upstream-Contact: Issues · The Common / The Common Project Template · GitLab -Source: https://gitlab.com/the-common/project-template - -Files: - *README.md - */README.md -Copyright: 2021 林博仁(Buo-ren, Lin) -License: CC-BY-SA-4.0+ diff --git a/.yamllint b/.yamllint index 243251a..2c9c514 100644 --- a/.yamllint +++ b/.yamllint @@ -12,7 +12,7 @@ # This file is based on The Unofficial yamllint Configuration Templates # https://github.com/Lin-Buo-Ren/yamllint-configuration-templates # -# Copyright 2021 林博仁(Buo-ren, Lin) +# Copyright 2021 林博仁(Buo-ren, Lin) # SPDX-License-Identifier: CC-BY-SA-4.0 rules: # Use this rule to control the number of spaces inside braces (`{` and `}`). diff --git a/README.md b/README.md index 057c997..06119ce 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,21 @@ -# The Common Project Template +# The common Ansible role template -A common project template to start of, batteries included. +A sensible template for initializing a new Ansible role project. - + +[![The GitLab CI pipeline status badge of the project's `main` branch](https://gitlab.com/the-common/ansible-role-template/badges/main/pipeline.svg?ignore_skipped=true "Click here to check out the comprehensive status of the GitLab CI pipelines")](https://gitlab.com/the-common/ansible-role-template/-/pipelines) [![GitHub Actions workflow status badge](https://github.com/the-common/ansible-role-template/actions/workflows/check-potential-problems.yml/badge.svg "GitHub Actions workflow status")](https://github.com/the-common/ansible-role-template/actions/workflows/check-potential-problems.yml) [![pre-commit enabled badge](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white "This project uses pre-commit to check potential problems")](https://pre-commit.com/) [![REUSE Specification compliance badge](https://api.reuse.software/badge/gitlab.com/the-common/ansible-role-template "This project complies to the REUSE specification to decrease software licensing costs")](https://api.reuse.software/info/gitlab.com/the-common/ansible-role-template) -[![The GitLab CI pipeline status badge of the project's `main` branch](https://gitlab.com/the-common/project-template/badges/main/pipeline.svg?ignore_skipped=true "Click here to check out the comprehensive status of the GitLab CI pipelines")](https://gitlab.com/the-common/project-template/-/pipelines) [![The "Check potential problems in the project" GitHub Actions workflow status badge](https://github.com/the-common/project-template/actions/workflows/check-potential-problems.yml/badge.svg "Click here to check out the comprehensive status of the \"Check potential problems in the project\" GitHub Actions workflow")](https://github.com/the-common/project-template/actions/workflows/check-potential-problems.yml) + ## Licensing -Unless otherwise noted, this product is licensed under [the 4.0 International version of the Creative Commons Attribution-ShareAlike license](https://creativecommons.org/licenses/by-sa/4.0/), or any of its more recent versions of your preference. +Unless otherwise noted(individual file's header/[REUSE.toml](REUSE.toml)), this product is licensed under [the 4.0 International version of the Creative Commons Attribution-ShareAlike license](https://creativecommons.org/licenses/by-sa/4.0/), or any of its more recent versions of your preference. + +As an exception of the previous clause, this product can be licensed otherwise under [the 2.0 version of the Apache license](https://www.apache.org/licenses/LICENSE-2.0) _if_ it is used to instantiate/refactor a project based on it rather than using it in the making of another template project. This allows relicensing the project assets to your liking in your non-template projects with only a conformance requirement of attributing this product somewhere in your project/product credits/acknowledgment/copyright notice documentation/user interface. -This work complies to the [REUSE Specification](https://reuse.software/spec/), refer [REUSE - Make licensing easy for everyone](https://reuse.software/) for info regarding the licensing of this product. +This work complies to [the REUSE Specification](https://reuse.software/spec/), refer the [REUSE - Make licensing easy for everyone](https://reuse.software/) website for info regarding the licensing of this product. diff --git a/REUSE.toml b/REUSE.toml new file mode 100644 index 0000000..99b8232 --- /dev/null +++ b/REUSE.toml @@ -0,0 +1,19 @@ +# REUSE licensing information association file +# +# References: +# +# * REUSE.toml | REUSE Specification – Version 3.2 | REUSE +# https://reuse.software/spec-3.2/#reusetoml +# +# Copyright 2024 林博仁(Buo-ren Lin) +# SPDX-License-Identifier: CC-BY-SA-4.0+ + +version = 1 + +[[annotations]] +path = [ + "*.README.md", + "**/README.md" +] +SPDX-FileCopyrightText = '2021 林博仁(Buo-ren Lin) ' +SPDX-License-Identifier = 'CC-BY-SA-4.0+' diff --git a/continuous-integration/create-gitlab-release.sh b/continuous-integration/create-gitlab-release.sh new file mode 100755 index 0000000..03820a8 --- /dev/null +++ b/continuous-integration/create-gitlab-release.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# Create GitLab project release +# +# Copyright 2024 林博仁(Buo-ren Lin) +# SPDX-License-Identifier: CC-BY-SA-4.0 + +set \ + -o errexit \ + -o nounset + +if ! test -v CI_PROJECT_ID; then + printf \ + 'Error: This program should be run under a GitLab CI environment.\n' \ + 1>&2 + exit 1 +fi + +printf \ + 'Info: Determining release version...\n' +release_version="${CI_COMMIT_TAG#v}" + +# bash - How to get script directory in POSIX sh? - Stack Overflow +# https://stackoverflow.com/questions/29832037/how-to-get-script-directory-in-posix-sh +script_dir="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)" +project_dir="${script_dir%/*}" + +printf \ + 'Info: Determining release details...\n' +detailed_changes_file="${project_dir}/.detailed_changes" +if ! test -e "${detailed_changes_file}"; then + printf \ + 'Error: The detailed changes file "%s" does not exist.\n' \ + "${detailed_changes_file}" \ + 1>&2 + exit 2 +fi + +release_cli_create_opts=( + --name "${CI_PROJECT_TITLE} ${release_version}" + --tag-name "${CI_COMMIT_TAG}" + + # WORKAROUND: Absolute path is not accepted as file input + --description "${detailed_changes_file##*/}" +) + +shopt -s nullglob +for file in "${project_dir}/${CI_PROJECT_NAME}-"*; do + filename="${file##*/}" + package_registry_url="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${CI_PROJECT_NAME}/${release_version}/${filename}" + + release_cli_create_opts+=( + --assets-link "{\"name\": \"${filename}\", \"url\": \"${package_registry_url}\"}" + ) +done + +printf \ + 'Info: Creating the GitLab release...\n' +if ! \ + release-cli create \ + "${release_cli_create_opts[@]}"; then + printf \ + 'Error: Unable to create the GitLab release.\n' \ + 1>&2 + exit 2 +fi + +printf \ + 'Info: Operation completed without errors.\n' diff --git a/continuous-integration/do-static-analysis.install-system-deps.sh b/continuous-integration/do-static-analysis.install-system-deps.sh index 3dce3c3..7f73c59 100755 --- a/continuous-integration/do-static-analysis.install-system-deps.sh +++ b/continuous-integration/do-static-analysis.install-system-deps.sh @@ -1,12 +1,35 @@ #!/usr/bin/env bash # System dependency installation logic for the static analysis program # -# Copyright 2023 林博仁(Buo-ren, Lin) +# Copyright 2024 林博仁(Buo-ren Lin) # SPDX-License-Identifier: CC-BY-SA-4.0 set \ -o errexit \ -o nounset +if test -v BASH_SOURCE; then + # Convenience variables + # shellcheck disable=SC2034 + { + script="$( + realpath \ + --strip \ + "${BASH_SOURCE[0]}" + )" + script_dir="${script%/*}" + script_filename="${script##*/}" + script_name="${script_filename%%.*}" + } +fi + +trap_exit(){ + if test -v temp_dir \ + && test -e "${temp_dir}"; then + rm -rf "${temp_dir}" + fi +} +trap trap_exit EXIT + if test "${EUID}" -ne 0; then printf \ 'Error: This program should be run as the superuser(root) user.\n' \ @@ -14,6 +37,29 @@ if test "${EUID}" -ne 0; then exit 1 fi +project_dir="$(dirname "${script_dir}")" +cache_dir="${project_dir}/.cache" + +if ! test -e "${cache_dir}"; then + install_opts=( + --directory + ) + if test -v SUDO_USER; then + # Configure same user as the running environment to avoid access + # problems afterwards + install_opts+=( + --owner "${SUDO_USER}" + --group "${SUDO_GID}" + ) + fi + if ! install "${install_opts[@]}" "${cache_dir}"; then + printf \ + 'Error: Unable to create the cache directory.\n' \ + 1>&2 + exit 2 + fi +fi + apt_archive_cache_mtime_epoch="$( stat \ --format=%Y \ @@ -35,24 +81,24 @@ fi # Silence warnings regarding unavailable debconf frontends export DEBIAN_FRONTEND=noninteractive -if ! test -v CI; then - base_runtime_dependency_pkgs=( - wget - ) - if ! dpkg -s "${base_runtime_dependency_pkgs[@]}" &>/dev/null; then +base_runtime_dependency_pkgs=( + wget +) +if ! dpkg -s "${base_runtime_dependency_pkgs[@]}" &>/dev/null; then + printf \ + 'Info: Installing base runtime dependency packages...\n' + if ! \ + apt-get install \ + -y \ + "${base_runtime_dependency_pkgs[@]}"; then printf \ - 'Info: Installing base runtime dependency packages...\n' - if ! \ - apt-get install \ - -y \ - "${base_runtime_dependency_pkgs[@]}"; then - printf \ - 'Error: Unable to install the base runtime dependency packages.\n' \ - 1>&2 - exit 2 - fi + 'Error: Unable to install the base runtime dependency packages.\n' \ + 1>&2 + exit 2 fi +fi +if ! test -v CI; then printf \ 'Info: Detecting local region code...\n' wget_opts=( @@ -136,11 +182,18 @@ if ! test -v CI; then fi runtime_dependency_pkgs=( + # For matching the ShellCheck version string + grep + git python3-minimal python3-pip python3-venv + + # For extracting prebuilt ShellCheck software archive + tar + xz-utils ) if ! dpkg -s "${runtime_dependency_pkgs[@]}" &>/dev/null; then printf \ @@ -154,5 +207,128 @@ if ! dpkg -s "${runtime_dependency_pkgs[@]}" &>/dev/null; then fi fi +shellcheck_dir="${cache_dir}/shellcheck-stable" + +if ! test -e "${shellcheck_dir}/shellcheck"; then + printf \ + "Info: Determining the host machine's hardware architecture...\\n" + if ! arch="$(arch)"; then + printf \ + "Error: Unable to determine the host machine's hardware architecture.\\n" \ + 1>&2 + exit 1 + fi + + printf \ + 'Info: Checking ShellCheck architecure availability...\n' + case "${arch}" in + x86_64|armv6hf|aarch64) + # Assuming the ShellCheck architecture is the same, which + # is probably incorrect... + shellcheck_arch="${arch}" + ;; + *) + printf \ + 'Error: Unsupported ShellCheck architecture "%s".\n' \ + "${arch}" \ + 1>&2 + exit 1 + ;; + esac + + printf \ + 'Info: Determining the ShellCheck prebuilt archive details...\n' + prebuilt_shellcheck_archive_url="https://github.com/koalaman/shellcheck/releases/download/stable/shellcheck-stable.linux.${shellcheck_arch}.tar.xz" + prebuilt_shellcheck_archive_filename="${prebuilt_shellcheck_archive_url##*/}" + + printf \ + 'Info: Creating the temporary directory for storing downloaded files...\n' + mktemp_opts=( + --directory + --tmpdir + ) + if ! temp_dir="$( + mktemp \ + "${mktemp_opts[@]}" \ + "${script_name}.XXXXXX" + )"; then + printf \ + 'Error: Unable to create the temporary directory for storing downloaded files.\n' \ + 1>&2 + exit 2 + fi + + printf \ + 'Info: Downloading the prebuilt ShellCheck software archive...\n' + downloaded_prebuilt_shellcheck_archive="${temp_dir}/${prebuilt_shellcheck_archive_filename}" + wget_opts=( + --output-document "${downloaded_prebuilt_shellcheck_archive}" + ) + if ! \ + wget \ + "${wget_opts[@]}" \ + "${prebuilt_shellcheck_archive_url}"; then + printf \ + 'Error: Unable to download the prebuilt ShellCheck software archive...\n' \ + 1>&2 + exit 2 + fi + + printf \ + 'Info: Extracting the prebuilt ShellCheck software archive...\n' + tar_opts=( + --extract + --verbose + --directory="${cache_dir}" + --file="${downloaded_prebuilt_shellcheck_archive}" + ) + if test -v SUDO_USER; then + # Configure same user as the running environment to avoid access + # problems afterwards + tar_opts+=( + --owner="${SUDO_USER}" + --group="${SUDO_GID}" + ) + fi + if ! tar "${tar_opts[@]}"; then + printf \ + 'Error: Unable to extract the prebuilt ShellCheck software archive.\n' \ + 1>&2 + exit 2 + fi +fi + +printf \ + 'Info: Setting up the command search PATHs so that the locally installed shellcheck command can be located...\n' +PATH="${shellcheck_dir}:${PATH}" + +printf \ + 'Info: Querying the ShellCheck version...\n' +if ! shellcheck_version_raw="$(shellcheck --version)"; then + printf \ + 'Error: Unable to query the ShellCheck version.\n' \ + 1>&2 + exit 2 +fi + +grep_opts=( + --perl-regexp + --only-matching +) +if ! shellcheck_version="$( + grep \ + "${grep_opts[@]}" \ + '(?<=version: ).*' \ + <<<"${shellcheck_version_raw}" + )"; then + printf \ + 'Error: Unable to parse out the ShellCheck version string.\n' \ + 1>&2 +fi + +printf \ + 'Info: ShellCheck version is "%s".\n' \ + "${shellcheck_version}" + printf \ 'Info: Operation completed without errors.\n' diff --git a/continuous-integration/do-static-analysis.sh b/continuous-integration/do-static-analysis.sh index 3424661..1711b25 100755 --- a/continuous-integration/do-static-analysis.sh +++ b/continuous-integration/do-static-analysis.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # Check potential problems in the project -# Copyright 2023 林博仁(Buo-ren, Lin) +# Copyright 2024 林博仁(Buo-ren Lin) # SPDX-License-Identifier: CC-BY-SA-4.0 set \ -o errexit \ @@ -19,6 +19,8 @@ if ! script="$( fi script_dir="${script%/*}" +project_dir="$(dirname "${script_dir}")" +cache_dir="${project_dir}/.cache" if ! test -e "${script_dir}/venv"; then printf \ @@ -53,6 +55,10 @@ if ! pip show pre-commit &>/dev/null; then fi fi +printf \ + 'Info: Setting up the command search PATHs so that the installed shellcheck command can be located...\n' +PATH="${cache_dir}/shellcheck-stable:${PATH}" + printf \ 'Info: Running pre-commit...\n' if ! \ diff --git a/continuous-integration/generate-build-artifacts.install-system-deps.sh b/continuous-integration/generate-build-artifacts.install-system-deps.sh index 9122d7f..da29dc5 100755 --- a/continuous-integration/generate-build-artifacts.install-system-deps.sh +++ b/continuous-integration/generate-build-artifacts.install-system-deps.sh @@ -2,7 +2,7 @@ # Install system dependencies required for generating the project # build artifacts # -# Copyright 2023 林博仁(Buo-ren, Lin) +# Copyright 2024 林博仁(Buo-ren Lin) # SPDX-License-Identifier: CC-BY-SA-4.0 set \ @@ -14,12 +14,12 @@ required_commands=( realpath ) flag_dependency_check_failed=false -for required_command in "${required_commands[@]}"; do - if ! command -v "${required_command}" >/dev/null; then +for command in "${required_commands[@]}"; do + if ! command -v "${command}" >/dev/null; then flag_dependency_check_failed=true printf \ 'Error: Unable to locate the "%s" command in the command search PATHs.\n' \ - "${required_command}" \ + "${command}" \ 1>&2 fi done diff --git a/continuous-integration/generate-build-artifacts.sh b/continuous-integration/generate-build-artifacts.sh index e55f37b..f9f9c0e 100755 --- a/continuous-integration/generate-build-artifacts.sh +++ b/continuous-integration/generate-build-artifacts.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Generate the project build artifacts # -# Copyright 2023 林博仁(Buo-ren, Lin) +# Copyright 2024 林博仁(Buo-ren Lin) # SPDX-License-Identifier: CC-BY-SA-4.0 set \ -o errexit \ @@ -61,7 +61,7 @@ git_describe_opts=( --dirty --tags ) -if ! project_version="$( +if ! version_describe="$( git describe \ "${git_describe_opts[@]}" )"; then @@ -70,19 +70,21 @@ if ! project_version="$( 1>&2 exit 2 fi +project_version="${version_describe#v}" printf \ 'Info: Generating the project archive...\n' -project_id="${CI_PROJECT_NAME}-${project_version}" +project_id="${CI_PROJECT_NAME:-"${project_id}"}" +release_id="${project_id}-${project_version}" git_archive_all_opts=( # Add an additional layer of folder for containing the archive # contents - --prefix="${project_id}/" + --prefix="${release_id}/" ) if ! \ git-archive-all \ "${git_archive_all_opts[@]}" \ - "${project_id}.tar.gz"; then + "${release_id}.tar.gz"; then printf \ 'Error: Unable to generate the project archive.\n' \ 1>&2 diff --git a/continuous-integration/generate-release-description.sh b/continuous-integration/generate-release-description.sh new file mode 100755 index 0000000..fa58434 --- /dev/null +++ b/continuous-integration/generate-release-description.sh @@ -0,0 +1,141 @@ +#!/usr/bin/env bash +# Generate release description text to explain the changes between the +# previous release +# +# Copyright 2024 林博仁(Buo-ren Lin) +# SPDX-License-Identifier: CC-BY-SA-4.0 + +set \ + -o errexit \ + -o nounset + +if ! test -v CI; then + printf \ + 'Error: This program should be run under a GitLab CI/GitHub Actions environment.\n' \ + 1>&2 + exit 1 +fi + +# bash - How to get script directory in POSIX sh? - Stack Overflow +# https://stackoverflow.com/questions/29832037/how-to-get-script-directory-in-posix-sh +script_dir="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)" +project_dir="${script_dir%/*}" + +printf 'Info: Querying the list of the release tag(s)...\n' +if ! git_tag_list="$(git tag --list 'v*')"; then + printf \ + 'Error: Unable to query the list of the release tag(s).\n' \ + 1>&2 + exit 2 +fi + +printf 'Info: Counting the release tags...\n' +if ! git_tag_count="$(wc -l <<<"${git_tag_list}")"; then + printf \ + 'Error: Unable to count the release tags.\n' \ + 1>&2 + exit 2 +fi + +detailed_changes_markup="## Detailed changes"$'\n\n' + +if test -v CI_COMMIT_TAG; then + release_tag="${CI_COMMIT_TAG}" +fi + +if test "${git_tag_count}" -eq 1; then + printf \ + 'Info: Only one release tag was detected, generating the release description text from the very beginning to the "%s" release tag...\n' \ + "${release_tag}" + if ! detailed_changes_markup+="$( + git log \ + "${git_log_opts[@]}" \ + "${release_tag}" + )"; then + printf \ + 'Error: Unable to generate the release description text from Git.\n' \ + 1>&2 + exit 2 + fi +else + printf \ + 'Info: Multiple release tags were detected, determining the previous release tag...\n' + printf \ + 'Info: Version-sorting the release tag list...\n' + if ! sorted_git_tag_list="$( + sort \ + -V \ + <<<"${git_tag_list}" + )"; then + printf \ + 'Error: Unable to version-sort the release tag list.\n' \ + 1>&2 + exit 2 + fi + + printf \ + 'Info: Filtering out the two latest release tags from the release tag list...\n' + if ! latest_two_git_tags="$( + tail \ + -n 2 \ + <<<"${sorted_git_tag_list}" + )"; then + printf \ + 'Error: Unable to filter out the two latest release tags from the release tag list.\n' \ + 1>&2 + exit 2 + fi + + printf \ + 'Info: Filtering out the previous release tag from the two latest release tags...\n' + if ! previous_git_tag="$( + head \ + -n 1 \ + <<<"${latest_two_git_tags}" + )"; then + printf \ + 'Error: Unable to filter out the previous release tag from the two latest release tags.\n' \ + 1>&2 + exit 2 + fi + + printf \ + 'Info: Generating the release description text from the previous release tag(%s) to the current release tag(%s)...\n' \ + "${previous_git_tag}" \ + "${release_tag}" \ + 1>&2 + git_log_opts=( + --format='format:* %s (%h) - %an' + ) + if ! detailed_changes_markup+="$( + git log \ + "${git_log_opts[@]}" \ + "${previous_git_tag}..${release_tag}" + )"; then + printf \ + 'Error: Unable to generate the release description text from the previous release tag(%s) to the current release tag(%s).\n' \ + "${previous_git_tag}" \ + "${release_tag}" \ + 1>&2 + exit 2 + fi +fi + +detailed_changes_file="${project_dir}/.detailed_changes" +printf \ + 'Info: Writing the detailed changes markup to the "%s" file...\n' \ + "${detailed_changes_file}" +if ! \ + printf \ + '%s\n' \ + "${detailed_changes_markup}" \ + | tee "${detailed_changes_file}"; then + printf \ + 'Error: Unable to write the detailed changes markup to the "%s" file.\n' \ + "${detailed_changes_file}" \ + 1>&2 + exit 2 +fi + +printf \ + 'Info: Operation completed without errors.\n' diff --git a/continuous-integration/patch-github-actions-sudo-security-policy.sh b/continuous-integration/patch-github-actions-sudo-security-policy.sh new file mode 100755 index 0000000..d9b2bcc --- /dev/null +++ b/continuous-integration/patch-github-actions-sudo-security-policy.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +# Patch the sudo security policy so that the environment variables +# defined in the GitHub Actions CI environment would be inherited in +# processes run using sudo +# +# References: +# +# * Including other files from within sudoers | Sudoers Manual | Sudo +# https://www.sudo.ws/docs/man/sudoers.man/#Including_other_files_from_within_sudoers +# +# Copyright 2024 林博仁(Buo-ren Lin) +# SPDX-License-Identifier: CC-BY-SA-4.0 + +# Configure the interpreter behavior to bail out during problematic +# situations +set \ + -o errexit \ + -o nounset + +required_commands=( + install + realpath + + # For checking the validity of the sudoers file + visudo +) +flag_dependency_check_failed=false +for required_command in "${required_commands[@]}"; do + if ! command -v "${required_command}" >/dev/null; then + flag_dependency_check_failed=true + printf \ + 'Error: Unable to locate the "%s" command in the command search PATHs.\n' \ + "${required_command}" \ + 1>&2 + fi +done +if test "${flag_dependency_check_failed}" == true; then + printf \ + 'Error: Dependency check failed, please check your installation.\n' \ + 1>&2 + exit 1 +fi + +if test "${EUID}" -ne 0; then + printf \ + 'Error: This program is required to be run as the superuser(root).\n' \ + 1>&2 + exit 1 +fi + +if test -v BASH_SOURCE; then + # Convenience variables + # shellcheck disable=SC2034 + { + script="$( + realpath \ + --strip \ + "${BASH_SOURCE[0]}" + )" + script_dir="${script%/*}" + } +fi + +ci_dir="${script_dir}" +sudoers_dropin_dir="${ci_dir}/sudoers.d" + +sudoers_dropin_dir_system=/etc/sudoers.d +if ! test -e "${sudoers_dropin_dir_system}"; then + printf \ + 'Info: Creating the sudoers drop-in directory...\n' + install_opts=( + --directory + --owner=root + --group=root + --mode=0755 + --verbose + ) + if ! \ + install \ + "${install_opts[@]}" \ + "${sudoers_dropin_dir_system}"; then + printf \ + 'Error: Unable to create the sudoers drop-in directory.\n' \ + 1>&2 + exit 2 + fi +fi + +for dropin_file in "${sudoers_dropin_dir}/"*.sudoers; do + dropin_filename="${dropin_file##*/}" + + printf \ + 'Info: Validating the "%s" sudoers drop-in file...\n' \ + "${dropin_filename}" + visudo_opts=( + # Enable check-only mode + --check + + # Specify an alternate sudoers file location + --file="${dropin_file}" + + # NOTE: We don't use --quiet as it will also inhibit the syntax + # error messages, dump the stdout stream instead + #--quiet + ) + if ! visudo "${visudo_opts[@]}" >/dev/null; then + printf \ + 'Error: Syntax validation failed for the "%s" sudoers drop-in file.\n' \ + "${dropin_filename}" \ + 1>&2 + exit 2 + fi + + printf \ + 'Info: Installing the "%s" sudoers drop-in file...\n' \ + "${dropin_filename}" + + # sudo will not accept filename with the period symbol in the + # filename, strip the convenicence filename suffix + dropin_filename_without_suffix="${dropin_filename%.sudoers}" + + installed_dropin_file="${sudoers_dropin_dir_system}/${dropin_filename_without_suffix}" + install_opts=( + --owner=root + --group=root + --mode=0644 + --verbose + ) + if ! \ + install \ + "${install_opts[@]}" \ + "${dropin_file}" \ + "${installed_dropin_file}"; then + printf \ + 'Error: Unable to install the sudoers drop-in configuration file "%s".\n' \ + "${dropin_filename}" \ + 1>&2 + exit 2 + fi +done + +printf \ + 'Info: Operation completed without errors.\n' diff --git a/continuous-integration/sudoers.d/90_allow_github_actions_default_envvars.sudoers b/continuous-integration/sudoers.d/90_allow_github_actions_default_envvars.sudoers new file mode 100644 index 0000000..9ac950d --- /dev/null +++ b/continuous-integration/sudoers.d/90_allow_github_actions_default_envvars.sudoers @@ -0,0 +1,20 @@ +# Allow exposing GitHub Actions default environment variables to the invoked superuser processes +# +# References: +# +# * Command environment | Sudoers Manual | Sudo +# https://www.sudo.ws/docs/man/sudoers.man/#Command_environment +# * Default environment - variablesVariables - GitHub Docs +# https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables +# +# Copyright 2024 林博仁(Buo-ren Lin) +# SPDX-License-Identifier: CC-BY-SA-4.0 + +# Whether we are in an CI environment +Defaults env_keep += "CI" + +# Variables defined by GitHub +Defaults env_keep += "GITHUB_*" + +# Variables defined by the GitHub Action runners +Defaults env_keep += "RUNNER_*" diff --git a/continuous-integration/sudoers.d/README.md b/continuous-integration/sudoers.d/README.md new file mode 100644 index 0000000..87dffa0 --- /dev/null +++ b/continuous-integration/sudoers.d/README.md @@ -0,0 +1,8 @@ +# sudoers.d + +The drop-in configuration files for [the Sudoers configuration file](https://www.sudo.ws/docs/man/sudoers.man/) + +## References + +* [Sudoers Manual | Sudo](https://www.sudo.ws/docs/man/sudoers.man/) +* [Including other files from within sudoers | Sudoers Manual | Sudo](https://www.sudo.ws/docs/man/sudoers.man/#Including_other_files_from_within_sudoers) diff --git a/continuous-integration/upload-gitlab-generic-packages.sh b/continuous-integration/upload-gitlab-generic-packages.sh new file mode 100755 index 0000000..64c318b --- /dev/null +++ b/continuous-integration/upload-gitlab-generic-packages.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env sh +# Upload release packages as GitLab generic packages +# +# Copyright 2024 林博仁(Buo-ren Lin) +# SPDX-License-Identifier: CC-BY-SA-4.0 + +set \ + -o errexit \ + -o nounset + +if ! test CI_PROJECT_ID; then + printf \ + 'Error: This program should be run under a GitLab CI environment.\n' \ + 1>&2 + exit 1 +fi + +printf \ + 'Info: Determining release version...\n' +release_version="${CI_COMMIT_TAG#v}" + +# bash - How to get script directory in POSIX sh? - Stack Overflow +# https://stackoverflow.com/questions/29832037/how-to-get-script-directory-in-posix-sh +script_dir="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)" +project_dir="${script_dir%/*}" + +for file in "${project_dir}/${CI_PROJECT_NAME}-"*; do + if test "${file}" = "${project_dir}/${CI_PROJECT_NAME}-*"; then + # No release packages are found, avoid missing file error + break + fi + + printf \ + 'Info: Uploading the "%s" file to the GitLab generic packages registry...\n' \ + "${file}" + + filename="${file##*/}" + package_registry_url="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${CI_PROJECT_NAME}/${release_version}/${filename}" + + if ! \ + curl \ + --header "JOB-TOKEN: ${CI_JOB_TOKEN}" \ + --upload-file "${file}" \ + "${package_registry_url}"; then + printf \ + 'Error: Unable to upload the "%s" file to the GitLab generic packages registry.\n' \ + "${file}" \ + 1>&2 + exit 2 + fi +done + +printf \ + 'Info: Operation completed without errors.\n' diff --git a/real.README.md b/real.README.md deleted file mode 100644 index 85695a6..0000000 --- a/real.README.md +++ /dev/null @@ -1,15 +0,0 @@ -# _project_name_ - -_project_summary_ - -![GitHub Actions workflow status badge](https://github.com/_namespace_/_project_/actions/workflows/check-potential-problems.yml/badge.svg "GitHub Actions workflow status") [![pre-commit enabled badge](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white "This project uses pre-commit to check potential problems")](https://pre-commit.com/) [![REUSE Specification compliance badge](https://api.reuse.software/badge/github.com/_namespace_/_project_ "This project complies to the REUSE specification to decrease software licensing costs")](https://api.reuse.software/info/github.com/_namespace_/_project_) - -## Reference - -To be addressed. - -## Licensing - -Unless otherwise noted, this product is licensed under the _license_version_ version of the [_license_name_ license](_license_url_), or any of its recent versions you would prefer. - -This work complies to the [REUSE Specification](https://reuse.software/spec/), refer [REUSE - Make licensing easy for everyone](https://reuse.software/) for info regarding the licensing of this product. diff --git a/real.markdownlint.yml b/real.markdownlint.yml deleted file mode 100644 index e3c6e02..0000000 --- a/real.markdownlint.yml +++ /dev/null @@ -1,44 +0,0 @@ -# Markdownlint(Node.js variant) configuration file -# https://github.com/igorshubovych/markdownlint-cli#configuration -# -# This file is based on The Common Markdownlint(Node.js variant) Configuration Templates project -# https://github.com/Lin-Buo-Ren/common-markdownlint-nodejs-config-templates -# -# Copyright 2021 林博仁(Buo-ren, Lin) -# SPDX-License-Identifier: CC-BY-SA-4.0 - -# Inherit Markdownlint rules -default: True - -# Only allow consistent un-ordered list bullet style(allow alternations in sub-levels) -MD004: - style: sublist - -# Only allow 4 spaces as indentation of lists -MD007: - indent: 4 - -# Only allow 2 spaces as linebreak sequence -MD009: - br_spaces: 2 - -# Disable line length limitation(not suitable with CJK context) -MD013: False - -# Allow missing padding blank line between the heading markup and the context -MD022: False - -# Allow duplicated non-sibling heading text -MD024: - siblings_only: True - -# Allow missing padding blank line between a list and its context -MD032: False - -# Allow using raw HTML markups as workarounds of deficiencies of Markdown -MD033: False - -# Allow using YAML front matter, while not require the definition of the -# `title` property -MD041: - front_matter_title: '.*'