infra(cx2-5): DNS auth/NetPol substrate, air-gap landing, arm64 ARC runner + tenant landing manifests
- fc-dns: add OnePasswordItem CRD for DNS API keys + NetworkPolicy for Phase 0 auth hardening; bump dns-web image tag - fc-landing: rewrite landing HTML to remove CDN dependencies (air-gap safe); add preview.html standalone preview - github-runner: add TOOLCACHE_ARCH to install-ruby-toolcache.sh for arm64 support; add Dockerfile.arm64 for arm64 ARC runner image - docs/gx10-tenant-landing: per-user Deployment+IngressRoute manifests (andrew/dustin/erik/fit/matt) + CUTOVER-RUNBOOK.md Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,20 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
itemPath: "vaults/IAmWorkin/items/dns-oidc-client"
|
itemPath: "vaults/IAmWorkin/items/dns-oidc-client"
|
||||||
---
|
---
|
||||||
|
# Service X-Api-Key for the cert-manager ACME webhook -> dns-web call path
|
||||||
|
# (Phase 0 auth-flip). The 1Password operator resolves this item into a K8s
|
||||||
|
# Secret of the same name; the `api_key` field becomes Secret key `api_key`.
|
||||||
|
# dns-web reads it as FlowerCore__Auth__ApiKey (FcApiKey scheme, Operator
|
||||||
|
# principal); dns-acme-webhook sends it as the X-Api-Key header. Dormant while
|
||||||
|
# FlowerCore__Auth__Enabled=false (all policies allow-all).
|
||||||
|
apiVersion: onepassword.com/v1
|
||||||
|
kind: OnePasswordItem
|
||||||
|
metadata:
|
||||||
|
name: dns-api-keys
|
||||||
|
namespace: fc-dns
|
||||||
|
spec:
|
||||||
|
itemPath: "vaults/IAmWorkin/items/FlowerCore DNS API Keys"
|
||||||
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: PersistentVolumeClaim
|
kind: PersistentVolumeClaim
|
||||||
metadata:
|
metadata:
|
||||||
@@ -111,7 +125,7 @@ spec:
|
|||||||
fsGroup: 1654
|
fsGroup: 1654
|
||||||
containers:
|
containers:
|
||||||
- name: dns-web
|
- name: dns-web
|
||||||
image: localhost/fc-dns-web:v20260614-wave5-isolation-6124856
|
image: localhost/fc-dns-web:v20260615-phase0-hybrid-f77fb94
|
||||||
imagePullPolicy: Never
|
imagePullPolicy: Never
|
||||||
securityContext:
|
securityContext:
|
||||||
readOnlyRootFilesystem: true
|
readOnlyRootFilesystem: true
|
||||||
@@ -148,6 +162,16 @@ spec:
|
|||||||
name: dns-oidc-client
|
name: dns-oidc-client
|
||||||
key: client_secret
|
key: client_secret
|
||||||
optional: true
|
optional: true
|
||||||
|
# Service X-Api-Key accepted by the FcApiKey scheme. The standard
|
||||||
|
# key maps to an Operator principal (satisfies OperatorPolicy on the
|
||||||
|
# ACME present/cleanup endpoints). optional:true keeps the pod
|
||||||
|
# starting if the 1P operator has not yet produced the secret.
|
||||||
|
- name: FlowerCore__Auth__ApiKey
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: dns-api-keys
|
||||||
|
key: api_key
|
||||||
|
optional: true
|
||||||
- name: FlowerCore__Auth__Enabled
|
- name: FlowerCore__Auth__Enabled
|
||||||
value: "false"
|
value: "false"
|
||||||
- name: FlowerCore__Auth__Oidc__Enabled
|
- name: FlowerCore__Auth__Oidc__Enabled
|
||||||
@@ -209,6 +233,54 @@ spec:
|
|||||||
targetPort: 5320
|
targetPort: 5320
|
||||||
type: ClusterIP
|
type: ClusterIP
|
||||||
---
|
---
|
||||||
|
# Defense-in-depth ingress isolation for dns-web (Phase 0). NetworkPolicy is
|
||||||
|
# L3/L4 and cannot path-scope, so it CANNOT restrict only present/cleanup — the
|
||||||
|
# real control on those endpoints is the X-Api-Key + OperatorPolicy. This policy
|
||||||
|
# simply confines who may reach dns-web:5320 to known network zones without
|
||||||
|
# breaking any live path:
|
||||||
|
# * Traefik pods -> UI/API on dns.iamworkin.lan
|
||||||
|
# * same fc-dns namespace -> dns-acme-webhook -> present/cleanup
|
||||||
|
# * cluster pod CIDR (10.42/16) -> in-cluster Prometheus scrape, etc.
|
||||||
|
# * node + LAN CIDRs -> kubelet probes, noc1 host-net Prometheus
|
||||||
|
# Egress is intentionally left unrestricted: dns-web must reach pfSense
|
||||||
|
# (diag_command.php / HTTPS), the K8s API, Authentik OIDC discovery, step-ca,
|
||||||
|
# and DNS — over-tight egress would break the provider + auth paths.
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: dns-web-ingress-isolation
|
||||||
|
namespace: fc-dns
|
||||||
|
spec:
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: dns-web
|
||||||
|
policyTypes:
|
||||||
|
- Ingress
|
||||||
|
ingress:
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: traefik-system
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: traefik
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: fc-dns
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.42.0.0/16
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.56.0/24
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.57.0/24
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.58.0/24
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.68.0/27
|
||||||
|
ports:
|
||||||
|
- port: 5320
|
||||||
|
protocol: TCP
|
||||||
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ServiceAccount
|
kind: ServiceAccount
|
||||||
metadata:
|
metadata:
|
||||||
@@ -303,7 +375,7 @@ spec:
|
|||||||
fsGroup: 1654
|
fsGroup: 1654
|
||||||
containers:
|
containers:
|
||||||
- name: dns-acme-webhook
|
- name: dns-acme-webhook
|
||||||
image: localhost/fc-dns-acme-webhook:v20260614-wave5-isolation-6124856
|
image: localhost/fc-dns-acme-webhook:v20260615-phase0-hybrid-f77fb94
|
||||||
imagePullPolicy: Never
|
imagePullPolicy: Never
|
||||||
securityContext:
|
securityContext:
|
||||||
readOnlyRootFilesystem: true
|
readOnlyRootFilesystem: true
|
||||||
@@ -322,6 +394,16 @@ spec:
|
|||||||
value: /tls/tls.key
|
value: /tls/tls.key
|
||||||
- name: FlowerCore__Dns__AcmeWebhook__ServiceBaseUrl
|
- name: FlowerCore__Dns__AcmeWebhook__ServiceBaseUrl
|
||||||
value: http://dns-web:5320
|
value: http://dns-web:5320
|
||||||
|
# X-Api-Key sent to dns-web on present/cleanup so the webhook
|
||||||
|
# authenticates as an Operator once dns-web auth is enabled.
|
||||||
|
# optional:true keeps the webhook starting before the 1P secret
|
||||||
|
# exists; the header is simply omitted when the value is empty.
|
||||||
|
- name: FlowerCore__Dns__AcmeWebhook__ApiKey
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: dns-api-keys
|
||||||
|
key: api_key
|
||||||
|
optional: true
|
||||||
- name: FlowerCore__Dns__AcmeWebhook__GroupName
|
- name: FlowerCore__Dns__AcmeWebhook__GroupName
|
||||||
value: acme.flowercore.io
|
value: acme.flowercore.io
|
||||||
- name: FlowerCore__Dns__AcmeWebhook__SolverName
|
- name: FlowerCore__Dns__AcmeWebhook__SolverName
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ metadata:
|
|||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/part-of: bluejay-infra
|
app.kubernetes.io/part-of: bluejay-infra
|
||||||
---
|
---
|
||||||
# Landing page HTML (public-safe - no internal LAN references)
|
# Landing page HTML (public-safe - no internal LAN references, no CDN)
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
metadata:
|
metadata:
|
||||||
@@ -20,144 +20,357 @@ data:
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>FlowerCore</title>
|
<title>FlowerCore</title>
|
||||||
<style>
|
<!--
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
FlowerCore public landing page.
|
||||||
body {
|
Air-gap safe: zero CDN, zero external fonts, zero external scripts.
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
All assets are inline SVG / CSS / JS only.
|
||||||
background: linear-gradient(135deg, #0a1628 0%, #1a2744 50%, #0d1f3c 100%);
|
Update Center link: https://update.flowercore.io/
|
||||||
color: #e0e8f0;
|
Animation: bloom mark (8-petal flower) rotates slowly with a floating
|
||||||
min-height: 100vh;
|
seed-core pulse. Respects prefers-reduced-motion (animation disabled).
|
||||||
display: flex;
|
-->
|
||||||
flex-direction: column;
|
<style>
|
||||||
align-items: center;
|
/* ── Blue Jay palette (matches decisions-waiting.html tokens) ── */
|
||||||
justify-content: center;
|
:root {
|
||||||
}
|
--bg: #0A1628;
|
||||||
.hero {
|
--bg2: #0E1E36;
|
||||||
text-align: center;
|
--surface: #111D33;
|
||||||
padding: 3rem;
|
--border: #1E3A5F;
|
||||||
max-width: 800px;
|
--text: #E8EDF5;
|
||||||
}
|
--text-muted: #8899B3;
|
||||||
.logo {
|
--gold: #FFB300;
|
||||||
font-size: 5rem;
|
--gold-light: #FFCA40;
|
||||||
margin-bottom: 1.5rem;
|
--gold-dim: #CC8F00;
|
||||||
filter: drop-shadow(0 0 20px rgba(74, 158, 255, 0.3));
|
--ice: #82BBFF;
|
||||||
}
|
--steel: #2E5FA3;
|
||||||
h1 {
|
}
|
||||||
font-size: 3rem;
|
|
||||||
background: linear-gradient(135deg, #4a9eff, #7ab3ff);
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
body {
|
||||||
background-clip: text;
|
font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;
|
||||||
margin-bottom: 0.5rem;
|
background: radial-gradient(ellipse at 60% 30%, #13233E 0%, #0C1A30 55%, #081222 100%);
|
||||||
}
|
color: var(--text);
|
||||||
.subtitle {
|
min-height: 100vh;
|
||||||
font-size: 1.3rem;
|
display: flex;
|
||||||
color: #7ab3ff;
|
flex-direction: column;
|
||||||
font-weight: 300;
|
align-items: center;
|
||||||
margin-bottom: 1rem;
|
justify-content: center;
|
||||||
}
|
padding: 2rem 1rem;
|
||||||
.description {
|
overflow-x: hidden;
|
||||||
font-size: 1rem;
|
}
|
||||||
color: #8aa8c4;
|
|
||||||
line-height: 1.6;
|
/* ── Skip link (keyboard accessibility) ── */
|
||||||
margin-bottom: 3rem;
|
.skip-link {
|
||||||
max-width: 600px;
|
position: absolute;
|
||||||
}
|
left: -999px;
|
||||||
.services {
|
top: 0;
|
||||||
display: grid;
|
background: var(--gold);
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
color: #0A1628;
|
||||||
gap: 1rem;
|
padding: 8px 16px;
|
||||||
width: 100%;
|
border-radius: 0 0 8px 0;
|
||||||
max-width: 700px;
|
font-weight: 700;
|
||||||
padding: 0 1rem;
|
z-index: 100;
|
||||||
}
|
text-decoration: none;
|
||||||
.service {
|
}
|
||||||
background: rgba(74, 158, 255, 0.08);
|
.skip-link:focus { left: 0; }
|
||||||
border: 1px solid rgba(74, 158, 255, 0.2);
|
|
||||||
border-radius: 8px;
|
/* ── Focus ring ── */
|
||||||
padding: 1.2rem;
|
a:focus-visible, button:focus-visible {
|
||||||
text-decoration: none;
|
outline: 3px solid var(--ice);
|
||||||
color: inherit;
|
outline-offset: 3px;
|
||||||
transition: all 0.2s;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
.service:hover {
|
|
||||||
background: rgba(74, 158, 255, 0.15);
|
/* ── Background star field (CSS only, no JS needed) ── */
|
||||||
border-color: rgba(74, 158, 255, 0.5);
|
.starfield {
|
||||||
transform: translateY(-2px);
|
position: fixed;
|
||||||
}
|
inset: 0;
|
||||||
.service h3 { color: #4a9eff; font-size: 0.95rem; margin-bottom: 0.3rem; }
|
pointer-events: none;
|
||||||
.service p { color: #8aa8c4; font-size: 0.8rem; }
|
overflow: hidden;
|
||||||
.status-bar {
|
z-index: 0;
|
||||||
display: flex;
|
}
|
||||||
gap: 2rem;
|
.star {
|
||||||
margin-top: 2rem;
|
position: absolute;
|
||||||
padding: 1rem 2rem;
|
border-radius: 50%;
|
||||||
background: rgba(74, 158, 255, 0.05);
|
background: #fff;
|
||||||
border-radius: 8px;
|
opacity: 0;
|
||||||
border: 1px solid rgba(74, 158, 255, 0.1);
|
animation: twinkle var(--d, 4s) var(--delay, 0s) ease-in-out infinite;
|
||||||
}
|
}
|
||||||
.status-item { text-align: center; }
|
@keyframes twinkle {
|
||||||
.status-item .value { color: #4a9eff; font-size: 1.5rem; font-weight: 700; }
|
0%, 100% { opacity: 0; transform: scale(0.5); }
|
||||||
.status-item .label { color: #6a8ca4; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 1px; }
|
50% { opacity: var(--op, 0.5); transform: scale(1); }
|
||||||
.footer {
|
}
|
||||||
margin-top: 3rem;
|
@media (prefers-reduced-motion: reduce) {
|
||||||
color: #4a6580;
|
.star { animation: none; opacity: 0.2; }
|
||||||
font-size: 0.8rem;
|
}
|
||||||
}
|
|
||||||
.footer a { color: #4a6580; text-decoration: none; }
|
/* ── Main content ── */
|
||||||
.footer a:hover { color: #7ab3ff; }
|
main {
|
||||||
</style>
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
max-width: 520px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Bloom mark container ── */
|
||||||
|
.bloom-wrap {
|
||||||
|
width: 180px;
|
||||||
|
height: 180px;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Outer glow ring */
|
||||||
|
.bloom-wrap::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: -24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: radial-gradient(circle, rgba(255,179,0,0.12) 0%, transparent 70%);
|
||||||
|
animation: pulse-glow 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.bloom-wrap::before { animation: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-glow {
|
||||||
|
0%, 100% { transform: scale(1); opacity: 1; }
|
||||||
|
50% { transform: scale(1.12); opacity: 0.7; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The bloom SVG itself */
|
||||||
|
.bloom-svg {
|
||||||
|
width: 160px;
|
||||||
|
height: 160px;
|
||||||
|
animation: bloom-spin 24s linear infinite;
|
||||||
|
transform-origin: center;
|
||||||
|
filter: drop-shadow(0 0 18px rgba(255,179,0,0.35));
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.bloom-svg { animation: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bloom-spin {
|
||||||
|
from { transform: rotate(0deg); }
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Seed core counter-rotates so it stays upright */
|
||||||
|
.bloom-core {
|
||||||
|
animation: bloom-counter 24s linear infinite;
|
||||||
|
transform-origin: 80px 80px; /* center of 160px viewBox */
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.bloom-core { animation: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bloom-counter {
|
||||||
|
from { transform: rotate(0deg); }
|
||||||
|
to { transform: rotate(-360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Core pulse */
|
||||||
|
.bloom-core-circle {
|
||||||
|
animation: core-pulse 2.4s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.bloom-core-circle { animation: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes core-pulse {
|
||||||
|
0%, 100% { r: 13; }
|
||||||
|
50% { r: 17; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Wordmark ── */
|
||||||
|
.wordmark {
|
||||||
|
letter-spacing: 0.22em;
|
||||||
|
font-size: 1.9rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--text);
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 0.35rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tagline {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Update Center link (primary action) ── */
|
||||||
|
.update-link {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.55rem;
|
||||||
|
background: var(--gold);
|
||||||
|
color: #0A1628;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
padding: 0.75rem 1.75rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
border: 2px solid var(--gold);
|
||||||
|
transition: background 0.18s, border-color 0.18s, transform 0.15s;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
.update-link:hover {
|
||||||
|
background: var(--gold-light);
|
||||||
|
border-color: var(--gold-light);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
.update-link:active {
|
||||||
|
background: var(--gold-dim);
|
||||||
|
border-color: var(--gold-dim);
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Arrow icon inside the button */
|
||||||
|
.update-link svg {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── "More soon" note ── */
|
||||||
|
.more-soon {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Footer ── */
|
||||||
|
footer {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
margin-top: 3rem;
|
||||||
|
color: #2E5FA3;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Responsive ── */
|
||||||
|
@media (max-width: 400px) {
|
||||||
|
.bloom-wrap { width: 140px; height: 140px; }
|
||||||
|
.bloom-svg { width: 124px; height: 124px; }
|
||||||
|
.bloom-core { transform-origin: 62px 62px; }
|
||||||
|
.wordmark { font-size: 1.5rem; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="hero">
|
<a href="#main" class="skip-link">Skip to content</a>
|
||||||
<div class="logo">🌻</div>
|
|
||||||
<h1>FlowerCore</h1>
|
<!-- CSS-only star field: 20 stars, varied positions/sizes/speeds -->
|
||||||
<p class="subtitle">Blue Jay Lab</p>
|
<div class="starfield" aria-hidden="true">
|
||||||
<p class="description">
|
<div class="star" style="width:2px;height:2px;top:8%;left:12%;--d:5s;--delay:0.3s;--op:0.6"></div>
|
||||||
Multi-tenant service management platform built on .NET 10,
|
<div class="star" style="width:1px;height:1px;top:15%;left:75%;--d:7s;--delay:1.1s;--op:0.4"></div>
|
||||||
Kubernetes, and GitOps. Digital signage, telephony IVR,
|
<div class="star" style="width:2px;height:2px;top:22%;left:45%;--d:4s;--delay:0.7s;--op:0.5"></div>
|
||||||
MySQL/PHP hosting, and infrastructure automation.
|
<div class="star" style="width:1px;height:1px;top:30%;left:88%;--d:6s;--delay:2.0s;--op:0.3"></div>
|
||||||
</p>
|
<div class="star" style="width:2px;height:2px;top:40%;left:5%;--d:8s;--delay:0.4s;--op:0.4"></div>
|
||||||
|
<div class="star" style="width:1px;height:1px;top:55%;left:30%;--d:5s;--delay:1.5s;--op:0.35"></div>
|
||||||
|
<div class="star" style="width:2px;height:2px;top:65%;left:62%;--d:7s;--delay:0.9s;--op:0.45"></div>
|
||||||
|
<div class="star" style="width:1px;height:1px;top:72%;left:20%;--d:4s;--delay:2.3s;--op:0.5"></div>
|
||||||
|
<div class="star" style="width:2px;height:2px;top:80%;left:82%;--d:6s;--delay:0.1s;--op:0.3"></div>
|
||||||
|
<div class="star" style="width:1px;height:1px;top:88%;left:50%;--d:9s;--delay:1.7s;--op:0.4"></div>
|
||||||
|
<div class="star" style="width:1px;height:1px;top:5%;left:55%;--d:6s;--delay:3.1s;--op:0.3"></div>
|
||||||
|
<div class="star" style="width:2px;height:2px;top:18%;left:92%;--d:5s;--delay:0.5s;--op:0.5"></div>
|
||||||
|
<div class="star" style="width:1px;height:1px;top:35%;left:68%;--d:7s;--delay:1.9s;--op:0.35"></div>
|
||||||
|
<div class="star" style="width:2px;height:2px;top:48%;left:18%;--d:4s;--delay:2.7s;--op:0.45"></div>
|
||||||
|
<div class="star" style="width:1px;height:1px;top:60%;left:95%;--d:8s;--delay:0.6s;--op:0.3"></div>
|
||||||
|
<div class="star" style="width:2px;height:2px;top:75%;left:40%;--d:6s;--delay:1.3s;--op:0.4"></div>
|
||||||
|
<div class="star" style="width:1px;height:1px;top:85%;left:70%;--d:5s;--delay:2.9s;--op:0.35"></div>
|
||||||
|
<div class="star" style="width:2px;height:2px;top:92%;left:8%;--d:7s;--delay:0.2s;--op:0.5"></div>
|
||||||
|
<div class="star" style="width:1px;height:1px;top:10%;left:33%;--d:4s;--delay:1.8s;--op:0.3"></div>
|
||||||
|
<div class="star" style="width:2px;height:2px;top:93%;left:88%;--d:9s;--delay:3.5s;--op:0.4"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<main id="main">
|
||||||
|
<!-- Animated bloom mark -->
|
||||||
|
<div class="bloom-wrap" role="img" aria-label="FlowerCore bloom mark">
|
||||||
|
<!--
|
||||||
|
Bloom mark: 8 elliptical petals + outer ring + pulsing seed core.
|
||||||
|
Source of truth: scripts/reformat/branding/oem-logo-bluejay.svg
|
||||||
|
Petals rotate slowly (24s/rev). Core counter-rotates to stay upright.
|
||||||
|
Outer glow added via CSS. Respects prefers-reduced-motion.
|
||||||
|
-->
|
||||||
|
<svg class="bloom-svg" viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
aria-hidden="true" focusable="false">
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="petal-grad" cx="50%" cy="50%" r="50%">
|
||||||
|
<stop offset="0%" stop-color="#FFCA40" stop-opacity="0.95"/>
|
||||||
|
<stop offset="100%" stop-color="#FFB300" stop-opacity="0.7"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="core-grad" cx="50%" cy="50%" r="50%">
|
||||||
|
<stop offset="0%" stop-color="#FFCA40"/>
|
||||||
|
<stop offset="100%" stop-color="#CC8F00"/>
|
||||||
|
</radialGradient>
|
||||||
|
<!-- Petal shape: thin tall ellipse offset upward from origin -->
|
||||||
|
<ellipse id="petal" cx="0" cy="-38" rx="6.5" ry="26"
|
||||||
|
fill="none" stroke="url(#petal-grad)" stroke-width="1.8" opacity="0.85"/>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- 8 petals, each 45° apart, centred at 80,80 -->
|
||||||
|
<g transform="translate(80 80)">
|
||||||
|
<use href="#petal" transform="rotate(0)"/>
|
||||||
|
<use href="#petal" transform="rotate(45)"/>
|
||||||
|
<use href="#petal" transform="rotate(90)"/>
|
||||||
|
<use href="#petal" transform="rotate(135)"/>
|
||||||
|
<use href="#petal" transform="rotate(180)"/>
|
||||||
|
<use href="#petal" transform="rotate(225)"/>
|
||||||
|
<use href="#petal" transform="rotate(270)"/>
|
||||||
|
<use href="#petal" transform="rotate(315)"/>
|
||||||
|
<!-- Outer ring -->
|
||||||
|
<circle r="44" fill="none" stroke="#FFB300" stroke-width="1.5" opacity="0.6"/>
|
||||||
|
<!-- Seed core: counter-rotates via CSS so it stays upright -->
|
||||||
|
<g class="bloom-core">
|
||||||
|
<circle class="bloom-core-circle" r="13" fill="url(#core-grad)"/>
|
||||||
|
<circle r="13" fill="none" stroke="#0A1628" stroke-width="1.5" opacity="0.45"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="services">
|
|
||||||
<a class="service" href="https://gitea.flowercore.io">
|
<!-- Wordmark -->
|
||||||
<h3>Source</h3>
|
<p class="wordmark">FlowerCore</p>
|
||||||
<p>Gitea repositories</p>
|
<p class="tagline">More to come.</p>
|
||||||
</a>
|
|
||||||
<a class="service" href="https://webmail.flowercore.io">
|
<!-- Primary CTA: Update Center -->
|
||||||
<h3>Mail</h3>
|
<a class="update-link" href="https://update.flowercore.io/" target="_blank"
|
||||||
<p>Webmail access</p>
|
rel="noopener noreferrer">
|
||||||
</a>
|
<!-- Inline download/arrow SVG — no external ref -->
|
||||||
<a class="service" href="https://element.flowercore.io">
|
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"
|
||||||
<h3>Chat</h3>
|
aria-hidden="true" focusable="false">
|
||||||
<p>Matrix messaging</p>
|
<path d="M8 2v8M4 7l4 4 4-4" stroke="#0A1628" stroke-width="2"
|
||||||
</a>
|
stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
<a class="service" href="https://github.com/FlowerCoreIO">
|
<path d="M2 13h12" stroke="#0A1628" stroke-width="2"
|
||||||
<h3>GitHub</h3>
|
stroke-linecap="round"/>
|
||||||
<p>Open source</p>
|
</svg>
|
||||||
</a>
|
Update Center
|
||||||
</div>
|
</a>
|
||||||
<div class="status-bar">
|
|
||||||
<div class="status-item">
|
<p class="more-soon">Software updates & releases</p>
|
||||||
<div class="value">17</div>
|
</main>
|
||||||
<div class="label">Services</div>
|
|
||||||
</div>
|
<footer>
|
||||||
<div class="status-item">
|
© FlowerCore
|
||||||
<div class="value">13</div>
|
</footer>
|
||||||
<div class="label">VLANs</div>
|
|
||||||
</div>
|
|
||||||
<div class="status-item">
|
|
||||||
<div class="value">12k+</div>
|
|
||||||
<div class="label">Tests</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="footer">
|
|
||||||
FlowerCore · Bare-metal RKE2 · ArgoCD managed
|
|
||||||
· <a href="mailto:admin@flowercore.io">Contact</a>
|
|
||||||
</p>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
---
|
---
|
||||||
|
|||||||
356
apps/fc-landing/preview.html
Normal file
356
apps/fc-landing/preview.html
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>FlowerCore</title>
|
||||||
|
<!--
|
||||||
|
FlowerCore public landing page.
|
||||||
|
Air-gap safe: zero CDN, zero external fonts, zero external scripts.
|
||||||
|
All assets are inline SVG / CSS / JS only.
|
||||||
|
Update Center link: https://update.flowercore.io/
|
||||||
|
Animation: bloom mark (8-petal flower) rotates slowly with a floating
|
||||||
|
seed-core pulse. Respects prefers-reduced-motion (animation disabled).
|
||||||
|
-->
|
||||||
|
<style>
|
||||||
|
/* ── Blue Jay palette (matches decisions-waiting.html tokens) ── */
|
||||||
|
:root {
|
||||||
|
--bg: #0A1628;
|
||||||
|
--bg2: #0E1E36;
|
||||||
|
--surface: #111D33;
|
||||||
|
--border: #1E3A5F;
|
||||||
|
--text: #E8EDF5;
|
||||||
|
--text-muted: #8899B3;
|
||||||
|
--gold: #FFB300;
|
||||||
|
--gold-light: #FFCA40;
|
||||||
|
--gold-dim: #CC8F00;
|
||||||
|
--ice: #82BBFF;
|
||||||
|
--steel: #2E5FA3;
|
||||||
|
}
|
||||||
|
|
||||||
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;
|
||||||
|
background: radial-gradient(ellipse at 60% 30%, #13233E 0%, #0C1A30 55%, #081222 100%);
|
||||||
|
color: var(--text);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Skip link (keyboard accessibility) ── */
|
||||||
|
.skip-link {
|
||||||
|
position: absolute;
|
||||||
|
left: -999px;
|
||||||
|
top: 0;
|
||||||
|
background: var(--gold);
|
||||||
|
color: #0A1628;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 0 0 8px 0;
|
||||||
|
font-weight: 700;
|
||||||
|
z-index: 100;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.skip-link:focus { left: 0; }
|
||||||
|
|
||||||
|
/* ── Focus ring ── */
|
||||||
|
a:focus-visible, button:focus-visible {
|
||||||
|
outline: 3px solid var(--ice);
|
||||||
|
outline-offset: 3px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Background star field (CSS only, no JS needed) ── */
|
||||||
|
.starfield {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
.star {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #fff;
|
||||||
|
opacity: 0;
|
||||||
|
animation: twinkle var(--d, 4s) var(--delay, 0s) ease-in-out infinite;
|
||||||
|
}
|
||||||
|
@keyframes twinkle {
|
||||||
|
0%, 100% { opacity: 0; transform: scale(0.5); }
|
||||||
|
50% { opacity: var(--op, 0.5); transform: scale(1); }
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.star { animation: none; opacity: 0.2; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Main content ── */
|
||||||
|
main {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
max-width: 520px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Bloom mark container ── */
|
||||||
|
.bloom-wrap {
|
||||||
|
width: 180px;
|
||||||
|
height: 180px;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Outer glow ring */
|
||||||
|
.bloom-wrap::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: -24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: radial-gradient(circle, rgba(255,179,0,0.12) 0%, transparent 70%);
|
||||||
|
animation: pulse-glow 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.bloom-wrap::before { animation: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-glow {
|
||||||
|
0%, 100% { transform: scale(1); opacity: 1; }
|
||||||
|
50% { transform: scale(1.12); opacity: 0.7; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The bloom SVG itself */
|
||||||
|
.bloom-svg {
|
||||||
|
width: 160px;
|
||||||
|
height: 160px;
|
||||||
|
animation: bloom-spin 24s linear infinite;
|
||||||
|
transform-origin: center;
|
||||||
|
filter: drop-shadow(0 0 18px rgba(255,179,0,0.35));
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.bloom-svg { animation: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bloom-spin {
|
||||||
|
from { transform: rotate(0deg); }
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Seed core counter-rotates so it stays upright */
|
||||||
|
.bloom-core {
|
||||||
|
animation: bloom-counter 24s linear infinite;
|
||||||
|
transform-origin: 80px 80px; /* center of 160px viewBox */
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.bloom-core { animation: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bloom-counter {
|
||||||
|
from { transform: rotate(0deg); }
|
||||||
|
to { transform: rotate(-360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Core pulse */
|
||||||
|
.bloom-core-circle {
|
||||||
|
animation: core-pulse 2.4s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.bloom-core-circle { animation: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes core-pulse {
|
||||||
|
0%, 100% { r: 13; }
|
||||||
|
50% { r: 17; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Wordmark ── */
|
||||||
|
.wordmark {
|
||||||
|
letter-spacing: 0.22em;
|
||||||
|
font-size: 1.9rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--text);
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 0.35rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tagline {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Update Center link (primary action) ── */
|
||||||
|
.update-link {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.55rem;
|
||||||
|
background: var(--gold);
|
||||||
|
color: #0A1628;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
padding: 0.75rem 1.75rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
border: 2px solid var(--gold);
|
||||||
|
transition: background 0.18s, border-color 0.18s, transform 0.15s;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
.update-link:hover {
|
||||||
|
background: var(--gold-light);
|
||||||
|
border-color: var(--gold-light);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
.update-link:active {
|
||||||
|
background: var(--gold-dim);
|
||||||
|
border-color: var(--gold-dim);
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Arrow icon inside the button */
|
||||||
|
.update-link svg {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── "More soon" note ── */
|
||||||
|
.more-soon {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Footer ── */
|
||||||
|
footer {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
margin-top: 3rem;
|
||||||
|
color: #2E5FA3;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Responsive ── */
|
||||||
|
@media (max-width: 400px) {
|
||||||
|
.bloom-wrap { width: 140px; height: 140px; }
|
||||||
|
.bloom-svg { width: 124px; height: 124px; }
|
||||||
|
.bloom-core { transform-origin: 62px 62px; }
|
||||||
|
.wordmark { font-size: 1.5rem; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a href="#main" class="skip-link">Skip to content</a>
|
||||||
|
|
||||||
|
<!-- CSS-only star field: 20 stars, varied positions/sizes/speeds -->
|
||||||
|
<div class="starfield" aria-hidden="true">
|
||||||
|
<div class="star" style="width:2px;height:2px;top:8%;left:12%;--d:5s;--delay:0.3s;--op:0.6"></div>
|
||||||
|
<div class="star" style="width:1px;height:1px;top:15%;left:75%;--d:7s;--delay:1.1s;--op:0.4"></div>
|
||||||
|
<div class="star" style="width:2px;height:2px;top:22%;left:45%;--d:4s;--delay:0.7s;--op:0.5"></div>
|
||||||
|
<div class="star" style="width:1px;height:1px;top:30%;left:88%;--d:6s;--delay:2.0s;--op:0.3"></div>
|
||||||
|
<div class="star" style="width:2px;height:2px;top:40%;left:5%;--d:8s;--delay:0.4s;--op:0.4"></div>
|
||||||
|
<div class="star" style="width:1px;height:1px;top:55%;left:30%;--d:5s;--delay:1.5s;--op:0.35"></div>
|
||||||
|
<div class="star" style="width:2px;height:2px;top:65%;left:62%;--d:7s;--delay:0.9s;--op:0.45"></div>
|
||||||
|
<div class="star" style="width:1px;height:1px;top:72%;left:20%;--d:4s;--delay:2.3s;--op:0.5"></div>
|
||||||
|
<div class="star" style="width:2px;height:2px;top:80%;left:82%;--d:6s;--delay:0.1s;--op:0.3"></div>
|
||||||
|
<div class="star" style="width:1px;height:1px;top:88%;left:50%;--d:9s;--delay:1.7s;--op:0.4"></div>
|
||||||
|
<div class="star" style="width:1px;height:1px;top:5%;left:55%;--d:6s;--delay:3.1s;--op:0.3"></div>
|
||||||
|
<div class="star" style="width:2px;height:2px;top:18%;left:92%;--d:5s;--delay:0.5s;--op:0.5"></div>
|
||||||
|
<div class="star" style="width:1px;height:1px;top:35%;left:68%;--d:7s;--delay:1.9s;--op:0.35"></div>
|
||||||
|
<div class="star" style="width:2px;height:2px;top:48%;left:18%;--d:4s;--delay:2.7s;--op:0.45"></div>
|
||||||
|
<div class="star" style="width:1px;height:1px;top:60%;left:95%;--d:8s;--delay:0.6s;--op:0.3"></div>
|
||||||
|
<div class="star" style="width:2px;height:2px;top:75%;left:40%;--d:6s;--delay:1.3s;--op:0.4"></div>
|
||||||
|
<div class="star" style="width:1px;height:1px;top:85%;left:70%;--d:5s;--delay:2.9s;--op:0.35"></div>
|
||||||
|
<div class="star" style="width:2px;height:2px;top:92%;left:8%;--d:7s;--delay:0.2s;--op:0.5"></div>
|
||||||
|
<div class="star" style="width:1px;height:1px;top:10%;left:33%;--d:4s;--delay:1.8s;--op:0.3"></div>
|
||||||
|
<div class="star" style="width:2px;height:2px;top:93%;left:88%;--d:9s;--delay:3.5s;--op:0.4"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<main id="main">
|
||||||
|
<!-- Animated bloom mark -->
|
||||||
|
<div class="bloom-wrap" role="img" aria-label="FlowerCore bloom mark">
|
||||||
|
<!--
|
||||||
|
Bloom mark: 8 elliptical petals + outer ring + pulsing seed core.
|
||||||
|
Source of truth: scripts/reformat/branding/oem-logo-bluejay.svg
|
||||||
|
Petals rotate slowly (24s/rev). Core counter-rotates to stay upright.
|
||||||
|
Outer glow added via CSS. Respects prefers-reduced-motion.
|
||||||
|
-->
|
||||||
|
<svg class="bloom-svg" viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
aria-hidden="true" focusable="false">
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="petal-grad" cx="50%" cy="50%" r="50%">
|
||||||
|
<stop offset="0%" stop-color="#FFCA40" stop-opacity="0.95"/>
|
||||||
|
<stop offset="100%" stop-color="#FFB300" stop-opacity="0.7"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="core-grad" cx="50%" cy="50%" r="50%">
|
||||||
|
<stop offset="0%" stop-color="#FFCA40"/>
|
||||||
|
<stop offset="100%" stop-color="#CC8F00"/>
|
||||||
|
</radialGradient>
|
||||||
|
<!-- Petal shape: thin tall ellipse offset upward from origin -->
|
||||||
|
<ellipse id="petal" cx="0" cy="-38" rx="6.5" ry="26"
|
||||||
|
fill="none" stroke="url(#petal-grad)" stroke-width="1.8" opacity="0.85"/>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- 8 petals, each 45° apart, centred at 80,80 -->
|
||||||
|
<g transform="translate(80 80)">
|
||||||
|
<use href="#petal" transform="rotate(0)"/>
|
||||||
|
<use href="#petal" transform="rotate(45)"/>
|
||||||
|
<use href="#petal" transform="rotate(90)"/>
|
||||||
|
<use href="#petal" transform="rotate(135)"/>
|
||||||
|
<use href="#petal" transform="rotate(180)"/>
|
||||||
|
<use href="#petal" transform="rotate(225)"/>
|
||||||
|
<use href="#petal" transform="rotate(270)"/>
|
||||||
|
<use href="#petal" transform="rotate(315)"/>
|
||||||
|
<!-- Outer ring -->
|
||||||
|
<circle r="44" fill="none" stroke="#FFB300" stroke-width="1.5" opacity="0.6"/>
|
||||||
|
<!-- Seed core: counter-rotates via CSS so it stays upright -->
|
||||||
|
<g class="bloom-core">
|
||||||
|
<circle class="bloom-core-circle" r="13" fill="url(#core-grad)"/>
|
||||||
|
<circle r="13" fill="none" stroke="#0A1628" stroke-width="1.5" opacity="0.45"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Wordmark -->
|
||||||
|
<p class="wordmark">FlowerCore</p>
|
||||||
|
<p class="tagline">More to come.</p>
|
||||||
|
|
||||||
|
<!-- Primary CTA: Update Center -->
|
||||||
|
<a class="update-link" href="https://update.flowercore.io/" target="_blank"
|
||||||
|
rel="noopener noreferrer">
|
||||||
|
<!-- Inline download/arrow SVG — no external ref -->
|
||||||
|
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
aria-hidden="true" focusable="false">
|
||||||
|
<path d="M8 2v8M4 7l4 4 4-4" stroke="#0A1628" stroke-width="2"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M2 13h12" stroke="#0A1628" stroke-width="2"
|
||||||
|
stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
Update Center
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<p class="more-soon">Software updates & releases</p>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
© FlowerCore
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
63
apps/github-runner/Dockerfile.arm64
Normal file
63
apps/github-runner/Dockerfile.arm64
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
FROM myoung34/github-runner:latest
|
||||||
|
|
||||||
|
# arm64 variant of the FlowerCore self-hosted runner image.
|
||||||
|
# Built/maintained alongside the amd64 Dockerfile. The ONLY substantive
|
||||||
|
# difference from the amd64 image is that the Actions/Ruby tool-cache arch
|
||||||
|
# label is "arm64" instead of the amd64 "x64". Ruby itself is still compiled
|
||||||
|
# from source via ruby-build, so it is naturally arm64 on an aarch64 host.
|
||||||
|
|
||||||
|
ARG RUBY_VERSION=3.3.11
|
||||||
|
ARG RUBY_MINOR=3.3
|
||||||
|
ARG RUBY_BUILD_VERSION=v20260326
|
||||||
|
ARG RUNNER_UID=1001
|
||||||
|
ARG RUNNER_GID=1001
|
||||||
|
# arm64 tool-cache arch label (was x64 on amd64). ruby/setup-ruby@v1 on a
|
||||||
|
# self-hosted aarch64 Linux runner discovers Ruby under _tool/Ruby/<ver>/arm64.
|
||||||
|
ARG TOOLCACHE_ARCH=arm64
|
||||||
|
|
||||||
|
ENV RUNNER_TOOL_CACHE=/home/runner/_tool
|
||||||
|
ENV RUNNER_RUBY_TOOLCACHE=/opt/runner-toolcache
|
||||||
|
ENV PATH="/home/runner/_tool/Ruby/${RUBY_MINOR}/${TOOLCACHE_ARCH}/bin:/opt/runner-toolcache/Ruby/${RUBY_MINOR}/${TOOLCACHE_ARCH}/bin:${PATH}"
|
||||||
|
|
||||||
|
USER root
|
||||||
|
|
||||||
|
# Bake the IAmWorkin step-ca root CA into the system trust store. Without
|
||||||
|
# this, .NET HttpClient calls from CI tests against *.iamworkin.lan
|
||||||
|
# (e.g. https://selenium.iamworkin.lan/session) fail with `PartialChain`
|
||||||
|
# because the runner image's default Ubuntu trust bundle doesn't include
|
||||||
|
# our internal Root CA. update-ca-certificates regenerates
|
||||||
|
# /etc/ssl/certs/ca-certificates.crt, which OpenSSL + .NET on Linux read
|
||||||
|
# automatically — no SSL_CERT_FILE env var needed.
|
||||||
|
COPY step-ca-root.crt /usr/local/share/ca-certificates/iamworkin-step-ca-root.crt
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||||
|
autoconf \
|
||||||
|
bison \
|
||||||
|
build-essential \
|
||||||
|
ca-certificates \
|
||||||
|
curl \
|
||||||
|
libdb-dev \
|
||||||
|
libffi-dev \
|
||||||
|
libgdbm-dev \
|
||||||
|
libgmp-dev \
|
||||||
|
libncurses-dev \
|
||||||
|
libreadline-dev \
|
||||||
|
libssl-dev \
|
||||||
|
libyaml-dev \
|
||||||
|
patch \
|
||||||
|
pkg-config \
|
||||||
|
uuid-dev \
|
||||||
|
zlib1g-dev \
|
||||||
|
&& update-ca-certificates \
|
||||||
|
&& curl -fsSL "https://github.com/rbenv/ruby-build/archive/refs/tags/${RUBY_BUILD_VERSION}.tar.gz" -o /tmp/ruby-build.tar.gz \
|
||||||
|
&& mkdir -p /tmp/ruby-build \
|
||||||
|
&& tar -xzf /tmp/ruby-build.tar.gz --strip-components=1 -C /tmp/ruby-build \
|
||||||
|
&& /tmp/ruby-build/install.sh \
|
||||||
|
&& rm -rf /tmp/ruby-build /tmp/ruby-build.tar.gz /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY install-ruby-toolcache.sh /usr/local/bin/install-ruby-toolcache.sh
|
||||||
|
|
||||||
|
RUN chmod +x /usr/local/bin/install-ruby-toolcache.sh \
|
||||||
|
&& RUBY_VERSION="${RUBY_VERSION}" RUBY_MINOR="${RUBY_MINOR}" TOOLCACHE_ARCH="${TOOLCACHE_ARCH}" TOOLCACHE_ROOT="${RUNNER_RUBY_TOOLCACHE}" RUNNER_UID="${RUNNER_UID}" RUNNER_GID="${RUNNER_GID}" /usr/local/bin/install-ruby-toolcache.sh \
|
||||||
|
&& ruby -v
|
||||||
@@ -6,12 +6,16 @@ RUBY_MINOR="${RUBY_MINOR:-3.3}"
|
|||||||
TOOLCACHE_ROOT="${TOOLCACHE_ROOT:-/opt/runner-toolcache}"
|
TOOLCACHE_ROOT="${TOOLCACHE_ROOT:-/opt/runner-toolcache}"
|
||||||
RUNNER_UID="${RUNNER_UID:-1001}"
|
RUNNER_UID="${RUNNER_UID:-1001}"
|
||||||
RUNNER_GID="${RUNNER_GID:-1001}"
|
RUNNER_GID="${RUNNER_GID:-1001}"
|
||||||
RUBY_PREFIX="${TOOLCACHE_ROOT}/Ruby/${RUBY_VERSION}/x64"
|
# Tool-cache arch label. Defaults to x64 (amd64 image, unchanged). The arm64
|
||||||
|
# Dockerfile passes TOOLCACHE_ARCH=arm64 so ruby/setup-ruby@v1 finds Ruby on
|
||||||
|
# aarch64 self-hosted runners.
|
||||||
|
TOOLCACHE_ARCH="${TOOLCACHE_ARCH:-x64}"
|
||||||
|
RUBY_PREFIX="${TOOLCACHE_ROOT}/Ruby/${RUBY_VERSION}/${TOOLCACHE_ARCH}"
|
||||||
|
|
||||||
mkdir -p "${TOOLCACHE_ROOT}/Ruby"
|
mkdir -p "${TOOLCACHE_ROOT}/Ruby"
|
||||||
RUBY_CONFIGURE_OPTS="${RUBY_CONFIGURE_OPTS:---disable-install-doc --disable-yjit}" ruby-build "${RUBY_VERSION}" "${RUBY_PREFIX}"
|
RUBY_CONFIGURE_OPTS="${RUBY_CONFIGURE_OPTS:---disable-install-doc --disable-yjit}" ruby-build "${RUBY_VERSION}" "${RUBY_PREFIX}"
|
||||||
|
|
||||||
touch "${TOOLCACHE_ROOT}/Ruby/${RUBY_VERSION}/x64.complete"
|
touch "${TOOLCACHE_ROOT}/Ruby/${RUBY_VERSION}/${TOOLCACHE_ARCH}.complete"
|
||||||
ln -sfn "${RUBY_VERSION}" "${TOOLCACHE_ROOT}/Ruby/${RUBY_MINOR}"
|
ln -sfn "${RUBY_VERSION}" "${TOOLCACHE_ROOT}/Ruby/${RUBY_MINOR}"
|
||||||
|
|
||||||
"${RUBY_PREFIX}/bin/ruby" -v
|
"${RUBY_PREFIX}/bin/ruby" -v
|
||||||
|
|||||||
83
docs/gx10-tenant-landing/CUTOVER-RUNBOOK.md
Normal file
83
docs/gx10-tenant-landing/CUTOVER-RUNBOOK.md
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# GX10 Tenant Landing-Site Migration — Cutover Runbook
|
||||||
|
|
||||||
|
Date: 2026-06-16. Migrates the 5 per-tenant public landing sites from the OLD RKE2
|
||||||
|
cluster (`10.0.56.200` Traefik) to the GX10 ARM64 cluster (`10.0.57.202` VIP /
|
||||||
|
NodePort `10.0.56.14:32491`).
|
||||||
|
|
||||||
|
## Deployed on GX10 (DONE — staged-verified, NOT yet receiving public traffic)
|
||||||
|
|
||||||
|
| Domain(s) | GX10 ns | Workload | TLS secret (in ns + traefik-system) | Live content replicated |
|
||||||
|
|-----------------------------------|--------------------|---------------|-------------------------------------|-------------------------|
|
||||||
|
| bluejay.dev, www.bluejay.dev | `fc-tenant-andrew` | nginx:alpine | `cf-origin-bluejay-dev` | "Blue Jay" (custom) |
|
||||||
|
| timeforta.co, www.timeforta.co | `fc-tenant-dustin` | nginx:alpine | `cf-origin-timeforta-co` | "Coming Soon" (generic) |
|
||||||
|
| erckak.dev, www.erckak.dev | `fc-tenant-erik` | nginx:alpine | `cf-origin-erckak-dev` | "Erckak" (custom) |
|
||||||
|
| flowerinsider.xyz, www.* | `fc-tenant-fit` | nginx:alpine | `cf-origin-flowerinsider-xyz` | "Flower Insider" (custom)|
|
||||||
|
| matt.flowercore.io | `fc-tenant-matt` | nginx:alpine | `cf-origin-flowercore-io` | "Coming Soon" (generic) |
|
||||||
|
|
||||||
|
All nginx pods 1/1 Running, IngressRoutes priority 100 (override the GX10
|
||||||
|
`public-catchall`). Each site replicates EXACTLY what was live on OLD at migration
|
||||||
|
time, so cutover is content-invisible.
|
||||||
|
|
||||||
|
Staged verification (all HTTP 200, correct content, SNI-correct cert):
|
||||||
|
```
|
||||||
|
curl -sk --resolve <host>:32491:10.0.56.14 https://<host>:32491/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Public routing reality (why NO automatic cutover happened)
|
||||||
|
|
||||||
|
Every tenant domain enters the network through Cloudflare (proxied) → a dedicated
|
||||||
|
pfSense WAN IP in 74.40.140.16/28 → pfSense port-forward. ALL FIVE currently forward
|
||||||
|
to OLD Traefik `10.0.56.200:443`:
|
||||||
|
|
||||||
|
| Domain | CF origin WAN IP | pfSense rdr today |
|
||||||
|
|-------------------|------------------|--------------------|
|
||||||
|
| bluejay.dev | 74.40.140.17 | → 10.0.56.200:443 |
|
||||||
|
| matt.flowercore.io| 74.40.140.19 | → 10.0.56.200:443 |
|
||||||
|
| timeforta.co | 74.40.140.21 | → 10.0.56.200:443 |
|
||||||
|
| erckak.dev | 74.40.140.23 | → 10.0.56.200:443 |
|
||||||
|
| flowerinsider.xyz | 74.40.140.25 | → 10.0.56.200:443 |
|
||||||
|
|
||||||
|
(Contrast: main flowercore.io = WAN `.24` → already GX10 `10.0.56.14:32491`.)
|
||||||
|
NOTE: matt.flowercore.io is bound to WAN `.19` (the MATT VPN IP), NOT `.24`, so the
|
||||||
|
"*.flowercore.io already NATs to GX10" assumption does NOT cover matt.
|
||||||
|
|
||||||
|
Because none of these NAT to GX10 yet, no cutover was performed (live sites untouched).
|
||||||
|
|
||||||
|
## OPERATOR ACTION — cutover = repoint the pfSense port-forward target
|
||||||
|
|
||||||
|
For each domain, change the HTTPS (and HTTP) port-forward TARGET from
|
||||||
|
`10.0.56.200` to `10.0.56.14:32491` (HTTPS) / `10.0.56.14:30776` (HTTP). pfSense
|
||||||
|
port-forwards (Firewall → NAT → Port Forward), edit these rule descriptions:
|
||||||
|
|
||||||
|
- `ANDREW: HTTPS to Traefik` 74.40.140.17:443 → change target `10.0.56.200:443` to `10.0.56.14:32491`
|
||||||
|
- `MATT: HTTPS to Traefik` 74.40.140.19:443 → change target `10.0.56.200:443` to `10.0.56.14:32491`
|
||||||
|
- `DUSTIN: HTTPS to Traefik` 74.40.140.21:443 → change target `10.0.56.200:443` to `10.0.56.14:32491`
|
||||||
|
- `ERIK: HTTPS to Traefik` 74.40.140.23:443 → change target `10.0.56.200:443` to `10.0.56.14:32491`
|
||||||
|
- `FIT: HTTPS to Traefik` 74.40.140.25:443 → change target `10.0.56.200:443` to `10.0.56.14:32491`
|
||||||
|
- (corresponding `:80 → 10.0.56.14:30776` HTTP rules likewise, optional — sites are HTTPS-only)
|
||||||
|
|
||||||
|
No Cloudflare DNS change is required: the WAN IPs stay the same, only the internal
|
||||||
|
NAT target moves. Each can be flipped independently (per-tenant blast radius).
|
||||||
|
|
||||||
|
Post-flip verify (external):
|
||||||
|
```
|
||||||
|
curl -sI https://<host>/ # expect HTTP 200, Server: cloudflare, unchanged content
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rollback
|
||||||
|
|
||||||
|
OLD cluster left fully intact (ArgoCD apps infra-andrew/dustin/erik/fit Synced+Healthy,
|
||||||
|
pods Running). To roll back any domain: revert that pfSense port-forward target to
|
||||||
|
`10.0.56.200`.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- The OLD cluster has DUPLICATE namespaces per tenant (`tenant-X` custom page +
|
||||||
|
`fc-tenant-X` generic landing), both with IngressRoutes claiming the same host.
|
||||||
|
Traefik non-deterministically picked a winner; live content was: andrew/erik/fit =
|
||||||
|
custom (`tenant-X`), dustin/matt = generic (`fc-tenant-X`). GX10 consolidates to ONE
|
||||||
|
namespace per tenant (`fc-tenant-X`) serving the content that was actually live.
|
||||||
|
- `infra-worldbuilder` (worldbuilder.iamworkin.lan, internal .NET app) was ALREADY
|
||||||
|
migrated to GX10 (`fc-worldbuilder`, 1/1 Running) — no action.
|
||||||
|
- `infra-flowercore` (tenant-flowercore/flowercore-web demo) has NO public route and is
|
||||||
|
superseded by the production `fc-system/fc-landing-public` (flowercore.io root) already
|
||||||
|
live on GX10 — intentionally NOT migrated.
|
||||||
225
docs/gx10-tenant-landing/andrew-deploy.yaml
Normal file
225
docs/gx10-tenant-landing/andrew-deploy.yaml
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: fc-tenant-andrew
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/part-of: bluejay-infra
|
||||||
|
flowercore.io/tenant: andrew
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: andrew-web-html
|
||||||
|
namespace: fc-tenant-andrew
|
||||||
|
data:
|
||||||
|
index.html: |
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Blue Jay — bluejay.dev</title>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
background: #0A1628;
|
||||||
|
color: #e0e8f0;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.bg {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background:
|
||||||
|
radial-gradient(ellipse 80% 60% at 50% 0%, rgba(43,138,255,0.15) 0%, transparent 70%),
|
||||||
|
radial-gradient(ellipse 60% 50% at 80% 100%, rgba(43,138,255,0.08) 0%, transparent 60%),
|
||||||
|
linear-gradient(135deg, #0A1628 0%, #111E36 50%, #0A1628 100%);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
max-width: 640px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
background: rgba(43,138,255,0.12);
|
||||||
|
border: 1px solid rgba(43,138,255,0.25);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 0.4rem 1.2rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #2B8AFF;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
font-size: 4rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
filter: drop-shadow(0 0 30px rgba(43,138,255,0.3));
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 2.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
background: linear-gradient(135deg, #2B8AFF 0%, #6BB3FF 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.domain {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #6BB3FF;
|
||||||
|
font-weight: 300;
|
||||||
|
font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
background: rgba(43,138,255,0.08);
|
||||||
|
border: 1px solid rgba(43,138,255,0.15);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.status .dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: #2B8AFF;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: pulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 0.4; }
|
||||||
|
50% { opacity: 1; box-shadow: 0 0 8px rgba(43,138,255,0.6); }
|
||||||
|
}
|
||||||
|
.status p {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #8aa8c4;
|
||||||
|
}
|
||||||
|
.divider {
|
||||||
|
width: 40px;
|
||||||
|
height: 2px;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(43,138,255,0.4), transparent);
|
||||||
|
margin: 2rem auto;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
color: #3a5570;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.footer a {
|
||||||
|
color: #4a7a9e;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
h1 { font-size: 2rem; }
|
||||||
|
.icon { font-size: 3rem; }
|
||||||
|
.container { padding: 1.5rem; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="bg"></div>
|
||||||
|
<div class="container">
|
||||||
|
<div class="badge">Andrew's Space</div>
|
||||||
|
<div class="icon">🐦</div>
|
||||||
|
<h1>Blue Jay</h1>
|
||||||
|
<p class="domain">bluejay.dev</p>
|
||||||
|
<div class="status">
|
||||||
|
<span class="dot"></span>
|
||||||
|
<p>Coming Soon</p>
|
||||||
|
</div>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<p class="footer">
|
||||||
|
Powered by <a href="https://flowercore.io">FlowerCore</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: andrew-web-nginx-conf
|
||||||
|
namespace: fc-tenant-andrew
|
||||||
|
data:
|
||||||
|
default.conf: |
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
location / { try_files $uri $uri/ =404; }
|
||||||
|
location /healthz { access_log off; return 200 "ok"; add_header Content-Type text/plain; }
|
||||||
|
}
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: andrew-web
|
||||||
|
namespace: fc-tenant-andrew
|
||||||
|
labels:
|
||||||
|
app: andrew-web
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: andrew-web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: andrew-web
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: nginx:alpine
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
name: http
|
||||||
|
volumeMounts:
|
||||||
|
- name: nginx-conf
|
||||||
|
mountPath: /etc/nginx/conf.d/default.conf
|
||||||
|
subPath: default.conf
|
||||||
|
- name: html
|
||||||
|
mountPath: /usr/share/nginx/html
|
||||||
|
resources:
|
||||||
|
requests: { memory: 32Mi, cpu: 10m }
|
||||||
|
limits: { memory: 64Mi, cpu: 50m }
|
||||||
|
livenessProbe:
|
||||||
|
httpGet: { path: /healthz, port: 80 }
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
readinessProbe:
|
||||||
|
httpGet: { path: /healthz, port: 80 }
|
||||||
|
initialDelaySeconds: 3
|
||||||
|
periodSeconds: 5
|
||||||
|
volumes:
|
||||||
|
- name: nginx-conf
|
||||||
|
configMap: { name: andrew-web-nginx-conf }
|
||||||
|
- name: html
|
||||||
|
configMap: { name: andrew-web-html }
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: andrew-web
|
||||||
|
namespace: fc-tenant-andrew
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: andrew-web
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 80
|
||||||
|
name: http
|
||||||
18
docs/gx10-tenant-landing/andrew-ingressroute.yaml
Normal file
18
docs/gx10-tenant-landing/andrew-ingressroute.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: andrew-web
|
||||||
|
namespace: fc-tenant-andrew
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- match: Host(`bluejay.dev`) || Host(`www.bluejay.dev`)
|
||||||
|
kind: Rule
|
||||||
|
priority: 100
|
||||||
|
services:
|
||||||
|
- name: andrew-web
|
||||||
|
port: 80
|
||||||
|
tls:
|
||||||
|
secretName: cf-origin-bluejay-dev
|
||||||
208
docs/gx10-tenant-landing/dustin-deploy.yaml
Normal file
208
docs/gx10-tenant-landing/dustin-deploy.yaml
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: fc-tenant-dustin
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/part-of: bluejay-infra
|
||||||
|
flowercore.io/tenant: dustin
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: dustin-web-html
|
||||||
|
namespace: fc-tenant-dustin
|
||||||
|
data:
|
||||||
|
index.html: |
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>timeforta.co — Coming Soon</title>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
background: #1a1a2e;
|
||||||
|
color: #e0e0e0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
font-size: 4rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
filter: drop-shadow(0 0 20px #4aff9e40);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #4aff9e;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
.domain {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #888;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
||||||
|
}
|
||||||
|
.divider {
|
||||||
|
width: 60px;
|
||||||
|
height: 3px;
|
||||||
|
background: #4aff9e;
|
||||||
|
margin: 0 auto 2rem;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.tagline {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
color: #ccc;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.4rem 1.2rem;
|
||||||
|
background: #4aff9e15;
|
||||||
|
border: 1px solid #4aff9e40;
|
||||||
|
border-radius: 20px;
|
||||||
|
color: #4aff9e;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
.pulse {
|
||||||
|
display: inline-block;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: #4aff9e;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 8px;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.3; }
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding: 1.5rem;
|
||||||
|
color: #555;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
border-top: 1px solid #ffffff08;
|
||||||
|
background: #1a1a2e;
|
||||||
|
}
|
||||||
|
footer a {
|
||||||
|
color: #4a9eff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
footer a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="icon">🌮</div>
|
||||||
|
<h1>timeforta.co</h1>
|
||||||
|
<div class="domain">Dustin</div>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<p class="tagline">It's always time for tacos.</p>
|
||||||
|
<div class="status">
|
||||||
|
<span class="pulse"></span>
|
||||||
|
Under Construction
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer>
|
||||||
|
Powered by <a href="https://flowercore.io">FlowerCore</a> • Hosted on Blue Jay Infrastructure
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: dustin-web-nginx-conf
|
||||||
|
namespace: fc-tenant-dustin
|
||||||
|
data:
|
||||||
|
default.conf: |
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
location / { try_files $uri $uri/ =404; }
|
||||||
|
location /healthz { access_log off; return 200 "ok"; add_header Content-Type text/plain; }
|
||||||
|
}
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dustin-web
|
||||||
|
namespace: fc-tenant-dustin
|
||||||
|
labels:
|
||||||
|
app: dustin-web
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: dustin-web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: dustin-web
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: nginx:alpine
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
name: http
|
||||||
|
volumeMounts:
|
||||||
|
- name: nginx-conf
|
||||||
|
mountPath: /etc/nginx/conf.d/default.conf
|
||||||
|
subPath: default.conf
|
||||||
|
- name: html
|
||||||
|
mountPath: /usr/share/nginx/html
|
||||||
|
resources:
|
||||||
|
requests: { memory: 32Mi, cpu: 10m }
|
||||||
|
limits: { memory: 64Mi, cpu: 50m }
|
||||||
|
livenessProbe:
|
||||||
|
httpGet: { path: /healthz, port: 80 }
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
readinessProbe:
|
||||||
|
httpGet: { path: /healthz, port: 80 }
|
||||||
|
initialDelaySeconds: 3
|
||||||
|
periodSeconds: 5
|
||||||
|
volumes:
|
||||||
|
- name: nginx-conf
|
||||||
|
configMap: { name: dustin-web-nginx-conf }
|
||||||
|
- name: html
|
||||||
|
configMap: { name: dustin-web-html }
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: dustin-web
|
||||||
|
namespace: fc-tenant-dustin
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: dustin-web
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 80
|
||||||
|
name: http
|
||||||
18
docs/gx10-tenant-landing/dustin-ingressroute.yaml
Normal file
18
docs/gx10-tenant-landing/dustin-ingressroute.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: dustin-web
|
||||||
|
namespace: fc-tenant-dustin
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- match: Host(`timeforta.co`) || Host(`www.timeforta.co`)
|
||||||
|
kind: Rule
|
||||||
|
priority: 100
|
||||||
|
services:
|
||||||
|
- name: dustin-web
|
||||||
|
port: 80
|
||||||
|
tls:
|
||||||
|
secretName: cf-origin-timeforta-co
|
||||||
225
docs/gx10-tenant-landing/erik-deploy.yaml
Normal file
225
docs/gx10-tenant-landing/erik-deploy.yaml
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: fc-tenant-erik
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/part-of: bluejay-infra
|
||||||
|
flowercore.io/tenant: erik
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: erik-web-html
|
||||||
|
namespace: fc-tenant-erik
|
||||||
|
data:
|
||||||
|
index.html: |
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Erckak — erckak.dev</title>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
background: #0A1628;
|
||||||
|
color: #e0e8f0;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.bg {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background:
|
||||||
|
radial-gradient(ellipse 80% 60% at 50% 0%, rgba(43,138,255,0.15) 0%, transparent 70%),
|
||||||
|
radial-gradient(ellipse 60% 50% at 80% 100%, rgba(43,138,255,0.08) 0%, transparent 60%),
|
||||||
|
linear-gradient(135deg, #0A1628 0%, #111E36 50%, #0A1628 100%);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
max-width: 640px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
background: rgba(43,138,255,0.12);
|
||||||
|
border: 1px solid rgba(43,138,255,0.25);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 0.4rem 1.2rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #2B8AFF;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
font-size: 4rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
filter: drop-shadow(0 0 30px rgba(43,138,255,0.3));
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 2.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
background: linear-gradient(135deg, #2B8AFF 0%, #6BB3FF 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.domain {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #6BB3FF;
|
||||||
|
font-weight: 300;
|
||||||
|
font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
background: rgba(43,138,255,0.08);
|
||||||
|
border: 1px solid rgba(43,138,255,0.15);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.status .dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: #2B8AFF;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: pulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 0.4; }
|
||||||
|
50% { opacity: 1; box-shadow: 0 0 8px rgba(43,138,255,0.6); }
|
||||||
|
}
|
||||||
|
.status p {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #8aa8c4;
|
||||||
|
}
|
||||||
|
.divider {
|
||||||
|
width: 40px;
|
||||||
|
height: 2px;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(43,138,255,0.4), transparent);
|
||||||
|
margin: 2rem auto;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
color: #3a5570;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.footer a {
|
||||||
|
color: #4a7a9e;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
h1 { font-size: 2rem; }
|
||||||
|
.icon { font-size: 3rem; }
|
||||||
|
.container { padding: 1.5rem; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="bg"></div>
|
||||||
|
<div class="container">
|
||||||
|
<div class="badge">Erik's Space</div>
|
||||||
|
<div class="icon">🚀</div>
|
||||||
|
<h1>Erckak</h1>
|
||||||
|
<p class="domain">erckak.dev</p>
|
||||||
|
<div class="status">
|
||||||
|
<span class="dot"></span>
|
||||||
|
<p>Coming Soon</p>
|
||||||
|
</div>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<p class="footer">
|
||||||
|
Powered by <a href="https://flowercore.io">FlowerCore</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: erik-web-nginx-conf
|
||||||
|
namespace: fc-tenant-erik
|
||||||
|
data:
|
||||||
|
default.conf: |
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
location / { try_files $uri $uri/ =404; }
|
||||||
|
location /healthz { access_log off; return 200 "ok"; add_header Content-Type text/plain; }
|
||||||
|
}
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: erik-web
|
||||||
|
namespace: fc-tenant-erik
|
||||||
|
labels:
|
||||||
|
app: erik-web
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: erik-web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: erik-web
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: nginx:alpine
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
name: http
|
||||||
|
volumeMounts:
|
||||||
|
- name: nginx-conf
|
||||||
|
mountPath: /etc/nginx/conf.d/default.conf
|
||||||
|
subPath: default.conf
|
||||||
|
- name: html
|
||||||
|
mountPath: /usr/share/nginx/html
|
||||||
|
resources:
|
||||||
|
requests: { memory: 32Mi, cpu: 10m }
|
||||||
|
limits: { memory: 64Mi, cpu: 50m }
|
||||||
|
livenessProbe:
|
||||||
|
httpGet: { path: /healthz, port: 80 }
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
readinessProbe:
|
||||||
|
httpGet: { path: /healthz, port: 80 }
|
||||||
|
initialDelaySeconds: 3
|
||||||
|
periodSeconds: 5
|
||||||
|
volumes:
|
||||||
|
- name: nginx-conf
|
||||||
|
configMap: { name: erik-web-nginx-conf }
|
||||||
|
- name: html
|
||||||
|
configMap: { name: erik-web-html }
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: erik-web
|
||||||
|
namespace: fc-tenant-erik
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: erik-web
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 80
|
||||||
|
name: http
|
||||||
18
docs/gx10-tenant-landing/erik-ingressroute.yaml
Normal file
18
docs/gx10-tenant-landing/erik-ingressroute.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: erik-web
|
||||||
|
namespace: fc-tenant-erik
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- match: Host(`erckak.dev`) || Host(`www.erckak.dev`)
|
||||||
|
kind: Rule
|
||||||
|
priority: 100
|
||||||
|
services:
|
||||||
|
- name: erik-web
|
||||||
|
port: 80
|
||||||
|
tls:
|
||||||
|
secretName: cf-origin-erckak-dev
|
||||||
225
docs/gx10-tenant-landing/fit-deploy.yaml
Normal file
225
docs/gx10-tenant-landing/fit-deploy.yaml
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: fc-tenant-fit
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/part-of: bluejay-infra
|
||||||
|
flowercore.io/tenant: fit
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: fit-web-html
|
||||||
|
namespace: fc-tenant-fit
|
||||||
|
data:
|
||||||
|
index.html: |
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Flower Insider — flowerinsider.xyz</title>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
background: #0A1628;
|
||||||
|
color: #e0e8f0;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.bg {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background:
|
||||||
|
radial-gradient(ellipse 80% 60% at 50% 0%, rgba(43,138,255,0.15) 0%, transparent 70%),
|
||||||
|
radial-gradient(ellipse 60% 50% at 80% 100%, rgba(43,138,255,0.08) 0%, transparent 60%),
|
||||||
|
linear-gradient(135deg, #0A1628 0%, #111E36 50%, #0A1628 100%);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
max-width: 640px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
background: rgba(43,138,255,0.12);
|
||||||
|
border: 1px solid rgba(43,138,255,0.25);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 0.4rem 1.2rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #2B8AFF;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
font-size: 4rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
filter: drop-shadow(0 0 30px rgba(43,138,255,0.3));
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 2.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
background: linear-gradient(135deg, #2B8AFF 0%, #6BB3FF 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.domain {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #6BB3FF;
|
||||||
|
font-weight: 300;
|
||||||
|
font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
background: rgba(43,138,255,0.08);
|
||||||
|
border: 1px solid rgba(43,138,255,0.15);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.status .dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: #2B8AFF;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: pulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 0.4; }
|
||||||
|
50% { opacity: 1; box-shadow: 0 0 8px rgba(43,138,255,0.6); }
|
||||||
|
}
|
||||||
|
.status p {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #8aa8c4;
|
||||||
|
}
|
||||||
|
.divider {
|
||||||
|
width: 40px;
|
||||||
|
height: 2px;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(43,138,255,0.4), transparent);
|
||||||
|
margin: 2rem auto;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
color: #3a5570;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.footer a {
|
||||||
|
color: #4a7a9e;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
h1 { font-size: 2rem; }
|
||||||
|
.icon { font-size: 3rem; }
|
||||||
|
.container { padding: 1.5rem; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="bg"></div>
|
||||||
|
<div class="container">
|
||||||
|
<div class="badge">Flower Insider Team</div>
|
||||||
|
<div class="icon">🌸</div>
|
||||||
|
<h1>Flower Insider</h1>
|
||||||
|
<p class="domain">flowerinsider.xyz</p>
|
||||||
|
<div class="status">
|
||||||
|
<span class="dot"></span>
|
||||||
|
<p>Coming Soon</p>
|
||||||
|
</div>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<p class="footer">
|
||||||
|
Powered by <a href="https://flowercore.io">FlowerCore</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: fit-web-nginx-conf
|
||||||
|
namespace: fc-tenant-fit
|
||||||
|
data:
|
||||||
|
default.conf: |
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
location / { try_files $uri $uri/ =404; }
|
||||||
|
location /healthz { access_log off; return 200 "ok"; add_header Content-Type text/plain; }
|
||||||
|
}
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: fit-web
|
||||||
|
namespace: fc-tenant-fit
|
||||||
|
labels:
|
||||||
|
app: fit-web
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: fit-web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: fit-web
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: nginx:alpine
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
name: http
|
||||||
|
volumeMounts:
|
||||||
|
- name: nginx-conf
|
||||||
|
mountPath: /etc/nginx/conf.d/default.conf
|
||||||
|
subPath: default.conf
|
||||||
|
- name: html
|
||||||
|
mountPath: /usr/share/nginx/html
|
||||||
|
resources:
|
||||||
|
requests: { memory: 32Mi, cpu: 10m }
|
||||||
|
limits: { memory: 64Mi, cpu: 50m }
|
||||||
|
livenessProbe:
|
||||||
|
httpGet: { path: /healthz, port: 80 }
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
readinessProbe:
|
||||||
|
httpGet: { path: /healthz, port: 80 }
|
||||||
|
initialDelaySeconds: 3
|
||||||
|
periodSeconds: 5
|
||||||
|
volumes:
|
||||||
|
- name: nginx-conf
|
||||||
|
configMap: { name: fit-web-nginx-conf }
|
||||||
|
- name: html
|
||||||
|
configMap: { name: fit-web-html }
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: fit-web
|
||||||
|
namespace: fc-tenant-fit
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: fit-web
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 80
|
||||||
|
name: http
|
||||||
18
docs/gx10-tenant-landing/fit-ingressroute.yaml
Normal file
18
docs/gx10-tenant-landing/fit-ingressroute.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: fit-web
|
||||||
|
namespace: fc-tenant-fit
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- match: Host(`flowerinsider.xyz`) || Host(`www.flowerinsider.xyz`)
|
||||||
|
kind: Rule
|
||||||
|
priority: 100
|
||||||
|
services:
|
||||||
|
- name: fit-web
|
||||||
|
port: 80
|
||||||
|
tls:
|
||||||
|
secretName: cf-origin-flowerinsider-xyz
|
||||||
208
docs/gx10-tenant-landing/matt-deploy.yaml
Normal file
208
docs/gx10-tenant-landing/matt-deploy.yaml
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: fc-tenant-matt
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/part-of: bluejay-infra
|
||||||
|
flowercore.io/tenant: matt
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: matt-web-html
|
||||||
|
namespace: fc-tenant-matt
|
||||||
|
data:
|
||||||
|
index.html: |
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>matt.flowercore.io — Coming Soon</title>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
background: #1a1a2e;
|
||||||
|
color: #e0e0e0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
font-size: 4rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
filter: drop-shadow(0 0 20px #ff6b4a40);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #ff6b4a;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
.domain {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #888;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
||||||
|
}
|
||||||
|
.divider {
|
||||||
|
width: 60px;
|
||||||
|
height: 3px;
|
||||||
|
background: #ff6b4a;
|
||||||
|
margin: 0 auto 2rem;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.tagline {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
color: #ccc;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.4rem 1.2rem;
|
||||||
|
background: #ff6b4a15;
|
||||||
|
border: 1px solid #ff6b4a40;
|
||||||
|
border-radius: 20px;
|
||||||
|
color: #ff6b4a;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
.pulse {
|
||||||
|
display: inline-block;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: #ff6b4a;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 8px;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.3; }
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding: 1.5rem;
|
||||||
|
color: #555;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
border-top: 1px solid #ffffff08;
|
||||||
|
background: #1a1a2e;
|
||||||
|
}
|
||||||
|
footer a {
|
||||||
|
color: #4a9eff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
footer a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="icon">🔥</div>
|
||||||
|
<h1>matt.flowercore.io</h1>
|
||||||
|
<div class="domain">Matt</div>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<p class="tagline">Building something extraordinary.</p>
|
||||||
|
<div class="status">
|
||||||
|
<span class="pulse"></span>
|
||||||
|
Under Construction
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer>
|
||||||
|
Powered by <a href="https://flowercore.io">FlowerCore</a> • Hosted on Blue Jay Infrastructure
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: matt-web-nginx-conf
|
||||||
|
namespace: fc-tenant-matt
|
||||||
|
data:
|
||||||
|
default.conf: |
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
location / { try_files $uri $uri/ =404; }
|
||||||
|
location /healthz { access_log off; return 200 "ok"; add_header Content-Type text/plain; }
|
||||||
|
}
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: matt-web
|
||||||
|
namespace: fc-tenant-matt
|
||||||
|
labels:
|
||||||
|
app: matt-web
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: matt-web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: matt-web
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: nginx:alpine
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
name: http
|
||||||
|
volumeMounts:
|
||||||
|
- name: nginx-conf
|
||||||
|
mountPath: /etc/nginx/conf.d/default.conf
|
||||||
|
subPath: default.conf
|
||||||
|
- name: html
|
||||||
|
mountPath: /usr/share/nginx/html
|
||||||
|
resources:
|
||||||
|
requests: { memory: 32Mi, cpu: 10m }
|
||||||
|
limits: { memory: 64Mi, cpu: 50m }
|
||||||
|
livenessProbe:
|
||||||
|
httpGet: { path: /healthz, port: 80 }
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
readinessProbe:
|
||||||
|
httpGet: { path: /healthz, port: 80 }
|
||||||
|
initialDelaySeconds: 3
|
||||||
|
periodSeconds: 5
|
||||||
|
volumes:
|
||||||
|
- name: nginx-conf
|
||||||
|
configMap: { name: matt-web-nginx-conf }
|
||||||
|
- name: html
|
||||||
|
configMap: { name: matt-web-html }
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: matt-web
|
||||||
|
namespace: fc-tenant-matt
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: matt-web
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 80
|
||||||
|
name: http
|
||||||
18
docs/gx10-tenant-landing/matt-ingressroute.yaml
Normal file
18
docs/gx10-tenant-landing/matt-ingressroute.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: matt-web
|
||||||
|
namespace: fc-tenant-matt
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- match: Host(`matt.flowercore.io`)
|
||||||
|
kind: Rule
|
||||||
|
priority: 100
|
||||||
|
services:
|
||||||
|
- name: matt-web
|
||||||
|
port: 80
|
||||||
|
tls:
|
||||||
|
secretName: cf-origin-flowercore-io
|
||||||
Reference in New Issue
Block a user