A static-ish website hosted on Google Apps Script, accessible via a stable HTTPS URL under your domain.
ANYONE_ANONYMOUS/ANYONEaccess blocked by domain policy — useDOMAIN- Public Drive file sharing blocked — embed images as base64 data URIs
- No Google Sites write API — Apps Script web apps are the workaround
appsscript.json # manifest
Code.js # doGet() entry point
index.html # your HTML
appsscript.json
{
"timeZone": "America/New_York",
"dependencies": {},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"webapp": {
"executeAs": "USER_DEPLOYING",
"access": "DOMAIN"
}
}Code.js
function doGet() {
return HtmlService.createHtmlOutputFromFile('index')
.setTitle("Page Title")
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}Push files → create version → update the same deployment in place (URL never changes).
import json
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
SCRIPT_ID = "<your-script-id>"
DEPLOY_ID = "<your-deployment-id>" # get this after first manual deploy
with open("/home/duane/.config/gcloud/application_default_credentials.json") as f:
data = json.load(f)
creds = Credentials(
token=None,
refresh_token=data["refresh_token"],
token_uri="https://oauth2.googleapis.com/token",
client_id=data["client_id"],
client_secret=data["client_secret"],
quota_project_id="agentic-sre-demo", # personal GCP project with Drive API enabled
)
svc = build("script", "v1", credentials=creds)
# 1. Push updated source files
svc.projects().updateContent(
scriptId=SCRIPT_ID,
body={"files": [
{"name": "appsscript", "type": "JSON", "source": open("appsscript.json").read()},
{"name": "Code", "type": "SERVER_JS", "source": open("Code.js").read()},
{"name": "index", "type": "HTML", "source": open("index.html").read()},
]}
).execute()
# 2. Create a new version
ver = svc.projects().versions().create(
scriptId=SCRIPT_ID, body={"description": "v2"}
).execute()
# 3. Update canonical deployment in place
svc.projects().deployments().update(
scriptId=SCRIPT_ID,
deploymentId=DEPLOY_ID,
body={"deploymentConfig": {
"scriptId": SCRIPT_ID,
"versionNumber": ver["versionNumber"],
"manifestFileName": "appsscript",
"description": "latest",
}}
).execute()
print("Done — URL unchanged")# Authenticate as Broadcom account
gcloud auth application-default login \
--scopes="openid,https://www.googleapis.com/auth/userinfo.email,\
https://www.googleapis.com/auth/cloud-platform,\
https://www.googleapis.com/auth/drive,\
https://www.googleapis.com/auth/script.projects,\
https://www.googleapis.com/auth/script.deployments" \
--no-launch-browser
# Enable Drive API on personal project (needed for quota)
gcloud services enable drive.googleapis.com --project=agentic-sre-demo \
--account=duane.nielsen.rocks@gmail.com
# Grant Broadcom account quota rights on personal project
gcloud projects add-iam-policy-binding agentic-sre-demo \
--member="user:duane.nielsen@broadcom.com" \
--role="roles/serviceusage.serviceUsageConsumer" \
--account=duane.nielsen.rocks@gmail.comCredentials land at ~/.config/gcloud/application_default_credentials.json.
- Open script.google.com, create a new project
- Deploy → New deployment → Web app → access = Domain
- Copy the Script ID (Project Settings) and Deployment ID — paste into your push script
After that, all updates go through the Python script above; the URL stays stable.
A pre-authed Chrome profile lives at ~/duane_ai_site/pw-user-data/.
import subprocess, os, time
from playwright.sync_api import sync_playwright
env = {**os.environ, "DISPLAY": ":1", "WAYLAND_DISPLAY": "wayland-1"}
proc = subprocess.Popen([
"google-chrome", "--remote-debugging-port=9222",
"--user-data-dir=/home/duane/duane_ai_site/pw-user-data",
"--no-first-run", "--no-default-browser-check",
"--password-store=basic", "--use-mock-keychain",
"--no-sandbox", "--window-size=1440,900",
], env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
time.sleep(2)
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp("http://localhost:9222")
page = browser.contexts[0].pages[0]
page.goto("<your-app-url>")
page.wait_for_load_state("networkidle")
page.screenshot(path="/tmp/site.png")
proc.terminate()Do NOT use --headless=new — Drive iframes need a real display. Kill any existing Chrome on the same profile first: pkill -f duane_ai_site/pw-user-data.
Re-auth if session expires:
python3 ~/duane_ai_site/auth.py # sign in via Broadcom SSO
python3 ~/duane_ai_site/auth.py --finish # save and close