Compare commits
4
Commits
17b3e2d7d8
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
053d19ce6a | ||
|
|
afc05deb91 | ||
|
|
2502c2946e | ||
|
|
d865ee2d2b |
+6
-1
@@ -19,4 +19,9 @@ npm-debug.log*
|
|||||||
|
|
||||||
# Raw WordPress XML Exports / Backups
|
# Raw WordPress XML Exports / Backups
|
||||||
*.WordPress.*.xml
|
*.WordPress.*.xml
|
||||||
*.wxr.xml
|
*.wxr.xml
|
||||||
|
|
||||||
|
# Kubernetes Production Secrets
|
||||||
|
k8s/01-secret.yaml
|
||||||
|
k8s/*secret.yaml
|
||||||
|
!k8s/*.example.yaml
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
.PHONY: help dev dev-persistent test lint clean docker-build docker-run
|
.PHONY: help dev dev-persistent test lint clean docker-build docker-run k8s-status k8s-logs k8s-restart k8s-sync-content
|
||||||
|
|
||||||
# Default target
|
# Default target
|
||||||
all: help
|
all: help
|
||||||
@@ -8,15 +8,19 @@ help:
|
|||||||
@echo " pengzhan.dev — Engineering Makefile"
|
@echo " pengzhan.dev — Engineering Makefile"
|
||||||
@echo "========================================================================"
|
@echo "========================================================================"
|
||||||
@echo "Available commands:"
|
@echo "Available commands:"
|
||||||
@echo " make dev Start local server in Ephemeral Staging Mode"
|
@echo " make dev Start local server in Ephemeral Staging Mode"
|
||||||
@echo " (Safe by default: site.db is NEVER mutated)"
|
@echo " (Safe by default: site.db is NEVER mutated)"
|
||||||
@echo " make dev-persistent Start local server in Persistent DB Mode"
|
@echo " make dev-persistent Start local server in Persistent DB Mode"
|
||||||
@echo " (Changes to comments & logs write to site.db)"
|
@echo " (Changes to comments & logs write to site.db)"
|
||||||
@echo " make test Run complete automated test suite (in isolated temp DB)"
|
@echo " make test Run complete automated test suite"
|
||||||
@echo " make lint Run comprehensive project linter (JS, EJS, CSS)"
|
@echo " make lint Run comprehensive project linter (JS, EJS, CSS)"
|
||||||
@echo " make clean Remove temporary staging DBs and build caches"
|
@echo " make clean Remove temporary staging DBs and build caches"
|
||||||
@echo " make docker-build Build multi-stage production Docker image"
|
@echo " make docker-build Build multi-stage production Docker image"
|
||||||
@echo " make docker-run Run production Docker container on port 3000"
|
@echo " make docker-run Run production Docker container on port 3000"
|
||||||
|
@echo " make k8s-status Show pods, service, ingress, and PVC in k8s"
|
||||||
|
@echo " make k8s-logs Stream live logs from production pod"
|
||||||
|
@echo " make k8s-restart Trigger zero-downtime rolling restart"
|
||||||
|
@echo " make k8s-sync-content Sync local markdown/media into live cluster PVC"
|
||||||
@echo "========================================================================"
|
@echo "========================================================================"
|
||||||
|
|
||||||
dev:
|
dev:
|
||||||
@@ -50,3 +54,27 @@ docker-build:
|
|||||||
docker-run:
|
docker-run:
|
||||||
@echo "Running Docker container on http://localhost:3000..."
|
@echo "Running Docker container on http://localhost:3000..."
|
||||||
docker run --rm -p 3000:3000 -v $(PWD)/sample-content:/app/content pengzhan.dev:latest
|
docker run --rm -p 3000:3000 -v $(PWD)/sample-content:/app/content pengzhan.dev:latest
|
||||||
|
|
||||||
|
k8s-status:
|
||||||
|
@echo "Checking Kubernetes status in namespace pengzhan-dev..."
|
||||||
|
kubectl get pods,svc,ingress,pvc -n pengzhan-dev -o wide
|
||||||
|
|
||||||
|
k8s-logs:
|
||||||
|
@echo "Streaming logs from pengzhan-web deployment..."
|
||||||
|
kubectl logs -f deployment/pengzhan-web -n pengzhan-dev
|
||||||
|
|
||||||
|
k8s-restart:
|
||||||
|
@echo "Triggering zero-downtime rolling restart of pengzhan-web..."
|
||||||
|
kubectl rollout restart deployment/pengzhan-web -n pengzhan-dev
|
||||||
|
kubectl rollout status deployment/pengzhan-web -n pengzhan-dev
|
||||||
|
|
||||||
|
k8s-sync-content:
|
||||||
|
@echo "Syncing local sample-content/ to Kubernetes /app/content/..."
|
||||||
|
@POD=$$(kubectl get pod -n pengzhan-dev -l app=pengzhan-web -o jsonpath='{.items[0].metadata.name}'); \
|
||||||
|
if [ -z "$$POD" ]; then \
|
||||||
|
echo "Error: No running pengzhan-web pod found."; exit 1; \
|
||||||
|
fi; \
|
||||||
|
echo "-> Target pod: $$POD"; \
|
||||||
|
kubectl cp sample-content/. pengzhan-dev/$$POD:/app/content/; \
|
||||||
|
kubectl exec -n pengzhan-dev $$POD -- chmod -R 775 /app/content; \
|
||||||
|
echo "-> Sync complete. Hot-reload watcher will index new content."
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: pengzhan-dev
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: pengzhan-dev
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Template Kubernetes secret for pengzhan.dev
|
||||||
|
# Copy this file to k8s/01-secret.yaml (gitignored) and customize before applying.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: pengzhan-secrets
|
||||||
|
namespace: pengzhan-dev
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
ADMIN_SECRET: "CHANGEME_ADMIN_SECRET"
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: pengzhan-content-pvc
|
||||||
|
namespace: pengzhan-dev
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: local-path
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 5Gi
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: pengzhan-web
|
||||||
|
namespace: pengzhan-dev
|
||||||
|
labels:
|
||||||
|
app: pengzhan-web
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
revisionHistoryLimit: 5
|
||||||
|
strategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 1
|
||||||
|
maxUnavailable: 0
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: pengzhan-web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: pengzhan-web
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
fsGroup: 1000
|
||||||
|
fsGroupChangePolicy: "OnRootMismatch"
|
||||||
|
initContainers:
|
||||||
|
- name: init-content-perms
|
||||||
|
image: busybox:1.36
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command: ["sh", "-c"]
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
mkdir -p /app/content/data
|
||||||
|
chown -R 1000:1000 /app/content || true
|
||||||
|
chmod -R 775 /app/content || true
|
||||||
|
volumeMounts:
|
||||||
|
- name: content-data
|
||||||
|
mountPath: /app/content
|
||||||
|
containers:
|
||||||
|
- name: pengzhan-web
|
||||||
|
image: git.pengzhan.dev/haopengzhan/pengzhan.dev:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
name: http
|
||||||
|
env:
|
||||||
|
- name: NODE_ENV
|
||||||
|
value: "production"
|
||||||
|
- name: PORT
|
||||||
|
value: "3000"
|
||||||
|
- name: CONTENT_DIR
|
||||||
|
value: "/app/content"
|
||||||
|
- name: ADMIN_SECRET
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: pengzhan-secrets
|
||||||
|
key: ADMIN_SECRET
|
||||||
|
volumeMounts:
|
||||||
|
- name: content-data
|
||||||
|
mountPath: /app/content
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 3000
|
||||||
|
initialDelaySeconds: 15
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 3000
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 2
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
volumes:
|
||||||
|
- name: content-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: pengzhan-content-pvc
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: pengzhan-web
|
||||||
|
namespace: pengzhan-dev
|
||||||
|
labels:
|
||||||
|
app: pengzhan-web
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app: pengzhan-web
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 3000
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: pengzhan-ingress
|
||||||
|
namespace: pengzhan-dev
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
rules:
|
||||||
|
- host: pengzhan.dev
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: pengzhan-web
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- pengzhan.dev
|
||||||
|
secretName: pengzhan-dev-tls
|
||||||
@@ -1,17 +1,40 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
injectHead({ pluginConfig }) {
|
injectHead({ pluginConfig, pageType }) {
|
||||||
const trackingId = pluginConfig.tracking_id;
|
const trackingId = pluginConfig.tracking_id;
|
||||||
if (!trackingId) return '';
|
if (!trackingId) return '';
|
||||||
|
|
||||||
|
// 1. Never inject analytics on administrative moderation pages
|
||||||
|
if (pageType === 'admin') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Do not inject in development, staging, or testing modes (unless explicitly enabled)
|
||||||
|
const isProd = process.env.NODE_ENV === 'production' && process.env.APP_ENV !== 'staging';
|
||||||
|
if (!isProd && !pluginConfig.enable_in_dev) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<!-- Google Analytics (GA4 via Plugin) -->
|
<!-- Google Analytics (GA4 via Plugin) -->
|
||||||
<script async src="https://www.googletagmanager.com/gtag/js?id=${trackingId}"></script>
|
<script async src="https://www.googletagmanager.com/gtag/js?id=${trackingId}"></script>
|
||||||
<script>
|
<script>
|
||||||
window.dataLayer = window.dataLayer || [];
|
(function() {
|
||||||
function gtag(){dataLayer.push(arguments);}
|
// 3. Client-side domain safeguard: only track on production pengzhan.dev domains
|
||||||
gtag('js', new Date());
|
var host = window.location.hostname;
|
||||||
gtag('config', '${trackingId}');
|
if (host !== 'pengzhan.dev' && !host.endsWith('.pengzhan.dev')) {
|
||||||
|
window['ga-disable-${trackingId}'] = true;
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
window.gtag = function(){};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag(){dataLayer.push(arguments);}
|
||||||
|
window.gtag = gtag;
|
||||||
|
gtag('js', new Date());
|
||||||
|
gtag('config', '${trackingId}');
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user