fix: version the Site's scripts by content so a release never meets a cached old one
All checks were successful
Build and deploy / Validate source (push) Successful in 7s
Build and deploy / Integration suite on a real stack (push) Successful in 2m9s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m44s

The proxy in front of production caches .js and .css for hours. After the
last release the Site got the new index.html with the old site-flow.js,
which wrote to an element the new page no longer has; the error left
"Adicionar ao carrinho" disabled. The web build now addresses every local
script and stylesheet by a hash of its content, replacing the hand-kept
?v= markers, so a new release always loads its own files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Cauê Faleiros
2026-09-28 11:27:55 -03:00
parent a1877bdf0a
commit 536510b148
5 changed files with 25 additions and 6 deletions

View File

@@ -22,7 +22,8 @@ LABEL org.opencontainers.image.title="DTF Site and Kanban" \
org.opencontainers.image.source="DTF System repository" org.opencontainers.image.source="DTF System repository"
ENV WEB_INDEX=index.html PUBLIC_HOST=invalid.example S3_PUBLIC_ENDPOINT=https://invalid.example ENV WEB_INDEX=index.html PUBLIC_HOST=invalid.example S3_PUBLIC_ENDPOINT=https://invalid.example
COPY --from=policy /build/default.conf.template /etc/nginx/templates/default.conf.template COPY --from=policy /build/default.conf.template /etc/nginx/templates/default.conf.template
COPY web/ /usr/share/nginx/html/ # The HTML from the policy stage, with every asset address versioned.
COPY --from=policy /build/web/ /usr/share/nginx/html/
# The official entrypoint renders the server configuration at startup and Nginx # The official entrypoint renders the server configuration at startup and Nginx
# writes its PID/cache files. Keep the service non-root while granting it # writes its PID/cache files. Keep the service non-root while granting it

View File

@@ -10,5 +10,6 @@ RUN apk upgrade --no-cache
ENV WEB_INDEX=index.html ENV WEB_INDEX=index.html
COPY --from=policy /build/default.conf.template /etc/nginx/templates/default.conf.template COPY --from=policy /build/default.conf.template /etc/nginx/templates/default.conf.template
ENV S3_PUBLIC_ENDPOINT=http://localhost:9000 ENV S3_PUBLIC_ENDPOINT=http://localhost:9000
COPY web/ /usr/share/nginx/html/ # The HTML from the policy stage, with every asset address versioned.
COPY --from=policy /build/web/ /usr/share/nginx/html/

View File

@@ -1,4 +1,7 @@
"""Compile the gateway configuration: hash any trusted inline script for the CSP. """Compile the gateway configuration and version the Site's assets.
Inline scripts are hashed for the CSP; local scripts and stylesheets get a
content hash in their address.
The Site's behaviour now lives in separate files, so normally there is nothing to The Site's behaviour now lives in separate files, so normally there is nothing to
hash and the policy is simply script-src 'self' — no allowlist to get wrong. The hash and the policy is simply script-src 'self' — no allowlist to get wrong. The
@@ -12,6 +15,20 @@ from pathlib import Path
import re import re
root = Path('/build') root = Path('/build')
# Every local script and stylesheet is addressed by its content, so a release
# can never pair new HTML with an old cached file: the proxy in front of the
# stack caches assets for hours, and a new index.html calling an old script
# broke the Site (2026-09-28). The HTML itself is served no-cache.
def versioned(match):
attribute, path = match.group(1), match.group(2)
digest = hashlib.sha256((root / 'web' / path.lstrip('/')).read_bytes()).hexdigest()[:12]
return f'{attribute}="{path}?v={digest}"'
for html in (root / 'web').glob('*.html'):
text = re.sub(r'\b(src|href)="(/[\w./-]+\.(?:js|css))(?:\?[^"]*)?"', versioned, html.read_text())
html.write_text(text)
hashes = [] hashes = []
for html in (root / 'web').glob('*.html'): for html in (root / 'web').glob('*.html'):
for attributes, script in re.findall(r'<script\b([^>]*)>(.*?)</script>', html.read_text(), re.S | re.I): for attributes, script in re.findall(r'<script\b([^>]*)>(.*?)</script>', html.read_text(), re.S | re.I):

View File

@@ -747,7 +747,7 @@ footer a:hover{color:var(--laranja2)}
justify-content:space-between;gap:12px;flex-wrap:wrap;font-size:11px;color:#5A6570} justify-content:space-between;gap:12px;flex-wrap:wrap;font-size:11px;color:#5A6570}
@media(prefers-reduced-motion:reduce){*{transition:none!important;scroll-behavior:auto}} @media(prefers-reduced-motion:reduce){*{transition:none!important;scroll-behavior:auto}}
</style> </style>
<link rel="stylesheet" href="/site-v2.css?v=6"> <link rel="stylesheet" href="/site-v2.css">
<script src="/site-pages.js"></script> <script src="/site-pages.js"></script>
</head> </head>
<body> <body>

View File

@@ -3,7 +3,7 @@
<title>Kanban DTF</title> <title>Kanban DTF</title>
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&amp;display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&amp;display=swap">
<link rel="stylesheet" href="/kanban.css?v=redesign-7"> <link rel="stylesheet" href="/kanban.css">
</head><body> </head><body>
<form id="login" class="login" hidden> <form id="login" class="login" hidden>
<div class="brand"><i aria-hidden="true">D</i>Kanban DTF</div> <div class="brand"><i aria-hidden="true">D</i>Kanban DTF</div>
@@ -67,4 +67,4 @@
<div class="scrim" id="scrim"></div> <div class="scrim" id="scrim"></div>
<aside class="panel" id="panel" aria-label="Pedido"></aside> <aside class="panel" id="panel" aria-label="Pedido"></aside>
</div> </div>
<script src="/upload.js"></script><script src="/kanban.js?v=redesign-8"></script></body></html> <script src="/upload.js"></script><script src="/kanban.js"></script></body></html>