From a9938ab038fb01d84131d85563e52151ddf4e968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Sun, 26 Jan 2025 15:57:57 +0800 Subject: [PATCH 1/2] dev: Implement Docker Compose configuration file and deployment script to initialize a development environment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows easier access to modify and test the project locally. Run the following commands to provision and access the development environment: ```bash docker compose --profile=dev up -d docker exec -it www-moztw-org-dev bash --login ``` Signed-off-by: 林博仁(Buo-ren Lin) --- dev-utils/README.md | 3 + dev-utils/deploy-dev-env.sh | 224 ++++++++++++++++++++++++++++++++++++ docker-compose.yml | 51 +++++--- 3 files changed, 263 insertions(+), 15 deletions(-) create mode 100644 dev-utils/README.md create mode 100755 dev-utils/deploy-dev-env.sh diff --git a/dev-utils/README.md b/dev-utils/README.md new file mode 100644 index 00000000..705fa4bc --- /dev/null +++ b/dev-utils/README.md @@ -0,0 +1,3 @@ +# dev-utils + +Utilities for assisting project development. diff --git a/dev-utils/deploy-dev-env.sh b/dev-utils/deploy-dev-env.sh new file mode 100755 index 00000000..4d8cabae --- /dev/null +++ b/dev-utils/deploy-dev-env.sh @@ -0,0 +1,224 @@ +#!/usr/bin/env bash +# Deploy environment for developing the project +# +# Copyright 2025 林博仁(Buo-ren Lin) +# SPDX-License-Identifier: MPL-2.0 + +set_opts=( + # Terminate script execution when an unhandled error occurs + -o errexit + -o errtrace + + # Terminate script execution when an unset parameter variable is + # referenced + -o nounset +) +if ! set "${set_opts[@]}"; then + printf \ + 'Error: Unable to configure the defensive interpreter behaviors.\n' \ + 1>&2 + exit 1 +fi + +required_commands=( + apt-get + dpkg + realpath +) +flag_required_command_check_failed=false +for command in "${required_commands[@]}"; do + if ! command -v "${command}" >/dev/null; then + flag_required_command_check_failed=true + printf \ + 'Error: This program requires the "%s" command to be available in your command search PATHs.\n' \ + "${command}" \ + 1>&2 + fi +done +if test "${flag_required_command_check_failed}" == true; then + printf \ + 'Error: Required command check failed, please check your installation.\n' \ + 1>&2 + exit 1 +fi + +if test -v BASH_SOURCE; then + # Convenience variables may not need to be referenced + # shellcheck disable=SC2034 + { + printf \ + 'Info: Determining the absolute path of the program...\n' + if ! script="$(realpath "${BASH_SOURCE[0]}")"; then + printf \ + 'Error: Unable to determine the absolute path of the program.\n' \ + 1>&2 + exit 1 + fi + script_dir="${script%/*}" + script_filename="${script##*/}" + script_name="${script_filename%%.*}" + } +fi +# Convenience variables may not need to be referenced +# shellcheck disable=SC2034 +{ + script_args=("${@}") +} + +trap_err(){ + printf -- \ + '%s: Error: The program prematurely terminated due to an unhandled error.\n' \ + "${script_name}" \ + 1>&2 + exit 99 +} +if ! trap trap_err ERR; then + printf -- \ + '%s: Error: Unable to set the ERR trap.\n' \ + "${script_name}" \ + 1>&2 + exit 1 +fi + +if test "${EUID}" -ne 0; then + printf -- \ + '%s: Error: This program is required to be run as the superuser(root).\n' \ + "${script_name}" \ + 1>&2 + exit 1 +fi + +if test "${#script_args[@]}" -ne 0; then + printf -- \ + '%s: Error: This program does not accept any command-line arguments.\n' \ + "${script_name}" \ + 1>&2 + exit 2 +fi + +if ! current_epoch_time="$(printf '%(%s)T')"; then + printf -- \ + '%s: Error: Unable to query the current epoch time.\n' \ + "${script_name}" \ + 1>&2 + exit 2 +fi + +if ! apt_list_modification_time="$(stat --format=%Y /var/lib/apt/lists)"; then + printf -- \ + '%s: Error: Unable to query the modification of the local cache of the APT software sources.\n' \ + "${script_name}" \ + 1>&2 + exit 2 +fi + +if test "$((current_epoch_time - apt_list_modification_time))" -ge 86400; then + printf -- \ + '%s: Info: Syncing the software source cache of the APT package manager...\n' \ + "${script_name}" + if ! apt-get update; then + printf -- \ + '%s: Error: Unable to sync the software source cache of the APT package manager.\n' \ + "${script_name}" \ + 1>&2 + exit 2 + fi +fi + +# Avoid debconf interactive prompts on Debian-like systems +export DEBIAN_FRONTEND=noninteractive + +dev_deps_pkgs=( + grunt + npm +) +if ! dpkg --status "${dev_deps_pkgs[@]}" &>/dev/null; then + printf -- \ + '%s: Info: Installing the project development dependencies packages...\n' \ + "${script_name}" + aptget_install_opts=( + --no-install-recommends + --yes + ) + if ! apt-get install "${aptget_install_opts[@]}" \ + "${dev_deps_pkgs[@]}"; then + printf -- \ + '%s: Error: Unable to install the project development dependencies packages.\n' \ + "${script_name}" \ + 1>&2 + exit 2 + fi +fi + +if test "${script_dir}" == /etc/profile.d; then + # We're in the development environment container + project_dir=/project +else + if ! project_dir="$(realpath "${script_dir}/..")"; then + printf -- \ + '%s: Error: Unable to determine the absolute path of the project directory.\n' \ + "${script_name}" \ + 1>&2 + exit 2 + fi +fi + +if ! cd "${project_dir}"; then + printf -- \ + '%s: Error: Unable to switch the working directory to the project directory.\n' \ + "${script_name}" \ + 1>&2 + exit 2 +fi + +printf -- \ + '%s: Info: Installing the Node.js dependencies of the project...\n' \ + "${script_name}" +if test -v SUDO_USER; then + if ! sudo -u "${SUDO_USER}" npm install; then + printf -- \ + '%s: Error: Unable to install the Node.js dependencies of the project.\n' \ + "${script_name}" \ + 1>&2 + exit 2 + fi +else + if ! npm install; then + printf -- \ + '%s: Error: Unable to install the Node.js dependencies of the project.\n' \ + "${script_name}" \ + 1>&2 + exit 2 + fi +fi + +printf -- \ + '%s: Info: Operation completed without errors.\n' \ + "${script_name}" + +if test "${#BASH_SOURCE[@]}" -gt 1; then + set_opts=( + +o errexit + +o errtrace + +o nounset + ) + if ! set "${set_opts[@]}"; then + printf -- \ + '%s: Error: Unable to reset the interpreter behaviors.\n' \ + "${script_name}" \ + 1>&2 + return 2 + fi + + if ! trap - ERR; then + printf -- \ + '%s: Error: Unable to reset the ERR trap.\n' \ + "${script_name}" \ + 1>&2 + return 2 + fi + + return 0 +else + exit 0 +fi diff --git a/docker-compose.yml b/docker-compose.yml index e31467e9..7095b11c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,17 +1,38 @@ -version: '3.4' - +# Docker Compose configuration file +# +# References: +# +# * Compose Specification | Compose file reference | Reference | Docker Docs +# https://docs.docker.com/compose/compose-file/ +# +# Copyright 2025 林博仁(Buo-ren Lin) +# SPDX-License-Identifier: MPL-2.0 +name: www-moztw-org services: - httpd: - container_name: moztw-httpd - image: httpd - restart: unless-stopped - ports: - - "8080:80" # change 8080 to whatever you want to listen to, e.g. "8888:80" will listen to port 8888 + # Environment for developing the project + dev-environment: + container_name: www-moztw-org-dev + hostname: www-moztw-org-dev + image: ubuntu:24.04 volumes: - - .:/var/www/html - - ./docker-apache.conf:/usr/local/apache2/conf/httpd.conf - healthcheck: - test: [ "CMD", "service" ,"apache2", "status" ] - interval: 5s - timeout: 20s - retries: 10 + - type: bind + source: ./ + target: /project + - type: bind + source: ./dev-utils/deploy-dev-env.sh + target: /etc/profile.d/20-deploy-dev-env.sh + environment: + # Set this environment variable to your local timezone settings for proper operation timestamp + - TZ=CST-8 + + # Avoid debconf interactive prompts on Debian-like systems + - DEBIAN_FRONTEND=noninteractive + ports: + # For serving the website + - 127.0.0.1:3000:3000 + # Browsersync + - 127.0.0.1:3001:3001 + init: true + command: sleep infinity + profiles: + - dev From 7d471105bdfdc128246b6ae07e6797ae4f67370a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Tue, 28 Jan 2025 20:22:21 +0800 Subject: [PATCH 2/2] dev: Implement test environment to test the built website cleanly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change allow one to test the website in an clean environment by running: ``` docker compose --profile=test up -d ``` after: ``` grunt dist ``` and browse using one's web browser. Signed-off-by: 林博仁(Buo-ren Lin) --- dev-utils/nginx-default.conf | 14 ++++++++++++++ docker-compose.yml | 20 ++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 dev-utils/nginx-default.conf diff --git a/dev-utils/nginx-default.conf b/dev-utils/nginx-default.conf new file mode 100644 index 00000000..d3ff9ae0 --- /dev/null +++ b/dev-utils/nginx-default.conf @@ -0,0 +1,14 @@ +# Default website virtual host configuration of the NGINX container +server { + listen 80; + server_name localhost; + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + charset utf-8; +} diff --git a/docker-compose.yml b/docker-compose.yml index 7095b11c..9348aca4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,3 +36,23 @@ services: command: sleep infinity profiles: - dev + + # Environment for testing the project + test-environment: + container_name: www-moztw-org-test + hostname: www-moztw-org-test + image: nginx:1.27-alpine + volumes: + - type: bind + source: ./_site + target: /usr/share/nginx/html + read_only: true + - type: bind + source: ./dev-utils/nginx-default.conf + target: /etc/nginx/conf.d/default.conf + read_only: true + ports: + # For serving the website + - 8080:80 + profiles: + - test