-
Notifications
You must be signed in to change notification settings - Fork 29
/
shoreman.sh
executable file
·133 lines (109 loc) · 3.72 KB
/
shoreman.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#!/bin/bash
# [shoreman](https://github.com/chrismytton/shoreman) is an
# implementation of the **Procfile** format. Inspired by the original
# [foreman](http://ddollar.github.com/foreman/) tool for ruby.
# Make sure that any errors cause the script to exit immediately.
set -eo pipefail
[[ "$TRACE" ]] && set -x
# ## Usage
# Usage message that is displayed when `--help` is given as an argument.
usage() {
echo "Usage: shoreman [procfile|Procfile] [envfile|.env]"
echo "Run Procfiles using shell."
echo
echo "The shoreman script reads commands from [procfile] and starts up the"
echo "processes that it describes."
}
# ## Logging
# For logging we want to prefix each entry with the current time, as well
# as the process name. This takes two arguments, the name of the process
# with its index, and then reads data from stdin, formats it, and sends it
# to stdout.
log() {
local index="$2"
local format="%s %s\t| %s"
# We add colors when output is a terminal. `SHOREMAN_COLORS` can override it.
if [ -t 1 -o "$SHOREMAN_COLORS" == "always" ] \
&& [ "$SHOREMAN_COLORS" != "never" ]; then
# Bash colors start from 31 up to 37. We calculate what color the process
# gets based on its index.
local color="$((31 + (index % 7)))"
format="\033[0;${color}m%s %s\t|\033[0m %s"
fi
while IFS= read -r data
do
printf "$format\n" "$(date +"%H:%M:%S")" "$1" "$data"
done
}
# ## Running commands
# When a process is started, we want to keep track of its pid so we can
# `kill` it when the parent process receives a signal, and so we can `wait`
# for it to finish before exiting the parent process.
store_pid() {
pids="$pids $1"
}
# This starts a command asynchronously and stores its pid in a list for use
# later on in the script.
start_command() {
bash -c "$1" 2>&1 | log "$2" "$3" &
pid="$(jobs -p %%)"
store_pid "$pid"
}
# ## Reading the .env file
# The .env file needs to be a list of assignments like in a shell script.
# Shell-style comments are permitted.
load_env_file() {
local env_file=${1:-'.env'}
# Set a default port before loading the .env file
export PORT=${PORT:-5000}
if [[ -f "$env_file" ]]; then
export $(grep "^[^#]*=.*" "$env_file" | xargs)
fi
}
# ## Reading the Procfile
# The Procfile needs to be parsed to extract the process names and commands.
# The file is given on stdin, see the `<` at the end of this while loop.
run_procfile() {
local procfile=${1:-'Procfile'}
# We give each process an index to track its color. We start with 1,
# because it corresponds to green which is easier on the eye than red (0).
local index=1
while read line || [[ -n "$line" ]]; do
if [[ -z "$line" ]] || [[ "$line" == \#* ]]; then continue; fi
local name="${line%%:*}"
local command="${line#*:[[:space:]]}"
start_command "$command" "${name}" "$index"
echo "'${command}' started with pid $pid" | log "${name}" "$index"
index=$((index + 1))
done < "$procfile"
}
# ## Cleanup
# When a `SIGINT`, `SIGTERM` or `EXIT` is received, this action is run, killing the
# child processes. The sleep stops STDOUT from pouring over the prompt, it
# should probably go at some point.
onexit() {
echo "SIGINT received"
echo "sending SIGTERM to all processes"
kill $pids
sleep 1
}
main() {
local procfile="$1"
local env_file="$2"
# If the `--help` option is given, show the usage message and exit.
expr -- "$*" : ".*--help" >/dev/null && {
usage
exit 0
}
load_env_file "$env_file"
run_procfile "$procfile"
trap onexit INT TERM
exitcode=0
for pid in $pids; do
# Wait for the children to finish executing before exiting.
# If said children exitcode is not successful, collect it.
wait "${pid}" || exitcode=$?
done
exit $exitcode
}
main "$@"