refactor: simplify staged stack configuration

This commit is contained in:
lingkai5wu committed 2026-10-10 17:37:11 +08:00
1 parent 48ddc3e728
commit ebab7d3213
2 files changed
+65 -79

No files matched your search

+9 -21
View File
@@ -1,26 +1,14 @@
# Global maximum number of stacks executed concurrently within a group. # Maximum number of stacks executed concurrently within each stage.
# Use 0 for unlimited concurrency. # Use 0 for unlimited concurrency.
max_parallel 2 max_parallel 3
# Groups run from top to bottom for up/start/restart/pull/update. # Each declaration is an ordered stage; stacks on the same line run in parallel.
# They run from bottom to top for stop/down. # Add --parallel N before the stacks to override max_parallel for that stage.
group gateway --parallel 1 \ # Example: before --parallel 2 stack-a stack-b
nginx # Unlisted stacks run in the default stage.
before nginx
group network \ before adguardhome ddns-go
adguardhome \ after backrest
ddns-go
group services --parallel 3 \
samba \
immich \
gitea \
openlist \
homeassistant \
qbittorrent
group backup --parallel 1 \
backrest
# Excluded stacks are ignored by all bulk commands. # Excluded stacks are ignored by all bulk commands.
# exclude experimental-stack # exclude experimental-stack
+56 -58
View File
@@ -10,9 +10,10 @@ LOCK_FILE="${STACKS_LOCK_FILE:-/run/lock/stacks-manage.lock}"
DEFAULT_MAX_PARALLEL=2 DEFAULT_MAX_PARALLEL=2
CONFIG_GROUP_NAMES=() BEFORE_GROUPS=()
CONFIG_GROUP_LIMITS=() BEFORE_LIMITS=()
CONFIG_GROUP_STACKS=() AFTER_GROUPS=()
AFTER_LIMITS=()
CONFIGURED_STACKS=() CONFIGURED_STACKS=()
EXCLUDED_STACKS=() EXCLUDED_STACKS=()
@@ -21,7 +22,6 @@ MANAGED_STACKS=()
GROUP_NAMES=() GROUP_NAMES=()
GROUP_LIMITS=() GROUP_LIMITS=()
GROUP_STACKS=() GROUP_STACKS=()
UNASSIGNED_STACKS=()
RUN_SUCCESS=0 RUN_SUCCESS=0
RUN_FAILED=0 RUN_FAILED=0
@@ -52,9 +52,10 @@ Commands:
list List discovered stacks and their group/status list List discovered stacks and their group/status
Bulk execution: Bulk execution:
up/start/restart/pull/update run groups in declaration order. up/start/restart/pull/update run before, default, then after.
stop/down run groups in reverse order. stop/down run the same groups in reverse order.
Stacks within a group run in parallel up to that group's limit. Each before/after declaration creates an ordered group.
Stacks within a group run in parallel up to its configured limit.
ps/images/list keep ordered output. ps/images/list keep ordered output.
Configuration: $CONFIG_FILE Configuration: $CONFIG_FILE
@@ -88,52 +89,48 @@ validate_name() {
die "invalid $kind name: $value" die "invalid $kind name: $value"
} }
# Set the default concurrency for groups without an override. # Set the maximum concurrency within each stage.
max_parallel() { max_parallel() {
[[ $# -eq 1 ]] || die "max_parallel expects exactly one value" [[ $# -eq 1 ]] || die "max_parallel expects exactly one value"
validate_parallel_limit "$1" validate_parallel_limit "$1"
DEFAULT_MAX_PARALLEL="$1" DEFAULT_MAX_PARALLEL="$1"
} }
# Declare an ordered group and its stacks. add_stage_group() {
# Usage: group NAME [--parallel N] STACK... local groups_name="$1"
group() { local limits_name="$2"
[[ $# -ge 2 ]] || die "group expects NAME [--parallel N] STACK..." local stage="$3"
local name="$1"
local limit="" local limit=""
local stack local stack
shift local -n groups="$groups_name"
local -n limits="$limits_name"
validate_name "group" "$name" shift 3
[[ "$name" != "unassigned" ]] ||
die "group name is reserved: $name"
contains "$name" "${CONFIG_GROUP_NAMES[@]}" &&
die "group is declared more than once: $name"
if [[ "${1:-}" == "--parallel" ]]; then if [[ "${1:-}" == "--parallel" ]]; then
[[ $# -ge 3 ]] || die "group $name: --parallel expects a value and at least one stack" [[ $# -ge 3 ]] || die "$stage --parallel expects a value and at least one stack"
limit="$2" limit="$2"
validate_parallel_limit "$limit" validate_parallel_limit "$limit"
shift 2 shift 2
fi fi
[[ $# -ge 1 ]] || die "group has no stacks: $name" [[ $# -ge 1 ]] || die "$stage expects at least one stack"
for stack in "$@"; do for stack in "$@"; do
validate_name "stack" "$stack" validate_name "stack" "$stack"
contains "$stack" "${CONFIGURED_STACKS[@]}" && contains "$stack" "${CONFIGURED_STACKS[@]}" &&
die "stack is assigned to more than one group: $stack" die "stack is assigned more than once: $stack"
contains "$stack" "${EXCLUDED_STACKS[@]}" && contains "$stack" "${EXCLUDED_STACKS[@]}" &&
die "stack is both grouped and excluded: $stack" die "stack is both assigned and excluded: $stack"
CONFIGURED_STACKS+=("$stack") CONFIGURED_STACKS+=("$stack")
done done
CONFIG_GROUP_NAMES+=("$name") groups+=("$*")
CONFIG_GROUP_LIMITS+=("$limit") limits+=("$limit")
CONFIG_GROUP_STACKS+=("$*")
} }
before() { add_stage_group BEFORE_GROUPS BEFORE_LIMITS before "$@"; }
after() { add_stage_group AFTER_GROUPS AFTER_LIMITS after "$@"; }
# Exclude stacks from all bulk operations. # Exclude stacks from all bulk operations.
exclude() { exclude() {
[[ $# -ge 1 ]] || die "exclude expects at least one stack" [[ $# -ge 1 ]] || die "exclude expects at least one stack"
@@ -144,7 +141,7 @@ exclude() {
contains "$stack" "${EXCLUDED_STACKS[@]}" && contains "$stack" "${EXCLUDED_STACKS[@]}" &&
die "stack is excluded more than once: $stack" die "stack is excluded more than once: $stack"
contains "$stack" "${CONFIGURED_STACKS[@]}" && contains "$stack" "${CONFIGURED_STACKS[@]}" &&
die "stack is both grouped and excluded: $stack" die "stack is both assigned and excluded: $stack"
EXCLUDED_STACKS+=("$stack") EXCLUDED_STACKS+=("$stack")
done done
} }
@@ -152,7 +149,7 @@ exclude() {
load_config() { load_config() {
[[ -f "$CONFIG_FILE" ]] || return 0 [[ -f "$CONFIG_FILE" ]] || return 0
# The trusted config calls max_parallel, group, and exclude above. # The trusted config calls max_parallel, before, after, and exclude above.
source "$CONFIG_FILE" || die "cannot load config file: $CONFIG_FILE" source "$CONFIG_FILE" || die "cannot load config file: $CONFIG_FILE"
} }
@@ -187,45 +184,52 @@ validate_config_stacks() {
done done
} }
build_execution_groups() { append_stage_groups() {
local i stack limit local stage="$1"
local groups_name="$2"
local limits_name="$3"
local i name
local -a stacks=() local -a stacks=()
local -n groups="$groups_name"
local -n limits="$limits_name"
for ((i = 0; i < ${#groups[@]}; i++)); do
name="$stage"
((${#groups[@]} > 1)) && name+="-$((i + 1))"
GROUP_NAMES+=("$name")
GROUP_LIMITS+=("${limits[i]:-$DEFAULT_MAX_PARALLEL}")
GROUP_STACKS+=("${groups[i]}")
read -r -a stacks <<<"${groups[i]}"
MANAGED_STACKS+=("${stacks[@]}")
done
}
build_execution_groups() {
local stack
local -a default_stacks=()
GROUP_NAMES=() GROUP_NAMES=()
GROUP_LIMITS=() GROUP_LIMITS=()
GROUP_STACKS=() GROUP_STACKS=()
MANAGED_STACKS=() MANAGED_STACKS=()
UNASSIGNED_STACKS=()
for ((i = 0; i < ${#CONFIG_GROUP_NAMES[@]}; i++)); do append_stage_groups before BEFORE_GROUPS BEFORE_LIMITS
limit="${CONFIG_GROUP_LIMITS[i]:-$DEFAULT_MAX_PARALLEL}"
GROUP_NAMES+=("${CONFIG_GROUP_NAMES[i]}")
GROUP_LIMITS+=("$limit")
GROUP_STACKS+=("${CONFIG_GROUP_STACKS[i]}")
read -r -a stacks <<<"${CONFIG_GROUP_STACKS[i]}"
MANAGED_STACKS+=("${stacks[@]}")
done
for stack in "${DISCOVERED_STACKS[@]}"; do for stack in "${DISCOVERED_STACKS[@]}"; do
if ! contains "$stack" "${CONFIGURED_STACKS[@]}" && if ! contains "$stack" "${CONFIGURED_STACKS[@]}" &&
! contains "$stack" "${EXCLUDED_STACKS[@]}"; then ! contains "$stack" "${EXCLUDED_STACKS[@]}"; then
UNASSIGNED_STACKS+=("$stack") default_stacks+=("$stack")
MANAGED_STACKS+=("$stack")
fi fi
done done
if ((${#UNASSIGNED_STACKS[@]} > 0)); then if ((${#default_stacks[@]} > 0)); then
GROUP_NAMES+=("unassigned") GROUP_NAMES+=("default")
GROUP_LIMITS+=("$DEFAULT_MAX_PARALLEL") GROUP_LIMITS+=("$DEFAULT_MAX_PARALLEL")
GROUP_STACKS+=("${UNASSIGNED_STACKS[*]}") GROUP_STACKS+=("${default_stacks[*]}")
MANAGED_STACKS+=("${default_stacks[@]}")
fi fi
}
warn_unassigned() { append_stage_groups after AFTER_GROUPS AFTER_LIMITS
((${#UNASSIGNED_STACKS[@]} == 0)) && return 0
echo "WARNING: unassigned stacks will run in the final implicit group: ${UNASSIGNED_STACKS[*]}" >&2
} }
stack_group() { stack_group() {
@@ -463,8 +467,6 @@ run_all_grouped() {
RUN_FAILED=0 RUN_FAILED=0
RUN_FAILED_STACKS=() RUN_FAILED_STACKS=()
RUN_SKIPPED_GROUPS=() RUN_SKIPPED_GROUPS=()
warn_unassigned
if [[ "$action" == "stop" || "$action" == "down" ]]; then if [[ "$action" == "stop" || "$action" == "down" ]]; then
direction="reverse" direction="reverse"
start=$((end - 1)) start=$((end - 1))
@@ -487,8 +489,6 @@ run_all_sequential() {
local action="$1" local action="$1"
local stack local stack
local -a failed_stacks=() local -a failed_stacks=()
warn_unassigned
for stack in "${MANAGED_STACKS[@]}"; do for stack in "${MANAGED_STACKS[@]}"; do
run_stack "$stack" "$action" || failed_stacks+=("$stack") run_stack "$stack" "$action" || failed_stacks+=("$stack")
done done
@@ -503,7 +503,6 @@ run_all_sequential() {
list_stacks() { list_stacks() {
local stack group_name status local stack group_name status
warn_unassigned
printf '%-24s %-16s %s\n' "STACK" "GROUP" "STATUS" printf '%-24s %-16s %s\n' "STACK" "GROUP" "STATUS"
printf '%-24s %-16s %s\n' "------------------------" "----------------" "---------------" printf '%-24s %-16s %s\n' "------------------------" "----------------" "---------------"
@@ -546,7 +545,6 @@ list_images() {
return return
fi fi
warn_unassigned
for stack in "${MANAGED_STACKS[@]}"; do for stack in "${MANAGED_STACKS[@]}"; do
stack_images "$stack" || ((failed += 1)) stack_images "$stack" || ((failed += 1))
done done