Initial commit: Essential Oil Formula Cost Calculator

This commit is contained in:
2026-04-06 13:46:32 +00:00
commit 0368e85abe
25 changed files with 20897 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: hourly-backup
namespace: oil-calculator
spec:
schedule: "0 * * * *" # Every hour
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 2
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: registry.oci.euphon.net/oil-calculator:latest
command:
- sh
- -c
- |
BACKUP_DIR=/data/backups
mkdir -p $BACKUP_DIR
DATE=$(date +%Y%m%d_%H%M%S)
# Backup SQLite database using .backup for consistency
sqlite3 /data/oil_calculator.db ".backup '$BACKUP_DIR/oil_calculator_${DATE}.db'"
echo "Backup done: $BACKUP_DIR/oil_calculator_${DATE}.db ($(du -h $BACKUP_DIR/oil_calculator_${DATE}.db | cut -f1))"
# Keep last 48 backups (2 days of hourly)
ls -t $BACKUP_DIR/oil_calculator_*.db | tail -n +49 | xargs rm -f 2>/dev/null
echo "Backups retained: $(ls $BACKUP_DIR/oil_calculator_*.db | wc -l)"
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: oil-calculator-data
restartPolicy: OnFailure
imagePullSecrets:
- name: regcred

30
deploy/cronjob.yaml Normal file
View File

@@ -0,0 +1,30 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: weekly-review
namespace: oil-calculator
spec:
schedule: "0 9 * * 1" # Every Monday 9:00 UTC (17:00 China time)
jobTemplate:
spec:
template:
spec:
containers:
- name: cron
image: curlimages/curl:latest
command:
- sh
- -c
- |
curl -sf -X POST \
-H "Authorization: Bearer $(ADMIN_TOKEN)" \
-H "Content-Type: application/json" \
-d '{}' \
http://oil-calculator.oil-calculator.svc/api/cron/weekly-review
env:
- name: ADMIN_TOKEN
valueFrom:
secretKeyRef:
name: oil-calculator-secrets
key: admin-token
restartPolicy: OnFailure

47
deploy/deployment.yaml Normal file
View File

@@ -0,0 +1,47 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: oil-calculator
namespace: oil-calculator
spec:
replicas: 1
selector:
matchLabels:
app: oil-calculator
template:
metadata:
labels:
app: oil-calculator
spec:
imagePullSecrets:
- name: regcred
containers:
- name: oil-calculator
image: registry.oci.euphon.net/oil-calculator:latest
imagePullPolicy: Always
ports:
- containerPort: 8000
env:
- name: DB_PATH
value: /data/oil_calculator.db
- name: FRONTEND_DIR
value: /app/frontend
- name: ADMIN_TOKEN
valueFrom:
secretKeyRef:
name: oil-calculator-secrets
key: admin-token
volumeMounts:
- name: data
mountPath: /data
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
volumes:
- name: data
persistentVolumeClaim:
claimName: oil-calculator-data

23
deploy/ingress.yaml Normal file
View File

@@ -0,0 +1,23 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: oil-calculator
namespace: oil-calculator
annotations:
traefik.ingress.kubernetes.io/router.tls.certresolver: le
spec:
ingressClassName: traefik
rules:
- host: oil.oci.euphon.net
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: oil-calculator
port:
number: 80
tls:
- hosts:
- oil.oci.euphon.net

4
deploy/namespace.yaml Normal file
View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: oil-calculator

11
deploy/pvc.yaml Normal file
View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: oil-calculator-data
namespace: oil-calculator
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi

11
deploy/service.yaml Normal file
View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: oil-calculator
namespace: oil-calculator
spec:
selector:
app: oil-calculator
ports:
- port: 80
targetPort: 8000

View File

@@ -0,0 +1,82 @@
#!/bin/bash
# Creates a restricted kubeconfig for the oil-calculator namespace only.
# Run on the k8s server as a user with cluster-admin access.
set -e
NAMESPACE=oil-calculator
SA_NAME=oil-calculator-deployer
echo "Creating ServiceAccount, Role, and RoleBinding..."
kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
name: ${SA_NAME}
namespace: ${NAMESPACE}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ${SA_NAME}-role
namespace: ${NAMESPACE}
rules:
- apiGroups: ["", "apps", "networking.k8s.io"]
resources: ["pods", "services", "deployments", "replicasets", "ingresses", "persistentvolumeclaims", "configmaps", "secrets", "pods/log"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ${SA_NAME}-binding
namespace: ${NAMESPACE}
subjects:
- kind: ServiceAccount
name: ${SA_NAME}
namespace: ${NAMESPACE}
roleRef:
kind: Role
name: ${SA_NAME}-role
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: Secret
metadata:
name: ${SA_NAME}-token
namespace: ${NAMESPACE}
annotations:
kubernetes.io/service-account.name: ${SA_NAME}
type: kubernetes.io/service-account-token
EOF
echo "Waiting for token..."
sleep 3
# Get cluster info
CLUSTER_SERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
CLUSTER_CA=$(kubectl config view --raw --minify -o jsonpath='{.clusters[0].cluster.certificate-authority-data}')
TOKEN=$(kubectl get secret ${SA_NAME}-token -n ${NAMESPACE} -o jsonpath='{.data.token}' | base64 -d)
cat > kubeconfig <<EOF
apiVersion: v1
kind: Config
clusters:
- cluster:
certificate-authority-data: ${CLUSTER_CA}
server: ${CLUSTER_SERVER}
name: oil-calculator
contexts:
- context:
cluster: oil-calculator
namespace: ${NAMESPACE}
user: ${SA_NAME}
name: oil-calculator
current-context: oil-calculator
users:
- name: ${SA_NAME}
user:
token: ${TOKEN}
EOF
echo "Kubeconfig written to ./kubeconfig"
echo "Test with: KUBECONFIG=./kubeconfig kubectl get pods"