feat: live staging with auto-refresh on main push (Fixes #33)

- index.html: polls Gitea API every 30s for new commits on main;
  shows countdown banner and auto-reloads when SHA changes
- Dockerfile + docker-compose.yml: serve the site via nginx;
  main on :4200, staging on :4201
- deploy.sh: rebuild and restart a named service
- .gitea/workflows/deploy.yml: SSH-deploy to host on every push to main
  (requires DEPLOY_HOST / DEPLOY_USER / DEPLOY_SSH_KEY repo secrets)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Alexander Whitestone
2026-03-23 21:20:58 -04:00
parent 3725c933cf
commit c4547d2e52
5 changed files with 119 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
name: Deploy Nexus
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Deploy to host via SSH
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
script: |
cd ~/the-nexus || git clone http://143.198.27.163:3000/Timmy_Foundation/the-nexus.git ~/the-nexus
cd ~/the-nexus
git fetch origin main
git reset --hard origin/main
./deploy.sh main

6
Dockerfile Normal file
View File

@@ -0,0 +1,6 @@
FROM nginx:alpine
COPY . /usr/share/nginx/html
RUN rm -f /usr/share/nginx/html/Dockerfile \
/usr/share/nginx/html/docker-compose.yml \
/usr/share/nginx/html/deploy.sh
EXPOSE 80

17
deploy.sh Executable file
View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
# deploy.sh — spin up (or update) the Nexus staging environment
# Usage: ./deploy.sh — rebuild and restart nexus-main (port 4200)
# ./deploy.sh staging — rebuild and restart nexus-staging (port 4201)
set -euo pipefail
SERVICE="${1:-nexus-main}"
case "$SERVICE" in
staging) SERVICE="nexus-staging" ;;
main) SERVICE="nexus-main" ;;
esac
echo "==> Deploying $SERVICE"
docker compose build "$SERVICE"
docker compose up -d --force-recreate "$SERVICE"
echo "==> Done. Container: $SERVICE"

20
docker-compose.yml Normal file
View File

@@ -0,0 +1,20 @@
version: "3.9"
services:
nexus-main:
build: .
container_name: nexus-main
restart: unless-stopped
ports:
- "4200:80"
labels:
- "deployment=main"
nexus-staging:
build: .
container_name: nexus-staging
restart: unless-stopped
ports:
- "4201:80"
labels:
- "deployment=staging"

View File

@@ -118,5 +118,55 @@
</footer>
<script type="module" src="./app.js"></script>
<!-- Live Refresh: polls Gitea for new commits on main, reloads when SHA changes -->
<div id="live-refresh-banner" style="
display:none; position:fixed; top:0; left:0; right:0; z-index:9999;
background:linear-gradient(90deg,#4af0c0,#7b5cff);
color:#050510; font-family:'JetBrains Mono',monospace; font-size:13px;
padding:8px 16px; text-align:center; font-weight:600;
">⚡ NEW DEPLOYMENT DETECTED — Reloading in <span id="lr-countdown">5</span>s…</div>
<script>
(function() {
const GITEA = 'http://143.198.27.163:3000/api/v1';
const REPO = 'Timmy_Foundation/the-nexus';
const BRANCH = 'main';
const INTERVAL = 30000; // poll every 30s
let knownSha = null;
async function fetchLatestSha() {
try {
const r = await fetch(`${GITEA}/repos/${REPO}/branches/${BRANCH}`, { cache: 'no-store' });
if (!r.ok) return null;
const d = await r.json();
return d.commit && d.commit.id ? d.commit.id : null;
} catch (e) { return null; }
}
async function poll() {
const sha = await fetchLatestSha();
if (!sha) return;
if (knownSha === null) { knownSha = sha; return; }
if (sha !== knownSha) {
knownSha = sha;
const banner = document.getElementById('live-refresh-banner');
const countdown = document.getElementById('lr-countdown');
banner.style.display = 'block';
let t = 5;
const tick = setInterval(() => {
t--;
countdown.textContent = t;
if (t <= 0) { clearInterval(tick); location.reload(); }
}, 1000);
}
}
// Start polling after page is interactive
fetchLatestSha().then(sha => { knownSha = sha; });
setInterval(poll, INTERVAL);
})();
</script>
</body>
</html>