Skip to content

Commit a9938ab

Browse files
committed
dev: Implement Docker Compose configuration file and deployment script to initialize a development environment
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) <buo.ren.lin@gmail.com>
1 parent 5320eef commit a9938ab

3 files changed

Lines changed: 263 additions & 15 deletions

File tree

dev-utils/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# dev-utils
2+
3+
Utilities for assisting project development.

dev-utils/deploy-dev-env.sh

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
#!/usr/bin/env bash
2+
# Deploy environment for developing the project
3+
#
4+
# Copyright 2025 林博仁(Buo-ren Lin) <buo.ren.lin@gmail.com>
5+
# SPDX-License-Identifier: MPL-2.0
6+
7+
set_opts=(
8+
# Terminate script execution when an unhandled error occurs
9+
-o errexit
10+
-o errtrace
11+
12+
# Terminate script execution when an unset parameter variable is
13+
# referenced
14+
-o nounset
15+
)
16+
if ! set "${set_opts[@]}"; then
17+
printf \
18+
'Error: Unable to configure the defensive interpreter behaviors.\n' \
19+
1>&2
20+
exit 1
21+
fi
22+
23+
required_commands=(
24+
apt-get
25+
dpkg
26+
realpath
27+
)
28+
flag_required_command_check_failed=false
29+
for command in "${required_commands[@]}"; do
30+
if ! command -v "${command}" >/dev/null; then
31+
flag_required_command_check_failed=true
32+
printf \
33+
'Error: This program requires the "%s" command to be available in your command search PATHs.\n' \
34+
"${command}" \
35+
1>&2
36+
fi
37+
done
38+
if test "${flag_required_command_check_failed}" == true; then
39+
printf \
40+
'Error: Required command check failed, please check your installation.\n' \
41+
1>&2
42+
exit 1
43+
fi
44+
45+
if test -v BASH_SOURCE; then
46+
# Convenience variables may not need to be referenced
47+
# shellcheck disable=SC2034
48+
{
49+
printf \
50+
'Info: Determining the absolute path of the program...\n'
51+
if ! script="$(realpath "${BASH_SOURCE[0]}")"; then
52+
printf \
53+
'Error: Unable to determine the absolute path of the program.\n' \
54+
1>&2
55+
exit 1
56+
fi
57+
script_dir="${script%/*}"
58+
script_filename="${script##*/}"
59+
script_name="${script_filename%%.*}"
60+
}
61+
fi
62+
# Convenience variables may not need to be referenced
63+
# shellcheck disable=SC2034
64+
{
65+
script_args=("${@}")
66+
}
67+
68+
trap_err(){
69+
printf -- \
70+
'%s: Error: The program prematurely terminated due to an unhandled error.\n' \
71+
"${script_name}" \
72+
1>&2
73+
exit 99
74+
}
75+
if ! trap trap_err ERR; then
76+
printf -- \
77+
'%s: Error: Unable to set the ERR trap.\n' \
78+
"${script_name}" \
79+
1>&2
80+
exit 1
81+
fi
82+
83+
if test "${EUID}" -ne 0; then
84+
printf -- \
85+
'%s: Error: This program is required to be run as the superuser(root).\n' \
86+
"${script_name}" \
87+
1>&2
88+
exit 1
89+
fi
90+
91+
if test "${#script_args[@]}" -ne 0; then
92+
printf -- \
93+
'%s: Error: This program does not accept any command-line arguments.\n' \
94+
"${script_name}" \
95+
1>&2
96+
exit 2
97+
fi
98+
99+
if ! current_epoch_time="$(printf '%(%s)T')"; then
100+
printf -- \
101+
'%s: Error: Unable to query the current epoch time.\n' \
102+
"${script_name}" \
103+
1>&2
104+
exit 2
105+
fi
106+
107+
if ! apt_list_modification_time="$(stat --format=%Y /var/lib/apt/lists)"; then
108+
printf -- \
109+
'%s: Error: Unable to query the modification of the local cache of the APT software sources.\n' \
110+
"${script_name}" \
111+
1>&2
112+
exit 2
113+
fi
114+
115+
if test "$((current_epoch_time - apt_list_modification_time))" -ge 86400; then
116+
printf -- \
117+
'%s: Info: Syncing the software source cache of the APT package manager...\n' \
118+
"${script_name}"
119+
if ! apt-get update; then
120+
printf -- \
121+
'%s: Error: Unable to sync the software source cache of the APT package manager.\n' \
122+
"${script_name}" \
123+
1>&2
124+
exit 2
125+
fi
126+
fi
127+
128+
# Avoid debconf interactive prompts on Debian-like systems
129+
export DEBIAN_FRONTEND=noninteractive
130+
131+
dev_deps_pkgs=(
132+
grunt
133+
npm
134+
)
135+
if ! dpkg --status "${dev_deps_pkgs[@]}" &>/dev/null; then
136+
printf -- \
137+
'%s: Info: Installing the project development dependencies packages...\n' \
138+
"${script_name}"
139+
aptget_install_opts=(
140+
--no-install-recommends
141+
--yes
142+
)
143+
if ! apt-get install "${aptget_install_opts[@]}" \
144+
"${dev_deps_pkgs[@]}"; then
145+
printf -- \
146+
'%s: Error: Unable to install the project development dependencies packages.\n' \
147+
"${script_name}" \
148+
1>&2
149+
exit 2
150+
fi
151+
fi
152+
153+
if test "${script_dir}" == /etc/profile.d; then
154+
# We're in the development environment container
155+
project_dir=/project
156+
else
157+
if ! project_dir="$(realpath "${script_dir}/..")"; then
158+
printf -- \
159+
'%s: Error: Unable to determine the absolute path of the project directory.\n' \
160+
"${script_name}" \
161+
1>&2
162+
exit 2
163+
fi
164+
fi
165+
166+
if ! cd "${project_dir}"; then
167+
printf -- \
168+
'%s: Error: Unable to switch the working directory to the project directory.\n' \
169+
"${script_name}" \
170+
1>&2
171+
exit 2
172+
fi
173+
174+
printf -- \
175+
'%s: Info: Installing the Node.js dependencies of the project...\n' \
176+
"${script_name}"
177+
if test -v SUDO_USER; then
178+
if ! sudo -u "${SUDO_USER}" npm install; then
179+
printf -- \
180+
'%s: Error: Unable to install the Node.js dependencies of the project.\n' \
181+
"${script_name}" \
182+
1>&2
183+
exit 2
184+
fi
185+
else
186+
if ! npm install; then
187+
printf -- \
188+
'%s: Error: Unable to install the Node.js dependencies of the project.\n' \
189+
"${script_name}" \
190+
1>&2
191+
exit 2
192+
fi
193+
fi
194+
195+
printf -- \
196+
'%s: Info: Operation completed without errors.\n' \
197+
"${script_name}"
198+
199+
if test "${#BASH_SOURCE[@]}" -gt 1; then
200+
set_opts=(
201+
+o errexit
202+
+o errtrace
203+
+o nounset
204+
)
205+
if ! set "${set_opts[@]}"; then
206+
printf -- \
207+
'%s: Error: Unable to reset the interpreter behaviors.\n' \
208+
"${script_name}" \
209+
1>&2
210+
return 2
211+
fi
212+
213+
if ! trap - ERR; then
214+
printf -- \
215+
'%s: Error: Unable to reset the ERR trap.\n' \
216+
"${script_name}" \
217+
1>&2
218+
return 2
219+
fi
220+
221+
return 0
222+
else
223+
exit 0
224+
fi

docker-compose.yml

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,38 @@
1-
version: '3.4'
2-
1+
# Docker Compose configuration file
2+
#
3+
# References:
4+
#
5+
# * Compose Specification | Compose file reference | Reference | Docker Docs
6+
# https://docs.docker.com/compose/compose-file/
7+
#
8+
# Copyright 2025 林博仁(Buo-ren Lin) <buo.ren.lin@gmail.com>
9+
# SPDX-License-Identifier: MPL-2.0
10+
name: www-moztw-org
311
services:
4-
httpd:
5-
container_name: moztw-httpd
6-
image: httpd
7-
restart: unless-stopped
8-
ports:
9-
- "8080:80" # change 8080 to whatever you want to listen to, e.g. "8888:80" will listen to port 8888
12+
# Environment for developing the project
13+
dev-environment:
14+
container_name: www-moztw-org-dev
15+
hostname: www-moztw-org-dev
16+
image: ubuntu:24.04
1017
volumes:
11-
- .:/var/www/html
12-
- ./docker-apache.conf:/usr/local/apache2/conf/httpd.conf
13-
healthcheck:
14-
test: [ "CMD", "service" ,"apache2", "status" ]
15-
interval: 5s
16-
timeout: 20s
17-
retries: 10
18+
- type: bind
19+
source: ./
20+
target: /project
21+
- type: bind
22+
source: ./dev-utils/deploy-dev-env.sh
23+
target: /etc/profile.d/20-deploy-dev-env.sh
24+
environment:
25+
# Set this environment variable to your local timezone settings for proper operation timestamp
26+
- TZ=CST-8
27+
28+
# Avoid debconf interactive prompts on Debian-like systems
29+
- DEBIAN_FRONTEND=noninteractive
30+
ports:
31+
# For serving the website
32+
- 127.0.0.1:3000:3000
33+
# Browsersync
34+
- 127.0.0.1:3001:3001
35+
init: true
36+
command: sleep infinity
37+
profiles:
38+
- dev

0 commit comments

Comments
 (0)