added support for multiple routes multiple ports
This commit is contained in:
parent
7399cf83f9
commit
7fd2531501
4 changed files with 445 additions and 151 deletions
191
panelctl.sh
191
panelctl.sh
|
|
@ -15,12 +15,38 @@ FORWARD_AUTH_BLOCK=' forward_auth 127.0.0.1:9091 {
|
|||
}
|
||||
'
|
||||
|
||||
validate_route_entry() {
|
||||
local entry="$1"
|
||||
# Format: domain|upstream (upstream = host:port)
|
||||
local domain="${entry%%|*}"
|
||||
local upstream="${entry#*|}"
|
||||
[[ -n "${domain}" ]] || fail "empty domain in route entry '${entry}'"
|
||||
[[ -n "${upstream}" ]] || fail "empty upstream in route entry '${entry}'"
|
||||
[[ "${entry}" == *"|"* ]] || fail "route entry '${entry}' missing '|' separator (expected domain|upstream)"
|
||||
validate_single_domain "${domain}"
|
||||
# Validate upstream has a port
|
||||
local upstream_port="${upstream##*:}"
|
||||
[[ "${upstream_port}" =~ ^[0-9]+$ ]] || fail "upstream '${upstream}' missing numeric port in route entry '${entry}'"
|
||||
validate_port "${upstream_port}"
|
||||
}
|
||||
|
||||
validate_routes() {
|
||||
local routes_str="$1"
|
||||
IFS=',' read -ra entries <<< "${routes_str}"
|
||||
[[ ${#entries[@]} -ge 1 ]] || fail "at least one route is required"
|
||||
for entry in "${entries[@]}"; do
|
||||
entry="$(echo "${entry}" | xargs)"
|
||||
validate_route_entry "${entry}"
|
||||
done
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
panelctl - minimal app panel helper
|
||||
|
||||
Usage:
|
||||
panelctl init <name> <domains> <port> [auth]
|
||||
panelctl init <name> "<domain>|<upstream>[,...]" [auth]
|
||||
panelctl set-routes <name> "<domain>|<upstream>[,...]"
|
||||
panelctl render-route <name>
|
||||
panelctl deploy <name>
|
||||
panelctl restart <name>
|
||||
|
|
@ -36,14 +62,15 @@ Usage:
|
|||
panelctl list
|
||||
panelctl show <name>
|
||||
|
||||
Domains can be comma-separated for multiple domains:
|
||||
panelctl init myapp "app.example.com,www.example.com" 18080 true
|
||||
Each route is a domain|upstream pair. Upstream is host:port.
|
||||
Multiple routes are comma-separated:
|
||||
panelctl init myapp "app.example.com|127.0.0.1:18080,api.example.com|127.0.0.1:18081" true
|
||||
|
||||
Wildcard domains are supported (requires DNS challenge in Caddy):
|
||||
panelctl init myapp "*.example.com" 18080 true
|
||||
panelctl init myapp "*.example.com|127.0.0.1:18080" true
|
||||
|
||||
Examples:
|
||||
panelctl init whoami whoami.srazka.com 18080 true
|
||||
panelctl init whoami "whoami.srazka.com|127.0.0.1:18080" true
|
||||
panelctl deploy whoami
|
||||
panelctl restart whoami
|
||||
panelctl status whoami
|
||||
|
|
@ -128,6 +155,23 @@ load_app() {
|
|||
[[ -f "${manifest}" ]] || fail "app '${name}' does not exist"
|
||||
# shellcheck disable=SC1090
|
||||
source "${manifest}"
|
||||
|
||||
# Backward compat: migrate old APP_DOMAIN/APP_PORT/APP_UPSTREAM to APP_ROUTES
|
||||
if [[ -z "${APP_ROUTES:-}" && -n "${APP_DOMAIN:-}" ]]; then
|
||||
local upstream="${APP_UPSTREAM:-127.0.0.1:${APP_PORT:-18080}}"
|
||||
local routes=""
|
||||
local domains_str="${APP_DOMAINS:-${APP_DOMAIN}}"
|
||||
IFS=',' read -ra domain_arr <<< "${domains_str}"
|
||||
for d in "${domain_arr[@]}"; do
|
||||
d="$(echo "${d}" | xargs)"
|
||||
if [[ -n "${routes}" ]]; then
|
||||
routes="${routes},${d}|${upstream}"
|
||||
else
|
||||
routes="${d}|${upstream}"
|
||||
fi
|
||||
done
|
||||
APP_ROUTES="${routes}"
|
||||
fi
|
||||
}
|
||||
|
||||
compose_command() {
|
||||
|
|
@ -203,19 +247,24 @@ run_compose() {
|
|||
|
||||
write_default_compose() {
|
||||
local name="$1"
|
||||
local port="$2"
|
||||
local routes="$2"
|
||||
local stack_dir
|
||||
local volume_dir
|
||||
stack_dir="$(app_stack_dir "${name}")"
|
||||
volume_dir="$(app_volume_dir "${name}")"
|
||||
|
||||
# Use first route's upstream port for the default compose mapping
|
||||
local first_route="${routes%%,*}"
|
||||
local first_upstream="${first_route#*|}"
|
||||
local container_port="${first_upstream##*:}"
|
||||
|
||||
cat >"${stack_dir}/compose.yaml" <<EOF
|
||||
services:
|
||||
app:
|
||||
image: docker.io/traefik/whoami:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:${port}:80"
|
||||
- "127.0.0.1:${container_port}:80"
|
||||
volumes:
|
||||
- ${volume_dir}/data:/data
|
||||
EOF
|
||||
|
|
@ -223,9 +272,8 @@ EOF
|
|||
|
||||
write_manifest() {
|
||||
local name="$1"
|
||||
local domains="$2"
|
||||
local port="$3"
|
||||
local auth="$4"
|
||||
local routes="$2"
|
||||
local auth="$3"
|
||||
local manifest
|
||||
local stack_dir
|
||||
local volume_dir
|
||||
|
|
@ -236,17 +284,9 @@ write_manifest() {
|
|||
volume_dir="$(app_volume_dir "${name}")"
|
||||
route_file="$(app_route_file)"
|
||||
|
||||
# First domain is the primary (used for APP_DOMAIN backward compat)
|
||||
local primary_domain
|
||||
IFS=',' read -ra domain_arr <<< "${domains}"
|
||||
primary_domain="$(echo "${domain_arr[0]}" | xargs)"
|
||||
|
||||
cat >"${manifest}" <<EOF
|
||||
APP_NAME="${name}"
|
||||
APP_DOMAIN="${primary_domain}"
|
||||
APP_DOMAINS="${domains}"
|
||||
APP_PORT="${port}"
|
||||
APP_UPSTREAM="127.0.0.1:${port}"
|
||||
APP_ROUTES="${routes}"
|
||||
APP_AUTH_PROTECTED="${auth}"
|
||||
APP_STACK_DIR="${stack_dir}"
|
||||
APP_COMPOSE_FILE="${stack_dir}/compose.yaml"
|
||||
|
|
@ -257,13 +297,11 @@ EOF
|
|||
|
||||
cmd_init() {
|
||||
local name="$1"
|
||||
local domains="$2"
|
||||
local port="$3"
|
||||
local auth="${4:-true}"
|
||||
local routes="$2"
|
||||
local auth="${3:-true}"
|
||||
|
||||
validate_name "${name}"
|
||||
validate_domains "${domains}"
|
||||
validate_port "${port}"
|
||||
validate_routes "${routes}"
|
||||
[[ "${auth}" == "true" || "${auth}" == "false" ]] || fail "auth must be true or false"
|
||||
|
||||
ensure_base_dirs
|
||||
|
|
@ -278,8 +316,8 @@ cmd_init() {
|
|||
[[ ! -f "${manifest}" ]] || fail "app '${name}' already exists"
|
||||
|
||||
mkdir -p "${stack_dir}" "${volume_dir}/data"
|
||||
write_default_compose "${name}" "${port}"
|
||||
write_manifest "${name}" "${domains}" "${port}" "${auth}"
|
||||
write_default_compose "${name}" "${routes}"
|
||||
write_manifest "${name}" "${routes}" "${auth}"
|
||||
cmd_render_route "${name}"
|
||||
|
||||
log info "initialized app '${name}'"
|
||||
|
|
@ -295,19 +333,6 @@ cmd_render_route() {
|
|||
auth_block="${FORWARD_AUTH_BLOCK}"
|
||||
fi
|
||||
|
||||
# Build domain list for Caddy block header.
|
||||
local caddy_domains=""
|
||||
local domains_str="${APP_DOMAINS:-${APP_DOMAIN}}"
|
||||
IFS=',' read -ra domain_arr <<< "${domains_str}"
|
||||
for d in "${domain_arr[@]}"; do
|
||||
d="$(echo "${d}" | xargs)"
|
||||
if [[ -n "${caddy_domains}" ]]; then
|
||||
caddy_domains="${caddy_domains}, ${d}"
|
||||
else
|
||||
caddy_domains="${d}"
|
||||
fi
|
||||
done
|
||||
|
||||
local route_file
|
||||
route_file="$(app_route_file)"
|
||||
|
||||
|
|
@ -323,12 +348,17 @@ cmd_render_route() {
|
|||
|
||||
{
|
||||
printf "# route:%s:start\n" "${name}"
|
||||
printf "%s {\n" "${caddy_domains}"
|
||||
if [[ -n "${auth_block}" ]]; then
|
||||
printf "%s\n" "${auth_block}"
|
||||
fi
|
||||
printf " reverse_proxy %s\n" "${APP_UPSTREAM}"
|
||||
printf "}\n"
|
||||
IFS=',' read -ra route_entries <<< "${APP_ROUTES}"
|
||||
for entry in "${route_entries[@]}"; do
|
||||
entry="$(echo "${entry}" | xargs)"
|
||||
local domain="${entry%%|*}"
|
||||
local upstream="${entry#*|}"
|
||||
if [[ -n "${auth_block}" ]]; then
|
||||
printf "%s {\n%s reverse_proxy %s\n}\n" "${domain}" "${auth_block}" "${upstream}"
|
||||
else
|
||||
printf "%s {\n reverse_proxy %s\n}\n" "${domain}" "${upstream}"
|
||||
fi
|
||||
done
|
||||
printf "# route:%s:end\n" "${name}"
|
||||
} >>"${tmp}"
|
||||
|
||||
|
|
@ -452,6 +482,45 @@ cmd_remove() {
|
|||
log info "removed app '${name}'"
|
||||
}
|
||||
|
||||
cmd_set_routes() {
|
||||
local name="$1"
|
||||
local routes="$2"
|
||||
local manifest
|
||||
|
||||
validate_name "${name}"
|
||||
validate_routes "${routes}"
|
||||
manifest="$(app_manifest "${name}")"
|
||||
[[ -f "${manifest}" ]] || fail "app '${name}' does not exist"
|
||||
|
||||
# Update APP_ROUTES in the manifest file, strip old fields, preserve others
|
||||
local tmp
|
||||
tmp="$(mktemp)"
|
||||
local found_routes=false
|
||||
while IFS= read -r line; do
|
||||
case "${line}" in
|
||||
APP_ROUTES=*)
|
||||
printf 'APP_ROUTES="%s"\n' "${routes}" >> "${tmp}"
|
||||
found_routes=true
|
||||
;;
|
||||
APP_DOMAIN=*|APP_DOMAINS=*|APP_PORT=*|APP_UPSTREAM=*)
|
||||
# Strip old format fields
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' "${line}" >> "${tmp}"
|
||||
;;
|
||||
esac
|
||||
done < "${manifest}"
|
||||
if ! "${found_routes}"; then
|
||||
printf 'APP_ROUTES="%s"\n' "${routes}" >> "${tmp}"
|
||||
fi
|
||||
install -m 0664 "${tmp}" "${manifest}"
|
||||
|
||||
# Re-render Caddy routes (auto-reloads via systemd.path watcher)
|
||||
cmd_render_route "${name}"
|
||||
|
||||
log info "updated routes for app '${name}'. Edit compose file if new ports need exposing."
|
||||
}
|
||||
|
||||
cmd_backup() {
|
||||
local name="$1"
|
||||
validate_name "${name}"
|
||||
|
|
@ -602,9 +671,29 @@ cmd_list() {
|
|||
source /dev/null # reset any leftover variables
|
||||
unset APP_REPO_URL APP_REPO_BRANCH APP_REPO_DIR 2>/dev/null || true
|
||||
source "${mf}"
|
||||
local domains="${APP_DOMAINS:-${APP_DOMAIN}}"
|
||||
# Backward compat: build APP_ROUTES from old format
|
||||
local routes="${APP_ROUTES:-}"
|
||||
if [[ -z "${routes}" && -n "${APP_DOMAIN:-}" ]]; then
|
||||
local upstream="${APP_UPSTREAM:-127.0.0.1:${APP_PORT:-18080}}"
|
||||
local domains_str="${APP_DOMAINS:-${APP_DOMAIN}}"
|
||||
IFS=',' read -ra domain_arr <<< "${domains_str}"
|
||||
for d in "${domain_arr[@]}"; do
|
||||
d="$(echo "${d}" | xargs)"
|
||||
if [[ -n "${routes}" ]]; then
|
||||
routes="${routes},${d}|${upstream}"
|
||||
else
|
||||
routes="${d}|${upstream}"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
# Show abbreviated: first route's domain + upstream, and count
|
||||
local first_route="${routes%%,*}"
|
||||
local route_count=1
|
||||
if [[ "${routes}" == *","* ]]; then
|
||||
route_count="$(( $(grep -o ',' <<< "${routes}" | wc -l) + 1 ))"
|
||||
fi
|
||||
local repo_info="${APP_REPO_URL:-}"
|
||||
echo "${APP_NAME} ${domains} ${APP_UPSTREAM} auth=${APP_AUTH_PROTECTED} ${repo_info}"
|
||||
echo "${APP_NAME} ${first_route} routes=${route_count} auth=${APP_AUTH_PROTECTED} ${repo_info}"
|
||||
done
|
||||
|
||||
if [[ "${found}" -eq 0 ]]; then
|
||||
|
|
@ -626,8 +715,12 @@ main() {
|
|||
|
||||
case "${cmd}" in
|
||||
init)
|
||||
[[ $# -ge 4 ]] || fail "usage: panelctl init <name> <domains> <port> [auth]"
|
||||
cmd_init "$2" "$3" "$4" "${5:-true}"
|
||||
[[ $# -ge 3 ]] || fail "usage: panelctl init <name> <routes> [auth]"
|
||||
cmd_init "$2" "$3" "${4:-true}"
|
||||
;;
|
||||
set-routes)
|
||||
[[ $# -eq 3 ]] || fail "usage: panelctl set-routes <name> <routes>"
|
||||
cmd_set_routes "$2" "$3"
|
||||
;;
|
||||
render-route)
|
||||
[[ $# -eq 2 ]] || fail "usage: panelctl render-route <name>"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue