From ac966827c37692612fe192e11f11e6963f1d6a60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=2C=20Lin=29?= Date: Wed, 15 Nov 2023 16:29:19 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=BC=95=E5=85=A5=201.5.5=20?= =?UTF-8?q?=E7=89=88=E7=9A=84=E4=B8=BB=E9=A1=8C=E7=AF=84=E6=9C=AC=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=EF=BC=88=E6=8C=81=E7=BA=8C=E6=95=B4=E5=90=88=E8=88=87?= =?UTF-8?q?=E4=BA=A4=E4=BB=98=E6=94=AF=E6=8F=B4=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 林博仁(Buo-ren, Lin) --- .editorconfig | 7 + .gitattributes | 18 ++ .github/workflows/README.md | 10 + .../workflows/check-potential-problems.yml | 48 +++++ .github/workflows/release.yml | 50 +++++ .gitignore | 90 ++++++++ .gitlab-ci.yml | 64 ++++++ .pre-commit-config.yaml | 4 +- .reuse/dep5 | 8 +- README.md | 3 +- .../create-gitlab-release.sh | 68 ++++++ .../do-static-analysis.install-system-deps.sh | 158 ++++++++++++++ continuous-integration/do-static-analysis.sh | 69 ++++++ ...ate-build-artifacts.install-system-deps.sh | 200 ++++++++++++++++++ .../generate-build-artifacts.sh | 95 +++++++++ .../generate-release-description.sh | 111 ++++++++++ .../upload-gitlab-generic-packages.sh | 54 +++++ 17 files changed, 1050 insertions(+), 7 deletions(-) create mode 100644 .gitattributes create mode 100644 .github/workflows/README.md create mode 100644 .github/workflows/check-potential-problems.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100755 continuous-integration/create-gitlab-release.sh create mode 100755 continuous-integration/do-static-analysis.install-system-deps.sh create mode 100755 continuous-integration/do-static-analysis.sh create mode 100755 continuous-integration/generate-build-artifacts.install-system-deps.sh create mode 100755 continuous-integration/generate-build-artifacts.sh create mode 100755 continuous-integration/generate-release-description.sh create mode 100755 continuous-integration/upload-gitlab-generic-packages.sh diff --git a/.editorconfig b/.editorconfig index e1c5105..d94dd8b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -41,6 +41,10 @@ trim_trailing_whitespace = false # Markdownlint to check instead indent_size = unset +[*.{diff,patch}] +# Trailing whitespaces are unchanged lines in patch files +trim_trailing_whitespace = false + # Vagrant configuration file [Vagrantfile] indent_size = 2 @@ -52,3 +56,6 @@ indent_size = 2 # YAML documents [*.{yml,yaml}] indent_size = 2 + +[.*.{yml,yaml}] +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d072302 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,18 @@ +# 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) +# 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 diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..8c03048 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,10 @@ +# workflows + +放置 [GitHub Actions](https://github.com/features/actions) 的工作流程(workflow)定義檔 + +## 參考資料
Reference + +* [Features • GitHub Actions](https://github.com/features/actions) + Product page +* [GitHub Actions Documentation - GitHub Docs](https://docs.github.com/en/actions) + Official documentation diff --git a/.github/workflows/check-potential-problems.yml b/.github/workflows/check-potential-problems.yml new file mode 100644 index 0000000..c85e79f --- /dev/null +++ b/.github/workflows/check-potential-problems.yml @@ -0,0 +1,48 @@ +# 用來檢查專案中的潛在問題的 GitHub Actions 工作流程(workflow)定義檔 +# +# 編輯參考: +# +# * 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) +# SPDX-License-Identifier: CC-BY-SA-4.0 +name: 檢查專案中的潛在問題 +on: + - push +jobs: + check-using-precommit: + name: 使用 pre-commit 檢查專案中的潛在問題 + runs-on: ubuntu-22.04 + env: + PIP_CACHE_DIR: ${{ github.workspace }}/.cache/pip + PRE_COMMIT_HOME: ${{ github.workspace }}/.cache/pre-commit + steps: + - name: 自版控庫取出內容 + uses: actions/checkout@v4 + + - name: Configure PyPI data cache to speed up continuous integration + uses: actions/cache@v3 + 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 + with: + key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }} + path: ${{ env.PRE_COMMIT_HOME }} + + - name: Running static analysis program + run: | + sudo ./continuous-integration/do-static-analysis.install-system-deps.sh + ./continuous-integration/do-static-analysis.sh + + - name: Send CI result notification to the Telegram channel + uses: yanzay/notify-telegram@v0.1.0 + if: always() + with: + chat: '@libre_knowledge_ci' + token: ${{ secrets.telegram_bot_api_token_ci }} + status: ${{ job.status }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..fe95e42 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,50 @@ +# 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 2023 林博仁(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: Checkout content from the Git repository + uses: actions/checkout@v4 + + - 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: 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@v0.1.15 + with: + name: ${{ env.project_id }} ${{ env.release_version }} + files: | + ${{ env.release_id }}.tar* + body_path: .detailed_changes diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4647fc3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,90 @@ +# Version Tracking Ignore Rules for Git VCS +# https://git-scm.com/docs/gitignore +# +# Exclude files not suitable for version tracking in Git +# +# This file is based on The Common .gitignore Templates +# https://github.com/the-common/gitignore-templates +# +# Copyright 2022 林博仁(Buo-ren, Lin) +# SPDX-License-Identifier: CC-BY-SA-4.0 + +# Don't track regular Unix hidden files +.* + +# Do track Git configuration files +!.git* + +# Do track EditorConfig configuration files +# https://editorconfig.org/ +!.editorconfig + +# Do track pre-commit configuration files +# https://pre-commit.com/ +!.pre-commit-config.yaml + +# Do track Markdownlint configuration files +# https://github.com/DavidAnson/markdownlint +!.markdownlint.* + +# Do track Drone CI configuration files +# https://docs.drone.io/ +!.drone.yml + +# Do track yamllint configuration files +!.yamllint + +# Do track REUSE configuration files +# https://reuse.software/ +!.reuse/ + +# Do track GitLab CI configuration file +!/.gitlab-ci.yml + +# Don't track common backup filename extensions +*~ +*.bak* +*.backup* +*.bk* +*.old* +*.orig* + +## Don't track common archive files +*.7z +*.bz2 +*.gz +*.tar* +*.xz +*.zip + +# Don't track binary image files +*.bmp +*.jpg +*.png + +# Don't track common export formats from Markdown +*.doc? +*.htm? +*.pdf + +# Don't track common program output logs +*.err +*.error +*.log +*.out +*.output + +# Don't track compiled Python code caches +*.pyc + +# Don't track Vagrant runtime directories +.vagrant/ + +# Don't track GNU gettext message catalog template +*.pot + +# Don't track GNU gettext machine-readable message catalogs +*.mo + +# Don't track continuous intregration virtual environments +/continuous-integration/venv/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..4a0e67e --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,64 @@ +# GitLab CI configuration file +# +# References: +# +# * `.gitlab-ci.yml` keyword reference | GitLab +# https://docs.gitlab.com/ee/ci/yaml/ +# +# Copyright 2023 林博仁(Buo-ren, Lin) +# SPDX-License-Identifier: CC-BY-SA-4.0 +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 + cache: + # Enable per-job and per-branch caching + key: $CI_JOB_NAME-$CI_COMMIT_REF_SLUG + paths: + - ${PIP_CACHE_DIR} + - ${PRE_COMMIT_HOME} + + script: + - ./continuous-integration/do-static-analysis.install-system-deps.sh + - ./continuous-integration/do-static-analysis.sh + +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: curlimages/curl:latest + script: + - ./continuous-integration/upload-gitlab-generic-packages.sh + +create-release: + stage: deploy + rules: + - if: $CI_COMMIT_TAG + needs: + - generate-build-artifacts + - upload-release-assets + image: registry.gitlab.com/gitlab-org/release-cli:latest + script: + - apk add bash git + - ./continuous-integration/generate-release-description.sh + - ./continuous-integration/create-gitlab-release.sh diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 622e6e4..266efa5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -42,13 +42,13 @@ repos: # Check REUSE compliance # https://reuse.software/ - repo: https://github.com/fsfe/reuse-tool - rev: v0.12.1 + rev: v1.0.0 hooks: - id: reuse # Check YAML files # https://github.com/adrienverge/yamllint - repo: https://github.com/adrienverge/yamllint - rev: v1.26.1 + rev: v1.30.0 hooks: - id: yamllint diff --git a/.reuse/dep5 b/.reuse/dep5 index ac2fdf1..778dd19 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -1,11 +1,11 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: 《自由知識協作平台》主題範本 -Upstream-Contact: Issues · libre-knowledge/subject-template -Source: https://github.com/libre-knowledge/subject-template +Upstream-Name: 《自由知識協作平台》「虛擬化」主題 +Upstream-Contact: 議題 · 自由知識協作平台 Libre Knowledge Collaboration Platform / 虛擬化 Virtualization · GitLab +Source: https://gitlab.com/libre-knowledge/virtualization Files: *README.md */README.md _config.yml -Copyright: 2022 自由知識協作平台貢獻者 +Copyright: 2023 自由知識協作平台貢獻者 License: CC-BY-SA-4.0 diff --git a/README.md b/README.md index 6cdbff8..6d9cfab 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ 將一個或多個實體資源,如硬體、作業系統、應用程式或資料儲存,透過軟體技術的手段,將其抽象化、隔離化、統合化及自動化,形成一個或多個虛擬的資源,讓使用者可以彈性地使用和管理這些虛擬化的資源,並且可以在不影響其他虛擬化資源的情況下進行管理和配置 -[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white "本專案使用 pre-commit 檢查專案中的潛在問題")](https://github.com/pre-commit/pre-commit) [![REUSE 規範遵從狀態標章](https://api.reuse.software/badge/gitlab.com/libre-knowledge/virtualization "本專案遵從 REUSE 規範降低軟體授權合規成本")](https://api.reuse.software/info/github.com/libre-knowledge/virtualization) + +[![GitLab CI 持續整合流程狀態標章](https://gitlab.com/libre-knowledge/virtualization/badges/main/pipeline.svg?ignore_skipped=true "點擊查看 GitLab CI 持續整合流程的運行狀態")](https://gitlab.com/libre-knowledge/virtualization/-/commits/main) [![「檢查專案中的潛在問題」GitHub Actions 作業流程狀態標章](https://github.com/libre-knowledge/virtualization/actions/workflows/check-potential-problems.yml/badge.svg "本專案使用 GitHub Actions 自動化檢查專案中的潛在問題")](https://github.com/libre-knowledge/virtualization/actions/workflows/check-potential-problems.yml) [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white "本專案使用 pre-commit 檢查專案中的潛在問題")](https://github.com/pre-commit/pre-commit) [![REUSE 規範遵從狀態標章](https://api.reuse.software/badge/github.com/libre-knowledge/virtualization "本專案遵從 REUSE 規範降低軟體授權合規成本")](https://api.reuse.software/info/github.com/libre-knowledge/virtualization) ## 基本概念 diff --git a/continuous-integration/create-gitlab-release.sh b/continuous-integration/create-gitlab-release.sh new file mode 100755 index 0000000..7b19eab --- /dev/null +++ b/continuous-integration/create-gitlab-release.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# Create GitLab project release +# +# Copyright 2023 林博仁(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 new file mode 100755 index 0000000..3dce3c3 --- /dev/null +++ b/continuous-integration/do-static-analysis.install-system-deps.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env bash +# System dependency installation logic for the static analysis program +# +# Copyright 2023 林博仁(Buo-ren, Lin) +# SPDX-License-Identifier: CC-BY-SA-4.0 +set \ + -o errexit \ + -o nounset + +if test "${EUID}" -ne 0; then + printf \ + 'Error: This program should be run as the superuser(root) user.\n' \ + 1>&2 + exit 1 +fi + +apt_archive_cache_mtime_epoch="$( + stat \ + --format=%Y \ + /var/cache/apt/archives +)" +current_time_epoch="$( + date +%s +)" +if test "$((current_time_epoch - apt_archive_cache_mtime_epoch))" -ge 86400; then + printf \ + 'Info: Refreshing the APT local package cache...\n' + if ! apt-get update; then + printf \ + 'Error: Unable to refresh the APT local package cache.\n' \ + 1>&2 + fi +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 + 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 + fi + + printf \ + 'Info: Detecting local region code...\n' + wget_opts=( + # Output to the standard output device + --output-document=- + + # Don't output debug messages + --quiet + ) + if ip_reverse_lookup_service_response="$( + wget \ + "${wget_opts[@]}" \ + https://ipinfo.io/json + )"; then + grep_opts=( + --perl-regexp + --only-matching + ) + if ! region_code="$( + grep \ + "${grep_opts[@]}" \ + '(?<="country": ")[[:alpha:]]+' \ + <<<"${ip_reverse_lookup_service_response}" + )"; then + printf \ + 'Warning: Unable to query the local region code, falling back to default.\n' \ + 1>&2 + region_code= + else + printf \ + 'Info: Local region code determined to be "%s"\n' \ + "${region_code}" + fi + else + printf \ + 'Warning: Unable to detect the local region code(IP address reverse lookup service not available), falling back to default.\n' \ + 1>&2 + region_code= + fi + + if test -n "${region_code}"; then + # The returned region code is capitalized, fixing it. + region_code="${region_code,,*}" + + printf \ + 'Info: Checking whether the local Ubuntu archive mirror exists...\n' + if ! \ + getent hosts \ + "${region_code}.archive.ubuntu.com" \ + >/dev/null; then + printf \ + "Warning: The local Ubuntu archive mirror doesn't seem to exist, falling back to default...\\n" + region_code= + fi + fi + + if test -n "${region_code}" \ + && ! grep -q "${region_code}.archive.u" /etc/apt/sources.list; then + printf \ + 'Info: Switching to use the local APT software repository mirror...\n' + if ! \ + sed \ + --in-place \ + "s@//archive.u@//${region_code}.archive.u@g" \ + /etc/apt/sources.list; then + printf \ + 'Error: Unable to switch to use the local APT software repository mirror.\n' \ + 1>&2 + exit 2 + fi + + printf \ + 'Info: Refreshing the local APT software archive cache...\n' + if ! apt-get update; then + printf \ + 'Error: Unable to refresh the local APT software archive cache.\n' \ + 1>&2 + exit 2 + fi + fi +fi + +runtime_dependency_pkgs=( + git + + python3-minimal + python3-pip + python3-venv +) +if ! dpkg -s "${runtime_dependency_pkgs[@]}" &>/dev/null; then + printf \ + 'Info: Installing the runtime dependency packages...\n' + if ! apt-get install -y \ + "${runtime_dependency_pkgs[@]}"; then + printf \ + 'Error: Unable to install the runtime dependency packages.\n' \ + 1>&2 + exit 2 + fi +fi + +printf \ + 'Info: Operation completed without errors.\n' diff --git a/continuous-integration/do-static-analysis.sh b/continuous-integration/do-static-analysis.sh new file mode 100755 index 0000000..3424661 --- /dev/null +++ b/continuous-integration/do-static-analysis.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# Check potential problems in the project +# Copyright 2023 林博仁(Buo-ren, Lin) +# SPDX-License-Identifier: CC-BY-SA-4.0 +set \ + -o errexit \ + -o nounset + +script="${BASH_SOURCE[0]}" +if ! script="$( + realpath \ + --strip \ + "${script}" + )"; then + printf \ + 'Error: Unable to determine the absolute path of the program.\n' \ + 1>&2 + exit 1 +fi + +script_dir="${script%/*}" + +if ! test -e "${script_dir}/venv"; then + printf \ + 'Info: Initializing the Python virtual environment...\n' + if ! python3 -m venv "${script_dir}/venv"; then + printf \ + 'Error: Unable to initialize the Python virtual environment.\n' \ + 1>&2 + exit 2 + fi +fi + +printf \ + 'Info: Activating the Python virtual environment...\n' +# Out of scope +# shellcheck source=/dev/null +if ! source "${script_dir}/venv/bin/activate"; then + printf \ + 'Error: Unable to activate the Python virtual environment.\n' \ + 1>&2 + exit 2 +fi + +printf \ + 'Info: Installing pre-commit...\n' +if ! pip show pre-commit &>/dev/null; then + if ! pip install pre-commit; then + printf \ + 'Error: Unable to install pre-commit.\n' \ + 1>&2 + exit 2 + fi +fi + +printf \ + 'Info: Running pre-commit...\n' +if ! \ + pre-commit run \ + --all-files \ + --color always; then + printf \ + 'Error: pre-commit check has failed, please verify.\n' \ + 1>&2 + exit 3 +fi + +printf \ + 'Info: Operation completed without errors.\n' diff --git a/continuous-integration/generate-build-artifacts.install-system-deps.sh b/continuous-integration/generate-build-artifacts.install-system-deps.sh new file mode 100755 index 0000000..9122d7f --- /dev/null +++ b/continuous-integration/generate-build-artifacts.install-system-deps.sh @@ -0,0 +1,200 @@ +#!/usr/bin/env bash +# Install system dependencies required for generating the project +# build artifacts +# +# Copyright 2023 林博仁(Buo-ren, Lin) +# SPDX-License-Identifier: CC-BY-SA-4.0 + +set \ + -o errexit \ + -o errtrace \ + -o nounset + +required_commands=( + realpath +) +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 +fi + +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 + +if test "${EUID}" -ne 0; then + printf \ + 'Error: This program should be run as the superuser(root) user.\n' \ + 1>&2 + exit 1 +fi + +apt_archive_cache_mtime_epoch="$( + stat \ + --format=%Y \ + /var/cache/apt/archives +)" +current_time_epoch="$( + date +%s +)" +if test "$((current_time_epoch - apt_archive_cache_mtime_epoch))" -ge 86400; then + printf \ + 'Info: Refreshing the APT local package cache...\n' + if ! apt-get update; then + printf \ + 'Error: Unable to refresh the APT local package cache.\n' \ + 1>&2 + fi +fi + +# Silence warnings regarding unavailable debconf frontends +export DEBIAN_FRONTEND=noninteractive + +if ! test -v CI; then + base_runtime_dependency_pkgs=( + curl + grep + sed + ) + 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 \ + 'Error: Unable to install the base runtime dependency packages.\n' \ + 1>&2 + exit 2 + fi + fi + + printf \ + 'Info: Detecting local region code...\n' + curl_opts=( + # Don't output debug messages + --silent + --show-error + ) + if ip_reverse_lookup_service_response="$( + curl \ + "${curl_opts[@]}" \ + https://ipinfo.io/json + )"; then + grep_opts=( + --perl-regexp + --only-matching + ) + if ! region_code="$( + grep \ + "${grep_opts[@]}" \ + '(?<="country": ")[[:alpha:]]+' \ + <<<"${ip_reverse_lookup_service_response}" + )"; then + printf \ + 'Warning: Unable to query the local region code, falling back to default.\n' \ + 1>&2 + region_code= + else + printf \ + 'Info: Local region code determined to be "%s"\n' \ + "${region_code}" + fi + else + printf \ + 'Warning: Unable to detect the local region code(IP address reverse lookup service not available), falling back to default.\n' \ + 1>&2 + region_code= + fi + + if test -n "${region_code}"; then + # The returned region code is capitalized, fixing it. + region_code="${region_code,,*}" + + printf \ + 'Info: Checking whether the local Ubuntu archive mirror exists...\n' + if ! \ + getent hosts \ + "${region_code}.archive.ubuntu.com" \ + >/dev/null; then + printf \ + "Warning: The local Ubuntu archive mirror doesn't seem to exist, falling back to default...\\n" + region_code= + fi + fi + + if test -n "${region_code}" \ + && ! grep -q "${region_code}.archive.u" /etc/apt/sources.list; then + printf \ + 'Info: Switching to use the local APT software repository mirror...\n' + if ! \ + sed \ + --in-place \ + "s@//archive.u@//${region_code}.archive.u@g" \ + /etc/apt/sources.list; then + printf \ + 'Error: Unable to switch to use the local APT software repository mirror.\n' \ + 1>&2 + exit 2 + fi + + printf \ + 'Info: Refreshing the local APT software archive cache...\n' + if ! apt-get update; then + printf \ + 'Error: Unable to refresh the local APT software archive cache.\n' \ + 1>&2 + exit 2 + fi + fi +fi + +runtime_dependency_pkgs=( + # project archive compression dependencies + #bzip2 + gzip + #xz + + git + + python3-minimal + python3-pip + python3-venv +) +if ! dpkg -s "${runtime_dependency_pkgs[@]}" &>/dev/null; then + printf \ + 'Info: Installing the runtime dependency packages...\n' + if ! apt-get install -y \ + "${runtime_dependency_pkgs[@]}"; then + printf \ + 'Error: Unable to install the runtime dependency packages.\n' \ + 1>&2 + exit 2 + fi +fi + +printf \ + 'Info: Operation completed without errors.\n' diff --git a/continuous-integration/generate-build-artifacts.sh b/continuous-integration/generate-build-artifacts.sh new file mode 100755 index 0000000..7601473 --- /dev/null +++ b/continuous-integration/generate-build-artifacts.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +# Generate the project build artifacts +# +# Copyright 2023 林博仁(Buo-ren, Lin) +# SPDX-License-Identifier: CC-BY-SA-4.0 +set \ + -o errexit \ + -o nounset + +script="${BASH_SOURCE[0]}" +if ! script="$( + realpath \ + --strip \ + "${script}" + )"; then + printf \ + 'Error: Unable to determine the absolute path of the program.\n' \ + 1>&2 + exit 1 +fi + +script_dir="${script%/*}" + +if ! test -e "${script_dir}/venv"; then + printf \ + 'Info: Initializing the Python virtual environment...\n' + if ! python3 -m venv "${script_dir}/venv"; then + printf \ + 'Error: Unable to initialize the Python virtual environment.\n' \ + 1>&2 + exit 2 + fi +fi + +printf \ + 'Info: Activating the Python virtual environment...\n' +# Out of scope +# shellcheck source=/dev/null +if ! source "${script_dir}/venv/bin/activate"; then + printf \ + 'Error: Unable to activate the Python virtual environment.\n' \ + 1>&2 + exit 2 +fi + +printf \ + 'Info: Installing git-archive-all...\n' +if ! pip show git-archive-all &>/dev/null; then + if ! pip install git-archive-all; then + printf \ + 'Error: Unable to install git-archive-all.\n' \ + 1>&2 + exit 2 + fi +fi + +printf \ + 'Info: Determining the project version...\n' +git_describe_opts=( + --always + --dirty + --tags +) +if ! version_describe="$( + git describe \ + "${git_describe_opts[@]}" + )"; then + printf \ + 'Error: Unable to determine the project version.\n' \ + 1>&2 + exit 2 +fi +project_version="${version_describe#v}" + +printf \ + 'Info: Generating the project archive...\n' +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="${release_id}/" +) +if ! \ + git-archive-all \ + "${git_archive_all_opts[@]}" \ + "${release_id}.tar.gz"; then + printf \ + 'Error: Unable to generate the project archive.\n' \ + 1>&2 + exit 2 +fi + +printf \ + 'Info: Operation completed without errors.\n' diff --git a/continuous-integration/generate-release-description.sh b/continuous-integration/generate-release-description.sh new file mode 100755 index 0000000..32fc7d7 --- /dev/null +++ b/continuous-integration/generate-release-description.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash +# Generate release description text to explain the changes between the +# previous release +# +# Copyright 2023 林博仁(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: Determining release description...\n' +git_tag_list="$(git tag --list)" +git_tag_count="$(wc -l <<<"${git_tag_list}")" + +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 + if ! detailed_changes_markup+="$( + git log \ + "${git_log_opts[@]}" \ + "${release_tag}" + )"; then + printf \ + 'Error: Unable to generate the commit list from Git.\n' \ + 1>&2 + exit 2 + fi +else + if ! sorted_git_tag_list="$( + sort \ + -V \ + <<<"${git_tag_list}" + )"; then + printf \ + 'Error: Unable to version-sort the Git tag list.\n' \ + 1>&2 + exit 2 + fi + 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' \ + 1>&2 + exit 2 + fi + 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' \ + 1>&2 + exit 2 + fi + 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' \ + "${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' \ + "${detailed_changes_markup}" \ + >"${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/upload-gitlab-generic-packages.sh b/continuous-integration/upload-gitlab-generic-packages.sh new file mode 100755 index 0000000..97e7639 --- /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 2023 林博仁(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'