Skip to content

Commit 5896349

Browse files
tomkyleLeoColomb
authored andcommitted
Customizable builds: a first approach (#141)
* Partials configuration This configuration file defines which .htaccess module partials are enabled or disabled. * Added requirements Assoc. arrays require Bash4 * Read new conf file The default config file can be overridden by script parameter. * New function insert_if Wraps the former explicit calls to insert_file and insert_file_comment_out, depending of the partials configuration. * Modern shebang The assoc. partials array defined in partials.conf requires Bash4. Mac users will have it installed with homebrew, so we have to specify that interpreter. * Restored fixtures creation * Switch back to old shebang * New conf syntax Each entry consists of a "keyword" and "filename", separated by space chars. - Keyword is "title", "enable", "disable", or "omit" - Filenames refer to module partials * Conf file for fixtures The same as for partials.conf, but with modules enabled always. * Work with new conf files The create_* functions read their respective conf file line by line, ignoring blank lines and those with leading # sign. - "title" items are used for header bars - "enabled" items are, well, enabled, - "disables" items are commented out, - "omit" items do not show up in output at all Bash4 features like associative arrays (and thus, "modern" shebang") are not required any longer. * Restored output readabilty * Introduce new variables for output files * Pass output file with parameter Preparing the uniting of create_htaccess and create_htaccess_fixture, the output file is passed as second parameter to these functions. * Removed create_htaccess_fixture Being nearly identical with create_htaccess, it became surfluous since we started to pass conf and output file as parameters. * Renamed variables - partials_*: its not about the partials, it's about htaccess - fixtures_ *: it's about one single fixture * Renamed conf file Once developer maintains his own config file, it should have a self-explanatory name * Rename fixture conf file …filenames be self-explanatory * Move fixture config Stuff belonging to test domain should go into appropriate directory, hence moved into "test" * Adapt filename changes * Tweak clean function Since output files may now potentially be stored anywhere, clean function should not always remove the whole dist/ folder. – To make sure there's a dist folder in the end, mkdir call now gets the -p option to avoid “File exists" message * Local variables be declared * Improved output directory creation htaccess files are potentially stored anywhere, so mkdir should make sure there is a directory to write in. * fixture build as command with arguments reflect changes on ci build commands * improve output by printing variables * http -> https is omitted for tests * cross-origin requests is omitted for tests * remove unused global var * Changed parameter order Output file first, config second. This allows custom output locations from a default config. * New variable repo_root This makes it possible to run the script from any custom working directory. * File check: $PWD vs. repo_root First seek partials under $PWD, fall back to repo_root * Error msg when partial file can not be found * Default output location now $PWD * Default output location now $PWD * Introduce the dist folder as look-worthy place * First draft for a custom-build section * Fixed wrong variable name * Improved "custom build" docs - Tried to improve the rationale introduction to custom builds - Added jump links/deep links here and there - Clarified sentences, hopefully * Added a first test script This is very basic and tests for the four possible parameter combinations. Could somebedy help refining the assert_file_contains() function? See comments in source. * Fixed a bug in configuration file logic When user passes a custom config file which in fact does _not_ exist, the script used to silently fallback to default configuration. Now it raises an error exit. * Update title line * Mock conf for test_build.sh * Added conf keyword test The new test builds a htaccess from the new htaccess_test_build.conf file, using mock partials with basic strings. Assertions used: - Title line present, in uppercase? - "AAAAAA" present? - "BBBBBB" commented-out? - "CCCCCC" not present? How to avoid the hard-coded strings? Ideas welcome… * +Output readability * Fix macOS BSD sed issue, #140 The BSD sed on macOS does not like "--in-place", instead use _-i ""_ * Remove surfluous whitespace * Source order and readability * Rename test script Former test_build.sh tests user-defined output and configuration files, so better rename to test_userbuild * Integrate test_userbuild.sh 'npm run test' now performs user build test as well * Return correct exit code main() now returns exit/return code of create_htaccess. Useful for error exit due to invalid keywords. * Headline wording * Amend mock files Create pair of valid and invalid mock (user) configuration files * Added test for invalid keywords * Revert return values thingy, as it leads Travis to fail When the config parser meets an invalid keyword, the function should not return 1 but rather exit 1 – so's no discussion about how to exit. * Docblock and whitespace improvements * Print Summary * Bullet proofing: temp files and backup The main faunction used to remove any existing .htaccess before creating the new one. This is risky, if the build process fails somehow. 1. Now, the new .htaccess is built in a temporary directory first, and moved to its target directory on success. 2. Any existing .htaccess will be backuped with ~ suffix, rather than being overwritten. * Docs draft: update shell output example * Docs: improved wording “sentences without attribution are often better”. Yup. * Removed surfluous function * Removed recursive flag …this should protect directories from being deleted. Thx @LeoColomb * Removed surfluous function Clean is not used any longer. * Simplify things - 'rm -Rf' fails quietly, so no [ -d ... ] check necessary - Temp file preparations better be handled transparently, leveraging output. * Removed trap There's only one file written during script, which is moved to target on success. So the directory is empty anyway, no need to delete something any longer – particularly in the rare case that $temp_directory inbetween has become "/" or sort of… * Remove temp directory. A temp *file* is plenty enough. * Remove printing exit code output after create function * reduce complexity remove temp file (sorry) only use a backup file to allow a revert * lint with shellcheck * remove node dependency when building * clean up command and build output * flat build bin file
1 parent 07e1513 commit 5896349

14 files changed

+705
-289
lines changed

.travis/update-dist.sh

+1-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,5 @@ $(npm bin)/set-up-ssh --key "$encrypted_22ef8dc7aed9_key" \
1111
--iv "$encrypted_22ef8dc7aed9_iv" \
1212
--path-encrypted-key "github-deploy-key.enc" \
1313
&& $(npm bin)/commit-changes --branch "master" \
14-
--commands "npm run build" \
14+
--commands "npm run build:dist" \
1515
--commit-message "Update the generated content [skip ci]"
16-

README.md

+109-2
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,16 @@ There are a few options for getting the Apache server configs:
1616
* Download the [zip archive](https://github.com/h5bp/server-configs-apache/archive/2.15.0.zip)
1717
* Install them via [npm](https://www.npmjs.com/):
1818
`npm install --save-dev apache-server-configs`
19+
20+
Inside the **dist/** folder, you'll find a ready-to-use **.htaccess** file.
1921

2022

2123
## Usage
2224

2325
If you have access to the [main server configuration
2426
file](https://httpd.apache.org/docs/current/configuring.html#main)
25-
(usually called `httpd.conf`), you should add the logic from the
26-
[`.htaccess`](https://github.com/h5bp/server-configs-apache/blob/master/dist/.htaccess)
27+
(usually called `httpd.conf`), you should add the logic from the pre-built
28+
[`dist/.htaccess`](https://github.com/h5bp/server-configs-apache/blob/master/dist/.htaccess)
2729
file in, for example, a
2830
[`<Directory>`](https://httpd.apache.org/docs/current/mod/core.html#directory)
2931
section in the main configuration file. This is usually the recommended
@@ -56,6 +58,111 @@ use them, please check the appropriate Apache documentation:
5658
* <https://httpd.apache.org/docs/current/configuring.html>
5759
* <https://httpd.apache.org/docs/current/howto/htaccess.html>
5860

61+
## Custom .htaccess builds
62+
63+
Security, mime-type, and caching best practices evolve, and so should do your *.htaccess* file. In the past, with each new *Apache Server Configs* release it was quite tedious to find out which *.htaccess* trick was just new or only had changes in certain nuances.
64+
65+
The [**build script**](#build-script-buildsh) with its re-usable and customizable [**build configuration**](#configuration-file-htaccessconf) lets you easily update your *.htaccess* file. Each new *.htaccess* build will contain the updated *Apache Server Configs* source files, enabled or commented-out according to your settings in the *htaccess.conf* of your project root.
66+
67+
### Configuration file: *htaccess.conf*
68+
69+
Allows you to define which module to [enable](#enabling-modules) or [disable](#disabling-modules) for your project. Just copy the default [**htaccess.conf**](https://github.com/h5bp/server-configs-apache/blob/master/htaccess.conf) from this repo into your project directory. Adjust to your needs, and/or [add custom code](#adding-custom-modules) snippets you need for your project. Its syntax is straight and pretty much self-explanatory:
70+
71+
72+
```
73+
# Example Module
74+
75+
title "example module"
76+
enable "src/example-module/images.conf"
77+
enable "src/example-module/web_fonts.conf"
78+
disable "src/example-module/not-needed.conf"
79+
omit "src/example-module/not-needed-at-all.conf"
80+
81+
... more modules ...
82+
```
83+
84+
#### Disabling modules
85+
86+
For example, the *“Cross-origin web fonts”* snippet is always included in our pre-built [*.htaccess*](https://github.com/h5bp/server-configs-apache/blob/master/dist/.htaccess) file and enabled. If your project does not deal with web fonts, you can **disable** or **omit** this section:
87+
88+
This will comment out the section:
89+
90+
```
91+
disable "src/cross-origin/web_fonts.conf"
92+
```
93+
94+
…and this will exclude the section, saving lines in output:
95+
96+
```
97+
omit "src/cross-origin/web_fonts.conf"
98+
```
99+
100+
101+
102+
#### Enabling modules
103+
104+
105+
For example, the *“Forcing https://”* snippet is disabled by default, although being included in our pre-built [*.htaccess*](https://github.com/h5bp/server-configs-apache/blob/master/dist/.htaccess). To enable this snippet, change the **disable** keyword to **enable:**
106+
107+
108+
```
109+
enable "src/rewrites/rewrite_http_to_https.conf"
110+
```
111+
112+
113+
#### Adding custom modules
114+
115+
Imagine you're passing all requests to non-existing files to your favourite web framework. The according *mod_rewrite* snippet would go like this:
116+
117+
```
118+
RewriteEngine On
119+
RewriteCond %{REQUEST_FILENAME} !-f
120+
RewriteCond %{REQUEST_FILENAME} !-d
121+
RewriteRule ^ index.php [QSA,L]
122+
```
123+
124+
Store this snippet in a file, e.g. **config/framework_rewrites.conf,** and add a reference in your **htaccess.conf:**
125+
126+
127+
```
128+
# PROJECT MODULES
129+
enable "config/framework_rewrites.conf"
130+
```
131+
132+
### Build script: *build.sh*
133+
134+
Dive into your project root and call the build script from wherever you cloned the repo. Here are three examples:
135+
136+
**1. Create a default .htaccess**
137+
in current work directory. An existing **htaccess.conf** in this directory will be used; if none is present, the [**default configuration**](https://github.com/h5bp/server-configs-apache/blob/master/htaccess.conf) will apply.
138+
139+
140+
```bash
141+
$ path/to/server-configs-apache/bin/build.sh
142+
143+
# Output looks like:
144+
[✔] Build .htaccess
145+
[✔] Moved in place: './.htaccess'
146+
```
147+
148+
**2. Custom output location**
149+
Just add output path and filename as parameter. By the way, if there's an existing *.htaccess* file, the build script will create a backup.
150+
151+
```bash
152+
$ path/to/server-configs-apache/bin/build.sh htdocs/.htaccess
153+
[✔] Build .htaccess
154+
[✔] Create backup: 'htdocs/.htaccess~'
155+
[✔] Moved in place: 'htdocs/.htaccess'
156+
```
157+
158+
**3. Custom .htaccess configuration**
159+
Why not maintain your personal **~/htaccess.conf?** This example creates a *.htaccess* in current work directory, according to your favourite settings you may have stored in your `$HOME` directory:
160+
161+
```bash
162+
$ path/to/server-configs-apache/bin/build.sh ./.htaccess ~/htaccess.conf
163+
```
164+
165+
59166
## Support
60167

61168
* ### __Apache v2.2.0+__

bin/build.sh

+181
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
#!/bin/bash
2+
3+
declare htaccess_config_default="htaccess.conf";
4+
declare htaccess_output_default="./.htaccess"
5+
declare repo_root
6+
repo_root=$(dirname "$(dirname "$0")")
7+
8+
# ----------------------------------------------------------------------
9+
# | Helper functions |
10+
# ----------------------------------------------------------------------
11+
12+
create_htaccess() {
13+
local file="${1}"
14+
local config="${2}"
15+
16+
local version
17+
version=$(grep version < "${repo_root}/package.json" | \
18+
head -1 | awk -F: '{ print $2 }' | sed 's/[",\t ]//g')
19+
20+
insert_line "# Apache Server Configs v$version | MIT License" "$file"
21+
insert_line "# https://github.com/h5bp/server-configs-apache" "$file"
22+
insert_line "" "$file"
23+
insert_line "# (!) Using \`.htaccess\` files slows down Apache, therefore, if you have" "$file"
24+
insert_line "# access to the main server configuration file (which is usually called" "$file"
25+
insert_line "# \`httpd.conf\`), you should add this logic there." "$file"
26+
insert_line "#" "$file"
27+
insert_line "# https://httpd.apache.org/docs/current/howto/htaccess.html" "$file"
28+
insert_line "" "$file"
29+
30+
31+
while IFS=$" " read -r keyword filename; do
32+
33+
# Skip lines which
34+
[[ "${keyword}" =~ ^[[:space:]]*# ]] && continue
35+
[ -z "${keyword}" ] && continue
36+
37+
# Remove quotes surrounding
38+
filename="${filename%\"}"
39+
filename="${filename#\"}"
40+
41+
# Evaluate
42+
case "${keyword}" in
43+
"title")
44+
insert_header "${filename}" "$file"
45+
insert_line "" "$file"
46+
;;
47+
"enable")
48+
if [ ! -f "${filename}" ]; then
49+
filename="${repo_root}/${filename}"
50+
fi
51+
52+
if [ ! -f "${filename}" ]; then
53+
print_error ".htaccess partial '${filename}' does not exist."
54+
exit 1
55+
fi
56+
57+
insert_file "${filename}" "$file"
58+
insert_line "" "$file"
59+
;;
60+
"disable")
61+
if [ ! -f "${filename}" ]; then
62+
filename="${repo_root}/${filename}"
63+
fi
64+
65+
if [ ! -f "${filename}" ]; then
66+
print_error ".htaccess partial '${filename}' does not exist."
67+
exit 1
68+
fi
69+
70+
insert_file_comment_out "${filename}" "$file"
71+
insert_line "" "$file"
72+
;;
73+
"omit")
74+
# noop
75+
;;
76+
*)
77+
print_error "Invalid keyword '${keyword}' for entry '${filename}'"
78+
exit 1
79+
;;
80+
esac
81+
82+
done < "${config}"
83+
84+
apply_pattern "$file"
85+
}
86+
87+
insert_line() {
88+
printf "$1\\n" >> "$2"
89+
}
90+
91+
insert_file() {
92+
cat "$1" >> "$2"
93+
}
94+
95+
insert_file_comment_out() {
96+
printf "%s\\n" "$(sed -E 's/^([^#])(.+)$/# \1\2/g' < "$1")" >> "$2"
97+
}
98+
99+
insert_header() {
100+
local title
101+
title=$(printf "$1" | tr '[:lower:]' '[:upper:]')
102+
103+
insert_line "# ######################################################################" "$2"
104+
insert_line "# # $title $(insert_space "$title") #" "$2"
105+
insert_line "# ######################################################################" "$2"
106+
}
107+
108+
insert_space() {
109+
total=65
110+
occupied=$(printf "$1" | wc -c)
111+
difference=$((total - occupied))
112+
printf '%0.s ' $(seq 1 $difference)
113+
}
114+
115+
apply_pattern() {
116+
sed -e "s/%FilesMatchPattern%/$( \
117+
sed '/^#/d' < "${repo_root}/src/files_match_pattern" | \
118+
tr -s '[:space:]' '|' | \
119+
sed 's/|$//' \
120+
)/g" -i "" "$1"
121+
}
122+
123+
print_error() {
124+
# Print output in red
125+
printf "\\e[0;31m [✖] $1 $2\\e[0m\\n"
126+
}
127+
128+
print_info() {
129+
# Print output in purple
130+
printf "\\n\\e[0;35m $1\\e[0m\\n\\n"
131+
}
132+
133+
print_success() {
134+
# Print output in green
135+
printf "\\e[0;32m [✔] $1\\e[0m\\n"
136+
}
137+
138+
# ----------------------------------------------------------------------
139+
# | Main |
140+
# ----------------------------------------------------------------------
141+
142+
main() {
143+
local htaccess_output="${1}"
144+
local htaccess_config="${2}"
145+
local htaccess_output_directory
146+
htaccess_output_directory=$(dirname "${htaccess_output}")
147+
148+
if [ -z "${htaccess_config}" ]; then
149+
if [ -f "${PWD}/${htaccess_config_default}" ]; then
150+
htaccess_config="${PWD}/${htaccess_config_default}"
151+
else
152+
htaccess_config="${repo_root}/${htaccess_config_default}"
153+
fi;
154+
fi
155+
156+
if [ ! -f "${htaccess_config}" ]; then
157+
print_error "'${htaccess_config}' does not exist."
158+
exit 1
159+
fi
160+
161+
mkdir -p "${htaccess_output_directory}"
162+
163+
if [ -f "${htaccess_output}" ]; then
164+
cp "${htaccess_output}" "${htaccess_output}.old"
165+
print_info "File already exist, create backup"
166+
fi
167+
168+
rm -f "${htaccess_output}"
169+
create_htaccess "${htaccess_output}" "${htaccess_config}"
170+
171+
if [ $? ]; then # Success
172+
print_success "Build ${htaccess_output}"
173+
else
174+
print_error "Error while building ${htaccess_output}"
175+
if [ -f "${htaccess_output}.old" ]; then
176+
cp "${htaccess_output}.old" "${htaccess_output}"
177+
fi
178+
fi
179+
}
180+
181+
main "${1:-$htaccess_output_default}" "${2}"

0 commit comments

Comments
 (0)