tunneling
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
.env
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
README.md
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Absolute host directory mounted at /shared.
|
||||||
|
SHARED_PATH=/absolute/path/to/shared
|
||||||
|
|
||||||
|
# Public base URL assigned to the Cloudflare Tunnel.
|
||||||
|
MCP_PUBLIC_URL=https://your-domain.example
|
||||||
|
|
||||||
|
# Token for a remotely managed Cloudflare Tunnel.
|
||||||
|
CLOUDFLARE_TUNNEL_TOKEN=replace-with-your-tunnel-token
|
||||||
|
|
||||||
|
# Optional settings.
|
||||||
|
TZ=UTC
|
||||||
|
COKACREMOTE_REF=main
|
||||||
|
# WORKMACHINE_IMAGE=workmachine:local
|
||||||
|
# MCP_ALLOWED_HOSTS=
|
||||||
|
# MCP_OAUTH_APPROVAL_KEY=
|
||||||
|
# MCP_AUTH_TOKEN=
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
.env
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
ARG TIMEZONE=UTC
|
||||||
|
ARG COKACREMOTE_REPOSITORY=https://github.com/kstost/cokacremote.git
|
||||||
|
ARG COKACREMOTE_REF=main
|
||||||
|
|
||||||
|
ENV TZ=${TIMEZONE} \
|
||||||
|
NODE_ENV=production \
|
||||||
|
MCP_HOST=127.0.0.1 \
|
||||||
|
MCP_PORT=3000 \
|
||||||
|
MCP_DEFAULT_CWD=/ \
|
||||||
|
MCP_ENDPOINT=/mcp \
|
||||||
|
MCP_TRUST_PROXY_HOPS=1 \
|
||||||
|
MCP_AUTH_TOKEN="" \
|
||||||
|
MCP_OAUTH_ENABLED=true \
|
||||||
|
MCP_OAUTH_STATE_FILE=/var/lib/cokacremote/oauth-state.json \
|
||||||
|
MCP_OAUTH_APPROVAL_KEY_FILE=/var/lib/cokacremote/oauth-approval-key
|
||||||
|
|
||||||
|
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||||
|
|
||||||
|
RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
ca-certificates \
|
||||||
|
curl \
|
||||||
|
git \
|
||||||
|
nginx \
|
||||||
|
openssl \
|
||||||
|
supervisor \
|
||||||
|
tmux \
|
||||||
|
tzdata \
|
||||||
|
&& curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
|
||||||
|
&& apt-get install -y --no-install-recommends nodejs \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN git clone --depth 1 --branch "${COKACREMOTE_REF}" \
|
||||||
|
"${COKACREMOTE_REPOSITORY}" /opt/cokacremote \
|
||||||
|
&& cd /opt/cokacremote \
|
||||||
|
&& npm ci --include=dev \
|
||||||
|
&& npm run build \
|
||||||
|
&& npm prune --omit=dev \
|
||||||
|
&& npm cache clean --force
|
||||||
|
|
||||||
|
COPY templates/AGENTS.md /usr/local/share/workmachine/AGENTS.md
|
||||||
|
|
||||||
|
RUN <<'SETUP'
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
rm -f /etc/nginx/sites-enabled/default
|
||||||
|
mkdir -p /etc/nginx/routes.d /etc/nginx/snippets /var/lib/cokacremote /shared
|
||||||
|
chmod 0700 /var/lib/cokacremote
|
||||||
|
|
||||||
|
cat > /etc/nginx/snippets/workmachine-proxy.conf <<'NGINX'
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Proto $workmachine_forwarded_proto;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $workmachine_connection_upgrade;
|
||||||
|
proxy_read_timeout 3600s;
|
||||||
|
proxy_send_timeout 3600s;
|
||||||
|
NGINX
|
||||||
|
|
||||||
|
cat > /etc/nginx/conf.d/workmachine.conf <<'NGINX'
|
||||||
|
map $http_upgrade $workmachine_connection_upgrade {
|
||||||
|
default upgrade;
|
||||||
|
'' close;
|
||||||
|
}
|
||||||
|
|
||||||
|
map $http_x_forwarded_proto $workmachine_forwarded_proto {
|
||||||
|
default $http_x_forwarded_proto;
|
||||||
|
'' $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 2999 default_server;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
include /etc/nginx/routes.d/*.conf;
|
||||||
|
include /shared/nginx/routes.d/*.conf;
|
||||||
|
}
|
||||||
|
NGINX
|
||||||
|
|
||||||
|
cat > /etc/nginx/routes.d/10-cokacremote.conf <<'NGINX'
|
||||||
|
location = /mcp {
|
||||||
|
include /etc/nginx/snippets/workmachine-proxy.conf;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
proxy_pass http://127.0.0.1:3000;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /health {
|
||||||
|
include /etc/nginx/snippets/workmachine-proxy.conf;
|
||||||
|
proxy_pass http://127.0.0.1:3000;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /.well-known/ {
|
||||||
|
include /etc/nginx/snippets/workmachine-proxy.conf;
|
||||||
|
proxy_pass http://127.0.0.1:3000;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ ^/(authorize|token|register|revoke)$ {
|
||||||
|
include /etc/nginx/snippets/workmachine-proxy.conf;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_pass http://127.0.0.1:3000;
|
||||||
|
}
|
||||||
|
NGINX
|
||||||
|
|
||||||
|
# Example: add a new application later without changing the default routes.
|
||||||
|
# Create /shared/nginx/routes.d/20-newapp.conf:
|
||||||
|
#
|
||||||
|
# location = /newapp {
|
||||||
|
# return 308 /newapp/;
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# location ^~ /newapp/ {
|
||||||
|
# include /etc/nginx/snippets/workmachine-proxy.conf;
|
||||||
|
# proxy_pass http://127.0.0.1:5000/;
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# Then validate and reload Nginx:
|
||||||
|
# nginx -t && nginx -s reload
|
||||||
|
|
||||||
|
cat > /etc/supervisor/conf.d/workmachine.conf <<'SUPERVISOR'
|
||||||
|
[supervisord]
|
||||||
|
nodaemon=true
|
||||||
|
logfile=/dev/null
|
||||||
|
pidfile=/run/supervisord.pid
|
||||||
|
|
||||||
|
[program:nginx]
|
||||||
|
command=/usr/sbin/nginx -g "daemon off;"
|
||||||
|
priority=10
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
startsecs=2
|
||||||
|
stopsignal=QUIT
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
|
||||||
|
[program:cokacremote]
|
||||||
|
command=/usr/bin/npm start
|
||||||
|
directory=/opt/cokacremote
|
||||||
|
priority=20
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
startsecs=2
|
||||||
|
stopasgroup=true
|
||||||
|
killasgroup=true
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
SUPERVISOR
|
||||||
|
|
||||||
|
cat > /usr/local/bin/workmachine-entrypoint <<'ENTRYPOINT'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
install -d -m 0755 /shared/nginx/routes.d
|
||||||
|
|
||||||
|
public_host=""
|
||||||
|
public_mcp_url="not configured"
|
||||||
|
|
||||||
|
if [[ "${MCP_OAUTH_ENABLED:-false}" == "true" ]]; then
|
||||||
|
: "${MCP_PUBLIC_URL:?Set MCP_PUBLIC_URL to the externally accessible base URL}"
|
||||||
|
MCP_PUBLIC_URL="${MCP_PUBLIC_URL%/}"
|
||||||
|
export MCP_PUBLIC_URL
|
||||||
|
export MCP_OAUTH_ISSUER="${MCP_OAUTH_ISSUER:-${MCP_PUBLIC_URL}}"
|
||||||
|
export MCP_OAUTH_RESOURCE="${MCP_OAUTH_RESOURCE:-${MCP_PUBLIC_URL}${MCP_ENDPOINT:-/mcp}}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${MCP_PUBLIC_URL:-}" ]]; then
|
||||||
|
public_host="$(node -e 'process.stdout.write(new URL(process.env.MCP_PUBLIC_URL).hostname)')"
|
||||||
|
public_mcp_url="${MCP_PUBLIC_URL}${MCP_ENDPOINT:-/mcp}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${MCP_ALLOWED_HOSTS:-}" ]]; then
|
||||||
|
if [[ -n "${public_host}" ]]; then
|
||||||
|
export MCP_ALLOWED_HOSTS="${public_host},localhost,127.0.0.1"
|
||||||
|
else
|
||||||
|
export MCP_ALLOWED_HOSTS="localhost,127.0.0.1"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -e /shared/AGENTS.md && ! -L /shared/AGENTS.md ]]; then
|
||||||
|
agents_tmp="$(mktemp)"
|
||||||
|
PUBLIC_BASE_URL="${MCP_PUBLIC_URL:-not configured}" PUBLIC_DOMAIN="${public_host:-not configured}" PUBLIC_MCP_URL="${public_mcp_url}" node -e 'const fs = require("fs"); const source = fs.readFileSync("/usr/local/share/workmachine/AGENTS.md", "utf8"); process.stdout.write(source.replaceAll("{{PUBLIC_BASE_URL}}", process.env.PUBLIC_BASE_URL).replaceAll("{{PUBLIC_DOMAIN}}", process.env.PUBLIC_DOMAIN).replaceAll("{{PUBLIC_MCP_URL}}", process.env.PUBLIC_MCP_URL));' > "${agents_tmp}"
|
||||||
|
install -m 0644 "${agents_tmp}" /shared/AGENTS.md
|
||||||
|
rm -f "${agents_tmp}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
install -d -m 0700 "$(dirname "${MCP_OAUTH_STATE_FILE}")"
|
||||||
|
install -d -m 0700 "$(dirname "${MCP_OAUTH_APPROVAL_KEY_FILE}")"
|
||||||
|
|
||||||
|
if [[ -z "${MCP_OAUTH_APPROVAL_KEY:-}" ]]; then
|
||||||
|
if [[ ! -s "${MCP_OAUTH_APPROVAL_KEY_FILE}" ]]; then
|
||||||
|
openssl rand -hex 32 > "${MCP_OAUTH_APPROVAL_KEY_FILE}"
|
||||||
|
chmod 0600 "${MCP_OAUTH_APPROVAL_KEY_FILE}"
|
||||||
|
fi
|
||||||
|
export MCP_OAUTH_APPROVAL_KEY
|
||||||
|
MCP_OAUTH_APPROVAL_KEY="$(<"${MCP_OAUTH_APPROVAL_KEY_FILE}")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
nginx -t
|
||||||
|
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/workmachine.conf
|
||||||
|
ENTRYPOINT
|
||||||
|
|
||||||
|
chmod 0755 /usr/local/bin/workmachine-entrypoint
|
||||||
|
SETUP
|
||||||
|
|
||||||
|
VOLUME ["/var/lib/cokacremote"]
|
||||||
|
|
||||||
|
WORKDIR /shared
|
||||||
|
|
||||||
|
EXPOSE 2999
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
||||||
|
CMD curl -fsS -H "Host: localhost" http://127.0.0.1:2999/health || exit 1
|
||||||
|
|
||||||
|
STOPSIGNAL SIGTERM
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/local/bin/workmachine-entrypoint"]
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# workmachine
|
||||||
|
|
||||||
|
A long-running Ubuntu development machine controlled through cokacremote MCP.
|
||||||
|
|
||||||
|
On first start, workmachine creates `/shared/AGENTS.md` from `templates/AGENTS.md` if the file does not already exist. Existing instructions are never overwritten.
|
||||||
|
|
||||||
|
## Configure
|
||||||
|
|
||||||
|
Run commands from the directory containing `docker-compose.yml`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /absolute/path/to/workmachine
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Set these values in `.env`:
|
||||||
|
|
||||||
|
- `SHARED_PATH`: absolute host directory mounted at `/shared`
|
||||||
|
- `MCP_PUBLIC_URL`: public HTTPS base URL without `/mcp`
|
||||||
|
- `CLOUDFLARE_TUNNEL_TOKEN`: Cloudflare Tunnel token
|
||||||
|
|
||||||
|
Example for macOS:
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
SHARED_PATH=/Users/yourname/Documents/workspace
|
||||||
|
MCP_PUBLIC_URL=https://example.com
|
||||||
|
CLOUDFLARE_TUNNEL_TOKEN=replace-with-your-real-tunnel-token
|
||||||
|
TZ=Asia/Seoul
|
||||||
|
COKACREMOTE_REF=main
|
||||||
|
```
|
||||||
|
|
||||||
|
This example creates the public MCP endpoint `https://example.com/mcp`. Use an absolute path for `SHARED_PATH`, do not add a trailing slash to `MCP_PUBLIC_URL`, and never commit the populated `.env` file.
|
||||||
|
|
||||||
|
In the Cloudflare Tunnel public-hostname settings, set the service URL to:
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://localhost:2999
|
||||||
|
```
|
||||||
|
|
||||||
|
## Start
|
||||||
|
|
||||||
|
From the directory containing `docker-compose.yml`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /absolute/path/to/workmachine
|
||||||
|
docker compose -p workmachine up -d --build
|
||||||
|
docker compose -p workmachine ps
|
||||||
|
docker compose -p workmachine logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
To rebuild without reusing layers from previous Docker builds, then recreate the
|
||||||
|
containers from the new image:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -p workmachine build --no-cache --pull
|
||||||
|
docker compose -p workmachine up -d --force-recreate
|
||||||
|
```
|
||||||
|
|
||||||
|
This keeps the existing `cokacremote-state` volume and its OAuth state.
|
||||||
|
|
||||||
|
To run from any directory, specify both the Compose file and environment file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -p workmachine -f /absolute/path/to/workmachine/docker-compose.yml --env-file /absolute/path/to/workmachine/.env up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
Nginx listens on port 2999 and forwards cokacremote routes to port 3000.
|
||||||
|
|
||||||
|
Read the generated OAuth approval key:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -p workmachine exec workmachine cat /var/lib/cokacremote/oauth-approval-key
|
||||||
|
```
|
||||||
|
|
||||||
|
## Add an application route
|
||||||
|
|
||||||
|
Create a file under `${SHARED_PATH}/nginx/routes.d`, for example `20-newapp.conf`:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
location = /newapp {
|
||||||
|
return 308 /newapp/;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /newapp/ {
|
||||||
|
include /etc/nginx/snippets/workmachine-proxy.conf;
|
||||||
|
proxy_pass http://127.0.0.1:5000/;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Validate and reload without restarting the container:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -p workmachine exec workmachine nginx -t
|
||||||
|
docker compose -p workmachine exec workmachine nginx -s reload
|
||||||
|
```
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
services:
|
||||||
|
workmachine:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
args:
|
||||||
|
TIMEZONE: "${TZ:-UTC}"
|
||||||
|
COKACREMOTE_REF: "${COKACREMOTE_REF:-main}"
|
||||||
|
image: "${WORKMACHINE_IMAGE:-workmachine:local}"
|
||||||
|
container_name: workmachine
|
||||||
|
hostname: workmachine
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
TZ: "${TZ:-UTC}"
|
||||||
|
MCP_PUBLIC_URL: "${MCP_PUBLIC_URL:?Set MCP_PUBLIC_URL in .env}"
|
||||||
|
MCP_ALLOWED_HOSTS: "${MCP_ALLOWED_HOSTS:-}"
|
||||||
|
MCP_OAUTH_ENABLED: "${MCP_OAUTH_ENABLED:-true}"
|
||||||
|
MCP_OAUTH_APPROVAL_KEY: "${MCP_OAUTH_APPROVAL_KEY:-}"
|
||||||
|
MCP_AUTH_TOKEN: "${MCP_AUTH_TOKEN:-}"
|
||||||
|
volumes:
|
||||||
|
- type: bind
|
||||||
|
source: "${SHARED_PATH:?Set SHARED_PATH in .env}"
|
||||||
|
target: /shared
|
||||||
|
- type: volume
|
||||||
|
source: cokacremote-state
|
||||||
|
target: /var/lib/cokacremote
|
||||||
|
stop_grace_period: 30s
|
||||||
|
|
||||||
|
cloudflared:
|
||||||
|
image: cloudflare/cloudflared:latest
|
||||||
|
container_name: chatgpt-cloudflared
|
||||||
|
restart: unless-stopped
|
||||||
|
network_mode: "service:workmachine"
|
||||||
|
depends_on:
|
||||||
|
workmachine:
|
||||||
|
condition: service_healthy
|
||||||
|
command:
|
||||||
|
- tunnel
|
||||||
|
- --no-autoupdate
|
||||||
|
- run
|
||||||
|
- --token
|
||||||
|
- "${CLOUDFLARE_TUNNEL_TOKEN:?Set CLOUDFLARE_TUNNEL_TOKEN in .env}"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
cokacremote-state:
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# Workmachine Instructions
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
- `/shared` is the persistent host-mounted workspace.
|
||||||
|
- Store projects and durable files under `/shared`.
|
||||||
|
- Treat files outside `/shared` as disposable unless documented otherwise.
|
||||||
|
|
||||||
|
## Public Access
|
||||||
|
|
||||||
|
- This workmachine is connected to its public domain through Cloudflare Tunnel.
|
||||||
|
- Public domain: `{{PUBLIC_DOMAIN}}`
|
||||||
|
- Public base URL: `{{PUBLIC_BASE_URL}}`
|
||||||
|
- Public MCP endpoint: `{{PUBLIC_MCP_URL}}`
|
||||||
|
- Cloudflare Tunnel forwards public requests to Nginx at `http://localhost:2999`.
|
||||||
|
- Nginx forwards the protected cokacremote paths, including `/mcp`, to `http://127.0.0.1:3000`.
|
||||||
|
|
||||||
|
## Instruction Scope
|
||||||
|
|
||||||
|
- Check for a nearer `AGENTS.md` before modifying a project.
|
||||||
|
- Project-specific instructions take precedence within that project.
|
||||||
|
- Preserve existing files, repositories, and uncommitted changes.
|
||||||
|
|
||||||
|
## Reserved Workmachine Resources
|
||||||
|
|
||||||
|
The following resources belong to the workmachine infrastructure and cokacremote. During unrelated application development, never reuse, overwrite, remove, stop, redirect, or otherwise interfere with them:
|
||||||
|
|
||||||
|
- TCP port `2999`: Nginx gateway
|
||||||
|
- TCP port `3000`: cokacremote MCP server
|
||||||
|
- `/opt/cokacremote`
|
||||||
|
- `/var/lib/cokacremote`
|
||||||
|
- `/etc/nginx/routes.d/10-cokacremote.conf`
|
||||||
|
- Nginx and cokacremote Supervisor processes
|
||||||
|
- `/mcp`
|
||||||
|
- `/health`
|
||||||
|
- `/.well-known/*`
|
||||||
|
- `/authorize`
|
||||||
|
- `/token`
|
||||||
|
- `/register`
|
||||||
|
- `/revoke`
|
||||||
|
|
||||||
|
Only modify these resources when the user explicitly requests maintenance of workmachine or cokacremote.
|
||||||
|
|
||||||
|
## Application Services
|
||||||
|
|
||||||
|
- Check listening ports before selecting an application port.
|
||||||
|
- New applications must not use ports `2999` or `3000`.
|
||||||
|
- Bind application servers to `127.0.0.1` unless instructed otherwise.
|
||||||
|
- Store application route files in `/shared/nginx/routes.d/`.
|
||||||
|
- Use one route file and one unique internal port per application.
|
||||||
|
- Do not modify `/etc/nginx/routes.d/10-cokacremote.conf`.
|
||||||
|
|
||||||
|
## Nginx Changes
|
||||||
|
|
||||||
|
- Validate configuration before applying it.
|
||||||
|
- Apply changes with `nginx -t && nginx -s reload`.
|
||||||
|
- Do not stop or restart Nginx when a reload is sufficient.
|
||||||
|
- Do not modify Cloudflare Tunnel settings unless explicitly requested.
|
||||||
|
|
||||||
|
## Safety
|
||||||
|
|
||||||
|
- Do not delete, overwrite, move, or reset user files without explicit approval.
|
||||||
|
- Do not expose secrets, tokens, `.env` files, or OAuth keys.
|
||||||
|
- Do not publish container ports directly without approval.
|
||||||
|
- Avoid destructive system and Git commands.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- Use the package manager selected by the existing lockfile.
|
||||||
|
- Run relevant tests or builds after changes.
|
||||||
|
- Report changed files, allocated ports, routes, and verification results.
|
||||||
Reference in New Issue
Block a user