fix(n8n-webhook): auto-load local gog automation env
This commit is contained in:
@@ -94,3 +94,8 @@
|
|||||||
- email draft item id `approval-mmnsx7iz-k26qb60c` → `execution.op = gmail.drafts.create`, `status = dry_run`
|
- email draft item id `approval-mmnsx7iz-k26qb60c` → `execution.op = gmail.drafts.create`, `status = dry_run`
|
||||||
- calendar item id `approval-mmnsx7ji-3rt7yd74` → `execution.op = calendar.create`, `status = dry_run`
|
- calendar item id `approval-mmnsx7ji-3rt7yd74` → `execution.op = calendar.create`, `status = dry_run`
|
||||||
- Current practical next step for real Gmail/Calendar execution: provide `GOG_KEYRING_PASSWORD` to the runtime environment that will invoke the bridge script, or switch `gog` to a keyring backend that supports unattended access on this host.
|
- Current practical next step for real Gmail/Calendar execution: provide `GOG_KEYRING_PASSWORD` to the runtime environment that will invoke the bridge script, or switch `gog` to a keyring backend that supports unattended access on this host.
|
||||||
|
- Follow-up completion on 2026-03-12:
|
||||||
|
- stored local-only Gog automation env in `/home/openclaw/.openclaw/credentials/gog.env` with restrictive permissions (`600`)
|
||||||
|
- updated `resolve-approval-with-gog.py` to auto-load that file when present
|
||||||
|
- verified non-interactive headless Gmail access works using the stored env (successful `gog gmail search ... --json --no-input`)
|
||||||
|
- verified the bridge itself auto-loads the env file by resolving a fresh `email_draft` approval item in `--dry-run` mode and attaching execution metadata successfully without manually exporting `GOG_ACCOUNT` / `GOG_KEYRING_PASSWORD`
|
||||||
|
|||||||
@@ -129,7 +129,8 @@ python3 scripts/resolve-approval-with-gog.py --id <approval-id> --decision appro
|
|||||||
```
|
```
|
||||||
|
|
||||||
Practical note:
|
Practical note:
|
||||||
- unattended execution needs `GOG_KEYRING_PASSWORD` in the environment because `gog`'s file keyring cannot prompt in non-TTY automation
|
- unattended execution needs `GOG_KEYRING_PASSWORD` available to the executor because `gog`'s file keyring cannot prompt in non-TTY automation
|
||||||
|
- the included bridge auto-loads `/home/openclaw/.openclaw/credentials/gog.env` when present, so you can keep `GOG_ACCOUNT` and `GOG_KEYRING_PASSWORD` there with mode `600`
|
||||||
- for safe plumbing tests without touching Google state, add `--dry-run`
|
- for safe plumbing tests without touching Google state, add `--dry-run`
|
||||||
|
|
||||||
### Add a new webhook-backed capability
|
### Add a new webhook-backed capability
|
||||||
|
|||||||
@@ -260,8 +260,10 @@ Behavior:
|
|||||||
- writes execution metadata back via `approval_history_attach_execution`
|
- writes execution metadata back via `approval_history_attach_execution`
|
||||||
|
|
||||||
Important automation note:
|
Important automation note:
|
||||||
- real unattended execution needs `GOG_KEYRING_PASSWORD` in the environment
|
- real unattended execution needs `GOG_KEYRING_PASSWORD` available to the executor
|
||||||
- without it, non-TTY `gog` calls will fail when the file keyring tries to prompt
|
- the included bridge auto-loads `/home/openclaw/.openclaw/credentials/gog.env` when present
|
||||||
|
- keep that file mode `600` if you use it for `GOG_ACCOUNT` / `GOG_KEYRING_PASSWORD`
|
||||||
|
- without the password, non-TTY `gog` calls will fail when the file keyring tries to prompt
|
||||||
- `--dry-run` works without touching Google state and is useful for plumbing verification
|
- `--dry-run` works without touching Google state and is useful for plumbing verification
|
||||||
|
|
||||||
## Validation
|
## Validation
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from pathlib import Path
|
|||||||
DEFAULT_BASE_URL = os.environ.get('N8N_BASE_URL', 'http://192.168.153.113:18808').rstrip('/')
|
DEFAULT_BASE_URL = os.environ.get('N8N_BASE_URL', 'http://192.168.153.113:18808').rstrip('/')
|
||||||
DEFAULT_ACTION_PATH = os.environ.get('N8N_ACTION_PATH', 'openclaw-action').strip('/')
|
DEFAULT_ACTION_PATH = os.environ.get('N8N_ACTION_PATH', 'openclaw-action').strip('/')
|
||||||
DEFAULT_SECRET_HEADER = os.environ.get('N8N_SECRET_HEADER', 'x-openclaw-secret')
|
DEFAULT_SECRET_HEADER = os.environ.get('N8N_SECRET_HEADER', 'x-openclaw-secret')
|
||||||
|
DEFAULT_GOG_ENV_FILE = Path(os.environ.get('GOG_ENV_FILE', '/home/openclaw/.openclaw/credentials/gog.env'))
|
||||||
|
|
||||||
|
|
||||||
def fail(msg: str, code: int = 1):
|
def fail(msg: str, code: int = 1):
|
||||||
@@ -24,6 +25,30 @@ def run(cmd, *, env=None):
|
|||||||
return proc.returncode, proc.stdout, proc.stderr
|
return proc.returncode, proc.stdout, proc.stderr
|
||||||
|
|
||||||
|
|
||||||
|
def load_env_file(path: Path) -> dict:
|
||||||
|
if not path.exists():
|
||||||
|
return {}
|
||||||
|
try:
|
||||||
|
st = path.stat()
|
||||||
|
if (st.st_mode & 0o077) != 0:
|
||||||
|
fail(f'insecure permissions on {path}; expected mode 600-ish')
|
||||||
|
except FileNotFoundError:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
loaded = {}
|
||||||
|
for raw_line in path.read_text(encoding='utf-8').splitlines():
|
||||||
|
line = raw_line.strip()
|
||||||
|
if not line or line.startswith('#') or '=' not in line:
|
||||||
|
continue
|
||||||
|
key, value = line.split('=', 1)
|
||||||
|
key = key.strip()
|
||||||
|
value = value.strip()
|
||||||
|
if len(value) >= 2 and value[0] == value[-1] and value[0] in {'"', "'"}:
|
||||||
|
value = value[1:-1]
|
||||||
|
loaded[key] = value
|
||||||
|
return loaded
|
||||||
|
|
||||||
|
|
||||||
def gog_account(args_account: str | None) -> str:
|
def gog_account(args_account: str | None) -> str:
|
||||||
account = args_account or os.environ.get('GOG_ACCOUNT', '').strip()
|
account = args_account or os.environ.get('GOG_ACCOUNT', '').strip()
|
||||||
if not account:
|
if not account:
|
||||||
@@ -155,6 +180,9 @@ def main():
|
|||||||
ap.add_argument('--secret-header', default=DEFAULT_SECRET_HEADER)
|
ap.add_argument('--secret-header', default=DEFAULT_SECRET_HEADER)
|
||||||
args = ap.parse_args()
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
file_env = load_env_file(DEFAULT_GOG_ENV_FILE)
|
||||||
|
os.environ.update({k: v for k, v in file_env.items() if k not in os.environ or not os.environ.get(k)})
|
||||||
|
|
||||||
secret = webhook_secret()
|
secret = webhook_secret()
|
||||||
resolved = call_action(
|
resolved = call_action(
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user