smazani
This commit is contained in:
Binary file not shown.
@@ -1,8 +0,0 @@
|
||||
.zed/
|
||||
_tmp/
|
||||
data/
|
||||
tools/registry/images
|
||||
config.sh
|
||||
_gen.sh
|
||||
CLAUDE.md
|
||||
codebook.toml
|
||||
@@ -1,123 +0,0 @@
|
||||
# KUBE 7.1.557
|
||||
|
||||
Bash script for deploying/backups and restore SODEW infrastructures.
|
||||
|
||||
|
||||
> [!WARNING]
|
||||
> Documentation is outdated
|
||||
|
||||
|
||||
### Install HELM
|
||||
```
|
||||
curl -fsSL https://get.helm.sh/helm-v3.16.2-linux-amd64.tar.gz | tar -xz
|
||||
sudo mv linux-amd64/helm /usr/local/bin/helm
|
||||
|
||||
helm version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
- [File structure](docs/file_scructure.md)
|
||||
- [Options](docs/options.md)
|
||||
- Usage
|
||||
- [Backup](docs/backup.md)
|
||||
- [Restore](docs/restore.md)
|
||||
- [Storage](docs/storage.md)
|
||||
- [k3s](docs/k3s.md)
|
||||
- [MariaDB](docs/resources/mariadb.md)
|
||||
- [Redis](docs/resources/redis.md)
|
||||
- [OpenLiteSpeed](docs/resources/openlitespeed.md)
|
||||
- [Postfix](docs/resources/postfix.md)
|
||||
- [Worker](docs/resources/worker.md)
|
||||
- [Site/Wordpress](docs/site.md)
|
||||
- [Transfer](docs/transfer.md)
|
||||
- [Shell](docs/shell.md)
|
||||
- [Log](docs/log.md)
|
||||
- [Cron](docs/cron.md)
|
||||
- [Test](docs/test.md)
|
||||
- [Install](docs/install.md)
|
||||
- [Script](docs/script.md)
|
||||
- [Mail server](docs/mta.md)
|
||||
|
||||
## Getting started
|
||||
|
||||
Before you start, you must create a configuration file:
|
||||
```bash
|
||||
cp config.sh.default config.sh
|
||||
```
|
||||
and make the necessary changes.
|
||||
|
||||
If necessary, you can set your own password values for MariaDB, Redis, rSync, OpenLiteSpeed ...
|
||||
This can be done by specifying values for the corresponding variables or by writing values to the corresponding files in the `data` directory. If there are no files, just run the script without parameters. The files with passwords will be created automatically. After that you can make the appropriate edits.
|
||||
|
||||
## Usage
|
||||
|
||||
The script requires elevated permissions to work (sudoers group).
|
||||
|
||||
## Scheme open ports
|
||||
```mermaid
|
||||
flowchart LR;
|
||||
subgraph MD[MariaDB replica]
|
||||
MDM[mariadb-master-0]
|
||||
MDS[mariadb-slave-0]
|
||||
end
|
||||
subgraph RD[Redis cluster]
|
||||
RD0[redis-0]
|
||||
RD1[redis-1]
|
||||
RD2[redis-2]
|
||||
RS0[redis-sentinel-0]
|
||||
RS1[redis-sentinel-1]
|
||||
RS2[redis-sentinel-2]
|
||||
end
|
||||
subgraph OLS[OpenLiteSpeed]
|
||||
OL1[openlitespeed-0]
|
||||
OL2[openlitespeed-1]
|
||||
end
|
||||
PTF[postfix-xxxxxxxxx-xxxxx]
|
||||
|
||||
MDM <==> MDS
|
||||
RD0 <==> RD1 <==> RD2 <==> RD0
|
||||
RS0 <==> RS1 <==> RS2 <==> RS0
|
||||
|
||||
P3306([3306])
|
||||
P3306 --mysql--> MD
|
||||
|
||||
P6379([6379])
|
||||
P26379([26379])
|
||||
P6379 & P26379 --redis--> RD
|
||||
|
||||
P80([80/443])
|
||||
P80 --http/https--> OLS
|
||||
|
||||
P7080([7080/31080])
|
||||
P7080 --admin panel--> OLS
|
||||
|
||||
P25([25])
|
||||
P587([587])
|
||||
P25 --smtp--> PTF
|
||||
P587 --smtp=tls--> PTF
|
||||
```
|
||||
## Scheme use ports
|
||||
```mermaid
|
||||
%%graph LR;
|
||||
flowchart LR;
|
||||
subgraph K3S[Server]
|
||||
MD[MariaDB]
|
||||
RD[Redis]
|
||||
|
||||
PTF[Postfix]
|
||||
subgraph OLS[OpenLiteSpeed]
|
||||
WP[Sites on Wordpress]
|
||||
ADM[Admin Panel]
|
||||
end
|
||||
end
|
||||
NET[Internet]
|
||||
|
||||
NET --80,443--> WP
|
||||
NET --31080--> ADM
|
||||
NET --25--> PTF
|
||||
|
||||
WP --3306--> MD
|
||||
WP --6379--> RD
|
||||
WP --587--> PTF
|
||||
``````
|
||||
@@ -1,51 +0,0 @@
|
||||
#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
|
||||
@@ -1,4 +0,0 @@
|
||||
apiVersion: v2
|
||||
name: stack
|
||||
version: 0.1.0
|
||||
type: application
|
||||
@@ -1,16 +0,0 @@
|
||||
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
|
||||
@@ -1,16 +0,0 @@
|
||||
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
|
||||
@@ -1,16 +0,0 @@
|
||||
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
|
||||
@@ -1,16 +0,0 @@
|
||||
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
|
||||
@@ -1,26 +0,0 @@
|
||||
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
|
||||
@@ -1,26 +0,0 @@
|
||||
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
|
||||
@@ -1,26 +0,0 @@
|
||||
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
|
||||
@@ -1,26 +0,0 @@
|
||||
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
|
||||
@@ -1,19 +0,0 @@
|
||||
{{- 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 }}
|
||||
@@ -1,299 +0,0 @@
|
||||
{{- 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 }}
|
||||
@@ -1,128 +0,0 @@
|
||||
{{- 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 }}
|
||||
@@ -1,301 +0,0 @@
|
||||
{{- 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 }}
|
||||
@@ -1,188 +0,0 @@
|
||||
{{- 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 }}
|
||||
@@ -1,416 +0,0 @@
|
||||
{{- 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 }}
|
||||
@@ -1,52 +0,0 @@
|
||||
{{- 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 }}
|
||||
@@ -1,42 +0,0 @@
|
||||
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'
|
||||
@@ -1,15 +0,0 @@
|
||||
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
|
||||
@@ -1,52 +0,0 @@
|
||||
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"
|
||||
@@ -1,6 +0,0 @@
|
||||
children=16
|
||||
max_memory_limit=512M
|
||||
memory_limit=512M
|
||||
max_execution_time=30
|
||||
post_max_size=512M
|
||||
upload_max_filesize=512M
|
||||
@@ -1,6 +0,0 @@
|
||||
children=4
|
||||
max_memory_limit=512M
|
||||
memory_limit=512M
|
||||
max_execution_time=30
|
||||
post_max_size=128M
|
||||
upload_max_filesize=128M
|
||||
@@ -1,6 +0,0 @@
|
||||
children=6
|
||||
max_memory_limit=512M
|
||||
memory_limit=512M
|
||||
max_execution_time=30
|
||||
post_max_size=128M
|
||||
upload_max_filesize=128M
|
||||
@@ -1,6 +0,0 @@
|
||||
children=8
|
||||
max_memory_limit=512M
|
||||
memory_limit=512M
|
||||
max_execution_time=30
|
||||
post_max_size=128M
|
||||
upload_max_filesize=128M
|
||||
@@ -1,8 +0,0 @@
|
||||
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]
|
||||
@@ -1,11 +0,0 @@
|
||||
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]
|
||||
@@ -1,11 +0,0 @@
|
||||
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]
|
||||
@@ -1,7 +0,0 @@
|
||||
RewriteEngine On
|
||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
|
||||
# Laravel
|
||||
RewriteRule . /public/index.php [L]
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
@@ -1,13 +0,0 @@
|
||||
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]
|
||||
@@ -1,14 +0,0 @@
|
||||
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]
|
||||
@@ -1,46 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
# 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
|
||||
@@ -1,23 +0,0 @@
|
||||
# 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
|
||||
@@ -1,23 +0,0 @@
|
||||
# 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
|
||||
@@ -1,19 +0,0 @@
|
||||
[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
|
||||
@@ -1,20 +0,0 @@
|
||||
# --- 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 ---
|
||||
@@ -1,22 +0,0 @@
|
||||
# --- 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
@@ -1,4 +0,0 @@
|
||||
<?php
|
||||
echo '<pre>pid: ' . getmypid() . '</pre>';
|
||||
echo phpinfo();
|
||||
|
||||
Binary file not shown.
@@ -1,8 +0,0 @@
|
||||
<?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();
|
||||
@@ -1,136 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,8 +0,0 @@
|
||||
<?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));
|
||||
@@ -1,646 +0,0 @@
|
||||
<?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;
|
||||
@@ -1,5 +0,0 @@
|
||||
<?php
|
||||
|
||||
if (is_readable('/var/www/shared/mu-plugins/load.php')) {
|
||||
require_once('/var/www/shared/mu-plugins/load.php');
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
<?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>';
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
if (!defined('SODEW_SMTP_ENABLE') || SODEW_SMTP_ENABLE === true) {
|
||||
if (is_readable(__DIR__ . '/sodew-smtp.php')) {
|
||||
require(__DIR__ . '/sodew-smtp.php');
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
<?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);
|
||||
});
|
||||
@@ -1,269 +0,0 @@
|
||||
#!/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.")
|
||||
@@ -1,169 +0,0 @@
|
||||
# App
|
||||
appAssetsPath="$appPath/assets"
|
||||
appDataPath="$appPath/data"
|
||||
|
||||
hostName=$(hostname)
|
||||
hostIp="127.0.0.1"
|
||||
hostUuid=$(fileValueGetOrCreate "$appDataPath/$hostName.uuid" $(uuidgen))
|
||||
hostSalt=$(filePasswordGetOrCreate "$appDataPath/$hostName.salt")
|
||||
basePath="/_data"
|
||||
domainsList="$appAssetsPath/domains.list"
|
||||
sftpAccessGroup="sftp-access"
|
||||
dnsResolver="@1.1.1.1"
|
||||
pigzThreads="4"
|
||||
rocketChatUrl=""
|
||||
|
||||
# Logs
|
||||
logPath="$appDataPath/logs"
|
||||
logFile="$logPath/$appDate.log"
|
||||
|
||||
# CPU/Memory profiles 4c/8c/16c/32c 16g/32g/64g/128g
|
||||
kubeCpuProfile="8c"
|
||||
kubeMemoryProfile="16g"
|
||||
|
||||
# k3s
|
||||
k3sCmd="/usr/local/bin/k3s"
|
||||
k3sNamespace="sodew-dev"
|
||||
k3sReadyRetries=20
|
||||
k3sReadySleep=4
|
||||
|
||||
# Redis
|
||||
redisKube="redis"
|
||||
redisSentinelKube="$redisKube-sentinel"
|
||||
redisYaml="$appDataPath/yaml/$redisKube.yaml"
|
||||
redisPath="$basePath/$redisKube"
|
||||
redisFileUsersAcl="users.acl"
|
||||
redisRootPass=$(filePasswordGetOrCreate "$appDataPath/$hostName.$redisKube")
|
||||
|
||||
# MariaDB
|
||||
mariadbKube="mariadb"
|
||||
mariadbMasterKube="$mariadbKube-master"
|
||||
mariadbSlaveKube="$mariadbKube-slave"
|
||||
mariadbYaml="$appDataPath/yaml/$mariadbKube.yaml"
|
||||
mariadbMasterPath="$basePath/$mariadbKube/master"
|
||||
mariadbSlavePath="$basePath/$mariadbKube/slave"
|
||||
mariadbRootPass=$(filePasswordGetOrCreate "$appDataPath/$hostName.$mariadbKube")
|
||||
|
||||
# OpenLiteSpeed
|
||||
olsKube="openlitespeed"
|
||||
olsYaml="$appDataPath/yaml/$olsKube.yaml"
|
||||
olsPath="$basePath/$olsKube"
|
||||
olsVhostsPath="$basePath/vhosts"
|
||||
olsPrivatePath="$olsPath/vhosts-private"
|
||||
olsPublicPath="$olsPath/vhosts-public"
|
||||
olsLogsPath="$olsPath/logs"
|
||||
olsAdminPath="$olsPath/admin"
|
||||
olsPhpIniPath="$olsPath/php-ini"
|
||||
olsConfigPath="$olsPath/config"
|
||||
olsConfigFile="$olsConfigPath/httpd_config.conf"
|
||||
olsVhostsConfigPath="$olsConfigPath/vhosts"
|
||||
olsPodVhostsPath="/var/www/vhosts"
|
||||
olsPodPrivatePath="/var/www/private"
|
||||
olsPodPublicPath="/var/www/public"
|
||||
olsAdminWhiteList=("0.0.0.0/0")
|
||||
olsAdminPass=$(filePasswordGetOrCreate "$appDataPath/$hostName.$olsKube")
|
||||
olsVhostMode="standalone"
|
||||
olsVhostFile="virtual.host.conf"
|
||||
olsVhostTemplate="v.template"
|
||||
olsQuotaBlockLimit=10485760
|
||||
olsQuotaInodeLimit=100000
|
||||
olsPhpList=() # olsPhpList=(81 82 83 84 85)
|
||||
|
||||
# Postfix
|
||||
postfixKube="postfix"
|
||||
postfixYaml="$appDataPath/yaml/$postfixKube.yaml"
|
||||
postfixPath="$basePath/$postfixKube"
|
||||
postfixConfigPath="$postfixPath/config"
|
||||
postfixDkimPath="$postfixPath/dkim"
|
||||
postfixTlsCrtFile="$appDataPath/$postfixKube/tls.crt"
|
||||
postfixTlsKeyFile="$appDataPath/$postfixKube/tls.key"
|
||||
postfixHost="mta.example.com"
|
||||
postfixPostmaster="postmaster@$postfixHost"
|
||||
postfixDefaultRealm="auth.mta"
|
||||
postfixDkimSelector="dkim"
|
||||
postfixDomainsFile="virtual_domains.maps"
|
||||
postfixAliasesFile="virtual_aliases.maps"
|
||||
postfixSendersFile="senders.maps"
|
||||
postfixIp="$hostIp"
|
||||
|
||||
# Worker
|
||||
workerKube="worker"
|
||||
workerYaml="$appDataPath/yaml/$workerKube.yaml"
|
||||
workerPath="$basePath/$workerKube"
|
||||
workerAgentPath="$workerPath/agent"
|
||||
workerTasksPath="$workerPath/tasks"
|
||||
workerFilesPath="$appDataPath/$workerKube"
|
||||
workerName="$hostName"
|
||||
workerPool=""
|
||||
workerApiUrl="https://api.sodew.ai/api/tasks"
|
||||
workerApiGettingPause=30
|
||||
workerApiSendingPause=15
|
||||
|
||||
# Metric
|
||||
metricKube="metric"
|
||||
metricYaml="$appDataPath/yaml/$metricKube.yaml"
|
||||
metricApiUrl="https://metric.api.sodew.ai/api/v1/write"
|
||||
metricPath="$basePath/$metricKube"
|
||||
metricPromPath="$metricPath/prom"
|
||||
metricVmagentPath="$metricPath/vmagent"
|
||||
|
||||
# Diag
|
||||
diagKube="diag"
|
||||
diagDeep=0
|
||||
diagSince="4h"
|
||||
diagOpcacheUrl="https://demo.133.vedos.cz/__opcache.php"
|
||||
diagApiUrl="https://api.sodew.ai/api/diag"
|
||||
diagFile="$appDataPath/$diagKube/${appDate}_$appTime.report"
|
||||
|
||||
# Malware
|
||||
malwareKube="malware"
|
||||
malwarePath="$appDataPath/$malwareKube"
|
||||
malwareQuarantinePath="$malwarePath/quarantine"
|
||||
|
||||
# Backups
|
||||
backupType="tar"
|
||||
backupFirstDir="_first"
|
||||
backupParallelJobs=1
|
||||
backupDailyPath="$appDataPath/backup-daily"
|
||||
backupDailySuffix=""
|
||||
backupDailyKeep=3
|
||||
backupArchivePath="$appDataPath/backup-archive"
|
||||
backupArchiveSuffix=".archive"
|
||||
backupArchiveInterval=30
|
||||
|
||||
# Storage
|
||||
# Default path to remote storage root for rSync / FTP / SSH
|
||||
storageType="rsync"
|
||||
storagePath="/$hostName"
|
||||
storageDailyKeep=10
|
||||
storageArchiveKeep=3
|
||||
|
||||
# Storage rSync
|
||||
rsyncUri="username@wedos.net/path"
|
||||
rsyncPassFile="$appDataPath/$hostName.rsync"
|
||||
filePasswordGetOrCreate "$rsyncPassFile" >/dev/null
|
||||
rsyncPath="$storagePath"
|
||||
|
||||
# Storage FTP
|
||||
ftpHost="wedos.net"
|
||||
ftpPort="21"
|
||||
ftpUser="user"
|
||||
ftpPass=$(filePasswordGetOrCreate "$appDataPath/$hostName.ftp")
|
||||
ftpPath="$storagePath"
|
||||
|
||||
# Storage SSH
|
||||
sshHost="wedos.net"
|
||||
sshUser="user"
|
||||
sshKeyFile="/home/user/.ssh/ssh_key"
|
||||
sshPath="/_storage$storagePath"
|
||||
|
||||
# List of tasks for CRON
|
||||
cronJobs=(
|
||||
"* * * * * /bin/bash $appPath/$appFile --script metric-kube"
|
||||
"0 * * * * /bin/bash $appPath/$appFile --script metric-sites"
|
||||
"*/5 * * * * /bin/bash $appPath/$appFile --script worker-observer"
|
||||
"20,40 * * * * /bin/bash $appPath/$appFile --script diag-report-ai-short"
|
||||
"5 * * * * /bin/bash $appPath/$appFile --script diag-report-ai-full"
|
||||
"0 * * * * /bin/bash $appPath/$appFile --script backup"
|
||||
"* * * * * /bin/bash $appPath/$appFile --script unigma"
|
||||
)
|
||||
@@ -1,151 +0,0 @@
|
||||
[KUBE](../README.md) / Backup
|
||||
|
||||
# Backup
|
||||
|
||||
- [Creation](#creation)
|
||||
- [Verification](#verification)
|
||||
- [Cleanup](#cleanup)
|
||||
- [LongTerm](#longterm)
|
||||
- [Commands](#commands)
|
||||
***
|
||||
|
||||
## Creation
|
||||
Directory structure of backups on the host machine: `<backup path>/<date>/<marker>`.
|
||||
- `<backup path>` – set by the `backupPath` parameter.
|
||||
- `<date>` – backup creation date in the format `YYYY-MM-DD`.
|
||||
- `<marker>` – a marker indicating the backup creation time in the format `HH-MM-SS`, with the first backup of the day named `<backupFirst>`.
|
||||
When the backup process starts, a directory `<backup path>/<date>/<marker>` is created (if it does not exist), where the backup files will be stored.
|
||||
|
||||
First, a backup of files is created depending on the `backupType` parameter:
|
||||
- `archive` – all subdirectories from `vhostsPath` are packed individually into separate archives named `<directory>.tar.gz`.
|
||||
- `rsync` – backup of the `vhostsPath` directory contents using the `rSync` utility.
|
||||
|
||||
Then, in each subdirectory of `vhostsPath`, the file `www/wp-config.php` is searched for, and database connection parameters are read from it. After that, the database is exported to a file and packed into an archive named `<directory>.sql.tar.gz`.
|
||||
|
||||
Next, the backup is copied to an external storage using the `rSync` utility. Two types of external storage (`storageType`) are supported:
|
||||
- `rsync` – for full functionality, connection parameters `rRync` and `FTP` must be set.
|
||||
- `ssh` – for full functionality, connection parameters `SSH` must be set.
|
||||
|
||||
Backup directory structure on external storage: `<storage path>/<hostname>`. Set by the following parameters:
|
||||
- `storagePath` – general path parameter for external storage
|
||||
- `rsyncPath` – path parameter for `rSync`
|
||||
- `ftpPath` – path parameter for `FTP`
|
||||
- `sshPath` – path parameter for `SSH`
|
||||
|
||||
Example:
|
||||
```bash
|
||||
storagePath="/$hostname"
|
||||
rsyncPath="$storagePath"
|
||||
ftpPath="$storagePath"
|
||||
sshPath="/_storage$storagePath"
|
||||
```
|
||||
Inside `<storage path>/<hostname>`, the directory structure on external storage fully mirrors the structure of `<backup path>` on the host machine.
|
||||
***
|
||||
|
||||
## Verification
|
||||
There are two types of backup verification available: verifying the latest backup and verifying all backups created on the current day.
|
||||
|
||||
When verifying the latest backup, the system searches for the latest backup execution log (directory `logs/backup` in the script folder). The file name is used to check the elapsed time since the last backup creation (parameter `backupElapsedMax`). The log contains the backup creation directory. Then, a non-recursive scan of subdirectories and files in the backup directory `<backup path>/<date>/<marker>` is performed. All directories and `*.tar.gz` files are considered website file backups, while `*.sql.tar.gz` files are considered database backups. A comparison is made between website file backups and database backups, and any discrepancies are noted. Next, the archive file sizes are checked (the minimum allowable size is set by the parameter `backupFileSizeMin`).
|
||||
|
||||
The next step is verifying the backup on external storage. The presence of the same subdirectories and files (non-recursively) is checked:
|
||||
```bash
|
||||
<backup path>/<date>/<marker>/* --> <storage path>/<hostname>/<date>/<marker>/*
|
||||
```
|
||||
A non-recursive file size comparison is performed, and any discrepancies are noted.
|
||||
|
||||
When verifying backups created on the current day, the day's directories are first compared:
|
||||
```bash
|
||||
<backup path>/<date>/* --> <storage path>/<hostname>/<date>/*
|
||||
```
|
||||
Then, each subdirectory is compared as described above.
|
||||
***
|
||||
|
||||
## Cleanup
|
||||
Backups cleanup occurs once every 24 hours. It is configured using two parameters:
|
||||
- `backupDays` – the number of past days (excluding the current day) for storing backups on the host machine.
|
||||
- `storageDays` – the number of past days (excluding the current day) for storing backups on external storage.
|
||||
|
||||
Example:
|
||||
```bash
|
||||
<backupPath>/2025-05-20/
|
||||
<backupPath>/2025-05-10/
|
||||
<backupPath>/2025-05-05/
|
||||
<backupPath>/2025-05-01/
|
||||
```
|
||||
|
||||
When `backupDays=2` and run `2025-05-20` will remain:
|
||||
```bash
|
||||
<backupPath>/2025-05-20/
|
||||
<backupPath>/2025-05-10/
|
||||
<backupPath>/2025-05-05/
|
||||
```
|
||||
|
||||
If `backupDays=2` and run after `2025-05-20` will remain:
|
||||
```bash
|
||||
<backupPath>/2025-05-20/
|
||||
<backupPath>/2025-05-10/
|
||||
```
|
||||
***
|
||||
|
||||
## LongTerm
|
||||
The `backupLongTermDays` parameter is responsible for the number of days for a long-term backup.
|
||||
When the corresponding command, the following happens
|
||||
1. The contents of the long-term backups directory (`<backupPath>/_longterm`) are checked.
|
||||
2. All copies older than `backupLongTermDays` except the earliest one are deleted.
|
||||
3. If there are no backups younger than `backupLongTermDays`, the first backup for the last day available is moved:
|
||||
```bash
|
||||
<backupPath>/<last day>/<backupFirst> --> <backupPath>/_longterm/<last day>/<backupFirst>
|
||||
```
|
||||
***
|
||||
|
||||
## Commands
|
||||
|
||||
### `-b, --backup [option]`
|
||||
Backup files and database of one site or all sites. Options:
|
||||
- `archive` - backup all domains to archives
|
||||
- `rsync` - backup all domains using rSync
|
||||
- `<domain>` - backup domain to archives (e.g., `example.com`)
|
||||
|
||||
> [!NOTE]
|
||||
> The default value (`archive` or `rsync`) is set by the `backupType` variable in the configuration file `config.sh`.
|
||||
|
||||
Example:
|
||||
```bash
|
||||
sudo kube.sh -b
|
||||
sudo kube.sh -b archive
|
||||
sudo kube.sh -b example.com
|
||||
```
|
||||
|
||||
Directory structure of backups: `<backupPath>/<date>/<marker>`
|
||||
- `<date>` - Date format `YYYY-MM-DD` (e.g., `2025-05-20`)
|
||||
- `<marker>` - First backup for day: `<backupFirst>` (`_first`), all next in time format `HH-MM-SS` (e.g., `09-30-55`)
|
||||
|
||||
Backup options:
|
||||
- `archive` - creates 2 archives and copies the `.conf` file for all sites + copies file `map.conf`:
|
||||
- `<backupPath>/<date>/<marker>/<domain*>.tar.gz`
|
||||
- `<backupPath>/<date>/<marker>/<domain*>.sql.tar.gz`
|
||||
- `<backupPath>/<date>/<marker>/<domain*>.conf`
|
||||
- `<backupPath>/<date>/<marker>/map.conf`
|
||||
- `rsync` - copies the `vhostsPath` virtual hosts folders, creates database archives and copied and database archives are created and copies `.conf` file for all sites + copies file `map.conf`:
|
||||
- `<backupPath>/<date>/<marker>/<domain*>`
|
||||
- `<backupPath>/<date>/<marker>/<domain*>.sql.tar.gz`
|
||||
- `<backupPath>/<date>/<marker>/<domain*>.conf`
|
||||
- `<backupPath>/<date>/<marker>/map.conf`
|
||||
- `domain` - creates 2 archives and copies the `.conf` file for the site:
|
||||
- `<backupPath>/<date>/<marker>/<domain>.tar.gz`
|
||||
- `<backupPath>/<date>/<marker>/<domain>.sql.tar.gz`
|
||||
- `<backupPath>/<date>/<marker>/<domain>.conf`
|
||||
|
||||
Example:
|
||||
```bash
|
||||
/backup/2025-05-21/08-00-00/example.com
|
||||
/backup/2025-05-21/08-00-00/example.com.conf
|
||||
/backup/2025-05-21/08-00-00/example.com.sql.tar.gz
|
||||
/backup/2025-05-21/_first/example.com
|
||||
/backup/2025-05-21/_first/example.com.conf
|
||||
/backup/2025-05-21/_first/example.com.sql.tar.gz
|
||||
/backup/2025-05-21/_first/map.conf
|
||||
/backup/2025-05-21/12-12-12/example.com.conf
|
||||
/backup/2025-05-20/12-12-12/example.com.tar.gz
|
||||
/backup/2025-05-20/12-12-12/example.com.sql.tar.gz
|
||||
```
|
||||
@@ -1,22 +0,0 @@
|
||||
[KUBE](../README.md) / CRON
|
||||
|
||||
# CRON
|
||||
|
||||
> Task management in CRON for scripts
|
||||
|
||||
## Commands
|
||||
|
||||
### `--cron <action>`
|
||||
|
||||
Working with CRON:
|
||||
- `add` - Adding jobs to CRON
|
||||
- `remove` - Removing jobs from CRON
|
||||
- `clean` - Clearing jobs of this script from CRON
|
||||
- `list` - Get list tasks for ROOT (default)
|
||||
|
||||
Example:
|
||||
```bash
|
||||
sudo kube.sh --cron
|
||||
sudo kube.sh --cron add
|
||||
sudo kube.sh --cron clean
|
||||
```
|
||||
@@ -1,21 +0,0 @@
|
||||
[KUBE](../README.md) / File structure
|
||||
|
||||
# File structure
|
||||
|
||||
- `assets/` - Supporting materials for infrastructure deployment
|
||||
- `assets/vhost.default/` - Template for vhosts without Wordpress (If there is)
|
||||
- `assets/vhost.wordpress/` - Template for vhosts with Wordpress (If there is)
|
||||
- `assets/yaml/*` - YAML templates for k3s
|
||||
- `assets/domains.list` - List of domains for Wordpress deployment
|
||||
- `assets/php.ini` - PHP settings file for OpenLiteSpeed
|
||||
- `assets/vhost.default.tar.gz` - Default packaged image of the site
|
||||
- `assets/vhost.wordpress.tar.gz` - Packaged Wordpress image
|
||||
- `assets/*.template` - Templates of files
|
||||
- `backups/` - Backup directory (default)
|
||||
- `data/` - Directory with service data for script
|
||||
- `docs/` - Directory with documents
|
||||
- `libs/` - Directory with function libraries
|
||||
- `logs/` - Directory with journals
|
||||
- `config.sh.default` - Settings script (example)
|
||||
- `kube.sh` - Executable script
|
||||
- `README.md` - Reference Guide
|
||||
@@ -1,14 +0,0 @@
|
||||
[KUBE](../README.md) / Install
|
||||
|
||||
# Install
|
||||
|
||||
> Scenarios for fully deploying the infrastructure for full operation. Install APK, update ipset/iptables, install kubernetes, create namespaces, apply yaml-files ...
|
||||
|
||||
## Commands
|
||||
|
||||
### `--install`
|
||||
|
||||
Example:
|
||||
```bash
|
||||
sudo kube.sh --install
|
||||
```
|
||||
@@ -1,44 +0,0 @@
|
||||
[KUBE](../README.md) / k3s
|
||||
|
||||
# k3s
|
||||
|
||||
## Resources
|
||||
|
||||
- [MariaDB](resources/mariadb.md)
|
||||
- [Redis](resources/redis.md)
|
||||
- [OpenLiteSpeed](resources/openlitespeed.md)
|
||||
- [Postfix](resources/postfix.md)
|
||||
- [Transfer](resources/transfer.md)
|
||||
|
||||
## Commands
|
||||
|
||||
### `-k, --k3s [option]`
|
||||
Options:
|
||||
- `create` - Create all resources
|
||||
- `clean` - Remove all resources
|
||||
- `status` - Status about a k3s resources
|
||||
- `info` - Detail about a k3s resources
|
||||
- `version` - Version info
|
||||
- `pod` - Get resources types of Pod
|
||||
- `pv` - Get resources types of PersistentVolume (PV)
|
||||
- `pvc` - Get resources types of PersistentVolumeClaim (PVC)
|
||||
- `service` - Get resources types of Service
|
||||
- `ing` - Get resources types of Ingress
|
||||
- `rs` - Get resources types of ReplicaSet
|
||||
- `sts` - Get resources types of StatefulSet
|
||||
- `deploy` - Get resources types of Deployment
|
||||
- `secret` - Get resources types of Secret
|
||||
- `cm` - Get resources types of ConfigMap
|
||||
- `ds` - Get resources types of DaemonSet
|
||||
- `job` - Get resources types of Job
|
||||
- `cj` - Get resources types of CronJob
|
||||
- `ep` - Get resources types of Endpoint
|
||||
- `nodes` - Get resources types of Node
|
||||
- `cs` - Get resources types of ComponentStatuses
|
||||
|
||||
Example:
|
||||
```bash
|
||||
sudo kube.sh -k status
|
||||
sudo kube.sh -k pod
|
||||
sudo kube.sh -k
|
||||
```
|
||||
@@ -1,38 +0,0 @@
|
||||
[KUBE](../README.md) / Log
|
||||
|
||||
# Log
|
||||
|
||||
> Opening the logs in the pods.
|
||||
|
||||
## Commands
|
||||
|
||||
### `--log`
|
||||
|
||||
> [!NOTE]
|
||||
> Standard kubectl parameters for logs can be added, for example:
|
||||
> `-f`: logs should be streamed<br>
|
||||
> `--previous`: print the logs for the previous instance of the container in a pod if it exists.
|
||||
|
||||
Example:
|
||||
```bash
|
||||
sudo kube.sh --log
|
||||
sudo kube.sh --log -f --tail=30
|
||||
```
|
||||
|
||||
Examples of responses:
|
||||
```bash
|
||||
🔹Log
|
||||
1: mariadb-master-0 [Running]
|
||||
2: mariadb-slave-0 [Running]
|
||||
3: openlitespeed-0 [Running]
|
||||
4: openlitespeed-1 [Running]
|
||||
5: postfix-78c47b95f6-42jcq [Running]
|
||||
6: redis-0 [Running]
|
||||
7: redis-1 [Running]
|
||||
8: redis-2 [Running]
|
||||
9: redis-sentinel-0 [Running]
|
||||
10: redis-sentinel-1 [Running]
|
||||
11: redis-sentinel-2 [Running]
|
||||
12: worker-6cd4bdb6b8-c6f5d [Running]
|
||||
Specify the pod number [0]:
|
||||
```
|
||||
@@ -1,286 +0,0 @@
|
||||
[KUBE](../README.md) / Mail server
|
||||
|
||||
# Mail server
|
||||
|
||||
## Template of zone
|
||||
```zone
|
||||
$TTL 300
|
||||
@ IN SOA ns1.mydns.example. dnsadmin.mydomain.com. (
|
||||
2025091001 ; serial
|
||||
3600 ; refresh
|
||||
900 ; retry
|
||||
1209600 ; expire
|
||||
300 ; minimum
|
||||
)
|
||||
IN NS ns1.mydns.example.
|
||||
IN NS ns2.mydns.example.
|
||||
|
||||
; ===== A/AAAA =====
|
||||
mta IN A {{PUBLIC_IPV4}}
|
||||
; mta IN AAAA {{PUBLIC_IPV6}} ; if available
|
||||
|
||||
; if desired, client sites can resolve to the same IP
|
||||
{{US1}} IN A {{PUBLIC_IPV4}}
|
||||
{{US2}} IN A {{PUBLIC_IPV4}}
|
||||
{{US3}} IN A {{PUBLIC_IPV4}}
|
||||
|
||||
; ===== MX =====
|
||||
; @ IN MX 10 mta.{{ROOT_DOMAIN}}. ; the root domain also accepts mail, if needed
|
||||
{{US1}} IN MX 10 mta.{{ROOT_DOMAIN}}.
|
||||
{{US2}} IN MX 10 mta.{{ROOT_DOMAIN}}.
|
||||
{{US3}} IN MX 10 mta.{{ROOT_DOMAIN}}.
|
||||
|
||||
; ===== SPF =====
|
||||
; @ IN TXT "v=spf1 mx ~all" ; if available
|
||||
mta IN TXT "v=spf1 a -all"
|
||||
{{US1}} IN TXT "v=spf1 mx ~all"
|
||||
{{US2}} IN TXT "v=spf1 mx ~all"
|
||||
{{US3}} IN TXT "v=spf1 mx ~all"
|
||||
|
||||
; ===== DKIM =====
|
||||
; For each customer domain, publish its own public key.
|
||||
; The selector can be shared (e.g., selector1); keys are different.
|
||||
selector1._domainkey.{{US1}} IN TXT "v=DKIM1; k=rsa; p={{PUBKEY_US1}}"
|
||||
selector1._domainkey.{{US2}} IN TXT "v=DKIM1; k=rsa; p={{PUBKEY_US2}}"
|
||||
selector1._domainkey.{{US3}} IN TXT "v=DKIM1; k=rsa; p={{PUBKEY_US3}}"
|
||||
|
||||
; ===== DMARC =====
|
||||
; Initially p=none to collect reports without impacting delivery.
|
||||
_dmarc.{{US1}} IN TXT "v=DMARC1; p=none; adkim=r; aspf=r; rua=mailto:{{DMARC_AGG}}; ruf=mailto:{{DMARC_FOR}}"
|
||||
_dmarc.{{US2}} IN TXT "v=DMARC1; p=none; adkim=r; aspf=r; rua=mailto:{{DMARC_AGG}}; ruf=mailto:{{DMARC_FOR}}"
|
||||
_dmarc.{{US3}} IN TXT "v=DMARC1; p=none; adkim=r; aspf=r; rua=mailto:{{DMARC_AGG}}; ruf=mailto:{{DMARC_FOR}}"
|
||||
; It is recommended to set DMARC on the root domain to cover ALL subdomains with a single policy.
|
||||
; _dmarc IN TXT "v=DMARC1; p=quarantine; sp=quarantine; adkim=r; aspf=r; rua=mailto:{{DMARC_AGG}}; ruf=mailto:{{DMARC_FOR}}"
|
||||
; (if test mode first — replace p=none, sp=none)
|
||||
|
||||
; ===== Additionally (optional but useful) =====
|
||||
; MTA-STS (if to implement)
|
||||
;_mta-sts IN TXT "v=STSv1; id=2025-09-10"
|
||||
;_smtp._tls IN TXT "v=TLSRPTv1; rua=mailto:tlsrpt@{{ROOT_DOMAIN}}"
|
||||
;autoconfig IN CNAME autoconfig.mailhost.example. ; for client autoconfiguration
|
||||
;autodiscover IN CNAME autodiscover.mailhost.example.
|
||||
```
|
||||
|
||||
```zone
|
||||
; ===== PTR =====
|
||||
{{PUBLIC_IPV4}} → mta.{{ROOT_DOMAIN}}
|
||||
```
|
||||
|
||||
Check: Postfix HELO/EHLO = mta.`{{ROOT_DOMAIN}}`
|
||||
|
||||
## Example
|
||||
`{{ROOT_DOMAIN}}` → krumax.cz \
|
||||
`{{PUBLIC_IPV4}}` → 31.31.73.67 \
|
||||
`{{US1}}`/`{{US2}}`/`{{US3}}` → us1 / us2 / us3 \
|
||||
`{{PUBKEY_US1}}`/`{{PUBKEY_US2}}`/`{{PUBKEY_US3}}` → DKIM public keys (only the content after `p=`, in a single line) \
|
||||
`{{DMARC_AGG}}`/`{{DMARC_FOR}}` → Addresses for DMARC reports (for example, dmarc@krumax.cz)
|
||||
|
||||
```zone
|
||||
$TTL 300
|
||||
|
||||
; ===== A =====
|
||||
mta IN A 31.31.73.67
|
||||
us1 IN A 31.31.73.67
|
||||
us2 IN A 31.31.73.67
|
||||
us3 IN A 31.31.73.67
|
||||
|
||||
; ===== MX =====
|
||||
us1 IN MX 10 mta.krumax.cz.
|
||||
us2 IN MX 10 mta.krumax.cz.
|
||||
us3 IN MX 10 mta.krumax.cz.
|
||||
|
||||
; ===== SPF =====
|
||||
mta IN TXT "v=spf1 a -all"
|
||||
us1 IN TXT "v=spf1 mx ~all"
|
||||
us2 IN TXT "v=spf1 mx ~all"
|
||||
us3 IN TXT "v=spf1 mx ~all"
|
||||
|
||||
; ===== DKIM =====
|
||||
selector1._domainkey.us1 IN TXT "v=DKIM1; k=rsa; p=MIIBI...(us1)"
|
||||
selector1._domainkey.us2 IN TXT "v=DKIM1; k=rsa; p=MIIBI...(us2)"
|
||||
selector1._domainkey.us3 IN TXT "v=DKIM1; k=rsa; p=MIIBI...(us3)"
|
||||
|
||||
; ===== DMARC =====
|
||||
_dmarc.us1 IN TXT "v=DMARC1; p=none; adkim=r; aspf=r; rua=mailto:dmarc@krumax.cz; ruf=mailto:dmarc@krumax.cz"
|
||||
_dmarc.us2 IN TXT "v=DMARC1; p=none; adkim=r; aspf=r; rua=mailto:dmarc@krumax.cz; ruf=mailto:dmarc@krumax.cz"
|
||||
_dmarc.us3 IN TXT "v=DMARC1; p=none; adkim=r; aspf=r; rua=mailto:dmarc@krumax.cz; ruf=mailto:dmarc@krumax.cz"
|
||||
```
|
||||
|
||||
```zone
|
||||
; ===== PTR =====
|
||||
31.31.73.67 → mta.krumax.cz
|
||||
```
|
||||
|
||||
|
||||
## Example of adding a new domain in Postfix
|
||||
1. Adding the domain and generating DKIM
|
||||
```bash
|
||||
sudo kube.sh postfix add us2.krumax.cz
|
||||
```
|
||||
2. Retrieving DKIM
|
||||
```bash
|
||||
sudo kube.sh postfix dkim us2.krumax.cz
|
||||
```
|
||||
3. Adding DNS records:
|
||||
```zone
|
||||
us2 IN A 31.31.73.67
|
||||
us2 IN MX 10 mta.krumax.cz.
|
||||
selector1._domainkey.us2 IN TXT "v=DKIM1; k=rsa; p=MIIBI...(from step 2)"
|
||||
_dmarc.us2 IN TXT "v=DMARC1; p=none; adkim=r; aspf=r; rua=mailto:dmarc@krumax.cz; ruf=mailto:dmarc@krumax.cz"
|
||||
```
|
||||
|
||||
|
||||
## Send mail in PHP.
|
||||
Create file for test send mail `send-mail.php`
|
||||
```php
|
||||
<?
|
||||
mb_internal_encoding('UTF-8');
|
||||
$to = 'maksym.krugol@wedos.org';
|
||||
$toName = 'Maksym Krugol';
|
||||
$from = 'no-reply@us1.krumax.cz';
|
||||
$fromName = 'Support team US1';
|
||||
$return = 'bounce@us1.krumax.cz';
|
||||
$subject = mb_encode_mimeheader('Test delivery (PHP)', 'UTF-8', 'B', "\r\n");
|
||||
$bodyText = "Hi! Test with PHP.";
|
||||
$bodyQP = quoted_printable_encode($bodyText);
|
||||
$headers = [
|
||||
"From: $fromName <$from>",
|
||||
"Reply-To: $from",
|
||||
"Return-Path: $return",
|
||||
"Date: " . date('r'),
|
||||
"Message-ID: <" . time() . "." . bin2hex(random_bytes(6)) . "@" . $hostname . ">",
|
||||
"MIME-Version: 1.0",
|
||||
"Content-Type: text/plain; charset=UTF-8",
|
||||
"Content-Transfer-Encoding: quoted-printable",
|
||||
"List-Unsubscribe: <mailto:unsubscribe@us1.krumax.cz?subject=unsubscribe>, <https://us1.krumax.cz/unsubscribe/".rawurlencode('maksym.krugol@wedos.org').">",
|
||||
];
|
||||
$headersStr = implode("\r\n", $headers);
|
||||
|
||||
$status = mail("$toName <$to>", $subject, $bodyQP, $headersStr, "-f$return");
|
||||
echo $status ? "Success\n" : "Failed\n";
|
||||
```
|
||||
|
||||
|
||||
## Send mail in Wordpress.
|
||||
1. Add Wordpress plugin `/wp-content/mu-plugins/smtp.php`
|
||||
```php
|
||||
<?php
|
||||
// For 25 port (without TLS/login)
|
||||
add_action('phpmailer_init', function ($phpmailer) {
|
||||
$phpmailer->isSMTP();
|
||||
$phpmailer->XMailer = 'krumaxMailer 1.0';
|
||||
$phpmailer->Host = 'mta.krumax.cz';
|
||||
$phpmailer->Port = 25;
|
||||
$phpmailer->SMTPAuth = false;
|
||||
$phpmailer->SMTPSecure = '';
|
||||
$phpmailer->SMTPAutoTLS = false;
|
||||
$phpmailer->From = 'no-reply@us1.krumax.cz';
|
||||
// $phpmailer->FromName = 'Support team US1';
|
||||
// $phpmailer->Hostname = 'us1.krumax.cz';
|
||||
// $phpmailer->Encoding = 'quoted-printable';
|
||||
// $phpmailer->Sender = 'bounce@us1.krumax.cz';
|
||||
// $phpmailer->Timeout = 15;
|
||||
});
|
||||
```
|
||||
```php
|
||||
<?php
|
||||
// For 587 port (with TLS/login)
|
||||
add_action('phpmailer_init', function ($phpmailer) {
|
||||
$phpmailer->isSMTP();
|
||||
$phpmailer->XMailer = 'krumaxMailer 1.0';
|
||||
$phpmailer->Host = 'mta.krumax.cz';
|
||||
$phpmailer->Port = 587;
|
||||
$phpmailer->Username = 'postmaster@us1.krumax.cz';
|
||||
$phpmailer->Password = 'qwerty';
|
||||
$phpmailer->SMTPAuth = true;
|
||||
$phpmailer->SMTPSecure = 'tls';
|
||||
$phpmailer->SMTPAutoTLS = true;
|
||||
$phpmailer->SMTPOptions = [
|
||||
'ssl' => [
|
||||
'allow_self_signed' => true,
|
||||
],
|
||||
];
|
||||
$phpmailer->From = 'no-reply@us1.krumax.cz';
|
||||
// $phpmailer->FromName = 'Support team US1';
|
||||
// $phpmailer->Hostname = 'us1.krumax.cz';
|
||||
// $phpmailer->Encoding = 'quoted-printable';
|
||||
// $phpmailer->Sender = 'bounce@us1.krumax.cz';
|
||||
// $phpmailer->Timeout = 15;
|
||||
});
|
||||
```
|
||||
2. Create file for test send mail `/send-mail.php`
|
||||
```php
|
||||
<?php
|
||||
require_once 'wp-load.php';
|
||||
|
||||
$domain = "us1.krumax.cz";
|
||||
$to = "maksym.krugol@wedos.org";
|
||||
$toName = "Maksym Krugol";
|
||||
$subject = "Test delivery (Wordpress)";
|
||||
$message = "Hi! Test with Wordpress.";
|
||||
$headers = [
|
||||
"List-Unsubscribe: <mailto:unsubscribe@{$domain}?subject=unsubscribe>, <https://{$domain}/unsubscribe/{$to}>"
|
||||
];
|
||||
|
||||
if (wp_mail("$toName <{$to}>", $subject, $message, $headers)) {
|
||||
echo "Success";
|
||||
} else {
|
||||
echo "Failed";
|
||||
}
|
||||
```
|
||||
3. Open URL http://us1.krumax.cz/send-mail.php
|
||||
|
||||
|
||||
## Send mail from pod in k3s (use Postfix).
|
||||
1. Install postfix: `apt-get install -y postfix`
|
||||
2. Set config:
|
||||
```bash
|
||||
postconf -e 'myhostname = mta.krumax.cz'
|
||||
postconf -e 'mydomain = us1.krumax.cz'
|
||||
postconf -e 'myorigin = $mydomain'
|
||||
```
|
||||
3. Create file `test.mail`:
|
||||
```
|
||||
From: Support team US1 <no-reply@us1.krumax.cz>
|
||||
To: Maksym Krugol <maksym.krugol@wedos.org>
|
||||
Subject: Test delivery (postfix)
|
||||
Date: Wed, 17 Sep 2025 10:53:13 +0200
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
Hi! Test with /usr/sbin/sendmail.
|
||||
```
|
||||
4. Send mail: `sendmail -v -oi -t -f 'no-reply@us1.krumax.cz' < test.mail`
|
||||
|
||||
## Send mail from pod in k3s (use msmtp).
|
||||
1. Install msmtp: `apt-get install -y msmtp msmtp-mta ca-certificates`
|
||||
2. Set config `/etc/msmtprc`:
|
||||
```
|
||||
defaults
|
||||
auth on
|
||||
tls on
|
||||
tls_starttls on
|
||||
tls_trust_file /etc/ssl/certs/ca-certificates.crt
|
||||
logfile /var/log/msmtp.log
|
||||
|
||||
account default
|
||||
host mta.krumax.cz
|
||||
port 587
|
||||
from no-reply@us1.krumax.cz
|
||||
user postmaster@us1.krumax.cz
|
||||
password qwerty
|
||||
```
|
||||
3. Create file `test.mail`:
|
||||
```
|
||||
From: Support team US1 <no-reply@us1.krumax.cz>
|
||||
To: Maksym Krugol <maksym.krugol@wedos.org>
|
||||
Subject: Test delivery (msmtp)
|
||||
Date: Wed, 17 Sep 2025 10:53:13 +0200
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
Hi! Test with msmtp/sendmail.
|
||||
```
|
||||
4. Send mail: `sendmail -v -oi -t -f 'no-reply@us1.krumax.cz' < test.mail`
|
||||
@@ -1,47 +0,0 @@
|
||||
|
||||
|
||||
|
||||
- [Getting started](#getting-started)
|
||||
- [Site Creation](#site-creation)
|
||||
- [Wordpress configuration](#wordpress-configuration)
|
||||
- [Transfer](docs/transfer.md)
|
||||
|
||||
> [!NOTE]
|
||||
> For `rSync` to work, the password must be in a file, with permissions `600` and owner `root`.
|
||||
|
||||
|
||||
***
|
||||
### `--info [resource]`
|
||||
|
||||
Extensive information on the status of various resources is provided:
|
||||
- `[all]` - Info from all resources
|
||||
- `k3s` - Info from all resources `k3s`
|
||||
- `mariadb` - Info from `MariaDB` (master-slave replica)
|
||||
- `redis` - Info from `Redis` and `RedisSentinel`
|
||||
- `openlitespeed` - Info from `OpenLiteSpeed`
|
||||
|
||||
Example:
|
||||
```bash
|
||||
sudo kube.sh --info
|
||||
sudo kube.sh --info all
|
||||
sudo kube.sh --info mariadb
|
||||
```
|
||||
|
||||
|
||||
|
||||
---
|
||||
***
|
||||
### `--version`
|
||||
|
||||
Collecting information about version system components:
|
||||
|
||||
- Kubernetes (k3s)
|
||||
- MariaDB
|
||||
- Redis
|
||||
- OpenLiteSpeed
|
||||
|
||||
Example:
|
||||
```bash
|
||||
sudo kube.sh --version
|
||||
```
|
||||
---
|
||||
@@ -1,129 +0,0 @@
|
||||
[KUBE](../README.md) / Options
|
||||
|
||||
# Options
|
||||
|
||||
>Options in script `config.sh`
|
||||
|
||||
- `assetsPath` - Directory assets (for script)
|
||||
- `dataPath` - Directory data (for script)
|
||||
- `namespace` - Default kubernetes namespace
|
||||
- `hostname` - Hostname server
|
||||
- `basePath` - Base path for all resources
|
||||
- `olsVhostsPath` - Directory for vhosts
|
||||
- `domainsList` - File with domains list
|
||||
***
|
||||
|
||||
- `k3sCmd` - Path to k3s on host
|
||||
- `k3sReadyRetries` - Number of attempts to check k3s readiness
|
||||
- `k3sReadySleep` - Pause between attempts
|
||||
***
|
||||
|
||||
- `redisKube` - Redis identifier in k3s
|
||||
- `redisSentinelKube` - Redis Sentinel identifier in k3s
|
||||
- `redisYaml` - Path to file template YAML for Redis
|
||||
- `redisPath` - Directory for Redis configuration
|
||||
- `redisFileUsersAcl` - Filename for users in Redis
|
||||
- `redisRootPass` - Redis password
|
||||
***
|
||||
|
||||
- `mariadbKube` - MariaDB identifier in k3s
|
||||
- `mariadbMasterKube` - MariaDB Master node identifier in k3s
|
||||
- `mariadbSlaveKube` - MariaDB Slave node identifier in k3s
|
||||
- `mariadbYaml` - Path to file template YAML for MariaDB
|
||||
- `mariadbMasterPath` - Directory for MariaDB Master node (replica)
|
||||
- `mariadbSlavePath` - Directory for MariaDB Slave node (replica)
|
||||
- `mariadbRootPass` - MariaDB root password
|
||||
***
|
||||
|
||||
- `olsKube` - Openlitespeed identifier in k3s
|
||||
- `olsYaml` - Path to file template YAML for OpenLiteSpeed
|
||||
- `olsPath` - Directory for OpenLiteSpeed configuration
|
||||
- `olsAdminPath` - Directory for OpenLiteSpeed Admin configuration
|
||||
- `olsConfigFile` - OpenLiteSpeed configuration file
|
||||
- `olsVhostsConfigPath` - Directory for vhosts configuration files
|
||||
- `olsAdminPass` - OpenLiteSpeed admin panel password
|
||||
***
|
||||
|
||||
- `postfixKube` - Postfix identifier in k3s
|
||||
- `postfixYaml` - Path to file template YAML for Postfix
|
||||
- `postfixPath` - Directory for Postfix
|
||||
- `postfixConfigPath` - Directory for Postfix configuration
|
||||
- `postfixDkimPath` - Directory for Postfix DKIM
|
||||
- `postfixTlsCrtFile` - File TLS certificate (if not specified, a self-signed one will be generated)
|
||||
- `postfixTlsKey` - Fil TLS key (if not specified, a self-signed one will be generated)
|
||||
- `postfixHost` - Host for MTA
|
||||
- `postfixPostmaster` - Postmaster (email) for MTA
|
||||
- `postfixDefaultRealm` - Default realm for MTA
|
||||
- `postfixDkimSelector` - Selector DKIM
|
||||
- `postfixDomainsFile` - File of Domains list
|
||||
- `postfixAliasesFile` - File of Aliases list
|
||||
- `postfixSendersFile` - File of Senders list
|
||||
- `postfixIp` - IP address for MTA
|
||||
***
|
||||
|
||||
- `workerKube` - Worker identifier in k3s
|
||||
- `workerYaml` - Path to file template YAML for Worker
|
||||
- `workerPath` - Directory for Worker
|
||||
- `workerAgentPath` - Path to directory with agent script
|
||||
- `workerTasksPath` - Path to directory with tasks-files
|
||||
- `workerFilesPath` - Path to directory with loading files
|
||||
- `workerName` - Worker name
|
||||
- `workerApiUrl` - URL to API
|
||||
- `workerApiGettingPause` - Pause between requests for a new task
|
||||
- `workerApiSendingPause` - Pause between sending task statuses
|
||||
***
|
||||
|
||||
- `logPath` - Directory journals (for script)
|
||||
- `logFile` - Log file name format
|
||||
***
|
||||
|
||||
- `backupPath` - Directory for backups on current host
|
||||
- `backupLogPath` - Directory for backups logs
|
||||
- `backupType` - Backup Type (rsync, archive)
|
||||
- `backupFirst` - Directory name for the first backup of the day
|
||||
- `backupDays` - Number of last days of backup storage (excluding current day backups)
|
||||
- `backupMask` - A mask for selecting backup storage day directories (must match `scriptDate`)
|
||||
- `backupLongTermPath` - Directory for long-term backups
|
||||
- `backupLongTermDays` - Minimum number of days for a long-term backup.
|
||||
- `backupExcludeFile` - List of files and directories that were excluded during backup
|
||||
- `backupExcludeList` - List of files and directories that should be excluded during backup
|
||||
- `backupFileSizeMin` - Minimum archive size during verification (bytes)
|
||||
- `backupElapsedMax` - Maximum time elapsed since the last backup was created (min)
|
||||
***
|
||||
|
||||
- `storagePath` - Directory for backups on external storage
|
||||
- `storageType` - Directory external storage (rsync, ssh)
|
||||
- `storageDays` - Number of last days of backup storage on external storage (excluding current day backups)
|
||||
***
|
||||
|
||||
- `rsyncUri` - URI (format: `user@server[:port][/path]`) (for rSync)
|
||||
- `rsyncPassFile` - File with password (for rSync)
|
||||
- `rsyncPath` - Directory for backups on external storage (for rSync)
|
||||
***
|
||||
|
||||
- `ftpHost` - Hostname (for FTP)
|
||||
- `ftpPort` - Port (for FTP)
|
||||
- `ftpUser` - Username (for FTP)
|
||||
- `ftpPass` - Password (for FTP)
|
||||
- `ftpPath` - Directory for backups on external storage (for FTP)
|
||||
***
|
||||
|
||||
- `sshHost` - Hostname (for SSH)
|
||||
- `sshUser` - Username (for SSH)
|
||||
- `sshKeyFile` - Public key file (for SSH)
|
||||
- `sshPath` - Directory for backups on external storage (for SSH)
|
||||
***
|
||||
|
||||
- `reportFile` - Filename for reports
|
||||
- `reportMask` - Report filename mask
|
||||
***
|
||||
|
||||
- `mailTo` - Email for sending reports (to)
|
||||
- `mailFrom` - Email for sending reports (from)
|
||||
***
|
||||
|
||||
- `cronJobs` - Jobs for adding to CRON
|
||||
***
|
||||
|
||||
- `diskUsedSpaceLimit` - Limiting the disk space used (%)
|
||||
***
|
||||
@@ -1,167 +0,0 @@
|
||||
[KUBE](../../README.md) / [k3s](../k3s.md) / MariaDB
|
||||
|
||||
# Resource MariaDB
|
||||
|
||||
> [!NOTE]
|
||||
> A replica of two pods `mariadb-master` and `mariadb-slave` is created. Each of the pods has a separate storage. When configuring the connection, the host is specified: `mariadb-master`.
|
||||
|
||||
- [Install](#install)
|
||||
- [Remove](#remove)
|
||||
- [Status](#status)
|
||||
- [Info](#info)
|
||||
- [Version](#version)
|
||||
- [Database list](#database-list)
|
||||
- [User list](#user-list)
|
||||
- [Dump](#dump)
|
||||
***
|
||||
|
||||
## Install
|
||||
|
||||
### Processing
|
||||
1. Preparation.
|
||||
- Recreate the secret with the passwords `root-password` and `replication-password`.
|
||||
|
||||
2. Creation.
|
||||
- Prepare the import file.
|
||||
- Import the YAML.
|
||||
|
||||
3. Post-processing.
|
||||
- Initialize the replica.
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --mariadb install
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[K3S] Resource add | mariadb
|
||||
✅[MariaDB] | Delete old Secret: OK
|
||||
✅[MariaDB] | Create new Secret: OK
|
||||
🔹[MariaDB] Preparing /app/kube/assets/yaml/mariadb.yaml
|
||||
🔹[K3S] Applying YAML /app/kube/data/yaml/mariadb.yaml
|
||||
💡[K3S] Waiting for pods to be ready... | 2 not ready
|
||||
💡[K3S] Waiting for pods to be ready... | 1 not ready
|
||||
✅[MariaDB] Applied /app/kube/data/yaml/mariadb.yaml
|
||||
🔹[MariaDB] Replica initialization...
|
||||
✅[MariaDB] Master node | Create replication user: OK
|
||||
✅[MariaDB] Master node | Status: OK
|
||||
✅[MariaDB] Slave node | Change master: OK
|
||||
✅[MariaDB] Slave node | Status: OK | Delay 0s
|
||||
✅[MariaDB] Replica initialization completed successfully
|
||||
```
|
||||
***
|
||||
|
||||
## Remove
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --mariadb remove
|
||||
```
|
||||
***
|
||||
|
||||
## Status
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --mariadb status
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[MariaDB] Status
|
||||
✅[MariaDB] Databases: 10 | Users: 10 | Size: 1.234 Gb
|
||||
✅[MariaDB] Master node | Running
|
||||
✅[MariaDB] Slave node | Running
|
||||
```
|
||||
***
|
||||
|
||||
## Info
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --mariadb info
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[MariaDB] Info
|
||||
🔹[MariaDB] Master node
|
||||
mysql-bin.000025 10341
|
||||
🔹[MariaDB] Slave node
|
||||
*************************** 1. row ***************************
|
||||
Slave_IO_State: Waiting for master to send event
|
||||
Master_Host: mariadb-master
|
||||
Master_User: replication_user
|
||||
Master_Port: 3306
|
||||
...
|
||||
```
|
||||
***
|
||||
|
||||
## Version
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --mariadb version
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[MariaDB] Version
|
||||
✅[MariaDB] Master node | mariadb from 11.7.2-MariaDB, client 15.2 for debian-linux-gnu (x86_64) using EditLine wrapper
|
||||
✅[MariaDB] Slave node | mariadb from 11.7.2-MariaDB, client 15.2 for debian-linux-gnu (x86_64) using EditLine wrapper
|
||||
```
|
||||
***
|
||||
|
||||
## Database list
|
||||
|
||||
> [!NOTE]
|
||||
> List of databases (except for service databases: `information_schema`, `performance_schema`, `mysql`, `sys`)
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --mariadb database
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[MariaDB] Database list
|
||||
us1_example_com
|
||||
us2_example_com
|
||||
us3_example_com
|
||||
```
|
||||
***
|
||||
|
||||
## User list
|
||||
|
||||
> [!NOTE]
|
||||
> List of users (except for service users `root`, `replication_user`)
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --mariadb user
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[MariaDB] User list
|
||||
us1_example_com
|
||||
us2_example_com
|
||||
us3_example_com
|
||||
```
|
||||
***
|
||||
|
||||
## Dump
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --mariadb dump
|
||||
sudo kube.sh --mariadb dump dump.sql
|
||||
sudo kube.sh --mariadb dump dump.sql us1_example_com
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[MariaDB] Dump
|
||||
✅[MariaDB] Dump: /app/kube/data/mariadb/2025-10-27_10-10-58.sql.tar.gz
|
||||
```
|
||||
@@ -1,179 +0,0 @@
|
||||
[KUBE](../../README.md) / [k3s](../k3s.md) / OpenLiteSpeed
|
||||
|
||||
# Resource OpenLiteSpeed
|
||||
|
||||
> [!NOTE]
|
||||
> Two (you can specify a different number) pods are created. They work simultaneously. If one pod fails, the second pod starts processing all requests until a replacement for the failed pod is brought up. When changes are made to the virtual host configuration, OpenLiteSpeed is restarted sequentially in all pods.
|
||||
>
|
||||
> The administration panel OpenLiteSpeed is available at an address:
|
||||
> - `<domain>:31080` (local and remote)
|
||||
> - `<server ip>:31080` (remote)
|
||||
> - `<node ip>:7080` (local, `<node ip>` can be found upon request: `kube.sh --info | grep 7080`)
|
||||
|
||||
- [Install](#install)
|
||||
- [Remove](#remove)
|
||||
- [Status](#status)
|
||||
- [Info](#info)
|
||||
- [Version](#version)
|
||||
- [Restart](#restart)
|
||||
- [Site list](#site-list)
|
||||
- [Site unlock](#site-unlock)
|
||||
- [Site lock](#site-lock)
|
||||
- [Config](#config-test)
|
||||
***
|
||||
|
||||
## Install
|
||||
|
||||
### Processing
|
||||
1. Creation.
|
||||
- Prepare the import file.
|
||||
- Import the YAML.
|
||||
|
||||
2. Post-processing.
|
||||
- Initialization.
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --ols install
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[OpenLiteSpeed] Install
|
||||
🔹[OpenLiteSpeed] Preparing /app/kube/assets/yaml/openlitespeed.yaml
|
||||
🔹[K3S] Applying YAML /app/kube/data/yaml/openlitespeed.yaml
|
||||
💡[K3S] Waiting for pods to be ready... | 1 not ready
|
||||
💡[K3S] Waiting for pods to be ready... | 1 not ready
|
||||
✅[OpenLiteSpeed] Applied /app/kube/data/yaml/openlitespeed.yaml
|
||||
🔹[OpenLiteSpeed] Initialization...
|
||||
🔹[OpenLiteSpeed] Authorization parameters updated
|
||||
service/traefik patched
|
||||
🔹[OpenLiteSpeed] Pod: openlitespeed-0 | Restart...
|
||||
🔹[OpenLiteSpeed] Pod: openlitespeed-1 | Restart...
|
||||
✅[OpenLiteSpeed] Initialization completed successfully
|
||||
```
|
||||
***
|
||||
|
||||
## Remove
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --ols remove
|
||||
```
|
||||
***
|
||||
|
||||
## Status
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --ols status
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[OpenLiteSpeed] Status
|
||||
✅[OpenLiteSpeed] openlitespeed-0 | Running | PID 251
|
||||
✅[OpenLiteSpeed] openlitespeed-1 | Running | PID 216
|
||||
```
|
||||
***
|
||||
|
||||
## Info
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --ols info
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[OpenLiteSpeed] Info
|
||||
🔹[OpenLiteSpeed] openlitespeed-0
|
||||
litespeed is running with PID 251.
|
||||
🔹[OpenLiteSpeed] openlitespeed-1
|
||||
litespeed is running with PID 216.
|
||||
```
|
||||
***
|
||||
|
||||
## Version
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --ols version
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[OpenLiteSpeed] Version
|
||||
✅[OpenLiteSpeed] openlitespeed-0 | LiteSpeed/1.8.3 Open (BUILD built: Fri Feb 28 16:33:02 UTC 2025)
|
||||
✅[OpenLiteSpeed] openlitespeed-0 | PHP: 8.3.17
|
||||
✅[OpenLiteSpeed] openlitespeed-1 | LiteSpeed/1.8.3 Open (BUILD built: Fri Feb 28 16:33:02 UTC 2025)
|
||||
✅[OpenLiteSpeed] openlitespeed-1 | PHP: 8.3.17
|
||||
```
|
||||
***
|
||||
|
||||
## Restart
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --ols restart
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[OpenLiteSpeed] Restart
|
||||
🔹[OpenLiteSpeed] Pod: openlitespeed-0 | Restart...
|
||||
🔹[OpenLiteSpeed] Pod: openlitespeed-1 | Restart...
|
||||
```
|
||||
***
|
||||
|
||||
## Site list
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --ols list [option]
|
||||
```
|
||||
|
||||
Options:
|
||||
- all - All sites (colored up/down) (default)
|
||||
- all - All sites
|
||||
- up - Unlocked sites
|
||||
- down - Locked sites
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
example1.com
|
||||
example2.com
|
||||
example3.com
|
||||
```
|
||||
***
|
||||
|
||||
## Site unlock
|
||||
|
||||
> [!NOTE]
|
||||
> Unlock domain (Resume site operation in OpenLiteSpeed).
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --ols up <domain>
|
||||
```
|
||||
***
|
||||
|
||||
## Site lock
|
||||
|
||||
> [!NOTE]
|
||||
> Lock domain (Pause the site in OpenLiteSpeed).
|
||||
>
|
||||
> The OpenLiteSpeed page will be displayed with a 403 error when the domain is accessed.
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --ols down <domain>
|
||||
```
|
||||
***
|
||||
|
||||
## Config test
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --ols config
|
||||
```
|
||||
@@ -1,121 +0,0 @@
|
||||
[KUBE](../../README.md) / [k3s](../k3s.md) / Postfix
|
||||
|
||||
# Resource Postfix
|
||||
|
||||
- [Install](#install)
|
||||
- [Remove](#remove)
|
||||
- [Status](#status)
|
||||
- [Add domain](#add-domain-)
|
||||
- [Get DKIM key](#get-dkim-key-for-dns-record-domain-or-dkim-record-lists)
|
||||
- [Check DNS records](#check-dns-records-for--or-mta)
|
||||
- [Get template of DNS records](#get-template-of-dns-records)
|
||||
***
|
||||
|
||||
## Install
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --postfix install
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[Postfix] Install
|
||||
🔹[Postfix] Preparing /app/kube/assets/yaml/postfix.yaml
|
||||
🔹[K3S] Applying YAML /app/kube/data/yaml/postfix.yaml
|
||||
💡[K3S] Waiting for pods to be ready... | 1 not ready
|
||||
💡[K3S] Waiting for pods to be ready... | 1 not ready
|
||||
💡[K3S] Waiting for pods to be ready... | 1 not ready
|
||||
💡[K3S] Waiting for pods to be ready... | 1 not ready
|
||||
✅[Postfix] Applied /app/kube/data/yaml/postfix.yaml
|
||||
```
|
||||
***
|
||||
|
||||
## Remove
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --postfix remove
|
||||
```
|
||||
***
|
||||
|
||||
## Status
|
||||
|
||||
> [!NOTE]
|
||||
> In progress...
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --postfix status
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
???
|
||||
```
|
||||
***
|
||||
|
||||
## Add domain <domain>
|
||||
|
||||
> [!NOTE]
|
||||
> In progress...
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --postfix add <domain>
|
||||
```
|
||||
***
|
||||
|
||||
## Get DKIM key for DNS record domain or DKIM record lists
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo ube.sh --postfix dkim [domain]
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
[Postfix] DKIM key for DNS
|
||||
selector1._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; s=email; "
|
||||
"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6rCyqEvQ1...FQIDAQAB" ) ; ----- DKIM key selector1 for krumax.cz
|
||||
```
|
||||
***
|
||||
|
||||
## Check DNS records for <domain> or MTA
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --postfix dns [domain]
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[Postfix] Check DNS records
|
||||
✅[Postfix] A record: mta.krumax.cz | 31.31.73.67
|
||||
✅[Postfix] SPF record: mta.krumax.cz | "v=spf1 a -all"
|
||||
✅[Postfix] PTR record: mta.krumax.cz | mta.krumax.cz.
|
||||
```
|
||||
***
|
||||
|
||||
## Get template of DNS records
|
||||
|
||||
Command:
|
||||
```
|
||||
sudo kube.sh --postfix template
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[Postfix] Template
|
||||
🔹[Postfix] For MTA ==============
|
||||
🔹[Postfix] IN A 31.31.73.67
|
||||
🔹[Postfix] IN TXT v=spf1 a -all
|
||||
🔹[Postfix] PTR -> mta.krumax.cz
|
||||
|
||||
🔹[Postfix] For site =============
|
||||
🔹[Postfix] IN A 31.31.73.67
|
||||
🔹[Postfix] IN MX 10 mta.krumax.cz.
|
||||
🔹[Postfix] IN TXT v=spf1 mx ~all
|
||||
🔹[Postfix] IN TXT v=DKIM1; ...
|
||||
🔹[Postfix] IN TXT v=DMARC1; ...
|
||||
```
|
||||
@@ -1,163 +0,0 @@
|
||||
[KUBE](../../README.md) / [k3s](../k3s.md) / Redis
|
||||
|
||||
|
||||
# Resource Redis
|
||||
|
||||
> [!NOTE]
|
||||
> A replica of three pods is created: 1 master (`redis-0`) + 2 slaves (`redis-1`, `redis-2`). A cluster of three RedisSentinel is created to manage the replica. When configuring the connection, the host is specified: redis-0.redis.
|
||||
|
||||
> [!WARNING]
|
||||
> In the current configuration, when changing the RedisSentinel master node of the replica, there is no automatic change of connection to the new master node. Solution: adding HAProxy to automatically redirect requests to the current Redis master node.
|
||||
|
||||
- [Install](#install)
|
||||
- [Remove](#remove)
|
||||
- [Status](#status)
|
||||
- [Info](#info)
|
||||
- [Version](#version)
|
||||
- [User list](#user-list)
|
||||
***
|
||||
|
||||
## Install
|
||||
|
||||
### Processing
|
||||
1. Preparation.
|
||||
- Recreate the secret with the passwords `root-password`.
|
||||
|
||||
2. Creation.
|
||||
- Prepare the import file.
|
||||
- Import the YAML.
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --redis install
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[Redis] Install
|
||||
✅[Redis] Delete old Secret: OK
|
||||
✅[Redis] Create new Secret: OK
|
||||
🔹[Redis] Preparing /app/kube/assets/yaml/redis.yaml
|
||||
🔹[K3S] Applying YAML /app/kube/data/yaml/redis.yaml
|
||||
💡[K3S] Waiting for pods to be ready... | 2 not ready
|
||||
💡[K3S] Waiting for pods to be ready... | 1 not ready
|
||||
✅[Redis] Applied /app/kube/data/yaml/redis.yaml
|
||||
```
|
||||
***
|
||||
|
||||
## Remove
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --redis remove
|
||||
```
|
||||
***
|
||||
|
||||
## Status
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --redis status
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[Redis] Status
|
||||
✅[Redis] redis-0 | Role: Master | Slaves: 2
|
||||
✅[Redis] redis-1 | Role: Slave | Master: redis-0.redis
|
||||
✅[Redis] redis-2 | Role: Slave | Master: redis-0.redis
|
||||
✅[RedisSentinel] redis-sentinel-0 | Master: mymaster [redis-0.redis:6379] | Slaves: 2 | Other sentinels: 2 | Quorum: 2
|
||||
✅[RedisSentinel] redis-sentinel-0 | Slave: 10.42.0.16:6379 [10.42.0.16:6379] | Master: redis-0.redis
|
||||
✅[RedisSentinel] redis-sentinel-0 | Slave: 10.42.0.14:6379 [10.42.0.14:6379] | Master: redis-0.redis
|
||||
✅[RedisSentinel] redis-sentinel-1 | Master: mymaster [redis-0.redis:6379] | Slaves: 2 | Other sentinels: 2 | Quorum: 2
|
||||
✅[RedisSentinel] redis-sentinel-1 | Slave: 10.42.0.16:6379 [10.42.0.16:6379] | Master: redis-0.redis
|
||||
✅[RedisSentinel] redis-sentinel-1 | Slave: 10.42.0.14:6379 [10.42.0.14:6379] | Master: redis-0.redis
|
||||
✅[RedisSentinel] redis-sentinel-2 | Master: mymaster [redis-0.redis:6379] | Slaves: 2 | Other sentinels: 2 | Quorum: 2
|
||||
✅[RedisSentinel] redis-sentinel-2 | Slave: 10.42.0.16:6379 [10.42.0.16:6379] | Master: redis-0.redis
|
||||
✅[RedisSentinel] redis-sentinel-2 | Slave: 10.42.0.14:6379 [10.42.0.14:6379] | Master: redis-0.redis
|
||||
```
|
||||
***
|
||||
|
||||
## Info
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --redis info
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[Redis] Info
|
||||
🔹[Redis] redis-0
|
||||
# Replication
|
||||
role:master
|
||||
connected_slaves:2
|
||||
slave0:ip=10.42.0.14,port=6379,state=online,offset=1092850,lag=0
|
||||
slave1:ip=10.42.0.16,port=6379,state=online,offset=1092714,lag=1
|
||||
...
|
||||
🔹[Redis] redis-1
|
||||
# Replication
|
||||
role:slave
|
||||
master_host:redis-0.redis
|
||||
master_port:6379
|
||||
master_link_status:up
|
||||
...
|
||||
🔹[Redis] redis-2
|
||||
# Replication
|
||||
role:slave
|
||||
master_host:redis-0.redis
|
||||
master_port:6379
|
||||
master_link_status:up
|
||||
...
|
||||
🔹[RedisSentinel] redis-sentinel-0
|
||||
# Sentinel
|
||||
sentinel_masters:1
|
||||
...
|
||||
master0:name=mymaster,status=ok,address=redis-0.redis:6379,slaves=2,sentinels=3
|
||||
🔹[RedisSentinel] redis-sentinel-1
|
||||
# Sentinel
|
||||
sentinel_masters:1
|
||||
...
|
||||
master0:name=mymaster,status=ok,address=redis-0.redis:6379,slaves=2,sentinels=3
|
||||
🔹[RedisSentinel] redis-sentinel-2
|
||||
# Sentinel
|
||||
sentinel_masters:1
|
||||
...
|
||||
master0:name=mymaster,status=ok,address=redis-0.redis:6379,slaves=2,sentinels=3
|
||||
```
|
||||
***
|
||||
|
||||
## Version
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --redis version
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[Redis] Version
|
||||
✅[Redis] redis-0 | 7.4.2
|
||||
✅[Redis] redis-1 | 7.4.2
|
||||
✅[Redis] redis-2 | 7.4.2
|
||||
✅[RedisSentinel] redis-sentinel-0 | 7.4.2
|
||||
✅[RedisSentinel] redis-sentinel-1 | 7.4.2
|
||||
✅[RedisSentinel] redis-sentinel-2 | 7.4.2
|
||||
```
|
||||
***
|
||||
|
||||
## User list
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --redis user
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[Redis] User list
|
||||
default
|
||||
us1_example_com
|
||||
us2_example_com
|
||||
us3_example_com
|
||||
```
|
||||
@@ -1,121 +0,0 @@
|
||||
[KUBE](../../README.md) / [k3s](../k3s.md) / Worker
|
||||
|
||||
# Resource Transfer
|
||||
|
||||
- [Install](#install)
|
||||
- [Remove](#remove)
|
||||
- [Transfer site](#transfer-site)
|
||||
- [Pause for Agent to receive new tasks from the API](#pause-for-agent-to-receive-new-tasks-from-the-api)
|
||||
- [Removing the pause for Agent to receive new tasks from the API](#removing-the-pause-for-agent-to-receive-new-tasks-from-the-api)
|
||||
- [Tasks list](#tasks-list)
|
||||
- [Update ENV](#update-env)
|
||||
***
|
||||
|
||||
## Install
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --worker install
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[Worker] Install
|
||||
🔹[Worker] Preparing /app/kube/assets/yaml/worker.yaml
|
||||
🔹[K3S] Applying YAML /app/kube/data/yaml/worker.yaml
|
||||
💡[K3S] Waiting for pods to be ready... | 1 not ready
|
||||
💡[K3S] Waiting for pods to be ready... | 1 not ready
|
||||
```
|
||||
***
|
||||
|
||||
## Remove
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --Worker remove
|
||||
```
|
||||
***
|
||||
|
||||
## Execute task
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --worker task <file> [domain]
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[Worker] Task: /_data/worker/tasks/6d5b3169-a15f-4007-8cc7-ebfc8d75e3b2.task
|
||||
🔹[Worker] Action: test
|
||||
🔹[Worker] Database URL: https://wp.krumax.cz/transfer_36b91e68-53d3-49ac-922a-0031e664125b/0bf26901-c12d-4015-9bbe-cefc5c1a92d6.sql.zip
|
||||
🔹[Worker] Files URL: https://wp.krumax.cz/transfer_36b91e68-53d3-49ac-922a-0031e664125b/0bf26901-c12d-4015-9bbe-cefc5c1a92d6.zip
|
||||
🔹[Worker] Download archive database --> /app/transfers/us00.krumax.cz/us00.krumax.cz.sql.zip
|
||||
🔹[Worker] Download archive files --> /app/transfers/us00.krumax.cz/us00.krumax.cz.zip
|
||||
🔹[Worker] Create site: us00.krumax.cz
|
||||
🔹Files source: /app/transfers/us00.krumax.cz/us00.krumax.cz.zip
|
||||
🔹Database source: /app/transfers/us00.krumax.cz/us00.krumax.cz.sql.zip
|
||||
🔹[OpenLiteSpeed] Pod: openlitespeed-0 | Restart...
|
||||
🔹[OpenLiteSpeed] Pod: openlitespeed-1 | Restart...
|
||||
✅[Worker] Action: test | Success
|
||||
```
|
||||
***
|
||||
|
||||
## Tasks list
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --worker list
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[Worker] Task list
|
||||
# | Task | Action | Status | Time, sec
|
||||
---------------------------------------------------------------------------
|
||||
1 | d580040f-b3b8-43e1-af7a-8e35c80a688c | test | new | ?
|
||||
2 | pause | pause | execution | 691175
|
||||
```
|
||||
***
|
||||
|
||||
## Pause for Agent to receive new tasks from the API (pause)
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --worker down
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[Worker] Put on pause...
|
||||
```
|
||||
***
|
||||
|
||||
## Removing the pause for Agent to receive new tasks from the API (unpause)
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --worker up
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[Worker] Resuming from pause...
|
||||
```
|
||||
***
|
||||
|
||||
## Reload
|
||||
|
||||
Command:
|
||||
```bash
|
||||
sudo kube.sh --worker reload
|
||||
```
|
||||
|
||||
Example of log:
|
||||
```bash
|
||||
🔹[Worker] Reload...
|
||||
🔹[Worker] Updated ENV:
|
||||
API_URL: http://api.sodew.ai/api/tasks
|
||||
WORKER_ID: worker-dev
|
||||
GETTING_PAUSE: 30
|
||||
SENDING_PAUSE: 15
|
||||
```
|
||||
@@ -1,73 +0,0 @@
|
||||
[KUBE](../README.md) / Restore
|
||||
|
||||
# Restore
|
||||
|
||||
The restore process is divided into three stages:
|
||||
1. Restoring website files from the `<domain>` directory or the `<domain>.tar.gz` archive.
|
||||
2. Restoring the `<domain>.conf` file.
|
||||
3. Restoring the database from the `<domain>.sql` file or the `<domain>.sql.tar.gz` archive.
|
||||
|
||||
> [!WARNING]
|
||||
> If an error occurs at any stage, the process does not stop.
|
||||
|
||||
> [!WARNING]
|
||||
> Files, databases, and other resources at each stage are either pre-cleaned or immediately overwritten without confirmation.
|
||||
|
||||
|
||||
## Commands
|
||||
|
||||
### `-r, --restore <domain> [action]`
|
||||
Restore site files and database from backup for a `domain`.
|
||||
|
||||
Example:
|
||||
```bash
|
||||
sudo kube.sh -r example.com
|
||||
```
|
||||
|
||||
The source of resources for recovery is the directories defined in `backupPath` and `backupLongTermPath`.\
|
||||
In the backup folder is searched for 3 types of resources:
|
||||
- `<source path>/<date>/<marker>/<domain>` - directory with site files (`file:d`)
|
||||
- `<source path>/<date>/<marker>/<domain>.tar.gz` - site file archive (`file:a`)
|
||||
- `<source path>/<date>/<marker>/<domain>.sql` - site database SQL-file (`db:f`)
|
||||
- `<source path>/<date>/<marker>/<domain>.sql.tar.gz` - site database archive (`db:a`)
|
||||
- `<source path>/<date>/<marker>/<domain>.conf` - site database archive (`conf`)
|
||||
|
||||
The search results in a list of format: `<counter>: <data> <marker> [<list of resource>]`. Then you should specify the number of the selected marker for restore.
|
||||
|
||||
> [!NOTE]
|
||||
> If one resource is selected (`file:d`/`file:a`/`db:f`/`db:a`/`conf`), only one resource will be restored. The other will remain unchanged.
|
||||
|
||||
> [!NOTE]
|
||||
> If there's a `file:d` and a `file:a`. Priority is given to `file:d`. If there's a `db:f` and a `db:a`. Priority is given to `db:f`.
|
||||
|
||||
> [!NOTE]
|
||||
> Once database are restored, the Redis keys for the domain are deleted.
|
||||
|
||||
Example:
|
||||
```bash
|
||||
1: 2025-04-29 14-22-22 [file:d file:a db:f conf]
|
||||
2: 2025-04-29 14-20-20 [file:d db:a]
|
||||
3: 2025-04-29 12-00-00 [file:a conf]
|
||||
4: 2025-04-29 _first [db:a]
|
||||
```
|
||||
|
||||
You can specify an additional parameter after the domain:
|
||||
- `list` - will display a list of available markers for restore
|
||||
- `last` - automatic site restore from the `last backup`
|
||||
- `full` - automatic site restore from the last `FULL backup`
|
||||
- `auto` - automatic site restore from the last `FULL backup` or the `last backup`
|
||||
- `N` - automatic site restore from the `last backup #N` (N: 1+)
|
||||
|
||||
`FULL backup` is a backup that contains a copy of the site files, a copy of the database, and a copy of the .conf file\
|
||||
`last backup #N` marker number (starting from 1) in the list sorted by creation time (can be seen with the list command)
|
||||
|
||||
Example:
|
||||
```bash
|
||||
sudo kube.sh -r example.com list
|
||||
sudo kube.sh -r example.com last
|
||||
sudo kube.sh -r example.com auto
|
||||
sudo kube.sh -r example.com 3
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
> Restoring from the last full copy will be done without question.
|
||||
@@ -1,24 +0,0 @@
|
||||
[KUBE](../README.md) / Script
|
||||
|
||||
# Script
|
||||
|
||||
> A set of scripts to execute.
|
||||
|
||||
## Commands
|
||||
|
||||
### `--script <script>`
|
||||
|
||||
Script:
|
||||
- `backup` - Creating a backup (within a day) of all sites and then synchronizing the backups (within a day) with external storage.
|
||||
- `backup-clean` - Cleanup of old backups on the current host and on external storage.
|
||||
- `backup-long-term` - Create/update a long-term backup.
|
||||
- `report-backup-last` - Report on creating the last backup.
|
||||
- `report-backup-day` - Backup report for the current day.
|
||||
- `report-status` - Creating and sending a report on the status of k3s and its nodes
|
||||
- `mariadb-dump` - Creating a full backup (of all databases) of MariaDB
|
||||
- `worker-observer` - Launching the task observer
|
||||
|
||||
Example:
|
||||
```bash
|
||||
sudo kube.sh --script backup
|
||||
```
|
||||
@@ -1,36 +0,0 @@
|
||||
[KUBE](../README.md) / Shell
|
||||
|
||||
# Shell
|
||||
|
||||
> Opening the shell in the pods.
|
||||
|
||||
## Commands
|
||||
|
||||
### `--shell [cli]`
|
||||
|
||||
> [!NOTE]
|
||||
> Specifying `cli` will open the appropriate CLI where possible (`MariaDB`, `Redis`, `Redis Sentinel`)
|
||||
|
||||
Example:
|
||||
```bash
|
||||
sudo kube.sh --shell
|
||||
sudo kube.sh --shell cli
|
||||
```
|
||||
|
||||
Examples of responses:
|
||||
```bash
|
||||
🔹Shell
|
||||
1: mariadb-master-0 [Running]
|
||||
2: mariadb-slave-0 [Running]
|
||||
3: openlitespeed-0 [Running]
|
||||
4: openlitespeed-1 [Running]
|
||||
5: postfix-78c47b95f6-42jcq [Running]
|
||||
6: redis-0 [Running]
|
||||
7: redis-1 [Running]
|
||||
8: redis-2 [Running]
|
||||
9: redis-sentinel-0 [Running]
|
||||
10: redis-sentinel-1 [Running]
|
||||
11: redis-sentinel-2 [Running]
|
||||
12: worker-6cd4bdb6b8-c6f5d [Running]
|
||||
Specify the pod number [0]:
|
||||
```
|
||||
@@ -1,191 +0,0 @@
|
||||
[KUBE](../README.md) / Site
|
||||
|
||||
# Site
|
||||
|
||||
## Site Creation
|
||||
|
||||
Stages of Site Creation:
|
||||
|
||||
### 1. Configuration.
|
||||
|
||||
The configuration, as a file (`<dataPath>/config/<domain>.config`), contains the connection parameters for `MariaDB` and `Redis`. The configuration can be prepared before starting the site creation or will be generated automatically.
|
||||
|
||||
Example configuration file:
|
||||
```
|
||||
databaseName=example_com
|
||||
databaseUser=example_com
|
||||
redisUser=example_com
|
||||
databasePass=qwerty
|
||||
redisPass=qwerty
|
||||
```
|
||||
|
||||
Fields not specified will be generated automatically. The names of the database, database user, and `Redis` user are generated based on the domain using the functions `mariadbDomain2id` and `domain2redis`.
|
||||
|
||||
### 2. Initial Check.
|
||||
|
||||
Before starting the site creation, some configuration checks are performed:
|
||||
* Check for absence of the vhost directory for the domain (`/path/to/vhosts/example.com`)
|
||||
* Check for absence of the domain entry in the `OpenLiteSpeed` configuration
|
||||
* Check for absence of the specified database
|
||||
* Check for absence of the specified database user
|
||||
* Check for absence of the specified `Redis` user
|
||||
|
||||
### 3. Distribution Check.
|
||||
|
||||
When creating a site, you can specify the source for the site files as `pathF` and the source for the database as `pathD`. `pathF` can be a directory or an archive file. `pathD` can be a SQL file for import as is, or an archive file.
|
||||
|
||||
If a file source `pathF` is specified, its presence is checked. Otherwise, the default distribution is used:
|
||||
* for `Wordpress`: the directory `<assetsPath>/vhost.wordpress` (if the directory is missing, the file `<assetsPath>/vhost.wordpress.tar.gz` is used)
|
||||
* for `non-Wordpress`: the directory `<assetsPath>/vhost.default` (if the directory is missing, the file `<assetsPath>/vhost.default.tar.gz` is used)
|
||||
|
||||
If the specified data sources are not found, or the default resources are not found, the process is aborted.
|
||||
|
||||
### 4. Site Creation.
|
||||
|
||||
When a site is created, the following steps occur:
|
||||
* creation of the site directory structure `<vhostPath>/<domain>/{www,tmp,session}`
|
||||
* copying site files from the source to the site directory
|
||||
* creation of an OS user for the site files and changing access rights
|
||||
* creation of a record in the `OpenLiteSpeed` configuration
|
||||
* creation of a database and database user in `MariaDB`
|
||||
* creation of a `Redis` user
|
||||
* importing the database from the source, if specified
|
||||
* writing database connection parameters (for `Wordpress`)
|
||||
* writing Redis connection parameters (for `Wordpress`)
|
||||
* writing to hosts (?!)
|
||||
* restarting `OpenLiteSpeed`
|
||||
|
||||
***
|
||||
|
||||
## Site on Wordpress creation
|
||||
|
||||
* MariaDB
|
||||
> The database name and database user name are formed on the basis of domain conversion, where all `.-` are replaced by `_`. If necessary, the conversion rule can be changed (mariadbDomain2id function). When generating connection parameters, the database password is saved to the `data/<domain>.mariadb` file.
|
||||
|
||||
Example for `example.com`:
|
||||
```php
|
||||
define( 'DB_HOST', 'mariadb-master' );
|
||||
define( 'DB_NAME', 'example_com' );
|
||||
define( 'DB_USER', 'example_com' );
|
||||
define( 'DB_PASSWORD', 'qwerty' );
|
||||
```
|
||||
|
||||
* Redis
|
||||
> Redis username is generated based on domain conversion, where all `.-` are replaced by `_`. The conversion rule can be changed if necessary (`domain2redis` function). When generating connection parameters, the database password is stored in the `data/<domain>.redis` file.
|
||||
|
||||
Example for `example.com`:
|
||||
```php
|
||||
define( 'WP_REDIS_HOST', 'redis-0.redis' );
|
||||
define( 'WP_REDIS_PORT', '6379' );
|
||||
define( 'WP_REDIS_PASSWORD', ['example_com', 'qwerty'] );
|
||||
define( 'WP_REDIS_PREFIX', 'example_com:' );
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> A separate Redis user is created for each Wordpress and a PrefixKey is also specified to separate keys in Redis.
|
||||
***
|
||||
|
||||
## Commands
|
||||
|
||||
### `--site add <domain> [pathF] [pathD]`
|
||||
Adding a site
|
||||
|
||||
Example:
|
||||
```bash
|
||||
sudo kube.sh --site add example.com
|
||||
sudo kube.sh --site add example.com /path/to/files /path/to/database
|
||||
```
|
||||
***
|
||||
|
||||
### `--site wp <option>`
|
||||
Add site(s) on Wordpress. Option:
|
||||
- `list` - add list of domains with Wordpress from a default file: (`domainsList` in `config.sh`)
|
||||
- `<file>` - add list of domains with Wordpress from a file (e.g., `/path/to/file.txt`)
|
||||
- `<domain>` [pathF] [pathD] - add a domain with Wordpress (e.g., `example.com`)
|
||||
|
||||
Example:
|
||||
```bash
|
||||
sudo kube.sh --site wp /path/to/file.txt
|
||||
sudo kube.sh --site wp list
|
||||
sudo kube.sh --site wp example.com
|
||||
sudo kube.sh --site wp example.com /path/to/files /path/to/database
|
||||
```
|
||||
***
|
||||
|
||||
### `--site remove <domain>`
|
||||
|
||||
> [!WARNING]
|
||||
> Site remove (site directories, site database, `OpenLiteSpeed` configuration entries).
|
||||
|
||||
Example:
|
||||
```bash
|
||||
sudo kube.sh --remove example.com
|
||||
```
|
||||
***
|
||||
|
||||
### `--site status [domain]`
|
||||
|
||||
When specifying a domain, the following checks are performed:
|
||||
* Presence of the domain in the `OpenLiteSpeed` configuration
|
||||
* Presence of the domain in the list of stopped sites (OpenLiteSpeed)
|
||||
* Presence of the domain directory in the virtual hosts directory (`olsVhostsPath`)
|
||||
* Presence of the `WordPress` configuration file (`wp-config.php`)
|
||||
* Checking the database name entry in the WordPress configuration and verifying its existence
|
||||
* Checking the database user entry in the WordPress configuration and verifying its existence
|
||||
* Checking database connectivity (retrieving the list of tables in the database)
|
||||
* Checking the Redis user entry in the `WordPress` configuration and verifying its existence
|
||||
* Checking connectivity to `Redis` (using the `PING` command)
|
||||
* Checking the site's HTTP response
|
||||
|
||||
If the domain is not specified, the following checks are performed for all domains:
|
||||
* Presence of the domain in the list of stopped sites (`OpenLiteSpeed`)
|
||||
* Presence of the domain directory in the virtual hosts directory (`olsVhostsPath`)
|
||||
* Presence of the `WordPress` configuration file (`wp-config.php`)
|
||||
* Checking the database name entry in the `WordPress` configuration and verifying its existence
|
||||
* Checking the database user entry in the `WordPress` configuration and verifying its existence
|
||||
* Checking the `Redis` user entry in the `WordPress` configuration and verifying its existence
|
||||
|
||||
Example:
|
||||
```bash
|
||||
sudo kube.sh --site
|
||||
sudo kube.sh --site example.com
|
||||
```
|
||||
|
||||
Examples of responses:
|
||||
```bash
|
||||
🔹Site | example.com
|
||||
✅example.com | Found in OpenLiteSpeed configuration
|
||||
✅example.com | Directory: /_data/vhosts/example.com
|
||||
✅example.com | Wordpress config: /_data/vhosts/example.com/www/wp-config.php
|
||||
✅example.com | MariaDB database: example_com
|
||||
✅example.com | MariaDB user: example_com
|
||||
✅example.com | MariaDB tables: 10
|
||||
✅example.com | Redis user: example_com
|
||||
✅example.com | Redis ping: PONG
|
||||
✅example.com | Site response: 200
|
||||
```
|
||||
|
||||
```bash
|
||||
🔹Site | all
|
||||
## | Domain | CFG | DIR | OLS | MD | MU | RU | WP
|
||||
--------------------------------------------------------------------------------
|
||||
1 | example1.com | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅
|
||||
2 | example2.com | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅
|
||||
3 | example3.com | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅
|
||||
4 | example4.com | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅
|
||||
5 | example5.com | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅
|
||||
6 | example6.com | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅
|
||||
7 | example7.com | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | --
|
||||
8 | example8.com | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | --
|
||||
9 | Example | --- | --- | ✅ | -- | -- | -- | --
|
||||
```
|
||||
|
||||
Where:
|
||||
- `CFG` - existence of the configuration file `$dataPath/config/<domain>.config`
|
||||
- `DIR` - existence of the directory `$olsVhostsPath/<domain>`
|
||||
- `OLS` - existence of an entry in the `OpenLiteSpeed` configuration
|
||||
- `MD` - existence of the database (based on the entry in the configuration file)
|
||||
- `MU` - existence of the database user (based on the entry in the configuration file)
|
||||
- `RU` - existence of the Redis user (based on the entry in the configuration file)
|
||||
- `WP` - existence of the WP configuration file `$olsVhostsPath/<domain>/www/wp-config.php`
|
||||
)
|
||||
@@ -1,23 +0,0 @@
|
||||
[KUBE](../README.md) / Storage
|
||||
|
||||
# Storage
|
||||
|
||||
## Commands
|
||||
|
||||
### `-s, --storage [option]`
|
||||
Copying backups to external storage. Options:
|
||||
- `rsync` - The backup directory `<backup path>/<current date>` is synchronized to the specified address `rsyncUri` using `rSync`.
|
||||
- `ssh` - The backup directory `<backup path>/<current date>` is synchronized to the specified address `sshHost` using `rSync`.
|
||||
|
||||
> [!NOTE]
|
||||
> The default value (`rsync` or `ssh`) is set by the `storageType` variable in the configuration file `config.sh`.
|
||||
|
||||
> [!NOTE]
|
||||
> For correct operation it is necessary to set up SSH-keys for authentication on the external storage. Create a directory (specified in `sshPath`) and grant necessary user rights on the external storage.
|
||||
|
||||
Example:
|
||||
```bash
|
||||
sudo kube.sh -s
|
||||
sudo kube.sh -s rsync
|
||||
sudo kube.sh -s ssh
|
||||
```
|
||||
@@ -1,24 +0,0 @@
|
||||
[KUBE](../README.md) / Test
|
||||
|
||||
# Test
|
||||
|
||||
> [!NOTE]
|
||||
> Testing the operation of individual units or the entire system.
|
||||
|
||||
## Commands
|
||||
|
||||
### `--test <option>`
|
||||
Options:
|
||||
- `mariadb` - A database with a random name is created on the master device and the existence of the created database is checked on the slave device.
|
||||
- `redis` - A key with a random name is created on the master and the existence of the created key is verified on the replicas.
|
||||
- `site` - A site is created (without `WordPress`). MariaDB (a database and user are created). `Redis` (a user is created). The site is opened via HTTP (the response code is checked). The site is deleted.
|
||||
- `wordpress` - A WordPress site is created. `MariaDB` (a database and user are created). `Redis` (a user is created). The site is opened via HTTP (the response code is checked). The site is deleted.
|
||||
- `mailint` - Internal test of sending a letter from one user to another.
|
||||
- `mailext` - Test sending a letter to the specified address (In progress...)
|
||||
|
||||
Example:
|
||||
```bash
|
||||
sudo kube.sh --test mariadb
|
||||
sudo kube.sh --test redis
|
||||
sudo kube.sh --test mailint
|
||||
```
|
||||
@@ -1,214 +0,0 @@
|
||||
[KUBE](../README.md) / Transfer sites
|
||||
|
||||
# Transfer sites
|
||||
|
||||
> [!NOTE]
|
||||
> In progress...
|
||||
|
||||
- [Transfer via console](#transfer-via-console)
|
||||
- [Transfer via plugin Wordpress](#transfer-via-plugin-worpress)
|
||||
- [Workflow 1. Cloning a website via the WordPress plugin](#workflow-1-cloning-a-website-via-the-wordpress-plugin)
|
||||
- [Workflow 2. Cloning websites to SODEW servers via the WordPress plugin](#workflow-2-cloning-websites-to-sodew-servers-via-the-wordpress-plugin)
|
||||
- [Workflow 3. Detailed description of the process](#workflow-3-detailed-description-of-the-process)
|
||||
|
||||
## Transfer via console:
|
||||
|
||||
```bash
|
||||
sudo kube.sh --worker task </path/to/configFile> [domain]
|
||||
```
|
||||
|
||||
### Configuration analysis.
|
||||
|
||||
Example:
|
||||
|
||||
```ini
|
||||
action=test
|
||||
files_url=https://wp.krumax.cz/transfer_36b91e68-53d3-49ac-922a-0031e664125b/2b3d170e-9f50-435c-b189-b8c3a45cbb8a.zip
|
||||
database_url=https://wp.krumax.cz/transfer_36b91e68-53d3-49ac-922a-0031e664125b/2b3d170e-9f50-435c-b189-b8c3a45cbb8a.sql.zip
|
||||
domain=new.domain.com
|
||||
status=new
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
* `action` — the main operation (`copy`/`test`/`migrate`/...)
|
||||
* `files_url` — link to the files archive
|
||||
* `database_url` — link to the DB archive
|
||||
* `status` — execution status:
|
||||
* `new` - new task
|
||||
* `execution` - in progress
|
||||
* `success` - task completed successfully
|
||||
* `failed` - task failed
|
||||
* `domain` - new domain (optional field)
|
||||
|
||||
> [!NOTE]
|
||||
> Working only action `test` (18.11.2025).
|
||||
|
||||
> [!NOTE]
|
||||
> If a domain is specified in the command call, the domain from the configuration file is ignored.
|
||||
|
||||
> [!NOTE]
|
||||
> If the domain is not specified in the command and not specified in the configuration file, it will be assigned automatically from the available ones (`domainsList`). If none are available, the task fails.
|
||||
|
||||
### Work
|
||||
|
||||
**Stage 1**. Configuration analysis.
|
||||
|
||||
**Stage 2**. Availability check and archive download.
|
||||
|
||||
The download is performed into the folder `<transferPath>/<domain>/`:
|
||||
|
||||
* `<transferPath>/<domain>/<domain>.zip` — files archive
|
||||
* `<transferPath>/<domain>/<domain>.sql.zip` — DB archive
|
||||
|
||||
**Stage 3**. Depending on the task:
|
||||
|
||||
* `test` - A site is created based on the downloaded backup.
|
||||
* `copy` - in progress...
|
||||
* `migrate` - in progress...
|
||||
|
||||
**Stage 4**. The status is changed in the configuration file
|
||||
|
||||
* `success` - upon successful completion of the task
|
||||
* `failed` - upon errors at any stage
|
||||
|
||||
## Transfer via plugin Wordpress
|
||||
|
||||
When using the plugin "SODEW Backup & Transfer" ([https://gitlab.wedos.org/mk250/transfer-plugin](https://gitlab.wedos.org/mk250/transfer-plugin)), it is possible to copy a site to SODEW hosting.
|
||||
|
||||
### Preparation
|
||||
|
||||
**Stage 1**. Create a full site backup in the plugin.
|
||||
|
||||
**Stage 2**. Create a task "Launch a copy of the website on SODEW hosting".
|
||||
|
||||
### Work
|
||||
|
||||
**Stage 1**. Sending a request to create a copy of the site for testing to `api.sodew.ai`. Request parameters:
|
||||
|
||||
* `action` – selected action
|
||||
* `transfer_id` – plugin identifier
|
||||
* `backup_id` – backup identifier
|
||||
* `base_url` – site URL (WordPress)
|
||||
* `base_path` – base path
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
[
|
||||
"action":"test",
|
||||
"transfer_id":"22222222-53d3-49ac-922a-0031e664125b",
|
||||
"backup_id":"33333333-c976-43c2-bdee-d7d6ec21900a",
|
||||
"base_url":"https://wp.us1.com",
|
||||
"base_path":"/usr/www/wp.us1.com",
|
||||
]
|
||||
```
|
||||
|
||||
**Stage 2**. Processing the request on the `api.sodew.ai` side.
|
||||
|
||||
1. Parameter validation
|
||||
2. Preparation and validation of URLs to the backup:
|
||||
```ini
|
||||
<base_url>/transfer_<transfer_id>/<backup_id>.zip
|
||||
<base_url>/transfer_<transfer_id>/<backup_id>.sql.zip
|
||||
```
|
||||
3. Creating a task for Workers
|
||||
|
||||
**Stage 3**. Request from the Worker (agent) to `api.sodew.ai` to obtain a new task and receiving it.
|
||||
The task is assigned to the Worker.
|
||||
|
||||
**Stage 4**. Processing of the `api.sodew.ai` response by the Worker (agent) and creating a task for the `kube.sh` script.
|
||||
|
||||
A task file `<task_id>.task` is created in the `workerTasksPath` folder with the following content:
|
||||
|
||||
* `action` – action
|
||||
* `files_url` – URL to the files archive
|
||||
* `database_url` – URL to the database archive
|
||||
* `domain` – domain (optional)
|
||||
* `status` – new (task status: new)
|
||||
|
||||
**Stage 5**. Launching the `kube.sh` task handler via CRON, searching for new tasks and starting execution of the found new task.
|
||||
|
||||
**Stage 6**. Validation of the task parameters. Checking the availability of backup files. Downloading the backup files. Creating the site. (Detailed: [Transfer](#transfer))
|
||||
|
||||
**Stage 7**. Monitoring of the task execution status by the Worker (agent) and sending reports to `api.sodew.ai`.
|
||||
|
||||
**Stage 8**. Receiving task execution reports from the Worker (agent).
|
||||
|
||||
**Stage 9**. Updating the task execution status on `api.sodew.ai` by the plugin.
|
||||
|
||||
## Workflow 1. Cloning a website via the WordPress plugin.
|
||||
|
||||
```mermaid
|
||||
flowchart TB;
|
||||
|
||||
subgraph SW[Server-worker]
|
||||
WO(Worker-observer<br>CRON)
|
||||
WA(Worker-agent)
|
||||
TP[[workerTasksPath/task_id.task<br>action<br>files_url<br>database_url<br>status=new]]
|
||||
CS[Сopy of user's website]
|
||||
end
|
||||
subgraph API[api.sodew.ai]
|
||||
AT(task_id<br>files_url<br>database_url)
|
||||
end
|
||||
subgraph US[User site]
|
||||
WP(Plugin WP<br>action<br>transfer_id<br>backup_id<br>base_url)
|
||||
end
|
||||
|
||||
WP -->|1. create task<br>check status| AT
|
||||
WA -->|2. get new task<br>send statuses| AT
|
||||
WA -->|3. save new task| TP
|
||||
WO -->|4. executing tasks| TP
|
||||
WO -->|5. load files| US
|
||||
WO -->|6. create site| CS
|
||||
```
|
||||
|
||||
## Workflow 2. Cloning websites to SODEW servers via the WordPress plugin.
|
||||
|
||||
```mermaid
|
||||
flowchart LR;
|
||||
US[[User sites...]]
|
||||
SW[[Server-workers...]]
|
||||
API(api.sodew.ai)
|
||||
|
||||
US -->|tasks...| API -->|tasks...| SW
|
||||
```
|
||||
|
||||
## Workflow 3. Detailed description of the process.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
|
||||
alt task
|
||||
Wordpress plugin ->> api.sodew.ai: new task
|
||||
|
||||
loop every 30sec
|
||||
Wordpress plugin ->> api.sodew.ai: how status task?
|
||||
activate api.sodew.ai
|
||||
api.sodew.ai ->> Wordpress plugin: status task is ...
|
||||
deactivate api.sodew.ai
|
||||
end
|
||||
end
|
||||
|
||||
loop every 30sec
|
||||
Worker-Agent ->> api.sodew.ai: receiving new tasks
|
||||
activate api.sodew.ai
|
||||
api.sodew.ai ->> Worker-Agent: <task>
|
||||
deactivate api.sodew.ai
|
||||
activate Worker-Agent
|
||||
Worker-Agent ->> workerTasksPath: saving a new task <task>
|
||||
deactivate Worker-Agent
|
||||
end
|
||||
|
||||
loop every 15sec
|
||||
Worker-Agent ->> workerTasksPath: reading task statuses
|
||||
activate Worker-Agent
|
||||
Worker-Agent ->> api.sodew.ai: sending task statuses
|
||||
deactivate Worker-Agent
|
||||
end
|
||||
|
||||
loop every 1min
|
||||
Worker-Observer ->> workerTasksPath: get new task
|
||||
Note over Worker-Observer: Execute task...
|
||||
end
|
||||
```
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
|
||||
readonly appVersion="7.1.557"
|
||||
readonly appName="KUBE"
|
||||
readonly appPath="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
readonly appFile="$(basename -- "$0")"
|
||||
readonly appDate="$(date +%Y-%m-%d)"
|
||||
readonly appTime="$(date +%H-%M-%S)"
|
||||
|
||||
. "$appPath/libs/app.sh"
|
||||
appBootstrap
|
||||
|
||||
function appDev() {
|
||||
printRow
|
||||
printFill 30 "◤◢" "$fontYellow" "◤$fontReset"
|
||||
|
||||
|
||||
|
||||
printFill 30 "◤◢" "$fontYellow" "◤$fontReset"
|
||||
}
|
||||
|
||||
appRouter "$@"
|
||||
exit $?
|
||||
@@ -1,132 +0,0 @@
|
||||
readonly EX_OK=0
|
||||
readonly EX_GENERAL=1
|
||||
readonly EX_USAGE=2
|
||||
readonly EX_CONFIG=3
|
||||
readonly EX_DEPENDENCY=4
|
||||
readonly EX_PERMISSION=5
|
||||
readonly EX_NOT_FOUND=6
|
||||
readonly EX_COMMAND_FAILED=10
|
||||
readonly EX_K8S=20
|
||||
readonly EX_DB=30
|
||||
readonly EX_BACKUP=40
|
||||
|
||||
# ERR_MSG=""
|
||||
# ERR_CODE=0
|
||||
|
||||
# err_set() {
|
||||
# ERR_CODE="$1"
|
||||
# ERR_MSG="$2"
|
||||
# return "$ERR_CODE"
|
||||
# }
|
||||
|
||||
# err_clear() {
|
||||
# ERR_CODE=0
|
||||
# ERR_MSG=""
|
||||
# }
|
||||
|
||||
# m_a_riadbStatus() {
|
||||
# err_clear
|
||||
|
||||
# local out err
|
||||
# if ! run out appError kubectl get pods -n mariadb; then
|
||||
# err_set 30 "Cannot get MariaDB pods: $err"
|
||||
# return $ERR_CODE
|
||||
# fi
|
||||
|
||||
# log_table "$out"
|
||||
# }
|
||||
|
||||
|
||||
# Print an error message to stderr.
|
||||
# $1 (message): error message text.
|
||||
function appError() {
|
||||
printf '%s\n' "$*" >&2
|
||||
}
|
||||
|
||||
# Print a fatal error message and exit the script.
|
||||
# [$1] (code): optional numeric exit code (defaults to 1); if omitted, $1 is the message.
|
||||
# $1 (message): error message text (all remaining args when code is provided).
|
||||
function appFailure() {
|
||||
local code=1
|
||||
|
||||
if [[ "${1:-}" =~ ^[0-9]+$ ]]; then
|
||||
code="$1"
|
||||
shift || true
|
||||
fi
|
||||
|
||||
printf '%b\n' "\033[0;91mCrash: $*\033[0m"
|
||||
exit "$code"
|
||||
}
|
||||
|
||||
# Source a shell file, exiting with failure if the file does not exist.
|
||||
# $1 (file): path to the shell file to load.
|
||||
function appLoadFile() {
|
||||
local file="$1"
|
||||
[[ -f "$file" ]] || appFailure 3 "File not found: $file"
|
||||
. "$file"
|
||||
}
|
||||
|
||||
# Validate all required configuration variables and abort on any missing or malformed value.
|
||||
function appCheckConfig() {
|
||||
local value
|
||||
|
||||
local -a values=('hostName' 'hostIp' 'hostUuid' 'hostSalt' 'k3sNamespace' 'redisKube' 'mariadbKube' 'olsKube' 'postfixKube' 'workerKube' 'redisFileUsersAcl' 'postfixIp' 'postfixHost' 'postfixPostmaster' 'postfixDkimSelector' 'workerApiUrl' 'workerName' 'backupType' 'backupFirstDir')
|
||||
for value in "${values[@]}"; do
|
||||
[[ -n "${!value-}" ]] || appFailure 4 "$value: not specified"
|
||||
done
|
||||
|
||||
values=('appAssetsPath' 'appDataPath' 'basePath' 'olsVhostsPath' 'domainsList' 'k3sCmd' 'redisPath' 'mariadbMasterPath' 'mariadbSlavePath' 'olsPath' 'olsAdminPath' 'olsPhpIniPath' 'olsLogsPath' 'olsConfigFile' 'olsPrivatePath' 'olsPublicPath' 'olsVhostsConfigPath' 'postfixPath' 'postfixDkimPath' 'workerPath' 'workerAgentPath' 'workerTasksPath' 'workerFilesPath' 'logPath' 'logFile' 'backupDailyPath' 'backupArchivePath' 'storagePath' 'rsyncPath' 'ftpPath' 'sshPath')
|
||||
for value in "${values[@]}"; do
|
||||
[[ "${!value-}" == /* ]] || appFailure 4 "$value: a variable must begin with /"
|
||||
[[ "${!value-}" != */ ]] || appFailure 4 "$value: a variable cannot end in /"
|
||||
done
|
||||
|
||||
values=('backupParallelJobs' 'k3sReadyRetries' 'k3sReadySleep' 'workerApiGettingPause' 'workerApiSendingPause' 'backupDailyKeep' 'backupArchiveInterval' 'storageDailyKeep' 'storageArchiveKeep' 'olsQuotaBlockLimit' 'olsQuotaInodeLimit')
|
||||
for value in "${values[@]}"; do
|
||||
[[ "${!value-}" =~ ^[0-9]+$ ]] && (( ${!value} >= 1 )) || appFailure 4 "$value: incorrect number value"
|
||||
done
|
||||
|
||||
if [[ "$backupDailySuffix" == "$backupArchiveSuffix" ]]; then
|
||||
appFailure 4 "backupDailySuffix = backupArchiveSuffix"
|
||||
fi
|
||||
}
|
||||
|
||||
# Initialize the application by loading core libraries, config, modules, and commands.
|
||||
function appBootstrap() {
|
||||
local file
|
||||
|
||||
[[ "$EUID" -eq 0 ]] || appFailure 2 "You must run this script as root"
|
||||
|
||||
# Core
|
||||
for file in \
|
||||
"$appPath/libs/main.sh" \
|
||||
"$appPath/libs/print.sh" \
|
||||
"$appPath/libs/file.sh"
|
||||
do
|
||||
appLoadFile "$file"
|
||||
done
|
||||
|
||||
# Config
|
||||
appLoadFile "$appPath/config.sh"
|
||||
appCheckConfig
|
||||
|
||||
# Modules
|
||||
for file in "$appPath/libs/modules/"*.sh; do
|
||||
appLoadFile "$file"
|
||||
done
|
||||
|
||||
# Commands
|
||||
for file in "$appPath/libs/commands/"*.sh; do
|
||||
appLoadFile "$file"
|
||||
done
|
||||
}
|
||||
|
||||
# Dispatch execution to the appropriate router based on the APP_MODE environment variable.
|
||||
function appRouter() {
|
||||
local mode="${APP_MODE:-cmd}"
|
||||
|
||||
case "$mode" in
|
||||
api) apiRouter "$@" ;;
|
||||
cmd|*) cmdRouter "$@" ;;
|
||||
esac
|
||||
}
|
||||
@@ -1,383 +0,0 @@
|
||||
backupLabel="[Backup]"
|
||||
|
||||
# Creates archive-based backups for all OpenLiteSpeed virtual hosts.
|
||||
# [$1] (path): destination directory.
|
||||
# [$2] (type): archive type: zip or tar.
|
||||
function cmdBackupSitesArchive() {
|
||||
local path
|
||||
path=$(backupPathGenerate "$1")
|
||||
|
||||
local type="${2:-$backupType}"
|
||||
|
||||
local error vhostList total
|
||||
run vhostList error openlitespeedConfigVhostList || { printDanger "Failed get list of vhosts: $error"; return 1; }
|
||||
total=$(grep -c . <<< "$vhostList")
|
||||
|
||||
printSection "Config"
|
||||
printDotText "Target" "all ($total)"
|
||||
printDotText "Type" "$type"
|
||||
printDotText "Path" "$path"
|
||||
|
||||
printSection "Executing"
|
||||
local domain label num i=0 lockFile tmpDir
|
||||
maxJobs="${backupParallelJobs:-1}"
|
||||
tmpDir=$(mktemp -d) || { printDanger "Failed to create temp directory"; return 1; }
|
||||
lockFile="$tmpDir/.lock"
|
||||
while read -r domain; do
|
||||
((i++))
|
||||
num=$(printf "%0${#total}d" "$i")
|
||||
label="$num: $fontBlue$domain$fontReset"
|
||||
|
||||
while (( $(jobs -rp | wc -l) >= maxJobs )); do
|
||||
wait -n 2>/dev/null || true
|
||||
done
|
||||
|
||||
(
|
||||
local jobError
|
||||
if runError jobError backupSite "$domain" "$path" "$type"; then
|
||||
{ flock 9; printDotText "$label" "$labelDone"; } 9>>"$lockFile"
|
||||
else
|
||||
touch "$tmpDir/$i"
|
||||
{ flock 9; printDotText "$label" "$labelFail"; printDanger "$jobError"; } 9>>"$lockFile"
|
||||
fi
|
||||
) &
|
||||
done < <(awk 'NF' <<< "$vhostList")
|
||||
wait
|
||||
|
||||
local failed=0 f
|
||||
for f in "$tmpDir"/[0-9]*; do
|
||||
[[ -f "$f" ]] || break
|
||||
((failed++))
|
||||
done
|
||||
rm -rf "$tmpDir"
|
||||
|
||||
printSection "Summary"
|
||||
printDotText "Total" "$fontBlue$total$fontReset"
|
||||
printDotText "Successful" "$fontGreen$((total-failed))$fontReset"
|
||||
printDotText "Failed" "$fontRed$failed$fontReset"
|
||||
}
|
||||
|
||||
# Creates rsync-based backups for all sites; first daily backup is full, later ones use hard links.
|
||||
# [$1] (path): destination directory.
|
||||
function cmdBackupSitesRsync() {
|
||||
local path
|
||||
path=$(backupPathGenerate "$1")
|
||||
|
||||
printSection "Config"
|
||||
printDotText "Target" "all"
|
||||
printDotText "Path" "$path"
|
||||
|
||||
local error vhostList
|
||||
run vhostList error openlitespeedConfigVhostList || { printDanger "Failed get list of vhosts: $error"; return 1; }
|
||||
|
||||
printText "Files..."
|
||||
if [[ "$path" == *"$backupFirstDir" ]]; then
|
||||
runError error rsync -a --mkpath -- "$olsVhostsPath/" "$path" || printDanger "$error"
|
||||
else
|
||||
runError error rsync -a --link-dest="$backupDailyPath/$backupFirstDir" -- "$olsVhostsPath/" "$path" || printDanger "$error"
|
||||
fi
|
||||
|
||||
printText "Databases..."
|
||||
while read -r domain; do
|
||||
runError error backupSiteDatabase "$domain" "$path/$domain.sql.tar.gz" || printDanger "$error"
|
||||
done < <(awk 'NF' <<< "$vhostList")
|
||||
|
||||
printText "Configs..."
|
||||
while read -r domain; do
|
||||
runError error cp -p -- "$olsVhostsConfigPath/$domain.conf" "$path/$domain.conf" || printDanger "$error"
|
||||
done < <(awk 'NF' <<< "$vhostList")
|
||||
}
|
||||
|
||||
# Runs a backup for all sites using the configured or given backup type.
|
||||
# [$1] (path): destination directory.
|
||||
# [$2] (type): backup type: zip, tar, or rsync.
|
||||
function cmdBackupSites() {
|
||||
local path="$1"
|
||||
path=$(backupPathGenerate "$path")
|
||||
|
||||
local type="${2:-$backupType}"
|
||||
|
||||
case "$type" in
|
||||
zip|tar)
|
||||
cmdBackupSitesArchive "$path" "$type" || return 1
|
||||
;;
|
||||
rsync)
|
||||
cmdBackupSitesRsync "$path" || return 1
|
||||
;;
|
||||
*)
|
||||
printSection "Config"
|
||||
printDanger "Unknown backup type: $type"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function cmdBackupArchiveDispatch() {
|
||||
printSection "Config"
|
||||
[[ -n "$backupArchivePath" ]] || { printDotText "Path" "$labelNull"; return 1; }
|
||||
printDotText "Path" "$backupArchivePath"
|
||||
[[ -n "$backupArchiveInterval" ]] || { printDotText "Period" "$labelNull"; return 1; }
|
||||
printDotText "Period" "$backupArchiveInterval"
|
||||
[[ -n "$backupArchiveDirPattern" ]] || { printDotText "Pattern" "$labelNull"; return 1; }
|
||||
printDotText "Pattern" "$backupArchiveDirPattern"
|
||||
|
||||
mkdir -p -- "$backupArchivePath" || { printDanger "Failed to create archive path"; return 1; }
|
||||
|
||||
local dirList archiveList dailyList error
|
||||
run dirList error fileList "$backupArchivePath" d || { printDanger "$error"; return 1; }
|
||||
archiveList=$(printf '%s\n' "$dirList" | grep -E -- "$backupArchiveDirPattern" | sort || true)
|
||||
|
||||
run dirList error fileList "$backupDailyPath" d || { printDanger "$error"; return 1; }
|
||||
dailyList=$(printf '%s\n' "$dirList" | grep -E -- "$backupDailyDirPattern" | sort || true)
|
||||
|
||||
printSection "Directories found"
|
||||
local archiveCount archiveRemoveCount i=0 num label dirDate dirDays archiveRemoveList=""
|
||||
archiveCount=$(grep -c . <<< "$archiveList" || true)
|
||||
if [[ "$archiveCount" -gt 0 ]]; then
|
||||
while read -r dir; do
|
||||
((++i))
|
||||
num=$(printf "%0${#archiveCount}d" "$i")
|
||||
label="$num: $fontBlue$dir$fontReset"
|
||||
|
||||
if (( i == archiveCount )); then
|
||||
printDotText "$label" "${fontGreen}save$fontReset"
|
||||
elif (( i == archiveCount - 1 )); then
|
||||
dirDate="${dir%$backupArchiveSuffix}"
|
||||
dirDays=$(( $(timeDiff "$dirDate" "$appDate") / 86400 ))
|
||||
if (( dirDays >= backupArchiveInterval )); then
|
||||
printDotText "$label" "${fontRed}delete$fontReset"
|
||||
archiveRemoveList+=$'\n'"$dir"
|
||||
else
|
||||
printDotText "$label" "${fontGreen}save$fontReset"
|
||||
fi
|
||||
else
|
||||
printDotText "$label" "${fontRed}delete$fontReset"
|
||||
archiveRemoveList+=$'\n'"$dir"
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$archiveList")
|
||||
|
||||
archiveRemoveCount=$(grep -c . <<< "$archiveRemoveList" || true)
|
||||
if [[ "$archiveRemoveCount" -gt 0 ]]; then
|
||||
printSection "Deleting Directories"
|
||||
|
||||
while read -r dir; do
|
||||
label="$backupArchivePath/$dir"
|
||||
if [[ -n "$dir" ]]; then
|
||||
if rm -rf -- "$backupArchivePath/$dir" &>/dev/null; then
|
||||
printDotText "$label" "$labelDone"
|
||||
else
|
||||
printDotText "$label" "$labelFail"
|
||||
fi
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$archiveRemoveList")
|
||||
fi
|
||||
fi
|
||||
|
||||
# Promote oldest daily (excluding today) to archive
|
||||
run dirList error fileList "$backupArchivePath" d || { printDanger "$error"; return 1; }
|
||||
archiveList=$(printf '%s\n' "$dirList" | grep -E -- "$backupArchiveDirPattern" | sort || true)
|
||||
archiveCount=$(grep -c . <<< "$archiveList" || true)
|
||||
if (( archiveCount < 2 )); then
|
||||
local oldestDaily
|
||||
oldestDaily=$(head -1 <<< "$dailyList" || true)
|
||||
[[ -n "$oldestDaily" ]] || return 0
|
||||
[[ "$oldestDaily" != "$appDate" ]] || return 0
|
||||
|
||||
printSection "Promote Directories"
|
||||
mv -- "$backupDailyPath/$oldestDaily" "$backupArchivePath/${oldestDaily}$backupArchiveSuffix" || {
|
||||
printDotText "$backupDailyPath/$oldestDaily" "$labelFail"
|
||||
printDanger "Failed to promote daily to archive: $oldestDaily"
|
||||
return 1
|
||||
}
|
||||
printDotText "$backupDailyPath/$oldestDaily" "$labelDone"
|
||||
fi
|
||||
}
|
||||
|
||||
function cmdBackupDailyDispatch() {
|
||||
printSection "Config"
|
||||
[[ -n "$backupDailyPath" ]] || { printDotText "Path" "$labelNull"; return 1; }
|
||||
printDotText "Path" "$backupDailyPath"
|
||||
[[ -n "$backupDailyKeep" ]] || { printDotText "Keep" "$labelNull"; return 1; }
|
||||
printDotText "Keep" "$backupDailyKeep"
|
||||
[[ -n "$backupDailyDirPattern" ]] || { printDotText "Pattern" "$labelNull"; return 1; }
|
||||
printDotText "Pattern" "$backupDailyDirPattern"
|
||||
|
||||
mkdir -p -- "$backupDailyPath" || { printDanger "Failed to create archive path"; return 1; }
|
||||
|
||||
local dirList dailyList error
|
||||
run dirList error fileList "$backupDailyPath" d || { printDanger "$error"; return 1; }
|
||||
dailyList=$(printf '%s\n' "$dirList" | grep -v "$appDate" | grep -E -- "$backupDailyDirPattern" | sort || true)
|
||||
|
||||
printSection "Directories found"
|
||||
local dailyCount dailyRemoveCount i=0 num label dailyRemoveList=""
|
||||
dailyCount=$(grep -c . <<< "$dailyList" || true)
|
||||
|
||||
if [[ "$dailyCount" -gt 0 ]]; then
|
||||
while read -r dir; do
|
||||
((++i))
|
||||
num=$(printf "%0${#dailyCount}d" "$i")
|
||||
label="$num: $fontBlue$dir$fontReset"
|
||||
|
||||
if [[ "$dir" == "$appDate" ]]; then
|
||||
printDotText "$label" "${fontGray}skipped$fontReset"
|
||||
elif (( dailyCount - backupDailyKeep >= i )); then
|
||||
printDotText "$label" "${fontRed}delete$fontReset"
|
||||
dailyRemoveList+=$'\n'"$dir"
|
||||
else
|
||||
printDotText "$label" "${fontGreen}save$fontReset"
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$dailyList")
|
||||
|
||||
dailyRemoveCount=$(grep -c . <<< "$dailyRemoveList" || true)
|
||||
if [[ "$dailyRemoveCount" -gt 0 ]]; then
|
||||
printSection "Deleting Directories"
|
||||
while read -r dir; do
|
||||
label="$backupDailyPath/$dir"
|
||||
if [[ -n "$dir" ]]; then
|
||||
if rm -rf -- "$backupDailyPath/$dir" &>/dev/null; then
|
||||
printDotText "$label" "$labelDone"
|
||||
else
|
||||
printDotText "$label" "$labelFail"
|
||||
fi
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$dailyRemoveList")
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function cmdBackupDispatch() {
|
||||
local second=0 interval=0
|
||||
|
||||
printText "$fontMagenta[Step 1 Processing of archive backups on external storage]$fontReset"
|
||||
cmdStorageBackupArchiveDispatch
|
||||
seconds=$(timeDiff "$appDate ${appTime//-/:}" "$(date +%Y-%m-%d) $(date +%H:%M:%S)")
|
||||
printDotText "Complete" "$(date +%Y-%m-%d) $(date +%H:%M:%S) $fontBlue$((seconds-interval))s$fontReset"
|
||||
interval="$seconds"
|
||||
|
||||
printRow
|
||||
printText "$fontMagenta[Step 2 Processing of daily backups on external storage]$fontReset"
|
||||
cmdStorageBackupDailyDispatch
|
||||
seconds=$(timeDiff "$appDate ${appTime//-/:}" "$(date +%Y-%m-%d) $(date +%H:%M:%S)")
|
||||
printDotText "Complete" "$(date +%Y-%m-%d) $(date +%H:%M:%S) $fontBlue$((seconds-interval))s$fontReset"
|
||||
interval="$seconds"
|
||||
|
||||
printRow
|
||||
printText "$fontMagenta[Step 3 Processing of archive backups on host (SKIP...)]$fontReset"
|
||||
# cmdBackupArchiveDispatch
|
||||
# seconds=$(timeDiff "$appDate ${appTime//-/:}" "$(date +%Y-%m-%d) $(date +%H:%M:%S)")
|
||||
# printDotText "Complete" "$(date +%Y-%m-%d) $(date +%H:%M:%S) $fontBlue$((seconds-interval))s$fontReset"
|
||||
# interval="$seconds"
|
||||
|
||||
printRow
|
||||
printText "$fontMagenta[Step 4 Processing of daily backups on host]$fontReset"
|
||||
cmdBackupDailyDispatch
|
||||
seconds=$(timeDiff "$appDate ${appTime//-/:}" "$(date +%Y-%m-%d) $(date +%H:%M:%S)")
|
||||
printDotText "Complete" "$(date +%Y-%m-%d) $(date +%H:%M:%S) $fontBlue$((seconds-interval))s$fontReset"
|
||||
interval="$seconds"
|
||||
|
||||
printRow
|
||||
printText "$fontMagenta[Step 5 Creating a full daily backup]$fontReset"
|
||||
cmdBackupSites
|
||||
seconds=$(timeDiff "$appDate ${appTime//-/:}" "$(date +%Y-%m-%d) $(date +%H:%M:%S)")
|
||||
printDotText "Complete" "$(date +%Y-%m-%d) $(date +%H:%M:%S) $fontBlue$((seconds-interval))s$fontReset"
|
||||
interval="$seconds"
|
||||
|
||||
printRow
|
||||
printText "$fontMagenta[Step 6 Synchronization with external storage (daily backups)]$fontReset"
|
||||
cmdStorageBackupDailySyncLast
|
||||
seconds=$(timeDiff "$appDate ${appTime//-/:}" "$(date +%Y-%m-%d) $(date +%H:%M:%S)")
|
||||
printDotText "Complete" "$(date +%Y-%m-%d) $(date +%H:%M:%S) $fontBlue$((seconds-interval))s$fontReset"
|
||||
interval="$seconds"
|
||||
|
||||
printRow
|
||||
printText "$fontMagenta[Step 7 Synchronization with external storage (archive backups) (SKIP...)]$fontReset"
|
||||
# cmdStorageBackupArchiveSyncLast
|
||||
# seconds=$(timeDiff "$appDate ${appTime//-/:}" "$(date +%Y-%m-%d) $(date +%H:%M:%S)")
|
||||
# printDotText "Complete" "$(date +%Y-%m-%d) $(date +%H:%M:%S) $fontBlue$((seconds-interval))s$fontReset"
|
||||
}
|
||||
|
||||
# Runs a backup for a single site or all sites.
|
||||
# [$1] (target): site domain or "all".
|
||||
# [$2] (path): destination directory.
|
||||
# [$3] (type): backup type.
|
||||
function cmdBackupRun() {
|
||||
local target="$1"
|
||||
[[ -n "$target" ]] || { printRow; printDanger "Target not specified"; return 1; }
|
||||
|
||||
local path="$2"
|
||||
path=$(backupPathGenerate "$path")
|
||||
|
||||
local type="${3:-$backupType}"
|
||||
|
||||
if [[ "$target" == "all" ]]; then
|
||||
cmdBackupSites "$path" "$type" || return 1
|
||||
else
|
||||
printSection "Config"
|
||||
printDotText "Target" "$target"
|
||||
printDotText "Type" "$type"
|
||||
printDotText "Path" "$path"
|
||||
|
||||
printSection "Executing"
|
||||
|
||||
local label error
|
||||
if runError error backupSite "$target" "$path" "$type"; then
|
||||
printDotText "$target" "$labelDone"
|
||||
else
|
||||
printDotText "$target" "$labelFail"
|
||||
printDanger "$error"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function cmdBackupList() {
|
||||
printRow
|
||||
|
||||
local dirList archiveList dailyList backupList backupCount error
|
||||
run dirList error fileList "$backupArchivePath" d || { printDanger "$error"; return 1; }
|
||||
archiveList=$(printf '%s\n' "$dirList" | grep -E -- "$backupArchiveDirPattern" | sort || true)
|
||||
|
||||
run dirList error fileList "$backupDailyPath" d || { printDanger "$error"; return 1; }
|
||||
dailyList=$(printf '%s\n' "$dirList" | grep -E -- "$backupDailyDirPattern" | sort || true)
|
||||
|
||||
backupList=$(printf '%s\n' "$archiveList" "$dailyList" | awk 'NF' | sort)
|
||||
backupCount=$(grep -c . <<< "$backupList" || true)
|
||||
local i=0 num dir label
|
||||
while read -r dir; do
|
||||
((++i))
|
||||
num=$(printf "%0${#backupCount}d" "$i")
|
||||
label="$num: $fontBlue$dir$fontReset"
|
||||
|
||||
if listContains "$dir" "$archiveList"; then
|
||||
printDotText "$label" "${fontGreen}archive$fontReset"
|
||||
elif listContains "$dir" "$dailyList"; then
|
||||
printDotText "$label" "${fontGreen}daily$fontReset"
|
||||
else
|
||||
printDotText "$label" "$labelUnknown"
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$backupList")
|
||||
}
|
||||
|
||||
function cmdBackupSize() {
|
||||
local dirList file size total=0 archiveList dailyList
|
||||
|
||||
printSection "Archive: $backupArchivePath"
|
||||
run dirList error fileList "$backupArchivePath" d || { printDanger "$error"; return 1; }
|
||||
archiveList=$(printf '%s\n' "$dirList" | grep -E -- "$backupArchiveDirPattern" | sort || true)
|
||||
while IFS= read -r file; do
|
||||
size=$(pathSize "$backupArchivePath/$file" "gb")
|
||||
printDotText "$file" "$size Gb"
|
||||
(( total += size ))
|
||||
done <<< "$archiveList"
|
||||
|
||||
printSection "Daily: $backupDailyPath"
|
||||
run dirList error fileList "$backupDailyPath" d || { printDanger "$error"; return 1; }
|
||||
dailyList=$(printf '%s\n' "$dirList" | grep -E -- "$backupDailyDirPattern" | sort || true)
|
||||
while IFS= read -r file; do
|
||||
size=$(pathSize "$backupDailyPath/$file" "gb")
|
||||
printDotText "$file" "$size Gb"
|
||||
(( total += size ))
|
||||
done <<< "$dailyList"
|
||||
|
||||
printRow
|
||||
printDotText "total" "$total Gb"
|
||||
}
|
||||
@@ -1,335 +0,0 @@
|
||||
function cmdHelpPrint() {
|
||||
printf "%b" "\n$fontYellow $1$fontReset\n$2\n"
|
||||
}
|
||||
|
||||
function cmdHelpK3S() {
|
||||
local title="-k|--k3s <action>"
|
||||
local desc="
|
||||
create - create all resources in k3s
|
||||
remove - remove all resources in k3s
|
||||
info - detail about a k3s
|
||||
status - status about a k3s
|
||||
top - top pod
|
||||
res [<resource>] - get resource info (all|pod|event|pv|pvc|deploy|ds|rs|sts|svc|ing|ip|secret|cm|job|cj|ep|no|ns|cs|netpol)"
|
||||
|
||||
cmdHelpPrint "$title" "$desc"
|
||||
}
|
||||
|
||||
function cmdHelpMariaDB() {
|
||||
local title="-m|--mariadb <action>"
|
||||
local desc="
|
||||
install - install MariaDB in k3s
|
||||
update - update MariaDB in k3s
|
||||
uninstall - uninstall MariaDB from k3s
|
||||
info - detail about a MariaDB
|
||||
status - status about a MariaDB
|
||||
replica - replica rebuild
|
||||
database - database list
|
||||
user - user list
|
||||
dump [all|<database>] [<file>] - dump specified database or all databases from Master
|
||||
dump_slave [<file>] - full dump from Slave"
|
||||
|
||||
cmdHelpPrint "$title" "$desc"
|
||||
}
|
||||
|
||||
function cmdHelpRedis() {
|
||||
local title="-r|--redis <action>"
|
||||
local desc="
|
||||
install - install Redis in k3s
|
||||
update - update Redis in k3s
|
||||
uninstall - uninstall Redis from k3s
|
||||
info - detail about a Redis
|
||||
status - status about a Redis
|
||||
user - user list
|
||||
flush - flush current DB
|
||||
pass - update default (root) pass"
|
||||
|
||||
cmdHelpPrint "$title" "$desc"
|
||||
}
|
||||
|
||||
function cmdHelpOpenLiteSpeed() {
|
||||
local title="-o|--ols <action>"
|
||||
local desc="
|
||||
install - install OpenLiteSpeed in k3s
|
||||
update - update OpenLiteSpeed in k3s
|
||||
uninstall - uninstall OpenLiteSpeed from k3s
|
||||
info - detail about a OpenLiteSpeed
|
||||
list <option> - vhost list (all|up|down)
|
||||
up - unpause vhost
|
||||
down - pause vhost
|
||||
alias - set aliases for domain
|
||||
restart - restart OLS (graceful restart)
|
||||
php_kill all|<domain> - kill lsphp for domain or all domains
|
||||
white_list - WebAdmin white list update
|
||||
allow_list - WebAdmin allow list update
|
||||
alias_list all|<domain> - get aliases for domain or all domains
|
||||
rebuild all|<domain> - rebuild owner and permission files
|
||||
config - check config $olsConfigFile"
|
||||
|
||||
cmdHelpPrint "$title" "$desc"
|
||||
}
|
||||
|
||||
function cmdHelpPostfix() {
|
||||
local title="-p|--postfix <action>"
|
||||
local desc="
|
||||
install - install Postfix in k3s
|
||||
update - update Postfix in k3s
|
||||
uninstall - uninstall Postfix from k3s
|
||||
info - detail about a Postfix
|
||||
status - status about a Postfix
|
||||
user - user list (SASL)
|
||||
dkim [<domain>] - autocreate and display DKIM key for DNS record or all domains !!!"
|
||||
|
||||
cmdHelpPrint "$title" "$desc"
|
||||
}
|
||||
|
||||
function cmdHelpWorker() {
|
||||
local title="-w|--worker <action>"
|
||||
local desc="
|
||||
install - install Worker in k3s
|
||||
update - update Worker in k3s
|
||||
uninstall - uninstall Worker from k3s
|
||||
list - task list
|
||||
down - stop receiving new tasks from the API (pause)
|
||||
up - start receiving new tasks from the API (unpause)
|
||||
task <file> [<domain>] - Execute task !!!!!"
|
||||
|
||||
cmdHelpPrint "$title" "$desc"
|
||||
}
|
||||
|
||||
function cmdHelpMetric() {
|
||||
local title="--metric <action>"
|
||||
local desc="
|
||||
install - install Metric in k3s
|
||||
update - update Metric in k3s
|
||||
uninstall - remove Metric from k3s
|
||||
restart - restart !!!!"
|
||||
|
||||
cmdHelpPrint "$title" "$desc"
|
||||
}
|
||||
|
||||
function cmdHelpSite() {
|
||||
local title="-s|--site <action>"
|
||||
local desc="
|
||||
add <domain> [<pathF>] [<pathD>] - add site (pathF: source files | pathD: source database)
|
||||
add_wp|wp <domain> [<pathF>] [<pathD>] - add site on Wordpress
|
||||
remove <domain> [-f|--force] - site full remove (-f|--force - forced mode)
|
||||
copy <domain> <domainNew> - create copy of site
|
||||
rename <domain> <domainNew> - rename of site
|
||||
info <domain> - view site info and settings
|
||||
status <domain> - check and verify site settings
|
||||
rebuild <domain> - rebuild config for domain (in MariaDB, Redis, OLS, ...)
|
||||
rebuild_wp <domain> - rewrite config connection to internal services in bootstrap.php
|
||||
reset_pass all|<domain> - reset ALL passwords for domain or all domains
|
||||
reset_auth all|<domain> - reset ALL authentication settings for domain or all domains
|
||||
dump <domain> [<file>] - dump database of site"
|
||||
|
||||
cmdHelpPrint "$title" "$desc"
|
||||
}
|
||||
|
||||
function cmdHelpWP() {
|
||||
local title="--wp <action>"
|
||||
local desc="
|
||||
user <domain> - user list
|
||||
pass <domain> <user> <pass> - user password set
|
||||
salt all|<domain> - shuffle salts
|
||||
check all|<domain> - check core and plugins
|
||||
exec all|<domain> <command> - command exec"
|
||||
|
||||
cmdHelpPrint "$title" "$desc"
|
||||
}
|
||||
|
||||
function cmdHelpSftp() {
|
||||
local title="--sftp <action>"
|
||||
local desc="
|
||||
enable <domain> - enable sftp access
|
||||
disable <domain> - disable sftp access
|
||||
reset_pass <domain> - reset pass
|
||||
user - list of users with SFTP access (group: $sftpAccessGroup)
|
||||
support - adding support for SFTP access (group: $sftpAccessGroup)"
|
||||
|
||||
cmdHelpPrint "$title" "$desc"
|
||||
}
|
||||
|
||||
function cmdHelpCron() {
|
||||
local title="--cron <action>"
|
||||
local desc="
|
||||
add - add jobs to cron
|
||||
remove - remove jobs from cron
|
||||
list - task list"
|
||||
|
||||
cmdHelpPrint "$title" "$desc"
|
||||
}
|
||||
|
||||
function cmdHelpBackup() {
|
||||
local title="--backup <action>"
|
||||
local desc="
|
||||
run all|<domain> [<path>] [<type>] - backup
|
||||
path: path to save backup, default: autogenerate
|
||||
type: type backup (zip|tar|archive|rsync), default: $backupType
|
||||
list - list backups on local
|
||||
size - list of backup sizes"
|
||||
|
||||
cmdHelpPrint "$title" "$desc"
|
||||
}
|
||||
|
||||
function cmdHelpRestore() {
|
||||
local title="--restore <domain> $fontRed[NO TESTED!]$fornReset"
|
||||
local desc="
|
||||
run <domain> [<option>] - manual site restore
|
||||
<empty> - manual site restore
|
||||
list - display a list of available markers for restore
|
||||
last - automatic site restore from the last backup
|
||||
full - automatic site restore from the last FULL backup
|
||||
auto - automatic site restore from the last FULL backup or the last backup
|
||||
N - automatic site restore from the last backup #N (N: 1+)"
|
||||
|
||||
cmdHelpPrint "$title" "$desc"
|
||||
}
|
||||
|
||||
function cmdHelpStorage() {
|
||||
local title="--storage [option]"
|
||||
local desc="
|
||||
list - list backups on external storage
|
||||
compare - comparison table
|
||||
size - list of backup sizes
|
||||
sync <type> - synchronization with external storage (archive|daily)
|
||||
remove - remove backups on external storage"
|
||||
|
||||
cmdHelpPrint "$title" "$desc"
|
||||
}
|
||||
|
||||
function cmdHelpScript() {
|
||||
local title="--script <action>"
|
||||
local desc="
|
||||
backup - creating a backup all sites and then synchronizing with external storage
|
||||
backup-long-term - creating long-term backups
|
||||
backup-clean - cleanup of old backups on the current host and on external storage
|
||||
mariadb-dump - creating a backup of all databases in MariaDB
|
||||
worker-observer - launching the task observer
|
||||
metric-kube - send kube metrics to API (Grafana)
|
||||
metric-sites - send sites metrics to API
|
||||
diag-report-ai-short - send diag short report to AI
|
||||
diag-report-ai-full - send diag full report to AI"
|
||||
|
||||
cmdHelpPrint "$title" "$desc"
|
||||
}
|
||||
|
||||
function cmdHelpTest() {
|
||||
local title="--test <action> $fontRed[NO TESTED!]$fornReset"
|
||||
local desc="
|
||||
mariadb - test MariaDB replica
|
||||
redis - test Redis cluster
|
||||
site - test create default site
|
||||
wordpress - test create Wordpress site"
|
||||
|
||||
cmdHelpPrint "$title" "$desc"
|
||||
}
|
||||
|
||||
function cmdHelpMalware() {
|
||||
local title="--malware <action>"
|
||||
local desc="
|
||||
check - check malware in all vhosts
|
||||
remove - remove malware in all vhosts"
|
||||
|
||||
cmdHelpPrint "$title" "$desc"
|
||||
}
|
||||
|
||||
function cmdHelpShell() {
|
||||
local title="--shell [cli]"
|
||||
local desc="
|
||||
opening the shell or cli (if any) in the pods"
|
||||
|
||||
cmdHelpPrint "$title" "$desc"
|
||||
}
|
||||
|
||||
function cmdHelpLog() {
|
||||
local title="--log [parameters]"
|
||||
local desc="
|
||||
opening the logs in the pods. Standard kubectl parameters for logs can be added, for example:
|
||||
-f: logs should be streamed
|
||||
--previous: print the logs for the previous instance of the container in a pod if it exists"
|
||||
|
||||
cmdHelpPrint "$title" "$desc"
|
||||
}
|
||||
|
||||
function cmdHelpDescribe() {
|
||||
local title="--describe <option>"
|
||||
local desc="
|
||||
pod - describe pods
|
||||
node - describe node"
|
||||
|
||||
cmdHelpPrint "$title" "$desc"
|
||||
}
|
||||
|
||||
function cmdHelpDelete() {
|
||||
local title="--delete"
|
||||
local desc="
|
||||
delete pods"
|
||||
|
||||
cmdHelpPrint "$title" "$desc"
|
||||
}
|
||||
|
||||
function cmdHelpInstall() {
|
||||
local title="--install"
|
||||
local desc="
|
||||
install full infrastructure (apk, update ipset/iptables, install k3s, apply yaml...)"
|
||||
|
||||
cmdHelpPrint "$title" "$desc"
|
||||
}
|
||||
|
||||
function cmdHelp() {
|
||||
local title="$appName"
|
||||
local desc="
|
||||
Usage: $0 [arguments...]"
|
||||
|
||||
cmdHelpPrint "$title" "$desc"
|
||||
|
||||
printDotFix 20 "$fontBlue -k|--k3s" "Commands for k3s"
|
||||
printDotFix 20 "$fontBlue -m|--mariadb" "Commands for MariaDB"
|
||||
printDotFix 20 "$fontBlue -r|--redis" "Commands for Redis"
|
||||
printDotFix 20 "$fontBlue -o|--ols" "Commands for Openlitespeed"
|
||||
printDotFix 20 "$fontBlue -p|--postfix" "Commands for Postfix"
|
||||
printDotFix 20 "$fontBlue -w|--worker" "Commands for Worker (agent)"
|
||||
printDotFix 20 "$fontBlue -s|--site" "Commands for Sites"
|
||||
printDotFix 20 "$fontBlue --metric" "Commands for Metrics"
|
||||
printDotFix 20 "$fontBlue --wp" "Commands for Wordpress"
|
||||
printDotFix 20 "$fontBlue --sftp" "Commands for SFTP"
|
||||
printDotFix 20 "$fontBlue --cron" "Commands for Cron"
|
||||
printDotFix 20 "$fontBlue --shell" "Shell or cli (if any) in pods"
|
||||
printDotFix 20 "$fontBlue --log" "Log of pods"
|
||||
printDotFix 20 "$fontBlue --describe" "Describe of pods"
|
||||
printDotFix 20 "$fontBlue --delete" "Delete pods"
|
||||
printDotFix 20 "$fontBlue --backup" "Create backup of sites"
|
||||
printDotFix 20 "$fontBlue --restore" "Restore sites from backups"
|
||||
printDotFix 20 "$fontBlue --storage" "Copy backups to storage"
|
||||
printDotFix 20 "$fontBlue --script" "Execute scripts"
|
||||
printDotFix 20 "$fontBlue --install" "Install full infrastructure"
|
||||
printDotFix 20 "$fontBlue --test" "Testing infrastructure"
|
||||
printDotFix 20 "$fontBlue --malware" "Malware search"
|
||||
printDotFix 20 "$fontBlue --help" "This help"
|
||||
printRow
|
||||
|
||||
cmdHelpK3S
|
||||
cmdHelpMariaDB
|
||||
cmdHelpRedis
|
||||
cmdHelpOpenLiteSpeed
|
||||
cmdHelpPostfix
|
||||
cmdHelpWorker
|
||||
cmdHelpSite
|
||||
cmdHelpMetric
|
||||
cmdHelpWP
|
||||
cmdHelpSftp
|
||||
cmdHelpCron
|
||||
cmdHelpShell
|
||||
cmdHelpLog
|
||||
cmdHelpDescribe
|
||||
cmdHelpDelete
|
||||
cmdHelpBackup
|
||||
cmdHelpRestore
|
||||
cmdHelpStorage
|
||||
cmdHelpScript
|
||||
cmdHelpInstall
|
||||
cmdHelpTest
|
||||
cmdHelpMalware
|
||||
}
|
||||
@@ -1,378 +0,0 @@
|
||||
k3sLabel="[K3S]"
|
||||
|
||||
# Interactively lists pods and stores the selected pod name in the given variable.
|
||||
# $1 (varname): name of the variable to store the selected pod name.
|
||||
function cmdK3sPodSelect() {
|
||||
local -n __pod="$1"
|
||||
|
||||
printRow
|
||||
|
||||
local podList error total
|
||||
run podList error k3sPodListStatus || { printDanger "k3sPodListStatus: $error"; return 1; }
|
||||
[[ -n "$podList" ]] || { printRow printDanger "Pods not found"; return 1; }
|
||||
total=$(grep -c . <<< "$podList")
|
||||
|
||||
local i=0 line name status color num
|
||||
while IFS= read -r line; do
|
||||
[[ -z "$line" ]] && continue
|
||||
((i++))
|
||||
num=$(printf "%${#total}d" "$i")
|
||||
name="${line%%|*}"
|
||||
status="${line#*|}"
|
||||
|
||||
color="$fontYellow"
|
||||
[[ "$status" == "Running" ]] && color="$fontGreen"
|
||||
[[ "$status" == "NotReady" ]] && color="$fontRed"
|
||||
[[ "$status" == "Terminating" ]] && color="$fontRed"
|
||||
|
||||
printDotText "$num: $fontBlue$name$fontReset" "$color$status$fontReset"
|
||||
done <<< "$podList"
|
||||
|
||||
printRow
|
||||
local input item selected
|
||||
read -r -p "Specify the pod number: " input
|
||||
|
||||
printRow
|
||||
[[ -z "$input" ]] && input=0
|
||||
[[ "$input" =~ ^[0-9]+$ ]] || { printDanger "Invalid pod number"; return 1; }
|
||||
item=$((10#$input))
|
||||
selected=$(awk 'NF {n++} n == item {print; exit}' item="$item" <<< "$podList")
|
||||
[[ -n "$selected" ]] || { printDanger "Unknown pod number"; return 1; }
|
||||
|
||||
__pod="${selected%%|*}"
|
||||
}
|
||||
|
||||
# Interactively opens a shell or CLI inside a selected pod.
|
||||
# [$1] (cli): pass "cli" to open the native CLI (mariadb/redis-cli) instead of a shell.
|
||||
function cmdShell() {
|
||||
printTitle " Shell $@"
|
||||
|
||||
local cli="$1"
|
||||
local pod
|
||||
cmdK3sPodSelect pod || return 1
|
||||
|
||||
local resource resourceEx
|
||||
resource=$(printf '%s' "$pod" | sed -E 's/-([0-9a-f]{8,}-[a-z0-9]+|[a-z0-9]{5}|[0-9]+)$//')
|
||||
resourceEx=$(printf '%s' "$pod" | sed -E 's/-([-a-z0-9]+)$//')
|
||||
local container=(-c "$resource")
|
||||
local cmd=(bash)
|
||||
local cmdLog=(bash)
|
||||
case "$resourceEx" in
|
||||
"$redisKube"|"$metricKube"|debug)
|
||||
cmd=(sh)
|
||||
cmdLog=(sh)
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ "$cli" == "cli" ]]; then
|
||||
case "$resource" in
|
||||
"$mariadbMasterKube"|"$mariadbSlaveKube")
|
||||
cmd=(mariadb -uroot -p"$mariadbRootPass")
|
||||
cmdLog=(mariadb -uroot -p"********")
|
||||
;;
|
||||
"$redisKube")
|
||||
if [[ -z "$redisRootPass" ]]; then
|
||||
cmd=(redis-cli)
|
||||
cmdLog=(redis-cli)
|
||||
else
|
||||
cmd=(redis-cli --no-auth-warning -a "$redisRootPass")
|
||||
cmdLog=(redis-cli --no-auth-warning -a"********")
|
||||
fi
|
||||
;;
|
||||
"$redisSentinelKube")
|
||||
if [[ -z "$redisRootPass" ]]; then
|
||||
cmd=(redis-cli -p 26379)
|
||||
cmdLog=(redis-cli -p 26379)
|
||||
else
|
||||
cmd=(redis-cli --no-auth-warning -p 26379 -a "$redisRootPass")
|
||||
cmdLog=(redis-cli --no-auth-warning -p 26379 -a"********")
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
printText "$fontBlue$k3sCmd kubectl -n $k3sNamespace exec -it pod/$pod ${container[*]} -- ${cmdLog[*]}$fontReset"
|
||||
printRow
|
||||
k3sRun exec -it "pod/$pod" "${container[@]}" -- "${cmd[@]}"
|
||||
}
|
||||
|
||||
# Interactively selects a pod and streams its logs.
|
||||
# [$@] (...): extra arguments passed to kubectl logs (e.g. -f, --tail=100).
|
||||
function cmdLog() {
|
||||
printTitle " Log $@"
|
||||
|
||||
local pod
|
||||
cmdK3sPodSelect pod || return 1
|
||||
|
||||
local resource
|
||||
resource=$(printf '%s' "$pod" | sed -E 's/-([0-9a-f]{8,}-[a-z0-9]+|[a-z0-9]{5}|[0-9]+)$//')
|
||||
local container=(-c "$resource")
|
||||
|
||||
printText "$fontBlue$k3sCmd kubectl -n $k3sNamespace logs pod/$pod ${container[*]} $*$fontReset"
|
||||
printRow
|
||||
k3sRun logs "pod/$pod" "${container[@]}" "$@"
|
||||
}
|
||||
|
||||
# Interactively selects a pod and describes it; also supports describing a node.
|
||||
# [$1] (target): pod (default) or node.
|
||||
function cmdDescribe() {
|
||||
local target="${1:-pod}"
|
||||
shift || true
|
||||
|
||||
printTitle " Describe $@"
|
||||
|
||||
case "$target" in
|
||||
pod)
|
||||
local pod
|
||||
cmdK3sPodSelect pod || return 1
|
||||
|
||||
printText "$fontBlue$k3sCmd kubectl -n $k3sNamespace describe pod/$pod $*$fontReset"
|
||||
printRow
|
||||
k3sRun describe "pod/$pod" "$@"
|
||||
k3sRun describe "pod/$pod" "$@"
|
||||
;;
|
||||
node)
|
||||
k3sRun describe node "$@"
|
||||
;;
|
||||
*)
|
||||
printRow
|
||||
printWarning "Unsupported or empty command: $target"
|
||||
cmdHelpDescribe
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Interactively selects and deletes a pod.
|
||||
function cmdDelete() {
|
||||
printTitle " Delete $@ $fontRed[DANGER]$fontReset"
|
||||
|
||||
local pod
|
||||
cmdK3sPodSelect pod || return 1
|
||||
|
||||
printText "$fontBlue$k3sCmd kubectl -n $k3sNamespace delete pod/$pod $*$fontReset"
|
||||
printRow
|
||||
k3sRun delete "pod/$pod" "$@"
|
||||
}
|
||||
|
||||
# Lists k3s resources; interactively prompts for type if not provided.
|
||||
# [$1] (resource): resource type abbreviation (pod, deploy, svc, etc.).
|
||||
function cmdK3sResourceList() {
|
||||
local resource="$1"
|
||||
if [[ ! "$resource" =~ ^(all|pod|event|pv|pvc|deploy|ds|rs|sts|svc|ing|ip|secret|cm|job|cj|ep|no|ns|cs|netpol)$ ]]; then
|
||||
local resList="
|
||||
all|Get all resources
|
||||
pod|Pod
|
||||
event|Event
|
||||
pv|PersistentVolume
|
||||
pvc|PersistentVolumeClaim
|
||||
deploy|Deployment
|
||||
ds|DaemonSet
|
||||
rs|ReplicaSet
|
||||
sts|StatefulSet
|
||||
svc|Service
|
||||
ing|Ingress
|
||||
ip|IPAddress
|
||||
secret|Secret
|
||||
cm|ConfigMap
|
||||
job|Job
|
||||
cj|CronJob
|
||||
ep|Endpoint
|
||||
no|Node
|
||||
ns|Namespace
|
||||
cs|ComponentStatuses
|
||||
netpol|NetworkPolicies"
|
||||
|
||||
printRow
|
||||
local i=0 line code info num
|
||||
total=$(grep -c . <<< "$resList")
|
||||
while IFS= read -r line; do
|
||||
[[ -n "$line" ]] || continue
|
||||
((i++))
|
||||
num=$(printf "%${#total}d" "$i")
|
||||
code="${line%%|*}"
|
||||
info="${line#*|}"
|
||||
printDotFix 20 "$num: $fontBlue$code$fontReset" "$info"
|
||||
done <<< "$resList"
|
||||
|
||||
printRow
|
||||
local input item selected
|
||||
read -r -p "Specify the type number [0]: " input
|
||||
|
||||
printRow
|
||||
[[ -z "$input" ]] && input=0
|
||||
[[ "$input" =~ ^[0-9]+$ ]] || { printDanger "Invalid type number"; return 1; }
|
||||
item=$((10#$input))
|
||||
selected=$(awk 'NF {n++} n == item {print; exit}' item="$item" <<< "$resList")
|
||||
[[ -n "$selected" ]] || { printDanger "Unknown type number"; return 1; }
|
||||
resource="${selected%%|*}"
|
||||
fi
|
||||
|
||||
run output error k3sRun get "$resource" || { printDanger "$error"; return 1; }
|
||||
printf '%s\n' "$output"
|
||||
}
|
||||
|
||||
# Installs k3s on the server.
|
||||
function cmdK3sInstall() {
|
||||
printInfo "$k3sLabel Install..."
|
||||
curl -sfL https://get.k3s.io | sh - || return 1
|
||||
systemctl enable --now k3s
|
||||
}
|
||||
|
||||
# Uninstalls k3s from the server.
|
||||
function cmdK3sUninstall() {
|
||||
/usr/local/bin/k3s-uninstall.sh
|
||||
}
|
||||
|
||||
# Adds a namespace to Kubernetes if it does not already exist.
|
||||
# $1 (namespace): namespace name.
|
||||
function cmdK3sNamespaceAdd() {
|
||||
local output error
|
||||
run output error "$k3sCmd" kubectl get ns -o jsonpath="{range .items[*]}{.metadata.name}{'\n'}{end}" || { printDanger "$k3sLabel $error"; return 1; }
|
||||
|
||||
if ! grep -qF -- "$k3sNamespace" <<< "$output"; then
|
||||
run output error "$k3sCmd" kubectl create ns "$k3sNamespace" || { printDanger "$k3sLabel $error"; return 1; }
|
||||
fi
|
||||
}
|
||||
|
||||
# Deletes all resources in the current namespace.
|
||||
function cmdK3sResourceClean() {
|
||||
printWarning "$k3sLabel Namespace: $k3sNamespace | Wiping..."
|
||||
|
||||
k3sRun delete all --all --ignore-not-found --wait=false --timeout=30s --request-timeout=60s || true
|
||||
k3sRun delete cm,secret,ingress,networkpolicy,svc,pvc,job,cronjob --all --ignore-not-found --wait=false --timeout=30s --request-timeout=60s || true
|
||||
|
||||
mapfile -t pvs < <(
|
||||
"$k3sCmd" kubectl get pv -o jsonpath='{range .items[?(@.spec.claimRef.namespace=="'"$k3sNamespace"'")]}{.metadata.name}{"\n"}{end}' 2>/dev/null
|
||||
)
|
||||
local pv
|
||||
for pv in "${pvs[@]}"; do
|
||||
[[ -n "$pv" ]] || continue
|
||||
"$k3sCmd" kubectl patch pv "$pv" --type=merge -p '{"metadata":{"finalizers":[]}}' || true
|
||||
"$k3sCmd" kubectl delete pv "$pv" --wait=false --timeout=15s || true
|
||||
done
|
||||
|
||||
printSuccess "$k3sLabel Namespace: $k3sNamespace | Wiped"
|
||||
}
|
||||
|
||||
# Validates, applies a YAML file, then waits for new pods to become ready.
|
||||
# $1 (file): path to the YAML configuration file.
|
||||
function cmdK3sYamlApplyEx() {
|
||||
local file="$1" output error
|
||||
|
||||
printSection "Applying YAML file"
|
||||
printDotText "file" "$file"
|
||||
|
||||
run output error k3sYamlCheck "$file" || {
|
||||
printDotText "applying" "$labelFail"
|
||||
printDanger "Error checking YAML: ${error:-$output}"
|
||||
return 1
|
||||
}
|
||||
printDotText "checking" "$labelDone"
|
||||
|
||||
k3sPodsSnapshot podList || {
|
||||
printDotText "applying" "$labelFail"
|
||||
printDanger "Failed get list of pods"
|
||||
return 1
|
||||
}
|
||||
|
||||
runError error k3sYamlApply "$file" || {
|
||||
printDotText "applying" "$labelFail"
|
||||
printDanger "Error applied YAML: ${error:-$output}"
|
||||
return 1
|
||||
}
|
||||
printDotText "applying" "$labelDone"
|
||||
|
||||
k3sWaitNewPodsReady podList || return 1
|
||||
}
|
||||
|
||||
# Displays pods, services, ingress, and kube-system services info.
|
||||
function cmdK3sInfo() {
|
||||
local output error line
|
||||
|
||||
printSection "k3s"
|
||||
if ! run output error k3sRun version; then
|
||||
printDanger "$error";
|
||||
else
|
||||
while IFS= read -r line; do
|
||||
printDotText "${line%% Version:*}" "${line##*: }"
|
||||
done < <(awk 'NF' <<< "$output")
|
||||
fi
|
||||
|
||||
if ! run output error "$k3sCmd" kubectl get nodes --no-headers; then
|
||||
printDanger "$error"
|
||||
else
|
||||
local name status roles age version
|
||||
while IFS='|' read -r name status roles age version; do
|
||||
printSection "$name"
|
||||
if [[ "$status" == "Ready" ]]; then
|
||||
printDotText "Status" "$fontGreen$status$fontReset"
|
||||
else
|
||||
printDotText "Status" "$fontRed$status$fontReset"
|
||||
fi
|
||||
printDotText "Roles" "$roles"
|
||||
printDotText "Age" "$age"
|
||||
printDotText "Version" "$version"
|
||||
done < <(awk 'NF{print $1 "|" $2 "|" $3 "|" $4 "|" $5}' <<< "$output")
|
||||
fi
|
||||
}
|
||||
|
||||
# Displays pods, services, ingress, kube-system services, and non-running pod bugs.
|
||||
function cmdK3sNodesStatus() {
|
||||
printSection "Pods"
|
||||
if run output error k3sRun get pods -o wide; then
|
||||
printText "$output"
|
||||
else
|
||||
printDanger "$error"
|
||||
fi
|
||||
|
||||
printSection "Services"
|
||||
if run output error k3sRun get svc -o wide; then
|
||||
printText "$output"
|
||||
else
|
||||
printDanger "$error"
|
||||
fi
|
||||
|
||||
printSection "Ingress"
|
||||
if run output error k3sRun get ing; then
|
||||
printText "$output"
|
||||
else
|
||||
printDanger "$error"
|
||||
fi
|
||||
|
||||
printSection "Services (kube-system)"
|
||||
if run output error "$k3sCmd" kubectl -n kube-system get svc; then
|
||||
printText "$output"
|
||||
else
|
||||
printDanger "$error"
|
||||
fi
|
||||
|
||||
printSection "Bugs..."
|
||||
|
||||
local output error
|
||||
if run output error kubectl get pods -A \
|
||||
--field-selector=status.phase!=Running,status.phase!=Pending \
|
||||
-o custom-columns='NS:.metadata.namespace,POD:.metadata.name,PHASE:.status.phase,REASON:.status.reason,NODE:.spec.nodeName'; then
|
||||
printText "$output"
|
||||
else
|
||||
printDanger "$error"
|
||||
fi
|
||||
|
||||
printText "
|
||||
PHASE | REASON
|
||||
------------------
|
||||
Failed | !=0 Process crashed
|
||||
Failed | Error Process ended with an error
|
||||
Failed | OOMKilled Ran out of memory
|
||||
Failed | Evicted Kubelet evicted the pod (disk/memory pressure)
|
||||
|
||||
Completed/Succeeded - Not error if Job/migration/init task"
|
||||
}
|
||||
|
||||
# Displays resource usage (CPU/memory) for all pods in the namespace.
|
||||
function cmdK3sTopPod() {
|
||||
local output error
|
||||
run output error k3sRun top pod || { printDanger "$error"; return 1; }
|
||||
|
||||
printRow
|
||||
printText "$output"
|
||||
}
|
||||
@@ -1,300 +0,0 @@
|
||||
function cmdMalwareFilesDelete() {
|
||||
local action="${1:-list}"
|
||||
local quarantinePath="$appDataPath/malware/${appDate}_${appTime}"
|
||||
local allowedMuPluginFiles="
|
||||
index.php
|
||||
00-hosting-loader.php
|
||||
load.php
|
||||
hosting-wp-domain-rename.php
|
||||
burst_rest_api_optimizer.php
|
||||
elementor-safe-mode.php
|
||||
mailoptin-customizer-optimizer.php
|
||||
wgpwpp-cache.php
|
||||
installatron_hide_status_test.php
|
||||
"
|
||||
|
||||
[[ "$action" == "remove" ]] && printTitle "Malware files and blocks (quarantine)" || printTitle "Malware files and blocks (detect)"
|
||||
|
||||
run vhostsList error fileList "$olsVhostsPath" d || { printDanger "$error"; return 1; }
|
||||
while read -r dir; do
|
||||
local found=0 pluginList wwwPath
|
||||
wwwPath="$olsVhostsPath/$dir/www"
|
||||
|
||||
# mu-plugins: malware (static $a=null) → quarantine + blocker dir
|
||||
run itemList error fileList "$wwwPath/wp-content/mu-plugins/" f || continue
|
||||
while read -r item; do
|
||||
local fullPath="$wwwPath/wp-content/mu-plugins/$item"
|
||||
if grep -qF '($i){static $a=null' "$fullPath"; then
|
||||
(( found++ )) || printSection "$dir"
|
||||
if [[ "$action" == "remove" ]]; then
|
||||
local pluginName="${item%.php}"
|
||||
local pluginDir="$wwwPath/wp-content/plugins/$pluginName"
|
||||
# move mu-plugin file to quarantine
|
||||
malwareQuarantineMove "$fullPath" && printDotText "$item" "${fontRed}malware $labelDone" \
|
||||
|| printDotText "$item" "${fontRed}malware $labelFail"
|
||||
# move plugin to quarantine if it exists
|
||||
if [[ -d "$pluginDir" ]]; then
|
||||
malwareQuarantineMove "$pluginDir" && printDotText "/wp-content/plugins/$pluginName" "${fontRed}malware plugin $labelDone" \
|
||||
|| printDotText "/wp-content/plugins/$pluginName" "${fontRed}malware plugin $labelFail"
|
||||
fi
|
||||
# create a blocker directory (instead of a file)
|
||||
# mkdir -p "$wwwPath/wp-content/mu-plugins/$pluginName.php" 2>/dev/null \
|
||||
# && printDotText "$pluginName.php" "${fontYellow}blocker dir $labelDone" \
|
||||
# || printDotText "$pluginName.php" "${fontRed}blocker dir $labelFail"
|
||||
else
|
||||
printDotText "/wp-content/mu-plugins/$item" "${fontRed}malware$fontReset"
|
||||
fi
|
||||
elif ! listContains "$item" "$allowedMuPluginFiles"; then
|
||||
(( found++ )) || printSection "$dir"
|
||||
printDotText "/wp-content/mu-plugins/$item" "$labelUnknown"
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$itemList")
|
||||
|
||||
# wp2shell (Auto-login to admin)
|
||||
pluginList=$(find "$wwwPath/wp-content/plugins" -maxdepth 1 -type d -name 'wp2shell*' 2>/dev/null)
|
||||
if [ -n "$pluginList" ]; then
|
||||
while IFS= read -r item; do
|
||||
(( found++ )) || printSection "$dir"
|
||||
if [[ "$action" == "remove" ]]; then
|
||||
malwareQuarantineMove "$item" && printDotText "${item#"$wwwPath"}" "${fontRed}malware $labelDone" \
|
||||
|| printDotText "${item#"$wwwPath"}" "${fontRed}malware $labelFail"
|
||||
else
|
||||
printDotText "${item#"$wwwPath"}" "${fontRed}malware$fontReset"
|
||||
fi
|
||||
done <<< "$pluginList"
|
||||
fi
|
||||
|
||||
# wp-static-cache (?)
|
||||
pluginList=$(find "$wwwPath/wp-content/plugins" -maxdepth 1 -type d -name 'wp-static-cache*' 2>/dev/null)
|
||||
if [ -n "$pluginList" ]; then
|
||||
while IFS= read -r item; do
|
||||
(( found++ )) || printSection "$dir"
|
||||
if [[ "$action" == "remove" ]]; then
|
||||
malwareQuarantineMove "$item" && printDotText "${item#"$wwwPath"}" "${fontRed}malware $labelDone" \
|
||||
|| printDotText "${item#"$wwwPath"}" "${fontRed}malware $labelFail"
|
||||
else
|
||||
printDotText "${item#"$wwwPath"}" "${fontRed}malware$fontReset"
|
||||
fi
|
||||
done <<< "$pluginList"
|
||||
fi
|
||||
|
||||
# hex-named php/zip в wp-content, wp-content/cache, themes/*, uploads/**/
|
||||
local hexZipList
|
||||
hexZipList=$(
|
||||
find "$wwwPath/wp-content" -maxdepth 1 -type f -regextype posix-extended -regex '.*/\.?[0-9a-f]{8}\.(php|zip)$' 2>/dev/null
|
||||
find "$wwwPath/wp-content/cache" -maxdepth 1 -type f -regextype posix-extended -regex '.*/\.?[0-9a-f]{8}\.(php|zip)$' 2>/dev/null
|
||||
find "$wwwPath/wp-content/themes" -maxdepth 2 -type f -regextype posix-extended -regex '.*/\.?[0-9a-f]{8}\.(php|zip)$' 2>/dev/null
|
||||
find "$wwwPath/wp-content/uploads" -maxdepth 3 -type f -regextype posix-extended -regex '.*/\.?[0-9a-f]{8}\.(php|zip)$' 2>/dev/null
|
||||
)
|
||||
if [ -n "$hexZipList" ]; then
|
||||
while IFS= read -r item; do
|
||||
(( found++ )) || printSection "$dir"
|
||||
if [[ "$action" == "remove" ]]; then
|
||||
malwareQuarantineMove "$item" && printDotText "${item#"$wwwPath"}" "${fontRed}malware $labelDone" \
|
||||
|| printDotText "${item#"$wwwPath"}" "${fontRed}malware $labelFail"
|
||||
else
|
||||
printDotText "${item#"$wwwPath"}" "${fontRed}malware$fontReset"
|
||||
fi
|
||||
done <<< "$hexZipList"
|
||||
fi
|
||||
|
||||
# wp-content/themes/*/functions.php cut out block SC_TH_BEGIN...SC_TH_END
|
||||
local funcList
|
||||
funcList=$(find "$wwwPath/wp-content/themes" -maxdepth 2 -name "functions.php" 2>/dev/null)
|
||||
if [ -n "$funcList" ]; then
|
||||
while IFS= read -r item; do
|
||||
if grep -qF '/* SC_TH_BEGIN:' "$item" 2>/dev/null; then
|
||||
(( found++ )) || printSection "$dir"
|
||||
if [[ "$action" == "remove" ]]; then
|
||||
# keep the original file in quarantine, then cut the block out
|
||||
malwareQuarantineCopy "$item" \
|
||||
&& sed -i '/\/\* SC_TH_BEGIN:/,/SC_TH_END:[^*]*\*\//d' "$item" \
|
||||
&& printDotText "${item#"$wwwPath"}" "${fontRed}SC_TH block $labelDone" \
|
||||
|| printDotText "${item#"$wwwPath"}" "${fontRed}SC_TH block $labelFail"
|
||||
else
|
||||
printDotText "${item#"$wwwPath"}" "${fontRed}SC_TH block$fontReset"
|
||||
fi
|
||||
fi
|
||||
done <<< "$funcList"
|
||||
fi
|
||||
|
||||
# wp-content/advanced-cache.php cut out block SC_ADV_BEGIN...SC_ADV_END
|
||||
local advCacheFile="$wwwPath/wp-content/advanced-cache.php"
|
||||
if grep -qF '/* SC_ADV_BEGIN:' "$advCacheFile" 2>/dev/null; then
|
||||
(( found++ )) || printSection "$dir"
|
||||
if [[ "$action" == "remove" ]]; then
|
||||
# keep the original file in quarantine, then cut the block out
|
||||
malwareQuarantineCopy "$advCacheFile" \
|
||||
&& sed -i '/\/\* SC_ADV_BEGIN:/,/SC_ADV_END:[^*]*\*\//d' "$advCacheFile" \
|
||||
&& printDotText "${advCacheFile#"$wwwPath"}" "${fontRed}SC_ADV block $labelDone" \
|
||||
|| printDotText "${advCacheFile#"$wwwPath"}" "${fontRed}SC_ADV block $labelFail"
|
||||
else
|
||||
printDotText "${advCacheFile#"$wwwPath"}" "${fontRed}SC_ADV block$fontReset"
|
||||
fi
|
||||
fi
|
||||
|
||||
# wp-content/db.php cut out block SC_DB_BEGIN...SC_DB_END
|
||||
local dbFile="$wwwPath/wp-content/db.php"
|
||||
if grep -qF '/* SC_DB_BEGIN:' "$dbFile" 2>/dev/null; then
|
||||
(( found++ )) || printSection "$dir"
|
||||
if [[ "$action" == "remove" ]]; then
|
||||
# keep the original file in quarantine, then cut the block out
|
||||
malwareQuarantineCopy "$dbFile" \
|
||||
&& sed -i '/\/\* SC_DB_BEGIN:/,/SC_DB_END:[^*]*\*\//d' "$dbFile" \
|
||||
&& printDotText "${dbFile#"$wwwPath"}" "${fontRed}SC_DB block $labelDone" \
|
||||
|| printDotText "${dbFile#"$wwwPath"}" "${fontRed}SC_DB block $labelFail"
|
||||
else
|
||||
printDotText "${dbFile#"$wwwPath"}" "${fontRed}SC_DB block$fontReset"
|
||||
fi
|
||||
fi
|
||||
|
||||
# other
|
||||
# for subdir in wp-content/mu-plugins wp-content/plugins; do
|
||||
# pluginList=$(find "$wwwPath/$subdir" -maxdepth 1 -regextype posix-extended -regex '^.*[^0-9a-f][0-9a-f]{6,8}(\.php)?$' 2>/dev/null)
|
||||
# if [ -n "$pluginList" ]; then
|
||||
# while IFS= read -r item; do
|
||||
# (( found++ )) || printSection "$dir"
|
||||
# printDotText "${item#"$wwwPath"}" "${fontYellow}warning$fontReset"
|
||||
# done <<< "$pluginList"
|
||||
# fi
|
||||
# done
|
||||
done < <(awk 'NF' <<< "$vhostsList")
|
||||
}
|
||||
|
||||
function cmdMalwareUserDelete() {
|
||||
local action="${1:-list}"
|
||||
|
||||
[[ "$action" == "remove" ]] && printTitle "Users (deleting)" || printTitle "Users (detect)"
|
||||
|
||||
local output error
|
||||
if ! run output error malwareUserList; then
|
||||
printDanger "$error"
|
||||
return 1
|
||||
fi
|
||||
local total=0
|
||||
if [[ "$action" == "remove" ]]; then
|
||||
# group user_id on db_name+table_name
|
||||
declare -A ids_map
|
||||
declare -A name_map
|
||||
while IFS=$'\t' read -r db_name user_id user_login user_registered table_name; do
|
||||
[[ -z "$db_name" ]] && continue
|
||||
(( total++ ))
|
||||
local key="${db_name}|${table_name}"
|
||||
ids_map[$key]+="${user_id},"
|
||||
name_map[$key]="$db_name | $table_name"
|
||||
done < <(awk 'NF' <<< "$output")
|
||||
# delete with a single query on the table
|
||||
for key in "${!ids_map[@]}"; do
|
||||
local ids="${ids_map[$key]%,}"
|
||||
local db_name="${key%%|*}"
|
||||
local table_name="${key##*|}"
|
||||
local sql="DELETE FROM \`${db_name}\`.\`${table_name}\` WHERE ID IN (${ids});"
|
||||
local qout qerr
|
||||
if run qout qerr mariadbMasterRootQuery "$sql"; then
|
||||
printDotText "${name_map[$key]}" "${ids} $labelDone"
|
||||
else
|
||||
printDotText "${name_map[$key]}" "${ids} $labelFail"
|
||||
printDanger "$qerr"
|
||||
fi
|
||||
done
|
||||
else
|
||||
while IFS=$'\t' read -r db_name user_id user_login user_registered table_name; do
|
||||
[[ -z "$db_name" ]] && continue
|
||||
(( total++ ))
|
||||
printDotText "${db_name} | ${user_login} (${user_id})" "$labelDanger"
|
||||
done < <(awk 'NF' <<< "$output")
|
||||
fi
|
||||
|
||||
printDotText "Total" "$total"
|
||||
}
|
||||
|
||||
function cmdMalwareOptionsDelete() {
|
||||
local action="${1:-list}"
|
||||
|
||||
[[ "$action" == "remove" ]] && printTitle "Options (deleting)" || printTitle "Options (detect)"
|
||||
|
||||
local output error
|
||||
if ! run output error malwareOptionsList; then
|
||||
printDanger "$error"
|
||||
return 1
|
||||
fi
|
||||
local total=0
|
||||
if [[ "$action" == "remove" ]]; then
|
||||
# group option_id by db_name+table_name
|
||||
declare -A ids_map
|
||||
declare -A name_map
|
||||
while IFS=$'\t' read -r db_name table_name option_id option_name; do
|
||||
[[ -z "$db_name" ]] && continue
|
||||
(( total++ ))
|
||||
local key="${db_name}|${table_name}"
|
||||
ids_map[$key]+="${option_id},"
|
||||
name_map[$key]="$db_name | $table_name"
|
||||
done < <(awk 'NF' <<< "$output")
|
||||
# delete with a single query per table
|
||||
for key in "${!ids_map[@]}"; do
|
||||
local ids="${ids_map[$key]%,}"
|
||||
local db_name="${key%%|*}"
|
||||
local table_name="${key##*|}"
|
||||
local sql="DELETE FROM \`${db_name}\`.\`${table_name}\` WHERE option_id IN (${ids});"
|
||||
local qout qerr
|
||||
if run qout qerr mariadbMasterRootQuery "$sql"; then
|
||||
printDotText "${name_map[$key]}" "${ids} $labelDone"
|
||||
else
|
||||
printDotText "${name_map[$key]}" "${ids} $labelFail"
|
||||
printDanger "$qerr"
|
||||
fi
|
||||
done
|
||||
else
|
||||
while IFS=$'\t' read -r db_name table_name option_id option_name; do
|
||||
[[ -z "$db_name" ]] && continue
|
||||
(( total++ ))
|
||||
printDotText "${db_name}" "${option_id}: ${option_name} $labelDanger"
|
||||
done < <(awk 'NF' <<< "$output")
|
||||
fi
|
||||
printDotText "Total" "$total"
|
||||
}
|
||||
|
||||
function cmdMalwareCronDelete() {
|
||||
local action="${1:-list}"
|
||||
|
||||
[[ "$action" == "remove" ]] && printTitle "Cron (deleting)" || printTitle "Cron (detect)"
|
||||
|
||||
local output error
|
||||
if ! run output error malwareCronList; then
|
||||
printDanger "$error"
|
||||
return 1
|
||||
fi
|
||||
local total=0
|
||||
while IFS=$'\t' read -r db_name table_name option_id option_name; do
|
||||
[[ -z "$db_name" ]] && continue
|
||||
(( total++ ))
|
||||
if [[ "$action" == "remove" ]]; then
|
||||
# remove only 'sc_cron_fetch' entry from the serialized cron array | pattern: i:TIMESTAMP;a:N:{s:13:"sc_cron_fetch";...}}}
|
||||
# local sql="UPDATE \`${db_name}\`.\`${table_name}\`
|
||||
# SET option_value = REGEXP_REPLACE(
|
||||
# option_value,
|
||||
# 'i:[0-9]+;a:1:\\\\{s:13:\"sc_cron_fetch\";a:1:\\\\{[^}]+\\\\}\\\\}\\\\}',
|
||||
# ''
|
||||
# )
|
||||
# WHERE option_id = ${option_id};"
|
||||
# local sql="UPDATE \`${db_name}\`.\`${table_name}\`
|
||||
# SET option_value = REGEXP_REPLACE(
|
||||
# option_value,
|
||||
# 'i:[0-9]+;a:1:\\\\{s:13:\"sc_cron_fetch\";a:1:\\\\{s:[0-9]+:\"[^\"]+\";a:[0-9]+:\\\\{[^}]*\\\\}\\\\}\\\\}\\\\}',
|
||||
# ''
|
||||
# )
|
||||
# WHERE option_id = ${option_id};"
|
||||
local sql="UPDATE \`${db_name}\`.\`${table_name}\` SET option_value = REPLACE(option_value, 's:13:\"sc_cron_fetch\"', 's:16:\"sc_cron_fetch_dis\"') WHERE option_id = ${option_id};"
|
||||
local qout qerr
|
||||
if run qout qerr mariadbMasterRootQuery "$sql"; then
|
||||
printDotText "${db_name}" "sc_cron_fetch $labelDone"
|
||||
else
|
||||
printDotText "${db_name}" "sc_cron_fetch $labelFail"
|
||||
printDanger "$qerr"
|
||||
fi
|
||||
else
|
||||
printDotText "${db_name}" "sc_cron_fetch $labelDanger"
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$output")
|
||||
printDotText "Total" "$total"
|
||||
}
|
||||
@@ -1,439 +0,0 @@
|
||||
mariadbLabel="[MariaDB]"
|
||||
|
||||
# Prepares Kubernetes secrets for MariaDB.
|
||||
function cmdMariadbPreparation() {
|
||||
printInfo "$mariadbLabel Preparation..."
|
||||
|
||||
local error
|
||||
runError error k3sRun delete secret "$mariadbKube-secret" --ignore-not-found || {
|
||||
printDotText "preparation" "$labelFail"
|
||||
printDanger "Delete old Secret: $error"
|
||||
return 1
|
||||
}
|
||||
|
||||
runError error k3sRun create secret generic "$mariadbKube-secret" --from-literal=root-password="$mariadbRootPass" --from-literal=replication-password="$mariadbRootPass" || {
|
||||
printDotText "preparation" "$labelFail"
|
||||
printDanger "Create new Secret: $error"
|
||||
return 1
|
||||
}
|
||||
|
||||
printDotText "preparation" "$labelDone"
|
||||
}
|
||||
|
||||
# Renders the MariaDB Kubernetes YAML manifest via Helm.
|
||||
function cmdMariadbYamlRender() {
|
||||
local templateFile="templates/$mariadbKube.yaml"
|
||||
|
||||
printSection "Render YAML file | Helm"
|
||||
printDotText "Template" "$appAssetsPath/k3s/$templateFile"
|
||||
[[ -f "$appAssetsPath/k3s/$templateFile" ]] || {
|
||||
printDanger "Template file not found"
|
||||
return 1
|
||||
}
|
||||
|
||||
local profileCpuFile="$appAssetsPath/k3s/profiles/cpu-$kubeCpuProfile.yaml"
|
||||
printDotText "CPU profile" "$profileCpuFile"
|
||||
[[ -f "$profileCpuFile" ]] || {
|
||||
printDanger "CPU profile file not found"
|
||||
return 1
|
||||
}
|
||||
|
||||
local profileMemoryFile="$appAssetsPath/k3s/profiles/memory-$kubeMemoryProfile.yaml"
|
||||
printDotText "Memory profile" "$profileMemoryFile"
|
||||
[[ -f "$profileMemoryFile" ]] || {
|
||||
printDanger "Memory profile file not found"
|
||||
return 1
|
||||
}
|
||||
|
||||
local varsFile
|
||||
varsFile=$(mktemp "/tmp/$mariadbKube.vars.XXXXXX.yaml") || {
|
||||
printDotText "render" "$labelFail"
|
||||
printDanger "Create temp variables file"
|
||||
return 1
|
||||
}
|
||||
printDotText "Variables" "$varsFile"
|
||||
trap 'rm -f -- "$varsFile"' RETURN
|
||||
cat > "$varsFile" <<EOF
|
||||
mariadbHelm: true
|
||||
namespace: $k3sNamespace
|
||||
mariadb: $mariadbKube
|
||||
mariadbMaster: $mariadbMasterKube
|
||||
mariadbSlave: $mariadbSlaveKube
|
||||
mariadbMasterPath: $mariadbMasterPath
|
||||
mariadbSlavePath: $mariadbSlavePath
|
||||
EOF
|
||||
|
||||
printDotText "Result" "$mariadbYaml"
|
||||
mkdir -p -- "$(dirname -- "$mariadbYaml")" || return 1
|
||||
fileBackup "$mariadbYaml"
|
||||
helm template stack "$appAssetsPath/k3s" -f "$varsFile" -f "$profileCpuFile" -f "$profileMemoryFile" --show-only "$templateFile" > "$mariadbYaml" || {
|
||||
printDotText "render" "$labelFail"
|
||||
printDanger "Render failed"
|
||||
return 1
|
||||
}
|
||||
|
||||
printDotText "render" "$labelDone"
|
||||
}
|
||||
|
||||
# Waits until both MariaDB master and slave respond to SQL queries.
|
||||
function cmdMariadbWaitMasterSlave() {
|
||||
local error step attempts=15
|
||||
|
||||
for ((step=1; step<=attempts; step++)); do
|
||||
runError error mariadbMasterRootQuery "SELECT 1;" && break
|
||||
if [[ "$step" -ne "$attempts" ]]; then
|
||||
printWarning "$mariadbLabel Master node | Waiting..."
|
||||
sleep 4
|
||||
else
|
||||
printDanger "$mariadbLabel Master node | Not responding"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
for ((step=1; step<=attempts; step++)); do
|
||||
runError error mariadbSlaveRootQuery "SELECT 1;" && break
|
||||
if [[ "$step" -ne "$attempts" ]]; then
|
||||
printWarning "$mariadbLabel Slave node | Waiting..."
|
||||
sleep 4
|
||||
else
|
||||
printDanger "$mariadbLabel Slave node | Not responding"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Rebuilds MariaDB replication from master to slave.
|
||||
# [$1] (masterPassword): replication password (defaults to $mariadbRootPass).
|
||||
function cmdMariadbReplicaRebuild() {
|
||||
local masterPassword="${1:-$mariadbRootPass}"
|
||||
|
||||
printInfo "$mariadbLabel Replica rebuild..."
|
||||
|
||||
local output error
|
||||
runError error mariadbMasterRootQuery "SELECT 1;" || {
|
||||
printDanger "$mariadbLabel Master node | Not responding: $error"
|
||||
return 1
|
||||
}
|
||||
runError error mariadbSlaveRootQuery "SELECT 1;" || {
|
||||
printDanger "$mariadbLabel Slave node | Not responding: $error"
|
||||
return 1
|
||||
}
|
||||
|
||||
runError error mariadbMasterRootQuery "CREATE USER IF NOT EXISTS 'replication_user'@'%' IDENTIFIED BY '$masterPassword'; GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'replication_user'@'%'; ALTER USER 'replication_user'@'%' IDENTIFIED BY '$masterPassword';" || {
|
||||
printDanger "$mariadbLabel Master node | Recreated replication user: $error"
|
||||
return 1
|
||||
}
|
||||
printInfo "$mariadbLabel Master node | Recreated replication user"
|
||||
|
||||
local dumpFile="$appDataPath/$mariadbMasterKube/${appDate}_${appTime}_replica_rebuild.sql"
|
||||
printInfo "$mariadbLabel Master node | Exporting full dump..."
|
||||
mariadbMasterRootExport all "$dumpFile" || {
|
||||
printDanger "$mariadbLabel Master node | Export failed"
|
||||
return 1
|
||||
}
|
||||
printInfo "$mariadbLabel Master node | Exported: $dumpFile"
|
||||
|
||||
local masterFile masterPos
|
||||
masterFile=$(grep -m1 -oE "MASTER_LOG_FILE='[^']+'" "$dumpFile" | sed "s/MASTER_LOG_FILE='//;s/'//")
|
||||
masterPos=$(grep -m1 -oE "MASTER_LOG_POS=[0-9]+" "$dumpFile" | sed 's/MASTER_LOG_POS=//')
|
||||
[[ -n "$masterFile" && -n "$masterPos" ]] || {
|
||||
printDanger "$mariadbLabel Master node | Cannot parse MASTER_LOG_FILE/MASTER_LOG_POS from dump"
|
||||
return 1
|
||||
}
|
||||
printInfo "$mariadbLabel Master node | Binlog: $masterFile:$masterPos"
|
||||
|
||||
printInfo "$mariadbLabel Slave node | Stopping replication..."
|
||||
runError error mariadbSlaveRootQuery "STOP SLAVE; RESET SLAVE ALL;" || {
|
||||
printDanger "$mariadbLabel Slave node | Stop/reset failed: $error"
|
||||
return 1
|
||||
}
|
||||
|
||||
printInfo "$mariadbLabel Slave node | Importing full dump..."
|
||||
runShell output error k3sRun exec -i pod/"$mariadbSlaveKube"-0 -c "$mariadbSlaveKube" -- mariadb -u "root" -p"$mariadbRootPass" "<" "$dumpFile" ">" /dev/null || {
|
||||
printDanger "$mariadbLabel Slave node | Import failed: ${error:-$output}"
|
||||
return 1
|
||||
}
|
||||
printInfo "$mariadbLabel Slave node | Import completed"
|
||||
|
||||
runError error mariadbSlaveRootQuery "CHANGE MASTER TO MASTER_HOST='${mariadbMasterKube}', MASTER_PORT=3306, MASTER_USER='replication_user', MASTER_PASSWORD='${masterPassword}', MASTER_LOG_FILE='${masterFile}', MASTER_LOG_POS=${masterPos}; START SLAVE;" || {
|
||||
printDanger "$mariadbLabel Slave node | Configure replication failed: $error"
|
||||
return 1
|
||||
}
|
||||
printInfo "$mariadbLabel Slave node | Replication configured"
|
||||
|
||||
run output error mariadbSlaveRootQuery "SHOW SLAVE STATUS\G" showColumn || {
|
||||
printDanger "$mariadbLabel Slave node | Status: $error"
|
||||
return 1
|
||||
}
|
||||
|
||||
local ioRunning sqlRunning secondsBehind lastError
|
||||
ioRunning=$(printf '%s\n' "$output" | awk -F': ' '/^ *Slave_IO_Running:/{print $2}' | tr -d '[:space:]')
|
||||
sqlRunning=$(printf '%s\n' "$output" | awk -F': ' '/^ *Slave_SQL_Running:/{print $2}' | tr -d '[:space:]')
|
||||
secondsBehind=$(printf '%s\n' "$output" | awk -F': ' '/^ *Seconds_Behind_Master:/{print $2}' | tr -d '[:space:]')
|
||||
lastError=$(printf '%s\n' "$output" | awk -F': ' '/^ *Last_Error:/{print substr($0, index($0,$2))}')
|
||||
if [[ "$ioRunning" != "Yes" || "$sqlRunning" != "Yes" ]]; then
|
||||
printWarning "$mariadbLabel Slave node | IO:${ioRunning:-?} | SQL:${sqlRunning:-?}"
|
||||
[[ -n "$lastError" ]] && printWarning "$mariadbLabel Slave node | Last error: $lastError"
|
||||
return 1
|
||||
fi
|
||||
|
||||
printSuccess "$mariadbLabel Replica rebuild completed | Delay ${secondsBehind:-0}s"
|
||||
}
|
||||
|
||||
# Updates MariaDB Kubernetes resources (limits, config, etc.) without re-initialization.
|
||||
function cmdMariadbUpdate() {
|
||||
cmdMariadbYamlRender || return 1
|
||||
cmdK3sYamlApplyEx "$mariadbYaml" || return 1
|
||||
}
|
||||
|
||||
# Installs MariaDB into Kubernetes and initializes replication.
|
||||
function cmdMariadbInstall() {
|
||||
cmdMariadbPreparation || return 1
|
||||
cmdMariadbYamlRender || return 1
|
||||
cmdK3sYamlApplyEx "$mariadbYaml" || return 1
|
||||
cmdMariadbWaitMasterSlave || return 1
|
||||
cmdMariadbReplicaRebuild || return 1
|
||||
}
|
||||
|
||||
# Uninstalls MariaDB Kubernetes resources.
|
||||
function cmdMariadbUninstall() {
|
||||
"$k3sCmd" kubectl delete -f "$mariadbYaml"
|
||||
}
|
||||
|
||||
# Checks MariaDB root password, replication health, database/user count, and total data size.
|
||||
function cmdMariadbInfo() {
|
||||
local password
|
||||
password=$(mariadbRootPassSecretGet)
|
||||
if [[ "$password" != "$mariadbRootPass" ]]; then
|
||||
printRow
|
||||
printDotText "Root password" "$labelFail"
|
||||
printDanger "Use mariadbRootPassSecretSave"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local podList output error pod phase
|
||||
|
||||
# Master
|
||||
if ! run podList error k3sPodListStatus "$mariadbMasterKube"; then
|
||||
printDanger "Get pods (master): $error"
|
||||
elif [[ -z "$podList" ]]; then
|
||||
printDanger "Pods (master) not found"
|
||||
else
|
||||
while IFS='|' read -r pod phase; do
|
||||
printSection "$pod"
|
||||
|
||||
if [[ "$phase" != "Running" ]]; then
|
||||
printDotText "Phase" "$fontRed$phase$fontReset"
|
||||
else
|
||||
printDotText "Phase" "$fontGreen$phase$fontReset"
|
||||
|
||||
if ! run output error mariadbMasterRootQuery "SELECT VERSION();"; then
|
||||
printDotText "MariaDB status" "$fontRed$labelFail$fontReset"
|
||||
printDanger "$error"
|
||||
continue
|
||||
fi
|
||||
printDotText "MariaDB status" "$labelRunning"
|
||||
printDotText "MariaDB version" "$output"
|
||||
|
||||
local masterStatus file position activeConnections
|
||||
if ! run masterStatus error mariadbMasterRootQuery "SHOW MASTER STATUS\G;" "showColumn"; then
|
||||
printDotText "Replica role" "$fontRed$labelUnknown$fontReset"
|
||||
printDanger "$error"
|
||||
continue
|
||||
fi
|
||||
|
||||
file=$(printf '%s\n' "$masterStatus" | awk '/^ *File:/{print $2}')
|
||||
if [[ -z "$file" ]]; then
|
||||
printDotText "Replica role" "$fontRed$labelUnknown$fontReset"
|
||||
printDanger "Params 'File' not found"
|
||||
continue
|
||||
fi
|
||||
|
||||
position=$(printf '%s\n' "$masterStatus" | awk '/^ *Position:/{print $2}')
|
||||
if [[ -z "$position" ]]; then
|
||||
printDotText "Replica role" "$fontRed$labelUnknown$fontReset"
|
||||
printDanger "Params 'Position' not found"
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! run output error mariadbMasterRootQuery "SHOW STATUS LIKE 'Threads_connected';"; then
|
||||
printDotText "Replica role" "$fontRed$labelUnknown$fontReset"
|
||||
printDanger "$error"
|
||||
continue
|
||||
fi
|
||||
|
||||
printDotText "Replica role" "${fontGreen}master$fontReset"
|
||||
|
||||
activeConnections=$(printf '%s\n' "$output" | awk '{print $2}')
|
||||
if [[ "$activeConnections" -ge 100 || "$activeConnections" -eq 1 ]]; then
|
||||
printDotText "Active connections" "$fontYellow$activeConnections$fontReset"
|
||||
else
|
||||
printDotText "Active connections" "$fontGreen$activeConnections$fontReset"
|
||||
fi
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$podList")
|
||||
fi
|
||||
|
||||
# Slave
|
||||
if ! run podList error k3sPodListStatus "$mariadbSlaveKube"; then
|
||||
printDanger "Get pods (slave): $error"
|
||||
elif [[ -z "$podList" ]]; then
|
||||
printDanger "Pods (slave) not found"
|
||||
else
|
||||
while IFS='|' read -r pod phase; do
|
||||
printSection "$pod"
|
||||
|
||||
if [[ "$phase" != "Running" ]]; then
|
||||
printDotText "Phase" "$fontRed$phase$fontReset"
|
||||
else
|
||||
printDotText "Phase" "$fontGreen$phase$fontReset"
|
||||
|
||||
if ! run output error mariadbSlaveRootQuery "SELECT VERSION();"; then
|
||||
printDotText "MariaDB status" "$fontRed$labelFail$fontReset"
|
||||
printDanger "$error"
|
||||
continue
|
||||
fi
|
||||
printDotText "MariaDB status" "$labelRunning"
|
||||
printDotText "MariaDB version" "$output"
|
||||
|
||||
local slaveStatus slaveIoRunning slaveSqlRunning lastError secondsBehindMaster
|
||||
if ! run slaveStatus error mariadbSlaveRootQuery "SHOW SLAVE STATUS\G;" "showColumn"; then
|
||||
printDotText "Replica role" "$fontRed$labelUnknown$fontReset"
|
||||
printDanger "$error"
|
||||
continue
|
||||
fi
|
||||
|
||||
slaveIoRunning=$(printf '%s\n' "$slaveStatus" | awk '/^ *Slave_IO_Running:/{print $2}')
|
||||
if [[ "$slaveIoRunning" != "Yes" ]]; then
|
||||
printDotText "Slave IO Running" "$fontRed$slaveIoRunning$fontReset"
|
||||
continue
|
||||
fi
|
||||
|
||||
slaveSqlRunning=$(printf '%s\n' "$slaveStatus" | awk '/^ *Slave_SQL_Running:/{print $2}')
|
||||
if [[ "$slaveSqlRunning" != "Yes" ]]; then
|
||||
printDotText "Slave SQL Running" "$fontRed$slaveSqlRunning$fontReset"
|
||||
continue
|
||||
fi
|
||||
|
||||
lastError=$(printf '%s\n' "$slaveStatus" | awk '/^ *Last_Error:/{$1=""; sub(/^[ \t]+/,""); print}')
|
||||
if [[ -n "$lastError" ]]; then
|
||||
printDotText "Slave SQL Running" "$fontRed$slaveSqlRunning$fontReset"
|
||||
printDanger "Last error: $lastError"
|
||||
continue
|
||||
fi
|
||||
|
||||
printDotText "Replica role" "${fontGreen}slave$fontReset"
|
||||
|
||||
secondsBehindMaster=$(printf '%s\n' "$slaveStatus" | awk '/^ *Seconds_Behind_Master:/{print $2}')
|
||||
if [[ "$secondsBehindMaster" -ne 0 ]]; then
|
||||
printDotText "Replication lags" "$fontYellow${secondsBehindMaster}s$fontReset"
|
||||
else
|
||||
printDotText "Replication lags" "${fontGreen}0s$fontReset"
|
||||
fi
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$podList")
|
||||
fi
|
||||
|
||||
printSection "MariaDB"
|
||||
|
||||
local databaseList userList dataSize
|
||||
if run output error mariadbDatabaseListGet; then
|
||||
mapfile -t databaseList < <(awk 'NF' <<< "$output")
|
||||
printDotText "Databases" "${#databaseList[@]}"
|
||||
else
|
||||
printDotText "Databases" "$labelUnknown"
|
||||
printDanger "$error"
|
||||
fi
|
||||
|
||||
if run output error mariadbUserListGet; then
|
||||
mapfile -t userList < <(awk 'NF' <<< "$output")
|
||||
printDotText "Users" "${#userList[@]}"
|
||||
else
|
||||
printDotText "Users" "$labelUnknown"
|
||||
printDanger "$error"
|
||||
fi
|
||||
|
||||
if ! run dataSize error mariadbMasterRootQuery "SELECT ROUND(COALESCE(SUM(DATA_LENGTH + INDEX_LENGTH), 0) / 1024 / 1024 / 1024, 2) AS t FROM information_schema.TABLES WHERE TABLE_TYPE = 'BASE TABLE';"; then
|
||||
printDotText "Size" "$labelUnknown"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "Size" "$dataSize Gb"
|
||||
fi
|
||||
}
|
||||
|
||||
# Shows MariaDB master and slave replication status.
|
||||
function cmdMariadbStatus() {
|
||||
local output error
|
||||
|
||||
printSection "$mariadbMasterKube"-0
|
||||
if ! run output error mariadbMasterRootQuery "SHOW MASTER STATUS;"; then
|
||||
printDanger "$error"
|
||||
else
|
||||
printText "$output"
|
||||
fi
|
||||
|
||||
printSection "$mariadbSlaveKube"-0
|
||||
if ! run output error mariadbSlaveRootQuery "SHOW SLAVE STATUS\G;" showColumn; then
|
||||
printDanger "$error"
|
||||
else
|
||||
printText "$output"
|
||||
fi
|
||||
}
|
||||
|
||||
# Lists all MariaDB databases.
|
||||
function cmdMariadbDatabase() {
|
||||
local output error
|
||||
|
||||
printRow
|
||||
|
||||
if ! run output error mariadbDatabaseListGet; then
|
||||
printDanger "$error"
|
||||
elif [[ -z "$output" ]]; then
|
||||
printText "$labelNull"
|
||||
else
|
||||
printText "$output"
|
||||
fi
|
||||
}
|
||||
|
||||
# Lists all MariaDB users.
|
||||
function cmdMariadbUser() {
|
||||
local output error
|
||||
|
||||
printRow
|
||||
|
||||
if ! run output error mariadbUserListGet; then
|
||||
printDanger "$error"
|
||||
elif [[ -z "$output" ]]; then
|
||||
printText "$labelNull"
|
||||
else
|
||||
printText "$output"
|
||||
fi
|
||||
}
|
||||
|
||||
# Exports a full dump from the MariaDB master node.
|
||||
# [$@] (...): arguments passed to mariadbMasterRootExport (e.g. database name, file).
|
||||
function cmdMariadbMasterDump() {
|
||||
local output error
|
||||
|
||||
printRow
|
||||
|
||||
if ! run output error mariadbMasterRootExport "$@"; then
|
||||
printDanger "$error"
|
||||
else
|
||||
printText "$output"
|
||||
fi
|
||||
}
|
||||
|
||||
# Exports a full dump from the MariaDB slave node.
|
||||
# [$@] (...): arguments passed to mariadbSlaveRootExport (e.g. database name, file).
|
||||
function cmdMariadbSlaveDump() {
|
||||
local output error
|
||||
|
||||
printRow
|
||||
|
||||
if ! run output error mariadbSlaveRootExport "$@"; then
|
||||
printDanger "$error"
|
||||
else
|
||||
printText "$output"
|
||||
fi
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
metricLabel="[Metric]"
|
||||
|
||||
# Copies vmagent config file to the configured metric path.
|
||||
function cmdMetricPreparation() {
|
||||
printSection "Preparation..."
|
||||
|
||||
mkdir -p -- "$(dirname -- "$metricVmagentPath")" || return 1
|
||||
cp -f -- "$appAssetsPath/metric/vmagent.yml" "$metricVmagentPath/vmagent.yml"
|
||||
|
||||
printDotText "preparation" "$labelDone"
|
||||
}
|
||||
|
||||
# Renders the Metric Kubernetes YAML manifest via Helm.
|
||||
function cmdMetricYamlRender() {
|
||||
local templateFile="templates/$metricKube.yaml"
|
||||
|
||||
printSection "Render YAML file | Helm"
|
||||
printDotText "Template" "$appAssetsPath/k3s/$templateFile"
|
||||
[[ -f "$appAssetsPath/k3s/$templateFile" ]] || {
|
||||
printDanger "Template file not found"
|
||||
return 1
|
||||
}
|
||||
|
||||
local varsFile
|
||||
varsFile=$(mktemp "/tmp/$metricKube.vars.XXXXXX.yaml") || {
|
||||
printDotText "render" "$labelFail"
|
||||
printDanger "Create temp variables file"
|
||||
return 1
|
||||
}
|
||||
printDotText "Variables" "$varsFile"
|
||||
trap 'rm -f -- "$varsFile"' RETURN
|
||||
cat > "$varsFile" <<EOF
|
||||
metricHelm: true
|
||||
namespace: $k3sNamespace
|
||||
metric: $metricKube
|
||||
metricApiUrl: $metricApiUrl
|
||||
metricPromPath: $metricPromPath
|
||||
metricVmagentPath: $metricVmagentPath
|
||||
EOF
|
||||
|
||||
printDotText "Result" "$metricYaml"
|
||||
mkdir -p -- "$(dirname -- "$metricYaml")" || return 1
|
||||
fileBackup "$metricYaml"
|
||||
helm template stack "$appAssetsPath/k3s" -f "$varsFile" --show-only "$templateFile" > "$metricYaml" || {
|
||||
printDotText "render" "$labelFail"
|
||||
printDanger "Render failed"
|
||||
return 1
|
||||
}
|
||||
|
||||
printDotText "render" "$labelDone"
|
||||
}
|
||||
|
||||
function cmdMetricxUpdate() {
|
||||
cmdMetricYamlRender || return 1
|
||||
cmdK3sYamlApplyEx "$metricYaml" || return 1
|
||||
}
|
||||
|
||||
# Installs Metric components into Kubernetes.
|
||||
function cmdMetricInstall() {
|
||||
cmdMetricPreparation || return 1
|
||||
cmdMetricYamlRender || return 1
|
||||
cmdK3sYamlApplyEx "$metricYaml" || return 1
|
||||
}
|
||||
|
||||
# Uninstalls Metric Kubernetes resources.
|
||||
function cmdMetricUninstall() {
|
||||
"$k3sCmd" kubectl delete -f "$metricYaml"
|
||||
}
|
||||
@@ -1,498 +0,0 @@
|
||||
openlitespeedLabel="[OpenLiteSpeed]"
|
||||
|
||||
# Renders the OpenLiteSpeed Kubernetes YAML manifest via Helm.
|
||||
function cmdOpenlitespeedYamlRender() {
|
||||
local templateFile="templates/$olsKube.yaml"
|
||||
|
||||
printSection "Render YAML file | Helm"
|
||||
printDotText "Template" "$appAssetsPath/k3s/$templateFile"
|
||||
[[ -f "$appAssetsPath/k3s/$templateFile" ]] || {
|
||||
printDanger "Template file not found"
|
||||
return 1
|
||||
}
|
||||
|
||||
local profileCpuFile="$appAssetsPath/k3s/profiles/cpu-$kubeCpuProfile.yaml"
|
||||
printDotText "CPU profile" "$profileCpuFile"
|
||||
[[ -f "$profileCpuFile" ]] || {
|
||||
printDanger "CPU profile file not found"
|
||||
return 1
|
||||
}
|
||||
|
||||
local profileMemoryFile="$appAssetsPath/k3s/profiles/memory-$kubeMemoryProfile.yaml"
|
||||
printDotText "Memory profile" "$profileMemoryFile"
|
||||
[[ -f "$profileMemoryFile" ]] || {
|
||||
printDanger "Memory profile file not found"
|
||||
return 1
|
||||
}
|
||||
|
||||
local adminWhiteList=() adminWhiteStr
|
||||
for i in "${!olsAdminWhiteList[@]}"; do
|
||||
adminWhiteList[$i]="\"${olsAdminWhiteList[$i]}\""
|
||||
done
|
||||
adminWhiteStr=$(IFS=','; printf '%s\n' "${adminWhiteList[*]}")
|
||||
|
||||
local varsFile
|
||||
varsFile=$(mktemp "/tmp/$olsKube.vars.XXXXXX.yaml") || {
|
||||
printDotText "render" "$labelFail"
|
||||
printDanger "Create temp variables file"
|
||||
return 1
|
||||
}
|
||||
printDotText "Variables" "$varsFile"
|
||||
trap 'rm -f -- "$varsFile"' RETURN
|
||||
cat > "$varsFile" <<EOF
|
||||
openlitespeedHelm: true
|
||||
namespace: $k3sNamespace
|
||||
openlitespeed: $olsKube
|
||||
olsPodVhostsPath: $olsPodVhostsPath
|
||||
olsPodPrivatePath: $olsPodPrivatePath
|
||||
olsPodPublicPath: $olsPodPublicPath
|
||||
olsVhostsPath: $olsVhostsPath
|
||||
olsPrivatePath: $olsPrivatePath
|
||||
olsPublicPath: $olsPublicPath
|
||||
olsConfigPath: $olsConfigPath
|
||||
olsPhpIniPath: $olsPhpIniPath
|
||||
olsLogsPath: $olsLogsPath
|
||||
olsAdminPath: $olsAdminPath
|
||||
olsAdminWhiteList: $adminWhiteStr
|
||||
EOF
|
||||
|
||||
printDotText "Result" "$olsYaml"
|
||||
mkdir -p -- "$(dirname -- "$olsYaml")" || return 1
|
||||
fileBackup "$olsYaml"
|
||||
helm template stack "$appAssetsPath/k3s" -f "$varsFile" -f "$profileCpuFile" -f "$profileMemoryFile" --show-only "$templateFile" > "$olsYaml" || {
|
||||
printDotText "render" "$labelFail"
|
||||
printDanger "Render failed"
|
||||
return 1
|
||||
}
|
||||
|
||||
printDotText "render" "$labelDone"
|
||||
}
|
||||
|
||||
# Initializes OpenLiteSpeed after Kubernetes deployment: patches Traefik, sets admin credentials, PHP config, rules, and restarts.
|
||||
function cmdOpenlitespeedInit() {
|
||||
printSection "Initialization..."
|
||||
|
||||
local ug
|
||||
ug="$(stat -c "%u:%g" "$olsConfigFile")"
|
||||
|
||||
# Patch svc traefik
|
||||
runSilent "$k3sCmd" kubectl -n kube-system patch svc traefik -p '{"spec": {"externalTrafficPolicy": "Local"}}' || {
|
||||
printDotText "Patch svc traefik" "$labelFail"
|
||||
return 1
|
||||
}
|
||||
printDotText "Patch svc traefik" "$labelDone"
|
||||
|
||||
cmdOpenlitespeedWaitReady || return 1
|
||||
|
||||
# Updating Administrator Password
|
||||
runSilent cmdOpenlitespeedAdminPassUpdate "$olsAdminPass" || {
|
||||
printDotText "Updating admin password" "$labelFail"
|
||||
return 1
|
||||
}
|
||||
printDotText "Updating admin password" "$labelDone"
|
||||
|
||||
# Adding redirect rules
|
||||
mkdir -p "$olsConfigPath/rules"
|
||||
cp -n "$appAssetsPath"/openlitespeed/rules/* "$olsConfigPath/rules/"
|
||||
chown -R "$ug" "$olsConfigPath/rules"
|
||||
chmod 750 -R "$olsConfigPath/rules"
|
||||
printDotText "Adding redirect rules" "$labelDone"
|
||||
|
||||
# Adding PHP configs
|
||||
local profileFile="$appAssetsPath/openlitespeed/profiles/memory-$kubeMemoryProfile.config"
|
||||
local children max_memory_limit memory_limit max_execution_time post_max_size upload_max_filesize
|
||||
children=$(configGet "$profileFile" "children")
|
||||
max_memory_limit=$(configGet "$profileFile" "max_memory_limit")
|
||||
memory_limit=$(configGet "$profileFile" "memory_limit")
|
||||
max_execution_time=$(configGet "$profileFile" "max_execution_time")
|
||||
post_max_size=$(configGet "$profileFile" "post_max_size")
|
||||
upload_max_filesize=$(configGet "$profileFile" "upload_max_filesize")
|
||||
|
||||
local phpIniFile="$olsPhpIniPath/00-ols-master.ini"
|
||||
fileBackup "$phpIniFile"
|
||||
cp -f -- "$appAssetsPath/openlitespeed/php/00-ols-master.ini" "$phpIniFile"
|
||||
sed -i \
|
||||
-e "s|{{children}}|$children|g" \
|
||||
-e "s|{{max_memory_limit}}|$max_memory_limit|g" \
|
||||
-e "s|{{memory_limit}}|$memory_limit|g" \
|
||||
-e "s|{{max_execution_time}}|$max_execution_time|g" \
|
||||
-e "s|{{post_max_size}}|$post_max_size|g" \
|
||||
-e "s|{{upload_max_filesize}}|$upload_max_filesize|g" \
|
||||
-- "$phpIniFile"
|
||||
find "$olsPhpIniPath" -type f -exec chmod 644 {} +
|
||||
printDotText "Adding PHP configs" "$labelDone"
|
||||
|
||||
# Path OLS Admin config
|
||||
runSilent openlitespeedAdminAllowList || {
|
||||
printDotText "Path OLS Admin config" "$labelFail"
|
||||
return 1
|
||||
}
|
||||
printDotText "Path OLS Admin config" "$labelDone"
|
||||
|
||||
# Path OLS config
|
||||
openlitespeedConfigRebuild || {
|
||||
printDotText "Path OLS config" "$labelFail"
|
||||
return 1
|
||||
}
|
||||
printDotText "Path OLS config" "$labelDone"
|
||||
|
||||
cmdOpenlitespeedRestart
|
||||
cmdOpenlitespeedPhpKill all
|
||||
}
|
||||
|
||||
# Updates OpenLiteSpeed Kubernetes resources (limits, replicas, etc.) without re-initialization.
|
||||
function cmdOpenlitespeedUpdate() {
|
||||
cmdOpenlitespeedYamlRender || return 1
|
||||
cmdK3sYamlApplyEx "$olsYaml" || return 1
|
||||
}
|
||||
|
||||
# Installs OpenLiteSpeed into Kubernetes and runs initialization.
|
||||
function cmdOpenlitespeedInstall() {
|
||||
cmdOpenlitespeedYamlRender || return 1
|
||||
cmdK3sYamlApplyEx "$olsYaml" || return 1
|
||||
cmdOpenlitespeedInit
|
||||
}
|
||||
|
||||
# Uninstalls OpenLiteSpeed Kubernetes resources.
|
||||
function cmdOpenlitespeedUninstall() {
|
||||
"$k3sCmd" kubectl delete -f "$olsYaml"
|
||||
}
|
||||
|
||||
# Waits until OpenLiteSpeed WebAdmin PHP is ready.
|
||||
function cmdOpenlitespeedWaitReady() {
|
||||
local tries=${k3sReadyRetries:-10}
|
||||
local sleepSec=${k3sReadySleep:-2}
|
||||
local i output error
|
||||
for ((i=1; i<=tries; i++)); do
|
||||
run output error openlitespeedExec /usr/local/lsws/admin/fcgi-bin/admin_php -v && return 0
|
||||
printWarning "$openlitespeedLabel Waiting..."
|
||||
sleep "$sleepSec"
|
||||
done
|
||||
|
||||
printDanger "$openlitespeedLabel Not ready after ${tries} tries"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Updates OpenLiteSpeed WebAdmin credentials.
|
||||
# $1 (password): WebAdmin password.
|
||||
# [$2] (user): WebAdmin username (default: admin).
|
||||
function cmdOpenlitespeedAdminPassUpdate() {
|
||||
local password="$1"
|
||||
[[ -n "$password" ]] || { printDanger "$openlitespeedLabel Password not specified"; return 1; }
|
||||
|
||||
local user="${2:-admin}"
|
||||
local encrypt output error
|
||||
|
||||
run encrypt error openlitespeedExec /usr/local/lsws/admin/fcgi-bin/admin_php -q /usr/local/lsws/admin/misc/htpasswd.php "$password" || {
|
||||
printDotText "Get encrypt" "$labelFail"
|
||||
printDanger "$error"
|
||||
return 1
|
||||
}
|
||||
printDotText "Get encrypt" "$labelDone"
|
||||
|
||||
run output error openlitespeedExec bash -c "echo '$user:$encrypt' > /usr/local/lsws/admin/conf/htpasswd" || {
|
||||
printDotText "Save data" "$labelFail"
|
||||
printDanger "$error"
|
||||
return 1
|
||||
}
|
||||
printDotText "Save data" "$labelDone"
|
||||
|
||||
# if admin... save to <hostname>.openlitespeed
|
||||
}
|
||||
|
||||
# Restarts OpenLiteSpeed on all OLS pods.
|
||||
function cmdOpenlitespeedRestart() {
|
||||
local podList error
|
||||
run podList error k3sPodListStatus "$olsKube" || { printDanger "Get pods: $error"; return 1; }
|
||||
[[ -n "$podList" ]] || { printDanger "Pods not found"; return 1; }
|
||||
|
||||
local pod phase output
|
||||
while IFS='|' read -r pod phase; do
|
||||
printSection "$pod"
|
||||
|
||||
if [[ "$phase" != "Running" ]]; then
|
||||
printDotText "Phase" "$fontRed$phase$fontReset"
|
||||
else
|
||||
printDotText "Phase" "$fontGreen$phase$fontReset"
|
||||
|
||||
if ! run output error openlitespeedPodExec "$pod" /usr/local/lsws/bin/lswsctrl restart; then
|
||||
printDotText "Restart" "$labelUnknown"
|
||||
printDanger "$error"
|
||||
elif [[ "$output" =~ "[OK]" ]]; then
|
||||
printDotText "Restart" "$fontGreen$output$fontReset"
|
||||
else
|
||||
printDotText "Restart" "$fontRed$output$fontReset"
|
||||
fi
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$podList")
|
||||
}
|
||||
|
||||
# Restarts PHP workers on all OLS pods.
|
||||
function cmdOpenlitespeedPhpKill() {
|
||||
local target="$1"
|
||||
[[ -n "$target" ]] || { printRow; printDanger "Target not specified"; return 1; }
|
||||
|
||||
local podList error
|
||||
run podList error k3sPodListStatus "$olsKube" || { printRow; printDanger "Get pods: $error"; return 1; }
|
||||
[[ -n "$podList" ]] || { printRow; printDanger "Pods not found"; return 1; }
|
||||
|
||||
local uid=""
|
||||
if [[ "$target" != "all" ]]; then
|
||||
local vhostList
|
||||
run vhostList error openlitespeedConfigVhostList || { printRow; printDanger "Cannot get vhost list: $error"; return 1; }
|
||||
listContains "$target" "$vhostList" || { printRow; printDanger "Unknown domain: $target"; return 1; }
|
||||
uid=$(domainToUid "$target")
|
||||
[[ -n "$uid" ]] || { printDanger "Cannot resolve UID for: $target"; return 1; }
|
||||
fi
|
||||
|
||||
local pod phase
|
||||
while IFS='|' read -r pod phase; do
|
||||
printSection "$pod"
|
||||
|
||||
if [[ "$phase" != "Running" ]]; then
|
||||
printDotText "Phase" "$fontRed$phase$fontReset"
|
||||
else
|
||||
printDotText "Phase" "$fontGreen$phase$fontReset"
|
||||
|
||||
if [[ -n "$uid" ]]; then
|
||||
runSilent openlitespeedPodExec "$pod" pkill -u "$uid" -x lsphp
|
||||
else
|
||||
runSilent openlitespeedPodExec "$pod" pkill -x lsphp
|
||||
fi
|
||||
printDotText "kill$fontBlue lsphp$fontReset processes for $target" "$labelDone"
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$podList")
|
||||
}
|
||||
|
||||
# Prints OpenLiteSpeed and PHP versions for each OLS pod.
|
||||
function cmdOpenlitespeedInfo() {
|
||||
local output error podList ver pid
|
||||
|
||||
if ! run podList error k3sPodListStatus "$olsKube"; then
|
||||
printDanger "Get pods: $error"
|
||||
elif [[ -z "$podList" ]]; then
|
||||
printDanger "Pods not found"
|
||||
else
|
||||
while IFS='|' read -r pod phase; do
|
||||
printSection "$pod"
|
||||
|
||||
if [[ "$phase" != "Running" ]]; then
|
||||
printDotText "Phase" "$fontRed$phase$fontReset"
|
||||
else
|
||||
printDotText "Phase" "$fontGreen$phase$fontReset"
|
||||
|
||||
if ! run output error openlitespeedPodExec "$pod" /usr/local/lsws/bin/lswsctrl status; then
|
||||
printDotText "Status" "$labelUnknown"
|
||||
printDanger "$error"
|
||||
elif [[ "$output" =~ "running" ]]; then
|
||||
printDotText "OpenLiteSpeed status" "$fontGreen$output$fontReset"
|
||||
else
|
||||
printDotText "OpenLiteSpeed status" "$fontRed$output$fontReset"
|
||||
fi
|
||||
|
||||
if run output error openlitespeedPodExec "$pod" /usr/local/lsws/bin/lshttpd -v; then
|
||||
ver=$(awk 'NR==1{print $1, $2}' <<< "$output")
|
||||
printDotText "OpenLiteSpeed version" "$ver"
|
||||
else
|
||||
printDotText "OpenLiteSpeed version" "$labelUnknown"
|
||||
printDanger "$error"
|
||||
fi
|
||||
|
||||
if run output error openlitespeedPodExec "$pod" php -r 'echo PHP_VERSION, "\n";'; then
|
||||
printDotText "PHP version" "$output"
|
||||
else
|
||||
printDotText "PHP version" "$labelUnknown"
|
||||
printDanger "$error"
|
||||
fi
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$podList")
|
||||
fi
|
||||
|
||||
printSection "Hosts"
|
||||
local vhostList vhostDown
|
||||
if run output error openlitespeedConfigVhostList; then
|
||||
mapfile -t vhostList < <(awk 'NF' <<< "$output")
|
||||
printDotText "all" "${#vhostList[@]}"
|
||||
else
|
||||
printDotText "all" "$labelUnknown"
|
||||
printDanger "$error"
|
||||
fi
|
||||
|
||||
if run output error openlitespeedConfigVhostList "down"; then
|
||||
mapfile -t vhostDownList < <(awk 'NF' <<< "$output")
|
||||
printDotText "down" "${#vhostDownList[@]}"
|
||||
else
|
||||
printDotText "down" "$labelUnknown"
|
||||
printDanger "$error"
|
||||
fi
|
||||
|
||||
local count="$(find "$olsVhostsPath" -mindepth 1 -maxdepth 1 -type d | wc -l)"
|
||||
printDotText "directories" "$fontGray$olsVhostsPath$fontReset $count"
|
||||
}
|
||||
|
||||
# Marks a virtual host as suspended.
|
||||
# $1 (domain): site domain name.
|
||||
function cmdOpenlitespeedVhostDown() {
|
||||
printRow
|
||||
|
||||
local error
|
||||
if ! runError error openlitespeedVhostConfigDown "$@"; then
|
||||
printDotText "VHost down" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "VHost down" "$labelPass"
|
||||
cmdOpenlitespeedRestart
|
||||
fi
|
||||
}
|
||||
|
||||
# Marks a virtual host as active.
|
||||
# $1 (domain): site domain name.
|
||||
function cmdOpenlitespeedVhostUp() {
|
||||
printRow
|
||||
|
||||
local error
|
||||
if ! runError error openlitespeedVhostConfigUp "$@"; then
|
||||
printDotText "VHost up" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "VHost up" "$labelPass"
|
||||
cmdOpenlitespeedRestart
|
||||
fi
|
||||
}
|
||||
|
||||
# Lists virtual hosts with optional status filtering.
|
||||
# [$1] (type): all (default) | up | down.
|
||||
function cmdOpenlitespeedSiteList() {
|
||||
printRow
|
||||
|
||||
local output error type="${1:-all}"
|
||||
if ! run output error openlitespeedConfigVhostList "$type"; then
|
||||
printDanger "$error"
|
||||
else
|
||||
printText "$output"
|
||||
fi
|
||||
}
|
||||
|
||||
# Updates the Traefik middleware allowlist for OpenLiteSpeed WebAdmin.
|
||||
function cmdOpenlitespeedAdminWhiteList() {
|
||||
printRow
|
||||
|
||||
local output error
|
||||
run output error openlitespeedAdminWhiteList || { printDotText "Update" "$labelFail"; printDanger "$error"; return 1; }
|
||||
|
||||
printDotText "White list" "$output"
|
||||
printDotText "Update" "$labelDone"
|
||||
}
|
||||
|
||||
# Updates OpenLiteSpeed WebAdmin access control from current Traefik pod IPs.
|
||||
function cmdOpenlitespeedAdminAllowList() {
|
||||
printRow
|
||||
|
||||
local output error
|
||||
run output error openlitespeedAdminAllowList || { printDotText "Update" "$labelFail"; printDanger "$error"; return 1; }
|
||||
|
||||
printDotText "Allow list: ${output:-$labelNull}"
|
||||
printDotText "Update" "$labelDone"
|
||||
cmdOpenlitespeedRestart
|
||||
}
|
||||
|
||||
# Sets aliases for an OpenLiteSpeed virtual host.
|
||||
# $1 (domain): primary site domain name.
|
||||
# $@ (...): alias domain names.
|
||||
function cmdOpenlitespeedVhostAliasSet() {
|
||||
local domain
|
||||
domain=$(domainPrepare "$1")
|
||||
|
||||
printRow
|
||||
|
||||
domainCheck "$domain" || return 1
|
||||
|
||||
local vhostList aliasList output error
|
||||
if ! run vhostList error openlitespeedConfigVhostList; then
|
||||
appError "Error retrieving vhost list: $error"
|
||||
return 1
|
||||
elif ! listContains "$domain" "$vhostList"; then
|
||||
appError "Domain not exists in OpenLiteSpeed config: $domain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
run output error openlitespeedVhostSet "$@" || {
|
||||
printDotText "Set" "$labelFail"
|
||||
printDanger "$error"
|
||||
return 1
|
||||
}
|
||||
|
||||
printDotText "Alias" "${output:-$labelNull}"
|
||||
printDotText "Set" "$labelDone"
|
||||
cmdOpenlitespeedRestart
|
||||
}
|
||||
|
||||
# Lists aliases for a domain or all domains.
|
||||
# [$1] (target): domain name, or "all" to list all.
|
||||
function cmdOpenlitespeedAliasList() {
|
||||
printRow
|
||||
|
||||
local output error domain target="$1"
|
||||
if [[ "$target" == all ]]; then
|
||||
if ! run output error openlitespeedConfigAliasList; then
|
||||
printDanger "$error"
|
||||
elif [[ -z "$output" ]]; then
|
||||
printText "$labelNull"
|
||||
else
|
||||
printText "$output"
|
||||
fi
|
||||
else
|
||||
domain=$(domainPrepare "$target")
|
||||
if ! run output error openlitespeedConfigVhostAliasList "$domain"; then
|
||||
printDanger "$error"
|
||||
elif [[ -z "$output" ]]; then
|
||||
printText "$labelNull"
|
||||
else
|
||||
printText "$output"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Tests the OpenLiteSpeed configuration inside the container.
|
||||
function cmdOpenlitespeedConfigCheck() {
|
||||
printRow
|
||||
|
||||
local output error
|
||||
run output error openlitespeedExec sh -lc "/usr/local/lsws/bin/openlitespeed -t 2>/dev/null || true"
|
||||
if grep -qi 'configuration failed!' <<< "$output"; then
|
||||
printDotText "Check config" "$labelFail"
|
||||
printDanger "$output"
|
||||
else
|
||||
printDotText "Check config" "$labelPass"
|
||||
fi
|
||||
}
|
||||
|
||||
function cmdOpenlitespeedVhostRebuild() {
|
||||
local target="$1"
|
||||
local vhostList error
|
||||
|
||||
printRow
|
||||
|
||||
if [[ -z "$target" ]]; then
|
||||
printDanger "Target not specified";
|
||||
elif ! run vhostList error openlitespeedConfigVhostList; then
|
||||
printDanger "Cannot get vhost list: $error";
|
||||
elif [[ "$target" == "all" ]]; then
|
||||
local domain
|
||||
for domain in $vhostList; do
|
||||
if ! runError error openlitespeedVhostRebuild "$domain"; then
|
||||
printDotText "$domain" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "$domain" "$labelDone"
|
||||
fi
|
||||
done
|
||||
elif ! listContains "$target" "$vhostList"; then
|
||||
printDanger "Unknown domain: $target";
|
||||
elif ! runError error openlitespeedVhostRebuild "$target"; then
|
||||
printDotText "$target" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "$target" "$labelDone"
|
||||
fi
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
postfixLabel="[Postfix]"
|
||||
|
||||
# Generates a self-signed TLS certificate for Postfix if one does not already exist.
|
||||
function cmdPostfixPreparation() {
|
||||
printSection "Preparation..."
|
||||
|
||||
if [[ ! -f "$postfixTlsCrtFile" || ! -f "$postfixTlsKeyFile" ]]; then
|
||||
local crtPath; crtPath=$(dirname "$postfixTlsCrtFile")
|
||||
local tlsCnfFile="$crtPath/openssl.cnf"
|
||||
local cn="${postfixHost:-$postfixDomain}"
|
||||
local sanList="DNS:${cn}"
|
||||
[[ -n "$postfixDomain" ]] && sanList="${sanList},DNS:${postfixDomain}"
|
||||
mkdir -p "$crtPath" || {
|
||||
printDotText "preparation" "$labelFail"
|
||||
printDanger "Failed to create folder for certificate";
|
||||
return 1;
|
||||
}
|
||||
|
||||
cat >"$tlsCnfFile" <<EOF
|
||||
[req]
|
||||
distinguished_name = dn
|
||||
x509_extensions = v3_req
|
||||
prompt = no
|
||||
[dn]
|
||||
CN = ${cn}
|
||||
[v3_req]
|
||||
subjectAltName = ${sanList}
|
||||
keyUsage = digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = serverAuth
|
||||
EOF
|
||||
openssl req -x509 -nodes -newkey rsa:2048 -days 365 -keyout "$postfixTlsKeyFile" -out "$postfixTlsCrtFile" -config "$tlsCnfFile" || {
|
||||
printDotText "preparation" "$labelFail"
|
||||
printDanger "TLS gen failed";
|
||||
return 1;
|
||||
}
|
||||
fi
|
||||
|
||||
printDotText "preparation" "$labelDone"
|
||||
}
|
||||
|
||||
# Renders the Postfix Kubernetes YAML manifest via Helm.
|
||||
function cmdPostfixYamlRender() {
|
||||
local templateFile="templates/$postfixKube.yaml"
|
||||
|
||||
printSection "Render YAML file | Helm"
|
||||
printDotText "Template" "$appAssetsPath/k3s/$templateFile"
|
||||
[[ -f "$appAssetsPath/k3s/$templateFile" ]] || {
|
||||
printDanger "Template file not found"
|
||||
return 1
|
||||
}
|
||||
|
||||
local val postfixTlsCrtB64 postfixTlsKeyB64
|
||||
val=$(base64 -w0 "$postfixTlsCrtFile") || {
|
||||
printDotText "render" "$labelFail"
|
||||
printDanger "Base64 gen failed (1)"
|
||||
return 1
|
||||
}
|
||||
postfixTlsCrtB64=$(sed 's/[&\\]/\\&/g' <<<"$val") || {
|
||||
printDotText "render" "$labelFail"
|
||||
printDanger "Base64 gen failed (2)"
|
||||
return 1
|
||||
}
|
||||
val=$(base64 -w0 "$postfixTlsKeyFile") || {
|
||||
printDotText "render" "$labelFail"
|
||||
printDanger "Base64 gen failed (3)"
|
||||
return 1
|
||||
}
|
||||
postfixTlsKeyB64=$(sed 's/[&\\]/\\&/g' <<<"$val") || {
|
||||
printDotText "render" "$labelFail"
|
||||
printDanger "Base64 gen failed (4)"
|
||||
return 1
|
||||
}
|
||||
|
||||
local varsFile
|
||||
varsFile=$(mktemp "/tmp/$postfixKube.vars.XXXXXX.yaml") || {
|
||||
printDotText "render" "$labelFail"
|
||||
printDanger "Create temp variables file"
|
||||
return 1
|
||||
}
|
||||
printDotText "Variables" "$varsFile"
|
||||
trap 'rm -f -- "$varsFile"' RETURN
|
||||
cat > "$varsFile" <<EOF
|
||||
postfixHelm: true
|
||||
namespace: $k3sNamespace
|
||||
postfix: $postfixKube
|
||||
postfixPath: $postfixPath
|
||||
postfixTlsCrtB64: $postfixTlsCrtB64
|
||||
postfixTlsKeyB64: $postfixTlsKeyB64
|
||||
postfixHost: $postfixHost
|
||||
postfixPostmaster: $postfixPostmaster
|
||||
postfixDefaultRealm: $postfixDefaultRealm
|
||||
postfixConfigPath: $postfixConfigPath
|
||||
postfixDkimPath: $postfixDkimPath
|
||||
postfixDkimSelector: $postfixDkimSelector
|
||||
postfixDomainsFile: $postfixDomainsFile
|
||||
postfixAliasesFile: $postfixAliasesFile
|
||||
postfixSendersFile: $postfixSendersFile
|
||||
EOF
|
||||
|
||||
printDotText "Result" "$postfixYaml"
|
||||
mkdir -p -- "$(dirname -- "$postfixYaml")" || return 1
|
||||
fileBackup "$postfixYaml"
|
||||
helm template stack "$appAssetsPath/k3s" -f "$varsFile" --show-only "$templateFile" > "$postfixYaml" || {
|
||||
printDotText "render" "$labelFail"
|
||||
printDanger "Render failed"
|
||||
return 1
|
||||
}
|
||||
|
||||
printDotText "render" "$labelDone"
|
||||
}
|
||||
|
||||
# Initializes Postfix after install: generates DKIM for postfixHost and sets sasldb2 ownership.
|
||||
function cmdPostfixInit() {
|
||||
printSection "Initialization..."
|
||||
|
||||
touch "$postfixConfigPath/$postfixDomainsFile" || return 1
|
||||
touch "$postfixConfigPath/$postfixAliasesFile" || return 1
|
||||
touch "$postfixConfigPath/$postfixSendersFile" || return 1
|
||||
|
||||
postfixDkimAdd "$postfixHost" || {
|
||||
printDotText "Add DKIM for host" "$labelFail"
|
||||
return 1
|
||||
}
|
||||
printDotText "Add DKIM for host" "$labelDone"
|
||||
|
||||
local error
|
||||
if ! runError error postfixExec chown postfix:postfix /config/sasldb2; then
|
||||
printDotText "Set owner /config/sasldb2" "$labelFail"
|
||||
printDanger "$error"
|
||||
return 1
|
||||
fi
|
||||
printDotText "Set owner /config/sasldb2" "$labelDone"
|
||||
}
|
||||
|
||||
function cmdPostfixUpdate() {
|
||||
cmdPostfixYamlRender || return 1
|
||||
cmdK3sYamlApplyEx "$postfixYaml" || return 1
|
||||
}
|
||||
|
||||
# Installs Postfix into Kubernetes.
|
||||
function cmdPostfixInstall() {
|
||||
cmdPostfixPreparation || return 1
|
||||
cmdPostfixYamlRender || return 1
|
||||
cmdK3sYamlApplyEx "$postfixYaml" || return 1
|
||||
cmdPostfixInit || return 1
|
||||
}
|
||||
|
||||
# Uninstalls Postfix Kubernetes resources.
|
||||
function cmdPostfixUninstall() {
|
||||
"$k3sCmd" kubectl delete -f "$postfixYaml"
|
||||
}
|
||||
|
||||
function cmdPostfixUser() {
|
||||
local output error
|
||||
|
||||
printRow
|
||||
|
||||
if ! run output error postfixUserList; then
|
||||
printDanger "$error"
|
||||
elif [[ -z "$output" ]]; then
|
||||
printText "$labelNull"
|
||||
else
|
||||
printText "$output"
|
||||
fi
|
||||
}
|
||||
|
||||
# Prints the Postfix mail version.
|
||||
function cmdPostfixInfo() {
|
||||
local output error podList ver pid
|
||||
|
||||
if ! run podList error k3sPodListStatus "$postfixKube"; then
|
||||
printDanger "Get pods: $error"
|
||||
elif [[ -z "$podList" ]]; then
|
||||
printDanger "Pods not found"
|
||||
else
|
||||
while IFS='|' read -r pod phase; do
|
||||
printSection "$pod"
|
||||
|
||||
if [[ "$phase" != "Running" ]]; then
|
||||
printDotText "Phase" "$fontRed$phase$fontReset"
|
||||
else
|
||||
printDotText "Phase" "$fontGreen$phase$fontReset"
|
||||
|
||||
if ! run output error postfixPodExec "$pod" postfix status; then
|
||||
printDotText "Postfix status" "$fontRed$labelFail$fontReset"
|
||||
printDanger "$error"
|
||||
else
|
||||
output="${output:-$error}"
|
||||
if [[ $output == *"is running"* ]]; then
|
||||
pid=${output##*PID: }
|
||||
pid=${pid%%[^0-9]*}
|
||||
printDotText "Postfix status" "$labelRunning"
|
||||
printDotText "pid" "$pid"
|
||||
else
|
||||
printDotText "Postfix status" "$fontRed$output$fontReset"
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! run output error postfixPodExec "$pod" postconf mail_version; then
|
||||
printDotText "Postfix mail version" "$fontRed$labelFail$fontReset"
|
||||
printDanger "$error"
|
||||
else
|
||||
ver=$(printf '%s' "$output" | cut -d '=' -f2 | tr -d '\r\n')
|
||||
printDotText "Postfix mail version" "$ver"
|
||||
fi
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$podList")
|
||||
fi
|
||||
}
|
||||
|
||||
# Checks MTA host DNS records (A, SPF, PTR, DKIM) against configured values.
|
||||
function cmdPostfixMtaDnsCheck() {
|
||||
local label output error text
|
||||
|
||||
printSection "MTA DNS: $postfixHost"
|
||||
|
||||
# A record
|
||||
if ! run output error dig +short A "$postfixHost" "$dnsResolver"; then
|
||||
printDotText "A record" "$fontRed${output:-$error}$fontReset"
|
||||
else
|
||||
text=$(printf '%s' "$output" | paste -sd, - | sed 's/,/ \/ /g')
|
||||
if grep -Fxq -- "$postfixIp" <<<"$output"; then
|
||||
printDotText "A record" "$fontGreen$text$fontReset"
|
||||
else
|
||||
printDotText "A record" "$fontYellow$text$fontReset"
|
||||
fi
|
||||
fi
|
||||
|
||||
# SPF record
|
||||
if ! run output error dig +short TXT "$postfixHost" "$dnsResolver"; then
|
||||
printDotText "SPF record" "$fontRed${output:-$error}$fontReset"
|
||||
elif grep -Eq '^"?v=spf1([[:space:]]|")' <<<"$output"; then
|
||||
printDotText "SPF record" "$fontGreen$output$fontReset"
|
||||
else
|
||||
printDotText "SPF record" "$fontRed$output$fontReset"
|
||||
fi
|
||||
|
||||
# PTR record
|
||||
if ! run output error dig -x "$postfixIp" +short "$dnsResolver"; then
|
||||
printDotText "PTR record" "$fontRed${output:-$error}$fontReset"
|
||||
else
|
||||
text=$(printf '%s' "$output" | paste -sd, - | sed 's/,/ \/ /g')
|
||||
if grep -Fxq -- "$postfixHost." <<<"$output"; then
|
||||
printDotText "PTR record" "$fontGreen$text$fontReset"
|
||||
else
|
||||
printDotText "PTR record" "$fontYellow$text$fontReset"
|
||||
fi
|
||||
fi
|
||||
|
||||
# DKIM record
|
||||
label="$postfixLabel DKIM record: $postfixHost"
|
||||
if ! run output error dig +short TXT "$postfixDkimSelector._domainkey.$postfixHost"; then
|
||||
printDotText "DKIM record" "$fontRed${output:-$error}$fontReset"
|
||||
else
|
||||
local dkimDnsNorm dkimFileRaw dkimFileNorm
|
||||
dkimDnsNorm=$(
|
||||
printf '%s\n' "$output" |
|
||||
tr -d '\n' |
|
||||
sed -e 's/"//g' -e 's/[[:space:]]//g' |
|
||||
sed -n 's/.*p=\([^;]*\).*/\1/p'
|
||||
)
|
||||
dkimFileRaw=$(postfixDkimGet "$postfixHost")
|
||||
dkimFileNorm=$(
|
||||
printf '%s\n' "$dkimFileRaw" |
|
||||
tr -d '\n' |
|
||||
sed -e 's/[()"]//g' -e 's/[[:space:]]//g' |
|
||||
sed -n 's/.*p=\([^;]*\).*/\1/p'
|
||||
)
|
||||
if [[ "$dkimDnsNorm" == "$dkimFileNorm" ]]; then
|
||||
printText "$fontGreen$dkimDnsNorm$fontReset"
|
||||
else
|
||||
printText "DNS : $fontYellow$dkimDnsNorm$fontReset"
|
||||
printText "File: $fontYellow$dkimFileNorm$fontReset"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
@@ -1,425 +0,0 @@
|
||||
redisLabel="[Redis]"
|
||||
redisSentinelLabel="[RedisSentinel]"
|
||||
redisHaproxyLabel="[RedisHAProxy]"
|
||||
|
||||
# Prepares Kubernetes secrets for Redis.
|
||||
function cmdRedisPreparation() {
|
||||
printSection "Preparation..."
|
||||
|
||||
local error
|
||||
runError error k3sRun delete secret "$redisKube-secret" --ignore-not-found || {
|
||||
printDotText "preparation" "$labelFail"
|
||||
printDanger "Delete old Secret: $error"
|
||||
return 1
|
||||
}
|
||||
|
||||
runError error k3sRun create secret generic "$redisKube-secret" --from-literal=root-password="$redisRootPass" || {
|
||||
printDotText "preparation" "$labelFail"
|
||||
printDanger "Create new Secret: $error"
|
||||
return 1
|
||||
}
|
||||
|
||||
fileBackup "$redisPath/$redisFileUsersAcl"
|
||||
|
||||
printDotText "preparation" "$labelDone"
|
||||
}
|
||||
|
||||
# Renders the Redis Kubernetes YAML manifest via Helm.
|
||||
function cmdRedisYamlRender() {
|
||||
local templateFile="templates/$redisKube.yaml"
|
||||
|
||||
printSection "Render YAML file | Helm"
|
||||
printDotText "Template" "$appAssetsPath/k3s/$templateFile"
|
||||
[[ -f "$appAssetsPath/k3s/$templateFile" ]] || {
|
||||
printDanger "Template file not found"
|
||||
return 1
|
||||
}
|
||||
|
||||
local profileCpuFile="$appAssetsPath/k3s/profiles/cpu-$kubeCpuProfile.yaml"
|
||||
printDotText "CPU profile" "$profileCpuFile"
|
||||
[[ -f "$profileCpuFile" ]] || {
|
||||
printDanger "CPU profile file not found"
|
||||
return 1
|
||||
}
|
||||
|
||||
local profileMemoryFile="$appAssetsPath/k3s/profiles/memory-$kubeMemoryProfile.yaml"
|
||||
printDotText "Memory profile" "$profileMemoryFile"
|
||||
[[ -f "$profileMemoryFile" ]] || {
|
||||
printDanger "Memory profile file not found"
|
||||
return 1
|
||||
}
|
||||
|
||||
local varsFile
|
||||
varsFile=$(mktemp "/tmp/$redisKube.vars.XXXXXX.yaml") || {
|
||||
printDotText "render" "$labelFail"
|
||||
printDanger "Create temp variables file"
|
||||
return 1
|
||||
}
|
||||
printDotText "Variables" "$varsFile"
|
||||
trap 'rm -f -- "$varsFile"' RETURN
|
||||
cat > "$varsFile" <<EOF
|
||||
redisHelm: true
|
||||
namespace: $k3sNamespace
|
||||
redis: $redisKube
|
||||
redisSentinel: $redisSentinelKube
|
||||
redisPath: $redisPath
|
||||
redisFileUsersAcl: $redisFileUsersAcl
|
||||
EOF
|
||||
|
||||
printDotText "Result" "$redisYaml"
|
||||
mkdir -p -- "$(dirname -- "$redisYaml")" || return 1
|
||||
fileBackup "$redisYaml"
|
||||
helm template stack "$appAssetsPath/k3s" -f "$varsFile" -f "$profileCpuFile" -f "$profileMemoryFile" --show-only "$templateFile" > "$redisYaml" || {
|
||||
printDotText "render" "$labelFail"
|
||||
printDanger "Render failed"
|
||||
return 1
|
||||
}
|
||||
|
||||
printDotText "render" "$labelDone"
|
||||
}
|
||||
|
||||
# Updates Redis Kubernetes resources (limits, config, etc.) without re-initialization.
|
||||
function cmdRedisUpdate() {
|
||||
cmdRedisYamlRender || return 1
|
||||
cmdK3sYamlApplyEx "$redisYaml" || return 1
|
||||
}
|
||||
|
||||
# Installs Redis into Kubernetes.
|
||||
function cmdRedisInstall() {
|
||||
cmdRedisPreparation || return 1
|
||||
cmdRedisYamlRender || return 1
|
||||
cmdK3sYamlApplyEx "$redisYaml" || return 1
|
||||
}
|
||||
|
||||
# Uninstalls Redis Kubernetes resources.
|
||||
function cmdRedisUninstall() {
|
||||
"$k3sCmd" kubectl delete -f "$redisYaml"
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
# local output error podList ver pid
|
||||
|
||||
# if ! run podList error k3sPodListStatus "$olsKube"; then
|
||||
# printDanger "Get pods: $error"
|
||||
# elif [[ -z "$podList" ]]; then
|
||||
# printDanger "Pods not found"
|
||||
# else
|
||||
# while IFS='|' read -r pod phase; do
|
||||
# printSection "$pod"
|
||||
|
||||
# if [[ "$phase" != "Running" ]]; then
|
||||
# printDotText "Phase" "$fontRed$phase$fontReset"
|
||||
# else
|
||||
# printDotText "Phase" "$fontGreen$phase$fontReset"
|
||||
|
||||
# if ! run output error openlitespeedPodExec "$pod" /usr/local/lsws/bin/lswsctrl status; then
|
||||
# printDotText "Status" "$labelUnknown"
|
||||
# printDanger "$error"
|
||||
# elif [[ "$output" =~ "running" ]]; then
|
||||
# printDotText "OpenLiteSpeed status" "$fontGreen$output$fontReset"
|
||||
# else
|
||||
# printDotText "OpenLiteSpeed status" "$fontRed$output$fontReset"
|
||||
# fi
|
||||
|
||||
# if run output error openlitespeedPodExec "$pod" /usr/local/lsws/bin/lshttpd -v; then
|
||||
# ver=$(awk 'NR==1{print $1, $2}' <<< "$output")
|
||||
# printDotText "OpenLiteSpeed version" "$ver"
|
||||
# else
|
||||
# printDotText "OpenLiteSpeed version" "$labelUnknown"
|
||||
# printDanger "$error"
|
||||
# fi
|
||||
|
||||
# if run output error openlitespeedPodExec "$pod" php -r 'echo PHP_VERSION, "\n";'; then
|
||||
# printDotText "PHP version" "$output"
|
||||
# else
|
||||
# printDotText "PHP version" "$labelUnknown"
|
||||
# printDanger "$error"
|
||||
# fi
|
||||
# fi
|
||||
# done < <(awk 'NF' <<< "$podList")
|
||||
# fi
|
||||
|
||||
# printRow
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Prints replication INFO for all Redis and Sentinel pods.
|
||||
function cmdRedisInfo() {
|
||||
local output error podList ver pid
|
||||
|
||||
if ! run podList error k3sPodListStatus "$redisKube"; then
|
||||
printDanger "Get pods: $error"
|
||||
elif [[ -z "$podList" ]]; then
|
||||
printDanger "Pods not found"
|
||||
else
|
||||
while IFS='|' read -r pod phase; do
|
||||
printSection "$pod"
|
||||
|
||||
if [[ "$phase" != "Running" ]]; then
|
||||
printDotText "Phase" "$fontRed$phase$fontReset"
|
||||
else
|
||||
printDotText "Phase" "$fontGreen$phase$fontReset"
|
||||
|
||||
if ! run output error redisPodExecCli "$pod" INFO server; then
|
||||
printDotText "Redis version" "$labelUnknown"
|
||||
printDanger "$error"
|
||||
else
|
||||
ver=$(printf '%s' "$output" | grep redis_version | cut -d ':' -f2 | tr -d '[:space:]')
|
||||
printDotText "Redis version" "$ver"
|
||||
fi
|
||||
|
||||
if ! run output error redisPodExecCli "$pod" INFO replication; then
|
||||
printDotText "Redis version" "$labelUnknown"
|
||||
printDanger "$error"
|
||||
else
|
||||
|
||||
local role slaves masterHost masterLinkStatus slaveLag
|
||||
role=$(printf '%s' "$output" | grep -i "role" | cut -d: -f2 | tr -d '\r\n')
|
||||
if [[ "$role" == "master" ]]; then
|
||||
printDotText "Replica role" "$fontGreen$role$fontReset"
|
||||
|
||||
slaves=$(printf '%s' "$output" | grep -i "connected_slaves" | cut -d: -f2 | tr -d '\r\n')
|
||||
if [[ "$slaves" -eq 0 ]]; then
|
||||
printDotText "Slaves" "$fontRed$slaves$fontReset"
|
||||
else
|
||||
printDotText "Slaves" "$fontGreen$slaves$fontReset"
|
||||
fi
|
||||
elif [[ "$role" == "slave" ]]; then
|
||||
printDotText "Replica role" "$fontGreen$role$fontReset"
|
||||
|
||||
masterHost=$(printf '%s' "$output" | grep -i "master_host" | cut -d: -f2 | tr -d '\r\n')
|
||||
masterLinkStatus=$(printf '%s' "$output" | grep -i "master_link_status" | cut -d: -f2 | tr -d '\r\n')
|
||||
if [[ -z "$masterHost" || "$masterLinkStatus" != "up" ]]; then
|
||||
[[ -z "$masterHost" ]] && printDotText "Master" "${fontRed}Not connect to master (master_host)$fontReset"
|
||||
[[ "$masterLinkStatus" != "up" ]] && printDotText "Master" "${fontRed}Not connect to master (master_link_status)$fontReset"
|
||||
continue
|
||||
fi
|
||||
printDotText "Master" "$fontGreen$masterHost$fontReset"
|
||||
|
||||
slaveLag=$(printf '%s' "$output" | grep -i "master_last_io_seconds_ago" | cut -d: -f2 | tr -d '\r\n')
|
||||
[[ "$slaveLag" -ge 3 ]] && printDotText "Replication delay" "$fontYallow${slaveLag}s$fontReset"
|
||||
else
|
||||
printDotText "Replica role" "$fontRed$role$fontReset"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$podList")
|
||||
fi
|
||||
|
||||
if ! run podList error k3sPodListStatus "$redisSentinelKube"; then
|
||||
printDanger "Get pods: $error"
|
||||
elif [[ -z "$podList" ]]; then
|
||||
printDanger "Pods not found"
|
||||
else
|
||||
local masterInfo slaveInfo
|
||||
local masterName masterIP masterPort masterNumOtherSentinels masterQuorum
|
||||
local masterSig replicaSig refPod refMasterSig refReplicaSig
|
||||
local activeReplicaCount mismatch=0
|
||||
|
||||
while IFS='|' read -r pod phase; do
|
||||
printSection "$pod"
|
||||
|
||||
if [[ "$phase" != "Running" ]]; then
|
||||
printDotText "Phase" "$fontRed$phase$fontReset"
|
||||
else
|
||||
printDotText "Phase" "$fontGreen$phase$fontReset"
|
||||
|
||||
if ! run output error redisPodExecCli "$pod" INFO server; then
|
||||
printDotText "RedisSentinel version" "$labelUnknown"
|
||||
printDanger "$error"
|
||||
else
|
||||
ver=$(printf '%s' "$output" | grep redis_version | cut -d ':' -f2 | tr -d '[:space:]')
|
||||
printDotText "RedisSentinel version" "$ver"
|
||||
fi
|
||||
|
||||
run masterInfo error redisPodExecCli "$pod" SENTINEL masters || {
|
||||
printDotText "Get sentinel masters" "$labelFail"
|
||||
printDanger "$error"
|
||||
mismatch=1
|
||||
continue
|
||||
}
|
||||
|
||||
local -A masterData=()
|
||||
while read -r key && read -r value; do
|
||||
masterData["$key"]="$value"
|
||||
done <<< "$masterInfo"
|
||||
masterName="${masterData[name]}"
|
||||
masterIP="${masterData[ip]}"
|
||||
masterPort="${masterData[port]}"
|
||||
masterNumOtherSentinels="${masterData[num-other-sentinels]:-0}"
|
||||
masterQuorum="${masterData[quorum]:-0}"
|
||||
|
||||
run slaveInfo error redisPodExecCli "$pod" --raw sentinel replicas "$masterName" || {
|
||||
printDotText "Get sentinel replicas" "$labelFail"
|
||||
printDanger "$error"
|
||||
mismatch=1
|
||||
continue
|
||||
}
|
||||
|
||||
replicaSig="$(redisSentinelParseReplicas "$slaveInfo" | awk 'NF' | sort)"
|
||||
activeReplicaCount="$(awk 'NF {c++} END {print c+0}' <<< "$replicaSig")"
|
||||
masterSig="${masterName}|${masterIP}|${masterPort}|${activeReplicaCount}|${masterNumOtherSentinels}|${masterQuorum}"
|
||||
|
||||
refPod="" refMasterSig="" refReplicaSig=""
|
||||
if [[ -z "$refPod" ]]; then
|
||||
refPod="$pod"
|
||||
refMasterSig="$masterSig"
|
||||
refReplicaSig="$replicaSig"
|
||||
fi
|
||||
|
||||
if [[ "$masterSig" != "$refMasterSig" || "$replicaSig" != "$refReplicaSig" ]]; then
|
||||
mismatch=1
|
||||
printDotText "Topology" "mismatch vs $fontRed$refPod$fontReset"
|
||||
# else
|
||||
# printDotText "Topology" "matches $fontGreen$refPod$fontReset"
|
||||
fi
|
||||
|
||||
printDotText " ┌ Replica" "$masterName"
|
||||
if (( masterQuorum < 1 )); then
|
||||
printDotText " ├ Quorum" "$fontRed$masterQuorum$fontReset"
|
||||
else
|
||||
printDotText " ├ Quorum" "$fontGreen$masterQuorum$fontReset"
|
||||
fi
|
||||
printDotText " ├ Other sentinels" "$masterNumOtherSentinels"
|
||||
|
||||
# printDotText "Master" "$masterIP:$masterPort"
|
||||
printDotText " ├ Master" "$masterIP"
|
||||
if (( activeReplicaCount < 1 )); then
|
||||
printDotText " └ Slave count" "$fontRed$activeReplicaCount$fontReset"
|
||||
else
|
||||
printDotText " └ Slave count" "$fontGreen$activeReplicaCount$fontReset"
|
||||
fi
|
||||
while IFS=$'\t' read -r slaveName slaveIP slavePort slaveMaster slaveRunId; do
|
||||
[[ -z "$slaveName" ]] && continue
|
||||
printText " ┊"
|
||||
printDotText " ├ Slave" "$slaveIP"
|
||||
printDotText " ├ Master" "$slaveMaster"
|
||||
printDotText " └ RunID" "$slaveRunId"
|
||||
done <<< "$replicaSig"
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$podList")
|
||||
fi
|
||||
|
||||
if ! run podList error k3sPodListStatus "$redisKube-haproxy"; then
|
||||
printDanger "Get pods: $error"
|
||||
elif [[ -z "$podList" ]]; then
|
||||
printDanger "Pods not found"
|
||||
else
|
||||
while IFS='|' read -r pod phase; do
|
||||
printSection "$pod"
|
||||
|
||||
if [[ "$phase" != "Running" ]]; then
|
||||
printDotText "Phase" "$fontRed$phase$fontReset"
|
||||
else
|
||||
printDotText "Phase" "$fontGreen$phase$fontReset"
|
||||
|
||||
if ! run output error k3sRun exec "pod/$pod" -- haproxy -v; then
|
||||
printDotText "HAProxy version" "$labelUnknown"
|
||||
printDanger "$error"
|
||||
else
|
||||
ver=$(awk 'NR==1{print $3}' <<< "$output")
|
||||
printDotText "HAProxy version" "$ver"
|
||||
fi
|
||||
|
||||
local role
|
||||
if ! run output error redisExecCli -h redis-master -p 6379 ROLE; then
|
||||
printDotText "Replica role" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
role=$(printf '%s' "$output" | head -n1 | tr -d '\r\n')
|
||||
if [[ "$role" != "master" ]]; then
|
||||
printDotText "Replica role" "$fontRed$role$fontReset"
|
||||
else
|
||||
printDotText "Replica role" "$fontGreen$role$fontReset"
|
||||
fi
|
||||
|
||||
fi
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$podList")
|
||||
fi
|
||||
}
|
||||
|
||||
# Checks replication role/health on each Redis pod and Sentinel topology consistency.
|
||||
function cmdRedisStatus() {
|
||||
local output error podList
|
||||
|
||||
if ! run podList error k3sPodList "$redisKube"; then
|
||||
printDanger "$redisLabel Get pods: $error"
|
||||
elif [[ -z "$podList" ]]; then
|
||||
printDanger "$redisLabel Pods not found"
|
||||
else
|
||||
while read -r pod; do
|
||||
printSection "$pod"
|
||||
if run output error redisPodExecCli "$pod" INFO replication; then
|
||||
printText "$output"
|
||||
else
|
||||
printDanger "$redisLabel $pod | $error"
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$podList")
|
||||
fi
|
||||
|
||||
if ! run podList error k3sPodList "$redisSentinelKube"; then
|
||||
printDanger "$redisSentinelLabel Get pods: $error"
|
||||
elif [[ -z "$podList" ]]; then
|
||||
printDanger "$redisSentinelLabel Pods not found"
|
||||
else
|
||||
while read -r pod; do
|
||||
printSection "$pod"
|
||||
if run output error redisPodExecCli "$pod" INFO sentinel; then
|
||||
printText "$output"
|
||||
else
|
||||
printDanger "$redisSentinelLabel $pod | $error"
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$podList")
|
||||
fi
|
||||
}
|
||||
|
||||
# Lists all Redis ACL users.
|
||||
function cmdRedisUser() {
|
||||
local output error
|
||||
|
||||
printRow
|
||||
|
||||
if ! run output error redisUserListGet; then
|
||||
printDanger "$error"
|
||||
elif [[ -z "$output" ]]; then
|
||||
printText "$labelNull"
|
||||
else
|
||||
printText "$output"
|
||||
fi
|
||||
}
|
||||
|
||||
# Flushes all keys from the Redis database.
|
||||
function cmdRedisDatabaseFlush() {
|
||||
local output error
|
||||
|
||||
printRow
|
||||
|
||||
if run output error redisDatabaseFlush; then
|
||||
printText "$output"
|
||||
else
|
||||
printDanger "$error"
|
||||
fi
|
||||
}
|
||||
|
||||
# Regenerates and applies the Redis root password across all nodes.
|
||||
function cmdRedisRootPassUpdate() {
|
||||
local output error
|
||||
|
||||
printRow
|
||||
|
||||
if run output error redisRootPassUpdate; then
|
||||
printText "$output"
|
||||
else
|
||||
printDanger "$error"
|
||||
fi
|
||||
}
|
||||
@@ -1,473 +0,0 @@
|
||||
restoreLabel="[Restore]"
|
||||
|
||||
# Restores site files from an archive.
|
||||
#
|
||||
# The archive is first extracted into a temporary directory located on the same
|
||||
# filesystem as the target vhost directory. After successful extraction, the
|
||||
# current vhost directory is moved to a temporary backup path and the restored
|
||||
# directory is moved into place.
|
||||
#
|
||||
# $1 (domain): Site domain name.
|
||||
# $2 (file): Source archive file path.
|
||||
#
|
||||
# Returns:
|
||||
# 0 on success, 1 on validation or restore failure.
|
||||
function restoreSiteFiles() {
|
||||
local domain
|
||||
domain=$(domainPrepare "$1")
|
||||
domainCheck "$domain" || return 1
|
||||
|
||||
local target="$olsVhostsPath/$domain"
|
||||
|
||||
local source="$2"
|
||||
if [[ -z "$source" ]]; then
|
||||
appError "Source files not specified"
|
||||
return 1
|
||||
fi
|
||||
if [[ ! -e "$source" ]]; then
|
||||
appError "Source files not found: $source"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local tmpDir restoreSource output rc
|
||||
if [[ -f "$source" ]]; then
|
||||
tmpDir=$(mktemp -d) || {
|
||||
appError "Failed to create temporary restore directory"
|
||||
return 1
|
||||
}
|
||||
|
||||
archiveExtract "$source" "$tmpDir" || {
|
||||
rm -rf -- "$tmpDir" &>/dev/null
|
||||
return 1
|
||||
}
|
||||
|
||||
restoreSource="$tmpDir"
|
||||
else
|
||||
restoreSource="$source"
|
||||
fi
|
||||
|
||||
rm -rf -- "$target" || {
|
||||
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
|
||||
appError "Failed to remove target directory: $target"
|
||||
return 1
|
||||
}
|
||||
|
||||
mkdir -p -- "$target" || {
|
||||
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
|
||||
appError "Failed to create target directory: $target"
|
||||
return 1
|
||||
}
|
||||
|
||||
output=$(cp -a -- "$restoreSource"/. "$target/" 2>&1)
|
||||
rc=$?
|
||||
if [[ $rc -ne 0 ]]; then
|
||||
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
|
||||
appError "Failed to copy restored files: $output"
|
||||
return 1
|
||||
fi
|
||||
|
||||
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
|
||||
|
||||
openlitespeedVhostRebuild "$domain" || return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Restores a site database from a SQL file or archive.
|
||||
#
|
||||
# If the source file is an archive, it is extracted into a temporary directory
|
||||
# first and must contain exactly one SQL file. If the target database already
|
||||
# exists, the SQL file is first imported into a temporary database to validate
|
||||
# the restore before recreating the target database.
|
||||
#
|
||||
# $1 (domain): Site domain name.
|
||||
# $2 (file): Source SQL file or archive file path.
|
||||
#
|
||||
# Returns:
|
||||
# 0 on success, 1 on validation or restore failure.
|
||||
function restoreSiteDatabase() {
|
||||
local domain
|
||||
domain=$(domainPrepare "$1")
|
||||
domainCheck "$domain" || return 1
|
||||
|
||||
local file="$2"
|
||||
if [[ -z "$file" ]]; then
|
||||
appError "Source database file not specified"
|
||||
return 1
|
||||
fi
|
||||
if [[ ! -f "$file" ]]; then
|
||||
appError "Source database file not found: $file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local tmpDir importFile
|
||||
importFile="$file"
|
||||
|
||||
case "$file" in
|
||||
*.zip|*.tar.gz|*.tgz)
|
||||
tmpDir=$(mktemp -d) || {
|
||||
appError "Failed to create temporary database restore directory"
|
||||
return 1
|
||||
}
|
||||
|
||||
archiveExtract "$file" "$tmpDir" || {
|
||||
rm -rf -- "$tmpDir" &>/dev/null
|
||||
return 1
|
||||
}
|
||||
|
||||
local sqlCount
|
||||
sqlCount=$(find -- "$tmpDir" -type f -name "*.sql" | wc -l)
|
||||
if [[ "$sqlCount" -ne 1 ]]; then
|
||||
rm -rf -- "$tmpDir" &>/dev/null
|
||||
appError "Archive must contain exactly one SQL file: $file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
importFile=$(find -- "$tmpDir" -type f -name "*.sql" -print -quit)
|
||||
;;
|
||||
esac
|
||||
|
||||
local databaseName databaseUser databasePass
|
||||
databaseName=$(siteConfigGetOrSet "$domain" "databaseName" "$(mariadbDomain2id "$domain")")
|
||||
databaseUser=$(siteConfigGetOrSet "$domain" "databaseUser" "$(mariadbDomain2id "$domain")")
|
||||
databasePass=$(siteConfigGetOrCreate "$domain" "databasePass")
|
||||
|
||||
local dbExists=0
|
||||
if mariadbDatabaseExists "$databaseName"; then
|
||||
dbExists=1
|
||||
fi
|
||||
|
||||
local output rc
|
||||
|
||||
if (( dbExists == 0 )); then
|
||||
if ! mariadbDatabaseUserSet "$databaseName" "$databaseUser" "$databasePass"; then
|
||||
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
|
||||
appError "Failed to create database/user"
|
||||
return 1
|
||||
fi
|
||||
|
||||
output=$(mariadbMasterImport "$databaseName" "$databaseUser" "$databasePass" "$importFile" 2>&1)
|
||||
rc=$?
|
||||
if [[ $rc -ne 0 ]]; then
|
||||
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
|
||||
appError "mariadbMasterImport: $output"
|
||||
return 1
|
||||
fi
|
||||
|
||||
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
|
||||
return 0
|
||||
fi
|
||||
|
||||
local ts tmpDb
|
||||
ts=$(date +%Y%m%d_%H%M%S)
|
||||
tmpDb="_test_${databaseName}_$ts"
|
||||
|
||||
if ! mariadbDatabaseUserSet "$tmpDb" "$databaseUser" "$databasePass"; then
|
||||
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
|
||||
appError "Failed to prepare tmp database"
|
||||
return 1
|
||||
fi
|
||||
|
||||
output=$(mariadbMasterImport "$tmpDb" "$databaseUser" "$databasePass" "$importFile" 2>&1)
|
||||
rc=$?
|
||||
if [[ $rc -ne 0 ]]; then
|
||||
mariadbDatabaseRemove "$tmpDb"
|
||||
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
|
||||
appError "Failed to import tmp database: $output"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! mariadbDatabaseRemove "$databaseName"; then
|
||||
mariadbDatabaseRemove "$tmpDb"
|
||||
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
|
||||
appError "Failed to recreate target database: remove failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! mariadbDatabaseUserSet "$databaseName" "$databaseUser" "$databasePass"; then
|
||||
mariadbDatabaseRemove "$tmpDb"
|
||||
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
|
||||
appError "Failed to recreate target database: create failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
output=$(mariadbMasterImport "$databaseName" "$databaseUser" "$databasePass" "$importFile" 2>&1)
|
||||
rc=$?
|
||||
if [[ $rc -ne 0 ]]; then
|
||||
mariadbDatabaseRemove "$tmpDb"
|
||||
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
|
||||
appError "Failed to import database: $output"
|
||||
return 1
|
||||
fi
|
||||
|
||||
mariadbDatabaseRemove "$tmpDb"
|
||||
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Restores selected parts of a site from explicit source paths.
|
||||
#
|
||||
# Each source is optional. If a source path is provided, the corresponding part
|
||||
# of the site is restored. If a source path is empty, that part is skipped.
|
||||
#
|
||||
# $1 (domain): Site domain name.
|
||||
# $2 (sourceFiles): Optional source directory or archive file path with site files.
|
||||
# $3 (sourceDatabase): Optional source SQL file or archive file path with database dump.
|
||||
# $4 (sourceConf): Optional source OpenLiteSpeed virtual host config file path.
|
||||
#
|
||||
# Returns:
|
||||
# 0 on success, 1 on validation or restore failure.
|
||||
function restoreSite() {
|
||||
local domain error
|
||||
domain=$(domainPrepare "$1")
|
||||
if ! runError error domainCheck "$domain"; then
|
||||
printDanger "$siteLabel $error"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local sourceFiles="$2"
|
||||
local sourceDatabase="$3"
|
||||
local sourceConf="$4"
|
||||
|
||||
# Files
|
||||
printInfo "$restoreLabel $domain | Files: $sourceFiles"
|
||||
if [[ -n "$sourceFiles" ]]; then
|
||||
if ! runError error restoreSiteFiles "$domain" "$sourceFiles"; then
|
||||
printDanger "$restoreLabel $domain | Files: $error"
|
||||
return 1
|
||||
fi
|
||||
printSuccess "$restoreLabel $domain | Files: Restoration complete"
|
||||
else
|
||||
printWarning "$restoreLabel $domain | Files: Skip restoring"
|
||||
fi
|
||||
|
||||
# Database
|
||||
printInfo "$restoreLabel $domain | Database: $sourceDatabase"
|
||||
if [[ -n "$sourceDatabase" ]]; then
|
||||
if ! runError error restoreSiteDatabase "$domain" "$sourceDatabase"; then
|
||||
printDanger "$restoreLabel $domain | Database: $error"
|
||||
return 1
|
||||
fi
|
||||
printSuccess "$restoreLabel $domain | Database: Restoration complete"
|
||||
else
|
||||
printWarning "$restoreLabel $domain | Database: Skip restoring"
|
||||
fi
|
||||
|
||||
# Config
|
||||
printInfo "$restoreLabel $domain | Config: $sourceConf"
|
||||
if [[ -n "$sourceConf" ]]; then
|
||||
if ! runError error cp -f -- "$sourceConf" "$olsVhostsConfigPath/$domain.conf"; then
|
||||
printDanger "$restoreLabel $domain | Config: $error"
|
||||
return 1
|
||||
fi
|
||||
printSuccess "$restoreLabel $domain | Config: Restoration complete"
|
||||
else
|
||||
printWarning "$restoreLabel $domain | Config: Skip restoring"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Restores a site from available backup entries.
|
||||
#
|
||||
# The function searches short-term and long-term backup paths for files,
|
||||
# database dumps, and OpenLiteSpeed virtual host configuration files matching
|
||||
# the specified domain. It can list available backups or restore a selected one.
|
||||
#
|
||||
# Supported index values:
|
||||
# empty: Show available backups and ask for a backup number.
|
||||
# list: Print available backup paths.
|
||||
# last: Restore the latest available backup.
|
||||
# full: Restore the latest backup that contains files, database, and config.
|
||||
# auto: Restore the latest full backup if available, otherwise the latest backup.
|
||||
# N: Restore backup by numeric index.
|
||||
#
|
||||
# $1 (domain): Site domain name.
|
||||
# $2 (index): Optional backup selector: list, last, full, auto, or numeric index.
|
||||
#
|
||||
# Returns:
|
||||
# 0 on success, 1 on validation, selection, or restore failure.
|
||||
function restoreRun() {
|
||||
local domain error
|
||||
domain=$(domainPrepare "$1")
|
||||
if ! runError error domainCheck "$domain"; then
|
||||
printDanger "$siteLabel $error"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local index="$2"
|
||||
if [[ -n "$index" && ! "$index" =~ ^[0-9]+$ && "$index" != "auto" && "$index" != "last" && "$index" != "full" && "$index" != "list" ]]; then
|
||||
printDanger "Unknown value of index"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local backupEntryList=()
|
||||
local backupPathList=()
|
||||
local path
|
||||
|
||||
shopt -s nullglob
|
||||
local entryList=("$backupDailyPath"/*/*/"$domain"*)
|
||||
shopt -u nullglob
|
||||
for entry in "${entryList[@]}"; do
|
||||
backupEntryList+=("$entry")
|
||||
path=2:$(dirname -- "$entry")
|
||||
if ! arrayContains "$path" "${backupPathList[@]}"; then
|
||||
backupPathList+=("$path")
|
||||
fi
|
||||
done
|
||||
|
||||
shopt -s nullglob
|
||||
local entryList=("$backupArchivePath"/*/*/"$domain"*)
|
||||
shopt -u nullglob
|
||||
for entry in "${entryList[@]}"; do
|
||||
backupEntryList+=("$entry")
|
||||
path=1:$(dirname -- "$entry")
|
||||
if ! arrayContains "$path" "${backupPathList[@]}"; then
|
||||
backupPathList+=("$path")
|
||||
fi
|
||||
done
|
||||
|
||||
local backupSortList=($(printf "%s\n" "${backupPathList[@]}" | \
|
||||
sed "s/\/${backupFirstDir}$/\/./" | \
|
||||
sort -r | \
|
||||
sed "s/\/.$/\/${backupFirstDir}/"))
|
||||
|
||||
local bType bPath bTime bDate sourceList indexFull suffix
|
||||
local counter=1
|
||||
for marker in "${backupSortList[@]}"; do
|
||||
bType="${marker:0:1}"
|
||||
bPath="${marker:2}"
|
||||
bTime=$(basename "$bPath")
|
||||
bDate=$(basename -- $(dirname -- "$bPath"))
|
||||
|
||||
sourceList=()
|
||||
if arrayContains "$bPath/$domain" "${backupEntryList[@]}"; then
|
||||
sourceList+=('files:dir')
|
||||
fi
|
||||
if arrayContains "$bPath/$domain.tar.gz" "${backupEntryList[@]}"; then
|
||||
sourceList+=('files:tar')
|
||||
fi
|
||||
if arrayContains "$bPath/$domain.tgz" "${backupEntryList[@]}"; then
|
||||
sourceList+=('files:tar')
|
||||
fi
|
||||
if arrayContains "$bPath/$domain.zip" "${backupEntryList[@]}"; then
|
||||
sourceList+=('files:zip')
|
||||
fi
|
||||
if arrayContains "$bPath/$domain.sql" "${backupEntryList[@]}"; then
|
||||
sourceList+=('db:sql')
|
||||
fi
|
||||
if arrayContains "$bPath/$domain.sql.tar.gz" "${backupEntryList[@]}"; then
|
||||
sourceList+=('db:tar')
|
||||
fi
|
||||
if arrayContains "$bPath/$domain.sql.tgz" "${backupEntryList[@]}"; then
|
||||
sourceList+=('db:tar')
|
||||
fi
|
||||
if arrayContains "$bPath/$domain.sql.zip" "${backupEntryList[@]}"; then
|
||||
sourceList+=('db:zip')
|
||||
fi
|
||||
if arrayContains "$bPath/$domain.conf" "${backupEntryList[@]}"; then
|
||||
sourceList+=('conf')
|
||||
fi
|
||||
|
||||
if arrayContains "conf" "${sourceList[@]}" && \
|
||||
( arrayContains "db:sql" "${sourceList[@]}" || arrayContains "db:tar" "${sourceList[@]}" || arrayContains "db:zip" "${sourceList[@]}" ) && \
|
||||
( arrayContains "files:dir" "${sourceList[@]}" || arrayContains "files:tar" "${sourceList[@]}" || arrayContains "files:zip" "${sourceList[@]}" ); then
|
||||
suffix="\033[34m${sourceList[*]}\033[0m"
|
||||
if [[ -z "$indexFull" ]]; then
|
||||
indexFull="$counter"
|
||||
fi
|
||||
else
|
||||
suffix="${sourceList[*]}"
|
||||
fi
|
||||
if [[ "$bType" == "1" ]]; then
|
||||
suffix+=" | \033[33mLongTerm\033[0m"
|
||||
fi
|
||||
|
||||
if [[ -z "$index" ]]; then
|
||||
printf "%2s: %-19s | $suffix\n" "$counter" "$bDate/$bTime"
|
||||
fi
|
||||
((counter++))
|
||||
done
|
||||
|
||||
local indexSelect=
|
||||
case "$index" in
|
||||
list)
|
||||
for marker in "${backupSortList[@]}"; do
|
||||
printf "%s\n" "${marker:2}"
|
||||
done
|
||||
return 0
|
||||
;;
|
||||
last)
|
||||
indexSelect=1
|
||||
;;
|
||||
full)
|
||||
indexSelect="$indexFull"
|
||||
;;
|
||||
auto)
|
||||
if [[ -n "$indexFull" ]]; then
|
||||
indexSelect="$indexFull"
|
||||
else
|
||||
indexSelect=1
|
||||
fi
|
||||
;;
|
||||
[0-9]*)
|
||||
indexSelect="$index"
|
||||
;;
|
||||
*)
|
||||
printWarning "$domain" "Warning! The relevant data will be cleared before restoring."
|
||||
read -p "Specify the backup number: " indexSelect
|
||||
;;
|
||||
esac
|
||||
|
||||
((indexSelect--)) 2>/dev/null
|
||||
if [[ "$indexSelect" -lt 0 || "$indexSelect" -ge "${#backupSortList[@]}" ]]; then
|
||||
printDanger "Unknown index number"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local restorePath="${backupSortList[$indexSelect]:2}"
|
||||
if [[ ! -d "$restorePath" ]]; then
|
||||
printDanger "Path for restore not found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local sourceFiles sourceDatabase sourceConf
|
||||
|
||||
if [[ -d "$restorePath/$domain" ]]; then
|
||||
sourceFiles="$restorePath/$domain"
|
||||
elif [[ -f "$restorePath/$domain.tar.gz" ]]; then
|
||||
sourceFiles="$restorePath/$domain.tar.gz"
|
||||
elif [[ -f "$restorePath/$domain.tgz" ]]; then
|
||||
sourceFiles="$restorePath/$domain.tgz"
|
||||
elif [[ -f "$restorePath/$domain.zip" ]]; then
|
||||
sourceFiles="$restorePath/$domain.zip"
|
||||
fi
|
||||
|
||||
if [[ -f "$restorePath/$domain.sql" ]]; then
|
||||
sourceDatabase="$restorePath/$domain.sql"
|
||||
elif [[ -f "$restorePath/$domain.sql.tar.gz" ]]; then
|
||||
sourceDatabase="$restorePath/$domain.sql.tar.gz"
|
||||
elif [[ -f "$restorePath/$domain.sql.tgz" ]]; then
|
||||
sourceDatabase="$restorePath/$domain.sql.tgz"
|
||||
elif [[ -f "$restorePath/$domain.sql.zip" ]]; then
|
||||
sourceDatabase="$restorePath/$domain.sql.zip"
|
||||
fi
|
||||
|
||||
if [[ -f "$restorePath/$domain.conf" ]]; then
|
||||
sourceConf="$restorePath/$domain.conf"
|
||||
fi
|
||||
|
||||
restoreSite "$domain" "$sourceFiles" "$sourceDatabase" "$sourceConf"
|
||||
|
||||
# Clean Redis
|
||||
redisDomainClean "$domain"
|
||||
|
||||
# Rebuild config site
|
||||
cmdSiteConfigRebuild "$domain"
|
||||
|
||||
# Rebuild config Wordpress
|
||||
wordpressConfigRebuild "$domain"
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -1,729 +0,0 @@
|
||||
# Prints a confirmation and returns 0 if confirmed.
|
||||
# $1 (query): query
|
||||
function cmdRouterConfirm() {
|
||||
local query="$1" confirmation
|
||||
|
||||
printRow
|
||||
read -r -p "${fontYellow}Warning!${fontReset} $query (y/n): " confirmation
|
||||
[[ "$confirmation" =~ ^[Yy]$ ]]
|
||||
}
|
||||
|
||||
function cmdK3s() {
|
||||
local action="${1:-}"
|
||||
shift || true
|
||||
|
||||
case "$action" in
|
||||
install)
|
||||
printTitle " $k3sLabel Install"
|
||||
if cmdRouterConfirm "Are you sure you want to$fontRed INSTALL$fontBlue ALL$fontReset resources to k3s?"; then
|
||||
cmdMariadbInstall
|
||||
cmdRedisInstall
|
||||
cmdOpenlitespeedInstall
|
||||
cmdPostfixInstall
|
||||
cmdWorkerInstall
|
||||
cmdMetricInstall
|
||||
fi
|
||||
;;
|
||||
uninstall)
|
||||
printTitle " $k3sLabel Uninstall"
|
||||
if cmdRouterConfirm "Are you sure you want to$fontRed UNINSTALL$fontBlue ALL$fontReset resources from k3s?"; then
|
||||
cmdK3sResourceClean
|
||||
fi
|
||||
;;
|
||||
info)
|
||||
printTitle " $k3sLabel Info"
|
||||
cmdK3sInfo
|
||||
;;
|
||||
status)
|
||||
printTitle " $k3sLabel Status"
|
||||
cmdK3sNodesStatus
|
||||
;;
|
||||
top)
|
||||
printTitle " $k3sLabel Top pod"
|
||||
cmdK3sTopPod
|
||||
;;
|
||||
res)
|
||||
printTitle " $k3sLabel Resources $@"
|
||||
cmdK3sResourceList "$@"
|
||||
;;
|
||||
*)
|
||||
printTitle " $k3sLabel"
|
||||
printRow
|
||||
printWarning "Unsupported or empty command: $action"
|
||||
cmdHelpK3S
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function cmdMariadb() {
|
||||
local action="${1:-}"
|
||||
shift || true
|
||||
|
||||
case "$action" in
|
||||
install)
|
||||
printTitle " $mariadbLabel Install"
|
||||
if cmdRouterConfirm "Are you sure you want to$fontRed INSTALL$fontBlue MariaDB$fontReset resources to k3s?"; then
|
||||
cmdMariadbInstall
|
||||
fi
|
||||
;;
|
||||
update)
|
||||
printTitle " $mariadbLabel Update"
|
||||
if cmdRouterConfirm "Are you sure you want to$fontRed UPDATE$fontBlue MariaDB$fontReset resources in k3s?"; then
|
||||
cmdMariadbUpdate
|
||||
fi
|
||||
;;
|
||||
uninstall)
|
||||
printTitle " $mariadbLabel Uninstall"
|
||||
if cmdRouterConfirm "Are you sure you want to$fontRed UNINSTALL$fontBlue MariaDB$fontReset resources from k3s?"; then
|
||||
cmdMariadbUninstall
|
||||
fi
|
||||
;;
|
||||
info)
|
||||
printTitle " $mariadbLabel Info"
|
||||
cmdMariadbInfo
|
||||
;;
|
||||
status)
|
||||
printTitle " $mariadbLabel Status"
|
||||
cmdMariadbStatus
|
||||
;;
|
||||
replica)
|
||||
printTitle " $mariadbLabel Replica rebuild"
|
||||
if cmdRouterConfirm "Are you sure you want to$fontRed REBUILD$fontBlue MariaDB replica$fontReset?"; then
|
||||
cmdMariadbReplicaRebuild
|
||||
fi
|
||||
;;
|
||||
database)
|
||||
printTitle " $mariadbLabel Database list"
|
||||
cmdMariadbDatabase
|
||||
;;
|
||||
user)
|
||||
printTitle " $mariadbLabel User list"
|
||||
cmdMariadbUser
|
||||
;;
|
||||
dump)
|
||||
printTitle " $mariadbLabel Dump Master $@"
|
||||
cmdMariadbMasterDump "$@"
|
||||
;;
|
||||
dump_slave)
|
||||
printTitle " $mariadbLabel Dump Slave $@"
|
||||
cmdMariadbSlaveDump "$@"
|
||||
;;
|
||||
*)
|
||||
printTitle " $mariadbLabel"
|
||||
printRow
|
||||
printWarning "Unsupported or empty command: $action"
|
||||
cmdHelpMariaDB
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function cmdRedis() {
|
||||
local action="${1:-}"
|
||||
shift || true
|
||||
|
||||
case "$action" in
|
||||
install)
|
||||
printTitle " $redisLabel Install"
|
||||
if cmdRouterConfirm "Are you sure you want to$fontRed INSTALL$fontBlue Redis$fontReset resources to k3s?"; then
|
||||
cmdRedisInstall
|
||||
fi
|
||||
;;
|
||||
update)
|
||||
printTitle " $redisLabel Update"
|
||||
if cmdRouterConfirm "Are you sure you want to$fontRed UPDATE$fontBlue Redis$fontReset resources in k3s?"; then
|
||||
cmdRedisUpdate
|
||||
fi
|
||||
;;
|
||||
uninstall)
|
||||
printTitle " $redisLabel Uninstall"
|
||||
if cmdRouterConfirm "Are you sure you want to$fontRed UNINSTALL$fontBlue Redis$fontReset resources from k3s?"; then
|
||||
cmdRedisUninstall
|
||||
fi
|
||||
;;
|
||||
info)
|
||||
printTitle " $redisLabel Info"
|
||||
cmdRedisInfo
|
||||
;;
|
||||
status)
|
||||
printTitle " $redisLabel Status"
|
||||
cmdRedisStatus
|
||||
;;
|
||||
user)
|
||||
printTitle " $redisLabel User list"
|
||||
cmdRedisUser
|
||||
;;
|
||||
flush)
|
||||
printTitle " $redisLabel Flush current DB"
|
||||
cmdRedisDatabaseFlush
|
||||
;;
|
||||
pass)
|
||||
printTitle " $redisLabel Update default (root) pass"
|
||||
cmdRedisRootPassUpdate
|
||||
;;
|
||||
*)
|
||||
printTitle " $redisLabel"
|
||||
printRow
|
||||
printWarning "Unsupported or empty command: $action"
|
||||
cmdHelpRedis
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function cmdOpenlitespeed() {
|
||||
local action="${1:-}"
|
||||
shift || true
|
||||
|
||||
case "$action" in
|
||||
install)
|
||||
printTitle " $openlitespeedLabel Install"
|
||||
if cmdRouterConfirm "Are you sure you want to$fontRed INSTALL$fontBlue OpenLiteSpeed$fontReset resources to k3s?"; then
|
||||
cmdOpenlitespeedInstall
|
||||
fi
|
||||
;;
|
||||
update)
|
||||
printTitle " $openlitespeedLabel Update"
|
||||
if cmdRouterConfirm "Are you sure you want to$fontRed UPDATE$fontBlue OpenLiteSpeed$fontReset resources in k3s?"; then
|
||||
cmdOpenlitespeedUpdate
|
||||
fi
|
||||
;;
|
||||
uninstall)
|
||||
printTitle " $openlitespeedLabel Uninstall"
|
||||
if cmdRouterConfirm "Are you sure you want to$fontRed UNINSTALL$fontBlue OpenLiteSpeed$fontReset resources from k3s?"; then
|
||||
cmdOpenlitespeedUninstall
|
||||
fi
|
||||
;;
|
||||
info)
|
||||
printTitle " $openlitespeedLabel Info"
|
||||
cmdOpenlitespeedInfo
|
||||
;;
|
||||
list)
|
||||
printTitle " $openlitespeedLabel List (all|up|down)"
|
||||
cmdOpenlitespeedSiteList "$@"
|
||||
;;
|
||||
up)
|
||||
printTitle " $openlitespeedLabel Up $@"
|
||||
cmdOpenlitespeedVhostUp "$@"
|
||||
;;
|
||||
down)
|
||||
printTitle " $openlitespeedLabel Down $@"
|
||||
cmdOpenlitespeedVhostDown "$@"
|
||||
;;
|
||||
alias)
|
||||
printTitle " $openlitespeedLabel Alias $1"
|
||||
cmdOpenlitespeedVhostAliasSet "$@"
|
||||
;;
|
||||
restart)
|
||||
printTitle " $openlitespeedLabel Restart"
|
||||
cmdOpenlitespeedRestart
|
||||
;;
|
||||
php_kill)
|
||||
printTitle " $openlitespeedLabel Kill PHP $@"
|
||||
cmdOpenlitespeedPhpKill "$@"
|
||||
;;
|
||||
white_list)
|
||||
printTitle " $openlitespeedLabel White list update"
|
||||
cmdOpenlitespeedAdminWhiteList
|
||||
;;
|
||||
allow_list)
|
||||
printTitle " $openlitespeedLabel Allow list update"
|
||||
cmdOpenlitespeedAdminAllowList
|
||||
;;
|
||||
alias_list)
|
||||
printTitle " $openlitespeedLabel Alias list $1"
|
||||
cmdOpenlitespeedAliasList "$@"
|
||||
;;
|
||||
rebuild)
|
||||
printTitle " $openlitespeedLabel Rebuild $1"
|
||||
cmdOpenlitespeedVhostRebuild "$@"
|
||||
;;
|
||||
config)
|
||||
printTitle " $openlitespeedLabel Config check"
|
||||
cmdOpenlitespeedConfigCheck
|
||||
;;
|
||||
*)
|
||||
printTitle " $openlitespeedLabel"
|
||||
printRow
|
||||
printWarning "Unsupported or empty command: $action"
|
||||
cmdHelpOpenLiteSpeed
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function cmdPostfix() {
|
||||
local action="${1:-}"
|
||||
shift || true
|
||||
|
||||
case "$action" in
|
||||
install)
|
||||
printTitle " $postfixLabel Install"
|
||||
if cmdRouterConfirm "Are you sure you want to$fontRed INSTALL$fontBlue Postfix$fontReset resources to k3s?"; then
|
||||
cmdPostfixInstall
|
||||
fi
|
||||
;;
|
||||
update)
|
||||
printTitle " $postfixLabel Update"
|
||||
if cmdRouterConfirm "Are you sure you want to$fontRed UPDATE$fontBlue Postfix$fontReset resources in k3s?"; then
|
||||
cmdPostfixUpdate
|
||||
fi
|
||||
;;
|
||||
uninstall)
|
||||
printTitle " $postfixLabel Uninstall"
|
||||
if cmdRouterConfirm "Are you sure you want to$fontRed UNINSTALL$fontBlue Postfix$fontReset resources from k3s?"; then
|
||||
cmdPostfixUninstall
|
||||
fi
|
||||
;;
|
||||
info)
|
||||
printTitle " $postfixLabel Info"
|
||||
cmdPostfixInfo
|
||||
;;
|
||||
status)
|
||||
printTitle " $postfixLabel Status"
|
||||
cmdPostfixMtaDnsCheck
|
||||
;;
|
||||
user)
|
||||
printTitle " $postfixLabel User list"
|
||||
cmdPostfixUser
|
||||
;;
|
||||
dkim)
|
||||
printInfo " $postfixLabel DKIM record for DNS (autocreate)"
|
||||
postfixDkimGet "$@"
|
||||
;;
|
||||
*)
|
||||
printTitle " $postfixLabel"
|
||||
printRow
|
||||
printWarning "Unsupported or empty command: $action"
|
||||
cmdHelpPostfix
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function cmdWorker() {
|
||||
local action="${1:-}"
|
||||
shift || true
|
||||
|
||||
case "$action" in
|
||||
install)
|
||||
printTitle " $workerLabel Install"
|
||||
if cmdRouterConfirm "Are you sure you want to$fontRed INSTALL$fontBlue Worker$fontReset resources to k3s?"; then
|
||||
cmdWorkerInstall
|
||||
fi
|
||||
;;
|
||||
update)
|
||||
printTitle " $workerLabel Update"
|
||||
if cmdRouterConfirm "Are you sure you want to$fontRed UPDATE$fontBlue Worker$fontReset resources in k3s?"; then
|
||||
cmdWorkerUpdate
|
||||
fi
|
||||
;;
|
||||
uninstall)
|
||||
printTitle " $workerLabel Uninstall"
|
||||
if cmdRouterConfirm "Are you sure you want to$fontRed UNINSTALL$fontBlue Worker$fontReset resources from k3s?"; then
|
||||
cmdWorkerUninstall
|
||||
fi
|
||||
;;
|
||||
task)
|
||||
printInfo " $workerLabel Run $1"
|
||||
reportFile="$logPath/task/${appDate}_$appTime.log"
|
||||
printText "Start: $appDate $appTime\n"
|
||||
cmdWorkerTaskHandle "$@"
|
||||
printText "\nFinish: $(date +%Y-%m-%d) $(date +%H-%M-%S) | Time: $(( $(date +%s) - scriptStart ))s"
|
||||
reportFile=""
|
||||
;;
|
||||
list)
|
||||
printTitle " $workerLabel Task list"
|
||||
cmdWorkerTaskList
|
||||
;;
|
||||
down)
|
||||
printInfo " $workerLabel Put on pause..."
|
||||
workerAgentStop
|
||||
;;
|
||||
up)
|
||||
printInfo " $workerLabel Resuming from pause..."
|
||||
workerAgentStart
|
||||
;;
|
||||
*)
|
||||
printTitle " $workerLabel"
|
||||
printRow
|
||||
printWarning "Unsupported or empty command: $action"
|
||||
cmdHelpWorker
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function cmdMetric() {
|
||||
local action="${1:-}"
|
||||
shift || true
|
||||
|
||||
case "$action" in
|
||||
install)
|
||||
printTitle " $metricLabel Install"
|
||||
if cmdRouterConfirm "Are you sure you want to$fontRed INSTALL$fontBlue Metric$fontReset resources to k3s?"; then
|
||||
cmdMetricInstall
|
||||
fi
|
||||
;;
|
||||
update)
|
||||
printTitle " $metricLabel Update"
|
||||
if cmdRouterConfirm "Are you sure you want to$fontRed UPDATE$fontBlue Metric$fontReset resources in k3s?"; then
|
||||
cmdMetricUpdate
|
||||
fi
|
||||
;;
|
||||
uninstall)
|
||||
printTitle " $metricLabel Uninstall"
|
||||
if cmdRouterConfirm "Are you sure you want to$fontRed UNINSTALL$fontBlue Metric$fontReset resources from k3s?"; then
|
||||
cmdMetricUninstall
|
||||
fi
|
||||
;;
|
||||
restart)
|
||||
printInfo " $metricLabel Restart"
|
||||
metricRestart
|
||||
;;
|
||||
*)
|
||||
printTitle " $metricLabel"
|
||||
printRow
|
||||
printWarning "Unsupported or empty command: $action"
|
||||
cmdHelpMetric
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function cmdSite() {
|
||||
local action="${1:-}"
|
||||
shift || true
|
||||
|
||||
case "$action" in
|
||||
add)
|
||||
printTitle " $siteLabel Add $*"
|
||||
cmdSiteAdd "$@"
|
||||
;;
|
||||
add_wp|wp)
|
||||
printTitle " $siteLabel Add Wordpress $*"
|
||||
cmdSiteAddWordpress "$@"
|
||||
;;
|
||||
remove)
|
||||
printTitle " $siteLabel Remove $*"
|
||||
cmdSiteRemove "$@"
|
||||
;;
|
||||
copy)
|
||||
printTitle " $siteLabel Copy $*"
|
||||
cmdSiteCopy "$@"
|
||||
;;
|
||||
rename)
|
||||
printTitle " $siteLabel Rename $*"
|
||||
cmdSiteRename "$@"
|
||||
;;
|
||||
rebuild)
|
||||
printTitle " $siteLabel Rebuild config $*"
|
||||
cmdSiteConfigRebuild "$@"
|
||||
;;
|
||||
rebuild_wp)
|
||||
printTitle " $siteLabel Wordpress config rebuild $*"
|
||||
cmdSiteWordpressRebuild "$@"
|
||||
;;
|
||||
reset_pass)
|
||||
printTitle " $siteLabel Reset ALL passwords for vhost $*"
|
||||
cmdSitePasswordReset "$@"
|
||||
;;
|
||||
reset_auth)
|
||||
printTitle " $siteLabel Reset ALL auth settings for vhost $*"
|
||||
cmdSiteAuthReset "$@"
|
||||
;;
|
||||
dump)
|
||||
printTitle " $siteLabel Database dump $*"
|
||||
cmdSiteDatabaseDump "$@"
|
||||
;;
|
||||
info)
|
||||
printTitle " $siteLabel Info $*"
|
||||
cmdSiteInfo "$@"
|
||||
;;
|
||||
status)
|
||||
printTitle " $siteLabel Status $*"
|
||||
cmdSiteStatus "$@"
|
||||
;;
|
||||
*)
|
||||
printTitle " $siteLabel"
|
||||
printRow
|
||||
printWarning "Unsupported or empty command: $action"
|
||||
cmdHelpSite
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function cmdWordpress() {
|
||||
local action="${1:-}"
|
||||
shift || true
|
||||
|
||||
case "$action" in
|
||||
user)
|
||||
printTitle " $wordpressLabel User list"
|
||||
cmdWordpressUserList "$@"
|
||||
;;
|
||||
pass)
|
||||
printTitle " $wordpressLabel User password set"
|
||||
cmdWordpressPasswordSet "$@"
|
||||
;;
|
||||
salt)
|
||||
printTitle " $wordpressLabel Shuffle salts $*"
|
||||
cmdWordpressSalt "$@"
|
||||
;;
|
||||
check)
|
||||
printTitle " $wordpressLabel Check core and plugins $*"
|
||||
cmdWordpressCheck "$@"
|
||||
;;
|
||||
exec)
|
||||
printTitle " $wordpressLabel Command exec $*"
|
||||
cmdWordpressExec "$@"
|
||||
;;
|
||||
*)
|
||||
printTitle " $wordpressLabel"
|
||||
printRow
|
||||
printWarning "Unsupported or empty command: $action"
|
||||
cmdHelpWP
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function cmdSftp() {
|
||||
local action="${1:-}"
|
||||
shift || true
|
||||
|
||||
case "$action" in
|
||||
user)
|
||||
printTitle " $systemLabel SFTP User list $*"
|
||||
cmdSftpUserList "$@"
|
||||
;;
|
||||
enable)
|
||||
printTitle " $systemLabel SFTP Access enable $*"
|
||||
cmdSftpAccessEnable "$@"
|
||||
;;
|
||||
disable)
|
||||
printTitle " $systemLabel SFTP Access disable $*"
|
||||
cmdSftpAccessDisable "$@"
|
||||
;;
|
||||
reset_pass)
|
||||
printTitle " $systemLabel SFTP Reset pass"
|
||||
cmdSftpPasswordSet "$@"
|
||||
;;
|
||||
support)
|
||||
printTitle " $systemLabel SFTP Adding support"
|
||||
if cmdRouterConfirm "Changes will be made to the$fontRed SSH configuration$fontReset. Make sure there is a$fontBlue backup way to connect$fontReset to the server. Continue?"; then
|
||||
cmdSftpAddingSupport
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
printTitle " $systemLabel SFTP"
|
||||
printRow
|
||||
printWarning "Unsupported or empty command: $action"
|
||||
cmdHelpSftp
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function cmdCron() {
|
||||
local action="${1:-}"
|
||||
shift || true
|
||||
|
||||
case "$action" in
|
||||
add)
|
||||
printTitle " $systemLabel Cron job add"
|
||||
cmdCronAdd
|
||||
;;
|
||||
remove)
|
||||
printTitle " $systemLabel Cron job remove"
|
||||
cmdCronRemove
|
||||
;;
|
||||
list)
|
||||
printTitle " $systemLabel Cron job list"
|
||||
cmdCronList
|
||||
;;
|
||||
*)
|
||||
printTitle " $systemLabel Cron"
|
||||
printRow
|
||||
printWarning "Unsupported or empty command: $action"
|
||||
cmdHelpCron
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function cmdBackup() {
|
||||
local action="${1:-}"
|
||||
shift || true
|
||||
|
||||
case "$action" in
|
||||
run)
|
||||
printTitle " $systemLabel Backup of target"
|
||||
cmdBackupRun "$@"
|
||||
;;
|
||||
list)
|
||||
printTitle " $systemLabel List of backups on local storage"
|
||||
cmdBackupList
|
||||
;;
|
||||
size)
|
||||
printTitle " $systemLabel List of Backup Sizes"
|
||||
cmdBackupSize
|
||||
;;
|
||||
# remove)
|
||||
# printTitle " $backupLabel Remove of backups on local storage"
|
||||
# cmdStorageBackupRemove
|
||||
# ;;
|
||||
*)
|
||||
printTitle " $systemLabel Backup"
|
||||
printRow
|
||||
printWarning "Unsupported or empty command: $action"
|
||||
cmdHelpBackup
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function cmdRestore() {
|
||||
local action="${1:-}"
|
||||
shift || true
|
||||
|
||||
case "$action" in
|
||||
run)
|
||||
printTitle " $restoreLabel Backup of target"
|
||||
restoreRun "$@"
|
||||
;;
|
||||
*)
|
||||
printTitle " $restoreLabel"
|
||||
printRow
|
||||
printWarning "Unsupported or empty command: $action"
|
||||
cmdHelpRestore
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function cmdStorage() {
|
||||
local action="${1:-}"
|
||||
shift || true
|
||||
|
||||
case "$action" in
|
||||
list)
|
||||
printTitle " $storageLabel List of backups on external storage"
|
||||
cmdStorageBackupList
|
||||
;;
|
||||
compare)
|
||||
printTitle " $systemLabel Comparison table"
|
||||
cmdStorageBackupCompare
|
||||
;;
|
||||
size)
|
||||
printTitle " $systemLabel List of Backup Sizes on external storage"
|
||||
cmdStorageBackupSize
|
||||
;;
|
||||
sync)
|
||||
printTitle " $systemLabel Synchronization with external storage"
|
||||
cmdStorageBackupSync "$@"
|
||||
;;
|
||||
remove)
|
||||
printTitle " $systemLabel Remove of backups on external storage"
|
||||
cmdStorageBackupRemove
|
||||
;;
|
||||
*)
|
||||
printTitle " $systemLabel Storage"
|
||||
printRow
|
||||
printWarning "Unsupported or empty command: $action"
|
||||
cmdHelpStorage
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Dispatches a named script sub-command and redirects output to a timestamped log file.
|
||||
# $1 (option): script name (backup, mariadb-dump, worker-observer, metric-kube, metric-sites, diag-report-ai-short, diag-report-ai-full).
|
||||
function cmdScript() {
|
||||
cmdScriptRun "$@"
|
||||
}
|
||||
|
||||
function cmdInstall() {
|
||||
printTitle " $ks3Label Full install"
|
||||
if cmdRouterConfirm "Are you sure you want to$fontRed INSTALL$fontReset $fontBlue FULL infrastrutute$fontReset?"; then
|
||||
cmdInstallFull
|
||||
fi
|
||||
}
|
||||
|
||||
function cmdTest() {
|
||||
local action="${1:-}"
|
||||
shift || true
|
||||
|
||||
case "$action" in
|
||||
mariadb)
|
||||
printInfo " $testLabel MariaDB replica"
|
||||
testMariadbReplica
|
||||
;;
|
||||
redis)
|
||||
printInfo " $testLabel Redis cluster"
|
||||
testRedisCluster
|
||||
;;
|
||||
site)
|
||||
printInfo " $testLabel Site"
|
||||
testSite
|
||||
;;
|
||||
wordpress)
|
||||
printInfo " $testLabel Wordpress"
|
||||
testSiteWordpress
|
||||
;;
|
||||
*)
|
||||
printTitle " $testLabel SFTP"
|
||||
printRow
|
||||
printWarning "Unsupported or empty command: $action"
|
||||
cmdHelpTest
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function cmdMalware() {
|
||||
local action="${1:-}"
|
||||
shift || true
|
||||
|
||||
case "$action" in
|
||||
check)
|
||||
printTitle " Malware check"
|
||||
cmdScriptMalwareCheck
|
||||
;;
|
||||
remove)
|
||||
printTitle " Malware remove"
|
||||
cmdScriptMalwareRemove
|
||||
;;
|
||||
*)
|
||||
printTitle " $testLabel Malware"
|
||||
printRow
|
||||
printWarning "Unsupported or empty command: $action"
|
||||
cmdHelpMalware
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function cmdRouter() {
|
||||
local action="${1:-}"
|
||||
shift || true
|
||||
|
||||
printHeader
|
||||
|
||||
case "$action" in
|
||||
-k|--k3s) cmdK3s "$@" ;;
|
||||
-m|--mariadb) cmdMariadb "$@" ;;
|
||||
-r|--redis) cmdRedis "$@" ;;
|
||||
-o|--ols) cmdOpenlitespeed "$@" ;;
|
||||
-p|--postfix) cmdPostfix "$@" ;;
|
||||
-w|--worker) cmdWorker "$@" ;;
|
||||
-s|--site) cmdSite "$@" ;;
|
||||
--metric) cmdMetric "$@" ;;
|
||||
--wp) cmdWordpress "$@" ;;
|
||||
--sftp) cmdSftp "$@" ;;
|
||||
--cron) cmdCron "$@" ;;
|
||||
--shell) cmdShell "$@" ;;
|
||||
--log) cmdLog "$@" ;;
|
||||
--describe) cmdDescribe "$@" ;;
|
||||
--delete) cmdDelete "$@" ;;
|
||||
--backup) cmdBackup "$@" ;;
|
||||
--restore) cmdRestore "$@" ;;
|
||||
--storage) cmdStorage "$@" ;;
|
||||
--script) cmdScript "$@" ;;
|
||||
--install) cmdInstall "$@" ;;
|
||||
--test) cmdTest "$@" ;;
|
||||
--malware) cmdMalware "$@" ;;
|
||||
--help) cmdHelp "$@" ;;
|
||||
dev) appDev "$@" ;;
|
||||
esac
|
||||
|
||||
local status=$?
|
||||
printFooter
|
||||
return "$status"
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
scriptLabel="[Script]"
|
||||
|
||||
# Runs the site backup dispatch with script logging.
|
||||
function cmdScriptBackup() {
|
||||
printDotText "Script" "Backup sites"
|
||||
printStart
|
||||
cmdBackupDispatch
|
||||
printFinish
|
||||
}
|
||||
|
||||
# Runs the MariaDB master full-dump script with script logging.
|
||||
function cmdScriptMariadbDump() {
|
||||
printDotText "Script" "MariaDB database dump"
|
||||
printStart
|
||||
cmdMariadbMasterDump
|
||||
printFinish
|
||||
}
|
||||
|
||||
# Runs the worker task observer with script logging.
|
||||
function cmdScriptWorkerObserver() {
|
||||
printDotText "Script" "Worker observer"
|
||||
printStart
|
||||
cmdWorkerObserver
|
||||
printFinish
|
||||
}
|
||||
|
||||
# Collects and pushes kube and lsphp metrics with script logging.
|
||||
function cmdScriptMetricKube() {
|
||||
printDotText "Script" "Metric KUBE"
|
||||
printStart
|
||||
metricKube
|
||||
metricLsphp
|
||||
printFinish
|
||||
}
|
||||
|
||||
# Collects and pushes per-site metrics with script logging.
|
||||
function cmdScriptMetricSites() {
|
||||
printDotText "Script" "Metric sites"
|
||||
printStart
|
||||
metricSites
|
||||
printFinish
|
||||
}
|
||||
|
||||
# Runs the AI diagnostic report with script logging.
|
||||
# [$1] (mode): report mode: short (default) or full.
|
||||
function cmdScriptDiagReportAi() {
|
||||
local mode
|
||||
mode=${1:-short}
|
||||
|
||||
printDotText "Script" "Diag report AI $mode"
|
||||
printStart
|
||||
diagRun "$mode"
|
||||
printFinish
|
||||
}
|
||||
|
||||
function cmdScriptMalwareCheck() {
|
||||
printDotText "Script" "Malware check"
|
||||
printStart
|
||||
|
||||
olsVhostsPath="$vhostsPath"
|
||||
|
||||
cmdMalwareFilesDelete list
|
||||
cmdMalwareUserDelete list
|
||||
cmdMalwareOptionsDelete list
|
||||
cmdMalwareCronDelete list
|
||||
|
||||
printFinish
|
||||
}
|
||||
|
||||
function cmdScriptMalwareRemove() {
|
||||
printDotText "Script" "Malware remove"
|
||||
printStart
|
||||
|
||||
olsVhostsPath="$vhostsPath"
|
||||
|
||||
cmdMalwareFilesDelete remove
|
||||
cmdMalwareUserDelete remove
|
||||
cmdMalwareOptionsDelete remove
|
||||
cmdMalwareCronDelete remove
|
||||
|
||||
printFinish
|
||||
}
|
||||
|
||||
function cmdScriptRun() {
|
||||
local option="$1"
|
||||
|
||||
printText "Output: $logPath/$option/${appDate}_$appTime.log"
|
||||
printRow
|
||||
|
||||
printFile="$logPath/$option/${appDate}_$appTime.log"
|
||||
local logFileOld="$logFile"
|
||||
logFile=""
|
||||
|
||||
case "$option" in
|
||||
backup)
|
||||
cmdScriptBackup
|
||||
;;
|
||||
mariadb-dump)
|
||||
cmdScriptMariadbDump
|
||||
;;
|
||||
worker-observer)
|
||||
cmdScriptWorkerObserver
|
||||
;;
|
||||
metric-kube)
|
||||
cmdScriptMetricKube
|
||||
;;
|
||||
metric-sites)
|
||||
cmdScriptMetricSites
|
||||
;;
|
||||
diag-report-ai-short)
|
||||
cmdScriptDiagReportAi "short"
|
||||
;;
|
||||
diag-report-ai-full)
|
||||
cmdScriptDiagReportAi "full"
|
||||
;;
|
||||
malware-remove)
|
||||
cmdScriptMalwareRemove
|
||||
;;
|
||||
unigma)
|
||||
cmdUnigma
|
||||
;;
|
||||
*)
|
||||
printFile=""
|
||||
logFile="$logFileOld"
|
||||
|
||||
printTitle " $scriptLabel"
|
||||
printRow
|
||||
printWarning "Unsupported or empty command: $action"
|
||||
cmdHelpScript
|
||||
;;
|
||||
esac
|
||||
|
||||
printFile=""
|
||||
logFile="$logFileOld"
|
||||
}
|
||||
@@ -1,837 +0,0 @@
|
||||
siteLabel="[Site]"
|
||||
siteWordpressLabel="[Wordpress]"
|
||||
|
||||
# Creates a new site: validates domain/source/DB, creates OLS vhost, copies files,
|
||||
# sets up MariaDB/Redis/Postfix, rolls back via cmdSiteRemove on failure.
|
||||
# $1 (domain): site domain name.
|
||||
# $2 (sourceFiles): source directory or archive with site files.
|
||||
# [$3] (sourceDatabase): optional SQL dump or archive with SQL dump.
|
||||
function cmdSiteAdd() {
|
||||
local domain sourceFiles
|
||||
domain=$(domainPrepare "$1")
|
||||
shift || true
|
||||
|
||||
sourceFiles="${1:-$appAssetsPath/vhost/default}"
|
||||
shift || true
|
||||
|
||||
printSection "Add site"
|
||||
printDotText "domain" "$domain"
|
||||
|
||||
if ! runError error domainCheck "$domain"; then
|
||||
printDotText "status" "$labelFail"
|
||||
printDanger "$error"
|
||||
elif ! run output error siteAdd "$domain" "$sourceFiles" "$@"; then
|
||||
printDotText "status" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "status" "$labelDone"
|
||||
|
||||
cmdOpenlitespeedRestart
|
||||
|
||||
fi
|
||||
}
|
||||
|
||||
# Removes a site and all associated MariaDB/Redis/Postfix/OLS/host resources.
|
||||
# $1 (domain): site domain name.
|
||||
function cmdSiteRemove() {
|
||||
local domain mode
|
||||
domain=$(domainPrepare "$1")
|
||||
shift || true
|
||||
|
||||
mode="$1"
|
||||
shift || true
|
||||
|
||||
if [[ ! "$mode" == "-f" && ! "$mode" == "--force" ]]; then
|
||||
if ! cmdRouterConfirm "Are you sure you want to$fontRed DELETE$fontReset the site $fontBlue$domain$fontReset?"; then
|
||||
printRow
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
printSection "Remove site"
|
||||
printDotText "domain" "$domain"
|
||||
|
||||
if ! runError error domainCheck "$domain"; then
|
||||
printDotText "status" "$labelFail"
|
||||
printDanger "$error"
|
||||
elif ! run output error siteRemove "$domain"; then
|
||||
printDotText "status" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "status" "$labelDone"
|
||||
|
||||
cmdOpenlitespeedRestart
|
||||
fi
|
||||
}
|
||||
|
||||
# Copies a site: exports source DB, creates target site, rebuilds WP config.
|
||||
# $1 (domain): source site domain name.
|
||||
# $2 (domainNew): target site domain name.
|
||||
function cmdSiteCopy() {
|
||||
local domain domainNew
|
||||
domain=$(domainPrepare "$1")
|
||||
domainNew=$(domainPrepare "$2")
|
||||
|
||||
printSection "Copy site"
|
||||
printDotText "source" "$domain"
|
||||
printDotText "target" "$domainNew"
|
||||
|
||||
if ! runError error domainCheck "$domain"; then
|
||||
printDotText "status" "$labelFail"
|
||||
printDanger "$error"
|
||||
elif ! runError error domainCheck "$domainNew"; then
|
||||
printDotText "status" "$labelFail"
|
||||
printDanger "$error"
|
||||
elif ! run output error siteCopy "$domain" "$domainNew"; then
|
||||
printDotText "status" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "status" "$labelDone"
|
||||
|
||||
cmdOpenlitespeedRestart
|
||||
|
||||
if ! runError error wordpressDomainUpdate "$domainNew"; then
|
||||
printDotText "update" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "update" "$labelDone"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Renames a site by copying it to the new domain and removing the old one.
|
||||
# $1 (domain): current site domain name.
|
||||
# $2 (domainNew): new site domain name.
|
||||
function cmdSiteRename() {
|
||||
local domain domainNew
|
||||
domain=$(domainPrepare "$1")
|
||||
domainNew=$(domainPrepare "$2")
|
||||
|
||||
printSection "Rename site"
|
||||
printDotText "source" "$domain"
|
||||
printDotText "target" "$domainNew"
|
||||
|
||||
if ! runError error domainCheck "$domain"; then
|
||||
printDotText "status" "$labelFail"
|
||||
printDanger "$error"
|
||||
elif ! runError error domainCheck "$domainNew"; then
|
||||
printDotText "status" "$labelFail"
|
||||
printDanger "$error"
|
||||
elif ! run output error siteRename "$domain" "$domainNew"; then
|
||||
printDotText "status" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "status" "$labelDone"
|
||||
|
||||
cmdOpenlitespeedRestart
|
||||
|
||||
if ! runError error wordpressDomainUpdate "$domainNew"; then
|
||||
printDotText "update" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "update" "$labelDone"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Creates a WordPress site from the bundled template, then rebuilds WP config.
|
||||
# $1 (domain): site domain name.
|
||||
# [$2] (sourceFiles): optional source directory or archive.
|
||||
# [$@] (...): optional remaining arguments passed to cmdSiteAdd (e.g. database dump).
|
||||
function cmdSiteAddWordpress() {
|
||||
local domain sourceFiles
|
||||
domain=$(domainPrepare "$1")
|
||||
shift || true
|
||||
|
||||
sourceFiles="${1:-$appAssetsPath/vhost/wordpress}"
|
||||
if [[ ! -e "$sourceFiles" ]]; then
|
||||
sourceFiles="$appAssetsPath/vhost/wordpress.tar.gz"
|
||||
fi
|
||||
shift || true
|
||||
|
||||
printSection "Add site (Wordpress)"
|
||||
printDotText "domain" "$domain"
|
||||
|
||||
if ! runError error domainCheck "$domain"; then
|
||||
printDotText "status" "$labelFail"
|
||||
printDanger "$error"
|
||||
elif ! run output error siteAdd "$domain" "$sourceFiles" "$@"; then
|
||||
printDotText "status" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "status" "$labelDone"
|
||||
|
||||
cmdOpenlitespeedRestart
|
||||
|
||||
if ! run output error wordpressConfigRebuild "$domain"; then
|
||||
printDanger "$error"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Rebuilds OLS vhost/config, MariaDB, Redis, Postfix, and WordPress salt for a site.
|
||||
# $1 (domain): site domain name.
|
||||
function cmdSiteConfigRebuild() {
|
||||
local domain
|
||||
domain=$(domainPrepare "$1")
|
||||
domainCheck "$domain" || return 1
|
||||
|
||||
printRow
|
||||
|
||||
if ! runError error openlitespeedVhostRebuild "$domain"; then
|
||||
printDotText "OLS vhost" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "OLS vhost" "$labelDone"
|
||||
fi
|
||||
|
||||
if ! runError error openlitespeedConfigVhostSet "$domain"; then
|
||||
printDotText "OLS config" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "OLS config" "$labelDone"
|
||||
fi
|
||||
|
||||
if ! runError error mariadbConfigRebuild "$domain"; then
|
||||
printDotText "MariaDB" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "MariaDB" "$labelDone"
|
||||
fi
|
||||
|
||||
if ! runError error redisConfigRebuild "$domain"; then
|
||||
printDotText "Redis" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "Redis" "$labelDone"
|
||||
fi
|
||||
|
||||
if ! runError error postfixConfigRebuild "$domain"; then
|
||||
printDotText "Postfix" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "Postfix" "$labelDone"
|
||||
fi
|
||||
|
||||
if ! runError error wordpressExec "$domain" config shuffle-salts; then
|
||||
printDotText "Wordpress salt" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "Wordpress salt" "$labelDone"
|
||||
fi
|
||||
}
|
||||
|
||||
# Rebuilds the WordPress wp-config.php for a site.
|
||||
# $1 (domain): site domain name.
|
||||
function cmdSiteWordpressRebuild() {
|
||||
local error
|
||||
|
||||
printRow
|
||||
|
||||
if ! runError error wordpressConfigRebuild "$@"; then
|
||||
printDotText "Wordpress config rebuild" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "Wordpress config rebuild" "$labelDone"
|
||||
fi
|
||||
}
|
||||
|
||||
# Resets databasePass/redisPass/postfixPass and rebuilds configs for one site or all sites.
|
||||
# Skips sites without wp-config.php in "all" mode.
|
||||
# $1 (target): site domain name or "all".
|
||||
function cmdSitePasswordReset() {
|
||||
local target="$1"
|
||||
local vhostList error
|
||||
|
||||
printRow
|
||||
|
||||
if [[ -z "$target" ]]; then
|
||||
printDanger "Target not specified";
|
||||
elif ! run vhostList error openlitespeedConfigVhostList; then
|
||||
printDanger "Cannot get vhost list: $error";
|
||||
elif [[ "$target" == "all" ]]; then
|
||||
local domain
|
||||
for domain in $vhostList; do
|
||||
if ! runError error sitePasswordReset "$domain"; then
|
||||
printDotText "$domain" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "$domain" "$labelDone"
|
||||
fi
|
||||
done
|
||||
elif ! listContains "$target" "$vhostList"; then
|
||||
printDanger "Unknown domain: $target";
|
||||
elif ! runError error sitePasswordReset "$target"; then
|
||||
printDotText "$target" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "$target" "$labelDone"
|
||||
fi
|
||||
}
|
||||
|
||||
# Resets all service credentials (passwords + usernames) and rebuilds configs for one site or all sites.
|
||||
# Skips sites without wp-config.php in "all" mode.
|
||||
# $1 (target): site domain name or "all".
|
||||
function cmdSiteAuthReset() {
|
||||
local target="$1"
|
||||
local vhostList error
|
||||
|
||||
printRow
|
||||
|
||||
if [[ -z "$target" ]]; then
|
||||
printDanger "Target not specified";
|
||||
elif ! run vhostList error openlitespeedConfigVhostList; then
|
||||
printDanger "Cannot get vhost list: $error";
|
||||
elif [[ "$target" == "all" ]]; then
|
||||
local domain
|
||||
for domain in $vhostList; do
|
||||
if ! runError error siteAuthReset "$domain"; then
|
||||
printDotText "$domain" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "$domain" "$labelDone"
|
||||
fi
|
||||
done
|
||||
elif ! listContains "$target" "$vhostList"; then
|
||||
printDanger "Unknown domain: $target";
|
||||
elif ! runError error siteAuthReset "$target"; then
|
||||
printDotText "$target" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "$target" "$labelDone"
|
||||
fi
|
||||
}
|
||||
|
||||
# Exports a site's database to a file.
|
||||
# $1 (domain): site domain name.
|
||||
# [$2] (file): target dump file (auto-generated if omitted).
|
||||
function cmdSiteDatabaseDump() {
|
||||
local domain error
|
||||
domain=$(domainPrepare "$1")
|
||||
if ! runError error domainCheck "$domain"; then
|
||||
printDanger "$siteLabel $error"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local file="$2"
|
||||
if [[ -z "$file" ]]; then
|
||||
file="$appDataPath/mariadb/${appDate}_${appTime}_$domain.sql.tar.gz"
|
||||
fi
|
||||
|
||||
local databaseName databaseUser databasePass
|
||||
databaseName=$(siteConfigGet "$domain" "databaseName")
|
||||
databaseUser=$(siteConfigGet "$domain" "databaseUser")
|
||||
databasePass=$(siteConfigGet "$domain" "databasePass")
|
||||
|
||||
printRow
|
||||
printDotText "file" "$file"
|
||||
printDotText "base" "$databaseName"
|
||||
printDotText "user" "$databaseUser"
|
||||
|
||||
if ! runError error mariadbMasterExport "$databaseUser" "$databasePass" "$databaseName" "$file"; then
|
||||
printDotText "status" "$labelFailed"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "status" "$labelDone"
|
||||
fi
|
||||
}
|
||||
|
||||
# Prints system, config, and OLS details for a site.
|
||||
# $1 (domain): site domain name.
|
||||
function cmdSiteInfo() {
|
||||
printRow
|
||||
|
||||
local domain error
|
||||
domain=$(domainPrepare "$1")
|
||||
if ! runError error domainCheck "$domain"; then
|
||||
printDanger "$error"
|
||||
return 1
|
||||
fi
|
||||
|
||||
printSection "System"
|
||||
local ug userId groupId
|
||||
ug=$(domainToUser "$domain")
|
||||
printDotText "user" "$ug"
|
||||
printDotText "group" "$ug"
|
||||
if ! run userId error id -u "$ug"; then
|
||||
printDotText "userID" "$labelUnknown"
|
||||
else
|
||||
printDotText "userID" "$fontGreen$userId$fontReset"
|
||||
fi
|
||||
if ! run groupId error id -g "$ug"; then
|
||||
printDotText "groupID" "$labelUnknown"
|
||||
else
|
||||
printDotText "groupID" "$fontGreen$groupId$fontReset"
|
||||
fi
|
||||
|
||||
local ip
|
||||
ip=$(systemHostGet "$domain")
|
||||
if [[ -z "$ip" ]]; then
|
||||
printDotText "/etc/hosts" "${fontGray}null$fontReset"
|
||||
elif [[ "$ip" == '127.0.0.1' ]]; then
|
||||
printDotText "/etc/hosts" "$fontGreen$ip$fontReset"
|
||||
else
|
||||
printDotText "/etc/hosts" "$fontRed$ip$fontReset"
|
||||
fi
|
||||
|
||||
local limits blockLimit inodeLimit
|
||||
if ! run limits error openlitespeedVhostQuotaGet "$ug"; then
|
||||
printDotText "quota block limit" "$labelUnknown"
|
||||
printDotText "quota inode limit" "$labelUnknown"
|
||||
else
|
||||
read -r blockLimit inodeLimit <<< "$limits"
|
||||
blockLimit=$(numFormat "$blockLimit"000)
|
||||
inodeLimit=$(numFormat "$inodeLimit")
|
||||
printDotText "quota block limit" "$blockLimit"
|
||||
printDotText "quota inode limit" "$inodeLimit"
|
||||
fi
|
||||
|
||||
if groups "$ug" | grep -qw "$sftpAccessGroup"; then
|
||||
printDotText "SFTP access" "$labelOn"
|
||||
else
|
||||
printDotText "SFTP access" "$labelOff"
|
||||
fi
|
||||
|
||||
printSection "Config"
|
||||
# printDotText "file$fontReset" "$appDataPath/config/$domain.config"
|
||||
if [[ ! -f "$appDataPath/config/$domain.config" ]]; then
|
||||
printDotText "file" "$fontRed$appDataPath/config/$domain.config$fontReset"
|
||||
printDotText "file" "${fontRed}not found$fontReset"
|
||||
else
|
||||
printDotText "file" "$fontGreen$appDataPath/config/$domain.config$fontReset"
|
||||
|
||||
local -a params
|
||||
local param value
|
||||
params=(alias databaseName databaseUser databasePass redisUser redisPass postfixUser postfixPass postfixForwardTo sftpPass quotaBlockLimit quotaInodeLimit)
|
||||
for param in "${params[@]}"; do
|
||||
value=$(siteConfigGet "$domain" "$param")
|
||||
printDotText "$param" "${value:-$labelNull}"
|
||||
done
|
||||
fi
|
||||
|
||||
printSection "OpenLiteSpeed"
|
||||
if [[ ! -f "$olsVhostsConfigPath/$domain.conf" ]]; then
|
||||
printDotText "file$fontReset" "$fontRed$olsVhostsConfigPath/$domain.conf$fontReset"
|
||||
printDotText "file$fontReset" "${fontRed}not found$fontReset"
|
||||
else
|
||||
printDotText "file$fontReset" "$fontGreen$olsVhostsConfigPath/$domain.conf$fontReset"
|
||||
fi
|
||||
|
||||
if [[ ! -d "$olsVhostsPath/$domain" ]]; then
|
||||
printDotText "path$fontReset" "$fontRed$olsVhostsPath/$domain$fontReset"
|
||||
printDotText "path$fontReset" "${fontRed}not found$fontReset"
|
||||
else
|
||||
printDotText "path$fontReset" "$fontGreen$olsVhostsPath/$domain$fontReset"
|
||||
fi
|
||||
|
||||
if [[ ! -d "$olsPrivatePath/$domain" ]]; then
|
||||
printDotText "data$fontReset" "$fontRed$olsPrivatePath/$domain$fontReset"
|
||||
printDotText "data$fontReset" "${fontRed}not found$fontReset"
|
||||
else
|
||||
printDotText "data$fontReset" "$fontGreen$olsPrivatePath/$domain$fontReset"
|
||||
fi
|
||||
}
|
||||
|
||||
# Checks site resources (config, system, dirs, OLS, MariaDB, Redis, Postfix, HTTP).
|
||||
# $1 (domain): site domain name.
|
||||
# [$@] (opts): full|short (mode).
|
||||
function cmdSiteStatus() {
|
||||
local domain opt error section label note
|
||||
domain=$(domainPrepare "$1")
|
||||
if ! runError error domainCheck "$domain"; then
|
||||
printDanger "$siteLabel $error"
|
||||
return 1
|
||||
fi
|
||||
|
||||
mode="${2:-full}"
|
||||
|
||||
printSection "Config"
|
||||
if [[ -f "$appDataPath/config/$domain.config" ]]; then
|
||||
printDotText "file" "$fontGreen$appDataPath/config/$domain.config$fontReset"
|
||||
|
||||
local -a params
|
||||
local param value
|
||||
params=(databaseName databaseUser databasePass redisUser redisPass postfixUser postfixPass quotaBlockLimit quotaInodeLimit)
|
||||
for param in "${params[@]}"; do
|
||||
value=$(siteConfigGet "$domain" "$param")
|
||||
if [[ -n "$value" ]]; then
|
||||
printDotText "$param" "$labelPass"
|
||||
else
|
||||
printDotText "$param" "$labelFail"
|
||||
fi
|
||||
done
|
||||
else
|
||||
printDotText "file" "$fontRed$appDataPath/config/$domain.config$fontReset"
|
||||
fi
|
||||
|
||||
printSection "System"
|
||||
local userId groupId ug
|
||||
ug=$(domainToUser "$domain")
|
||||
note="$fontGray$ug$fontReset"
|
||||
if ! run userId error id -u "$ug"; then
|
||||
printDotText "user" "$note $labelFail"
|
||||
else
|
||||
printDotText "user" "$note $labelPass"
|
||||
fi
|
||||
if ! run groupId error id -g "$ug"; then
|
||||
printDotText "group" "$note $labelFail"
|
||||
else
|
||||
printDotText "group" "$note $labelPass"
|
||||
fi
|
||||
|
||||
local ip
|
||||
ip=$(systemHostGet "$domain")
|
||||
note="$fontGray$ip$fontReset"
|
||||
if [[ "$ip" != '127.0.0.1' ]]; then
|
||||
printDotText "/etc/hosts" "$note $labelFail"
|
||||
else
|
||||
printDotText "/etc/hosts" "$note $labelPass"
|
||||
fi
|
||||
|
||||
local limits blockLimit inodeLimit
|
||||
if ! run limits error openlitespeedVhostQuotaGet "$ug"; then
|
||||
printDotText "quota block limit" "$labelFail"
|
||||
printDotText "quota inode limit" "$labelFail"
|
||||
else
|
||||
read -r blockLimit inodeLimit <<< "$limits"
|
||||
blockLimit=$(numFormat "$blockLimit"000)
|
||||
inodeLimit=$(numFormat "$inodeLimit")
|
||||
printDotText "quota block limit" "$blockLimit $labelPass"
|
||||
printDotText "quota inode limit" "$inodeLimit $labelPass"
|
||||
fi
|
||||
|
||||
local sftpConfig
|
||||
if ! sftpConfig=$(sshd -T -C user="$ug",host="$hostName",addr=127.0.0.1); then
|
||||
printDotText "SFTP config" "$labelFail"
|
||||
else
|
||||
label="$fontBlue SFTP ForceCommand$fontReset"
|
||||
if ! grep -Fxq "forcecommand internal-sftp -d /www -u 027" <<< "$sftpConfig"; then
|
||||
printDotText "SFTP ForceCommand" "$labelFail"
|
||||
else
|
||||
printDotText "SFTP ForceCommand" "$labelPass"
|
||||
fi
|
||||
if ! grep -Fxq "chrootdirectory %h" <<< "$sftpConfig"; then
|
||||
printDotText "SFTP ChrootDirectory" "$labelFail"
|
||||
else
|
||||
printDotText "SFTP ChrootDirectory" "$labelPass"
|
||||
fi
|
||||
fi
|
||||
|
||||
printSection "Path | existence, owner, permissions, ACL:nobody"
|
||||
local path
|
||||
path="$olsVhostsPath/$domain"
|
||||
label="vhost $fontBlue/$fontReset"
|
||||
note="${fontGray}755:root:root$fontReset"
|
||||
if [[ ! -d "$path" ]]; then
|
||||
printDotText "$label" "$note $labelFail"
|
||||
elif ! fileSignatureCheck "$path" "755:root:root"; then
|
||||
printDotText "$label" "$note $labelFail"
|
||||
else
|
||||
printDotText "$label" "$note $labelPass"
|
||||
fi
|
||||
|
||||
path="$olsVhostsPath/$domain/www"
|
||||
label="vhost $fontBlue/www$fontReset"
|
||||
note="${fontGray}2750:u:g acl:r-x$fontReset"
|
||||
if [[ ! -d "$path" ]]; then
|
||||
printDotText "$label" "$note $labelFail"
|
||||
elif ! fileSignatureCheck "$path" "2750:$ug:$ug"; then
|
||||
printDotText "$label" "$note $labelFail"
|
||||
elif ! fileSignatureAclCheck "$path" "user:nobody:r-x"; then
|
||||
printDotText "$label" "$note $labelFail"
|
||||
else
|
||||
printDotText "$label" "$note $labelPass"
|
||||
fi
|
||||
|
||||
local -a dirs
|
||||
local dir
|
||||
dirs=(session tmp)
|
||||
for dir in "${dirs[@]}"; do
|
||||
path="$olsVhostsPath/$domain/$dir"
|
||||
label="vhost $fontBlue/$dir$fontReset"
|
||||
note="${fontGray}770:u:g acl:rwx$fontReset"
|
||||
if [[ ! -d "$path" ]]; then
|
||||
printDotText "$label" "$note $labelFail"
|
||||
elif ! fileSignatureCheck "$path" "770:$ug:$ug"; then
|
||||
printDotText "$label" "$note $labelFail"
|
||||
elif ! fileSignatureAclCheck "$path" "user:nobody:rwx"; then
|
||||
printDotText "$label" "$note $labelFail"
|
||||
else
|
||||
printDotText "$label" "$note $labelPass"
|
||||
fi
|
||||
done
|
||||
|
||||
path="$olsPrivatePath/$domain"
|
||||
label="vhost-data $fontBlue/$fontReset"
|
||||
note="${fontGray}750:u:g acl:r-x$fontReset"
|
||||
if [[ ! -d "$path" ]]; then
|
||||
printDotText "$label" "$note $labelFail"
|
||||
elif ! fileSignatureCheck "$path" "750:$ug:$ug"; then
|
||||
printDotText "$label" "$note $labelFail"
|
||||
elif ! fileSignatureAclCheck "$path" "user:nobody:r-x"; then
|
||||
printDotText "$label" "$note $labelFail"
|
||||
else
|
||||
printDotText "$label" "$note $labelPass"
|
||||
fi
|
||||
|
||||
path="$olsPrivatePath/$domain/bootstrap.php"
|
||||
label="vhost-data $fontBlue/bootstrap.php$fontReset"
|
||||
note="${fontGray}600:u:g acl:r--$fontReset"
|
||||
if [[ ! -f "$path" ]]; then
|
||||
printDotText "$label" "$note $labelFail"
|
||||
elif ! fileSignatureCheck "$path" "600:$ug:$ug"; then
|
||||
printDotText "$label" "$note $labelFail"
|
||||
elif ! fileSignatureAclCheck "$path" "user:nobody:r--"; then
|
||||
printDotText "$label" "$note $labelFail"
|
||||
else
|
||||
printDotText "$label" "$note $labelPass"
|
||||
fi
|
||||
# fileSignatureAclDiff "$path" "user:nobody:r--"
|
||||
|
||||
printSection "OpenLiteSpeed"
|
||||
if ! run vhostList error openlitespeedConfigVhostList; then
|
||||
printDotText "get vhost list" "$labelFail"
|
||||
else
|
||||
note="$fontGray$domain$fontReset"
|
||||
if listContains "$domain" "$vhostList"; then
|
||||
printDotText "vhost" "$note $labelPass"
|
||||
else
|
||||
printDotText "vhost" "$note $labelFail"
|
||||
fi
|
||||
fi
|
||||
note="$fontGray$domain.conf$fontReset"
|
||||
if [[ ! -f "$olsVhostsConfigPath/$domain.conf" ]]; then
|
||||
printDotText "config" "$note $labelFail"
|
||||
else
|
||||
printDotText "config" "$note $labelPass"
|
||||
fi
|
||||
|
||||
printSection "MariaDB"
|
||||
local mariadbDatabaseList mariadbUserList
|
||||
if ! run mariadbDatabaseList error mariadbDatabaseListGet; then
|
||||
printDotText "get database list" "$labelFail"
|
||||
elif ! run mariadbUserList error mariadbUserListGet; then
|
||||
printDotText "get user list" "$labelFail"
|
||||
else
|
||||
mariadbDatabase=$(siteConfigGet "$domain" "databaseName")
|
||||
note="$fontGray$mariadbDatabase$fontReset"
|
||||
if ! listContains "$mariadbDatabase" "$mariadbDatabaseList"; then
|
||||
printDotText "base" "$note $labelFail"
|
||||
else
|
||||
printDotText "base" "$note $labelPass"
|
||||
fi
|
||||
|
||||
mariadbUser=$(siteConfigGet "$domain" "databaseUser")
|
||||
note="$fontGray$mariadbUser$fontReset"
|
||||
if ! listContains "$mariadbUser" "$mariadbUserList"; then
|
||||
printDotText "user" "$note $labelFail"
|
||||
else
|
||||
printDotText "user" "$note $labelPass"
|
||||
fi
|
||||
|
||||
if [[ "$mode" == "full" ]]; then
|
||||
mariadbPass=$(siteConfigGet "$domain" "databasePass")
|
||||
if ! run output error mariadbMasterExec mariadb -u"$mariadbUser" -p"$mariadbPass" -N -e "SELECT 1;"; then
|
||||
printDotText "access" "$labelFail"
|
||||
elif [[ "$output" != "1" ]]; then
|
||||
printDotText "access" "$labelFail"
|
||||
else
|
||||
printDotText "access" "$labelPass"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
printSection "Redis"
|
||||
local redisUserList
|
||||
if ! run redisUserList error redisUserListGet; then
|
||||
printDotText "$fontBlue get user list$fontReset" "$labelFail"
|
||||
else
|
||||
redisUser=$(siteConfigGet "$domain" "redisUser")
|
||||
note="$fontGray$redisUser$fontReset"
|
||||
if ! listContains "$redisUser" "$redisUserList"; then
|
||||
printDotText "user" "$note $labelFail"
|
||||
else
|
||||
printDotText "user" "$note $labelPass"
|
||||
fi
|
||||
if [[ "$mode" == "full" ]]; then
|
||||
redisPass=$(siteConfigGet "$domain" "redisPass")
|
||||
if ! run output error redisExec redis-cli --no-auth-warning --user "$redisUser" --pass "$redisPass" PING; then
|
||||
printDotText "access" "$labelFail"
|
||||
elif [[ "$output" != "PONG" ]]; then
|
||||
printDotText "access" "$labelFail"
|
||||
else
|
||||
printDotText "access" "$labelPass"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
printSection "Postfix"
|
||||
local postfixSaslUserList
|
||||
if ! run postfixSaslUserList error postfixSaslUserList; then
|
||||
printDotText "get SASL list" "$labelFail"
|
||||
else
|
||||
postfixUser=$(siteConfigGet "$domain" "postfixUser")
|
||||
note="$fontGray$postfixUser$fontReset"
|
||||
if ! listContains "$postfixUser@$postfixDefaultRealm" "$postfixSaslUserList"; then
|
||||
printDotText "SASL user" "$note $labelFail"
|
||||
else
|
||||
printDotText "SASL user" "$note $labelPass"
|
||||
fi
|
||||
postfixPass=$(siteConfigGet "$domain" "postfixPass")
|
||||
printDotText "SASL access" "${fontGray}in progress$fontReset"
|
||||
fi
|
||||
|
||||
if [[ "$mode" == "full" ]]; then
|
||||
printSection "HTTP"
|
||||
local httpCode urlEffective
|
||||
if ! run httpCode error urlCode "http://$domain"; then
|
||||
printDotText "HTTP code" "${fontGray}unknown$fontReset"
|
||||
elif [[ "$httpCode" == 200 ]]; then
|
||||
printDotText "HTTP code" "$fontGreen$httpCode$fontReset"
|
||||
elif [[ "$httpCode" =~ ^[23][0-9]{2}$ ]]; then
|
||||
printDotText "HTTP code" "$fontYellow$httpCode$fontReset"
|
||||
else
|
||||
printDotText "HTTP code" "$fontRed$httpCode$fontReset"
|
||||
fi
|
||||
if ! run urlEffective error urlAccessible "$domain"; then
|
||||
printDotText "URL effective" "${fontGray}unknown$fontReset"
|
||||
else
|
||||
printDotText "URL effective" "$fontGreen$urlEffective$fontReset"
|
||||
fi
|
||||
|
||||
printSection "Files"
|
||||
siteFilesCheck "$domain"
|
||||
fi
|
||||
}
|
||||
|
||||
function siteFilesCheck() {
|
||||
local domain error ug dots
|
||||
domain=$(domainPrepare "$1")
|
||||
if ! runError error domainCheck "$domain"; then
|
||||
printDanger "$error"
|
||||
return 1
|
||||
fi
|
||||
|
||||
ug=$(domainToUser "$domain")
|
||||
dots=30
|
||||
|
||||
# fileSignatureDiff "$olsVhostsPath/$domain" "755:root:root"
|
||||
if ! run output error fileSignatureDiff "$olsVhostsPath/$domain" "755:root:root"; then
|
||||
printDanger "${error:-$output}"
|
||||
elif [[ -n "$output" ]]; then
|
||||
local prefix=":$olsVhostsPath/$domain"
|
||||
printDot "$dots" "${fontCyan}vhost d 755:root:root$fontReset" "" "${output/$prefix/ /}"
|
||||
fi
|
||||
|
||||
# fileSignatureDiffList "$olsVhostsPath/$domain/www" "d" "2750|$ug:$ug"
|
||||
if ! run output error fileSignatureDiffList "$olsVhostsPath/$domain/www" "d" "2750|$ug:$ug"; then
|
||||
printDanger "${error:-$output}"
|
||||
else
|
||||
lineCount=$(grep -c . <<< "$output" || true)
|
||||
if [[ "$lineCount" -gt 0 ]]; then
|
||||
local label="${fontCyan}www d 2750:u:g$fontReset"
|
||||
local prefix=":$olsVhostsPath/$domain/www/"
|
||||
while read -r line; do
|
||||
printDot "$dots" "$label" "" "${line/$prefix/ /}"
|
||||
done < <(awk 'NF' <<< "$output")
|
||||
fi
|
||||
fi
|
||||
|
||||
# fileSignatureDiffList "$olsVhostsPath/$domain/www" "f" "(600|640):$ug:$ug"
|
||||
if ! run output error fileSignatureDiffList "$olsVhostsPath/$domain/www" "f" "(600|640):$ug:$ug"; then
|
||||
printDanger "${error:-$output}"
|
||||
else
|
||||
lineCount=$(grep -c . <<< "$output" || true)
|
||||
if [[ "$lineCount" -gt 0 ]]; then
|
||||
local label="${fontCyan}www f (600|640):u:g$fontReset"
|
||||
local prefix=":$olsVhostsPath/$domain/www/"
|
||||
while read -r line; do
|
||||
printDot "$dots" "$label" "" "${line/$prefix/ /}"
|
||||
done < <(awk 'NF' <<< "$output")
|
||||
fi
|
||||
fi
|
||||
|
||||
# fileSignatureDiffList "$olsVhostsPath/$domain/tmp" "d" "770:$ug:$ug"
|
||||
if ! run output error fileSignatureDiffList "$olsVhostsPath/$domain/tmp" "d" "770:$ug:$ug"; then
|
||||
printDanger "${error:-$output}"
|
||||
else
|
||||
lineCount=$(grep -c . <<< "$output" || true)
|
||||
if [[ "$lineCount" -gt 0 ]]; then
|
||||
local label="${fontCyan}tmp d 770:u:g$fontReset"
|
||||
local prefix=":$olsVhostsPath/$domain/tmp/"
|
||||
while read -r line; do
|
||||
printDot "$dots" "$label" "" "${line/$prefix/ /}"
|
||||
done < <(awk 'NF' <<< "$output")
|
||||
fi
|
||||
fi
|
||||
|
||||
# fileSignatureDiffList "$olsVhostsPath/$domain/tmp" "f" "660:$ug:$ug"
|
||||
if ! run output error fileSignatureDiffList "$olsVhostsPath/$domain/tmp" "f" "660:$ug:$ug"; then
|
||||
printDanger "${error:-$output}"
|
||||
else
|
||||
lineCount=$(grep -c . <<< "$output" || true)
|
||||
if [[ "$lineCount" -gt 0 ]]; then
|
||||
local label="${fontCyan}tmp f 660:u:g$fontReset"
|
||||
local prefix=":$olsVhostsPath/$domain/tmp/"
|
||||
while read -r line; do
|
||||
printDot "$dots" "$label" "" "${line/$prefix/ /}"
|
||||
done < <(awk 'NF' <<< "$output")
|
||||
fi
|
||||
fi
|
||||
|
||||
# fileSignatureDiffList "$olsVhostsPath/$domain/session" "d" "770:$ug:$ug"
|
||||
if ! run output error fileSignatureDiffList "$olsVhostsPath/$domain/session" "d" "770:$ug:$ug"; then
|
||||
printDanger "${error:-$output}"
|
||||
else
|
||||
lineCount=$(grep -c . <<< "$output" || true)
|
||||
if [[ "$lineCount" -gt 0 ]]; then
|
||||
local label="${fontCyan}session d 770:u:g$fontReset"
|
||||
local prefix=":$olsVhostsPath/$domain/session/"
|
||||
while read -r line; do
|
||||
printDot "$dots" "$label" "" "${line/$prefix/ /}"
|
||||
done < <(awk 'NF' <<< "$output")
|
||||
fi
|
||||
fi
|
||||
|
||||
# fileSignatureDiffList "$olsVhostsPath/$domain/session" "f" "(660|600):$ug:$ug"
|
||||
if ! run output error fileSignatureDiffList "$olsVhostsPath/$domain/session" "f" "(660|600):$ug:$ug"; then
|
||||
printDanger "${error:-$output}"
|
||||
else
|
||||
lineCount=$(grep -c . <<< "$output" || true)
|
||||
if [[ "$lineCount" -gt 0 ]]; then
|
||||
local label="${fontCyan}session f (660|600):u:g$fontReset"
|
||||
local prefix=":$olsVhostsPath/$domain/session/"
|
||||
while read -r line; do
|
||||
printDot "$dots" "$label" "" "${line/$prefix/ /}"
|
||||
done < <(awk 'NF' <<< "$output")
|
||||
fi
|
||||
fi
|
||||
|
||||
# fileSignatureDiffList "$olsPrivatePath/$domain" "d" "750:$ug:$ug"
|
||||
if ! run output error fileSignatureDiffList "$olsPrivatePath/$domain" "d" "750:$ug:$ug"; then
|
||||
printDanger "${error:-$output}"
|
||||
else
|
||||
lineCount=$(grep -c . <<< "$output" || true)
|
||||
if [[ "$lineCount" -gt 0 ]]; then
|
||||
local label="${fontCyan}data d 750:u:g$fontReset"
|
||||
local prefix=":$olsPrivatePath/$domain/"
|
||||
while read -r line; do
|
||||
printDot "$dots" "$label" "" "${line/$prefix/ /}"
|
||||
done < <(awk 'NF' <<< "$output")
|
||||
fi
|
||||
fi
|
||||
|
||||
# fileSignatureDiffList "$olsPrivatePath/$domain" "f" "600:$ug:$ug"
|
||||
if ! run output error fileSignatureDiffList "$olsPrivatePath/$domain" "f" "600:$ug:$ug"; then
|
||||
printDanger "${error:-$output}"
|
||||
else
|
||||
lineCount=$(grep -c . <<< "$output" || true)
|
||||
if [[ "$lineCount" -gt 0 ]]; then
|
||||
local label="${fontCyan}data f 600:u:g$fontReset"
|
||||
local prefix=":$olsPrivatePath/$domain/"
|
||||
while read -r line; do
|
||||
printDot "$dots" "$label" "" "${line/$prefix/ /}"
|
||||
done < <(awk 'NF' <<< "$output")
|
||||
fi
|
||||
fi
|
||||
}
|
||||
@@ -1,374 +0,0 @@
|
||||
storageLabel="[Storage]"
|
||||
|
||||
# Lists all backup directories on external storage with type labels (archive, daily, or unknown).
|
||||
function cmdStorageBackupList() {
|
||||
local dirList error dirCount archiveList dailyList
|
||||
run dirList error storageFileList "/" d || { printDanger "$error"; return 1; }
|
||||
dirCount=$(grep -c . <<< "$dirList" || true)
|
||||
archiveList=$(printf '%s\n' "$dirList" | grep -E -- "$backupArchiveDirPattern" | sort || true)
|
||||
dailyList=$(printf '%s\n' "$dirList" | grep -E -- "$backupDailyDirPattern" | sort || true)
|
||||
|
||||
printRow
|
||||
|
||||
local i=0 num dir label
|
||||
while read -r dir; do
|
||||
((++i))
|
||||
num=$(printf "%0${#dirCount}d" "$i")
|
||||
label="$num: $fontBlue$dir$fontReset"
|
||||
|
||||
if listContains "$dir" "$archiveList"; then
|
||||
printDotText "$label" "${fontGreen}archive$fontReset"
|
||||
elif listContains "$dir" "$dailyList"; then
|
||||
printDotText "$label" "${fontGreen}daily$fontReset"
|
||||
else
|
||||
printDotText "$label" "$labelUnknown"
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$dirList")
|
||||
}
|
||||
|
||||
# Rotates archive backups on external storage: deletes old entries exceeding $storageArchiveKeep.
|
||||
function cmdStorageBackupArchiveDispatch() {
|
||||
local dirList error
|
||||
run dirList error storageFileList "/" d || { printDanger "$error"; return 1; }
|
||||
|
||||
printSection "Config"
|
||||
printDotText "Type" "$storageType"
|
||||
printDotText "Path" "$rsyncPath"
|
||||
printDotText "Archive keep" "$storageArchiveKeep"
|
||||
printDotText "Archive pattern" "$backupArchiveDirPattern"
|
||||
|
||||
printSection "Directories found"
|
||||
archiveList=$(printf '%s\n' "$dirList" | grep -E -- "$backupArchiveDirPattern" | sort || true)
|
||||
local archiveCount archiveRemoveCount i=0 num label dirDate dirDays archiveRemoveList=""
|
||||
archiveCount=$(grep -c . <<< "$archiveList" || true)
|
||||
if [[ "$archiveCount" -gt 0 ]]; then
|
||||
while read -r dir; do
|
||||
((++i))
|
||||
num=$(printf "%0${#archiveCount}d" "$i")
|
||||
label="$num: $fontBlue$dir$fontReset"
|
||||
|
||||
if (( archiveCount - storageArchiveKeep >= i )); then
|
||||
printDotText "$label" "${fontRed}delete$fontReset"
|
||||
archiveRemoveList+=$'\n'"$dir"
|
||||
else
|
||||
printDotText "$label" "${fontGreen}save$fontReset"
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$archiveList")
|
||||
|
||||
archiveRemoveCount=$(grep -c . <<< "$archiveRemoveList" || true)
|
||||
if [[ "$archiveRemoveCount" -gt 0 ]]; then
|
||||
printSection "Deleting Directories"
|
||||
while read -r dir; do
|
||||
label="$dir"
|
||||
if ! runError error storageBackupRemove "$dir"; then
|
||||
printDotText "$label" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "$label" "$labelDone"
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$archiveRemoveList")
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Rotates daily backups on external storage: deletes old entries exceeding $storageDailyKeep, skips today.
|
||||
function cmdStorageBackupDailyDispatch() {
|
||||
local dirList error
|
||||
run dirList error storageFileList "/" d || { printDanger "$error"; return 1; }
|
||||
|
||||
printSection "Config"
|
||||
printDotText "Type" "$storageType"
|
||||
printDotText "Path" "$rsyncPath"
|
||||
printDotText "Daily keep" "$storageDailyKeep"
|
||||
printDotText "Daily pattern" "$backupDailyDirPattern"
|
||||
|
||||
printSection "Directories found"
|
||||
dailyList=$(printf '%s\n' "$dirList" | grep -v "$appDate" | grep -E -- "$backupDailyDirPattern" | sort || true)
|
||||
local dailyCount dailyRemoveCount i=0 num label dailyRemoveList=""
|
||||
dailyCount=$(grep -c . <<< "$dailyList" || true)
|
||||
if [[ "$dailyCount" -gt 0 ]]; then
|
||||
while read -r dir; do
|
||||
((++i))
|
||||
num=$(printf "%0${#dailyCount}d" "$i")
|
||||
label="$num: $fontBlue$dir$fontReset"
|
||||
|
||||
if [[ "$dir" == "$appDate" ]]; then
|
||||
printDotText "$label" "${fontGray}skipped$fontReset"
|
||||
elif (( dailyCount - storageDailyKeep >= i )); then
|
||||
printDotText "$label" "${fontRed}delete$fontReset"
|
||||
dailyRemoveList+=$'\n'"$dir"
|
||||
else
|
||||
printDotText "$label" "${fontGreen}save$fontReset"
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$dailyList")
|
||||
|
||||
dailyRemoveCount=$(grep -c . <<< "$dailyRemoveList" || true)
|
||||
if [[ "$dailyRemoveCount" -gt 0 ]]; then
|
||||
printSection "Deleting Directories"
|
||||
while read -r dir; do
|
||||
label="$dir"
|
||||
if ! runError error storageBackupRemove "$dir"; then
|
||||
printDotText "$label" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "$label" "$labelDone"
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$dailyRemoveList")
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Synchronizes the local path to external storage (rsync or SSH).
|
||||
# $1 (sourcePath): local path to sync.
|
||||
# [$2] (type): storage type (rsync or ssh); defaults to $storageType.
|
||||
function cmdStoragePathSync() {
|
||||
local sourcePath="$1"
|
||||
[[ -n "$sourcePath" ]] || { printDanger "Source path not specified"; return 1; }
|
||||
|
||||
local type="${2:-$storageType}"
|
||||
local error
|
||||
|
||||
printSection "Config"
|
||||
printDotText "Source" "$sourcePath"
|
||||
if [[ ! -e "$sourcePath" ]]; then
|
||||
printDotText "$sourcePath" "${fontRed}not found$fontReset"
|
||||
return 1
|
||||
fi
|
||||
|
||||
case "$type" in
|
||||
rsync)
|
||||
printDotText "Type" "$type"
|
||||
printDotText "rSync URI" "$rsyncUri"
|
||||
printDotText "rSync path" "$rsyncPath"
|
||||
printSection "Executing"
|
||||
runError error storagePathSyncRsync "$sourcePath" || {
|
||||
printDotText "synchronization" "$labelFail"
|
||||
printDanger "$error"
|
||||
return 1
|
||||
}
|
||||
;;
|
||||
ssh)
|
||||
printDotText "Type" "$type"
|
||||
printDotText "SSH URI" "$sshUser@$sshHost"
|
||||
printDotText "SSH path" "$sshPath"
|
||||
printSection "Executing"
|
||||
runError error storagePathSyncSSH "$sourcePath" || {
|
||||
printDotText "synchronization" "$labelFail"
|
||||
printDanger "$error"
|
||||
return 1
|
||||
}
|
||||
;;
|
||||
*)
|
||||
printDotText "Type" "$type $labelUnknown"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
printDotText "synchronization" "$labelDone"
|
||||
}
|
||||
|
||||
# Syncs today's daily backup ($backupDailyPath/$appDate) to external storage.
|
||||
function cmdStorageBackupDailySyncLast() {
|
||||
cmdStoragePathSync "$backupDailyPath/$appDate"
|
||||
}
|
||||
|
||||
# Syncs the newest archive backup directory to external storage.
|
||||
function cmdStorageBackupArchiveSyncLast() {
|
||||
local dirList archiveList archiveDir error
|
||||
run dirList error fileList "$backupArchivePath" d || { printDanger "$error"; return 1; }
|
||||
archiveList=$(printf '%s\n' "$dirList" | grep -E -- "$backupArchiveDirPattern" | sort || true)
|
||||
archiveDir=$(tail -n 1 <<< "$archiveList")
|
||||
[[ -n "$archiveDir" ]] || { printDanger "No archive directories found"; return 1; }
|
||||
|
||||
cmdStoragePathSync "$backupArchivePath/$archiveDir"
|
||||
}
|
||||
|
||||
# Interactively selects a local archive backup directory and syncs it to external storage.
|
||||
function cmdStorageBackupArchiveSync() {
|
||||
local dirList error archiveList archiveCount
|
||||
run dirList error fileList "$backupArchivePath" d || { printDanger "$error"; return 1; }
|
||||
archiveList=$(printf '%s\n' "$dirList" | grep -E -- "$backupArchiveDirPattern" | sort || true)
|
||||
archiveCount=$(grep -c . <<< "$archiveList" || true)
|
||||
|
||||
printRow
|
||||
|
||||
local i=0 num dir
|
||||
while read -r dir; do
|
||||
((++i))
|
||||
num=$(printf "%0${#archiveCount}d" "$i")
|
||||
printDotText "$num: $fontBlue$dir$fontReset" "${fontGreen}archive$fontReset"
|
||||
done < <(awk 'NF' <<< "$archiveList")
|
||||
|
||||
printRow
|
||||
local input item selected
|
||||
read -r -p "Specify the backup number: " input
|
||||
|
||||
printRow
|
||||
[[ -z "$input" ]] && input=0
|
||||
[[ "$input" =~ ^[0-9]+$ ]] || { printDanger "Invalid backup number"; return 1; }
|
||||
item=$((10#$input))
|
||||
selected=$(awk 'NF {n++} n == item {print; exit}' item="$item" <<< "$archiveList")
|
||||
[[ -n "$selected" ]] || { printDanger "Unknown backup number"; return 1; }
|
||||
|
||||
cmdStoragePathSync "$backupArchivePath/$selected"
|
||||
}
|
||||
|
||||
# Interactively selects a local daily backup directory and syncs it to external storage.
|
||||
function cmdStorageBackupDailySync() {
|
||||
local dirList error dailyList dailyCount
|
||||
run dirList error fileList "$backupDailyPath" d || { printDanger "$error"; return 1; }
|
||||
dailyList=$(printf '%s\n' "$dirList" | grep -E -- "$backupDailyDirPattern" | sort || true)
|
||||
dailyCount=$(grep -c . <<< "$dailyList" || true)
|
||||
|
||||
printRow
|
||||
|
||||
local i=0 num dir
|
||||
while read -r dir; do
|
||||
((++i))
|
||||
num=$(printf "%0${#dailyCount}d" "$i")
|
||||
printDotText "$num: $fontBlue$dir$fontReset" "${fontGreen}daily$fontReset"
|
||||
done < <(awk 'NF' <<< "$dailyList")
|
||||
|
||||
printRow
|
||||
local input item selected
|
||||
read -r -p "Specify the backup number: " input
|
||||
|
||||
printRow
|
||||
[[ -z "$input" ]] && input=0
|
||||
[[ "$input" =~ ^[0-9]+$ ]] || { printDanger "Invalid backup number"; return 1; }
|
||||
item=$((10#$input))
|
||||
selected=$(awk 'NF {n++} n == item {print; exit}' item="$item" <<< "$dailyList")
|
||||
[[ -n "$selected" ]] || { printDanger "Unknown backup number"; return 1; }
|
||||
|
||||
cmdStoragePathSync "$backupDailyPath/$selected"
|
||||
}
|
||||
|
||||
# Dispatches interactive backup sync by type.
|
||||
# $1 (type): backup type: archive or daily.
|
||||
function cmdStorageBackupSync() {
|
||||
local type
|
||||
type="$1"
|
||||
|
||||
case "$type" in
|
||||
archive)
|
||||
cmdStorageBackupArchiveSync
|
||||
;;
|
||||
daily)
|
||||
cmdStorageBackupDailySync
|
||||
;;
|
||||
*)
|
||||
printDanger "Unknown type backup: $type";
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Interactively selects a backup directory on external storage and removes it.
|
||||
function cmdStorageBackupRemove() {
|
||||
local dirList error dirCount archiveList dailyList dir i=0 num label
|
||||
run dirList error storageFileList "/" d || { printDanger "$error"; return 1; }
|
||||
|
||||
dirCount=$(grep -c . <<< "$dirList" || true)
|
||||
archiveList=$(printf '%s\n' "$dirList" | grep -E -- "$backupArchiveDirPattern" | sort || true)
|
||||
dailyList=$(printf '%s\n' "$dirList" | grep -E -- "$backupDailyDirPattern" | sort || true)
|
||||
|
||||
printRow
|
||||
|
||||
while read -r dir; do
|
||||
((++i))
|
||||
num=$(printf "%0${#dirCount}d" "$i")
|
||||
label="$num: $fontBlue$dir$fontReset"
|
||||
|
||||
if listContains "$dir" "$archiveList"; then
|
||||
printDotText "$label" "${fontGreen}archive$fontReset"
|
||||
elif listContains "$dir" "$dailyList"; then
|
||||
printDotText "$label" "${fontGreen}daily$fontReset"
|
||||
else
|
||||
printDotText "$label" "$labelUnknown"
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$dirList")
|
||||
|
||||
printRow
|
||||
local input item selected
|
||||
read -r -p "Specify the backup number: " input
|
||||
|
||||
printRow
|
||||
[[ -z "$input" ]] && input=0
|
||||
[[ "$input" =~ ^[0-9]+$ ]] || { printDanger "Invalid backup number"; return 1; }
|
||||
item=$((10#$input))
|
||||
selected=$(awk 'NF {n++} n == item {print; exit}' item="$item" <<< "$dirList")
|
||||
[[ -n "$selected" ]] || { printDanger "Unknown backup number"; return 1; }
|
||||
|
||||
if ! runError error storageBackupRemove "$selected"; then
|
||||
printDotText "$selected" "$labelFail"
|
||||
printDanger "$error"
|
||||
return 1
|
||||
fi
|
||||
printDotText "$selected" "$labelDone"
|
||||
}
|
||||
|
||||
# Compares local and remote backup directories, showing which exist on each side.
|
||||
function cmdStorageBackupCompare() {
|
||||
local dirList error localArchiveList localDailyList remoteArchiveList remoteDailyList
|
||||
|
||||
run dirList error fileList "$backupArchivePath" d || { printDanger "Archive path: $error"; return 1; }
|
||||
localArchiveList=$(printf '%s\n' "$dirList" | grep -E -- "$backupArchiveDirPattern" | sort || true)
|
||||
|
||||
run dirList error fileList "$backupDailyPath" d || { printDanger "Archive path: $error"; return 1; }
|
||||
localDailyList=$(printf '%s\n' "$dirList" | grep -E -- "$backupDailyDirPattern" | sort || true)
|
||||
|
||||
run dirList error storageFileList "/" d || { printDanger "$error"; return 1; }
|
||||
remoteArchiveList=$(printf '%s\n' "$dirList" | grep -E -- "$backupArchiveDirPattern" | sort || true)
|
||||
remoteDailyList=$(printf '%s\n' "$dirList" | grep -E -- "$backupDailyDirPattern" | sort || true)
|
||||
|
||||
printSection "Local"
|
||||
printDotText "Archive" "$(grep -c . <<< "$localArchiveList" || true)"
|
||||
printDotText "Daily" "$(grep -c . <<< "$localDailyList" || true)"
|
||||
|
||||
printSection "Remote"
|
||||
printDotText "Archive" "$(grep -c . <<< "$remoteArchiveList" || true)"
|
||||
printDotText "Daily" "$(grep -c . <<< "$remoteDailyList" || true)"
|
||||
|
||||
local allDirs
|
||||
allDirs=$(printf '%s\n' "$localArchiveList" "$localDailyList" "$remoteArchiveList" "$remoteDailyList" | awk 'NF' | sort -u)
|
||||
|
||||
printSection "Comparison"
|
||||
local dir localStatus remoteStatus
|
||||
out="${fontRed}X$fontReset"
|
||||
localIn="${fontGreen}L$fontReset"
|
||||
remoteIn="${fontGreen}R$fontReset"
|
||||
while read -r dir; do
|
||||
localStatus="$out"
|
||||
remoteStatus="$out"
|
||||
|
||||
if listContains "$dir" "$localArchiveList"; then
|
||||
localStatus="$localIn"
|
||||
elif listContains "$dir" "$localDailyList"; then
|
||||
localStatus="$localIn"
|
||||
fi
|
||||
if listContains "$dir" "$remoteArchiveList"; then
|
||||
remoteStatus="$remoteIn"
|
||||
elif listContains "$dir" "$remoteDailyList"; then
|
||||
remoteStatus="$remoteIn"
|
||||
fi
|
||||
|
||||
printDotText "$fontBlue$dir$fontReset" "[ $localStatus : $remoteStatus ]"
|
||||
done < <(awk 'NF' <<< "$allDirs")
|
||||
}
|
||||
|
||||
function cmdStorageBackupSize() {
|
||||
local fileList file size total
|
||||
|
||||
printSection "FTP: $ftpPath"
|
||||
|
||||
total=0
|
||||
fileList=$(ftpFileList | sort -n)
|
||||
while IFS= read -r file; do
|
||||
size=$(ftpPathSize "/$file" "gb")
|
||||
printDotText "$file" "$size Gb"
|
||||
(( total += size ))
|
||||
done <<< "$fileList"
|
||||
|
||||
printRow
|
||||
printDotText "total" "$total Gb"
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
systemLabel="[System]"
|
||||
|
||||
# Runs the full system installation flow.
|
||||
function cmdInstallFull() {
|
||||
systemApt || return 1
|
||||
systemIpset || return 1
|
||||
systemIptables || return 1
|
||||
|
||||
cmdK3sInstall || return 1
|
||||
cmdK3sNamespaceAdd || return 1
|
||||
|
||||
cmdMariadbInstall || return 1
|
||||
cmdRedisInstall || return 1
|
||||
cmdOpenlitespeedInstall || return 1
|
||||
cmdPostfixInstall || return 1
|
||||
cmdWorkerInstall || return 1
|
||||
cmdMetricInstall || return 1
|
||||
}
|
||||
|
||||
# Displays numbered cron job list and stores selected job + current crontab in namerefs.
|
||||
# Known jobs (cronJobs[]): green if installed, gray if missing.
|
||||
# Unknown kube.sh jobs found in crontab: yellow, numbered after known jobs.
|
||||
# $1 (varname): nameref for selected job string
|
||||
# $2 (listvar): nameref for current crontab lines
|
||||
function cmdCronSelect() {
|
||||
local -n __cronJob="$1"
|
||||
local -n __cronList="$2"
|
||||
|
||||
printRow
|
||||
|
||||
local error
|
||||
run __cronList error systemCronList || { printDanger "Error retrieving the CRON job list: $error"; return 1; }
|
||||
|
||||
local -a shownJobs
|
||||
local i job fontColor
|
||||
for ((i=0; i<${#cronJobs[@]}; i++)); do
|
||||
job="${cronJobs[$i]}"
|
||||
shownJobs+=("$job")
|
||||
grep -Fxq -- "$job" <<< "$__cronList" && fontColor="$fontGreen" || fontColor="$fontGray"
|
||||
printf "%2d: %s\n" "$((i+1))" "$fontColor$job$fontReset"
|
||||
done
|
||||
while IFS= read -r job; do
|
||||
[[ -z "$job" ]] && continue
|
||||
grep -Fxq -- "$job" <(printf '%s\n' "${cronJobs[@]}") && continue
|
||||
grep -Fq -- "$appPath/kube.sh" <<< "$job" || continue
|
||||
shownJobs+=("$job")
|
||||
printf "%2d: %s\n" "${#shownJobs[@]}" "$fontYellow$job$fontReset"
|
||||
done <<< "$__cronList"
|
||||
|
||||
printRow
|
||||
local input item
|
||||
read -r -p "Specify the job number: " input
|
||||
|
||||
printRow
|
||||
[[ -z "$input" ]] && input=0
|
||||
[[ "$input" =~ ^[0-9]+$ ]] || { printDanger "Invalid job number"; return 1; }
|
||||
item=$((10#$input - 1))
|
||||
(( item < 0 || item >= ${#shownJobs[@]} )) && { printDanger "Unknown job number"; return 1; }
|
||||
|
||||
__cronJob="${shownJobs[$item]}"
|
||||
}
|
||||
|
||||
# Interactively selects and adds a managed cron job to root crontab.
|
||||
function cmdCronAdd() {
|
||||
local selected jobList
|
||||
cmdCronSelect selected jobList || return 1
|
||||
printDotText "job" "$selected"
|
||||
|
||||
grep -Fxq -- "$selected" <(printf '%s\n' "${cronJobs[@]}") || {
|
||||
printDotText "adding" "$labelFail"
|
||||
printDanger "Cannot add unmanaged job"
|
||||
return 1
|
||||
}
|
||||
|
||||
if grep -Fxq -- "$selected" <<< "$jobList"; then
|
||||
printDotText "adding" "$labelDone"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local tmp
|
||||
tmp=$(mktemp) || return 1
|
||||
{ printf '%s\n' "$jobList"; printf '%s\n' "$selected"; } > "$tmp"
|
||||
crontab -u root "$tmp" || {
|
||||
rm -f "$tmp"
|
||||
printDotText "adding" "$labelFail"
|
||||
return 1
|
||||
}
|
||||
|
||||
rm -f "$tmp"
|
||||
printDotText "adding" "$labelDone"
|
||||
}
|
||||
|
||||
# Interactively selects and removes a managed cron job from root crontab.
|
||||
function cmdCronRemove() {
|
||||
local selected jobList
|
||||
cmdCronSelect selected jobList || return 1
|
||||
printDotText "job" "$selected"
|
||||
|
||||
if ! grep -Fxq -- "$selected" <<< "$jobList"; then
|
||||
printDotText "removing" "$labelDone"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local tmp
|
||||
tmp=$(mktemp) || return 1
|
||||
grep -Fxv -- "$selected" <<< "$jobList" > "$tmp"
|
||||
crontab -u root "$tmp" || {
|
||||
rm -f "$tmp"
|
||||
printDotText "removing" "$labelFail"
|
||||
return 1
|
||||
}
|
||||
|
||||
rm -f "$tmp"
|
||||
printDotText "removing" "$labelDone"
|
||||
}
|
||||
|
||||
# Shows managed cron jobs; installed entries in green, missing in gray, unknown in yellow.
|
||||
function cmdCronList() {
|
||||
local jobList error job fontColor list=""
|
||||
run jobList error systemCronList || {
|
||||
printDanger "systemCronList: $error";
|
||||
return 1;
|
||||
}
|
||||
for ((i=0; i<${#cronJobs[@]}; i++)); do
|
||||
grep -Fxq -- "${cronJobs[$i]}" <<< "$jobList" && fontColor="$fontGreen" || fontColor="$fontGray"
|
||||
list+=$'\n'"$fontColor${cronJobs[$i]}$fontReset"
|
||||
done
|
||||
while IFS= read -r job; do
|
||||
[[ -z "$job" ]] && continue
|
||||
grep -Fxq -- "$job" <(printf '%s\n' "${cronJobs[@]}") && continue
|
||||
grep -Fq -- "$appPath/kube.sh" <<< "$job" || continue
|
||||
list+=$'\n'"$fontYellow$job$fontReset"
|
||||
done <<< "$jobList"
|
||||
|
||||
printRow
|
||||
local i=0 line total
|
||||
total=$(grep -c . <<< "$list")
|
||||
while IFS= read -r line; do
|
||||
[[ -n "$line" ]] || continue
|
||||
((i++))
|
||||
num=$(printf "%${#total}d" "$i")
|
||||
printDotFix 20 "$num: $line"
|
||||
done <<< "$list"
|
||||
}
|
||||
|
||||
# Lists users in the SFTP access group.
|
||||
function cmdSftpUserList() {
|
||||
local output error
|
||||
|
||||
printRow
|
||||
|
||||
if ! run output error sftpUserList "$@"; then
|
||||
printDanger "$error"
|
||||
else
|
||||
printText "$output"
|
||||
fi
|
||||
}
|
||||
|
||||
# Enables SFTP access for a domain user.
|
||||
function cmdSftpAccessEnable() {
|
||||
local error
|
||||
|
||||
printRow
|
||||
|
||||
if ! runError error sftpAccessEnable "$@"; then
|
||||
printDotText "SFTP access enable" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "SFTP access enable" "$labelDone"
|
||||
fi
|
||||
}
|
||||
|
||||
# Disables SFTP access for a domain user by removing them from the SFTP group.
|
||||
function cmdSftpAccessDisable() {
|
||||
local error
|
||||
|
||||
printRow
|
||||
|
||||
if ! runError error sftpAccessDisable "$@"; then
|
||||
printDotText "SFTP access enable" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "SFTP access enable" "$labelDone"
|
||||
fi
|
||||
}
|
||||
|
||||
# Sets the SFTP password for a domain user from site config.
|
||||
function cmdSftpPasswordSet() {
|
||||
local error
|
||||
|
||||
printRow
|
||||
|
||||
if ! runError error sftpPasswordSet "$@"; then
|
||||
printDotText "SFTP password set" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "SFTP password set" "$labelDone"
|
||||
fi
|
||||
}
|
||||
|
||||
# [WARNING] Patches sshd_config to enable SFTP via internal-sftp with group-based chroot.
|
||||
function cmdSftpAddingSupport() {
|
||||
local error
|
||||
|
||||
printSection "Add SFTP support"
|
||||
if ! runError error sftpAddingSupport; then
|
||||
printDotText "adding" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "adding" "$labelDone"
|
||||
fi
|
||||
|
||||
printSection "Update fail2ban config"
|
||||
if ! runError error fail2banConfigUpdate; then
|
||||
printDotText "updating" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "updating" "$labelDone"
|
||||
fi
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
testLabel="[Test]"
|
||||
|
||||
# Detect current master pod by parsing INFO replication.
|
||||
function redisReplicaMasterGet() {
|
||||
local output error
|
||||
if ! run output error k3sPodList "$redisKube"; then
|
||||
appError "$error"
|
||||
return 1
|
||||
fi
|
||||
while read -r pod; do
|
||||
if ! run output error redisPodExecCli "$pod" INFO replication; then
|
||||
appError "$pod | $error"
|
||||
continue
|
||||
fi
|
||||
|
||||
if echo "$output" | grep -q "role:master"; then
|
||||
echo "$pod"
|
||||
return 0
|
||||
fi
|
||||
done < <(awk 'NF' <<<"$output")
|
||||
|
||||
appError "Master node not found"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Test MariaDB replica
|
||||
function testMariadbReplica() {
|
||||
local database=$(uuidgen)
|
||||
|
||||
if ! run output error mariadbMasterRootQuery "CREATE DATABASE \`$database\`;"; then
|
||||
printDanger "$testLabel $mariadbMasterKube | Database: $database | Create: $error"
|
||||
return 1
|
||||
else
|
||||
printSuccess "$testLabel $mariadbMasterKube | Database: $database | Create: OK"
|
||||
fi
|
||||
|
||||
local databaseMaster
|
||||
if ! run databaseMaster error mariadbMasterRootQuery "SHOW DATABASES LIKE '$database';"; then
|
||||
printDanger "$testLabel "$mariadbMasterKube" | Database list: $error"
|
||||
else
|
||||
if [[ "$databaseMaster" == "$database" ]]; then
|
||||
printSuccess "$testLabel $mariadbMasterKube | Database: $database | Exists"
|
||||
else
|
||||
printDanger "$testLabel $mariadbMasterKube | Database: $database | Not found"
|
||||
fi
|
||||
fi
|
||||
|
||||
local databaseSlave
|
||||
if ! run databaseSlave error mariadbSlaveRootQuery "SHOW DATABASES LIKE '$database';"; then
|
||||
printDanger "$testLabel "$mariadbSlaveKube" | Database list: $error"
|
||||
else
|
||||
if [[ "$databaseSlave" == "$database" ]]; then
|
||||
printSuccess "$testLabel $mariadbSlaveKube | Database: $database | Exists"
|
||||
else
|
||||
printDanger "$testLabel $mariadbSlaveKube | Database: $database | Not found"
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! run output error mariadbMasterRootQuery "DROP DATABASE \`$database\`;"; then
|
||||
printDanger "$testLabel $mariadbMasterKube | Database: $database | Drop: $error"
|
||||
return 1
|
||||
else
|
||||
printSuccess "$testLabel $mariadbMasterKube | Database: $database | Drop: OK"
|
||||
fi
|
||||
}
|
||||
|
||||
# Test Redis cluster
|
||||
function testRedisCluster() {
|
||||
local key=$(uuidgen)
|
||||
local value=$(uuidgen)
|
||||
|
||||
local podMaster podList error
|
||||
if ! run podList error k3sPodList "$redisKube"; then
|
||||
printDanger "$testLabel k3sPodList: $error"
|
||||
return 1
|
||||
fi
|
||||
if ! run podMaster error redisReplicaMasterGet; then
|
||||
printDanger "$testLabel redisReplicaMasterGet: $error"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! run output error redisPodExecCli "$podMaster" SET "$key" "$value"; then
|
||||
printDanger "$testLabel $podMaster | Key: $key | Set: $error"
|
||||
return 1
|
||||
else
|
||||
printSuccess "$testLabel $podMaster | Key: $key | Set: $value"
|
||||
fi
|
||||
|
||||
while read pod; do
|
||||
if ! run output error redisPodExecCli "$pod" GET "$key"; then
|
||||
printDanger "$testLabel $pod | Key: $key | Get: $error"
|
||||
continue
|
||||
fi
|
||||
if [[ "$output" == "$value" ]]; then
|
||||
printSuccess "$testLabel $pod | Key: $key | Get: $output"
|
||||
else
|
||||
printDanger "$testLabel $pod | Key: $key | Get: $output"
|
||||
fi
|
||||
done < <(awk 'NF' <<<"$podList")
|
||||
|
||||
if ! run output error redisPodExecCli "$podMaster" DEL "$key"; then
|
||||
printDanger "$testLabel $podMaster | Key: $key | Del: $error"
|
||||
return 1
|
||||
else
|
||||
printSuccess "$testLabel $podMaster | Key: $key | Del: OK"
|
||||
fi
|
||||
}
|
||||
|
||||
# Test create site
|
||||
function testSite() {
|
||||
local domain
|
||||
domain="test-$(stringRandom 16 'a-z').test"
|
||||
|
||||
if ! run output error cmdSiteAddDefault "$domain"; then
|
||||
printDanger "$testLabel Domain: $domain | Create: $error"
|
||||
else
|
||||
printSuccess "$testLabel Domain: $domain | Create: OK"
|
||||
cmdOpenlitespeedRestart
|
||||
|
||||
if ! run output error systemHostAdd "$domain"; then
|
||||
printDanger "$testLabel Domain: $domain | Host add: $error"
|
||||
else
|
||||
printSuccess "$testLabel Domain: $domain | Host add: OK"
|
||||
fi
|
||||
|
||||
local code
|
||||
if ! run code error curl -s -L -o /dev/null -w "%{http_code}" "$domain"; then
|
||||
printDanger "$testLabel Domain: $domain | Curl: $code: $error"
|
||||
else
|
||||
if [[ "$code" == "200" ]]; then
|
||||
printSuccess "$testLabel Domain: $domain | Curl: $code"
|
||||
else
|
||||
printWarning "$testLabel Domain: $domain | Curl: $code"
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! run output error systemHostRemove "$domain"; then
|
||||
printDanger "$testLabel Domain: $domain | Host remove: $error"
|
||||
else
|
||||
printSuccess "$testLabel Domain: $domain | Host remove: OK"
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! run output error cmdSiteRemove "$domain"; then
|
||||
printDanger "$testLabel Domain: $domain | Site remove: $error"
|
||||
else
|
||||
printSuccess "$testLabel Domain: $domain | Site remove: OK"
|
||||
fi
|
||||
cmdOpenlitespeedRestart
|
||||
}
|
||||
|
||||
# Test create site Wordpress
|
||||
function testSiteWordpress() {
|
||||
local domain
|
||||
domain="test-$(stringRandom 16 'a-z').test"
|
||||
|
||||
if ! run output error cmdSiteAddWordpress $domain; then
|
||||
printDanger "$testLabel Domain: $domain | Create: $error"
|
||||
else
|
||||
printSuccess "$testLabel Domain: $domain | Create: OK"
|
||||
cmdOpenlitespeedRestart
|
||||
|
||||
if ! run output error systemHostAdd "$domain"; then
|
||||
printDanger "$testLabel Domain: $domain | Host add: $error"
|
||||
else
|
||||
printSuccess "$testLabel Domain: $domain | Host add: OK"
|
||||
fi
|
||||
|
||||
local code
|
||||
if ! run code error curl -s -L -o /dev/null -w "%{http_code}" "$domain"; then
|
||||
printDanger "$testLabel Domain: $domain | Curl: $code: $error"
|
||||
else
|
||||
if [[ "$code" == "200" ]]; then
|
||||
printSuccess "$testLabel Domain: $domain | Curl: $code"
|
||||
else
|
||||
printWarning "$testLabel Domain: $domain | Curl: $code"
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! run output error systemHostRemove "$domain"; then
|
||||
printDanger "$testLabel Domain: $domain | Host remove: $error"
|
||||
else
|
||||
printSuccess "$testLabel Domain: $domain | Host remove: OK"
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! run output error cmdSiteRemove "$domain"; then
|
||||
printDanger "$testLabel Domain: $domain | Site remove: $error"
|
||||
else
|
||||
printSuccess "$testLabel Domain: $domain | Site remove: OK"
|
||||
fi
|
||||
cmdOpenlitespeedRestart
|
||||
}
|
||||
|
||||
|
||||
# testRedisCluster
|
||||
|
||||
|
||||
# local key="kube:haproxy:uuid" uuid
|
||||
# uuid=$(uuidgen)
|
||||
# if run output error redisExecCli -h redis-master -p 6379 SET "$key" "$uuid"; then
|
||||
# printInfo "$redisHaproxyLabel Set $key | $uuid"
|
||||
# else
|
||||
# printDanger "$redisHaproxyLabel Set $key: $error"
|
||||
# fi
|
||||
# if run output error redisExecCli -h redis-master -p 6379 GET "$key"; then
|
||||
# printInfo "$redisHaproxyLabel Get $key | $output"
|
||||
# if [[ "$uuid" != "$output" ]]; then
|
||||
# printDanger "$redisHaproxyLabel Validation failed"
|
||||
# else
|
||||
# printSuccess "$redisHaproxyLabel Validation success"
|
||||
# fi
|
||||
# else
|
||||
# printDanger "$redisHaproxyLabel Get $key: $error"
|
||||
# fi
|
||||
@@ -1,59 +0,0 @@
|
||||
UNIGMA_PHP=""
|
||||
UNIGMA_MEM=""
|
||||
UNIGMA_EXEC=""
|
||||
|
||||
function cmdUnigma() {
|
||||
local podList vhostsList error raw pod phase
|
||||
|
||||
printSection "Unigma"
|
||||
|
||||
run podList error k3sPodListStatus "$olsKube" || { printDanger "Get pods: $error"; return 1; }
|
||||
[[ -n "$podList" ]] || { printDanger "Pods not found"; return 1; }
|
||||
|
||||
# run vhostList error fileList "$olsVhostsPath" d || { printDanger "$error"; return 1; }
|
||||
run vhostList error openlitespeedConfigVhostList || { printDanger "Failed get list of vhosts: $error"; return 1; }
|
||||
while read -r vhost; do
|
||||
run raw error unigmaGetTxt "$vhost" || {
|
||||
printDotText "$vhost" "${fontGray}failed get Unigma data$fontReset";
|
||||
continue
|
||||
}
|
||||
# raw='php=8.1;mem=112M;exec=60'
|
||||
|
||||
unigmaParse "$raw" || {
|
||||
printDotText "$vhost" "$labelFail";
|
||||
printDanger "$error"
|
||||
continue
|
||||
}
|
||||
|
||||
unigmaIniSet "$vhost" "$UNIGMA_MEM" "$UNIGMA_EXEC"
|
||||
case $? in
|
||||
1) printDotText "$vhost" "$labelFail"; printDanger "$error"; continue ;;
|
||||
2)
|
||||
uid=$(domainToUid "$vhost")
|
||||
[[ -n "$uid" ]] || {
|
||||
printDotText "$vhost" "$labelFail";
|
||||
printDanger "Cannot resolve UID for: $vhost";
|
||||
continue;
|
||||
}
|
||||
|
||||
local pod phase
|
||||
while IFS='|' read -r pod phase; do
|
||||
if [[ "$phase" != "Running" ]]; then
|
||||
printDotText " $pod" "$fontRed$phase$fontReset"
|
||||
else
|
||||
runSilent openlitespeedPodExec "$pod" pkill -u "$uid" -x lsphp
|
||||
printDotText " kill$fontBlue lsphp$fontReset processes for $vhost"
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$podList")
|
||||
;;
|
||||
esac
|
||||
|
||||
unigmaPhpSet "$vhost" "$UNIGMA_PHP" || {
|
||||
printDotText "$vhost" "$labelFail";
|
||||
printDanger "$error"
|
||||
continue
|
||||
}
|
||||
|
||||
printDotText "$vhost" "$labelDone";
|
||||
done < <(awk 'NF' <<< "$vhostList")
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
wordpressLabel="[Wordpress]"
|
||||
|
||||
# Lists all WordPress users for a site.
|
||||
# $1 (domain): site domain name.
|
||||
function cmdWordpressUserList() {
|
||||
local output error
|
||||
|
||||
printRow
|
||||
|
||||
if ! run output error wordpressUserList "$@"; then
|
||||
printDanger "$error"
|
||||
else
|
||||
printText "$output"
|
||||
fi
|
||||
}
|
||||
|
||||
# Sets the password for a WordPress user.
|
||||
# $1 (domain): site domain name.
|
||||
# $2 (user): WordPress username or user ID.
|
||||
# $3 (password): new password.
|
||||
function cmdWordpressPasswordSet() {
|
||||
local output error
|
||||
|
||||
printRow
|
||||
|
||||
if ! run output error wordpressPasswordSet "$@"; then
|
||||
printDanger "$error"
|
||||
else
|
||||
printText "$output"
|
||||
fi
|
||||
}
|
||||
|
||||
# Executes an arbitrary WP-CLI command inside the site's vhost or all vhosts.
|
||||
# $1 (all|domain): site domain name or all vhosts.
|
||||
# $@ (...): WP-CLI sub-command and arguments.
|
||||
function cmdWordpressExec() {
|
||||
local target="$1"
|
||||
shift || true
|
||||
|
||||
local vhostList error
|
||||
|
||||
printRow
|
||||
|
||||
if [[ -z "$target" ]]; then
|
||||
printDanger "Target not specified";
|
||||
elif ! run vhostList error openlitespeedConfigVhostList; then
|
||||
printDanger "Cannot get vhost list: $error";
|
||||
elif [[ "$target" == "all" ]]; then
|
||||
local domain
|
||||
for domain in $vhostList; do
|
||||
printSection "$domain"
|
||||
if ! run output error wordpressExec "$domain" "$@"; then
|
||||
printDanger "$error"
|
||||
else
|
||||
printText "$output"
|
||||
fi
|
||||
done
|
||||
elif ! listContains "$target" "$vhostList"; then
|
||||
printDanger "Unknown domain: $target";
|
||||
elif ! run output error wordpressExec "$target" "$@"; then
|
||||
printDanger "$error"
|
||||
else
|
||||
printText "$output"
|
||||
fi
|
||||
}
|
||||
|
||||
function cmdWordpressSalt() {
|
||||
local target="$1"
|
||||
local vhostList error
|
||||
|
||||
printRow
|
||||
|
||||
if [[ -z "$target" ]]; then
|
||||
printDanger "Target not specified";
|
||||
elif ! run vhostList error openlitespeedConfigVhostList; then
|
||||
printDanger "Cannot get vhost list: $error";
|
||||
elif [[ "$target" == "all" ]]; then
|
||||
local domain
|
||||
for domain in $vhostList; do
|
||||
if ! runError error wordpressSalt "$domain"; then
|
||||
printDotText "$domain" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "$domain" "$labelDone"
|
||||
fi
|
||||
done
|
||||
elif ! listContains "$target" "$vhostList"; then
|
||||
printDanger "Unknown domain: $target";
|
||||
elif ! runError error wordpressSalt "$target"; then
|
||||
printDotText "$target" "$labelFail"
|
||||
printDanger "$error"
|
||||
else
|
||||
printDotText "$target" "$labelDone"
|
||||
fi
|
||||
}
|
||||
|
||||
function cmdWordpressCheck() {
|
||||
local target="$1"
|
||||
local vhostList error
|
||||
|
||||
printRow
|
||||
|
||||
if [[ -z "$target" ]]; then
|
||||
printDanger "Target not specified";
|
||||
elif ! run vhostList error openlitespeedConfigVhostList; then
|
||||
printDanger "Cannot get vhost list: $error";
|
||||
elif [[ "$target" == "all" ]]; then
|
||||
local domain
|
||||
for domain in $vhostList; do
|
||||
printSection "$domain | core"
|
||||
wordpressExec "$domain" core verify-checksums
|
||||
|
||||
printSection "$domain | plugins"
|
||||
wordpressExec "$domain" plugin verify-checksums --all
|
||||
done
|
||||
elif ! listContains "$target" "$vhostList"; then
|
||||
printDanger "Unknown domain: $target";
|
||||
else
|
||||
printSection "$target | core"
|
||||
wordpressExec "$target" core verify-checksums
|
||||
|
||||
printSection "$target | plugins"
|
||||
wordpressExec "$target" plugin verify-checksums --all
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Updates all WordPress URLs in DB to match the current domain, cleans cache and Redis.
|
||||
# $1 (domain): site domain name.
|
||||
# [$2] (abspathOld): old absolute path to replace.
|
||||
function cmdWordpressDomainUpdate() {
|
||||
local domain="$1"
|
||||
[[ -n "$domain" ]] || { printDanger "$wordpressLabel Domain not specified"; return 1; }
|
||||
local siteNew="https://$domain"
|
||||
|
||||
local abspathOld="$2"
|
||||
|
||||
local isMultiSite
|
||||
isMultiSite=$(wordpressExec "$domain" eval 'echo is_multisite() ? 1 : 0;')
|
||||
if [[ "$isMultiSite" == "1" ]]; then
|
||||
printDanger "$wordpressLabel This script is for SINGLE SITE only. Detected multisite. Abort."
|
||||
return 1
|
||||
fi
|
||||
|
||||
local siteConst homeConst siteOption homeOption
|
||||
siteConst=$(wordpressExec "$domain" eval 'echo defined("WP_SITEURL")?WP_SITEURL:"";' || true)
|
||||
siteConst=$(strTrimSlash "$siteConst")
|
||||
homeConst=$(wordpressExec "$domain" eval 'echo defined("WP_HOME")?WP_HOME:"";' || true)
|
||||
homeConst=$(strTrimSlash "$homeConst")
|
||||
siteOption=$(wordpressExec "$domain" option get siteurl 2>/dev/null || true)
|
||||
siteOption=$(strTrimSlash "$siteOption")
|
||||
homeOption=$(wordpressExec "$domain" option get home 2>/dev/null || true)
|
||||
homeOption=$(strTrimSlash "$homeOption")
|
||||
printInfo "$wordpressLabel Wordpress siteurl | Const: $siteConst | Option: $siteOption"
|
||||
printInfo "$wordpressLabel Wordpress home | Const: $homeConst | Option: $homeOption"
|
||||
|
||||
local siteOld="${siteConst:-$siteOption}"
|
||||
if [[ -z "$siteOld" ]]; then
|
||||
siteOld="${homeConst:-$homeOption}"
|
||||
fi
|
||||
if [[ -z "$siteOld" ]]; then
|
||||
appError "Could not detect siteOld (neither constants nor DB options present)."
|
||||
return 2
|
||||
fi
|
||||
local schemeOld hostOld rootOld
|
||||
schemeOld=$(printf '%s' "$siteOld" | awk -F:// '{print $1}')
|
||||
hostOld=$(printf '%s' "$siteOld" | awk -F[/:] '{print $4}')
|
||||
rootOld="$schemeOld://$hostOld"
|
||||
printInfo "$wordpressLabel Wordpress OLD | Site: $siteOld | Scheme: $schemeOld | Host: $hostOld | Root: $rootOld"
|
||||
|
||||
local sitePath homePath
|
||||
sitePath=$(wordpressExec "$domain" eval 'echo parse_url(get_option("siteurl"), PHP_URL_PATH)?:"";' || true)
|
||||
[[ "$sitePath" == "/" ]] && sitePath=""
|
||||
homePath=$(wordpressExec "$domain" eval 'echo parse_url(get_option("home"), PHP_URL_PATH)?:"";' || true)
|
||||
[[ "$homePath" == "/" ]] && homePath=""
|
||||
|
||||
local wpType="unknown"
|
||||
if [[ -z "$homePath" && -z "$sitePath" ]]; then wpType="single_root"
|
||||
elif [[ -n "$homePath" && -n "$sitePath" && "$homePath" == "$sitePath" ]]; then wpType="single_subdir"
|
||||
elif [[ -z "$homePath" && -n "$sitePath" ]]; then wpType="single_mixed"
|
||||
fi
|
||||
printInfo "$wordpressLabel Wordpress OLD | Type: $wpType"
|
||||
|
||||
local siteConstHas homeConstHas
|
||||
siteConstHas=$(wordpressExec "$domain" eval 'echo defined("WP_SITEURL")?1:0;')
|
||||
if [[ "$siteConstHas" == "1" ]]; then
|
||||
wordpressExec "$domain" config delete WP_SITEURL --type=constant || true
|
||||
fi
|
||||
homeConstHas=$(wordpressExec "$domain" eval 'echo defined("WP_HOME")?1:0;')
|
||||
if [[ "$homeConstHas" == "1" ]]; then
|
||||
wordpressExec "$domain" config delete WP_HOME --type=constant || true
|
||||
fi
|
||||
|
||||
printInfo "$wordpressLabel Update siteurl: $siteNew"
|
||||
wordpressExec "$domain" option update siteurl "$siteNew"
|
||||
|
||||
printInfo "$wordpressLabel Update home: $siteNew"
|
||||
wordpressExec "$domain" option update home "$siteNew"
|
||||
|
||||
printInfo "$wordpressLabel Replace in DB (1): $siteOld --> $siteNew"
|
||||
wordpressSearchReplace "$domain" "$siteOld" "$siteNew"
|
||||
if [[ -n "$homeOption" && "$homeOption" != "$siteOld" ]]; then
|
||||
printInfo "$wordpressLabel Replace in DB (2): $homeOption --> $siteNew"
|
||||
wordpressSearchReplace "$domain" "$homeOption" "$siteNew"
|
||||
fi
|
||||
if [[ -n "$siteOption" && "$siteOption" != "$siteOld" && "$siteOption" != "$homeOption" ]]; then
|
||||
printInfo "$wordpressLabel Replace in DB (3): $siteOption --> $siteNew"
|
||||
wordpressSearchReplace "$domain" "$siteOption" "$siteNew"
|
||||
fi
|
||||
if [[ -n "$hostOld" ]]; then
|
||||
printInfo "$wordpressLabel Replace in DB (4): //$hostOld --> $siteNew"
|
||||
wordpressSearchReplace "$domain" "//$hostOld" "$siteNew"
|
||||
fi
|
||||
if [[ "$wpType" == "single_mixed" ]]; then
|
||||
printInfo "$wordpressLabel Replace in DB (5): $rootOld --> $siteNew"
|
||||
wordpressSearchReplace "$domain" "$rootOld" "$siteNew"
|
||||
fi
|
||||
if [[ -n "$abspathOld" ]]; then
|
||||
printInfo "$wordpressLabel Replace in DB (6): $abspathOld --> $olsPodVhostsPath/$domain/www"
|
||||
wordpressSearchReplace "$domain" "$abspathOld" "$olsPodVhostsPath/$domain/www"
|
||||
fi
|
||||
|
||||
printInfo "$wordpressLabel Replace in DB (7): $hostOld --> $domain"
|
||||
wordpressSearchReplace "$domain" "$hostOld" "$domain"
|
||||
|
||||
printInfo "$wordpressLabel Delete options: upload_path/upload_url_path..."
|
||||
wordpressExec "$domain" option delete upload_path || true
|
||||
wordpressExec "$domain" option delete upload_url_path || true
|
||||
|
||||
printInfo "$wordpressLabel Clean cache..."
|
||||
wordpressExec "$domain" transient delete --all || true
|
||||
|
||||
printInfo "$wordpressLabel Redis clean..."
|
||||
redisDomainClean "$domain" || true
|
||||
}
|
||||
@@ -1,301 +0,0 @@
|
||||
workerLabel="[Worker]"
|
||||
taskStatusExecution="execution"
|
||||
taskStatusSuccess="success"
|
||||
taskStatusFailed="failed"
|
||||
taskStatusWaiting="waiting"
|
||||
taskStatusNew="new"
|
||||
|
||||
# Prepares worker agent directory and UUID file.
|
||||
function cmdWorkerPreparation() {
|
||||
printSection "Preparation..."
|
||||
|
||||
if [[ ! -f "$workerAgentPath/worker.py" ]]; then
|
||||
mkdir -p "$workerAgentPath"
|
||||
cp -f -- "$appAssetsPath/$workerKube/worker.py" "$workerAgentPath/worker.py"
|
||||
fi
|
||||
rm -f "$workerAgentPath/worker.uuid"
|
||||
fileValueGetOrCreate "$workerAgentPath/worker.uuid" "$hostUuid" >/dev/null 2>&1 || {
|
||||
printDotText "preparation" "$labelFail"
|
||||
printDanger "Generation UUID failed";
|
||||
return 1;
|
||||
}
|
||||
|
||||
printDotText "preparation" "$labelDone"
|
||||
}
|
||||
|
||||
# Renders the Worker Kubernetes YAML manifest via Helm.
|
||||
function cmdWorkerYamlRender() {
|
||||
local templateFile="templates/$workerKube.yaml"
|
||||
|
||||
printSection "Render YAML file | Helm"
|
||||
printDotText "Template" "$appAssetsPath/k3s/$templateFile"
|
||||
[[ -f "$appAssetsPath/k3s/$templateFile" ]] || {
|
||||
printDanger "Template file not found"
|
||||
return 1
|
||||
}
|
||||
|
||||
local varsFile
|
||||
varsFile=$(mktemp "/tmp/$workerKube.vars.XXXXXX.yaml") || {
|
||||
printDotText "render" "$labelFail"
|
||||
printDanger "Create temp variables file"
|
||||
return 1
|
||||
}
|
||||
printDotText "Variables" "$varsFile"
|
||||
trap 'rm -f -- "$varsFile"' RETURN
|
||||
cat > "$varsFile" <<EOF
|
||||
workerHelm: true
|
||||
namespace: $k3sNamespace
|
||||
worker: $workerKube
|
||||
workerAgentPath: $workerAgentPath
|
||||
workerTasksPath: $workerTasksPath
|
||||
workerUuid: $hostUuid
|
||||
workerName: $workerName
|
||||
workerPool: $workerPool
|
||||
workerApiUrl: $workerApiUrl
|
||||
workerApiGettingPause: $workerApiGettingPause
|
||||
workerApiSendingPause: $workerApiSendingPause
|
||||
EOF
|
||||
|
||||
printDotText "Result" "$workerYaml"
|
||||
mkdir -p -- "$(dirname -- "$workerYaml")" || return 1
|
||||
fileBackup "$workerYaml"
|
||||
helm template stack "$appAssetsPath/k3s" -f "$varsFile" --show-only "$templateFile" > "$workerYaml" || {
|
||||
printDotText "render" "$labelFail"
|
||||
printDanger "Render failed"
|
||||
return 1
|
||||
}
|
||||
|
||||
printDotText "render" "$labelDone"
|
||||
}
|
||||
|
||||
function cmdWorkerUpdate() {
|
||||
cmdWorkerYamlRender || return 1
|
||||
cmdK3sYamlApplyEx "$workerYaml" || return 1
|
||||
}
|
||||
|
||||
# Installs Worker into Kubernetes.
|
||||
function cmdWorkerInstall() {
|
||||
cmdWorkerPreparation || return 1
|
||||
cmdWorkerYamlRender || return 1
|
||||
cmdK3sYamlApplyEx "$workerYaml" || return 1
|
||||
}
|
||||
|
||||
# Uninstalls Worker Kubernetes resources.
|
||||
function cmdWorkerUninstall() {
|
||||
"$k3sCmd" kubectl delete -f "$workerYaml"
|
||||
}
|
||||
|
||||
# Executes one worker task described in an INI-like config file.
|
||||
# $1 (taskFile): path to the task config file.
|
||||
# [$2] (domain): override domain (read from task file if omitted).
|
||||
function cmdWorkerTaskHandle() {
|
||||
local taskFile="$1"
|
||||
[[ -n "$taskFile" ]] || { printDanger "$workerLabel Task file not specified"; return 1; }
|
||||
[[ -f "$taskFile" ]] || { printDanger "$workerLabel Task: $taskFile | File not found"; return 1; }
|
||||
printInfo "$workerLabel Task: $taskFile"
|
||||
|
||||
local domain="$2"
|
||||
[[ -n "$domain" ]] || domain=$(configGet "$taskFile" "domain")
|
||||
|
||||
local status
|
||||
status=$(configGet "$taskFile" "status")
|
||||
if [[ "$status" != "$taskStatusNew" && "$status" != "$taskStatusWaiting" ]]; then
|
||||
printDanger "$workerLabel Task: $taskFile | Status not '$taskStatusNew' or '$taskStatusWaiting'"
|
||||
return 1
|
||||
fi
|
||||
configSet "$taskFile" "status" "$taskStatusExecution"
|
||||
configSet "$taskFile" "start" "$(date +%s)"
|
||||
|
||||
local action
|
||||
action=$(configGet "$taskFile" "action")
|
||||
printInfo "$workerLabel Action: $action"
|
||||
case "$action" in
|
||||
"copy")
|
||||
local databaseUrl
|
||||
databaseUrl=$(configGet "$taskFile" "database_url")
|
||||
if ! run output error urlAccessible "$databaseUrl"; then
|
||||
printDanger "$workerLabel URL database archive is invalid: $databaseUrl"
|
||||
configSet "$taskFile" "status" "$taskStatusFailed"
|
||||
configSet "$taskFile" "message" "URL database archive is invalid: $databaseUrl"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local filesUrl
|
||||
filesUrl=$(configGet "$taskFile" "files_url")
|
||||
if ! run output error urlAccessible "$filesUrl"; then
|
||||
printDanger "$workerLabel URL files archive is invalid: $filesUrl"
|
||||
configSet "$taskFile" "status" "$taskStatusFailed"
|
||||
configSet "$taskFile" "message" "URL files archive is invalid: $filesUrl"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -z "$domain" ]]; then
|
||||
domain=$(domainsListMark "$domainsList")
|
||||
fi
|
||||
if [[ -z "$domain" ]]; then
|
||||
printDanger "$workerLabel Domain not specified or no domains available"
|
||||
configSet "$taskFile" "status" "$taskStatusFailed"
|
||||
configSet "$taskFile" "message" "Domain not specified or no domains available"
|
||||
return 1
|
||||
else
|
||||
configSet "$taskFile" "domain" "$domain"
|
||||
fi
|
||||
|
||||
configSet "$taskFile" "status" "$taskStatusExecution"
|
||||
|
||||
mkdir -p "$workerFilesPath"
|
||||
local fileName filesLocal databaseLocal
|
||||
fileName="${domain}_$(uuidgen)"
|
||||
|
||||
databaseLocal="$workerFilesPath/$(urlLocalFileGen "$databaseUrl" "$fileName")"
|
||||
printInfo "$workerLabel Download archive database --> $databaseLocal"
|
||||
if ! run output error fileDownload "$databaseUrl" "$databaseLocal"; then
|
||||
printDanger "$error"
|
||||
configSet "$taskFile" "status" "$taskStatusFailed"
|
||||
configSet "$taskFile" "message" "$error"
|
||||
return 1
|
||||
fi
|
||||
|
||||
filesLocal="$workerFilesPath/$(urlLocalFileGen "$filesUrl" "$fileName")"
|
||||
printInfo "$workerLabel Download archive files --> $filesLocal"
|
||||
if ! run output error fileDownload "$filesUrl" "$filesLocal"; then
|
||||
printDanger "$error"
|
||||
configSet "$taskFile" "status" "$taskStatusFailed"
|
||||
configSet "$taskFile" "message" "$error"
|
||||
return 1
|
||||
fi
|
||||
|
||||
printInfo "$workerLabel Create site: $domain"
|
||||
if ! run output error cmdSiteAddWordpress "$domain" "$filesLocal" "$databaseLocal"; then
|
||||
printDanger "$error"
|
||||
configSet "$taskFile" "status" "$taskStatusFailed"
|
||||
configSet "$taskFile" "message" "Error create site: $error"
|
||||
return 1
|
||||
else
|
||||
cmdWordpressDomainUpdate "$domain"
|
||||
cmdOpenlitespeedRestart
|
||||
fi
|
||||
;;
|
||||
"pack")
|
||||
local output error
|
||||
local uuid="worker_$(uuidgen)"
|
||||
|
||||
if ! run vhostList error openlitespeedConfigVhostList; then
|
||||
printDanger "Vhost list: $error"
|
||||
configSet "$taskFile" "status" "$taskStatusFailed"
|
||||
configSet "$taskFile" "message" "Error getting list of virtual hosts"
|
||||
return 1
|
||||
elif ! listContains "$domain" "$vhostList"; then
|
||||
printDanger "Vhost list: Domain is not exists in OpenLiteSpeed config"
|
||||
configSet "$taskFile" "status" "$taskStatusFailed"
|
||||
configSet "$taskFile" "message" "Domain is not exists in OpenLiteSpeed config"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! run output error backupSiteFiles "$domain" "$olsVhostsPath/$domain/www/$uuid"; then
|
||||
printDanger "$error"
|
||||
configSet "$taskFile" "status" "$taskStatusFailed"
|
||||
configSet "$taskFile" "message" "Error create files archive: $error"
|
||||
return 1
|
||||
fi
|
||||
if ! run output error backupSiteDatabase "$domain" "$olsVhostsPath/$domain/www/$uuid.sql"; then
|
||||
printDanger "$error"
|
||||
configSet "$taskFile" "status" "$taskStatusFailed"
|
||||
configSet "$taskFile" "message" "Error create database archive: $error"
|
||||
return 1
|
||||
fi
|
||||
|
||||
configSet "$taskFile" "files_url" "https://$domain/$uuid.tar.gz"
|
||||
configSet "$taskFile" "database_url" "https://$domain/$uuid.sql.tar.gz"
|
||||
;;
|
||||
"delete")
|
||||
printSuccess "$workerLabel Action: Site delete..."
|
||||
cmdSiteRemove "$domain"
|
||||
cmdOpenlitespeedRestart
|
||||
;;
|
||||
"update")
|
||||
domain=$(configGet "$taskFile" "domain")
|
||||
if [[ -z "$domain" ]]; then
|
||||
printDanger "$workerLabel Domain not specified"
|
||||
configSet "$taskFile" "status" "$taskStatusFailed"
|
||||
configSet "$taskFile" "message" "Domain not specified"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local domainList
|
||||
domainList=$(postfixDomainList)
|
||||
if ! listContains "$domain" "$domainList"; then
|
||||
printDanger "$workerLabel Domain $domain not found"
|
||||
configSet "$taskFile" "status" "$taskStatusFailed"
|
||||
configSet "$taskFile" "message" "Domain $domain not found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local postfixForwardTo
|
||||
postfixForwardTo=$(configGet "$taskFile" "mail_forward_to")
|
||||
siteConfigSet "$domain" "postfixForwardTo" "$postfixForwardTo"
|
||||
postfixVirtualAliasSet "@$domain" "$postfixForwardTo"
|
||||
postfixPostmapRebuild
|
||||
;;
|
||||
*)
|
||||
printDanger "$workerLabel Unknown action: $action"
|
||||
configSet "$taskFile" "status" "$taskStatusFailed"
|
||||
configSet "$taskFile" "message" "Unknown action: $action"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
configSet "$taskFile" "status" "$taskStatusSuccess"
|
||||
printSuccess "$workerLabel Action: $action | Success"
|
||||
|
||||
local taskFileBase
|
||||
taskFileBase=$(basename -- "$taskFile")
|
||||
mkdir -p "$appDataPath/worker-task" || true
|
||||
cp -f -- "$taskFile" "$appDataPath/worker-task/$taskFileBase" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Scans task dir and runs handler for tasks with status new or waiting.
|
||||
function cmdWorkerObserver() {
|
||||
local fileList error
|
||||
run fileList error fileList "$workerTasksPath" f || { appError "$error"; return 1; }
|
||||
while IFS= read -r file; do
|
||||
local taskFile="$workerTasksPath/$file"
|
||||
local status
|
||||
status=$(configGet "$taskFile" "status")
|
||||
if [[ "$status" == "$taskStatusNew" || "$status" == "$taskStatusWaiting" ]]; then
|
||||
printSuccess "$workerLabel $file | Status: $status | Starting..."
|
||||
cmdWorkerTaskHandle "$taskFile"
|
||||
else
|
||||
printInfo "$workerLabel $file | Status: $status | Skip"
|
||||
fi
|
||||
done < <(awk 'NF' <<< "$fileList")
|
||||
}
|
||||
|
||||
# Lists worker tasks with action, status, and lifetime in seconds.
|
||||
function cmdWorkerTaskList() {
|
||||
local fileList error
|
||||
run fileList error fileList "$workerTasksPath" f || {
|
||||
printDanger "$error";
|
||||
return 1;
|
||||
}
|
||||
|
||||
local count=0
|
||||
while IFS= read -r file; do
|
||||
((count++))
|
||||
local task=${file%.task}
|
||||
local action status start
|
||||
action=$(configGet "$workerTasksPath/$file" "action")
|
||||
status=$(configGet "$workerTasksPath/$file" "status")
|
||||
start=$(configGet "$workerTasksPath/$file" "start")
|
||||
local live="?"
|
||||
if [[ -n "$start" ]]; then
|
||||
live=$(( $(date +%s) - start ))
|
||||
fi
|
||||
|
||||
printSection "$task"
|
||||
printDotText "file" "$file"
|
||||
printDotText "action" "$action"
|
||||
printDotText "status" "$status"
|
||||
printDotText "live, sec" "$live"
|
||||
done < <(awk 'NF' <<< "$fileList")
|
||||
}
|
||||
@@ -1,726 +0,0 @@
|
||||
# Searches for entries (files and directories) in the specified local directory.
|
||||
#
|
||||
# $1 (path): The path to the directory on the local machine where the search for entries will be performed.
|
||||
# $2 (type): Type entry (f: files, d: directories, ...).
|
||||
function fileList() {
|
||||
local path="${1-}"
|
||||
local type="${2-}"
|
||||
local args=(-mindepth 1 -maxdepth 1)
|
||||
|
||||
[[ -n "$path" ]] || { appError "Path not specified"; return 1; }
|
||||
[[ -d "$path" ]] || { appError "Directory not found: $path"; return 1; }
|
||||
|
||||
if [[ -n "$type" ]]; then
|
||||
case "$type" in
|
||||
f|d|l|p|s|b|c) args+=(-type "$type") ;;
|
||||
*) appError "Unsupported file type: $type"; return 1 ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
find "$path" "${args[@]}" -printf '%f\n'
|
||||
}
|
||||
|
||||
# Searches for entries (files or directories) in the specified directory on an FTP server.
|
||||
#
|
||||
# $1 (path): The path to the directory on the FTP server where the search for entries will be performed.
|
||||
# $2 (type): Type entry (f: files, d: directories, ...).
|
||||
function ftpFileList() {
|
||||
local path="$1"
|
||||
local type="$2"
|
||||
local output error
|
||||
|
||||
run output error curl -sS -u "$ftpUser:$ftpPass" "ftp://$ftpHost:$ftpPort$ftpPath$path/" --connect-timeout 10 \
|
||||
|| { appError "$error"; return 1; }
|
||||
|
||||
case "$type" in
|
||||
f) printf '%s\n' "$output" | grep -v '^d' | awk '{print $NF}' ;;
|
||||
d) printf '%s\n' "$output" | grep '^d' | awk '{print $NF}' ;;
|
||||
*) printf '%s\n' "$output" | awk '{print $NF}' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Searches for entries (files or directories) in the specified directory on a remote server via SSH.
|
||||
#
|
||||
# $1 (path): The path to the directory on the remote server where the search for entries will be performed.
|
||||
# $2 (type): Type entry (f: files, d: directories, ...).
|
||||
function sshFileList() {
|
||||
local path="$1"
|
||||
local type="$2"
|
||||
local typeArg="${type:+-type $type}"
|
||||
local output error
|
||||
|
||||
run output error ssh -i "$sshKeyFile" "$sshUser@$sshHost" \
|
||||
"find '${sshPath}${path}' -maxdepth 1 -mindepth 1 ${typeArg} -exec basename {} \\;" \
|
||||
|| { appError "$error"; return 1; }
|
||||
|
||||
printf '%s\n' "$output"
|
||||
}
|
||||
|
||||
# Searches for entries (files or directories) in the specified directory on external storage.
|
||||
function storageFileList() {
|
||||
local -A storageFunc=(
|
||||
[ftp]=ftpFileList
|
||||
[rsync]=ftpFileList
|
||||
[ssh]=sshFileList
|
||||
)
|
||||
[[ -n "${storageFunc[$storageType]:-}" ]] || { appError "Unknown value storageType: $storageType"; return 1; }
|
||||
|
||||
"${storageFunc[$storageType]}" "$@"
|
||||
}
|
||||
|
||||
# Cleans a specified directory on a remote server using rsync.
|
||||
#
|
||||
# $1 (path): The path to the directory to be cleaned on the remote server.
|
||||
#
|
||||
# The function creates a temporary empty directory on the local machine and syncs it with the remote directory
|
||||
# using rsync with the `--delete` option, which removes any files on the remote side that are not present in
|
||||
# the local directory. After completion, the temporary directory is deleted. An error message is displayed in case of failure.
|
||||
function rsyncDirectoryClean() {
|
||||
local path="$1"
|
||||
[[ -n "$path" ]] || { appError "Path not specified"; return 1; }
|
||||
|
||||
local emptyPath="$appDataPath/$(uuidgen)"
|
||||
local error
|
||||
runError error mkdir -p "$emptyPath" || { appError "Failed to create temp directory: $error"; return 1; }
|
||||
|
||||
runError error rsync -r --delete --password-file="$rsyncPassFile" "$emptyPath/" rsync://"$rsyncUri$path/" \
|
||||
|| { rm -rf "$emptyPath"; appError "Failed to clean remote directory: $error"; return 1; }
|
||||
rm -rf "$emptyPath"
|
||||
}
|
||||
|
||||
# Deletes a directory on an FTP server.
|
||||
#
|
||||
# $1 (path): The path to the directory on the FTP server to be deleted (in ftpPath).
|
||||
function ftpDirectoryDelete() {
|
||||
local path="$1"
|
||||
[[ -n "$path" ]] || { appError "Path not specified"; return 1; }
|
||||
|
||||
local error
|
||||
runError error curl -u "$ftpUser:$ftpPass" -Q "-RMD $ftpPath$path" "ftp://$ftpHost:$ftpPort" --connect-timeout 10 \
|
||||
|| { appError "$error"; return 1; }
|
||||
}
|
||||
|
||||
# Deletes a directory on a remote server via SSH (in sshPath).
|
||||
#
|
||||
# $1 (path): The path to the directory to be deleted.
|
||||
function sshDirectoryDelete() {
|
||||
local path="$1"
|
||||
[[ -n "$path" ]] || { appError "Path not specified"; return 1; }
|
||||
|
||||
local error
|
||||
runError error ssh -i "$sshKeyFile" "$sshUser@$sshHost" "rm -rf '${sshPath}${path}'" \
|
||||
|| { appError "$error"; return 1; }
|
||||
}
|
||||
|
||||
function fileAtomicWrite() {
|
||||
local file="$1"
|
||||
local content="$2"
|
||||
local path tmp
|
||||
path="$(dirname "$file")"
|
||||
|
||||
mkdir -p "$path"
|
||||
tmp="$(mktemp "$path/.tmp.XXXXXX")"
|
||||
printf '%s\n' "$content" > "$tmp"
|
||||
mv -f "$tmp" "$file"
|
||||
}
|
||||
|
||||
# Create a timestamped backup copy of a file if it exists.
|
||||
# $1 (file): path to the file to back up.
|
||||
# [$2] (suffix): custom suffix for the backup file (defaults to a timestamp .bak suffix).
|
||||
function fileBackup() {
|
||||
local file="$1"
|
||||
if [[ -z "$file" ]]; then
|
||||
appError "File not specified"
|
||||
return 1
|
||||
fi
|
||||
if [[ ! -f "$file" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local suffix="${2:-.$(date +%F_%H-%M-%S).bak}"
|
||||
|
||||
cp -p -- "$file" "$file$suffix" || {
|
||||
appError "Failed to backup file: $file"
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Download remote file to a local path.
|
||||
function fileDownload() {
|
||||
local remoteFile="$1"
|
||||
local localFile="$2"
|
||||
local retries="${3:-5}"
|
||||
local delay="${4:-3}"
|
||||
|
||||
[[ -n "$remoteFile" ]] || { appError "Remote file not specified"; return 1; }
|
||||
[[ -n "$localFile" ]] || { appError "Local file not specified"; return 1; }
|
||||
|
||||
mkdir -p "$(dirname "$localFile")" || { appError "Failed to create folder"; return 1; }
|
||||
|
||||
local tmpFile="${localFile}.part"
|
||||
local i rc curlError lastError
|
||||
for ((i = 1; i <= retries; i++)); do
|
||||
curlError=$(curl -kfsSL --http1.1 --max-redirs 10 --connect-timeout 30 --speed-time 60 --speed-limit 1024 -C - -o "$tmpFile" "$remoteFile" 2>&1)
|
||||
rc=$?
|
||||
if [[ $rc -eq 0 ]]; then
|
||||
mv -f "$tmpFile" "$localFile" || { appError "Failed to move downloaded file"; return 1; }
|
||||
return 0
|
||||
fi
|
||||
|
||||
lastError="$curlError"
|
||||
[[ $rc -ne 33 ]] || rm -f "$tmpFile" # rc=33: server doesn't support resume, restart from scratch
|
||||
|
||||
sleep "$delay"
|
||||
done
|
||||
|
||||
lastError="${lastError//$'\r'/ }"
|
||||
lastError="${lastError//$'\n'/ }"
|
||||
|
||||
appError "Failed to download file after $retries attempts | $lastError"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Creates an archive from a source file or directory.
|
||||
#
|
||||
# Supported archive formats are selected by the target file extension:
|
||||
# .zip, .tar.gz, or .tgz.
|
||||
#
|
||||
# $1 (source): Source file or directory to archive.
|
||||
# $2 (target): Target archive file path.
|
||||
#
|
||||
# Returns:
|
||||
# 0 on success, 1 on validation or archive creation failure.
|
||||
function archiveCreate() {
|
||||
local source="$1"
|
||||
if [[ -z "$source" ]]; then
|
||||
appError "Source path not specified"
|
||||
return 1
|
||||
fi
|
||||
if [[ ! -e "$source" ]]; then
|
||||
appError "Source path not found: $source"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local target="$2"
|
||||
if [[ -z "$target" ]]; then
|
||||
appError "Target file not specified"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local compressProgram="${3:-}"
|
||||
|
||||
local sourcePath sourceBase targetPath targetBase output rc
|
||||
sourcePath=$(dirname -- "$source")
|
||||
sourceBase=$(basename -- "$source")
|
||||
targetPath=$(dirname -- "$target")
|
||||
targetBase=$(basename -- "$target")
|
||||
|
||||
case "$targetBase" in
|
||||
*.zip|*.tar.gz|*.tgz)
|
||||
;;
|
||||
*)
|
||||
appError "Unknown type archive: $targetBase"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
mkdir -p -- "$targetPath" || {
|
||||
appError "Failed to create directory: $targetPath"
|
||||
return 1
|
||||
}
|
||||
|
||||
case "$targetBase" in
|
||||
*.zip)
|
||||
if [[ -d "$source" ]]; then
|
||||
output=$(cd "$source" && zip -qr "$target" . 2>&1)
|
||||
else
|
||||
output=$(cd "$sourcePath" && zip -qr "$target" "$sourceBase" 2>&1)
|
||||
fi
|
||||
rc=$?
|
||||
[[ $rc -le 1 ]] || { appError "Error creating ZIP (rc=$rc)${output:+: $output}"; return 1; }
|
||||
[[ $rc -ne 1 ]] || [[ -z "$output" ]] || appError "Warning creating ZIP${output:+: $output}"
|
||||
;;
|
||||
*.tar.gz|*.tgz)
|
||||
local -a tarCompressFlags
|
||||
if [[ -n "$compressProgram" ]]; then
|
||||
tarCompressFlags=("--use-compress-program=$compressProgram")
|
||||
else
|
||||
tarCompressFlags=("-z")
|
||||
fi
|
||||
if [[ -d "$source" ]]; then
|
||||
output=$(tar --warning=no-file-changed -c "${tarCompressFlags[@]}" -f "$target" -C "$source" . 2>&1)
|
||||
else
|
||||
output=$(tar --warning=no-file-changed -c "${tarCompressFlags[@]}" -f "$target" -C "$sourcePath" "$sourceBase" 2>&1)
|
||||
fi
|
||||
rc=$?
|
||||
[[ $rc -le 1 ]] || { appError "Error creating TAR (rc=$rc)${output:+: $output}"; return 1; }
|
||||
[[ $rc -ne 1 ]] || [[ -z "$output" ]] || appError "Warning creating TAR${output:+: $output}"
|
||||
;;
|
||||
esac
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Extracts an archive into a target directory.
|
||||
#
|
||||
# Supported archive formats are selected by the source file extension:
|
||||
# .zip, .tar.gz, or .tgz.
|
||||
#
|
||||
# $1 (source): Source archive file path.
|
||||
# $2 (target): Target directory where archive contents should be extracted.
|
||||
#
|
||||
# Returns:
|
||||
# 0 on success, 1 on validation or extraction failure.
|
||||
function archiveExtract() {
|
||||
local source="$1"
|
||||
if [[ -z "$source" ]]; then
|
||||
appError "Source archive not specified"
|
||||
return 1
|
||||
fi
|
||||
if [[ ! -f "$source" ]]; then
|
||||
appError "Source archive not found: $source"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local target="$2"
|
||||
if [[ -z "$target" ]]; then
|
||||
appError "Target directory not specified"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local sourceBase output rc
|
||||
sourceBase=$(basename -- "$source")
|
||||
|
||||
case "$sourceBase" in
|
||||
*.zip|*.tar.gz|*.tgz)
|
||||
;;
|
||||
*)
|
||||
appError "Unknown type archive: $sourceBase"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
mkdir -p -- "$target" || {
|
||||
appError "Failed to create directory: $target"
|
||||
return 1
|
||||
}
|
||||
|
||||
case "$sourceBase" in
|
||||
*.zip)
|
||||
output=$(unzip -oq "$source" -d "$target" 2>&1)
|
||||
rc=$?
|
||||
if [[ $rc -ne 0 ]]; then
|
||||
appError "Error extracting ZIP: $output"
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
*.tar.gz|*.tgz)
|
||||
output=$(tar -xzf "$source" -C "$target" 2>&1)
|
||||
rc=$?
|
||||
if [[ $rc -ne 0 ]]; then
|
||||
appError "Error extracting TAR: $output"
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Retrieves the size of the specified path (file or directory) in b/kb/mb/gb.
|
||||
#
|
||||
# $1 (path): The path to the file or directory whose size is to be retrieved.
|
||||
# $2 (unit): Unit.
|
||||
# $3 (precision): Precision.
|
||||
function pathSize() {
|
||||
local path="$1"
|
||||
local unit="${2:-}"
|
||||
local precision="${3:-0}"
|
||||
local divisor="1"
|
||||
case "${unit,,}" in
|
||||
kb) divisor="1024" ;;
|
||||
mb) divisor="1048576" ;;
|
||||
gb) divisor="1073741824" ;;
|
||||
esac
|
||||
[[ -n "$path" ]] || { appError "Path not specified"; return 1; }
|
||||
|
||||
local output error bytes
|
||||
if [[ -d "$path" ]]; then
|
||||
run output error du -sb "$path" || { appError "$error"; return 1; }
|
||||
bytes=$(printf '%s\n' "$output" | cut -f1)
|
||||
elif [[ -f "$path" ]]; then
|
||||
run output error stat -c %s "$path" || { appError "$error"; return 1; }
|
||||
bytes="$output"
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
|
||||
LC_NUMERIC=C awk -v bytes="$bytes" -v divisor="$divisor" -v precision="$precision" 'BEGIN { printf "%.*f\n", precision, bytes / divisor }'
|
||||
}
|
||||
|
||||
function ftpPathSize() {
|
||||
local path="$1"
|
||||
local unit="${2:-}"
|
||||
local precision="${3:-0}"
|
||||
local divisor="1"
|
||||
case "${unit,,}" in
|
||||
kb) divisor="1024" ;;
|
||||
mb) divisor="1048576" ;;
|
||||
gb) divisor="1073741824" ;;
|
||||
esac
|
||||
|
||||
local output error total
|
||||
if run output error curl -sS -u "$ftpUser:$ftpPass" "ftp://$ftpHost:$ftpPort$ftpPath$path/" --connect-timeout 10; then
|
||||
total=0
|
||||
local line file size
|
||||
while IFS= read -r line; do
|
||||
file=$(awk '{print $NF}' <<< "$line")
|
||||
[[ -n "$file" && "$file" != "." && "$file" != ".." ]] || continue
|
||||
|
||||
if [[ "$line" =~ ^d ]]; then
|
||||
size=$(ftpPathSize "$path/$file") || return 1
|
||||
else
|
||||
size=$(awk '{print $5}' <<< "$line")
|
||||
[[ "$size" =~ ^[0-9]+$ ]] || continue
|
||||
fi
|
||||
(( total += size ))
|
||||
done <<< "$output"
|
||||
else
|
||||
local parent name
|
||||
parent=$(dirname "$path")
|
||||
name=$(basename "$path")
|
||||
run output error curl -sS -u "$ftpUser:$ftpPass" "ftp://$ftpHost:$ftpPort$ftpPath$parent/" --connect-timeout 10 || {
|
||||
appError "$error"; return 1
|
||||
}
|
||||
total=$(awk -v f="$name" '$NF == f {print $5}' <<< "$output")
|
||||
[[ "$total" =~ ^[0-9]+$ ]] || { appError "Cannot determine size of: $path"; return 1; }
|
||||
fi
|
||||
|
||||
LC_NUMERIC=C awk -v b="$total" -v d="$divisor" -v p="$precision" 'BEGIN { printf "%.*f\n", p, b/d }'
|
||||
}
|
||||
|
||||
# Return the contents of a file if it exists and is non-empty, otherwise write the given value to it and return it.
|
||||
# $1 (file): path to the file.
|
||||
# $2 (value): value to write when the file is missing or empty.
|
||||
function fileValueGetOrCreate() {
|
||||
local file="${1-}"
|
||||
local value="${2-}"
|
||||
|
||||
if [[ -z "$file" ]]; then
|
||||
appError "File not specified"
|
||||
return 1
|
||||
fi
|
||||
if [[ -s "$file" ]]; then
|
||||
cat "$file"
|
||||
return $?
|
||||
fi
|
||||
|
||||
if [[ -z "$value" ]]; then
|
||||
appError "Value not specified"
|
||||
return 1
|
||||
fi
|
||||
|
||||
mkdir -p -- "$(dirname -- "$file")" || {
|
||||
appError "Failed to create folder for: $file"
|
||||
return 1
|
||||
}
|
||||
|
||||
install -o root -g root -m 600 /dev/null "$file" || {
|
||||
appError "Failed to create file: $file"
|
||||
return 1
|
||||
}
|
||||
|
||||
printf '%s\n' "$value" > "$file" || {
|
||||
appError "Failed to save value: $file"
|
||||
return 1
|
||||
}
|
||||
|
||||
printf '%s\n' "$value"
|
||||
}
|
||||
|
||||
# Retrieves or generates a password and stores it in the specified file.
|
||||
#
|
||||
# $1: path to the file where the password should be stored.
|
||||
# [$2+]: additional parameters are passed to the stringRandom function for generating the password (optional).
|
||||
function filePasswordGetOrCreate() {
|
||||
local file="${1-}"
|
||||
if [[ -z "$file" ]]; then
|
||||
appError "File not specified"
|
||||
return 1
|
||||
fi
|
||||
shift || true
|
||||
|
||||
local value
|
||||
if [[ -s "$file" ]]; then
|
||||
cat "$file"
|
||||
return $?
|
||||
fi
|
||||
value="$(strRandom "$@")" || {
|
||||
appError "Failed to generate password"
|
||||
return 1
|
||||
}
|
||||
|
||||
fileValueGetOrCreate "$file" "$value"
|
||||
}
|
||||
|
||||
# Get value by key from "KEY=VALUE" content or file (returns first match)
|
||||
function configGet() {
|
||||
local file="${1-}"
|
||||
local key="${2-}"
|
||||
local line value
|
||||
|
||||
if [[ -z "$file" ]]; then
|
||||
appError "Config file not specified"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -z "$key" ]]; then
|
||||
appError "Config key not specified"
|
||||
return 1
|
||||
fi
|
||||
|
||||
[[ -f "$file" ]] || {
|
||||
printf '\n'
|
||||
return 0
|
||||
}
|
||||
|
||||
line="$(awk -F= -v key="$key" '$1 == key { print; exit }' "$file")" || {
|
||||
printf '\n';
|
||||
return 0;
|
||||
}
|
||||
[[ "$line" == "$key="* ]] || {
|
||||
printf '\n'
|
||||
return 0
|
||||
}
|
||||
|
||||
value="${line#*=}"
|
||||
value="$(sed -E 's/[[:space:]]+$//' <<<"$value")"
|
||||
|
||||
printf '%s\n' "$value"
|
||||
}
|
||||
|
||||
# Ensure key=value is present in file (remove previous key lines, then append).
|
||||
function configSet() {
|
||||
local file="${1-}"
|
||||
local key="${2-}"
|
||||
local value="${3-}"
|
||||
local tmp
|
||||
|
||||
if [[ -z "$file" ]]; then
|
||||
appError "Config file not specified"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -z "$key" ]]; then
|
||||
appError "Config key not specified"
|
||||
return 1
|
||||
fi
|
||||
|
||||
value="${value//$'\r'/ }"
|
||||
value="${value//$'\n'/ }"
|
||||
|
||||
mkdir -p -- "$(dirname -- "$file")" || {
|
||||
appError "Failed to create folder for: $file"
|
||||
return 1
|
||||
}
|
||||
|
||||
[[ -f "$file" ]] || install -o root -g root -m 600 /dev/null "$file" || {
|
||||
appError "Failed to create file: $file"
|
||||
return 1
|
||||
}
|
||||
|
||||
tmp="$(mktemp)" || return 1
|
||||
|
||||
awk -F= -v key="$key" '$1 != key' "$file" > "$tmp" || true
|
||||
|
||||
if [[ -n "$value" ]]; then
|
||||
printf '%s=%s\n' "$key" "$value" >> "$tmp" || {
|
||||
rm -f -- "$tmp"
|
||||
appError "Failed to write config value: $key"
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
|
||||
cat "$tmp" > "$file" || {
|
||||
rm -f -- "$tmp"
|
||||
appError "Failed to update config file: $file"
|
||||
return 1
|
||||
}
|
||||
|
||||
rm -f -- "$tmp"
|
||||
}
|
||||
|
||||
# Get param from config or set via configSet if missing
|
||||
function configGetOrSet() {
|
||||
local file="${1-}"
|
||||
local key="${2-}"
|
||||
local value="${3-}"
|
||||
local current
|
||||
|
||||
current="$(configGet "$file" "$key")" || return 1
|
||||
if [[ -n "$current" ]]; then
|
||||
printf '%s\n' "$current"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -z "$value" ]]; then
|
||||
appError "Config value not specified: $key"
|
||||
return 1
|
||||
fi
|
||||
|
||||
configSet "$file" "$key" "$value" || return 1
|
||||
printf '%s\n' "$value"
|
||||
}
|
||||
|
||||
# Get param from config or create via configSet (+gen stringRandom if missing value) if missing
|
||||
function configGetOrCreate() {
|
||||
local file="${1-}"
|
||||
local key="${2-}"
|
||||
shift 2 || true
|
||||
|
||||
local value current
|
||||
|
||||
current="$(configGet "$file" "$key")" || return 1
|
||||
if [[ -n "$current" ]]; then
|
||||
printf '%s\n' "$current"
|
||||
return 0
|
||||
fi
|
||||
|
||||
value="$(strRandom "$@")" || {
|
||||
appError "Failed to generate config value: $key"
|
||||
return 1
|
||||
}
|
||||
|
||||
configSet "$file" "$key" "$value" || return 1
|
||||
printf '%s\n' "$value"
|
||||
}
|
||||
|
||||
# Check that no line in a file exceeds the specified maximum length, printing offending lines to stderr.
|
||||
# $1 (file): path to the file to check.
|
||||
# $2 (max): maximum allowed line length in characters.
|
||||
function fileCheckLineLength() {
|
||||
local file="$1"
|
||||
local max="$2"
|
||||
local line len num=0 found=0
|
||||
|
||||
[[ -f "$file" ]] || { appError "File not found: $file"; return 1; }
|
||||
[[ "$max" =~ ^[0-9]+$ ]] || { appError "Invalid max line length: $max"; return 1; }
|
||||
|
||||
while IFS= read -r line || [[ -n $line ]]; do
|
||||
((num++))
|
||||
len=${#line}
|
||||
|
||||
if (( len > max )); then
|
||||
printf 'Line %d exceeds %d characters (%d)\n' "$num" "$max" "$len" >&2
|
||||
found=1
|
||||
fi
|
||||
done < "$file"
|
||||
|
||||
return "$found"
|
||||
}
|
||||
|
||||
function fileSignatureDiffList() {
|
||||
local path="$1" type="$2" mask="$3"
|
||||
[[ -n "$path" && -n "$type" && -n "$mask" ]] || { appError "Missing argument(s)"; return 1; }
|
||||
[[ -d "$path" ]] || { appError "Path not found: $path"; return 1; }
|
||||
|
||||
find "$path" -type "$type" -printf '%m:%u:%g:%p\n' 2>/dev/null | grep -vE "$mask:"
|
||||
return 0
|
||||
}
|
||||
|
||||
function fileSignatureDiff() {
|
||||
local path="$1" mask="$2" sign
|
||||
[[ -n "$path" && -n "$mask" ]] || { appError "Missing argument(s)"; return 1; }
|
||||
[[ -d "$path" ]] || { appError "Path not found: $path"; return 1; }
|
||||
|
||||
sign="$(stat -c '%a:%U:%G' "$path")"
|
||||
[[ "$sign" == "$mask" ]] || printf "%s\n" "$sign:$path";
|
||||
}
|
||||
|
||||
function fileSignatureAclDiff() {
|
||||
local path="$1" mask="$2"
|
||||
[[ -n "$path" && -n "$mask" ]] || { printDanger "Missing argument(s)"; return 1; }
|
||||
[[ -d "$path" ]] || { printDanger "Path not found: $path"; return 1; }
|
||||
|
||||
local acl unexpected
|
||||
acl=$(getfacl -p "$path" 2>/dev/null)
|
||||
unexpected=$(grep -vE "^(#|\$|(default:)?((user|group|mask)::|other::---|$mask\$))" <<< "$acl" | paste -sd '|')
|
||||
|
||||
grep -qxF "$mask" <<< "$acl" || unexpected+="${unexpected:+|}missing:$mask"
|
||||
grep -qxF "default:$mask" <<< "$acl" || unexpected+="${unexpected:+|}missing:default:$mask"
|
||||
|
||||
[[ -z "$unexpected" ]] || printf "%s\n" "$unexpected:$path"
|
||||
}
|
||||
|
||||
function fileSignatureCheck() {
|
||||
local output error
|
||||
run output error fileSignatureDiff "$@" || return 1
|
||||
[[ -z "$output" ]]
|
||||
}
|
||||
|
||||
function fileSignatureAclCheck() {
|
||||
local output error
|
||||
run output error fileSignatureAclDiff "$@" || return 1
|
||||
[[ -z "$output" ]]
|
||||
}
|
||||
|
||||
# Checking the availability of a file (URL) for download.
|
||||
# Check if a URL is accessible and print the final effective URL on success.
|
||||
# $1 (url): the URL to check.
|
||||
function urlAccessible() {
|
||||
local url="$1" code final
|
||||
if [[ -z "$url" ]]; then
|
||||
appError "URL not specified"
|
||||
return 1
|
||||
fi
|
||||
|
||||
final=$(curl -ksSL --max-redirs 10 --range 0-0 -o /dev/null -w "%{url_effective} %{http_code}" "$url") || return 1
|
||||
code="${final##* }" # http code
|
||||
final="${final% *}" # final URL
|
||||
if [[ "$code" =~ ^[23][0-9]{2}$ ]]; then
|
||||
printf "%s" "$final"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Fetch and print the HTTP status code for the given URL.
|
||||
# $1 (url): the URL to request.
|
||||
function urlCode() {
|
||||
local url="$1" code
|
||||
if [[ -z "$url" ]]; then
|
||||
appError "URL not specified"
|
||||
return 1
|
||||
fi
|
||||
|
||||
code=$(curl -ksSL --max-redirs 10 -o /dev/null -w "%{http_code}" "$url") || return 1
|
||||
printf "%s" "$code"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Derive a local filename from a URL, optionally using a custom base name.
|
||||
# $1 (url): the source URL to extract the filename from.
|
||||
# [$2] (localFileName): custom base name; if given, the URL extension is appended to it.
|
||||
function urlLocalFileGen() {
|
||||
local url="$1" code
|
||||
if [[ -z "$url" ]]; then
|
||||
appError "URL not specified"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local localFileName="$2"
|
||||
|
||||
name="${url##*/}"
|
||||
name="${name%%\?*}"
|
||||
base="${name%%.*}"
|
||||
ext="${name#*.}"
|
||||
|
||||
if [[ -z "$localFileName" ]]; then
|
||||
printf '%s' "$name"
|
||||
else
|
||||
printf '%s' "$localFileName.$ext"
|
||||
fi
|
||||
}
|
||||
@@ -1,361 +0,0 @@
|
||||
# Remove a single trailing slash from a string.
|
||||
# $1 (s): input string.
|
||||
function strTrimSlash() {
|
||||
local s="${1-}"
|
||||
s="${s%/}"
|
||||
printf '%s' "$s"
|
||||
}
|
||||
|
||||
# Generates a random password with a specified length and character set.
|
||||
#
|
||||
# $1 (length): length of the password (defaults to 32 characters).
|
||||
# $2 (charset): string of characters to use for generating the password (defaults to "A-Za-z0-9@#$%^*_+=[]{}:").
|
||||
function strRandom() {
|
||||
local length="${1:-32}"
|
||||
local charset="${2:-"A-Za-z0-9_.-"}"
|
||||
LC_ALL=C tr -dc "$charset" < /dev/urandom | head -c "$length"
|
||||
}
|
||||
|
||||
# Format a number with thousands separators (space-separated groups).
|
||||
# $1 (number): the number to format.
|
||||
function numFormat() {
|
||||
printf '%s\n' "${1-}" | sed ':a;s/\B[0-9]\{3\}\>/ &/;ta'
|
||||
}
|
||||
|
||||
# Check if a value exists in an array passed as remaining arguments.
|
||||
# $1 (needle): value to search for.
|
||||
# $2 (items): array elements to search in (passed as individual arguments).
|
||||
function arrayContains() {
|
||||
local needle="${1-}"
|
||||
shift || true
|
||||
|
||||
local item
|
||||
for item in "$@"; do
|
||||
[[ "$item" == "$needle" ]] && return 0
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Return success if $value exists as full line in multiline $list
|
||||
function listContains() {
|
||||
local value="${1-}"
|
||||
local list="${2-}"
|
||||
|
||||
[[ -n "$value" ]] || return 1
|
||||
|
||||
grep -Fxq -- "$value" <<<"$list"
|
||||
}
|
||||
|
||||
# Calculate the difference in seconds between two datetime strings.
|
||||
# $1 (from): start datetime string (accepted by date -d).
|
||||
# $2 (to): end datetime string (accepted by date -d).
|
||||
function timeDiff() {
|
||||
local from="${1-}"
|
||||
local to="${2-}"
|
||||
local t1 t2
|
||||
|
||||
[[ -n "$from" ]] || { appError "time_from not specified"; return 1; }
|
||||
[[ -n "$to" ]] || { appError "time_to not specified"; return 1; }
|
||||
|
||||
t1="$(date -d "$from" +%s)" || { appError "invalid time_from: $from"; return 1; }
|
||||
t2="$(date -d "$to" +%s)" || { appError "invalid time_to: $to"; return 1; }
|
||||
|
||||
printf '%s\n' "$(( t2 - t1 ))"
|
||||
}
|
||||
|
||||
# Normalize a domain name: lowercase, strip whitespace and trailing dot, then IDN-encode.
|
||||
# $1 (domain): raw domain string to normalize.
|
||||
function domainPrepare() {
|
||||
local domain="${1-}"
|
||||
|
||||
domain="${domain,,}"
|
||||
domain="${domain//[[:space:]]/}"
|
||||
domain="${domain%.}"
|
||||
|
||||
LC_ALL=C.UTF-8 idn2 <<<"$domain"
|
||||
}
|
||||
|
||||
# Checks if the given string is a valid domain.
|
||||
#
|
||||
# $1 (domain): a string representing the domain.
|
||||
function domainValidate() {
|
||||
local domain="${1-}"
|
||||
|
||||
(( ${#domain} >= 4 )) || { appError "Domain name is too short: $domain"; return 1; }
|
||||
(( ${#domain} <= 253 )) || { appError "Domain name is too long: $domain"; return 1; }
|
||||
[[ "$domain" =~ ^([A-Za-z0-9]([-A-Za-z0-9]{0,61}[A-Za-z0-9])?\.)+([A-Za-z]{2,63}|xn--[A-Za-z0-9-]{2,59})$ ]] || { appError "Invalid domain name format: $domain"; return 1; }
|
||||
}
|
||||
|
||||
# Validate a domain name and reject reserved names.
|
||||
# $1 (domain): domain name to check.
|
||||
function domainCheck() {
|
||||
local domain="${1-}"
|
||||
local reserved=("root" "user")
|
||||
|
||||
arrayContains "$domain" "${reserved[@]}" && { appError "Reserved domain name: $domain"; return 1; }
|
||||
domainValidate "$domain"
|
||||
}
|
||||
|
||||
# Generate username and groupname from domain
|
||||
function domainToUser() {
|
||||
local domain="${1-}"
|
||||
local base hash
|
||||
|
||||
base="$(printf '%s' "$domain" \
|
||||
| tr '[:upper:]' '[:lower:]' \
|
||||
| sed -E 's/[^a-z0-9-]+/-/g; s/-{2,}/-/g; s/^-//; s/-$//')"
|
||||
|
||||
hash="$(printf '%s' "$domain$hostSalt" \
|
||||
| sha256sum \
|
||||
| awk '{print substr($1,1,8)}')"
|
||||
|
||||
printf '%s' "${base:0:21}-${hash}"
|
||||
}
|
||||
|
||||
function domainToUid() {
|
||||
local domain="${1-}"
|
||||
local username uid
|
||||
|
||||
if [[ -z "$domain" ]]; then
|
||||
appError "Domain not specified"
|
||||
return 1
|
||||
fi
|
||||
|
||||
username=$(domainToUser "$domain")
|
||||
if [[ -z "$username" ]]; then
|
||||
appError "Cannot compute username for: $domain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
uid=$(id -u "$username" 2>/dev/null)
|
||||
if [[ -z "$uid" ]]; then
|
||||
appError "No such user: $username"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "$uid" == "0" ]]; then
|
||||
appError "Refusing to return root UID for: $domain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf '%s' "$uid"
|
||||
}
|
||||
|
||||
function domainToGid() {
|
||||
local domain="${1-}"
|
||||
local username gid
|
||||
|
||||
if [[ -z "$domain" ]]; then
|
||||
appError "Domain not specified"
|
||||
return 1
|
||||
fi
|
||||
|
||||
username=$(domainToUser "$domain")
|
||||
if [[ -z "$username" ]]; then
|
||||
appError "Cannot compute username for: $domain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
gid=$(id -g "$username" 2>/dev/null)
|
||||
if [[ -z "$gid" ]]; then
|
||||
appError "No such user: $username"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "$gid" == "0" ]]; then
|
||||
appError "Refusing to return root GID for: $domain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf '%s' "$gid"
|
||||
}
|
||||
|
||||
# Generate random string from domain
|
||||
function domainToRandom() {
|
||||
local domain="${1-}"
|
||||
local maxLen="${2:-256}"
|
||||
local tailLen="${3:-8}"
|
||||
local base tail baseLen
|
||||
|
||||
base="$(printf '%s' "$domain" \
|
||||
| tr '[:upper:]' '[:lower:]' \
|
||||
| sed -E 's/[^a-z0-9]+/_/g; s/_{2,}/_/g; s/^_//; s/_$//')"
|
||||
|
||||
tail="$(strRandom "$tailLen" 'a-z0-9')" || return 1
|
||||
|
||||
baseLen=$(( maxLen - tailLen - 1 ))
|
||||
(( baseLen < 1 )) && baseLen=1
|
||||
|
||||
printf '%s' "${base:0:baseLen}_${tail}"
|
||||
}
|
||||
|
||||
# Run a command and capture its stdout and stderr into named variables.
|
||||
# $1 (outVar): name of the variable to receive stdout.
|
||||
# $2 (errVar): name of the variable to receive stderr.
|
||||
# $3 (cmd): command and arguments to execute.
|
||||
function run() {
|
||||
local -n __runOut="$1"
|
||||
local -n __runError="$2"
|
||||
shift 2
|
||||
|
||||
local stdoutTmp stderrTmp status
|
||||
stdoutTmp=$(mktemp) || return 1
|
||||
stderrTmp=$(mktemp) || { rm -f "$stdoutTmp"; return 1; }
|
||||
|
||||
"$@" >"$stdoutTmp" 2>"$stderrTmp"
|
||||
status=$?
|
||||
|
||||
__runOut=$(<"$stdoutTmp")
|
||||
__runError=$(<"$stderrTmp")
|
||||
|
||||
rm -f "$stdoutTmp" "$stderrTmp"
|
||||
return "$status"
|
||||
}
|
||||
|
||||
# Run command, discard stdout
|
||||
function runError() {
|
||||
local -n __runErrorErr="$1"
|
||||
shift || true
|
||||
|
||||
local __runErrorOutput __runErrorError status
|
||||
run __runErrorOutput __runErrorError "$@"
|
||||
status=$?
|
||||
|
||||
if [[ -n "$__runErrorError" ]]; then
|
||||
__runErrorErr="$__runErrorError"
|
||||
else
|
||||
__runErrorErr="$__runErrorOutput"
|
||||
fi
|
||||
|
||||
return "$status"
|
||||
}
|
||||
|
||||
# Run a command, discarding output, and print an error message on failure.
|
||||
# $1 (cmd): command and arguments to execute.
|
||||
function runFail() {
|
||||
local __runFailErr
|
||||
runError __runFailErr "$@"
|
||||
local status=$?
|
||||
[[ $status -eq 0 ]] || appError "$__runFailErr"
|
||||
return $status
|
||||
}
|
||||
|
||||
# Run a command and discard all stdout and stderr output.
|
||||
# $1 (cmd): command and arguments to execute.
|
||||
function runSilent() {
|
||||
local output error
|
||||
run output error "$@"
|
||||
return $?
|
||||
}
|
||||
|
||||
# Run a shell command string (with operator support) and capture stdout and stderr into named variables.
|
||||
# $1 (outVar): name of the variable to receive stdout.
|
||||
# $2 (errVar): name of the variable to receive stderr.
|
||||
# $3 (cmd): command and arguments, including shell operators (|, &&, ;, etc.).
|
||||
function runShell() {
|
||||
local -n __out="$1"
|
||||
local -n __err="$2"
|
||||
shift 2
|
||||
|
||||
local stdoutTmp stderrTmp status
|
||||
stdoutTmp=$(mktemp) || return 1
|
||||
stderrTmp=$(mktemp) || { rm -f "$stdoutTmp"; return 1; }
|
||||
|
||||
# Arguments: > >> < | || & && ; ( ) convert to operators.
|
||||
local cmd="" arg
|
||||
for arg in "$@"; do
|
||||
if [[ "$arg" =~ ^([0-9]?>|[0-9]?>>|[0-9]?<|\||\|\||&&|;|\(|\))$ ]]; then
|
||||
cmd+=" $arg"
|
||||
else
|
||||
cmd+=" $(printf '%q' "$arg")"
|
||||
fi
|
||||
done
|
||||
|
||||
(
|
||||
set -o pipefail
|
||||
eval -- "$cmd"
|
||||
) >"$stdoutTmp" 2>"$stderrTmp"
|
||||
status=$?
|
||||
|
||||
__out=$(<"$stdoutTmp")
|
||||
__err=$(<"$stderrTmp")
|
||||
|
||||
rm -f "$stdoutTmp" "$stderrTmp"
|
||||
return "$status"
|
||||
}
|
||||
|
||||
# Converts notation (28Gi, 512Mi, 1Ki, 4Gb, ...) to bytes; returns -1 for empty input.
|
||||
function sizeToBytes() {
|
||||
local v="$1"
|
||||
case "$v" in
|
||||
*Ti) echo $(( ${v%Ti} * 1099511627776 )) ;;
|
||||
*Gi) echo $(( ${v%Gi} * 1073741824 )) ;;
|
||||
*Mi) echo $(( ${v%Mi} * 1048576 )) ;;
|
||||
*Ki) echo $(( ${v%Ki} * 1024 )) ;;
|
||||
*Tb) echo $(( ${v%Tb} * 1000000000000 )) ;;
|
||||
*Gb) echo $(( ${v%Gb} * 1000000000 )) ;;
|
||||
*Mb) echo $(( ${v%Mb} * 1000000 )) ;;
|
||||
*Kb) echo $(( ${v%Kb} * 1000 )) ;;
|
||||
"") echo -1 ;;
|
||||
*) echo "$v" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
#=========================================================================
|
||||
|
||||
# Comment out the first non-empty, non-commented line in the domains list file.
|
||||
# Prints the domain name on success, returns 1 if no domain available.
|
||||
function domainsListMark() {
|
||||
local file="$1"
|
||||
local line domain num=0 lineNum=0
|
||||
|
||||
[[ -f "$file" ]] || { appError "Domains list file not found: $file"; return 1; }
|
||||
while IFS= read -r line; do
|
||||
(( num++ ))
|
||||
[[ -z "$line" || "$line" == \#* ]] && continue
|
||||
domain="$line"
|
||||
lineNum=$num
|
||||
break
|
||||
done < "$file"
|
||||
|
||||
[[ $lineNum -eq 0 ]] && return 1
|
||||
|
||||
sed -i "${lineNum}s/^/#/" "$file"
|
||||
printf '%s\n' "$domain"
|
||||
}
|
||||
|
||||
# Remove the '#' prefix from the specified domain line in the domains list file.
|
||||
function domainsListUnmark() {
|
||||
local file="$1"
|
||||
local domain="$2"
|
||||
local line num=0 lineNum=0
|
||||
|
||||
[[ -f "$file" ]] || { appError "Domains list file not found: $file"; return 1; }
|
||||
[[ -n "$domain" ]] || { appError "Domain not specified"; return 1; }
|
||||
while IFS= read -r line; do
|
||||
(( num++ ))
|
||||
[[ "$line" == "#${domain}" ]] && { lineNum=$num; break; }
|
||||
done < "$file"
|
||||
|
||||
[[ $lineNum -eq 0 ]] && { appError "Domain not marked in list: $domain"; return 1; }
|
||||
|
||||
sed -i "${lineNum}s/^#//" "$file"
|
||||
}
|
||||
|
||||
|
||||
# Sends an email with the specified subject and body.
|
||||
#
|
||||
# $1 (mailSubject): string containing the subject of the email.
|
||||
# $2 (mailBody): string containing the body of the email.
|
||||
function emailSend() {
|
||||
local mailSubject="$1"
|
||||
local mailBody="$2"
|
||||
|
||||
[[ -n "$mailSubject" ]] || { appError "Subject not specified"; return 1; }
|
||||
[[ -n "$mailBody" ]] || { appError "Body email not specified"; return 1; }
|
||||
|
||||
local result
|
||||
result=$(printf 'Subject:%s\nFrom:%s\nTo:%s\n\n%s' "$mailSubject" "$mailFrom" "$mailTo" "$mailBody" | msmtp "$mailTo" 2>&1)
|
||||
[[ $? -eq 0 ]] || { appError "$result"; return 1; }
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user