nová verze

This commit is contained in:
2026-08-13 09:10:21 +02:00
parent 76f3e68805
commit 69b0216521
120 changed files with 20366 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
#mta.krumax.cz
#api.krumax.cz
#us1.krumax.cz
us2.krumax.cz
us3.krumax.cz
us4.krumax.cz
us5.krumax.cz
us6.krumax.cz
us7.krumax.cz
us8.krumax.cz
us9.krumax.cz
us10.krumax.cz
us11.krumax.cz
us12.krumax.cz
us13.krumax.cz
us14.krumax.cz
us15.krumax.cz
us16.krumax.cz
us17.krumax.cz
us18.krumax.cz
us19.krumax.cz
us20.krumax.cz
us21.krumax.cz
us22.krumax.cz
us23.krumax.cz
us24.krumax.cz
us25.krumax.cz
us26.krumax.cz
us27.krumax.cz
us28.krumax.cz
us29.krumax.cz
us30.krumax.cz
us31.krumax.cz
us32.krumax.cz
us33.krumax.cz
us34.krumax.cz
us35.krumax.cz
us36.krumax.cz
us37.krumax.cz
us38.krumax.cz
us39.krumax.cz
us40.krumax.cz
us41.krumax.cz
us42.krumax.cz
us43.krumax.cz
us44.krumax.cz
us45.krumax.cz
us46.krumax.cz
us47.krumax.cz
us48.krumax.cz
us49.krumax.cz
+4
View File
@@ -0,0 +1,4 @@
apiVersion: v2
name: stack
version: 0.1.0
type: application
@@ -0,0 +1,16 @@
profiles:
mariadbMaster:
cpuRequest: 1000m
cpuLimit: 4000m
threadPoolSize: 4
mariadbSlave:
cpuRequest: 250m
cpuLimit: 1000m
threadPoolSize: 1
redis:
cpuRequest: 250m
cpuLimit: 1000m
maxClients: 20000
openlitespeed:
cpuRequest: 3000m
cpuLimit: 7000m
@@ -0,0 +1,16 @@
profiles:
mariadbMaster:
cpuRequest: 2000m
cpuLimit: 8000m
threadPoolSize: 6
mariadbSlave:
cpuRequest: 500m
cpuLimit: 2000m
threadPoolSize: 1
redis:
cpuRequest: 750m
cpuLimit: 4000m
maxClients: 30000
openlitespeed:
cpuRequest: 5000m
cpuLimit: 12000m
@@ -0,0 +1,16 @@
profiles:
mariadbMaster:
cpuRequest: 500m
cpuLimit: 1500m
threadPoolSize: 2
mariadbSlave:
cpuRequest: 100m
cpuLimit: 500m
threadPoolSize: 1
redis:
cpuRequest: 100m
cpuLimit: 500m
maxClients: 8000
openlitespeed:
cpuRequest: 750m
cpuLimit: 2000m
@@ -0,0 +1,16 @@
profiles:
mariadbMaster:
cpuRequest: 750m
cpuLimit: 2500m
threadPoolSize: 3
mariadbSlave:
cpuRequest: 200m
cpuLimit: 750m
threadPoolSize: 1
redis:
cpuRequest: 150m
cpuLimit: 750m
maxClients: 12000
openlitespeed:
cpuRequest: 1250m
cpuLimit: 3000m
@@ -0,0 +1,26 @@
profiles:
mariadbMaster:
memoryRequest: 28Gi
memoryLimit: 40Gi
maxConnections: 600
innodbBufferPoolSize: 30G
innodbBufferPoolInstances: 16
tableOpenCache: 16000
tableOpenCacheInstances: 16
tmpTableSize: 64M
mariadbSlave:
memoryRequest: 8Gi
memoryLimit: 12Gi
maxConnections: 50
innodbBufferPoolSize: 8G
innodbBufferPoolInstances: 8
tableOpenCache: 8000
tableOpenCacheInstances: 8
tmpTableSize: 64M
redis:
memoryRequest: 2Gi
memoryLimit: 5Gi
maxmemory: 3500mb
openlitespeed:
memoryRequest: 8Gi
memoryLimit: 24Gi
@@ -0,0 +1,26 @@
profiles:
mariadbMaster:
memoryRequest: 2Gi
memoryLimit: 4Gi
maxConnections: 80
innodbBufferPoolSize: 2G
innodbBufferPoolInstances: 2
tableOpenCache: 2000
tableOpenCacheInstances: 4
tmpTableSize: 8M
mariadbSlave:
memoryRequest: 512Mi
memoryLimit: 1Gi
maxConnections: 30
innodbBufferPoolSize: 512M
innodbBufferPoolInstances: 1
tableOpenCache: 1000
tableOpenCacheInstances: 2
tmpTableSize: 8M
redis:
memoryRequest: 128Mi
memoryLimit: 512Mi
maxmemory: 350mb
openlitespeed:
memoryRequest: 2Gi
memoryLimit: 4Gi
@@ -0,0 +1,26 @@
profiles:
mariadbMaster:
memoryRequest: 4Gi
memoryLimit: 8Gi
maxConnections: 150
innodbBufferPoolSize: 5G
innodbBufferPoolInstances: 5
tableOpenCache: 4000
tableOpenCacheInstances: 8
tmpTableSize: 16M
mariadbSlave:
memoryRequest: 1Gi
memoryLimit: 2Gi
maxConnections: 50
innodbBufferPoolSize: 1G
innodbBufferPoolInstances: 1
tableOpenCache: 2000
tableOpenCacheInstances: 4
tmpTableSize: 16M
redis:
memoryRequest: 256Mi
memoryLimit: 1Gi
maxmemory: 700mb
openlitespeed:
memoryRequest: 3Gi
memoryLimit: 6Gi
@@ -0,0 +1,26 @@
profiles:
mariadbMaster:
memoryRequest: 8Gi
memoryLimit: 16Gi
maxConnections: 250
innodbBufferPoolSize: 10G
innodbBufferPoolInstances: 10
tableOpenCache: 8000
tableOpenCacheInstances: 16
tmpTableSize: 32M
mariadbSlave:
memoryRequest: 2Gi
memoryLimit: 4Gi
maxConnections: 50
innodbBufferPoolSize: 2G
innodbBufferPoolInstances: 2
tableOpenCache: 4000
tableOpenCacheInstances: 8
tmpTableSize: 32M
redis:
memoryRequest: 512Mi
memoryLimit: 2Gi
maxmemory: 1500mb
openlitespeed:
memoryRequest: 8Gi
memoryLimit: 16Gi
@@ -0,0 +1,19 @@
{{- if .Values.debugHelm }}
---
apiVersion: v1
kind: Pod
metadata: { name: debug, namespace: "{{ .Values.namespace }}" }
spec:
containers:
- name: debug
image: nicolaka/netshoot:latest
command: ["sh","-c","sleep infinity"]
stdin: true
tty: true
securityContext:
capabilities:
add: ["NET_RAW","NET_ADMIN"] # for tcpdump/mtr
restartPolicy: Never
{{- end }}
@@ -0,0 +1,299 @@
{{- if .Values.mariadbHelm }}
---
apiVersion: v1
kind: ConfigMap
metadata: { name: "{{ .Values.mariadbMaster }}-config", namespace: "{{ .Values.namespace }}" }
data:
replication.cnf: |
[mysqld]
skip_name_resolve=1
server-id=1
# --- log ---
log_bin=mysql-bin
binlog_format=ROW
binlog_expire_logs_seconds=604800
max_binlog_total_size=32212254720
# --- durability ---
innodb_flush_log_at_trx_commit=1
sync_binlog=1
# --- connection / thread pool ---
max_connections={{ .Values.profiles.mariadbMaster.maxConnections }}
thread_handling=pool-of-threads
thread_pool_size={{ .Values.profiles.mariadbMaster.threadPoolSize }}
thread_pool_max_threads=128
thread_pool_stall_limit=500
innodb_buffer_pool_size={{ .Values.profiles.mariadbMaster.innodbBufferPoolSize }}
innodb_buffer_pool_instances={{ .Values.profiles.mariadbMaster.innodbBufferPoolInstances }}
innodb_flush_method=O_DIRECT
innodb_flush_neighbors=0
innodb_io_capacity=2000
innodb_io_capacity_max=4000
innodb_log_file_size=1G
innodb_log_buffer_size=64M
# --- cache ---
query_cache_type=0
query_cache_size=0
table_open_cache={{ .Values.profiles.mariadbMaster.tableOpenCache }}
table_open_cache_instances={{ .Values.profiles.mariadbMaster.tableOpenCacheInstances }}
table_definition_cache={{ .Values.profiles.mariadbMaster.tableOpenCache }}
open_files_limit=65535
# --- buffers ---
tmp_table_size={{ .Values.profiles.mariadbMaster.tmpTableSize }}
max_heap_table_size={{ .Values.profiles.mariadbMaster.tmpTableSize }}
sort_buffer_size=2M
join_buffer_size=2M
read_buffer_size=256K
read_rnd_buffer_size=512K
# --- timeouts ---
wait_timeout=60
interactive_timeout=300
max_allowed_packet=64M
slow_query_log=1
long_query_time=1
slow_query_log_file=/var/lib/mysql/slow.log
log_error=/var/lib/mysql/error.log
---
apiVersion: v1
kind: ConfigMap
metadata: { name: "{{ .Values.mariadbSlave }}-config", namespace: "{{ .Values.namespace }}" }
data:
replication.cnf: |
[mysqld]
skip_name_resolve=1
server-id=2
read_only=1
# --- log ---
relay-log=relay-log
relay_log_purge=1
relay_log_recovery=1
# --- connection / thread pool ---
max_connections={{ .Values.profiles.mariadbSlave.maxConnections }}
thread_handling=pool-of-threads
thread_pool_size={{ .Values.profiles.mariadbSlave.threadPoolSize }}
thread_pool_max_threads=32
thread_pool_stall_limit=500
# --- InnoDB ---
innodb_buffer_pool_size={{ .Values.profiles.mariadbSlave.innodbBufferPoolSize }}
innodb_buffer_pool_instances={{ .Values.profiles.mariadbSlave.innodbBufferPoolInstances }}
innodb_flush_method=O_DIRECT
innodb_flush_neighbors=0
innodb_io_capacity=1000
innodb_io_capacity_max=2000
innodb_log_file_size=512M
innodb_log_buffer_size=32M
# --- durability (for standby recovery replica) ---
innodb_flush_log_at_trx_commit=1
sync_binlog=1
# --- cache ---
query_cache_type=0
query_cache_size=0
table_open_cache={{ .Values.profiles.mariadbSlave.tableOpenCache }}
table_open_cache_instances={{ .Values.profiles.mariadbSlave.tableOpenCacheInstances }}
table_definition_cache={{ .Values.profiles.mariadbSlave.tableOpenCache }}
open_files_limit=65535
# --- buffers ---
tmp_table_size={{ .Values.profiles.mariadbSlave.tmpTableSize }}
max_heap_table_size={{ .Values.profiles.mariadbSlave.tmpTableSize }}
sort_buffer_size=1M
join_buffer_size=1M
read_buffer_size=256K
read_rnd_buffer_size=512K
# --- timeouts ---
wait_timeout=60
interactive_timeout=300
max_allowed_packet=64M
# --- logs ---
slow_query_log=0
log_error=/var/lib/mysql/error.log
---
apiVersion: v1
kind: PersistentVolume
metadata: { name: "{{ .Values.mariadbMaster }}-pv" }
spec:
capacity: { storage: 100Gi }
volumeMode: Filesystem
accessModes: [ ReadWriteOnce ]
persistentVolumeReclaimPolicy: Retain
hostPath: { path: "{{ .Values.mariadbMasterPath }}", type: DirectoryOrCreate }
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: "{{ .Values.mariadbMaster }}-pvc", namespace: "{{ .Values.namespace }}" }
spec:
volumeName: "{{ .Values.mariadbMaster }}-pv"
volumeMode: Filesystem
accessModes: [ ReadWriteOnce ]
resources: { requests: { storage: 100Gi } }
storageClassName: ""
---
apiVersion: v1
kind: PersistentVolume
metadata: { name: "{{ .Values.mariadbSlave }}-pv" }
spec:
capacity: { storage: 100Gi }
volumeMode: Filesystem
accessModes: [ ReadWriteOnce ]
persistentVolumeReclaimPolicy: Retain
hostPath: { path: "{{ .Values.mariadbSlavePath }}", type: DirectoryOrCreate }
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: "{{ .Values.mariadbSlave }}-pvc", namespace: "{{ .Values.namespace }}" }
spec:
volumeName: "{{ .Values.mariadbSlave }}-pv"
volumeMode: Filesystem
accessModes: [ ReadWriteOnce ]
resources: { requests: { storage: 100Gi } }
storageClassName: ""
---
apiVersion: apps/v1
kind: StatefulSet
metadata: { name: "{{ .Values.mariadbMaster }}", namespace: "{{ .Values.namespace }}" }
spec:
serviceName: "{{ .Values.mariadbMaster }}-headless"
replicas: 1
selector: { matchLabels: { app: "{{ .Values.mariadbMaster }}" } }
template:
metadata: { labels: { app: "{{ .Values.mariadbMaster }}" } }
spec:
terminationGracePeriodSeconds: 60
containers:
- name: "{{ .Values.mariadbMaster }}"
image: mariadb:12.2
resources:
requests: { cpu: "{{ .Values.profiles.mariadbMaster.cpuRequest }}", memory: "{{ .Values.profiles.mariadbMaster.memoryRequest }}" }
limits: { cpu: "{{ .Values.profiles.mariadbMaster.cpuLimit }}", memory: "{{ .Values.profiles.mariadbMaster.memoryLimit }}" }
ports:
- { containerPort: 3306 }
env:
- { name: MARIADB_ROOT_PASSWORD, valueFrom: { secretKeyRef: { name: "{{ .Values.mariadb }}-secret", key: root-password } } }
readinessProbe:
exec:
command: ["sh","-lc",'mariadb-admin ping -h 127.0.0.1 -uroot -p"$MARIADB_ROOT_PASSWORD" --silent']
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 6
livenessProbe:
exec:
command: ["sh","-lc",'mariadb-admin ping -h 127.0.0.1 -uroot -p"$MARIADB_ROOT_PASSWORD" --silent']
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 6
volumeMounts:
- { name: "{{ .Values.mariadbMaster }}-volume", mountPath: /var/lib/mysql }
- { name: "{{ .Values.mariadbMaster }}-config-volume", mountPath: /etc/mysql/conf.d/replication.cnf, subPath: replication.cnf }
volumes:
- name: "{{ .Values.mariadbMaster }}-volume"
persistentVolumeClaim: { claimName: "{{ .Values.mariadbMaster }}-pvc" }
- name: "{{ .Values.mariadbMaster }}-config-volume"
configMap: { name: "{{ .Values.mariadbMaster }}-config" }
---
apiVersion: apps/v1
kind: StatefulSet
metadata: { name: "{{ .Values.mariadbSlave }}", namespace: "{{ .Values.namespace }}" }
spec:
serviceName: "{{ .Values.mariadbSlave }}-headless"
replicas: 1
selector: { matchLabels: { app: "{{ .Values.mariadbSlave }}" } }
template:
metadata: { labels: { app: "{{ .Values.mariadbSlave }}" } }
spec:
terminationGracePeriodSeconds: 60
containers:
- name: "{{ .Values.mariadbSlave }}"
image: mariadb:12.2
resources:
requests: { cpu: "{{ .Values.profiles.mariadbSlave.cpuRequest }}", memory: "{{ .Values.profiles.mariadbSlave.memoryRequest }}" }
limits: { cpu: "{{ .Values.profiles.mariadbSlave.cpuLimit }}", memory: "{{ .Values.profiles.mariadbSlave.memoryLimit }}" }
ports:
- { containerPort: 3306 }
env:
- { name: MARIADB_ROOT_PASSWORD, valueFrom: { secretKeyRef: { name: "{{ .Values.mariadb }}-secret", key: root-password } } }
readinessProbe:
exec:
command: ["sh","-lc",'mariadb-admin ping -h 127.0.0.1 -uroot -p"$MARIADB_ROOT_PASSWORD" --silent']
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 6
livenessProbe:
exec:
command: ["sh","-lc",'mariadb-admin ping -h 127.0.0.1 -uroot -p"$MARIADB_ROOT_PASSWORD" --silent']
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 6
volumeMounts:
- { name: "{{ .Values.mariadbSlave }}-volume", mountPath: /var/lib/mysql }
- { name: "{{ .Values.mariadbSlave }}-config-volume", mountPath: /etc/mysql/conf.d/replication.cnf, subPath: replication.cnf }
volumes:
- name: "{{ .Values.mariadbSlave }}-volume"
persistentVolumeClaim: { claimName: "{{ .Values.mariadbSlave }}-pvc" }
- name: "{{ .Values.mariadbSlave }}-config-volume"
configMap: { name: "{{ .Values.mariadbSlave }}-config" }
---
apiVersion: v1
kind: Service
metadata: { name: "{{ .Values.mariadbMaster }}", namespace: "{{ .Values.namespace }}" }
spec:
selector: { app: "{{ .Values.mariadbMaster }}" }
ports: [ { name: mysql, protocol: TCP, port: 3306, targetPort: 3306 } ]
---
apiVersion: v1
kind: Service
metadata: { name: "{{ .Values.mariadbSlave }}", namespace: "{{ .Values.namespace }}" }
spec:
selector: { app: "{{ .Values.mariadbSlave }}" }
ports: [ { name: mysql, protocol: TCP, port: 3306, targetPort: 3306 } ]
---
apiVersion: v1
kind: Service
metadata: { name: "{{ .Values.mariadbMaster }}-headless", namespace: "{{ .Values.namespace }}" }
spec:
clusterIP: None
selector: { app: "{{ .Values.mariadbMaster }}" }
ports:
- { name: mysql, protocol: TCP, port: 3306, targetPort: 3306 }
---
apiVersion: v1
kind: Service
metadata: { name: "{{ .Values.mariadbSlave }}-headless", namespace: "{{ .Values.namespace }}" }
spec:
clusterIP: None
selector: { app: "{{ .Values.mariadbSlave }}" }
ports:
- { name: mysql, protocol: TCP, port: 3306, targetPort: 3306 }
{{- end }}
@@ -0,0 +1,128 @@
{{- if .Values.metricHelm }}
---
apiVersion: v1
kind: Service
metadata: { name: "{{ .Values.metric }}-node-exporter", namespace: "{{ .Values.namespace }}" }
spec:
selector: { app: "{{ .Values.metric }}-node-exporter" }
ports: [ { name: metrics, protocol: TCP, port: 9100, targetPort: 9100 } ]
---
apiVersion: apps/v1
kind: DaemonSet
metadata: { name: "{{ .Values.metric }}-node-exporter", namespace: "{{ .Values.namespace }}" }
spec:
selector:
matchLabels: { app: "{{ .Values.metric }}-node-exporter" }
template:
metadata:
labels: { app: "{{ .Values.metric }}-node-exporter" }
spec:
hostNetwork: true
hostPID: true
dnsPolicy: ClusterFirstWithHostNet
tolerations:
- operator: "Exists"
containers:
- name: "{{ .Values.metric }}-node-exporter"
image: quay.io/prometheus/node-exporter:v1.8.2
args:
- --web.listen-address=:9100
- --path.procfs=/host/proc
- --path.sysfs=/host/sys
- --path.rootfs=/host/root
- --collector.textfile.directory={{ .Values.metricPromPath }}
ports: [ { containerPort: 9100, hostPort: 9100, name: metrics } ]
resources:
requests: { cpu: "10m", memory: "32Mi" }
limits: { cpu: "150m", memory: "200Mi" }
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
volumeMounts:
- { name: proc, mountPath: /host/proc, readOnly: true }
- { name: sys, mountPath: /host/sys, readOnly: true }
- { name: root, mountPath: /host/root, readOnly: true }
- { name: textfile, mountPath: "{{ .Values.metricPromPath }}", readOnly: true }
volumes:
- name: proc
hostPath: { path: /proc, type: Directory }
- name: sys
hostPath: { path: /sys, type: Directory }
- name: root
hostPath: { path: /, type: Directory }
- name: textfile
hostPath: { path: "{{ .Values.metricPromPath }}", type: DirectoryOrCreate }
---
apiVersion: v1
kind: ServiceAccount
metadata: { name: "{{ .Values.metric }}-vmagent", namespace: "{{ .Values.namespace }}" }
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata: { name: "{{ .Values.metric }}-vmagent" }
rules:
- apiGroups: [""]
resources: ["nodes", "nodes/proxy", "services", "endpoints", "pods"]
verbs: ["get", "list", "watch"]
- apiGroups: ["discovery.k8s.io"]
resources: ["endpointslices"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata: { name: "{{ .Values.metric }}-vmagent" }
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: "{{ .Values.metric }}-vmagent"
subjects:
- kind: ServiceAccount
name: "{{ .Values.metric }}-vmagent"
namespace: "{{ .Values.namespace }}"
---
apiVersion: v1
kind: Service
metadata: { name: "{{ .Values.metric }}-vmagent", namespace: "{{ .Values.namespace }}" }
spec:
selector: { app: "{{ .Values.metric }}-vmagent" }
ports: [ { name: http, protocol: TCP, port: 8429, targetPort: 8429 } ]
---
apiVersion: apps/v1
kind: Deployment
metadata: { name: "{{ .Values.metric }}-vmagent", namespace: "{{ .Values.namespace }}" }
spec:
replicas: 1
selector:
matchLabels: { app: "{{ .Values.metric }}-vmagent" }
template:
metadata:
labels: { app: "{{ .Values.metric }}-vmagent" }
spec:
serviceAccountName: "{{ .Values.metric }}-vmagent"
containers:
- name: "{{ .Values.metric }}-vmagent"
image: victoriametrics/vmagent:v1.112.0
args:
- -promscrape.config=/etc/vmagent/vmagent.yml
- -httpListenAddr=:8429
- -loggerLevel=INFO
- -remoteWrite.url={{ .Values.metricApiUrl }}
- -remoteWrite.label=server={{ .Values.namespace }}
ports: [ { containerPort: 8429, name: http } ]
resources:
requests: { cpu: "30m", memory: "128Mi" }
limits: { cpu: "300m", memory: "512Mi" }
volumeMounts:
- { name: "{{ .Values.metric }}-vmagent-config-volume", mountPath: /etc/vmagent/vmagent.yml, subPath: vmagent.yml, readOnly: true }
volumes:
- name: "{{ .Values.metric }}-vmagent-config-volume"
hostPath: { path: "{{ .Values.metricVmagentPath }}", type: DirectoryOrCreate }
{{- end }}
@@ -0,0 +1,301 @@
{{- if .Values.openlitespeedHelm }}
---
apiVersion: v1
kind: PersistentVolume
metadata: { name: "{{ .Values.openlitespeed }}-vhosts-pv" }
spec:
capacity: { storage: 500Gi }
volumeMode: Filesystem
accessModes: [ ReadWriteMany ]
persistentVolumeReclaimPolicy: Retain
hostPath: { path: "{{ .Values.vhostsPath }}", type: DirectoryOrCreate }
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: "{{ .Values.openlitespeed }}-vhosts-pvc", namespace: "{{ .Values.namespace }}" }
spec:
volumeName: "{{ .Values.openlitespeed }}-vhosts-pv"
volumeMode: Filesystem
accessModes: [ ReadWriteMany ]
resources: { requests: { storage: 500Gi } }
storageClassName: ""
---
apiVersion: v1
kind: PersistentVolume
metadata: { name: "{{ .Values.openlitespeed }}-private-pv" }
spec:
capacity: { storage: 5Gi }
volumeMode: Filesystem
accessModes: [ ReadWriteMany ]
persistentVolumeReclaimPolicy: Retain
hostPath: { path: "{{ .Values.olsPrivatePath }}", type: DirectoryOrCreate }
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: "{{ .Values.openlitespeed }}-private-pvc", namespace: "{{ .Values.namespace }}" }
spec:
volumeName: "{{ .Values.openlitespeed }}-private-pv"
volumeMode: Filesystem
accessModes: [ ReadWriteMany ]
resources: { requests: { storage: 5Gi } }
storageClassName: ""
---
apiVersion: v1
kind: PersistentVolume
metadata: { name: "{{ .Values.openlitespeed }}-public-pv" }
spec:
capacity: { storage: 10Gi }
volumeMode: Filesystem
accessModes: [ ReadWriteMany ]
persistentVolumeReclaimPolicy: Retain
hostPath: { path: "{{ .Values.olsPublicPath }}", type: DirectoryOrCreate }
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: "{{ .Values.openlitespeed }}-public-pvc", namespace: "{{ .Values.namespace }}" }
spec:
volumeName: "{{ .Values.openlitespeed }}-public-pv"
volumeMode: Filesystem
accessModes: [ ReadWriteMany ]
resources: { requests: { storage: 10Gi } }
storageClassName: ""
---
apiVersion: v1
kind: PersistentVolume
metadata: { name: "{{ .Values.openlitespeed }}-config-pv" }
spec:
capacity: { storage: 1Gi }
volumeMode: Filesystem
accessModes: [ ReadWriteMany ]
persistentVolumeReclaimPolicy: Retain
hostPath: { path: "{{ .Values.olsConfigPath }}", type: DirectoryOrCreate }
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: "{{ .Values.openlitespeed }}-config-pvc", namespace: "{{ .Values.namespace }}" }
spec:
volumeName: "{{ .Values.openlitespeed }}-config-pv"
volumeMode: Filesystem
accessModes: [ ReadWriteMany ]
resources: { requests: { storage: 1Gi } }
storageClassName: ""
---
apiVersion: v1
kind: PersistentVolume
metadata: { name: "{{ .Values.openlitespeed }}-admin-pv" }
spec:
capacity: { storage: 1Gi }
volumeMode: Filesystem
accessModes: [ ReadWriteMany ]
persistentVolumeReclaimPolicy: Retain
hostPath: { path: "{{ .Values.olsAdminPath }}", type: DirectoryOrCreate }
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: "{{ .Values.openlitespeed }}-admin-pvc", namespace: "{{ .Values.namespace }}" }
spec:
volumeName: "{{ .Values.openlitespeed }}-admin-pv"
volumeMode: Filesystem
accessModes: [ ReadWriteMany ]
resources: { requests: { storage: 1Gi } }
storageClassName: ""
---
apiVersion: v1
kind: PersistentVolume
metadata: { name: "{{ .Values.openlitespeed }}-phpini-pv" }
spec:
capacity: { storage: 1Gi }
volumeMode: Filesystem
accessModes: [ ReadWriteMany ]
persistentVolumeReclaimPolicy: Retain
hostPath: { path: "{{ .Values.olsPhpIniPath }}", type: DirectoryOrCreate }
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: "{{ .Values.openlitespeed }}-phpini-pvc", namespace: "{{ .Values.namespace }}" }
spec:
volumeName: "{{ .Values.openlitespeed }}-phpini-pv"
volumeMode: Filesystem
accessModes: [ ReadWriteMany ]
resources: { requests: { storage: 1Gi } }
storageClassName: ""
---
apiVersion: v1
kind: PersistentVolume
metadata: { name: "{{ .Values.openlitespeed }}-logs-pv" }
spec:
capacity: { storage: 10Gi }
volumeMode: Filesystem
accessModes: [ ReadWriteMany ]
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
hostPath: { path: "{{ .Values.olsLogsPath }}", type: DirectoryOrCreate }
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: "{{ .Values.openlitespeed }}-logs-pvc", namespace: "{{ .Values.namespace }}" }
spec:
volumeName: "{{ .Values.openlitespeed }}-logs-pv"
volumeMode: Filesystem
accessModes: [ ReadWriteMany ]
resources: { requests: { storage: 10Gi } }
storageClassName: ""
---
apiVersion: apps/v1
kind: Deployment
metadata: { name: "{{ .Values.openlitespeed }}", namespace: "{{ .Values.namespace }}" }
spec:
replicas: 2
strategy: { type: RollingUpdate, rollingUpdate: { maxSurge: 0, maxUnavailable: 1 } }
selector: { matchLabels: { app: "{{ .Values.openlitespeed }}" } }
template:
metadata: { labels: { app: "{{ .Values.openlitespeed }}" } }
spec:
initContainers:
- name: "{{ .Values.openlitespeed }}-init"
image: litespeedtech/openlitespeed:1.8.5-lsphp85
command: ["sh", "-c"]
args:
- |
if [ ! -f /mnt/config/httpd_config.conf ]; then
cp -a /usr/local/lsws/conf/. /mnt/config/
fi
if [ ! -f /mnt/admin/admin_config.conf ]; then
cp -a /usr/local/lsws/admin/conf/. /mnt/admin/
fi
volumeMounts:
- { name: "{{ .Values.openlitespeed }}-config-volume", mountPath: /mnt/config }
- { name: "{{ .Values.openlitespeed }}-admin-volume", mountPath: /mnt/admin }
shareProcessNamespace: true
containers:
- name: "{{ .Values.openlitespeed }}"
image: litespeedtech/openlitespeed:1.8.5-lsphp85
ports:
- { containerPort: 80 }
- { containerPort: 7080 }
resources:
requests: { cpu: "{{ .Values.profiles.openlitespeed.cpuRequest }}", memory: "{{ .Values.profiles.openlitespeed.memoryRequest }}" }
limits: { cpu: "{{ .Values.profiles.openlitespeed.cpuLimit }}", memory: "{{ .Values.profiles.openlitespeed.memoryLimit }}" }
readinessProbe: { tcpSocket: { port: 80 }, initialDelaySeconds: 5, periodSeconds: 5, failureThreshold: 3 }
livenessProbe: { tcpSocket: { port: 80 }, initialDelaySeconds: 10, periodSeconds: 10, failureThreshold: 5 }
volumeMounts:
- { name: "{{ .Values.openlitespeed }}-vhosts-volume", mountPath: {{ .Values.olsPodVhostsPath }} }
- { name: "{{ .Values.openlitespeed }}-private-volume", mountPath: {{ .Values.olsPodPrivatePath }}, readOnly: true }
- { name: "{{ .Values.openlitespeed }}-public-volume", mountPath: {{ .Values.olsPodPublicPath }}, readOnly: true }
- { name: "{{ .Values.openlitespeed }}-config-volume", mountPath: /usr/local/lsws/conf }
- { name: "{{ .Values.openlitespeed }}-admin-volume", mountPath: /usr/local/lsws/admin/conf }
- { name: "{{ .Values.openlitespeed }}-phpini-volume", mountPath: /etc/ols-php-ini }
- { name: "{{ .Values.openlitespeed }}-logs-volume", mountPath: /usr/local/lsws/logs }
- { name: "{{ .Values.openlitespeed }}-cache-volume", mountPath: /usr/local/lsws/cachedata }
- { name: "{{ .Values.openlitespeed }}-tmp-volume", mountPath: /tmp/lshttpd }
volumes:
- name: "{{ .Values.openlitespeed }}-vhosts-volume"
persistentVolumeClaim: { claimName: "{{ .Values.openlitespeed }}-vhosts-pvc" }
- name: "{{ .Values.openlitespeed }}-private-volume"
persistentVolumeClaim: { claimName: "{{ .Values.openlitespeed }}-private-pvc" }
- name: "{{ .Values.openlitespeed }}-public-volume"
persistentVolumeClaim: { claimName: "{{ .Values.openlitespeed }}-public-pvc" }
- name: "{{ .Values.openlitespeed }}-config-volume"
persistentVolumeClaim: { claimName: "{{ .Values.openlitespeed }}-config-pvc" }
- name: "{{ .Values.openlitespeed }}-admin-volume"
persistentVolumeClaim: { claimName: "{{ .Values.openlitespeed }}-admin-pvc" }
- name: "{{ .Values.openlitespeed }}-phpini-volume"
persistentVolumeClaim: { claimName: "{{ .Values.openlitespeed }}-phpini-pvc" }
- name: "{{ .Values.openlitespeed }}-logs-volume"
persistentVolumeClaim: { claimName: "{{ .Values.openlitespeed }}-logs-pvc" }
- name: "{{ .Values.openlitespeed }}-cache-volume"
emptyDir: { sizeLimit: 100Gi }
- name: "{{ .Values.openlitespeed }}-tmp-volume"
emptyDir: { sizeLimit: 100Gi }
---
apiVersion: v1
kind: Service
metadata: { name: "{{ .Values.openlitespeed }}", namespace: "{{ .Values.namespace }}" }
spec:
selector: { app: "{{ .Values.openlitespeed }}" }
ports:
- { name: http, protocol: TCP, port: 80, targetPort: 80 }
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata: { name: "{{ .Values.openlitespeed }}-ingress", namespace: "{{ .Values.namespace }}" }
spec:
ingressClassName: traefik
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service: { name: "{{ .Values.openlitespeed }}", port: { number: 80 } }
# -------------------------
# Admin panel
# -------------------------
---
apiVersion: v1
kind: Service
metadata: { name: "{{ .Values.openlitespeed }}-admin", namespace: "{{ .Values.namespace }}" }
spec:
selector: { app: "{{ .Values.openlitespeed }}" }
type: ClusterIP
ports:
- { name: admin, protocol: TCP, port: 7080, targetPort: 7080 }
---
apiVersion: traefik.io/v1alpha1
kind: MiddlewareTCP
metadata: { name: "{{ .Values.openlitespeed }}-admin-allowlist", namespace: "{{ .Values.namespace }}" }
spec:
ipAllowList: { sourceRange: [ {{ .Values.olsAdminWhiteList }} ] }
---
apiVersion: traefik.io/v1alpha1
kind: IngressRouteTCP
metadata: { name: "{{ .Values.openlitespeed }}-admin", namespace: "{{ .Values.namespace }}" }
spec:
entryPoints: [ "olsadmin" ]
routes:
- match: HostSNI(`*`)
middlewares:
- name: "{{ .Values.openlitespeed }}-admin-allowlist"
services:
- name: "{{ .Values.openlitespeed }}-admin"
port: 7080
tls:
passthrough: true
---
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata: { name: traefik, namespace: kube-system }
spec:
valuesContent: |-
ports:
olsadmin:
port: 9443
expose:
default: true
exposedPort: 9443
protocol: TCP
additionalArguments:
- "--entryPoints.olsadmin.address=:9443/tcp"
{{- end }}
@@ -0,0 +1,188 @@
{{- if .Values.postfixHelm }}
---
apiVersion: v1
kind: Secret
metadata: { name: "{{ .Values.postfix }}-tls", namespace: "{{ .Values.namespace }}" }
type: kubernetes.io/tls
data:
tls.crt: {{ .Values.postfixTlsCrtB64 }}
tls.key: {{ .Values.postfixTlsKeyB64 }}
---
apiVersion: v1
kind: ConfigMap
metadata: { name: "{{ .Values.postfix }}-sasl", namespace: "{{ .Values.namespace }}" }
data:
smtpd.conf: |
pwcheck_method: auxprop
auxprop_plugin: sasldb
sasldb_path: /config/sasldb2
mech_list: PLAIN LOGIN
default_realm: {{ .Values.postfixDefaultRealm }}
---
apiVersion: v1
kind: PersistentVolume
metadata: { name: "{{ .Values.postfix }}-config-pv" }
spec:
capacity: { storage: 1Gi }
volumeMode: Filesystem
accessModes: [ ReadWriteOnce ]
persistentVolumeReclaimPolicy: Retain
hostPath: { path: "{{ .Values.postfixConfigPath }}", type: DirectoryOrCreate }
---
apiVersion: v1
kind: PersistentVolume
metadata: { name: "{{ .Values.postfix }}-dkim-pv" }
spec:
capacity: { storage: 1Gi }
volumeMode: Filesystem
accessModes: [ ReadWriteOnce ]
persistentVolumeReclaimPolicy: Retain
hostPath: { path: "{{ .Values.postfixDkimPath }}", type: DirectoryOrCreate }
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: "{{ .Values.postfix }}-config-pvc", namespace: "{{ .Values.namespace }}" }
spec:
volumeName: "{{ .Values.postfix }}-config-pv"
volumeMode: Filesystem
accessModes: [ ReadWriteOnce ]
resources: { requests: { storage: 1Gi } }
storageClassName: ""
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: "{{ .Values.postfix }}-dkim-pvc", namespace: "{{ .Values.namespace }}" }
spec:
volumeName: "{{ .Values.postfix }}-dkim-pv"
volumeMode: Filesystem
accessModes: [ ReadWriteOnce ]
resources: { requests: { storage: 1Gi } }
storageClassName: ""
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: "{{ .Values.postfix }}-queue-pvc", namespace: "{{ .Values.namespace }}" }
spec:
accessModes: ["ReadWriteOnce"]
resources: { requests: { storage: 5Gi } }
---
apiVersion: apps/v1
kind: Deployment
metadata: { name: "{{ .Values.postfix }}", namespace: "{{ .Values.namespace }}" }
spec:
replicas: 1
selector: { matchLabels: { app: "{{ .Values.postfix }}" } }
template:
metadata: { labels: { app: "{{ .Values.postfix }}" } }
spec:
enableServiceLinks: false
initContainers:
- name: "{{ .Values.postfix }}-dkim-init"
image: boky/postfix:4.4.0-alpine
imagePullPolicy: IfNotPresent
command: ["/bin/sh", "-lc"]
args:
- |
set -e
if [ ! -f /dkim/opendkim.conf ]; then
cp -a /etc/opendkim/* /dkim/
fi
touch /dkim/TrustedHosts /dkim/SigningTable /dkim/KeyTable
chown opendkim:opendkim /dkim/TrustedHosts /dkim/KeyTable /dkim/SigningTable
chmod 0644 /dkim/TrustedHosts /dkim/KeyTable /dkim/SigningTable
printf '%s\n' 127.0.0.1 localhost 10.42.0.0/16 10.43.0.0/16 > /dkim/TrustedHosts
volumeMounts:
- name: "{{ .Values.postfix }}-dkim-volume"
mountPath: /dkim
containers:
- name: "{{ .Values.postfix }}"
image: boky/postfix:4.4.0-alpine
ports:
- { containerPort: 25, name: smtp }
- { containerPort: 587, name: submission }
env:
- { name: POSTFIX_myhostname, value: "{{ .Values.postfixHost }}" }
- { name: POSTFIX_smtpd_banner, value: "$myhostname ESMTP" }
- { name: POSTFIX_mynetworks, value: "127.0.0.0/8" }
- { name: POSTFIX_inet_interfaces, value: "all" }
# Rules
- { name: POSTFIX_smtpd_client_restrictions, value: "permit_mynetworks, permit_sasl_authenticated, reject" }
- { name: POSTFIX_smtpd_relay_restrictions, value: "permit_sasl_authenticated, reject_unauth_destination" }
- { name: POSTFIX_smtpd_sender_restrictions, value: "reject_non_fqdn_sender,reject_unknown_sender_domain,reject_sender_login_mismatch,permit_sasl_authenticated,reject_unauth_destination" }
# TLS
- { name: POSTFIX_smtpd_tls_cert_file, value: "/etc/ssl/mail/tls.crt" }
- { name: POSTFIX_smtpd_tls_key_file, value: "/etc/ssl/mail/tls.key" }
- { name: POSTFIX_smtpd_tls_security_level, value: "may" }
- { name: POSTFIX_smtp_tls_security_level, value: "may" }
# SASL (Cyrus, sasldb2)
- { name: POSTFIX_smtpd_sasl_auth_enable, value: "yes" }
- { name: POSTFIX_smtpd_sasl_type, value: "cyrus" }
- { name: POSTFIX_smtpd_sasl_path, value: "smtpd" }
- { name: POSTFIX_cyrus_sasl_config_path, value: "/etc/sasl2" }
# Maps (Virtual domain/alias and senders)
- { name: POSTFIX_virtual_alias_domains, value: "lmdb:/config/{{ .Values.postfixDomainsFile }}" }
- { name: POSTFIX_virtual_alias_maps, value: "lmdb:/config/{{ .Values.postfixAliasesFile }}" }
- { name: POSTFIX_smtpd_sender_login_maps, value: "lmdb:/config/{{ .Values.postfixSendersFile }}" }
# DKIM
- { name: DKIM_SELECTOR, value: "{{ .Values.postfixDkimSelector }}" }
- { name: POSTFIX_smtpd_milters, value: "inet:localhost:8891" }
- { name: POSTFIX_non_smtpd_milters, value: "$smtpd_milters" }
- { name: POSTFIX_milter_default_action, value: "accept" }
- { name: POSTFIX_milter_protocol, value: "6" }
# Other
- { name: ALLOW_EMPTY_SENDER_DOMAINS, value: "true" }
- { name: ALLOWED_SENDER_DOMAINS, value: "" }
volumeMounts:
- { name: "{{ .Values.postfix }}-tls-volume", mountPath: /etc/ssl/mail, readOnly: true }
- { name: "{{ .Values.postfix }}-sasl-volume", mountPath: /etc/sasl2 }
- { name: "{{ .Values.postfix }}-config-volume", mountPath: /config }
- { name: "{{ .Values.postfix }}-dkim-volume", mountPath: /etc/opendkim }
- { name: "{{ .Values.postfix }}-dkim-volume", mountPath: /etc/opendkim/keys, subPath: keys }
- { name: "{{ .Values.postfix }}-queue-volume", mountPath: /var/spool/postfix }
volumes:
- name: "{{ .Values.postfix }}-tls-volume"
secret: { secretName: "{{ .Values.postfix }}-tls" }
- name: "{{ .Values.postfix }}-sasl-volume"
configMap: { name: "{{ .Values.postfix }}-sasl" }
- name: "{{ .Values.postfix }}-config-volume"
persistentVolumeClaim: { claimName: "{{ .Values.postfix }}-config-pvc" }
- name: "{{ .Values.postfix }}-dkim-volume"
persistentVolumeClaim: { claimName: "{{ .Values.postfix }}-dkim-pvc" }
- name: "{{ .Values.postfix }}-queue-volume"
persistentVolumeClaim: { claimName: "{{ .Values.postfix }}-queue-pvc" }
---
apiVersion: v1
kind: Service
metadata: { name: "{{ .Values.postfix }}-public", namespace: "{{ .Values.namespace }}" }
spec:
type: LoadBalancer
externalTrafficPolicy: Local
selector: { app: "{{ .Values.postfix }}" }
ports: [ { name: smtp, port: 25, targetPort: 25 } ]
---
apiVersion: v1
kind: Service
metadata: { name: "{{ .Values.postfix }}", namespace: "{{ .Values.namespace }}" }
spec:
selector: { app: "{{ .Values.postfix }}" }
ports: [ { name: submission, port: 587, targetPort: 587 } ]
{{- end }}
@@ -0,0 +1,416 @@
{{- if .Values.redisHelm }}
---
apiVersion: v1
kind: ConfigMap
metadata: { name: "{{ .Values.redis }}-config", namespace: "{{ .Values.namespace }}" }
data:
redis.conf: |
bind 0.0.0.0
port 6379
dir /data
# --- ACL ---
aclfile /data-acl/{{ .Values.redisFileUsersAcl }}
# --- all in one DB ---
databases 1
# --- network ---
protected-mode yes
tcp-backlog 1024
tcp-keepalive 300
timeout 0
# --- connections ---
maxclients {{ .Values.profiles.redis.maxClients }}
# --- memory (tune) ---
maxmemory {{ .Values.profiles.redis.maxmemory }}
maxmemory-policy allkeys-lfu
maxmemory-samples 5
maxmemory-eviction-tenacity 10
maxmemory-clients 5%
client-query-buffer-limit 256mb
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
# --- replication ---
replica-serve-stale-data yes
replica-read-only yes
repl-backlog-size 64mb
repl-backlog-ttl 3600
min-replicas-to-write 0
#min-replicas-max-lag 10
# --- persistence ---
appendonly yes
appendfsync everysec
aof-rewrite-incremental-fsync yes
rdb-save-incremental-fsync yes
save ""
# --- log ---
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
---
apiVersion: v1
kind: PersistentVolume
metadata: { name: "{{ .Values.redis }}-users-pv" }
spec:
capacity: { storage: 1Gi }
volumeMode: Filesystem
accessModes: [ ReadWriteMany ]
persistentVolumeReclaimPolicy: Retain
hostPath: { path: "{{ .Values.redisPath }}", type: DirectoryOrCreate }
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: "{{ .Values.redis }}-users-pvc", namespace: "{{ .Values.namespace }}" }
spec:
volumeName: "{{ .Values.redis }}-users-pv"
volumeMode: Filesystem
accessModes: [ ReadWriteMany ]
resources: { requests: { storage: 1Gi } }
storageClassName: ""
---
apiVersion: apps/v1
kind: StatefulSet
metadata: { name: "{{ .Values.redis }}", namespace: "{{ .Values.namespace }}" }
spec:
serviceName: "{{ .Values.redis }}"
replicas: 2
selector: { matchLabels: { app: "{{ .Values.redis }}" } }
persistentVolumeClaimRetentionPolicy: { whenDeleted: Delete, whenScaled: Retain }
template:
metadata: { labels: { app: "{{ .Values.redis }}" } }
spec:
terminationGracePeriodSeconds: 30
initContainers:
- name: init-redis-acl
image: redis:8.6-alpine
resources:
requests: { cpu: "10m", memory: "32Mi" }
limits: { cpu: "100m", memory: "128Mi" }
env:
- { name: POD_NAME, valueFrom: { fieldRef: { fieldPath: metadata.name } } }
- { name: REDIS_PASSWORD, valueFrom: { secretKeyRef: { name: "{{ .Values.redis }}-secret", key: root-password } } }
command: ["/bin/sh","-c"]
args:
- |
set -eu
ACL="/data-acl/{{ .Values.redisFileUsersAcl }}"
HASH=$(printf '%s' "$REDIS_PASSWORD" | sha256sum | awk '{print $1}')
mkdir -p /data-acl
if [ "$POD_NAME" = "{{ .Values.redis }}-0" ]; then
tmp="${ACL}.tmp"
if [ -f "$ACL" ]; then
awk '!(tolower($1)=="user" && $2=="default")' "$ACL" > "$tmp"
else
: > "$tmp"
fi
# default user is used by replication auth and HAProxy health checks
printf 'user default on sanitize-payload #%s ~* &* +@all\n' "$HASH" >> "$tmp"
mv "$tmp" "$ACL"
chmod 600 "$ACL"
else
i=0
while [ ! -f "$ACL" ] && [ $i -lt 300 ]; do
sleep 1
i=$((i+1))
done
[ -f "$ACL" ] || exit 1
fi
volumeMounts:
- { name: "{{ .Values.redis }}-users-volume", mountPath: /data-acl }
containers:
- name: "{{ .Values.redis }}"
image: redis:8.6-alpine
resources:
requests: { cpu: "{{ .Values.profiles.redis.cpuRequest }}", memory: "{{ .Values.profiles.redis.memoryRequest }}" }
limits: { cpu: "{{ .Values.profiles.redis.cpuLimit }}", memory: "{{ .Values.profiles.redis.memoryLimit }}" }
ports:
- { name: redis, containerPort: 6379 }
env:
- { name: POD_NAME, valueFrom: { fieldRef: { fieldPath: metadata.name } } }
- { name: POD_IP, valueFrom: { fieldRef: { fieldPath: status.podIP } } }
- { name: REDIS_PASSWORD, valueFrom: { secretKeyRef: { name: "{{ .Values.redis }}-secret", key: root-password } } }
- { name: SENTINEL_HOST, value: "{{ .Values.redisSentinel }}" }
- { name: SENTINEL_PORT, value: "26379" }
- { name: MASTER_NAME, value: "mymaster" }
command: ["/bin/sh","-c"]
args:
- |
set -eu
POD_DNS_SHORT="${POD_NAME}.{{ .Values.redis }}"
POD_DNS_FQDN="${POD_NAME}.{{ .Values.redis }}.{{ .Values.namespace }}.svc.cluster.local"
get_master() {
REDISCLI_AUTH="$REDIS_PASSWORD" \
redis-cli -h "$SENTINEL_HOST" -p "$SENTINEL_PORT" \
SENTINEL get-master-addr-by-name "$MASTER_NAME" 2>/dev/null | tr -d '\r'
}
out=""
i=0
while [ $i -lt 20 ]; do
out="$(get_master || true)"
[ -n "$out" ] && break
i=$((i+1))
sleep 1
done
master_host="$(printf '%s\n' "$out" | sed -n '1p')"
master_port="$(printf '%s\n' "$out" | sed -n '2p')"
[ -n "$master_port" ] || master_port="6379"
is_me_master() {
[ "$master_host" = "$POD_IP" ] || [ "$master_host" = "$POD_DNS_SHORT" ] || [ "$master_host" = "$POD_DNS_FQDN" ]
}
if [ -n "$master_host" ]; then
if is_me_master; then
exec redis-server /etc/redis/redis.conf --masteruser default --masterauth "$REDIS_PASSWORD"
else
exec redis-server /etc/redis/redis.conf --replicaof "$master_host" "$master_port" --masteruser default --masterauth "$REDIS_PASSWORD"
fi
else
# bootstrap if sentinel is not ready yet
if [ "$POD_NAME" = "{{ .Values.redis }}-0" ]; then
exec redis-server /etc/redis/redis.conf
else
exec redis-server /etc/redis/redis.conf --replicaof {{ .Values.redis }}-0.{{ .Values.redis }} 6379 --masteruser default --masterauth "$REDIS_PASSWORD"
fi
fi
volumeMounts:
- { name: "{{ .Values.redis }}-data-volume", mountPath: /data }
- { name: "{{ .Values.redis }}-config-volume", mountPath: /etc/redis }
- { name: "{{ .Values.redis }}-users-volume", mountPath: /data-acl }
volumes:
- name: "{{ .Values.redis }}-config-volume"
configMap: { name: "{{ .Values.redis }}-config" }
- name: "{{ .Values.redis }}-users-volume"
persistentVolumeClaim: { claimName: "{{ .Values.redis }}-users-pvc" }
volumeClaimTemplates:
- metadata: { name: "{{ .Values.redis }}-data-volume" }
spec:
accessModes: [ ReadWriteOnce ]
resources: { requests: { storage: 10Gi } }
---
apiVersion: v1
kind: Service
metadata: { name: "{{ .Values.redis }}", namespace: "{{ .Values.namespace }}" }
spec:
clusterIP: None
selector: { app: "{{ .Values.redis }}" }
ports:
- { name: redis, protocol: TCP, port: 6379, targetPort: 6379 }
# -------------------------
# Sentinel (1) with PVC
# -------------------------
---
apiVersion: v1
kind: ConfigMap
metadata: { name: "{{ .Values.redisSentinel }}-config", namespace: "{{ .Values.namespace }}" }
data:
sentinel.conf.tmpl: |
bind 0.0.0.0
port 26379
dir /data
sentinel deny-scripts-reconfig yes
sentinel resolve-hostnames yes
sentinel announce-hostnames yes
# quorum=1 (single sentinel)
sentinel monitor mymaster {{ .Values.redis }}-0.{{ .Values.redis }} 6379 1
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
sentinel auth-user mymaster default
sentinel auth-pass mymaster __PASSWORD__
requirepass __PASSWORD__
---
apiVersion: apps/v1
kind: StatefulSet
metadata: { name: "{{ .Values.redisSentinel }}", namespace: "{{ .Values.namespace }}" }
spec:
replicas: 1
serviceName: "{{ .Values.redisSentinel }}"
selector: { matchLabels: { app: "{{ .Values.redisSentinel }}" } }
persistentVolumeClaimRetentionPolicy: { whenDeleted: Delete, whenScaled: Retain }
template:
metadata: { labels: { app: "{{ .Values.redisSentinel }}" } }
spec:
containers:
- name: "{{ .Values.redisSentinel }}"
image: redis:8.6-alpine
resources:
requests: { cpu: "50m", memory: "128Mi" }
limits: { cpu: "200m", memory: "256Mi" }
ports:
- { name: sentinel, containerPort: 26379 }
env:
- { name: REDIS_PASSWORD, valueFrom: { secretKeyRef: { name: "{{ .Values.redis }}-secret", key: root-password } } }
command: ["/bin/sh","-c"]
args:
- |
set -eu
CONF=/data/sentinel.conf
if [ ! -f "$CONF" ]; then
pwd_escaped=$(printf '%s' "$REDIS_PASSWORD" | sed -e 's/[\/&]/\\&/g')
sed "s/__PASSWORD__/${pwd_escaped}/g" /tmpl/sentinel.conf.tmpl > "$CONF"
chmod 600 "$CONF"
fi
exec redis-server "$CONF" --sentinel
volumeMounts:
- { name: "{{ .Values.redisSentinel }}-tmpl", mountPath: /tmpl }
- { name: "{{ .Values.redisSentinel }}-data", mountPath: /data }
volumes:
- name: "{{ .Values.redisSentinel }}-tmpl"
configMap: { name: "{{ .Values.redisSentinel }}-config" }
volumeClaimTemplates:
- metadata: { name: "{{ .Values.redisSentinel }}-data" }
spec:
accessModes: [ ReadWriteOnce ]
resources: { requests: { storage: 1Gi } }
---
apiVersion: v1
kind: Service
metadata: { name: "{{ .Values.redisSentinel }}", namespace: "{{ .Values.namespace }}" }
spec:
selector: { app: "{{ .Values.redisSentinel }}" }
ports:
- { name: sentinel, port: 26379, targetPort: 26379 }
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: "{{ .Values.redisSentinel }}-allow-only-checker", namespace: "{{ .Values.namespace }}" }
spec:
podSelector: { matchLabels: { app: "{{ .Values.redisSentinel }}" } }
policyTypes:
- Ingress
ingress:
- from:
- podSelector: { matchLabels: { app: "{{ .Values.redis }}" } }
- podSelector: { matchLabels: { app: "{{ .Values.redisSentinel }}" } }
ports:
- { protocol: TCP, port: 26379 }
# -------------------------
# HAProxy: single master endpoint
# -------------------------
---
apiVersion: v1
kind: ConfigMap
metadata: { name: "{{ .Values.redis }}-haproxy-config", namespace: "{{ .Values.namespace }}" }
data:
haproxy.cfg: |
global
log stdout format raw local0
maxconn 20000
resolvers kubedns
nameserver dns1 10.43.0.10:53
accepted_payload_size 8192
resolve_retries 3
timeout resolve 1s
timeout retry 1s
hold valid 10s
hold obsolete 30s
defaults
mode tcp
log global
option tcplog
option log-health-checks
timeout connect 5s
timeout client 1m
timeout server 1m
timeout check 1s
frontend fe_redis_master
bind *:6379
default_backend be_redis_master
backend be_redis_master
mode tcp
balance first
option tcp-check
option srvtcpka
timeout queue 2s
timeout connect 2s
timeout check 3s
timeout server 10m
tcp-check connect
tcp-check send-lf "AUTH default $REDIS_PASSWORD\r\n"
tcp-check expect string +OK
tcp-check send INFO\ replication\r\n
tcp-check expect string role:master
tcp-check send QUIT\r\n
tcp-check expect string +OK
server redis0 {{ .Values.redis }}-0.{{ .Values.redis }}.{{ .Values.namespace }}.svc.cluster.local:6379 check resolvers kubedns resolve-prefer ipv4 init-addr libc,none inter 5s fall 2 rise 2
server redis1 {{ .Values.redis }}-1.{{ .Values.redis }}.{{ .Values.namespace }}.svc.cluster.local:6379 check resolvers kubedns resolve-prefer ipv4 init-addr libc,none inter 5s fall 2 rise 2
---
apiVersion: apps/v1
kind: Deployment
metadata: { name: "{{ .Values.redis }}-haproxy", namespace: "{{ .Values.namespace }}" }
spec:
replicas: 1
selector: { matchLabels: { app: "{{ .Values.redis }}-haproxy" } }
template:
metadata:
labels: { app: "{{ .Values.redis }}-haproxy" }
spec:
containers:
- name: "{{ .Values.redis }}-haproxy"
image: haproxy:2.9-alpine
resources:
requests: { cpu: "50m", memory: "64Mi" }
limits: { cpu: "500m", memory: "256Mi" }
ports:
- { name: redis, containerPort: 6379 }
env:
- { name: REDIS_PASSWORD, valueFrom: { secretKeyRef: { name: "{{ .Values.redis }}-secret", key: root-password } } }
command: ["/bin/sh","-c"]
args:
- |
set -eu
haproxy -c -f /usr/local/etc/haproxy/haproxy.cfg
exec haproxy -f /usr/local/etc/haproxy/haproxy.cfg -db
readinessProbe: { tcpSocket: { port: 6379 }, initialDelaySeconds: 1, periodSeconds: 2, failureThreshold: 3 }
livenessProbe: { tcpSocket: { port: 6379 }, initialDelaySeconds: 10, periodSeconds: 10, failureThreshold: 3 }
volumeMounts:
# - { name: cfg, mountPath: /usr/local/etc/haproxy/haproxy.cfg, subPath: haproxy.cfg }
- { name: cfg, mountPath: /usr/local/etc/haproxy }
volumes:
- name: cfg
configMap: { name: "{{ .Values.redis }}-haproxy-config" }
---
apiVersion: v1
kind: Service
metadata: { name: "{{ .Values.redis }}-master", namespace: "{{ .Values.namespace }}" }
spec:
selector: { app: "{{ .Values.redis }}-haproxy" }
ports:
- { name: redis, port: 6379, targetPort: 6379 }
{{- end }}
@@ -0,0 +1,52 @@
{{- if .Values.workerHelm }}
---
apiVersion: apps/v1
kind: Deployment
metadata: { name: "{{ .Values.worker }}", namespace: "{{ .Values.namespace }}" }
spec:
replicas: 1
selector: { matchLabels: { app: "{{ .Values.worker }}" } }
template:
metadata: { labels: { app: "{{ .Values.worker }}" } }
spec:
containers:
- name: "{{ .Values.worker }}"
image: python:3.12-slim
imagePullPolicy: IfNotPresent
resources:
requests: { cpu: "50m", memory: "64Mi" }
limits: { cpu: "200m", memory: "256Mi" }
ports:
- { containerPort: 8080 }
workingDir: /app/agent
command: ["/bin/sh","-c"]
args:
- |
set -e
if [ ! -f /app/agent/worker.py ]; then
echo "ERROR: /app/agent/worker.py not found" >&2
ls -la /app/agent >&2 || true
exit 1
fi
exec python -u /app/agent/worker.py
env:
- { name: WORKER_UUID, value: "{{ .Values.workerUuid }}" }
- { name: WORKER_NAME, value: "{{ .Values.workerName }}" }
- { name: WORKER_POOL, value: "{{ .Values.workerPool }}" }
- { name: API_URL, value: "{{ .Values.workerApiUrl }}" }
- { name: GETTING_PAUSE, value: "{{ .Values.workerApiGettingPause }}" }
- { name: SENDING_PAUSE, value: "{{ .Values.workerApiSendingPause }}" }
volumeMounts:
- { name: "{{ .Values.worker }}-agent-volume", mountPath: /app/agent }
- { name: "{{ .Values.worker }}-tasks-volume", mountPath: /app/tasks }
readinessProbe: { httpGet: { path: /healthz, port: 8080 }, periodSeconds: 5, timeoutSeconds: 2 }
livenessProbe: { httpGet: { path: /healthz, port: 8080 }, periodSeconds: 10, timeoutSeconds: 2, failureThreshold: 3 }
startupProbe: { httpGet: { path: /healthz, port: 8080 }, periodSeconds: 2, timeoutSeconds: 2, failureThreshold: 30 }
volumes:
- name: "{{ .Values.worker }}-agent-volume"
hostPath: { path: "{{ .Values.workerAgentPath }}", type: DirectoryOrCreate }
- name: "{{ .Values.worker }}-tasks-volume"
hostPath: { path: "{{ .Values.workerTasksPath }}", type: DirectoryOrCreate }
{{- end }}
+42
View File
@@ -0,0 +1,42 @@
global:
scrape_interval: 15s
scrape_timeout: 10s
scrape_configs:
- job_name: "metric-node-exporter"
kubernetes_sd_configs:
- role: pod
relabel_configs:
- action: keep
source_labels: [__meta_kubernetes_pod_label_app]
regex: metric-node-exporter
- action: keep
source_labels: [__meta_kubernetes_pod_container_port_number]
regex: "9100"
- action: replace
source_labels: [__meta_kubernetes_pod_node_name]
target_label: node
- job_name: "metric-kubelet-cadvisor"
scheme: https
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
tls_config:
insecure_skip_verify: true
kubernetes_sd_configs:
- role: node
relabel_configs:
- target_label: __address__
replacement: kubernetes.default.svc:443
- source_labels: [__meta_kubernetes_node_name]
target_label: __metrics_path__
replacement: /api/v1/nodes/$1/proxy/metrics/cadvisor
- source_labels: [__meta_kubernetes_node_name]
target_label: node
metric_relabel_configs:
- source_labels: [__name__]
action: keep
regex: 'container_memory_working_set_bytes|container_memory_usage_bytes|container_memory_rss|container_memory_cache|container_cpu_usage_seconds_total|container_cpu_system_seconds_total|container_cpu_user_seconds_total'
+15
View File
@@ -0,0 +1,15 @@
From: "Monitoring" <postmaster@mta.krumax.cz>
To: Maksym <maksym.krugol@wedos.org>
Subject: Test message (RFC822 raw)
Date: Thu, 05 Feb 2026 10:15:00 +0100
Message-ID: <test-20260205-101500.1@mta.krumax.cz>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Hello!
This is a raw RFC822 message file sent via sendmail -t.
Regards,
Monitoring
@@ -0,0 +1,52 @@
expose_php = Off
allow_url_fopen = Off
allow_url_include = Off
enable_dl = Off
short_open_tag = Off
; --- block user_ini ---
user_ini.filename =
; -- disable functions ---
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,putenv,dl,show_source,highlight_file,symlink,readlink,link,proc_terminate,proc_get_status,pfsockopen,posix_kill,posix_setuid,posix_setgid,posix_setsid,posix_setpgid,posix_getgrnam,posix_getpgid,posix_getpwuid,posix_getpwnam,posix_getrlimit,posix_initgroups,posix_mkfifo,posix_mknod,posix_uname,register_tick_function,openlog,syslog,proc_close,proc_nice,diskfreespace,disk_free_space,disk_total_space,leak,pcntl_alarm,pcntl_async_signals,pcntl_exec,pcntl_fork,pcntl_get_last_error,pcntl_getcpuaffinity,pcntl_getpriority,pcntl_rfork,pcntl_setcpuaffinity,pcntl_setpriority,pcntl_signal,pcntl_signal_dispatch,pcntl_signal_get_handler,pcntl_sigprocmask,pcntl_sigtimedwait,pcntl_sigwaitinfo,pcntl_strerror,pcntl_unshare,pcntl_wait,pcntl_waitid,pcntl_waitpid,pcntl_wexitstatus,pcntl_wifexited,pcntl_wifsignaled,pcntl_wifstopped,pcntl_wstopsig,pcntl_wtermsig,sys_getloadavg
; not use for LSAPI
;pm.max_children = {{children}}
; --- memory limit ---
max_memory_limit = {{max_memory_limit}}
; --- default (redefined per vhost) ---
memory_limit = {{memory_limit}}
max_execution_time = {{max_execution_time}}
max_input_time = 30
max_input_vars = 1000
output_buffering = 4096
; --- uploads ---
post_max_size = {{post_max_size}}
upload_max_filesize = {{upload_max_filesize}}
max_file_uploads = 10
; --- log --- (to stderr k3s?)
display_errors = Off
log_errors = On
;error_log = /usr/local/lsws/conf/logs/php_errors.log
error_log = /proc/self/fd/2
; --- sessions (redefined per vhost) ---
session.save_path = "/tmp"
session.use_only_cookies = 1
session.cookie_httponly = 1
session.cookie_secure = 1
session.cookie_samesite = "Lax"
; --- OPcache ---
opcache.enable = 1
opcache.enable_cli = 0
opcache.memory_consumption = 256
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 100000
opcache.validate_timestamps = 1
opcache.revalidate_freq = 2
opcache.jit = "disable"
@@ -0,0 +1,6 @@
children=16
max_memory_limit=512M
memory_limit=512M
max_execution_time=30
post_max_size=512M
upload_max_filesize=512M
@@ -0,0 +1,6 @@
children=4
max_memory_limit=512M
memory_limit=512M
max_execution_time=30
post_max_size=128M
upload_max_filesize=128M
@@ -0,0 +1,6 @@
children=6
max_memory_limit=512M
memory_limit=512M
max_execution_time=30
post_max_size=128M
upload_max_filesize=128M
@@ -0,0 +1,6 @@
children=8
max_memory_limit=512M
memory_limit=512M
max_execution_time=30
post_max_size=128M
upload_max_filesize=128M
@@ -0,0 +1,8 @@
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Front-controller
RewriteRule ^/?index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
@@ -0,0 +1,11 @@
RewriteEngine On
# no www -> add www + https
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^ https://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
# www, http -> https
RewriteCond %{HTTP:X-Forwarded-Proto} !https [NC]
RewriteCond %{HTTP:Forwarded} !proto=https [NC]
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
@@ -0,0 +1,11 @@
RewriteEngine On
# www -> non-www + https
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]
# http -> https
RewriteCond %{HTTP:X-Forwarded-Proto} !https [NC]
RewriteCond %{HTTP:Forwarded} !proto=https [NC]
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
@@ -0,0 +1,7 @@
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Laravel
RewriteRule . /public/index.php [L]
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
@@ -0,0 +1,13 @@
RewriteEngine On
RewriteCond %{DOCUMENT_ROOT}/.php81 -f
RewriteRule ^ - [H=application/x-httpd-lsphp81]
RewriteCond %{DOCUMENT_ROOT}/.php82 -f
RewriteRule ^ - [H=application/x-httpd-lsphp82]
RewriteCond %{DOCUMENT_ROOT}/.php83 -f
RewriteRule ^ - [H=application/x-httpd-lsphp83]
RewriteCond %{DOCUMENT_ROOT}/.php84 -f
RewriteRule ^ - [H=application/x-httpd-lsphp84]
@@ -0,0 +1,14 @@
RewriteEngine On
# Unigma 1.0.0 fix
RewriteCond %{REQUEST_URI} ^/v(8[1-5])/ [OR]
RewriteCond %{REQUEST_URI} ^/v(8[1-5])/router\.lua [OR]
RewriteCond %{REQUEST_URI} ^/router\.lua
RewriteRule ^ - [L]
RewriteCond %{REQUEST_URI} !^/router\.lua
RewriteCond %{REQUEST_URI} !^/[^/]+/www/
RewriteCond %{HTTP_HOST} ^([a-z0-9.-]+)$
RewriteRule ^/?(.*)$ /%1/www/$1
RewriteRule ^/?(.*\.php)$ /router.lua?orig=/$1 [L]
@@ -0,0 +1,46 @@
allowSymbolLink 1
enableScript 1
restrained 1
setUIDMode 2
vhRoot /var/www/vhosts/$VH_NAME
virtualHostConfig {
docRoot $VH_ROOT/www/
vhDomain $VH_NAME
vhAliases *.$VH_NAME
index {
useServer 0
indexFiles index.php
}
phpIniOverride {
# --- paths ---
php_admin_value upload_tmp_dir $VH_ROOT/tmp
php_admin_value session.save_path $VH_ROOT/session
php_admin_value open_basedir "$VH_ROOT/www:$VH_ROOT/tmp:$VH_ROOT/session:/var/www/private/$VH_NAME:/var/www/public"
php_admin_value auto_prepend_file /var/www/private/$VH_NAME/bootstrap.php
# --- hard limits ---
php_admin_value memory_limit 512M
php_admin_value max_execution_time 30
php_admin_value max_input_time 30
php_admin_value max_input_vars 1000
php_admin_value post_max_size 128M
php_admin_value upload_max_filesize 128M
}
rewrite {
enable 1
autoLoadHtaccess 1
rules <<<END_rules
rewriteFile /usr/local/lsws/conf/rules/php.rules
rewriteFile /usr/local/lsws/conf/rules/https.rules
rewriteFile /usr/local/lsws/conf/rules/front-controller.rules
END_rules
}
accessControl {
allow 127.0.0.1, ::1, 10.42.0.0/16, 10.43.0.0/16
}
}
@@ -0,0 +1,38 @@
docRoot /var/www/vhosts/{{domain}}/www/
vhDomain {{domain}}
vhAliases *.{{domain}}
index {
useServer 0
indexFiles index.php
}
phpIniOverride {
# --- paths ---
php_admin_value upload_tmp_dir /var/www/vhosts/{{domain}}/tmp
php_admin_value session.save_path /var/www/vhosts/{{domain}}/session
php_admin_value open_basedir "/var/www/vhosts/{{domain}}/www:/var/www/vhosts/{{domain}}/tmp:/var/www/vhosts/{{domain}}/session:/var/www/data/{{domain}}:/var/www/shared"
php_admin_value auto_prepend_file /var/www/data/{{domain}}/bootstrap.php
# --- hard limits ---
php_admin_value memory_limit {{memory_limit}}
php_admin_value max_execution_time {{max_execution_time}}
php_admin_value max_input_time 30
php_admin_value max_input_vars 1000
php_admin_value post_max_size {{post_max_size}}
php_admin_value upload_max_filesize {{upload_max_filesize}}
}
rewrite {
enable 1
autoLoadHtaccess 1
rules <<<END_rules
rewriteFile /usr/local/lsws/conf/rules/php.rules
rewriteFile /usr/local/lsws/conf/rules/https.rules
rewriteFile /usr/local/lsws/conf/rules/front-controller.rules
END_rules
}
accessControl {
allow 127.0.0.1, ::1, 10.42.0.0/16, 10.43.0.0/16
}
@@ -0,0 +1,38 @@
docRoot $VH_ROOT/www/
vhDomain $VH_NAME
vhAliases *.$VH_NAME
index {
useServer 0
indexFiles index.php
}
phpIniOverride {
# --- paths ---
php_admin_value upload_tmp_dir $VH_ROOT/tmp
php_admin_value session.save_path $VH_ROOT/session
php_admin_value open_basedir "$VH_ROOT/www:$VH_ROOT/tmp:$VH_ROOT/session:/var/www/private/$VH_NAME:/var/www/public"
php_admin_value auto_prepend_file /var/www/private/$VH_NAME/bootstrap.php
# --- hard limits ---
php_admin_value memory_limit {{memory_limit}}
php_admin_value max_execution_time {{max_execution_time}}
php_admin_value max_input_time 30
php_admin_value max_input_vars 1000
php_admin_value post_max_size {{post_max_size}}
php_admin_value upload_max_filesize {{upload_max_filesize}}
}
rewrite {
enable 1
autoLoadHtaccess 1
rules <<<END_rules
rewriteFile /usr/local/lsws/conf/rules/php.rules
rewriteFile /usr/local/lsws/conf/rules/https.rules
rewriteFile /usr/local/lsws/conf/rules/front-controller.rules
END_rules
}
accessControl {
allow 127.0.0.1, ::1, 10.42.0.0/16, 10.43.0.0/16
}
@@ -0,0 +1,23 @@
# OLS
aliasLimit=0
phpChildren=16
maxExecutionTime=30
maxMemoryLimit=512M
memoryLimit=512M
postMaxSize=512M
uploadMaxFilesize=512M
# Filesystem
blockSoftLimit=0
blockHardLimit=0
inodeSoftLimit=0
inodeHardLimit=0
# Database
databaseSoftLimit=0
# Backup 1/7
backupPeriod=7
# Mail 50/500
mailDayLimit=0
@@ -0,0 +1,23 @@
# OLS
aliasLimit=0
phpChildren=16
maxExecutionTime=30
maxMemoryLimit=512M
memoryLimit=512M
postMaxSize=512M
uploadMaxFilesize=512M
# Filesystem
blockSoftLimit=0
blockHardLimit=0
inodeSoftLimit=0
inodeHardLimit=0
# Database
databaseSoftLimit=0
# Backup 1/7
backupPeriod=7
# Mail 50/500
mailDayLimit=0
@@ -0,0 +1,23 @@
# OLS
aliasLimit=0
phpChildren=16
maxExecutionTime=30
maxMemoryLimit=512M
memoryLimit=512M
postMaxSize=512M
uploadMaxFilesize=512M
# Filesystem
blockSoftLimit=0
blockHardLimit=0
inodeSoftLimit=0
inodeHardLimit=0
# Database
databaseSoftLimit=0
# Backup 1/7
backupPeriod=7
# Mail 50/500
mailDayLimit=0
@@ -0,0 +1,19 @@
[sshd]
enabled = true
backend = systemd
maxretry = 4
findtime = 600
bantime = 3600
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 604800
[recidive]
enabled = true
backend = systemd
filter = recidive
logpath = /var/log/fail2ban.log
banaction = %(banaction_allports)s
bantime = 604800
findtime = 86400
maxretry = 3
@@ -0,0 +1,20 @@
# --- KUBE SFTP GLOBAL BEGIN ---
PermitRootLogin no
PermitUserEnvironment no
LoginGraceTime 30
MaxAuthTries 3
MaxSessions 5
MaxStartups 200:30:500
Compression no
DebianBanner no
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes128-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256
PubkeyAcceptedAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256
# --- KUBE SFTP GLOBAL END ---
@@ -0,0 +1,22 @@
# --- KUBE SFTP GROUP BEGIN ---
Match Group {{sftp_access_group}}
ChrootDirectory %h
ForceCommand internal-sftp -d /www -u 027
PasswordAuthentication yes
KbdInteractiveAuthentication no
PubkeyAuthentication yes
PermitTTY no
PermitTunnel no
AllowTcpForwarding no
AllowAgentForwarding no
AllowStreamLocalForwarding no
X11Forwarding no
ClientAliveInterval 300
ClientAliveCountMax 2
# --- KUBE SFTP GROUP END ---
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,4 @@
<?php
echo '<pre>pid: ' . getmypid() . '</pre>';
echo phpinfo();
Binary file not shown.
@@ -0,0 +1,8 @@
<?php
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
echo "time: <b>" . time() . "</b> | hostname: <b>" . gethostname() . "</b> | pid: <b>" . getmypid() . "</b><br>";
phpinfo();
+136
View File
@@ -0,0 +1,136 @@
<?php
require __DIR__ . '/wp-load.php';
define('MAIL_TEST_KEY', 'dev');
$smtpLog = '';
add_action('phpmailer_init', function ($phpmailer) use (&$smtpLog) {
$phpmailer->SMTPDebug = 2;
$phpmailer->Debugoutput = function ($str, $level) use (&$smtpLog) {
$smtpLog .= date('Y-m-d H:i:s') . ' ' . htmlentities(preg_replace('/[\r\n]+/', '', $str), ENT_QUOTES, 'UTF-8') . "<br>\n";
};
});
$success = '';
$error = '';
$err = null;
add_action('wp_mail_failed', function($e) use (&$err) {
if (is_wp_error($e)) {
$err = $e->get_error_message();
} else {
$err = 'Unknown wp_mail error';
}
});
$domain = wp_parse_url(home_url(), PHP_URL_HOST);
$to = "maksym.krugol@wedos.org";
$headers = [
"List-Unsubscribe: <mailto:unsubscribe@{$domain}?subject=unsubscribe>, <https://{$domain}/unsubscribe/{$to}>"
];
$subject = "Service status update and new API keys - " . (new DateTime())->format('d.m.Y');
$message = '';
$message .= "Service: host-" . bin2hex(random_bytes(2)) . PHP_EOL;
$message .= "Status: active" . PHP_EOL;
$message .= "Service tag: " . bin2hex(random_bytes(6)) . PHP_EOL . PHP_EOL;
for ($i = 1; $i < 9; $i++) {
$message .= "API key" . $i . ": " . bin2hex(random_bytes(40)) . PHP_EOL;
}
$message .= PHP_EOL . "Thank you for your understanding." . PHP_EOL . PHP_EOL . "Support team US1" . PHP_EOL . (new DateTime())->format('d.m.Y H:i');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$to = isset($_POST['to']) ? trim($_POST['to']) : $to;
$subject = isset($_POST['subject']) ? trim($_POST['subject']) : $subject;
$message = isset($_POST['message']) ? trim($_POST['message']) : $message;
$key = isset($_POST['key']) ? trim($_POST['key']) : '';
if (!hash_equals(MAIL_TEST_KEY, $key)) {
$error = 'Incorrect key.';
} elseif ($to === '' || !is_email($to)) {
$error = 'Incorrect recipient address.';
} elseif ($subject === '') {
$error = 'Incorrect subject.';
} else {
$sent = wp_mail($to, $subject, $message, $headers);
if ($sent) {
$success = "Success";
} else {
$error = "Failed | " . ($err ? htmlspecialchars($err, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') : 'See PHP/WP error_log for details.');
}
}
}
?>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Test send mail</title>
<style>
body {
margin: 16px;
padding: 0;
background-color: #1e1f22;
color: #bcbec4;
font-family: Consolas, "DejaVu Sans Mono", "Liberation Mono", Menlo, Monaco, "Courier New", monospace;
font-size: 14px;
}
input, textarea {
padding: 0 8px;
width: 282px;
line-height: 24px;
background-color: transparent;
color: #bcbec4;
border: 1px solid #393b40;
border-radius: 4px;
}
button {
padding: 8px 12px;
border: 1px solid #ccc;
border-radius: 4px;
background: #f5f5f5;
cursor: pointer;
}
hr {
border: none;
height: 1px;
background-color: #393b40;
}
</style>
</head>
<body>
<h1>Test send mail <?php echo uniqid() ?></h1>
<form method="post">
<p>
<label>
Recipient:<br>
<input type="email" name="to" required style="width: 400px;" value="<?php echo $to; ?>">
</label>
</p>
<p>
<label>
Subject:<br>
<input type="text" name="subject" required style="width: 800px;" value="<?php echo $subject; ?>">
</label>
</p>
<p>
<label>
Message:<br>
<textarea name="message" rows="10" style="width: 800px;"><?php echo esc_textarea($message); ?></textarea>
</label>
</p>
<p>
<label>
Key:<br>
<input type="password" name="key" required style="width: 100px;">
</label>
<button type="submit">Send</button>
</p>
</form>
<?php echo $domain ?>
<?php if ($success): ?><p style="color: green;"><?php echo esc_html($success); ?></p><?php endif; ?>
<?php if ($error): ?><p style="color: red;"><?php echo esc_html($error); ?></p><?php endif; ?>
<?php if (!empty($smtpLog)): echo '<hr><h2>SMTP debug log</h2><div>' . $smtpLog . '</div>'; endif; ?>
</body>
</html>
@@ -0,0 +1,8 @@
<?php
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
echo "hostname: " . gethostname() . "\npid: " . getmypid() . "\nopcache_get_status: ";
print_r(opcache_get_status(false));
+646
View File
@@ -0,0 +1,646 @@
<?php
declare(strict_types=1);
header('Content-Type: text/plain; charset=utf-8');
$SCORE = ['PASS' => 0, 'WARN' => 0, 'FAIL' => 0];
$FINDINGS = [];
function add_result(string $level, string $title, string $detail = ''): void {
global $SCORE, $FINDINGS;
if (!isset($SCORE[$level])) {
$level = 'WARN';
}
$SCORE[$level]++;
$FINDINGS[] = [$level, $title, $detail];
}
function line(string $label, $value = null): void {
if ($value === null) {
echo $label . PHP_EOL;
return;
}
if (is_bool($value)) {
$value = $value ? 'YES' : 'NO';
} elseif (is_array($value) || is_object($value)) {
$value = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
echo str_pad($label, 46) . ': ' . $value . PHP_EOL;
}
function section(string $title): void {
echo PHP_EOL . "--- {$title} ---" . PHP_EOL;
}
function bytes_from_ini(?string $val): ?int {
if ($val === null || $val === '') return null;
$val = trim($val);
if ($val === '-1') return -1;
$last = strtolower(substr($val, -1));
$num = (float)$val;
return match($last) {
'g' => (int)($num * 1024 * 1024 * 1024),
'm' => (int)($num * 1024 * 1024),
'k' => (int)($num * 1024),
default => (int)$num,
};
}
function with_error_capture(callable $fn): array {
$error = null;
set_error_handler(function($severity, $message) use (&$error) {
$error = $message;
return true;
});
try {
$result = $fn();
restore_error_handler();
return ['result' => $result, 'error' => $error];
} catch (Throwable $e) {
restore_error_handler();
return ['result' => null, 'error' => $e->getMessage()];
}
}
function try_read_file(string $path): array {
return with_error_capture(function() use ($path) {
$data = @file_get_contents($path);
if ($data === false) return false;
return 'len=' . strlen($data);
}) + ['path' => $path];
}
function try_scandir_path(string $path): array {
return with_error_capture(function() use ($path) {
$data = @scandir($path);
if ($data === false) return false;
return array_slice($data, 0, 15);
}) + ['path' => $path];
}
function try_socket(string $target, int $port, float $timeout = 1.2): array {
$errno = 0;
$errstr = '';
$fp = @fsockopen($target, $port, $errno, $errstr, $timeout);
$ok = is_resource($fp);
if ($ok) fclose($fp);
return [
'target' => $target,
'port' => $port,
'connected' => $ok,
'errno' => $errno,
'errstr' => $errstr,
];
}
function try_unix_socket(string $path, float $timeout = 1.0): array {
$errno = 0;
$errstr = '';
$fp = @stream_socket_client('unix://' . $path, $errno, $errstr, $timeout);
$ok = is_resource($fp);
if ($ok) fclose($fp);
return [
'path' => $path,
'connected' => $ok,
'errno' => $errno,
'errstr' => $errstr,
];
}
echo "=== SHARED HOSTING SAFE AUDIT v2.1 ===" . PHP_EOL;
echo "Time: " . date('c') . PHP_EOL;
$docRoot = realpath($_SERVER['DOCUMENT_ROOT'] ?? getcwd()) ?: getcwd();
$scriptFile = $_SERVER['SCRIPT_FILENAME'] ?? __FILE__;
$baseTmp = $docRoot . '/.audit_tmp_' . getmypid() . '_' . mt_rand(1000, 9999);
@mkdir($baseTmp, 0700, true);
section('Runtime identity');
line('PHP version', PHP_VERSION);
line('SAPI', PHP_SAPI);
line('OS', PHP_OS_FAMILY);
line('Document root', $docRoot);
line('Script filename', $scriptFile);
line('Current dir', getcwd());
line('Loaded php.ini', php_ini_loaded_file() ?: 'none');
line('Additional .ini files', php_ini_scanned_files() ?: 'none');
line('Hostname', php_uname('n'));
line('Server software', $_SERVER['SERVER_SOFTWARE'] ?? 'n/a');
line('Server addr', $_SERVER['SERVER_ADDR'] ?? 'n/a');
line('Server port', $_SERVER['SERVER_PORT'] ?? 'n/a');
line('Remote addr', $_SERVER['REMOTE_ADDR'] ?? 'n/a');
section('PHP limits and config');
$iniKeys = [
'memory_limit',
'max_execution_time',
'max_input_time',
'max_input_vars',
'post_max_size',
'upload_max_filesize',
'open_basedir',
'disable_functions',
'disable_classes',
'user_ini.filename',
'user_ini.cache_ttl',
'file_uploads',
'allow_url_fopen',
'allow_url_include',
'session.save_path',
'upload_tmp_dir',
'sys_temp_dir',
'display_errors',
'log_errors',
'expose_php',
'mail.add_x_header',
'mysqli.default_socket',
'pdo_mysql.default_socket',
];
foreach ($iniKeys as $k) {
line($k, ini_get($k));
}
section('Dangerous functions present');
$dangerFns = [
'exec','shell_exec','system','passthru','proc_open','popen',
'pcntl_exec','pcntl_fork','putenv','mail','symlink','link',
'stream_socket_client','fsockopen'
];
foreach ($dangerFns as $fn) {
line("function_exists($fn)", function_exists($fn));
}
section('Loaded extensions');
$exts = ['mysqli','pdo_mysql','redis','ftp','curl','openssl','pcntl','posix','sockets','imap','intl'];
foreach ($exts as $ext) {
line("extension_loaded($ext)", extension_loaded($ext));
}
section('Filesystem isolation');
$fsTests = [
$docRoot,
$docRoot . '/../',
$docRoot . '/../../',
'/var/www',
'/var/www/vhosts',
'/etc/passwd',
'/etc/hosts',
'/proc/self/environ',
'/proc/meminfo',
'/tmp',
'/var/tmp',
'/run',
'/run/php',
'/run/mysqld',
'/dev/shm',
'/var/spool/postfix',
];
foreach ($fsTests as $path) {
$isDir = @is_dir($path);
$result = $isDir ? try_scandir_path($path) : try_read_file($path);
line($path, $result);
}
section('Path traversal / realpath checks');
$traversalTests = [
$docRoot . '/../www',
$docRoot . '/../tmp',
$docRoot . '/../session',
$docRoot . '/../../../../etc/passwd',
$docRoot . '/../other-vhost/www',
];
foreach ($traversalTests as $path) {
line("realpath($path)", @realpath($path) ?: 'false');
line("read($path)", try_read_file($path));
}
section('Allowed path write tests');
$writeFile = $baseTmp . '/write-test.txt';
$res = with_error_capture(function() use ($writeFile) {
return file_put_contents($writeFile, "audit\n");
});
line('Write file in docroot tmp', ['path' => $writeFile] + $res);
line('File exists after write', file_exists($writeFile));
line('File readable after write', is_readable($writeFile));
line('File writable after write', is_writable($writeFile));
$renameTo = $baseTmp . '/write-test-renamed.txt';
$res = with_error_capture(function() use ($writeFile, $renameTo) {
return @rename($writeFile, $renameTo);
});
line('Rename inside allowed path', ['from' => $writeFile, 'to' => $renameTo] + $res);
$copyToSession = dirname($docRoot) . '/session/audit-copy.txt';
$res = with_error_capture(function() use ($renameTo, $copyToSession) {
return @copy($renameTo, $copyToSession);
});
line('Copy from docroot to session dir', ['to' => $copyToSession] + $res);
section('Symlink / hardlink inside allowed path');
$src = $baseTmp . '/src.txt';
@file_put_contents($src, 'x');
$symlinkTarget = $baseTmp . '/sym.txt';
$hardlinkTarget = $baseTmp . '/hard.txt';
$symlinkRes = with_error_capture(fn() => @symlink($src, $symlinkTarget));
$hardlinkRes = with_error_capture(fn() => @link($src, $hardlinkTarget));
line('Symlink allowed path', $symlinkRes);
line('Hardlink allowed path', $hardlinkRes);
line('Symlink exists', is_link($symlinkTarget));
line('Hardlink exists', file_exists($hardlinkTarget));
section('Runtime override attempts');
$overrideTests = [
'memory_limit' => '2048M',
'max_execution_time' => '600',
'upload_max_filesize' => '2048M',
'post_max_size' => '2048M',
'open_basedir' => '/',
];
$overrideResults = [];
foreach ($overrideTests as $key => $value) {
$before = ini_get($key);
$ret = @ini_set($key, $value);
$after = ini_get($key);
$overrideResults[$key] = [
'before' => $before,
'return' => $ret,
'after' => $after,
'changed' => ($before !== $after),
];
line("ini_set($key)", $overrideResults[$key]);
}
section('Execution capability tests');
$execResults = [];
if (function_exists('exec')) {
$execResults['exec'] = with_error_capture(function() {
$out = [];
$rc = 0;
@exec('id 2>&1', $out, $rc);
return ['rc' => $rc, 'out' => implode("\n", array_slice($out, 0, 5))];
});
line('exec("id")', $execResults['exec']);
}
if (function_exists('shell_exec')) {
$execResults['shell_exec'] = with_error_capture(function() {
$out = @shell_exec('whoami 2>&1');
return $out === null ? null : trim($out);
});
line('shell_exec("whoami")', $execResults['shell_exec']);
}
if (function_exists('system')) {
$execResults['system'] = with_error_capture(function() {
ob_start();
$rc = 0;
@system('pwd 2>&1', $rc);
$out = ob_get_clean();
return ['rc' => $rc, 'out' => trim((string)$out)];
});
line('system("pwd")', $execResults['system']);
}
if (function_exists('proc_open')) {
$execResults['proc_open'] = with_error_capture(function() {
$desc = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];
$proc = @proc_open('id', $desc, $pipes);
if (!is_resource($proc)) return 'proc_open failed';
fclose($pipes[0]);
$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$code = proc_close($proc);
return ['rc' => $code, 'stdout' => trim($stdout), 'stderr' => trim($stderr)];
});
line('proc_open("id")', $execResults['proc_open']);
}
if (function_exists('popen')) {
$execResults['popen'] = with_error_capture(function() {
$h = @popen('id 2>&1', 'r');
if (!is_resource($h)) return 'popen failed';
$out = stream_get_contents($h);
$rc = pclose($h);
return ['rc' => $rc, 'out' => trim((string)$out)];
});
line('popen("id")', $execResults['popen']);
}
section('Fork capability');
line('extension_loaded(pcntl)', extension_loaded('pcntl'));
line('function_exists(pcntl_fork)', function_exists('pcntl_fork'));
line('Active fork test', 'SKIPPED in web SAPI for safety');
section('Controlled memory probe');
$memoryLimit = bytes_from_ini(ini_get('memory_limit'));
$chunks = [];
$allocated = 0;
$chunkSize = 4 * 1024 * 1024;
$target = ($memoryLimit !== null && $memoryLimit > 0) ? (int)($memoryLimit * 0.70) : 64 * 1024 * 1024;
$oom = false;
$oomMsg = null;
try {
while ($allocated + $chunkSize <= $target) {
$chunks[] = str_repeat('A', $chunkSize);
$allocated += $chunkSize;
}
} catch (Throwable $e) {
$oom = true;
$oomMsg = $e->getMessage();
}
line('memory_limit parsed', $memoryLimit);
line('allocated safely', $allocated);
line('oom caught', $oom);
line('oom message', $oomMsg ?: 'none');
unset($chunks);
section('Controlled CPU / timeout probe');
$start = microtime(true);
$iterations = 0;
$limitSeconds = max(1, (int)ini_get('max_execution_time'));
$softBudget = min(3, max(1, $limitSeconds - 1));
while ((microtime(true) - $start) < $softBudget) {
hash('sha256', random_bytes(256), false);
$iterations++;
}
line('elapsed', round(microtime(true) - $start, 3) . 's');
line('iterations', $iterations);
line('soft budget', $softBudget . 's');
section('set_time_limit test');
$setTimeLimitRes = with_error_capture(function() {
return @set_time_limit(600);
});
line('set_time_limit(600)', $setTimeLimitRes);
line('max_execution_time after set_time_limit', ini_get('max_execution_time'));
section('Network reachability');
$netTargets = [
['127.0.0.1', 25],
['127.0.0.1', 3306],
['127.0.0.1', 6379],
['127.0.0.1', 11211],
['127.0.0.1', 7080],
['127.0.0.1', 80],
['127.0.0.1', 443],
];
$netResults = [];
foreach ($netTargets as [$host, $port]) {
$netResults["$host:$port"] = try_socket($host, $port);
line("$host:$port", $netResults["$host:$port"]);
}
section('Unix socket reachability');
$unixCandidates = [
'/run/mysqld/mysqld.sock',
'/var/run/mysqld/mysqld.sock',
'/tmp/mysql.sock',
'/run/redis/redis-server.sock',
'/var/run/redis/redis.sock',
'/tmp/redis.sock',
'/usr/local/lsws/admin/tmp/admin.sock',
];
$unixResults = [];
foreach ($unixCandidates as $sock) {
$unixResults[$sock] = try_unix_socket($sock);
line($sock, $unixResults[$sock]);
}
section('Stream wrappers');
$wrappers = stream_get_wrappers();
sort($wrappers);
line('wrappers', $wrappers);
$wrapperReads = [
'php://memory',
'php://temp',
'data://text/plain;base64,SGVsbG8=',
];
foreach ($wrapperReads as $wrapper) {
line("read $wrapper", with_error_capture(function() use ($wrapper) {
$d = @file_get_contents($wrapper);
if ($d === false) return false;
return 'len=' . strlen($d) . ' data=' . substr($d, 0, 40);
}));
}
section('Include wrapper tests');
$includeFile = $baseTmp . '/include-test.php';
file_put_contents($includeFile, "<?php return ['ok' => true, 'time' => time()];");
$includeLocal = with_error_capture(function() use ($includeFile) {
return include $includeFile;
});
$includeData = with_error_capture(function() {
return @include 'data://text/plain;base64,PD9waHAgcmV0dXJuIFsiZGF0YSI9PnRydWVdOw==';
});
line('include local file', $includeLocal);
line('include data:// wrapper', $includeData);
section('Environment leakage');
$envKeys = ['HOME','USER','LOGNAME','PATH','TMPDIR','TEMP','HOSTNAME'];
$envOut = [];
foreach ($envKeys as $k) {
$envOut[$k] = getenv($k);
}
line('getenv selected', $envOut);
line('_ENV count', is_array($_ENV) ? count($_ENV) : 'n/a');
line('_SERVER selected', [
'PATH' => $_SERVER['PATH'] ?? null,
'USER' => $_SERVER['USER'] ?? null,
'HOME' => $_SERVER['HOME'] ?? null,
]);
section('Self-request capability');
$selfUrl = null;
if (!empty($_SERVER['HTTP_HOST'])) {
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$selfUrl = $scheme . '://' . $_SERVER['HTTP_HOST'] . ($_SERVER['REQUEST_URI'] ?? '/');
}
line('self url', $selfUrl ?: 'n/a');
if ($selfUrl && function_exists('file_get_contents')) {
line('self file_get_contents', with_error_capture(function() use ($selfUrl) {
$ctx = stream_context_create(['http' => ['timeout' => 2]]);
$data = @file_get_contents($selfUrl, false, $ctx);
if ($data === false) return false;
return 'len=' . strlen($data);
}));
}
section('Session basics');
$sessionRes = with_error_capture(function() {
if (session_status() !== PHP_SESSION_ACTIVE) {
@session_start();
}
$_SESSION['audit_test'] = 'ok';
return session_id();
});
line('session.save_path', ini_get('session.save_path'));
line('session_start()', $sessionRes);
line('session file expected', ini_get('session.save_path') . '/sess_' . session_id());
section('mail() basics');
line('function_exists(mail)', function_exists('mail'));
line('sendmail_path', ini_get('sendmail_path'));
line('mail() active send test', 'SKIPPED by design');
section('.user.ini verification hint');
$userIniFile = $docRoot . '/.user.ini.audit-test';
$userIniContent = <<<TXT
; Rename this file to .user.ini for a live test, then wait for user_ini.cache_ttl
memory_limit=3072M
max_execution_time=900
upload_max_filesize=3072M
post_max_size=3072M
auto_prepend_file=
TXT;
@file_put_contents($userIniFile, $userIniContent);
line('Prepared helper file', $userIniFile);
line('How to test .user.ini', 'Rename .user.ini.audit-test -> .user.ini, wait cache_ttl, reload script, compare values.');
section('Assessment');
$openBasedir = (string)ini_get('open_basedir');
$disableFunctions = (string)ini_get('disable_functions');
$userIni = (string)ini_get('user_ini.filename');
$allowUrlInclude = (string)ini_get('allow_url_include');
if ($openBasedir !== '') {
add_result('PASS', 'open_basedir is set', $openBasedir);
} else {
add_result('FAIL', 'open_basedir is empty');
}
if (!empty($overrideResults['memory_limit']['changed'])) {
add_result('FAIL', 'memory_limit can be changed via ini_set()', json_encode($overrideResults['memory_limit']));
} else {
add_result('PASS', 'memory_limit is not changeable via ini_set()');
}
if (!empty($overrideResults['max_execution_time']['changed'])) {
add_result('FAIL', 'max_execution_time can be changed via ini_set()', json_encode($overrideResults['max_execution_time']));
} else {
add_result('PASS', 'max_execution_time is not changeable via ini_set()');
}
if (!empty($overrideResults['upload_max_filesize']['changed'])) {
add_result('FAIL', 'upload_max_filesize can be changed via ini_set()', json_encode($overrideResults['upload_max_filesize']));
} else {
add_result('PASS', 'upload_max_filesize resisted ini_set()');
}
if (!empty($overrideResults['post_max_size']['changed'])) {
add_result('FAIL', 'post_max_size can be changed via ini_set()', json_encode($overrideResults['post_max_size']));
} else {
add_result('PASS', 'post_max_size resisted ini_set()');
}
if (!empty($overrideResults['open_basedir']['changed'])) {
add_result('FAIL', 'open_basedir can be changed via ini_set()', json_encode($overrideResults['open_basedir']));
} else {
add_result('PASS', 'open_basedir resisted ini_set()');
}
$dangerousAvailable = [];
foreach (['exec','shell_exec','system','passthru','proc_open','popen'] as $fn) {
if (function_exists($fn) && !str_contains($disableFunctions, $fn)) {
$dangerousAvailable[] = $fn;
}
}
if ($dangerousAvailable) {
add_result('FAIL', 'Command execution functions are available', implode(', ', $dangerousAvailable));
} else {
add_result('PASS', 'Command execution functions are blocked');
}
if (extension_loaded('pcntl')) {
add_result('FAIL', 'pcntl extension is loaded', 'Not recommended for shared hosting web SAPI');
} else {
add_result('PASS', 'pcntl extension is not loaded');
}
if ($userIni === '' || strtolower($userIni) === 'none') {
add_result('PASS', '.user.ini appears disabled');
} else {
add_result('WARN', '.user.ini appears enabled', $userIni);
}
if ($allowUrlInclude === '' || $allowUrlInclude === '0') {
add_result('PASS', 'allow_url_include is off');
} else {
add_result('FAIL', 'allow_url_include is on');
}
if (is_link($symlinkTarget)) {
add_result('WARN', 'Symlink creation works inside allowed path', $symlinkTarget);
} else {
add_result('PASS', 'Symlink creation did not work');
}
if (file_exists($hardlinkTarget)) {
add_result('WARN', 'Hardlink creation works inside allowed path', $hardlinkTarget);
} else {
add_result('PASS', 'Hardlink creation did not work');
}
$localhostSensitiveOpen = [];
foreach (['127.0.0.1:25','127.0.0.1:3306','127.0.0.1:6379','127.0.0.1:7080'] as $k) {
if (!empty($netResults[$k]['connected'])) {
$localhostSensitiveOpen[] = $k;
}
}
if ($localhostSensitiveOpen) {
add_result('WARN', 'Sensitive localhost TCP ports reachable', implode(', ', $localhostSensitiveOpen));
} else {
add_result('PASS', 'Sensitive localhost TCP ports not reachable');
}
$reachableUnix = [];
foreach ($unixResults as $sock => $res) {
if (!empty($res['connected'])) {
$reachableUnix[] = $sock;
}
}
if ($reachableUnix) {
add_result('WARN', 'Sensitive UNIX sockets reachable', implode(', ', $reachableUnix));
} else {
add_result('PASS', 'Sensitive UNIX sockets not reachable');
}
section('Score');
line('PASS', $SCORE['PASS']);
line('WARN', $SCORE['WARN']);
line('FAIL', $SCORE['FAIL']);
section('Findings');
foreach ($FINDINGS as [$level, $title, $detail]) {
echo '[' . $level . '] ' . $title;
if ($detail !== '') {
echo ' :: ' . $detail;
}
echo PHP_EOL;
}
section('Cleanup');
$cleanupFiles = [
$renameTo,
$copyToSession,
$src,
$symlinkTarget,
$hardlinkTarget,
$includeFile,
$userIniFile,
];
foreach ($cleanupFiles as $f) {
if (is_link($f) || file_exists($f)) {
@unlink($f);
}
}
@rmdir($baseTmp);
echo PHP_EOL . "Done." . PHP_EOL;
@@ -0,0 +1,5 @@
<?php
if (is_readable('/var/www/shared/mu-plugins/load.php')) {
require_once('/var/www/shared/mu-plugins/load.php');
}
@@ -0,0 +1,45 @@
<?php
/**
* Plugin Name: MU Test Plugin
* Description: MU Test plugin.
* Author: WEDOS
* Version: 1.0.0
*/
if (!defined('ABSPATH')) {
exit;
}
add_action('admin_notices', function () {
if (!current_user_can('manage_options')) {
return;
}
echo '<div class="notice notice-success is-dismissible">';
echo '<p><strong>MU TEST OK</strong> — shared MU plugin.</p>';
echo '</div>';
});
add_action('admin_bar_menu', function ($wp_admin_bar) {
if (!current_user_can('manage_options')) {
return;
}
$wp_admin_bar->add_node([
'id' => 'mu-test-ok',
'title' => 'MU TEST OK',
'href' => admin_url('plugins.php?plugin_status=mustuse'),
'meta' => [
'title' => 'MU plugin',
],
]);
}, 100);
add_action('wp_footer', function () {
if (!current_user_can('manage_options')) {
return;
}
echo '<div style="position:fixed;right:16px;bottom:16px;z-index:99999;padding:10px 14px;background:#16a34a;color:#fff;border-radius:8px;font:14px/1.4 sans-serif;box-shadow:0 4px 16px rgba(0,0,0,.2);">MU TEST OK</div>';
});
@@ -0,0 +1,7 @@
<?php
if (!defined('SODEW_SMTP_ENABLE') || SODEW_SMTP_ENABLE === true) {
if (is_readable(__DIR__ . '/sodew-smtp.php')) {
require(__DIR__ . '/sodew-smtp.php');
}
}
@@ -0,0 +1,46 @@
<?php
/**
* @author Maksym Krugol <maksym.krugol@wedos.org>
* @copyright SODEW, s.r.o.
*
* @wordpress-plugin
* Plugin Name: SODEW SMTP config
* Plugin URI: https://sodew.ai
* Description: SMTP config
* Author: SODEW
* Author URI: https://sodew.ai
* Version: 1.1
*/
defined('ABSPATH') || exit;
add_action('phpmailer_init', function ($phpmailer) {
$domain = wp_parse_url(home_url(), PHP_URL_HOST);
$fromName = defined('SODEW_SMTP_FROM_NAME') ? SODEW_SMTP_FROM_NAME : 'WordPress';
$replyTo = defined('SODEW_SMTP_REPLY_TO') ? SODEW_SMTP_REPLY_TO : "wordpress@$domain";
$replyToName = defined('SODEW_SMTP_REPLY_TO_NAME') ? SODEW_SMTP_REPLY_TO_NAME : $fromName;
$phpmailer->isSMTP();
$phpmailer->XMailer = 'sodewMailer 1.1';
$phpmailer->Host = 'postfix';
$phpmailer->Port = 587;
$phpmailer->SMTPAuth = true;
$phpmailer->SMTPSecure = 'tls';
$phpmailer->SMTPAutoTLS = true;
$phpmailer->SMTPOptions = [
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
'peer_name' => SODEW_SMTP_HOST,
'allow_self_signed' => true,
],
];
$phpmailer->Username = SODEW_SMTP_USER;
$phpmailer->Password = SODEW_SMTP_PASS;
$phpmailer->setFrom(SODEW_SMTP_POSTMASTER, $fromName, false);
$phpmailer->Sender = SODEW_SMTP_POSTMASTER;
$phpmailer->clearReplyTos();
$phpmailer->addReplyTo($replyTo, $replyToName);
});
+269
View File
@@ -0,0 +1,269 @@
#!/usr/bin/env python3
import os
import time
import json
import threading
import http.server
from pathlib import Path
from urllib.parse import urlencode
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
from collections.abc import Mapping
from urllib.response import addinfourl
from typing import cast, Any
from datetime import datetime
class H(http.server.BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/healthz":
self.send_response(200)
self.end_headers()
self.wfile.write(b"ok")
else:
self.send_response(404)
self.end_headers()
def log_message(self, format, *args):
pass
def int_env(name: str, default: int) -> int:
try:
return int(os.getenv(name, str(default)))
except Exception:
return default
TASKS_PATH = Path("/app/tasks")
API_URL = os.getenv("API_URL", "https://api.sodew.ai/api/tasks").rstrip("/")
WORKER_UUID = os.getenv("WORKER_UUID", "worker-uuid")
WORKER_NAME = os.getenv("WORKER_NAME", "worker-name")
WORKER_POOL = os.getenv("WORKER_POOL", "")
WORKER_VERSION = "6.3.445"
GETTING_PAUSE = int_env("GETTING_PAUSE", 30)
SENDING_PAUSE = int_env("SENDING_PAUSE", 15)
HTTP_TIMEOUT = 15
DEFAULT_HEADERS = {"Accept": "application/json", "Content-Type": "application/json", "User-Agent": "kube-worker/1.1"}
def start_health():
t = threading.Thread(target=http.server.HTTPServer(('0.0.0.0', 8080), H).serve_forever, daemon=True)
t.start()
def ensure_dir() -> None:
TASKS_PATH.mkdir(parents=True, exist_ok=True)
def log_info(text: str) -> None:
print(datetime.now().strftime("[%Y.%m.%d %H:%M:%S]") + f" {text}")
def format_error_body(body: Any) -> str:
if body is None:
return "<no body>"
if isinstance(body, dict):
msg = body.get("message")
if isinstance(msg, str) and msg.strip():
return msg
try:
return json.dumps(body, ensure_ascii=False, sort_keys=True)
except Exception:
return repr(body)
if isinstance(body, list):
try:
return json.dumps(body, ensure_ascii=False, sort_keys=True)
except Exception:
return repr(body)
try:
return str(body)
except Exception:
return "<unprintable body>"
def task_read(path: Path) -> dict[str, str]:
result: dict[str, str] = {}
for line in path.read_text(encoding="utf-8", errors="ignore").splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
k, v = line.split("=", 1)
result[k.strip()] = v.strip()
return result
def task_save(path: Path, data: Mapping[str, object]) -> None:
flat: dict[str, object] = dict(data)
lines: list[str] = []
for key, value in flat.items():
if not isinstance(key, str):
key = str(key)
if value is None:
value_str = ""
elif isinstance(value, (dict, list)):
try:
value_str = json.dumps(value, ensure_ascii=False)
except Exception:
value_str = str(value)
else:
value_str = str(value)
value_str = value_str.replace("\n", " ").replace("\r", " ")
lines.append(f"{key}={value_str}")
content = "\n".join(lines) + "\n"
path.write_text(content, encoding="utf-8")
def http_request_json(
method: str,
url: str,
*,
params: Mapping[str, str] | None = None,
json_body: Mapping[str, object] | None = None,
headers: Mapping[str, str] | None = None,
timeout: int = HTTP_TIMEOUT,
) -> tuple[int, object | None]:
if params:
qs = urlencode(params)
url = f"{url}?{qs}"
body_bytes = None
req_headers = dict(DEFAULT_HEADERS)
if headers:
req_headers.update(headers)
if json_body is not None:
body_bytes = json.dumps(json_body).encode("utf-8")
req = Request(url, data=body_bytes, headers=req_headers, method=method)
try:
with urlopen(req, timeout=timeout) as resp:
resp_typed = cast(addinfourl, resp)
code = resp_typed.getcode() or 0
body = resp_typed.read()
except HTTPError as e:
try:
err_bytes = e.read()
except Exception:
err_bytes = b""
if not err_bytes:
return e.code, None
try:
return e.code, json.loads(err_bytes.decode("utf-8", errors="replace"))
except Exception:
return e.code, err_bytes.decode("utf-8", errors="replace")
except URLError as e:
return 0, {"error": "network", "reason": str(e)}
if not body:
return code, None
try:
return code, json.loads(body.decode("utf-8", errors="replace"))
except Exception:
return code, None
def task_receive() -> dict[str, Any] | None:
log_info(f"Receiving new tasks from API | Worker: {WORKER_NAME}")
url = f"{API_URL}/receive"
payload: dict[str, str] = {
"worker_name": WORKER_NAME,
"worker_uuid": WORKER_UUID,
"worker_version": WORKER_VERSION
}
if WORKER_POOL and WORKER_POOL.strip():
payload["worker_pool"] = WORKER_POOL.strip()
code, body = http_request_json("POST", url, json_body=payload)
if code == 204:
log_info("Code 204 | No tasks from API")
return None
if code == 200:
try:
log_info("Code: 200 | Raw data: " + (json.dumps(body, ensure_ascii=False, sort_keys=True) if isinstance(body, (dict, list)) else repr(body)))
except Exception:
log_info("Code: 200 | Raw data: <unserializable>")
if isinstance(body, dict):
d = cast(dict[str, object], body)
try:
log_info("Task: received | " + json.dumps(d, ensure_ascii=False, sort_keys=True))
except Exception:
log_info("Task: received | <unserializable dict>")
if "uuid" in d and "action" in d:
return d
log_info("Task: missing required fields 'uuid' or 'action'")
else:
log_info("Task: not recognized (body is not a dict)")
else:
message_error = format_error_body(body)
log_info(f"Code: {code} | Error: {message_error}")
return None
def main() -> None:
start_health()
ensure_dir()
while True:
task_files = list(TASKS_PATH.glob("*.task"))
task_count = len(task_files)
log_info(f"Task files found: {task_count}")
if not task_files:
task = task_receive()
if task:
uuid = str(task.get("uuid", "")).strip()
action = str(task.get("action", "")).strip()
task.pop("uuid", None)
task["status"] = "waiting"
if uuid and action:
log_info(f"Task from API: {uuid}")
path = TASKS_PATH / f"{uuid}.task"
if not path.exists():
task_save(path=path, data=task)
else:
log_info(f"Task: {uuid} | Error: uuid or action is empty, skip")
time.sleep(GETTING_PAUSE)
else:
for path in task_files:
uuid = path.stem
try:
data = task_read(path)
log_info(f"Task: {uuid} | Parse task success")
except Exception:
log_info(f"Task: {uuid} | Task not recognized")
continue
action = (data.get("action") or "unknown").strip().lower()
if action == "pause":
continue
status = (data.get("status") or "unknown").strip().lower()
payload: dict[str, str] = dict(data)
payload["worker_uuid"] = WORKER_UUID
payload["status"] = status
log_info(f"Task: {uuid} | Payload: {json.dumps(payload, ensure_ascii=False)}")
url = f"{API_URL}/{uuid}/status"
code, body = http_request_json("PATCH", url, json_body=payload)
if not (200 <= code < 300):
message_error = format_error_body(body)
log_info(f"Task: {uuid} | Code: {code} | Error: {message_error}")
continue
if status not in {"new", "waiting", "execution"}:
log_info(f"Task: {uuid} | Remove...")
try:
path.unlink(missing_ok=True)
except Exception:
pass
time.sleep(SENDING_PAUSE)
if __name__ == "__main__":
print(f"[ Worker Agent {WORKER_VERSION} | {datetime.now():%Y-%m-%d %H-%M-%S} ______________ ]")
print(f"🔹Worker: {WORKER_NAME} | {WORKER_UUID}")
print(f"🔹Tasks path: {TASKS_PATH}")
print(f"🔹API URL: {API_URL}")
try:
main()
except KeyboardInterrupt:
print("🔥Interrupted by user.")