diff --git a/kubernetes/gitea/gitea-cloudflare.yml b/kubernetes/gitea/cloudflare.yml similarity index 75% rename from kubernetes/gitea/gitea-cloudflare.yml rename to kubernetes/gitea/cloudflare.yml index 83e3934..f893a69 100644 --- a/kubernetes/gitea/gitea-cloudflare.yml +++ b/kubernetes/gitea/cloudflare.yml @@ -28,8 +28,6 @@ spec: imagePullPolicy: Always args: - tunnel - - --metrics - - 0.0.0.0:2000 - run env: - name: TUNNEL_TOKEN @@ -37,10 +35,3 @@ spec: secretKeyRef: name: cloudflared-gitea-token key: token - livenessProbe: - httpGet: - path: /ready - port: 2000 - failureThreshold: 1 - initialDelaySeconds: 10 - periodSeconds: 10 diff --git a/kubernetes/gitea/landingpage.yml b/kubernetes/gitea/landingpage.yml new file mode 100644 index 0000000..a1b883d --- /dev/null +++ b/kubernetes/gitea/landingpage.yml @@ -0,0 +1,391 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: gitea-landing-page + namespace: gitea +data: + home.tmpl: | + {{template "base/head" .}} + +
+ +
+
+
+ AM +
+

Alex Mickelson

+

Software engineer. Builder of useful things. Probably over-engineering something right now.

+ +
+
+ +
+
+

Recent Projects

+ Latest commits across all repositories +
+
+
+
+
+
+
+
+
+
+ +
+ + + {{template "base/footer" .}} + + custom-landing.css: | + #alex-landing { + min-height: 100vh; + background: #0d1117; + color: #e6edf3; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + } + + /* Hero */ + .hero { + display: flex; + justify-content: center; + align-items: center; + padding: 80px 24px 60px; + text-align: center; + } + .hero-inner { + max-width: 640px; + } + .avatar-ring { + width: 96px; + height: 96px; + border-radius: 50%; + background: linear-gradient(135deg, #58a6ff, #bc8cff); + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto 24px; + box-shadow: 0 0 0 4px #21262d, 0 0 0 8px #58a6ff44; + } + .initials { + font-size: 2rem; + font-weight: 700; + color: #0d1117; + letter-spacing: -1px; + } + .hero h1 { + font-size: 3rem; + font-weight: 800; + margin: 0 0 12px; + background: linear-gradient(135deg, #e6edf3 0%, #58a6ff 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + } + .tagline { + font-size: 1.1rem; + color: #8b949e; + margin: 0 0 32px; + line-height: 1.6; + } + .hero-links { + display: flex; + gap: 12px; + justify-content: center; + flex-wrap: wrap; + } + .btn-primary { + padding: 10px 24px; + border-radius: 8px; + background: #238636; + color: #fff; + font-weight: 600; + font-size: 0.95rem; + text-decoration: none; + transition: background 0.2s; + } + .btn-primary:hover { background: #2ea043; } + .btn-ghost { + padding: 10px 24px; + border-radius: 8px; + border: 1px solid #30363d; + color: #e6edf3; + font-weight: 600; + font-size: 0.95rem; + text-decoration: none; + transition: border-color 0.2s, background 0.2s; + } + .btn-ghost:hover { border-color: #58a6ff; background: #58a6ff11; } + + /* Projects section */ + .projects-section { + max-width: 1100px; + margin: 0 auto; + padding: 0 24px 80px; + } + .section-header { + margin-bottom: 24px; + display: flex; + align-items: baseline; + gap: 12px; + flex-wrap: wrap; + } + .section-header h2 { + font-size: 1.5rem; + font-weight: 700; + margin: 0; + color: #e6edf3; + } + .subtitle { + font-size: 0.9rem; + color: #8b949e; + } + + /* Grid */ + .repo-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + gap: 16px; + } + + /* Skeleton loaders */ + .skeleton-card { + height: 160px; + border-radius: 12px; + background: linear-gradient(90deg, #161b22 25%, #21262d 50%, #161b22 75%); + background-size: 200% 100%; + animation: shimmer 1.4s infinite; + } + @keyframes shimmer { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } + } + + /* Repo cards */ + .repo-card { + background: #161b22; + border: 1px solid #21262d; + border-radius: 12px; + padding: 20px; + text-decoration: none; + color: inherit; + display: flex; + flex-direction: column; + gap: 10px; + transition: border-color 0.2s, transform 0.2s, box-shadow 0.2s; + cursor: pointer; + } + .repo-card:hover { + border-color: #58a6ff; + transform: translateY(-2px); + box-shadow: 0 8px 24px #58a6ff1a; + } + .repo-card-header { + display: flex; + align-items: center; + gap: 10px; + } + .repo-icon { + font-size: 1.1rem; + flex-shrink: 0; + } + .repo-name { + font-size: 1rem; + font-weight: 600; + color: #58a6ff; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .repo-private { + font-size: 0.7rem; + background: #21262d; + border: 1px solid #30363d; + border-radius: 4px; + padding: 1px 6px; + color: #8b949e; + flex-shrink: 0; + } + .repo-desc { + font-size: 0.875rem; + color: #8b949e; + line-height: 1.5; + flex: 1; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + } + .repo-commit { + font-size: 0.8rem; + color: #6e7681; + border-top: 1px solid #21262d; + padding-top: 10px; + display: flex; + align-items: center; + gap: 6px; + overflow: hidden; + } + .commit-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: #238636; + flex-shrink: 0; + } + .commit-msg { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; + } + .commit-time { + color: #484f58; + flex-shrink: 0; + } + .repo-meta { + display: flex; + gap: 14px; + font-size: 0.8rem; + color: #6e7681; + } + .repo-meta span { display: flex; align-items: center; gap: 4px; } + + /* Error state */ + .error-msg { + grid-column: 1 / -1; + text-align: center; + padding: 40px; + color: #8b949e; + font-size: 0.95rem; + } + + custom-landing.js: | + (async function () { + const grid = document.getElementById('repo-grid'); + if (!grid) return; + + const baseUrl = window.GITEA_SUB_URL || ''; + + function timeAgo(dateStr) { + const diff = (Date.now() - new Date(dateStr)) / 1000; + if (diff < 60) return 'just now'; + if (diff < 3600) return Math.floor(diff / 60) + 'm ago'; + if (diff < 86400) return Math.floor(diff / 3600) + 'h ago'; + if (diff < 2592000) return Math.floor(diff / 86400) + 'd ago'; + if (diff < 31536000) return Math.floor(diff / 2592000) + 'mo ago'; + return Math.floor(diff / 31536000) + 'y ago'; + } + + function escapeHtml(str) { + return (str || '').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); + } + + function langDot(lang) { + const colors = { + Go:'#00ADD8', Python:'#3572A5', JavaScript:'#f1e05a', TypeScript:'#2b7489', + Rust:'#dea584', Java:'#b07219', 'C#':'#178600', Nix:'#7e7eff', + Shell:'#89e051', HTML:'#e34c26', CSS:'#563d7c', Elixir:'#6e4a7e', + }; + return colors[lang] + ? `` + : '📦'; + } + + async function fetchJson(url) { + const resp = await fetch(url, { credentials: 'include' }); + if (!resp.ok) throw new Error(resp.status); + return resp.json(); + } + + async function getLatestCommit(repo) { + try { + const commits = await fetchJson( + `${baseUrl}/api/v1/repos/${encodeURIComponent(repo.full_name)}/commits?limit=1&page=1` + ); + if (commits && commits.length > 0) return commits[0]; + } catch (_) {} + return null; + } + + // Sort by most recently updated + async function loadRepos() { + let repos; + try { + repos = await fetchJson( + `${baseUrl}/api/v1/repos/search?sort=newest&order=desc&limit=12&token=` + ); + repos = repos.data || repos; + } catch (e) { + grid.innerHTML = `
+ Could not load repositories. Browse manually → +
`; + return; + } + + if (!repos || repos.length === 0) { + grid.innerHTML = `
No public repositories found.
`; + return; + } + + // Sort by updated desc (API may already do this) + repos.sort((a, b) => new Date(b.updated) - new Date(a.updated)); + + // Render cards immediately, then fill in commits async + grid.innerHTML = ''; + for (const repo of repos) { + const card = document.createElement('a'); + card.className = 'repo-card'; + card.href = `${baseUrl}/${escapeHtml(repo.full_name)}`; + card.dataset.repoName = repo.full_name; + card.innerHTML = ` +
+ ${langDot(repo.language)} + ${escapeHtml(repo.name)} + ${repo.private ? 'private' : ''} +
+
${escapeHtml(repo.description) || 'No description'}
+
+ ${repo.language ? `${langDot(repo.language)} ${escapeHtml(repo.language)}` : ''} + ⭐ ${repo.stars_count || 0} + 🍴 ${repo.forks_count || 0} + 🕒 ${timeAgo(repo.updated)} +
+
+ + loading commit… +
+ `.trim(); + grid.appendChild(card); + } + + // Fetch commits in parallel + await Promise.all(repos.map(async (repo) => { + const commit = await getLatestCommit(repo); + const el = document.getElementById(`commit-${CSS.escape(repo.full_name)}`); + if (!el) return; + if (commit) { + const msg = commit.commit?.message?.split('\n')[0] || ''; + const when = timeAgo(commit.commit?.committer?.date || commit.created); + el.innerHTML = ` + + ${escapeHtml(msg)} + ${when} + `; + } else { + el.innerHTML = `no commits visible`; + } + })); + } + + loadRepos(); + })(); diff --git a/kubernetes/gitea/web.yml b/kubernetes/gitea/web.yml index 0883f95..81ae475 100644 --- a/kubernetes/gitea/web.yml +++ b/kubernetes/gitea/web.yml @@ -34,18 +34,27 @@ spec: value: "gitea" - name: GITEA__database__PASSWD value: wauiofnasufnweaiufbsdklfjb23456 - - name: GITEA__server__ROOT_URL - value: "https://git.alexmickelson.guru/" + - name: GITEA__server__PROTOCOL + value: "http" + - name: GITEA__server__DOMAIN + value: "git.alexmickelson.guru" + - name: GITEA__server__PUBLIC_URL_DETECTION + value: "auto" - name: GITEA__server__LOCAL_ROOT_URL - value: "http://gitea-gitea-web-svc.beefalo-newton.ts.net:3000/" + value: "http://gitea-web-svc.gitea.svc.cluster.local:3000/" - name: GITEA__server__SSH_DOMAIN value: "gitea-gitea-web-svc.beefalo-newton.ts.net" - name: GITEA__server__SSH_PORT value: "22" + # security + - name: GITEA__service__ENABLE_BASIC_AUTHENTICATION + value: "false" - name: GITEA__service__DISABLE_REGISTRATION value: "true" - name: GITEA__service__ALLOW_ONLY_EXTERNAL_REGISTRATION value: "false" + - name: GITEA__openid__ENABLE_OPENID_SIGNIN + value: "false" - name: GITEA__openid__ENABLE_OPENID_SIGNUP value: "false" volumeMounts: @@ -57,6 +66,18 @@ spec: - name: localtime mountPath: /etc/localtime readOnly: true + - name: landing-page + mountPath: /data/gitea/templates/home.tmpl + subPath: home.tmpl + readOnly: true + - name: landing-page + mountPath: /data/gitea/public/assets/css/custom-landing.css + subPath: custom-landing.css + readOnly: true + - name: landing-page + mountPath: /data/gitea/public/assets/js/custom-landing.js + subPath: custom-landing.js + readOnly: true volumes: - name: gitea-data hostPath: @@ -68,6 +89,9 @@ spec: - name: localtime hostPath: path: /etc/localtime + - name: landing-page + configMap: + name: gitea-landing-page --- apiVersion: v1