Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions dev-utils/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# dev-utils

Utilities for assisting project development.
224 changes: 224 additions & 0 deletions dev-utils/deploy-dev-env.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
#!/usr/bin/env bash
# Deploy environment for developing the project
#
# Copyright 2025 林博仁(Buo-ren Lin) <[email protected]>
# 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
14 changes: 14 additions & 0 deletions dev-utils/nginx-default.conf
Original file line number Diff line number Diff line change
@@ -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;
}
69 changes: 55 additions & 14 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,58 @@
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) <[email protected]>
# SPDX-License-Identifier: MPL-2.0
name: www-moztw-org
services:
httpd:
container_name: moztw-httpd
image: httpd
restart: unless-stopped
# Environment for developing the project
dev-environment:
container_name: www-moztw-org-dev
hostname: www-moztw-org-dev
image: ubuntu:24.04
volumes:
- 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:
- "8080:80" # change 8080 to whatever you want to listen to, e.g. "8888:80" will listen to port 8888
# For serving the website
- 127.0.0.1:3000:3000
# Browsersync
- 127.0.0.1:3001:3001
init: true
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:
- .:/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: ./_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