Skip to content

Commit b373514

Browse files
committed
add: multistore command for managing Magento multi-store configurations with automated config generation and SSL support
1 parent 64f7168 commit b373514

3 files changed

Lines changed: 288 additions & 1 deletion

File tree

bin/roll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ declare ROLL_PARAMS=()
4040
declare ROLL_CMD_VERB=
4141
declare ROLL_CMD_EXEC=
4242
declare ROLL_CMD_HELP=
43-
declare ROLL_CMD_ANYARGS=(svc env db redis sync shell debug rootnotty rootshell clinotty root node npm cli copyfromcontainer copytocontainer composer grunt magento magerun backup restore restore-full duplicate magento2-init)
43+
declare ROLL_CMD_ANYARGS=(svc env db redis sync shell debug rootnotty rootshell clinotty root node npm cli copyfromcontainer copytocontainer composer grunt magento magerun backup restore restore-full duplicate magento2-init tapsync)
4444

4545
## parse first argument as command and determine validity
4646
if (( "$#" )); then

commands/multistore.cmd

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
#!/usr/bin/env bash
2+
[[ ! ${ROLL_DIR} ]] && >&2 echo -e "\033[31mThis script is not intended to be run directly!\033[0m" && exit 1
3+
4+
ROLL_ENV_PATH="$(locateEnvPath)" || exit $?
5+
loadEnvConfig "${ROLL_ENV_PATH}" || exit $?
6+
7+
STORES_JSON="${ROLL_ENV_PATH}/.roll/stores.json"
8+
ROLL_ENV_YML="${ROLL_ENV_PATH}/.roll/roll-env.yml"
9+
NGINX_MAP_DIR="${ROLL_ENV_PATH}/.roll/nginx"
10+
NGINX_MAP_FILE="${NGINX_MAP_DIR}/stores.map"
11+
12+
function show_help() {
13+
echo "Usage: roll multistore <command>"
14+
echo ""
15+
echo "Commands:"
16+
echo " init Generate configs from .roll/stores.json and sign SSL certificates"
17+
echo " refresh Regenerate configs from .roll/stores.json (no SSL signing)"
18+
echo " list Show current store configuration"
19+
echo " help Show this help message"
20+
echo ""
21+
echo "Configuration:"
22+
echo " Create .roll/stores.json with hostname to store code mapping:"
23+
echo ""
24+
echo ' {'
25+
echo ' "stores": {'
26+
echo ' "store-nl.test": "store_nl",'
27+
echo ' "store-be.test": "store_be",'
28+
echo ' "main.test": ""'
29+
echo ' },'
30+
echo ' "run_type": "store"'
31+
echo ' }'
32+
echo ""
33+
echo "Generated files:"
34+
echo " .roll/roll-env.yml - Docker Compose override for Traefik routing"
35+
echo " .roll/nginx/stores.map - Nginx hostname to store code mapping"
36+
}
37+
38+
function check_stores_json() {
39+
if [[ ! -f "${STORES_JSON}" ]]; then
40+
fatal "Missing ${STORES_JSON}. Please create it first. Run 'roll multistore help' for format."
41+
fi
42+
43+
if ! command -v jq &> /dev/null; then
44+
fatal "jq is required for parsing JSON. Please install it: brew install jq"
45+
fi
46+
}
47+
48+
function get_all_hostnames() {
49+
jq -r '.stores | keys[]' "${STORES_JSON}"
50+
}
51+
52+
function get_store_code() {
53+
local hostname="$1"
54+
jq -r --arg h "$hostname" '.stores[$h] // ""' "${STORES_JSON}"
55+
}
56+
57+
function get_run_type() {
58+
jq -r '.run_type // "store"' "${STORES_JSON}"
59+
}
60+
61+
function escape_hostname_for_regex() {
62+
echo "$1" | sed 's/\./\\\\./g'
63+
}
64+
65+
function generate_nginx_map() {
66+
info "Generating nginx store mapping..."
67+
68+
mkdir -p "${NGINX_MAP_DIR}"
69+
70+
local run_type
71+
run_type=$(get_run_type)
72+
73+
cat > "${NGINX_MAP_FILE}" <<EOF
74+
# AUTO-GENERATED by roll multistore - do not edit manually
75+
# Regenerate with: roll multistore refresh
76+
77+
map \$http_host \$mage_run_code {
78+
EOF
79+
80+
while IFS= read -r hostname; do
81+
local store_code
82+
store_code=$(get_store_code "$hostname")
83+
local escaped_hostname
84+
escaped_hostname=$(escape_hostname_for_regex "$hostname")
85+
86+
if [[ -n "$store_code" ]]; then
87+
echo " ~*^(app\\.)?${escaped_hostname}\$ ${store_code};" >> "${NGINX_MAP_FILE}"
88+
else
89+
echo " # ${hostname} - uses default store code" >> "${NGINX_MAP_FILE}"
90+
fi
91+
done < <(get_all_hostnames)
92+
93+
cat >> "${NGINX_MAP_FILE}" <<EOF
94+
default '';
95+
}
96+
97+
map \$http_host \$mage_run_type {
98+
default ${run_type};
99+
}
100+
EOF
101+
102+
success "Generated ${NGINX_MAP_FILE}"
103+
}
104+
105+
function generate_roll_env_yml() {
106+
info "Generating roll-env.yml..."
107+
108+
mkdir -p "${ROLL_ENV_PATH}/.roll"
109+
110+
# Build Traefik host rules
111+
local traefik_rules=""
112+
local first=true
113+
114+
while IFS= read -r hostname; do
115+
if [[ "$first" == "true" ]]; then
116+
first=false
117+
else
118+
traefik_rules+=" || "
119+
fi
120+
traefik_rules+="HostRegexp(\\\`{subdomain:.+}.${hostname}\\\`) || Host(\\\`${hostname}\\\`)"
121+
done < <(get_all_hostnames)
122+
123+
# Build extra_hosts entries
124+
local extra_hosts=""
125+
while IFS= read -r hostname; do
126+
extra_hosts+=" - \"${hostname}:\${TRAEFIK_ADDRESS:-0.0.0.0}\"\n"
127+
done < <(get_all_hostnames)
128+
129+
cat > "${ROLL_ENV_YML}" <<EOF
130+
# AUTO-GENERATED by roll multistore - do not edit manually
131+
# Regenerate with: roll multistore refresh
132+
133+
services:
134+
nginx:
135+
labels:
136+
- traefik.http.routers.\${ROLL_ENV_NAME}-nginx.rule=
137+
HostRegexp(\`{subdomain:.+}.\${TRAEFIK_DOMAIN}\`) || Host(\`\${TRAEFIK_DOMAIN}\`)
138+
|| ${traefik_rules}
139+
volumes:
140+
- ./.roll/nginx/stores.map:/etc/nginx/default.d/stores.map:ro
141+
142+
php-fpm:
143+
extra_hosts:
144+
$(echo -e "$extra_hosts" | sed 's/^//')
145+
php-debug:
146+
extra_hosts:
147+
$(echo -e "$extra_hosts" | sed 's/^//')
148+
EOF
149+
150+
success "Generated ${ROLL_ENV_YML}"
151+
}
152+
153+
function sign_certificates() {
154+
info "Signing SSL certificates for all hostnames..."
155+
156+
local hostnames=()
157+
while IFS= read -r hostname; do
158+
hostnames+=("$hostname")
159+
done < <(get_all_hostnames)
160+
161+
if [[ ${#hostnames[@]} -gt 0 ]]; then
162+
"${ROLL_DIR}/bin/roll" sign-certificate "${hostnames[@]}"
163+
success "SSL certificates signed for: ${hostnames[*]}"
164+
fi
165+
}
166+
167+
function list_stores() {
168+
if [[ ! -f "${STORES_JSON}" ]]; then
169+
echo "No stores.json found at ${STORES_JSON}"
170+
return
171+
fi
172+
173+
echo "Store Configuration from ${STORES_JSON}:"
174+
echo ""
175+
echo "Run Type: $(get_run_type)"
176+
echo ""
177+
printf "%-30s %-20s\n" "Hostname" "Store Code"
178+
printf "%-30s %-20s\n" "--------" "----------"
179+
180+
while IFS= read -r hostname; do
181+
local store_code
182+
store_code=$(get_store_code "$hostname")
183+
if [[ -z "$store_code" ]]; then
184+
store_code="(default)"
185+
fi
186+
printf "%-30s %-20s\n" "$hostname" "$store_code"
187+
done < <(get_all_hostnames)
188+
189+
echo ""
190+
191+
if [[ -f "${ROLL_ENV_YML}" ]]; then
192+
success "roll-env.yml: EXISTS"
193+
else
194+
warning "roll-env.yml: MISSING (run 'roll multistore init')"
195+
fi
196+
197+
if [[ -f "${NGINX_MAP_FILE}" ]]; then
198+
success "nginx/stores.map: EXISTS"
199+
else
200+
warning "nginx/stores.map: MISSING (run 'roll multistore init')"
201+
fi
202+
}
203+
204+
# Main command handler
205+
case "${ROLL_PARAMS[0]}" in
206+
init)
207+
check_stores_json
208+
generate_nginx_map
209+
generate_roll_env_yml
210+
sign_certificates
211+
echo ""
212+
success "Multistore configuration initialized!"
213+
info "Run 'roll env up' to apply the changes."
214+
;;
215+
refresh)
216+
check_stores_json
217+
generate_nginx_map
218+
generate_roll_env_yml
219+
echo ""
220+
success "Multistore configuration refreshed!"
221+
info "Run 'roll env restart nginx' to apply the changes."
222+
;;
223+
list)
224+
list_stores
225+
;;
226+
help|--help|-h|"")
227+
show_help
228+
;;
229+
*)
230+
fatal "Unknown command: ${ROLL_PARAMS[0]}. Run 'roll multistore help' for usage."
231+
;;
232+
esac

commands/multistore.help

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
Manage Magento multi-store configuration with automatic config generation.
2+
3+
Usage:
4+
roll multistore <command>
5+
6+
Commands:
7+
init Generate configs from .roll/stores.json and sign SSL certificates
8+
refresh Regenerate configs from .roll/stores.json (without re-signing SSL)
9+
list Show current store configuration and status
10+
help Show this help message
11+
12+
Configuration:
13+
Create .roll/stores.json in your project root with hostname to store code mapping:
14+
15+
{
16+
"stores": {
17+
"store-nl.test": "store_nl",
18+
"store-be.test": "store_be",
19+
"main.test": ""
20+
},
21+
"run_type": "store"
22+
}
23+
24+
- "stores": Object mapping hostnames to Magento store codes
25+
- Empty string ("") means use the default store code
26+
- "run_type": Either "store" or "website" (default: "store")
27+
28+
Generated Files:
29+
.roll/roll-env.yml Docker Compose override with:
30+
- Traefik routing rules for all hostnames
31+
- Nginx volume mount for store mapping
32+
- extra_hosts for PHP containers
33+
34+
.roll/nginx/stores.map Nginx configuration with:
35+
- $mage_run_code variable mapping
36+
- $mage_run_type variable
37+
38+
Workflow:
39+
1. Create .roll/stores.json with your store configuration
40+
2. Run: roll multistore init
41+
3. Run: roll env up
42+
43+
To add/modify stores later:
44+
1. Edit .roll/stores.json
45+
2. Run: roll multistore refresh
46+
3. Run: roll env restart nginx
47+
48+
Prerequisites:
49+
- jq must be installed (brew install jq)
50+
- Project must have .env.roll configured
51+
52+
Examples:
53+
roll multistore init # First time setup
54+
roll multistore refresh # After editing stores.json
55+
roll multistore list # Check current configuration

0 commit comments

Comments
 (0)