gemini-computer-use 0.2.3 → 0.4.1
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 +935 -148
- package/bin/cli.js +112 -17
- package/core/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/__pycache__/agent.cpython-313.pyc +0 -0
- package/core/__pycache__/code_cache.cpython-313.pyc +0 -0
- package/core/__pycache__/mcp_tools.cpython-313.pyc +0 -0
- package/core/__pycache__/schemas.cpython-313.pyc +0 -0
- package/core/__pycache__/server.cpython-313.pyc +0 -0
- package/core/__pycache__/vitals.cpython-313.pyc +0 -0
- package/core/__pycache__/web_share.cpython-313.pyc +0 -0
- package/core/agent.py +688 -27
- package/core/agent_harness.py +9 -2
- package/core/code_cache.py +15 -3
- package/core/device.py +2400 -0
- package/core/domain.py +303 -0
- package/core/mcp_tools.py +1949 -110
- package/core/policies.py +35 -5
- package/core/server.py +78 -32
- package/core/termux.py +916 -0
- package/core/updater.py +1713 -0
- package/core/version.py +31 -0
- package/core/vitals.py +66 -2
- package/core/web_share.py +814 -0
- package/gateway.py +1469 -98
- package/install-gui.cmd +40 -0
- package/install-gui.ps1 +1882 -0
- package/install-gui.strings.json +211 -0
- package/install.ps1 +960 -92
- package/install.sh +1214 -81
- package/ops/doctor.ps1 +735 -0
- package/ops/mesh-domain.sh +86 -0
- package/ops/nginx/README.md +77 -0
- package/ops/nginx/antigravity-mesh-mcp.conf +172 -0
- package/ops/nginx/mesh-domain.vhost.template +58 -0
- package/ops/nginx/render-domain.sh +123 -0
- package/ops/update.ps1 +373 -0
- package/ops/windows/README.md +298 -0
- package/ops/windows/agent-watchdog.ps1 +575 -0
- package/ops/windows/run-hidden.vbs +64 -0
- package/package.json +6 -2
- package/skills/orchestrator.md +12 -0
|
@@ -0,0 +1,86 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
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.
|
|
@@ -0,0 +1,172 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
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"
|