add the pure bash core: core, str, naming and tags

Ported from make/utils.mk, make/def.mk, make/apps/def.docker.mk and
make/apps/def.mk, with the examples that sat as dead comments in
make/apps/def.mk turned into actual assertions.

Two deviations from the make macros are pinned and documented in DELTAS.md;
both only show up on code paths nothing uses.
This commit is contained in:
Yann Autissier
2026-09-03 18:26:00 +02:00
parent 6c97f99f87
commit 5b4db64114
9 changed files with 537 additions and 0 deletions
+45
View File
@@ -0,0 +1,45 @@
#shellcheck shell=sh
# core: logging, error handling and command execution.
#
# Every myos command goes through myos_run, which honours DRYRUN by printing
# the command instead of running it. Messages go to stderr so that stdout stays
# usable for data (myos env, myos config, myos ls).
MYOS_E_OK=0 # success
MYOS_E_FAIL=1 # command failed
MYOS_E_USAGE=2 # bad invocation
MYOS_E_NOSTACK=3 # stack not found
MYOS_E_NOREQ=4 # missing requirement
myos_colors() {
if [ -t 2 ] && [ "${TERM:-dumb}" != dumb ] && [ -z "${NO_COLOR:-}" ]; then
MYOS_C_ERROR=$(printf '\033[31m'); MYOS_C_WARN=$(printf '\033[01;33m')
MYOS_C_INFO=$(printf '\033[33m'); MYOS_C_DEBUG=$(printf '\033[01;34m')
MYOS_C_VALUE=$(printf '\033[36m'); MYOS_C_RESET=$(printf '\033[0m')
else
MYOS_C_ERROR=; MYOS_C_WARN=; MYOS_C_INFO=; MYOS_C_DEBUG=; MYOS_C_VALUE=; MYOS_C_RESET=
fi
}
myos_error() { printf '%sERROR:%s %s\n' "$MYOS_C_ERROR" "$MYOS_C_RESET" "$*" >&2; }
myos_warning() { printf '%sWARNING:%s %s\n' "$MYOS_C_WARN" "$MYOS_C_RESET" "$*" >&2; }
myos_info() { [ -n "${VERBOSE:-}" ] && printf '%s%s%s\n' "$MYOS_C_INFO" "$*" "$MYOS_C_RESET" >&2; return 0; }
myos_debug() { [ -n "${DEBUG:-}" ] && printf '%s%s%s\n' "$MYOS_C_DEBUG" "$*" "$MYOS_C_RESET" >&2; return 0; }
# myos_die CODE MESSAGE...
myos_die() { _code=$1; shift; myos_error "$*"; exit "$_code"; }
# myos_run CMD... run a command, or print it when DRYRUN is true
myos_run() {
if [ "${DRYRUN:-false}" = true ]; then
printf '%s\n' "$*"
return 0
fi
myos_debug "+ $*"
"$@"
}
# myos_have CMD is a command available?
myos_have() { command -v "$1" >/dev/null 2>&1; }
myos_colors
+78
View File
@@ -0,0 +1,78 @@
#shellcheck shell=sh
# naming: compose project name, service name, networks, user identity.
#
# Ported from make/apps/def.docker.mk (COMPOSE_PROJECT_NAME, COMPOSE_SERVICE_NAME),
# make/def.docker.mk (HOST_*/USER_*, DOCKER_NETWORK_*) and make/def.mk (RESU).
# myos_scope REF -> host | user | cluster | app
# The first segment of a stack reference decides how the stack is named:
# host stacks are singletons of the machine (they bind privileged ports),
# user stacks are singletons of the user, cluster stacks are swarm namespaces.
myos_scope() {
[ -n "${MYOS_SCOPE:-}" ] && { printf '%s' "$MYOS_SCOPE"; return 0; }
case ${1%%/*} in
host) printf 'host' ;;
User|user) printf 'user' ;;
cluster) printf 'cluster' ;;
*) printf 'app' ;;
esac
}
# myos_resu MAIL -> user.domain identity of a mail address (make: RESU)
# Also sets MYOS_RESU_NIAMOD (reversed domain + reversed user) and
# MYOS_RESU_PATH (that identity as a path), used by the User stacks.
myos_resu() {
_mail=$(myos_lower "${1:-}" | tr '+_' '..')
case $_mail in
*@*) ;;
*) MYOS_RESU_NIAMOD=; MYOS_RESU_PATH=; printf '%s' "${USER:-}"; return 0 ;;
esac
_user=${_mail%@*}
_domain=${_mail##*@}
[ -n "$_domain" ] || { MYOS_RESU_NIAMOD=; MYOS_RESU_PATH=; printf '%s' "${USER:-}"; return 0; }
_niamod=$(myos_reverse "$(printf '%s' "$_domain" | tr '.' ' ')" | tr ' ' '.')
_resu=$(myos_reverse "$(printf '%s' "$_user" | tr '.' ' ')" | tr ' ' '.')
MYOS_RESU_NIAMOD="$_niamod.$_resu"
MYOS_RESU_PATH=$(printf '%s' "$MYOS_RESU_NIAMOD" | tr '.' '/')
printf '%s.%s' "$_user" "$_domain"
}
# myos_project_name SCOPE USER ENV APP [PATH]
# host -> HOST_COMPOSE_PROJECT_NAME, defaults to the hostname
# user -> USER_COMPOSE_PROJECT_NAME, defaults to the RESU identity
# cluster -> the stack name: one namespace per swarm, not per user
# app -> MYOS_PROJECT_FORMAT: user-env-app (default) or user-app-env (legacy)
myos_project_name() {
_scope=$1; _user=$2; _env=$3; _app=$4; _path=${5:-}
[ -n "${DOCKER_COMPOSE_PROJECT_NAME:-}" ] && { printf '%s' "$DOCKER_COMPOSE_PROJECT_NAME"; return 0; }
case $_scope in
host)
printf '%s' "${HOST_COMPOSE_PROJECT_NAME:-${HOSTNAME:-localhost}}"; return 0 ;;
user)
if [ -n "${USER_COMPOSE_PROJECT_NAME:-}" ]; then printf '%s' "$USER_COMPOSE_PROJECT_NAME"
else printf '%s' "$(myos_resu "${MAIL:-}" | tr '.' '-')"; fi
return 0 ;;
cluster)
printf '%s' "${MYOS_CLUSTER_PROJECT:-$(myos_name "$_app")}"; return 0 ;;
esac
_n=$(myos_name "$_app")
case ${MYOS_PROJECT_FORMAT:-user-env-app} in
user-app-env) _out="$_user-$_n-$_env" ;;
user-env-app) _out="$_user-$_env-$_n" ;;
*) myos_die "$MYOS_E_USAGE" "unknown MYOS_PROJECT_FORMAT: ${MYOS_PROJECT_FORMAT}" ;;
esac
# the path fragment loses its slashes too (make: $(subst /,,$(subst -,,$(APP_PATH))))
[ -n "$_path" ] && _out="$_out-$(myos_name "$_path" | tr -d /)"
myos_lower "$_out" | tr -d '.'
}
# myos_service_name PROJECT prefix of the SERVICE_<port>_NAME labels
myos_service_name() { printf '%s' "$1" | tr '_' '-'; }
# myos_network_default PROJECT
# The leading underscore keeps this network first in alphabetical order, so it
# is the first interface attached and service names never resolve across stacks.
# https://github.com/moby/libnetwork/issues/2093
myos_network_default() { printf '_%s' "$1"; }
myos_network_private() { printf '%s' "${DOCKER_NETWORK_PRIVATE:-${1}-${2}}"; }
myos_network_public() { printf '%s' "${DOCKER_NETWORK_PUBLIC:-${1}}"; }
+33
View File
@@ -0,0 +1,33 @@
#shellcheck shell=sh
# str: string helpers ported from make/utils.mk and make/def.mk.
# myos_lower STRING / myos_upper STRING
myos_lower() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]'; }
myos_upper() { printf '%s' "$1" | tr '[:lower:]-.' '[:upper:]__'; }
# myos_name STRING compose-project-safe name: lowercase, no . - _
# (make: $(subst _,,$(subst -,,$(subst .,,$(call LOWERCASE,$(1))))))
myos_name() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | tr -d '._-'; }
# myos_slugify STRING keep [a-z0-9_], everything else becomes _
myos_slugify() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9_]/_/g'; }
# myos_reverse WORDS... reverse the order of space separated words
myos_reverse() {
_out=
for _w in $1; do _out="$_w${_out:+ }$_out"; done
printf '%s' "$_out"
}
# myos_verle A B true when version A <= B (make: verle)
myos_verle() {
[ -n "$1" ] || return 1
[ -n "$2" ] || return 1
[ "$1" = "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" ]
}
# myos_verlt A B true when version A < B
myos_verlt() {
[ "$1" = "$2" ] && return 1
myos_verle "$1" "$2"
}
+70
View File
@@ -0,0 +1,70 @@
#shellcheck shell=sh
# tags: fabio route tags derived from stack variables.
#
# Ported from make/apps/def.mk (uri, url, urlprefix, urlprefixs, tagprefix,
# envprefix, servicenvs). Registrator publishes the SERVICE_<port>_TAGS label
# to consul, fabio routes on the urlprefix- tags it finds there.
# myos_var NAME value of the variable named NAME, empty when unset
myos_var() { eval "printf '%s' \"\${$1:-}\""; }
# myos_uri SERVICE PORT [BASE_URI]
# <service>.<base uri>, unless <SERVICE>_SERVICE[_<port>]_NAME overrides the prefix
myos_uri() {
_svc=$1; _port=${2:-}; _base=${3:-${APP_URI:-}}
_u=$(myos_upper "$_svc")
_name=$(myos_var "${_u}_SERVICE_${_port}_NAME")
[ -n "$_name" ] || _name=$(myos_var "${_u}_SERVICE_NAME")
[ -n "$_name" ] || _name=$_svc
_out=
for _b in $_base; do _out="${_out:+$_out }${_name}.${_b}"; done
printf '%s' "$_out"
}
# myos_url SERVICE PORT [BASE_URI]
myos_url() {
_out=
for _u in $(myos_uri "$@"); do _out="${_out:+$_out }${APP_SCHEME:-http}://$_u"; done
printf '%s' "$_out"
}
# myos_urlprefix [PATH] [OPTS] [URIS]
# one comma separated "urlprefix-<uri><path>* [opts]" per uri
myos_urlprefix() {
_path=${1:-}; _opts=${2:-}; _uris=${3:-${APP_URI:-}}
_out=
for _u in $_uris; do
_tag="urlprefix-${_u}${_path}${MYOS_URL_SUFFIX:-*}${_opts:+ $_opts}"
_out="${_out:+$_out,}$_tag"
done
printf '%s' "$_out"
}
# myos_envprefix STACK PORT KEYS...
# "key=value" for each <STACK>_SERVICE_<port>_<KEY> that is set
myos_envprefix() {
_stack=$1; _port=$2; shift 2
_out=
for _k in "$@"; do
_v=$(myos_var "$(myos_upper "${_stack}_SERVICE_${_port}_${_k}")")
[ -n "$_v" ] && _out="${_out:+$_out }${_k}=${_v}"
done
printf '%s' "$_out"
}
# myos_tagprefix STACK PORT [URI_KEYS...]
# the fabio tag of a service, assembled from its PATH, OPTS and URIS variables
myos_tagprefix() {
_stack=$1; _port=$2; shift 2
_u=$(myos_upper "$_stack")
_path=$(myos_var "${_u}_SERVICE_${_port}_PATH"); [ -n "$_path" ] || _path=$(myos_var "${_u}_SERVICE_PATH")
_opts=$(myos_var "${_u}_SERVICE_${_port}_OPTS"); [ -n "$_opts" ] || _opts=$(myos_var "${_u}_SERVICE_OPTS")
[ -n "$_opts" ] || _opts=$(myos_envprefix "$_stack" "$_port" allow auth deny prepend proto register strip)
_uris=
for _k in "$@"; do
_v=$(myos_var "${_u}_SERVICE_${_port}_${_k}"); [ -n "$_v" ] && _uris="${_uris:+$_uris }$_v"
done
[ -n "$_uris" ] || _uris=$(myos_var "${_u}_SERVICE_${_port}_URIS")
[ -n "$_uris" ] || _uris=$(myos_uri "$_stack" "$_port")
myos_urlprefix "$_path" "$_opts" "$_uris"
}