diff --git a/.editorconfig b/.editorconfig index d94dd8b..4e41e32 100644 --- a/.editorconfig +++ b/.editorconfig @@ -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/.github/workflows/check-potential-problems.yml b/.github/workflows/check-potential-problems.yml index c85e79f..018c486 100644 --- a/.github/workflows/check-potential-problems.yml +++ b/.github/workflows/check-potential-problems.yml @@ -9,7 +9,9 @@ # SPDX-License-Identifier: CC-BY-SA-4.0 name: 檢查專案中的潛在問題 on: - - push + push: + branches: + - '**' jobs: check-using-precommit: name: 使用 pre-commit 檢查專案中的潛在問題 @@ -17,6 +19,7 @@ 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: 自版控庫取出內容 uses: actions/checkout@v4 @@ -34,7 +37,19 @@ jobs: 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@v3 + 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 index fe95e42..48c3f2b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,8 +18,27 @@ jobs: name: Release product and their build aritfacts runs-on: ubuntu-20.04 steps: - - name: Checkout content from the Git repository + - 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 @@ -33,6 +52,11 @@ jobs: - 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 diff --git a/.gitignore b/.gitignore index 60348c7..7def9e9 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ # https://github.com/the-common/gitignore-templates # # Copyright 2022 林博仁(Buo-ren, Lin) <Buo.Ren.Lin@gmail.com> -# SPDX-License-Identifier: CC-BY-SA-4.0 +# SPDX-License-Identifier: CC-BY-SA-4.0 OR LicenseRef-Apache-2.0-If-Not-Used-In-Template-Projects # Don't track regular Unix hidden files .* diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4a0e67e..9fc5987 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,7 +6,7 @@ # https://docs.gitlab.com/ee/ci/yaml/ # # Copyright 2023 林博仁(Buo-ren, Lin) <Buo.Ren.Lin@gmail.com> -# SPDX-License-Identifier: CC-BY-SA-4.0 +# SPDX-License-Identifier: CC-BY-SA-4.0 OR LicenseRef-Apache-2.0-If-Not-Used-In-Template-Projects do-static-analysis: stage: test rules: @@ -16,12 +16,14 @@ do-static-analysis: 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 diff --git a/.gitmodules b/.gitmodules index b130482..9ea7f86 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,4 +6,4 @@ # https://git-scm.com/docs/gitmodules # # Copyright 2021 林博仁(Buo-ren, Lin) <Buo.Ren.Lin@gmail.com> -# SPDX-License-Identifier: CC-BY-SA-4.0 +# SPDX-License-Identifier: CC-BY-SA-4.0 OR LicenseRef-Apache-2.0-If-Not-Used-In-Template-Projects diff --git a/.markdownlint.yml b/.markdownlint.yml index 800a1b2..b5663d5 100644 --- a/.markdownlint.yml +++ b/.markdownlint.yml @@ -5,7 +5,7 @@ # https://github.com/Lin-Buo-Ren/common-markdownlint-nodejs-config-templates # # Copyright 2021 林博仁(Buo-ren, Lin) <Buo.Ren.Lin@gmail.com> -# SPDX-License-Identifier: CC-BY-SA-4.0 +# SPDX-License-Identifier: CC-BY-SA-4.0 OR LicenseRef-Apache-2.0-If-Not-Used-In-Template-Projects # Inherit Markdownlint rules default: True diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 266efa5..0608f7e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ # https://github.com/Lin-Buo-Ren/common-precommit-config-template # # Copyright 2021 林博仁(Buo-ren, Lin) <Buo.Ren.Lin@gmail.com> -# SPDX-License-Identifier: CC-BY-SA-4.0 +# SPDX-License-Identifier: CC-BY-SA-4.0 OR LicenseRef-Apache-2.0-If-Not-Used-In-Template-Projects repos: # Some out-of-the-box hooks for pre-commit @@ -42,13 +42,34 @@ repos: # Check REUSE compliance # https://reuse.software/ - repo: https://github.com/fsfe/reuse-tool - rev: v1.0.0 + rev: v3.0.2 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 index 31c9300..3b2d3d9 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -4,8 +4,8 @@ Upstream-Contact: Issues · libre-knowledge/subject-template <https://gitlab.com Source: https://gitlab.com/libre-knowledge/subject-template Files: - *README.md - */README.md - _config.yml -Copyright: Copyright 2023 自由知識協作平台貢獻者 <https://gitlab.com/libre-knowledge/libre-knowledge/-/issues> -License: CC-BY-SA-4.0 + *README.md + */README.md + _config.yml +Copyright: Copyright 2024 自由知識協作平台貢獻者 <https://gitlab.com/libre-knowledge/libre-knowledge/-/issues> +License: CC-BY-SA-4.0+ OR LicenseRef-Apache-2.0-If-Not-Used-In-Template-Projects diff --git a/.yamllint b/.yamllint index 243251a..ba05db4 100644 --- a/.yamllint +++ b/.yamllint @@ -13,7 +13,7 @@ # https://github.com/Lin-Buo-Ren/yamllint-configuration-templates # # Copyright 2021 林博仁(Buo-ren, Lin) <Buo.Ren.Lin@gmail.com> -# SPDX-License-Identifier: CC-BY-SA-4.0 +# SPDX-License-Identifier: CC-BY-SA-4.0 OR LicenseRef-Apache-2.0-If-Not-Used-In-Template-Projects rules: # Use this rule to control the number of spaces inside braces (`{` and `}`). # https://yamllint.readthedocs.io/en/stable/rules.html#module-yamllint.rules.braces diff --git a/LICENSES/LicenseRef-Apache-2.0-If-Not-Used-In-Template-Projects.txt b/LICENSES/LicenseRef-Apache-2.0-If-Not-Used-In-Template-Projects.txt new file mode 100644 index 0000000..b293d10 --- /dev/null +++ b/LICENSES/LicenseRef-Apache-2.0-If-Not-Used-In-Template-Projects.txt @@ -0,0 +1,17 @@ +# The Apache-2.0-If-Not-Used-In-Template-Projects pseudo license + +Assets that are declared as either licensed under [the 4.0 International +version of the Creative Commons Attribution-ShareAlike license](https://creativecommons.org/licenses/by-sa/4.0/) +or this pseudo license 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 +re-licensing the project assets to your liking in your non-template +projects with only a legal conformance requirement of attributing this +product somewhere in your project/product credits/acknowledgment/copyright +notice documentation/user interface. + +Note that some assets that are not declared using this pseudo license +may also allow re-licensing as the declared license does not impose such +restrictions in the first place, refer the individual license terms for +details. diff --git a/README.md b/README.md index 41fa6a9..9dee428 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ + .markdownlint.yml 1. 編輯子模組專案 [real.README.md 主題說明文件模板](real.README.md),將 `_佔位字_` 替換為適當之內容(別忘了替換 `libre-knowledge/_專案ID_`),並移除未使用之(待補)章節 1. 用 [real.markdownlint.yml Markdownlint 配置文件](real.markdownlint.yml)替換掉 [.markdownlint.yml 本專案專屬的 Markdownlint 配置文件](.markdownlint.yml) +1. 將「real.gitattributes Git 路徑屬性配置文件」更名為「.gitattributes」 1. 替換 [.reuse/dep5 REUSE DEP5 機器可讀著作權宣告文件](.reuse/dep5)文件中的 `Upstream-Name`(替換為 _主題名稱_)欄位、 `Upstream-Contact`(替換為該主題專案議題追蹤系統的網頁標題與網址)欄位跟 `Source`(替換為專案網址)欄位 1. 將 [real.README.md 主題說明文件模板](real.README.md) 替換掉 [README.md 本專案說明文件](README.md) 1. 將變更提交為新修訂版(參考提交標題: `docs: 撰寫主題說明文件雛型`) diff --git a/continuous-integration/create-gitlab-release.sh b/continuous-integration/create-gitlab-release.sh index 7b19eab..51433d6 100755 --- a/continuous-integration/create-gitlab-release.sh +++ b/continuous-integration/create-gitlab-release.sh @@ -2,7 +2,7 @@ # Create GitLab project release # # Copyright 2023 林博仁(Buo-ren, Lin) <Buo.Ren.Lin@gmail.com> -# SPDX-License-Identifier: CC-BY-SA-4.0 +# SPDX-License-Identifier: CC-BY-SA-4.0 OR LicenseRef-Apache-2.0-If-Not-Used-In-Template-Projects set \ -o errexit \ diff --git a/continuous-integration/do-static-analysis.install-system-deps.sh b/continuous-integration/do-static-analysis.install-system-deps.sh index 3dce3c3..650e0b0 100755 --- a/continuous-integration/do-static-analysis.install-system-deps.sh +++ b/continuous-integration/do-static-analysis.install-system-deps.sh @@ -2,11 +2,34 @@ # System dependency installation logic for the static analysis program # # Copyright 2023 林博仁(Buo-ren, Lin) <Buo.Ren.Lin@gmail.com> -# SPDX-License-Identifier: CC-BY-SA-4.0 +# SPDX-License-Identifier: CC-BY-SA-4.0 OR LicenseRef-Apache-2.0-If-Not-Used-In-Template-Projects 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..69ff394 100755 --- a/continuous-integration/do-static-analysis.sh +++ b/continuous-integration/do-static-analysis.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Check potential problems in the project # Copyright 2023 林博仁(Buo-ren, Lin) <Buo.Ren.Lin@gmail.com> -# SPDX-License-Identifier: CC-BY-SA-4.0 +# SPDX-License-Identifier: CC-BY-SA-4.0 OR LicenseRef-Apache-2.0-If-Not-Used-In-Template-Projects set \ -o errexit \ -o nounset @@ -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..7fbfbea 100755 --- a/continuous-integration/generate-build-artifacts.install-system-deps.sh +++ b/continuous-integration/generate-build-artifacts.install-system-deps.sh @@ -3,7 +3,7 @@ # build artifacts # # Copyright 2023 林博仁(Buo-ren, Lin) <buo.ren.lin@gmail.com> -# SPDX-License-Identifier: CC-BY-SA-4.0 +# SPDX-License-Identifier: CC-BY-SA-4.0 OR LicenseRef-Apache-2.0-If-Not-Used-In-Template-Projects set \ -o errexit \ @@ -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 7601473..67f6f11 100755 --- a/continuous-integration/generate-build-artifacts.sh +++ b/continuous-integration/generate-build-artifacts.sh @@ -2,7 +2,7 @@ # Generate the project build artifacts # # Copyright 2023 林博仁(Buo-ren, Lin) <buo.ren.lin@gmail.com> -# SPDX-License-Identifier: CC-BY-SA-4.0 +# SPDX-License-Identifier: CC-BY-SA-4.0 OR LicenseRef-Apache-2.0-If-Not-Used-In-Template-Projects set \ -o errexit \ -o nounset diff --git a/continuous-integration/generate-release-description.sh b/continuous-integration/generate-release-description.sh index 32fc7d7..45bc956 100755 --- a/continuous-integration/generate-release-description.sh +++ b/continuous-integration/generate-release-description.sh @@ -3,7 +3,7 @@ # previous release # # Copyright 2023 林博仁(Buo-ren, Lin) <Buo.Ren.Lin@gmail.com> -# SPDX-License-Identifier: CC-BY-SA-4.0 +# SPDX-License-Identifier: CC-BY-SA-4.0 OR LicenseRef-Apache-2.0-If-Not-Used-In-Template-Projects set \ -o errexit \ @@ -21,69 +21,99 @@ fi script_dir="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)" project_dir="${script_dir%/*}" -printf \ - 'Info: Determining release description...\n' -git_tag_list="$(git tag --list)" -git_tag_count="$(wc -l <<<"${git_tag_list}")" +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' -git_log_opts=( - --format='format:* %s (%h) - %an' -) 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 commit list from Git.\n' \ + '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 Git tag list.\n' \ + '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 tags from the Git tag list.\n' \ + '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 Git tags.\n' \ + '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 Git commit list between the "%s" tag and the "%s" tag.\n' \ + '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 @@ -97,9 +127,9 @@ printf \ "${detailed_changes_file}" if ! \ printf \ - '%s' \ + '%s\n' \ "${detailed_changes_markup}" \ - >"${detailed_changes_file}"; then + | tee "${detailed_changes_file}"; then printf \ 'Error: Unable to write the detailed changes markup to the "%s" file.\n' \ "${detailed_changes_file}" \ 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..5492007 --- /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 2023 林博仁(Buo-ren, Lin) <buo.ren.lin@gmail.com> +# SPDX-License-Identifier: CC-BY-SA-4.0 OR LicenseRef-Apache-2.0-If-Not-Used-In-Template-Projects + +# 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..c188aa5 --- /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 2023 林博仁(Buo-ren, Lin) <Buo.Ren.Lin@gmail.com> +# SPDX-License-Identifier: CC-BY-SA-4.0 OR LicenseRef-Apache-2.0-If-Not-Used-In-Template-Projects + +# 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 index 97e7639..635061e 100755 --- a/continuous-integration/upload-gitlab-generic-packages.sh +++ b/continuous-integration/upload-gitlab-generic-packages.sh @@ -2,7 +2,7 @@ # Upload release packages as GitLab generic packages # # Copyright 2023 林博仁(Buo-ren, Lin) <Buo.Ren.Lin@gmail.com> -# SPDX-License-Identifier: CC-BY-SA-4.0 +# SPDX-License-Identifier: CC-BY-SA-4.0 OR LicenseRef-Apache-2.0-If-Not-Used-In-Template-Projects set \ -o errexit \ @@ -25,7 +25,7 @@ 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 + if test "${file}" = "${project_dir}/${CI_PROJECT_NAME}-*"; then # No release packages are found, avoid missing file error break fi diff --git a/real.gitattributes b/real.gitattributes new file mode 100644 index 0000000..af1a06c --- /dev/null +++ b/real.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 2023 林博仁(Buo-ren, Lin) <Buo.Ren.Lin@gmail.com> +# SPDX-License-Identifier: CC-BY-SA-4.0 OR LicenseRef-Apache-2.0-If-Not-Used-In-Template-Projects + +# 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/real.markdownlint.yml b/real.markdownlint.yml index e3c6e02..fc7eb65 100644 --- a/real.markdownlint.yml +++ b/real.markdownlint.yml @@ -5,7 +5,7 @@ # https://github.com/Lin-Buo-Ren/common-markdownlint-nodejs-config-templates # # Copyright 2021 林博仁(Buo-ren, Lin) <Buo.Ren.Lin@gmail.com> -# SPDX-License-Identifier: CC-BY-SA-4.0 +# SPDX-License-Identifier: CC-BY-SA-4.0 OR LicenseRef-Apache-2.0-If-Not-Used-In-Template-Projects # Inherit Markdownlint rules default: True