@meistrari/mise-en-place 2.19.1 → 2.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -0
- package/mise/tasks/registry-login.sh +13 -1
- package/mise/tasks/tela-proxy/_default.sh +24 -0
- package/mise/tasks/tela-proxy/compose.yaml +26 -0
- package/mise/tasks/tela-proxy/lib.sh +168 -0
- package/mise/tasks/tela-proxy/stop.sh +9 -0
- package/mise/tasks/tela-proxy/traefik.yml +46 -0
- package/mise/tasks/tela-proxy/worktree.sh +63 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,6 +58,78 @@ You can customize the update behaviour through a `miseEnPlace.updateMeistrariLib
|
|
|
58
58
|
|
|
59
59
|
When `exact` is `true`, every updated package is written as an exact version: each package is still updated as far as its strategy allows, but the resulting version is written without a `^` or `~`.
|
|
60
60
|
|
|
61
|
+
## Local proxy for Compose services
|
|
62
|
+
The `tela-proxy` mise tasks run one Traefik container shared by every project on the machine. It serves each Compose service attached to the `tela-network` Docker network at a `*.localhost` host, so services publish no HTTP ports and several projects run side by side.
|
|
63
|
+
|
|
64
|
+
Requires Docker; mise installs the `jq` the tasks use. The proxy listens on port 80 of the loopback interface only, and the dashboard of active routes is at `http://tela-proxy.localhost`.
|
|
65
|
+
|
|
66
|
+
### Tasks
|
|
67
|
+
- **`mise run tela-proxy`** (alias `tela-proxy:start`): creates `tela-network` when missing and starts the proxy. Make it a dependency of every task that starts your Compose project (`#MISE depends=["tela-proxy"]`), so it also runs the conflict check below.
|
|
68
|
+
- **`mise run tela-proxy:stop`**: stops the proxy. It stays stopped across reboots until `tela-proxy` runs again.
|
|
69
|
+
- **`mise run tela-proxy:worktree [label] [-- command...]`** (aliases `worktree`, `wt`): runs the command under a separate Compose project for a worktree. Without a command, it opens `$SHELL` with the same environment.
|
|
70
|
+
|
|
71
|
+
### Hosts
|
|
72
|
+
A service gets `http://{service}.{project}.localhost`, where `project` is the Compose project name. Set it with `name:` in the Compose file so it stays the same in every clone; otherwise Compose uses the directory name.
|
|
73
|
+
|
|
74
|
+
- A service named after the project prefix drops it: service `auth-api` in project `auth` is `api.auth.localhost`.
|
|
75
|
+
- A service named exactly like the project is `{project}.localhost`.
|
|
76
|
+
- Traefik uses the port the image exposes (`EXPOSE`).
|
|
77
|
+
- A service opts out with the `traefik.enable=false` label, or replaces its host with a `traefik.http.routers.<name>.rule` label.
|
|
78
|
+
|
|
79
|
+
Only HTTP is routed. Databases keep their published ports.
|
|
80
|
+
|
|
81
|
+
### Networks
|
|
82
|
+
Keep the Compose `default` network private to the project, and attach only the services the proxy routes to `tela-network`:
|
|
83
|
+
|
|
84
|
+
```yaml
|
|
85
|
+
services:
|
|
86
|
+
auth-api:
|
|
87
|
+
networks:
|
|
88
|
+
default:
|
|
89
|
+
aliases: [auth-api-internal]
|
|
90
|
+
tela-network: {}
|
|
91
|
+
auth-postgres: {} # default only
|
|
92
|
+
|
|
93
|
+
networks:
|
|
94
|
+
tela-network:
|
|
95
|
+
external: true
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Every project and worktree registers its service names on `tela-network`, so a name there can resolve to another checkout's container. On the private `default` network, names resolve only within the project:
|
|
99
|
+
|
|
100
|
+
- Databases and other internal services stay on `default` only, and their names are safe to use. Label them `traefik.enable=false`: the proxy sees every container and would otherwise create a route it cannot reach.
|
|
101
|
+
- Calls from one service of the project to an HTTP service of the same project use an alias declared only on `default` (`http://auth-api-internal:3001`), because the service name is also on `tela-network`.
|
|
102
|
+
- Another project reaches an HTTP service by its service name on `tela-network` (`http://auth-api:3001`).
|
|
103
|
+
|
|
104
|
+
Changing `networks:` needs the containers recreated: stop the stack and start it again.
|
|
105
|
+
|
|
106
|
+
Inside a container, `*.localhost` resolves to the container itself, so containers never call each other through the proxy hosts.
|
|
107
|
+
|
|
108
|
+
### Worktrees
|
|
109
|
+
`tela-proxy:worktree` exports these variables before running the command:
|
|
110
|
+
|
|
111
|
+
- **`COMPOSE_PROJECT_NAME`**: `{project}_{label}`. It overrides `name:`, so the worktree gets its own containers and volumes.
|
|
112
|
+
- **`TELA_LOCAL_DOMAIN`**: `{project}-{label}.localhost`, the parent domain of the worktree's hosts (`api.auth-feat-x.localhost`).
|
|
113
|
+
- **`TELA_PROXY_WORKTREE`**: the label. The task refuses to open a shell inside another one.
|
|
114
|
+
|
|
115
|
+
The label defaults to the name of the current git worktree, lowercased and limited to `a-z`, `0-9` and `-`. In the main checkout, pass one explicitly.
|
|
116
|
+
|
|
117
|
+
Build the URLs a project gives its own services from `TELA_LOCAL_DOMAIN`, defaulting to the main checkout's domain:
|
|
118
|
+
|
|
119
|
+
```yaml
|
|
120
|
+
services:
|
|
121
|
+
auth-api:
|
|
122
|
+
environment:
|
|
123
|
+
BETTER_AUTH_URL: http://api.${TELA_LOCAL_DOMAIN:-auth.localhost}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Published database ports still collide with the main checkout. Override them through the variables your Compose file reads, for example `AUTH_POSTGRES_PORT=5434 mise run wt -- mise run dev`.
|
|
127
|
+
|
|
128
|
+
### Conflict check
|
|
129
|
+
`tela-proxy` and `tela-proxy:worktree` look up the containers of the current Compose project. When they were started from another directory that still exists, the task fails instead of letting `up` recreate them and share their volumes. Running `mise run dev` inside a worktree without `tela-proxy:worktree` fails this way. Containers from a directory that no longer exists, such as a removed worktree, only produce a warning and are taken over.
|
|
130
|
+
|
|
131
|
+
The check only covers commands that go through these tasks. A plain `docker compose` call, or one that passes `-p`, bypasses it.
|
|
132
|
+
|
|
61
133
|
## Makefile
|
|
62
134
|
The postinstall script does not create a `Makefile` automatically. To use the shared Makefile commands, create a `Makefile` and include this package's `Makefile`.
|
|
63
135
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
#MISE description="Login to the internal
|
|
2
|
+
#MISE description="Login to the internal npm and container registries. Opens the browser for npm, then the registry UI so you can copy your CLI secret."
|
|
3
|
+
#USAGE flag "--npm-registry <url>" default="https://npm.internal.tela.tools/" help="npm registry to log in to through the browser." env="NPM_REGISTRY"
|
|
3
4
|
#USAGE flag "-r --registry <registry>" default="registry.internal.tela.tools" help="Registry host to authenticate against." env="REGISTRY_HOST"
|
|
4
5
|
#USAGE flag "-u --username <username>" default="" help="Registry OIDC username. Prompted for when omitted." env="REGISTRY_USERNAME"
|
|
5
6
|
#USAGE flag "-f --force" default=#false help="Re-authenticate even if the stored credential still works."
|
|
@@ -9,6 +10,7 @@
|
|
|
9
10
|
|
|
10
11
|
set -euo pipefail
|
|
11
12
|
|
|
13
|
+
NPM_REGISTRY="${usage_npm_registry:-${NPM_REGISTRY:-https://npm.internal.tela.tools/}}"
|
|
12
14
|
REGISTRY="${usage_registry:-${REGISTRY_HOST:-registry.internal.tela.tools}}"
|
|
13
15
|
USERNAME="${usage_username:-${REGISTRY_USERNAME:-}}"
|
|
14
16
|
|
|
@@ -60,6 +62,16 @@ open_browser() {
|
|
|
60
62
|
|
|
61
63
|
command -v docker >/dev/null 2>&1 || die "docker is not installed"
|
|
62
64
|
command -v jq >/dev/null 2>&1 || die "jq is not installed"
|
|
65
|
+
command -v npm >/dev/null 2>&1 || die "npm is not installed"
|
|
66
|
+
|
|
67
|
+
# The internal npm registry comes first: its web login only needs the browser,
|
|
68
|
+
# while the Harbor step below waits for a pasted secret. Like Harbor, it is only
|
|
69
|
+
# reachable over the NetBird VPN.
|
|
70
|
+
if [ "${usage_force:-false}" != "true" ] && npm whoami --registry "$NPM_REGISTRY" >/dev/null 2>&1; then
|
|
71
|
+
echo "Already logged in to ${NPM_REGISTRY}."
|
|
72
|
+
else
|
|
73
|
+
npm login --auth-type=web --registry "$NPM_REGISTRY"
|
|
74
|
+
fi
|
|
63
75
|
|
|
64
76
|
check_reachable || die "cannot reach ${REGISTRY}. It is only routable over the NetBird VPN — connect and retry."
|
|
65
77
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#MISE description="Create tela-network and start the shared proxy that serves Compose services on *.localhost"
|
|
3
|
+
#MISE alias="tela-proxy:start"
|
|
4
|
+
#MISE tools={jq="1.8.2"}
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
# shellcheck source=lib.sh
|
|
9
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
|
10
|
+
|
|
11
|
+
enter_caller_dir
|
|
12
|
+
|
|
13
|
+
check_project_conflict
|
|
14
|
+
|
|
15
|
+
acquire_proxy_lock
|
|
16
|
+
ensure_network
|
|
17
|
+
if ! proxy_compose up -d --quiet-pull >/dev/null; then
|
|
18
|
+
die "could not start the proxy. If port 80 is taken, find who holds it with:
|
|
19
|
+
sudo ss -ltnp 'sport = :80' (Linux)
|
|
20
|
+
sudo lsof -iTCP:80 -sTCP:LISTEN (macOS)"
|
|
21
|
+
fi
|
|
22
|
+
release_proxy_lock
|
|
23
|
+
|
|
24
|
+
info "tela-proxy is running: http://<service>.<project>.localhost (dashboard: http://tela-proxy.localhost)"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Shared local proxy. Started by `mise run tela-proxy`, never by hand: the task
|
|
2
|
+
# copies traefik.yml to TELA_PROXY_TRAEFIK_CONFIG and sets TELA_PROXY_CONFIG_HASH,
|
|
3
|
+
# so a changed config recreates the container.
|
|
4
|
+
name: tela-proxy
|
|
5
|
+
|
|
6
|
+
services:
|
|
7
|
+
traefik:
|
|
8
|
+
image: traefik:v3.7.18
|
|
9
|
+
restart: unless-stopped
|
|
10
|
+
command: [--configFile=/etc/traefik/traefik.yml]
|
|
11
|
+
ports:
|
|
12
|
+
# Loopback only, on both stacks: *.localhost resolves to ::1 on Linux.
|
|
13
|
+
- '127.0.0.1:80:80'
|
|
14
|
+
- '[::1]:80:80'
|
|
15
|
+
volumes:
|
|
16
|
+
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
17
|
+
- ${TELA_PROXY_TRAEFIK_CONFIG:?run mise run tela-proxy}:/etc/traefik/traefik.yml:ro
|
|
18
|
+
labels:
|
|
19
|
+
tela-proxy.config-hash: ${TELA_PROXY_CONFIG_HASH:-unset}
|
|
20
|
+
traefik.http.routers.tela-proxy.rule: Host(`tela-proxy.localhost`)
|
|
21
|
+
traefik.http.routers.tela-proxy.service: api@internal
|
|
22
|
+
|
|
23
|
+
networks:
|
|
24
|
+
default:
|
|
25
|
+
name: tela-network
|
|
26
|
+
external: true
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Shared helpers for the tela-proxy mise tasks. Sourced, not executed.
|
|
2
|
+
|
|
3
|
+
TELA_PROXY_HOME="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
4
|
+
TELA_PROXY_NETWORK="tela-network"
|
|
5
|
+
TELA_PROXY_PROJECT="tela-proxy"
|
|
6
|
+
|
|
7
|
+
die() { printf '\033[31merror:\033[0m %s\n' "$*" >&2; exit 1; }
|
|
8
|
+
warn() { printf '\033[33mwarning:\033[0m %s\n' "$*" >&2; }
|
|
9
|
+
info() { printf '\033[36m%s\033[0m\n' "$*" >&2; }
|
|
10
|
+
|
|
11
|
+
# mise runs a task from its config root, not from where it was called. It keeps
|
|
12
|
+
# the caller's directory in MISE_ORIGINAL_CWD.
|
|
13
|
+
enter_caller_dir() {
|
|
14
|
+
cd "${MISE_ORIGINAL_CWD:-$PWD}"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
sha256() {
|
|
18
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
19
|
+
sha256sum | cut -d' ' -f1
|
|
20
|
+
else
|
|
21
|
+
shasum -a 256 | cut -d' ' -f1
|
|
22
|
+
fi
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# Each repository runs these tasks from its own node_modules, so traefik.yml is
|
|
26
|
+
# copied to a fixed path before mounting it. Mounting it from node_modules would
|
|
27
|
+
# change the container config with every repository and recreate the proxy.
|
|
28
|
+
TELA_PROXY_STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/tela-proxy"
|
|
29
|
+
|
|
30
|
+
# Starts from several repositories can run at once and touch the same network,
|
|
31
|
+
# config file and container. mkdir is atomic and works the same on Linux and
|
|
32
|
+
# macOS, which has no flock.
|
|
33
|
+
TELA_PROXY_LOCK_DIR="$TELA_PROXY_STATE_DIR/lock"
|
|
34
|
+
TELA_PROXY_LOCK_TIMEOUT_SECONDS=120
|
|
35
|
+
|
|
36
|
+
acquire_proxy_lock() {
|
|
37
|
+
mkdir -p "$TELA_PROXY_STATE_DIR"
|
|
38
|
+
local waited=0
|
|
39
|
+
until mkdir "$TELA_PROXY_LOCK_DIR" 2>/dev/null; do
|
|
40
|
+
if [ "$waited" -ge "$TELA_PROXY_LOCK_TIMEOUT_SECONDS" ]; then
|
|
41
|
+
die "another tela-proxy start has held $TELA_PROXY_LOCK_DIR for ${TELA_PROXY_LOCK_TIMEOUT_SECONDS}s. If no start is running, remove that directory."
|
|
42
|
+
fi
|
|
43
|
+
sleep 1
|
|
44
|
+
waited=$((waited + 1))
|
|
45
|
+
done
|
|
46
|
+
trap 'rmdir "$TELA_PROXY_LOCK_DIR" 2>/dev/null || true' EXIT
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
release_proxy_lock() {
|
|
50
|
+
rmdir "$TELA_PROXY_LOCK_DIR" 2>/dev/null || true
|
|
51
|
+
trap - EXIT
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
proxy_compose() {
|
|
55
|
+
mkdir -p "$TELA_PROXY_STATE_DIR"
|
|
56
|
+
if ! cmp -s "$TELA_PROXY_HOME/traefik.yml" "$TELA_PROXY_STATE_DIR/traefik.yml"; then
|
|
57
|
+
# Replace the file in one rename, so Traefik never reads a partial copy.
|
|
58
|
+
cp "$TELA_PROXY_HOME/traefik.yml" "$TELA_PROXY_STATE_DIR/traefik.yml.$$"
|
|
59
|
+
mv "$TELA_PROXY_STATE_DIR/traefik.yml.$$" "$TELA_PROXY_STATE_DIR/traefik.yml"
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# The caller's Compose env (a worktree's COMPOSE_PROJECT_NAME, COMPOSE_FILE)
|
|
63
|
+
# must not leak into the proxy project.
|
|
64
|
+
TELA_PROXY_CONFIG_HASH="$(cat "$TELA_PROXY_HOME/compose.yaml" "$TELA_PROXY_HOME/traefik.yml" | sha256)" \
|
|
65
|
+
TELA_PROXY_TRAEFIK_CONFIG="$TELA_PROXY_STATE_DIR/traefik.yml" \
|
|
66
|
+
env -u COMPOSE_PROJECT_NAME -u COMPOSE_FILE -u COMPOSE_PROFILES \
|
|
67
|
+
docker compose -p "$TELA_PROXY_PROJECT" -f "$TELA_PROXY_HOME/compose.yaml" "$@"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
ensure_network() {
|
|
71
|
+
docker network inspect "$TELA_PROXY_NETWORK" >/dev/null 2>&1 \
|
|
72
|
+
|| docker network create --driver bridge "$TELA_PROXY_NETWORK" >/dev/null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# Prints the Compose project name for the current directory, honoring
|
|
76
|
+
# COMPOSE_PROJECT_NAME and COMPOSE_FILE from the environment or `.env`, since
|
|
77
|
+
# Compose resolves them itself. Prints nothing when there is no Compose project
|
|
78
|
+
# here. --no-env-resolution keeps a missing env_file from failing the lookup.
|
|
79
|
+
compose_project_name() {
|
|
80
|
+
# stderr goes to its own file: Compose can warn (an unset variable, an
|
|
81
|
+
# obsolete `version`) and still succeed, and the warning must not reach jq.
|
|
82
|
+
local output errors status=0
|
|
83
|
+
errors="$(mktemp)"
|
|
84
|
+
output="$(docker compose config --no-env-resolution --format json 2>"$errors")" || status=$?
|
|
85
|
+
if [ "$status" -eq 0 ]; then
|
|
86
|
+
rm -f "$errors"
|
|
87
|
+
jq -r '.name // empty' <<<"$output"
|
|
88
|
+
return
|
|
89
|
+
fi
|
|
90
|
+
output="$(cat "$errors")"
|
|
91
|
+
rm -f "$errors"
|
|
92
|
+
case "$output" in
|
|
93
|
+
*"no configuration file provided"*) return 0 ;;
|
|
94
|
+
esac
|
|
95
|
+
die "could not read the Compose project in $(pwd): $output"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# Prints COMPOSE_FILE as Compose sees it: from the environment, else from the
|
|
99
|
+
# `.env` in the current directory.
|
|
100
|
+
compose_file_setting() {
|
|
101
|
+
if [ -n "${COMPOSE_FILE:-}" ]; then
|
|
102
|
+
printf '%s\n' "$COMPOSE_FILE"
|
|
103
|
+
elif [ -f .env ]; then
|
|
104
|
+
sed -n -E 's/^[[:space:]]*(export[[:space:]]+)?COMPOSE_FILE[[:space:]]*=[[:space:]]*["'\'']?([^"'\'']*)["'\'']?[[:space:]]*$/\2/p' .env | tail -n 1
|
|
105
|
+
fi
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# Prints the Compose project directory for the current directory: the
|
|
109
|
+
# directory of the first COMPOSE_FILE entry, resolved from here, else the
|
|
110
|
+
# nearest ancestor with a default-named file. This is the working_dir label
|
|
111
|
+
# Compose writes on the containers it creates.
|
|
112
|
+
compose_project_dir() {
|
|
113
|
+
local compose_file first
|
|
114
|
+
compose_file="$(compose_file_setting)"
|
|
115
|
+
if [ -n "$compose_file" ]; then
|
|
116
|
+
first="${compose_file%%"${COMPOSE_PATH_SEPARATOR:-:}"*}"
|
|
117
|
+
(cd "$(dirname "$first")" && pwd -P)
|
|
118
|
+
return
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
local dir name
|
|
122
|
+
dir="$(pwd -P)"
|
|
123
|
+
while :; do
|
|
124
|
+
for name in compose.yaml compose.yml docker-compose.yaml docker-compose.yml; do
|
|
125
|
+
if [ -f "$dir/$name" ]; then
|
|
126
|
+
printf '%s\n' "$dir"
|
|
127
|
+
return
|
|
128
|
+
fi
|
|
129
|
+
done
|
|
130
|
+
[ "$dir" = "/" ] && return
|
|
131
|
+
dir="$(dirname "$dir")"
|
|
132
|
+
done
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# Fails when containers of this Compose project were started from another
|
|
136
|
+
# directory that still exists: `up` would recreate them and `down -v` would
|
|
137
|
+
# delete their volumes. Containers from a directory that is gone (a removed
|
|
138
|
+
# worktree) only warn, and are taken over.
|
|
139
|
+
check_project_conflict() {
|
|
140
|
+
local project project_dir
|
|
141
|
+
project="$(compose_project_name)"
|
|
142
|
+
[ -n "$project" ] || return 0
|
|
143
|
+
|
|
144
|
+
# Compose found a project, so failing to place it must not skip the check.
|
|
145
|
+
project_dir="$(compose_project_dir)"
|
|
146
|
+
[ -n "$project_dir" ] \
|
|
147
|
+
|| die "could not find the directory of Compose project '$project' from $(pwd)"
|
|
148
|
+
|
|
149
|
+
local dirs other other_real
|
|
150
|
+
dirs="$(docker ps -a \
|
|
151
|
+
--filter "label=com.docker.compose.project=$project" \
|
|
152
|
+
--format '{{ .Label "com.docker.compose.project.working_dir" }}' | sort -u)" \
|
|
153
|
+
|| die "could not list the containers of Compose project '$project'"
|
|
154
|
+
|
|
155
|
+
while IFS= read -r other; do
|
|
156
|
+
[ -n "$other" ] || continue
|
|
157
|
+
if [ ! -d "$other" ]; then
|
|
158
|
+
warn "Compose project '$project' has containers from $other, which no longer exists. They will be recreated from $project_dir and keep their volumes."
|
|
159
|
+
continue
|
|
160
|
+
fi
|
|
161
|
+
other_real="$(cd "$other" && pwd -P)"
|
|
162
|
+
[ "$other_real" = "$project_dir" ] && continue
|
|
163
|
+
die "Compose project '$project' is already used by $other_real.
|
|
164
|
+
Starting it from $project_dir would recreate those containers and share their volumes.
|
|
165
|
+
Run this checkout under its own project name instead:
|
|
166
|
+
mise run tela-proxy:worktree [label] -- <command>"
|
|
167
|
+
done <<< "$dirs"
|
|
168
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Static config for the shared local proxy started by `mise run tela-proxy`.
|
|
2
|
+
#
|
|
3
|
+
# Every Compose container on tela-network gets an HTTP route without declaring
|
|
4
|
+
# anything. The host comes from the Compose project and service labels:
|
|
5
|
+
#
|
|
6
|
+
# service auth-api, project auth -> api.auth.localhost
|
|
7
|
+
# service auth, project auth -> auth.localhost
|
|
8
|
+
# service dashboard, project auth -> dashboard.auth.localhost
|
|
9
|
+
# service auth-api, project auth_feat-x -> api.auth-feat-x.localhost
|
|
10
|
+
#
|
|
11
|
+
# The `_` in a project name separates the base from a worktree label, set by
|
|
12
|
+
# `mise run tela-proxy:worktree`. A container opts out with traefik.enable=false
|
|
13
|
+
# or replaces its host with a traefik.http.routers.<name>.rule label.
|
|
14
|
+
|
|
15
|
+
entryPoints:
|
|
16
|
+
web:
|
|
17
|
+
address: ':80'
|
|
18
|
+
|
|
19
|
+
providers:
|
|
20
|
+
docker:
|
|
21
|
+
network: tela-network
|
|
22
|
+
exposedByDefault: true
|
|
23
|
+
defaultRule: |-
|
|
24
|
+
{{- $project := index .Labels "com.docker.compose.project" -}}
|
|
25
|
+
{{- $service := index .Labels "com.docker.compose.service" -}}
|
|
26
|
+
{{- if or (not $project) (not $service) -}}
|
|
27
|
+
Host(`{{ normalize .Name }}.localhost`)
|
|
28
|
+
{{- else -}}
|
|
29
|
+
{{- $parts := splitList "_" $project -}}
|
|
30
|
+
{{- $base := first $parts -}}
|
|
31
|
+
{{- /* Every part is kept, so team_app_one and team_app_two stay apart. */ -}}
|
|
32
|
+
{{- $domain := join "-" $parts -}}
|
|
33
|
+
{{- $host := printf "%s.%s.localhost" $service $domain -}}
|
|
34
|
+
{{- if eq $service $base -}}
|
|
35
|
+
{{- $host = printf "%s.localhost" $domain -}}
|
|
36
|
+
{{- else if hasPrefix (printf "%s-" $base) $service -}}
|
|
37
|
+
{{- $host = printf "%s.%s.localhost" (trimPrefix (printf "%s-" $base) $service) $domain -}}
|
|
38
|
+
{{- end -}}
|
|
39
|
+
Host(`{{ $host }}`)
|
|
40
|
+
{{- end -}}
|
|
41
|
+
|
|
42
|
+
api:
|
|
43
|
+
dashboard: true
|
|
44
|
+
|
|
45
|
+
log:
|
|
46
|
+
level: INFO
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#MISE description="Run a command, or open a shell, with Compose isolated under a worktree project name"
|
|
3
|
+
#MISE alias=["worktree", "wt"]
|
|
4
|
+
#MISE interactive=true
|
|
5
|
+
#MISE tools={jq="1.8.2"}
|
|
6
|
+
#USAGE arg "[label]" help="Worktree label. Defaults to the name of the current git worktree."
|
|
7
|
+
#USAGE arg "[cmd]..." var=#true double_dash="required" help="Command to run. Opens $SHELL when omitted."
|
|
8
|
+
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
# shellcheck source=lib.sh
|
|
12
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
|
13
|
+
|
|
14
|
+
enter_caller_dir
|
|
15
|
+
|
|
16
|
+
# mise passes the label (when given) followed by the command, and a single empty
|
|
17
|
+
# argument when there is neither.
|
|
18
|
+
label="${usage_label:-}"
|
|
19
|
+
if [ -n "$label" ] && [ "${1:-}" = "$label" ]; then
|
|
20
|
+
shift
|
|
21
|
+
fi
|
|
22
|
+
if [ "$#" -eq 1 ] && [ -z "$1" ]; then
|
|
23
|
+
shift
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
if [ -n "${TELA_PROXY_WORKTREE:-}" ]; then
|
|
27
|
+
die "already inside worktree '$TELA_PROXY_WORKTREE'. Run commands directly, or exit this shell first."
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
if [ -z "$label" ]; then
|
|
31
|
+
git_dir="$(git rev-parse --absolute-git-dir 2>/dev/null)" \
|
|
32
|
+
|| die "not inside a git repository. Pass a label: mise run tela-proxy:worktree <label>"
|
|
33
|
+
common_dir="$(cd "$(git rev-parse --git-common-dir)" && pwd -P)"
|
|
34
|
+
if [ "$(cd "$git_dir" && pwd -P)" = "$common_dir" ]; then
|
|
35
|
+
die "this is the main checkout, not a linked worktree. Pass a label: mise run tela-proxy:worktree <label>"
|
|
36
|
+
fi
|
|
37
|
+
label="$(basename "$(git rev-parse --show-toplevel)")"
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
label="$(printf '%s' "$label" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9-]+/-/g; s/-+/-/g; s/^-//; s/-$//')"
|
|
41
|
+
[ -n "$label" ] || die "the worktree label is empty after keeping only a-z, 0-9 and '-'"
|
|
42
|
+
|
|
43
|
+
base="$(unset COMPOSE_PROJECT_NAME; compose_project_name)"
|
|
44
|
+
[ -n "$base" ] || die "no Compose project found in $(pwd)"
|
|
45
|
+
case "$base" in
|
|
46
|
+
*_*) die "Compose project name '$base' contains '_', which the proxy uses to separate the worktree label. Rename it with 'name:' in the Compose file." ;;
|
|
47
|
+
esac
|
|
48
|
+
|
|
49
|
+
export COMPOSE_PROJECT_NAME="${base}_${label}"
|
|
50
|
+
export TELA_LOCAL_DOMAIN="${base}-${label}.localhost"
|
|
51
|
+
export TELA_PROXY_WORKTREE="$label"
|
|
52
|
+
|
|
53
|
+
check_project_conflict
|
|
54
|
+
|
|
55
|
+
info "Compose project: $COMPOSE_PROJECT_NAME"
|
|
56
|
+
info "Hosts: http://<service>.$TELA_LOCAL_DOMAIN"
|
|
57
|
+
|
|
58
|
+
if [ "$#" -gt 0 ]; then
|
|
59
|
+
exec "$@"
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
info "Opening ${SHELL:-bash} for worktree '$label'. Exit it to leave."
|
|
63
|
+
exec "${SHELL:-bash}"
|