gemini-computer-use 0.4.2 → 0.4.4

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.
@@ -1,86 +0,0 @@
1
- #!/usr/bin/env bash
2
- # ==============================================================================
3
- # Antigravity Mesh - resolve the public domain, once, for every shell script.
4
- #
5
- # Sourced by deploy_gateway.sh and ops/nginx/render-domain.sh. The order below
6
- # is the same order core/domain.py implements for the runtime; keeping the shell
7
- # side in one file means "where does the domain come from" has a single answer
8
- # for scripts too.
9
- #
10
- # 1. MESH_PUBLIC_URL in the environment;
11
- # 2. MESH_PUBLIC_URL (or the AGY_PUBLIC_BASE_URL alias) in the domain file -
12
- # /etc/antigravity-mesh/domain.env by default, MESH_DOMAIN_FILE overrides;
13
- # 3. core/domain.py's DEFAULT_PUBLIC_BASE_URL, read from the checkout.
14
- #
15
- # On success exports:
16
- # MESH_DOMAIN_URL https://mesh.example.com (no trailing slash)
17
- # MESH_DOMAIN_HOST mesh.example.com
18
- # MESH_DOMAIN_SOURCE human-readable origin, for logs
19
- # MESH_DOMAIN_FILE_RESOLVED the domain file path that was consulted
20
- # and returns 0. Returns 1 when nothing yields a domain.
21
- # ==============================================================================
22
-
23
- mesh_resolve_domain() {
24
- local repo_dir="${1:-.}"
25
- local value="" candidate_python="" source=""
26
-
27
- MESH_DOMAIN_FILE_RESOLVED="${MESH_DOMAIN_FILE:-/etc/antigravity-mesh/domain.env}"
28
-
29
- if [ -n "${MESH_PUBLIC_URL:-}" ]; then
30
- value="$MESH_PUBLIC_URL"
31
- source="MESH_PUBLIC_URL environment variable"
32
- fi
33
-
34
- local domain_file="$MESH_DOMAIN_FILE_RESOLVED"
35
- if [ -z "$value" ] && [ -f "$domain_file" ]; then
36
- # Windows PowerShell writes a UTF-8 BOM, and it would sit in front of the
37
- # first key, so 'MESH_PUBLIC_URL=' would never match. Strip it first.
38
- local domain_text
39
- if [ "$(head -c 3 "$domain_file" 2>/dev/null | od -An -tx1 | tr -d ' \n')" = "efbbbf" ]; then
40
- domain_text="$(tail -c +4 "$domain_file")"
41
- else
42
- domain_text="$(cat "$domain_file")"
43
- fi
44
- value="$(printf '%s\n' "$domain_text" | sed -n 's/^[[:space:]]*MESH_PUBLIC_URL[[:space:]]*=[[:space:]]*//p' | head -n 1)"
45
- if [ -z "$value" ]; then
46
- value="$(printf '%s\n' "$domain_text" | sed -n 's/^[[:space:]]*AGY_PUBLIC_BASE_URL[[:space:]]*=[[:space:]]*//p' | head -n 1)"
47
- fi
48
- value="$(printf '%s' "$value" | tr -d '"' | tr -d "'" | tr -d '\r')"
49
- [ -n "$value" ] && source="$domain_file"
50
- fi
51
-
52
- if [ -z "$value" ]; then
53
- # Path candidates, in order. The Microsoft Store "python.exe" (and its
54
- # python3.exe twin) is an App Execution Alias: it is on PATH, it prints
55
- # nothing usable, and it stops working after a Store repair - the same trap
56
- # the Windows installer refuses to pin into an autostart entry. Skipping it
57
- # by path is what lets the real interpreter behind it be found, or makes the
58
- # resolver refuse honestly instead of trusting a stub.
59
- local resolved_python=""
60
- for candidate_python in python3 python py; do
61
- command -v "$candidate_python" >/dev/null 2>&1 || continue
62
- resolved_python="$(command -v "$candidate_python")"
63
- case "$resolved_python" in
64
- *[Ww]indows[Aa]pps*) continue ;;
65
- esac
66
- value="$("$resolved_python" -c "import sys; sys.path.insert(0, r'$repo_dir'); from core import domain; print(domain.DEFAULT_PUBLIC_BASE_URL)" 2>/dev/null || true)"
67
- [ -n "$value" ] && { source="$repo_dir/core/domain.py default"; break; }
68
- done
69
- fi
70
-
71
- if [ -z "$value" ]; then
72
- MESH_DOMAIN_URL=""; MESH_DOMAIN_HOST=""; MESH_DOMAIN_SOURCE=""
73
- export MESH_DOMAIN_URL MESH_DOMAIN_HOST MESH_DOMAIN_SOURCE MESH_DOMAIN_FILE_RESOLVED
74
- return 1
75
- fi
76
-
77
- case "$value" in
78
- http://*|https://*) ;;
79
- *) value="https://$value" ;;
80
- esac
81
- MESH_DOMAIN_URL="${value%/}"
82
- MESH_DOMAIN_HOST="$(printf '%s' "$MESH_DOMAIN_URL" | sed -e 's|^[A-Za-z][A-Za-z0-9+.-]*://||' -e 's|/.*$||')"
83
- MESH_DOMAIN_SOURCE="$source"
84
- export MESH_DOMAIN_URL MESH_DOMAIN_HOST MESH_DOMAIN_SOURCE MESH_DOMAIN_FILE_RESOLVED
85
- return 0
86
- }
@@ -1,77 +0,0 @@
1
- # Gateway nginx configuration
2
-
3
- Two files, and neither holds a domain literal:
4
-
5
- | File | Role |
6
- | :--- | :--- |
7
- | `antigravity-mesh-mcp.conf` | the snippet included from the gateway server block: installers, node bootstrap payload, public file shares, MCP endpoints |
8
- | `mesh-domain.vhost.template` | the HTTPS vhost for the gateway, with `__MESH_DOMAIN__` and `__MESH_CERT_NAME__` placeholders |
9
- | `render-domain.sh` | renders the template from the one place the domain is declared |
10
-
11
- ## Where the domain comes from
12
-
13
- `render-domain.sh` sources `ops/mesh-domain.sh`, which resolves the domain exactly
14
- as `core/domain.py` does at runtime:
15
-
16
- 1. `MESH_PUBLIC_URL` in the environment;
17
- 2. `MESH_PUBLIC_URL` (or the `AGY_PUBLIC_BASE_URL` alias) in the domain file —
18
- `/etc/antigravity-mesh/domain.env` by default, `MESH_DOMAIN_FILE` overrides it;
19
- 3. `core/domain.py`'s `DEFAULT_PUBLIC_BASE_URL`, the single declaration of the
20
- project default.
21
-
22
- `deploy_gateway.sh` uses the same script, so the gateway, the node, the installers
23
- and nginx all agree on one value.
24
-
25
- ## Rendering
26
-
27
- ```bash
28
- ops/nginx/render-domain.sh # render into ./rendered/, change nothing
29
- ops/nginx/render-domain.sh --out DIR # render into DIR
30
- ops/nginx/render-domain.sh --check # diff the rendered vhost against the live one
31
- ops/nginx/render-domain.sh --apply # install into /etc/nginx
32
- ```
33
-
34
- `--apply` backs up the current vhost and snippet into
35
- `/root/backups/antigravity-mesh/nginx-<timestamp>/`, runs `nginx -t`, and only then
36
- reloads; on a failed test nothing is reloaded and the rollback command is printed.
37
-
38
- `--check` is the way to prove the live host still matches the repository: with the
39
- domain configured correctly it renders byte-identically to
40
- `/etc/nginx/sites-available/<domain>`.
41
-
42
- ## Switching the domain
43
-
44
- 1. Put the new value in the one place — `MESH_PUBLIC_URL` in
45
- `/etc/antigravity-mesh/domain.env` (node hosts:
46
- `%USERPROFILE%\.config\antigravity-mesh\domain.env`).
47
- 2. Issue the certificate for the new name (certbot, or `tailscale cert` when the
48
- name is a Tailscale node name).
49
- 3. `ops/nginx/render-domain.sh --apply`.
50
- 4. `MESH_GATEWAY=… deploy_gateway.sh` to publish installers carrying the new
51
- default, if the domain is also served from that host.
52
-
53
- ## Two lines in the snippet are load-bearing for MCP clients
54
-
55
- ### `Connection ""` on `/sse`, `/mcp`, `/messages`
56
-
57
- These are plain HTTP endpoints. The WebSocket idiom
58
- `proxy_set_header Connection "upgrade"` was applied to them too, which advertises
59
- an upgrade on every POST. Only `/ws/tunnel` is a real WebSocket and keeps it.
60
-
61
- ### `keepalive_timeout 0` on `/sse`, `/mcp`, `/messages`
62
-
63
- Google's MCP frontend sends a chunked `DELETE` whose terminator can arrive after
64
- nginx has already answered. On a kept-alive connection those leftover bytes were
65
- parsed as a new request line — nginx logged `"0" 400` for it (79 times in 12
66
- minutes) and closed the connection, losing whatever the client had in flight;
67
- the client then reported `DEADLINE_EXCEEDED` after its 60 s deadline while the
68
- tool had already run on the node. Closing the connection after each response
69
- removes the desync entirely.
70
-
71
- ### `default_type text/plain`, never `add_header Content-Type`
72
-
73
- `add_header Content-Type …` *appends* a header, and `alias` has already set one by
74
- extension, so the response carried two `Content-Type` values
75
- (`application/octet-stream` then `text/plain; charset=utf-8`). A client that honours
76
- the first treats the installer as binary: PowerShell's `Invoke-WebRequest` then
77
- hands `Invoke-Expression` a byte array, and `irm …/install.ps1 | iex` cannot work.
@@ -1,172 +0,0 @@
1
- # --- Antigravity Mesh FastMCP Gateway Endpoints ---
2
- # --- Scanner / secret-hunting noise control ---
3
- # Automated probes for .env, secrets, backups and WordPress files always
4
- # 404. Answer them here so they never reach the app or its logs.
5
- location ~* \.(env|git|svn|bak|old|sql|log|ini|conf|yml|yaml|json)$ {
6
- access_log off;
7
- return 404;
8
- }
9
- location ~* /(wp-content|wp-admin|wp-config|vendor|node_modules)/ {
10
- access_log off;
11
- return 404;
12
- }
13
-
14
- # --- Served bootstrap files -------------------------------------------
15
- # "default_type text/plain", never "add_header Content-Type ...": add_header
16
- # APPENDS a header, and `alias` has already set Content-Type by extension, so
17
- # the response carried TWO Content-Type values
18
- # ("application/octet-stream" then "text/plain; charset=utf-8"). A client that
19
- # honours the first one treats the script as binary: PowerShell's
20
- # Invoke-WebRequest then hands Invoke-Expression a byte array instead of text,
21
- # and the documented `irm .../install.ps1 | iex` cannot work.
22
- location /install.sh {
23
- alias /var/www/antigravity-mesh/install.sh;
24
- default_type text/plain;
25
- charset utf-8;
26
- add_header Access-Control-Allow-Origin *;
27
- }
28
-
29
- location /install.ps1 {
30
- alias /var/www/antigravity-mesh/install.ps1;
31
- default_type text/plain;
32
- charset utf-8;
33
- add_header Access-Control-Allow-Origin *;
34
- }
35
-
36
- location = /gateway.py {
37
- # Source is published in the GitHub repository; the live gateway does not
38
- # need to serve it. Keeps the auth/tunnel logic off the public surface.
39
- access_log off;
40
- return 404;
41
- }
42
-
43
- # --- Only the bootstrap files are public -------------------------------
44
- # A curl/npx install needs exactly these; the rest of core/ (hooks, policies,
45
- # schemas, subagents, triggers, code_cache, agent_harness) is internal and is
46
- # not fetched by any installer, so it is not served.
47
- location ~ ^/core/(hooks|policies|schemas|subagents|triggers|code_cache|agent_harness)\.py$ {
48
- access_log off;
49
- return 404;
50
- }
51
-
52
- location /core/ {
53
- alias /var/www/antigravity-mesh/core/;
54
- default_type text/plain;
55
- charset utf-8;
56
- add_header Access-Control-Allow-Origin *;
57
- }
58
-
59
- location /skills/ {
60
- alias /var/www/antigravity-mesh/skills/;
61
- default_type text/plain;
62
- charset utf-8;
63
- add_header Access-Control-Allow-Origin *;
64
- }
65
-
66
- # The Windows watchdog and the doctor travel with the node bootstrap: the
67
- # served install.ps1 registers the scheduled task that runs the watchdog, and
68
- # the README tells the operator to run the doctor, so both must be fetchable
69
- # by a node that installed itself from this domain.
70
- location /ops/ {
71
- alias /var/www/antigravity-mesh/ops/;
72
- default_type text/plain;
73
- charset utf-8;
74
- add_header Access-Control-Allow-Origin *;
75
- }
76
-
77
- location /mcp {
78
- proxy_pass http://127.0.0.1:8096;
79
- proxy_http_version 1.1;
80
- proxy_set_header Upgrade $http_upgrade;
81
- proxy_set_header Connection "";
82
- keepalive_timeout 0;
83
- proxy_set_header Host $host;
84
- proxy_set_header X-Real-IP $remote_addr;
85
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
86
- proxy_set_header X-Forwarded-Proto $scheme;
87
- proxy_set_header X-Target-User "";
88
- proxy_buffering off;
89
- proxy_cache off;
90
- proxy_read_timeout 86400s;
91
- proxy_send_timeout 86400s;
92
- }
93
-
94
- location /sse {
95
- proxy_pass http://127.0.0.1:8096/sse;
96
- proxy_http_version 1.1;
97
- proxy_set_header Upgrade $http_upgrade;
98
- proxy_set_header Connection "";
99
- keepalive_timeout 0;
100
- proxy_set_header Host $host;
101
- proxy_set_header X-Real-IP $remote_addr;
102
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
103
- proxy_set_header X-Forwarded-Proto $scheme;
104
- proxy_set_header X-Target-User "";
105
- proxy_buffering off;
106
- proxy_cache off;
107
- proxy_read_timeout 86400s;
108
- proxy_send_timeout 86400s;
109
- }
110
-
111
- location /messages {
112
- proxy_pass http://127.0.0.1:8096/messages;
113
- proxy_http_version 1.1;
114
- proxy_set_header Upgrade $http_upgrade;
115
- proxy_set_header Connection "";
116
- keepalive_timeout 0;
117
- proxy_set_header Host $host;
118
- proxy_set_header X-Real-IP $remote_addr;
119
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
120
- proxy_set_header X-Forwarded-Proto $scheme;
121
- proxy_set_header X-Target-User "";
122
- proxy_buffering off;
123
- proxy_cache off;
124
- proxy_read_timeout 86400s;
125
- proxy_send_timeout 86400s;
126
- }
127
-
128
- location /ws/tunnel {
129
- proxy_pass http://127.0.0.1:8096/ws/tunnel;
130
- proxy_http_version 1.1;
131
- proxy_set_header Upgrade $http_upgrade;
132
- proxy_set_header Connection "upgrade";
133
- proxy_set_header Host $host;
134
- proxy_set_header X-Real-IP $remote_addr;
135
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
136
- proxy_set_header X-Forwarded-Proto $scheme;
137
- proxy_set_header X-Target-User "";
138
- proxy_buffering off;
139
- proxy_cache off;
140
- proxy_read_timeout 86400s;
141
- proxy_send_timeout 86400s;
142
- }
143
-
144
- location /api/register {
145
- limit_req zone=mesh_register burst=5 nodelay;
146
- proxy_pass http://127.0.0.1:8096/api/register;
147
- proxy_http_version 1.1;
148
- proxy_set_header Host $host;
149
- proxy_set_header X-Real-IP $remote_addr;
150
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
151
- proxy_set_header X-Forwarded-Proto $scheme;
152
- proxy_set_header X-Target-User "";
153
- }
154
-
155
- location /health-mesh {
156
- proxy_pass http://127.0.0.1:8096/health;
157
- proxy_http_version 1.1;
158
- proxy_set_header Host $host;
159
- proxy_set_header X-Target-User "";
160
- }
161
-
162
- location = /.well-known/oauth-protected-resource {
163
- proxy_pass http://127.0.0.1:8096;
164
- proxy_http_version 1.1;
165
- proxy_set_header Host $host;
166
- }
167
-
168
- location ^~ /.well-known/oauth-protected-resource/ {
169
- proxy_pass http://127.0.0.1:8096;
170
- proxy_http_version 1.1;
171
- proxy_set_header Host $host;
172
- }
@@ -1,58 +0,0 @@
1
- # ==============================================================================
2
- # Antigravity Mesh - the gateway's HTTPS vhost, as a template
3
- #
4
- # Rendered by ops/nginx/render-domain.sh, which takes the domain from the one
5
- # place that declares it (MESH_PUBLIC_URL, the domain file, or core/domain.py).
6
- # Do not edit the rendered file on the host: edit this template and re-render,
7
- # or the domain ends up in two places again.
8
- #
9
- # Placeholders:
10
- # __MESH_DOMAIN__ the public domain, e.g. mesh.example.com
11
- # __MESH_CERT_NAME__ the certbot lineage name (usually the same as the domain)
12
- #
13
- # Only the Mesh surface lives here. Other names that may share a host
14
- # (api-agy., agy/, /v1/, ...) belong to other services and keep their own files.
15
- # ==============================================================================
16
-
17
- # HTTP -> HTTPS, plus the ACME path so certificate renewal keeps working.
18
- server {
19
- listen 80;
20
- listen [::]:80;
21
- server_name __MESH_DOMAIN__;
22
-
23
- location ^~ /.well-known/acme-challenge/ {
24
- root /var/www/acme;
25
- default_type text/plain;
26
- }
27
- location / {
28
- return 301 https://$host$request_uri;
29
- }
30
- }
31
-
32
- # HTTPS: the Mesh gateway itself.
33
- server {
34
- listen 443 ssl;
35
- listen [::]:443 ssl;
36
- server_name __MESH_DOMAIN__;
37
-
38
- ssl_certificate /etc/letsencrypt/live/__MESH_CERT_NAME__/fullchain.pem;
39
- ssl_certificate_key /etc/letsencrypt/live/__MESH_CERT_NAME__/privkey.pem;
40
- include /etc/letsencrypt/options-ssl-nginx.conf;
41
- ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
42
-
43
- # The installers, the node bootstrap payload and the public file shares.
44
- include /etc/nginx/snippets/antigravity-mesh-mcp.conf;
45
-
46
- # The MCP surface: SSE and Streamable HTTP stream, so buffering stays off.
47
- location / {
48
- proxy_pass http://127.0.0.1:8096;
49
- proxy_set_header Host $host;
50
- proxy_set_header X-Real-IP $remote_addr;
51
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
52
- proxy_set_header X-Forwarded-Proto $scheme;
53
- proxy_http_version 1.1;
54
- proxy_set_header Upgrade $http_upgrade;
55
- proxy_set_header Connection "upgrade";
56
- proxy_read_timeout 86400;
57
- }
58
- }
@@ -1,123 +0,0 @@
1
- #!/usr/bin/env bash
2
- # ==============================================================================
3
- # Antigravity Mesh - render the nginx configuration for the public domain.
4
- #
5
- # The domain comes from the one place ops/mesh-domain.sh resolves it from:
6
- # MESH_PUBLIC_URL, then the domain file, then core/domain.py's single default.
7
- # Nothing here holds a domain literal.
8
- #
9
- # Usage:
10
- # ops/nginx/render-domain.sh # render into ./rendered/
11
- # ops/nginx/render-domain.sh --out DIR # render into DIR
12
- # ops/nginx/render-domain.sh --apply # install into /etc/nginx
13
- # # (backup + nginx -t + reload)
14
- # ops/nginx/render-domain.sh --check # fail if the rendered vhost
15
- # # differs from the live one
16
- # Env:
17
- # MESH_DOMAIN_FILE path to the domain file
18
- # MESH_CERT_NAME certbot lineage name (default: the domain)
19
- # MESH_NGINX_DIR nginx config root for --apply (default /etc/nginx)
20
- # ==============================================================================
21
- set -euo pipefail
22
-
23
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
24
- REPO_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
25
- TEMPLATE="$SCRIPT_DIR/mesh-domain.vhost.template"
26
- SNIPPET="$SCRIPT_DIR/antigravity-mesh-mcp.conf"
27
-
28
- # shellcheck source=../mesh-domain.sh
29
- . "$REPO_DIR/ops/mesh-domain.sh"
30
-
31
- BOLD=""; GREEN=""; YELLOW=""; RED=""; RESET=""
32
- if [ -t 1 ]; then
33
- BOLD="\033[1m"; GREEN="\033[0;32m"; YELLOW="\033[1;33m"; RED="\033[0;31m"; RESET="\033[0m"
34
- fi
35
- info() { printf "${BOLD}%s${RESET}\n" "$*"; }
36
- ok() { printf "${GREEN}✓ %s${RESET}\n" "$*"; }
37
- die() { printf "${RED}✗ %s${RESET}\n" "$*" >&2; exit 1; }
38
-
39
- OUT_DIR=""
40
- APPLY=false
41
- CHECK=false
42
- while [ $# -gt 0 ]; do
43
- case "$1" in
44
- --out) OUT_DIR="${2:-}"; [ -n "$OUT_DIR" ] || die "--out needs a directory"; shift 2 ;;
45
- --apply) APPLY=true; shift ;;
46
- --check) CHECK=true; shift ;;
47
- -h|--help) sed -n '2,26p' "$0" | sed 's/^#\{1,2\} \{0,1\}//'; exit 0 ;;
48
- *) die "Unknown parameter: $1 (try --help)" ;;
49
- esac
50
- done
51
-
52
- mesh_resolve_domain "$REPO_DIR" \
53
- || die "Cannot determine the public domain: export MESH_PUBLIC_URL, write MESH_PUBLIC_URL into ${MESH_DOMAIN_FILE:-/etc/antigravity-mesh/domain.env}, or run from a checkout that has core/domain.py"
54
-
55
- CERT_NAME="${MESH_CERT_NAME:-$MESH_DOMAIN_HOST}"
56
- NGINX_DIR="${MESH_NGINX_DIR:-/etc/nginx}"
57
-
58
- info "Rendering the Mesh nginx configuration"
59
- echo " domain : $MESH_DOMAIN_URL"
60
- echo " server_name : $MESH_DOMAIN_HOST"
61
- echo " cert name : $CERT_NAME"
62
- echo " source : $MESH_DOMAIN_SOURCE"
63
-
64
- render() {
65
- local dst="$1"
66
- mkdir -p "$(dirname "$dst")"
67
- sed -e "s|__MESH_DOMAIN__|$MESH_DOMAIN_HOST|g" \
68
- -e "s|__MESH_CERT_NAME__|$CERT_NAME|g" \
69
- "$TEMPLATE" > "$dst"
70
- grep -q '__MESH_' "$dst" && die "unsubstituted placeholder left in $dst"
71
- return 0
72
- }
73
-
74
- if [ "$CHECK" = true ]; then
75
- live_vhost="$NGINX_DIR/sites-available/$MESH_DOMAIN_HOST"
76
- [ -f "$live_vhost" ] || die "no live vhost at $live_vhost to compare against"
77
- tmp="$(mktemp)"
78
- trap 'rm -f "$tmp"' EXIT
79
- render "$tmp"
80
- if diff -u "$live_vhost" "$tmp" >/dev/null; then
81
- ok "the live vhost matches the rendered template"
82
- exit 0
83
- fi
84
- diff -u "$live_vhost" "$tmp" || true
85
- die "the live vhost differs from the rendered template (see the diff above)"
86
- fi
87
-
88
- if [ "$APPLY" = true ]; then
89
- command -v nginx >/dev/null 2>&1 || die "nginx is not installed here; use --out to render elsewhere"
90
- TS="$(date +%Y%m%d-%H%M%S)"
91
- backup_dir="/root/backups/antigravity-mesh/nginx-$TS"
92
- mkdir -p "$backup_dir"
93
- vhost_target="$NGINX_DIR/sites-available/$MESH_DOMAIN_HOST"
94
- snippet_target="$NGINX_DIR/snippets/antigravity-mesh-mcp.conf"
95
-
96
- [ -f "$vhost_target" ] && cp -a "$vhost_target" "$backup_dir/" || true
97
- [ -f "$snippet_target" ] && cp -a "$snippet_target" "$backup_dir/" || true
98
-
99
- render "$vhost_target"
100
- install -m 644 "$SNIPPET" "$snippet_target"
101
- ln -sfn "$vhost_target" "$NGINX_DIR/sites-enabled/$MESH_DOMAIN_HOST"
102
-
103
- if ! nginx -t 2>&1; then
104
- warn_rollback="cp -a $backup_dir/. $NGINX_DIR/ && nginx -t && systemctl reload nginx"
105
- printf "${RED}✗ nginx rejected the rendered configuration${RESET}\n" >&2
106
- echo " the previous files are in $backup_dir" >&2
107
- echo " rollback: $warn_rollback" >&2
108
- exit 1
109
- fi
110
- systemctl reload nginx
111
- ok "applied; backup in $backup_dir"
112
- echo " rollback: cp -a $backup_dir/. $NGINX_DIR/ && nginx -t && systemctl reload nginx"
113
- exit 0
114
- fi
115
-
116
- # Default: render next to the caller, so nothing on the host changes.
117
- OUT_DIR="${OUT_DIR:-$PWD/rendered}"
118
- render "$OUT_DIR/$MESH_DOMAIN_HOST"
119
- install -m 644 "$SNIPPET" "$OUT_DIR/antigravity-mesh-mcp.conf"
120
- ok "rendered into $OUT_DIR"
121
- echo " vhost : $OUT_DIR/$MESH_DOMAIN_HOST"
122
- echo " snippet : $OUT_DIR/antigravity-mesh-mcp.conf"
123
- echo " apply : $0 --apply"