Clean up pasarguard
This commit is contained in:
@@ -1,212 +0,0 @@
|
|||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: pasarguard-scripts-ingress
|
|
||||||
labels:
|
|
||||||
app: pasarguard-node-ingress
|
|
||||||
data:
|
|
||||||
init-uuid-ingress.sh: |
|
|
||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
echo "Started"
|
|
||||||
# NODE_NAME is already set via environment variable
|
|
||||||
NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
|
|
||||||
|
|
||||||
# Get DNS name from node label xray-public-address
|
|
||||||
DNS_NAME=$(kubectl get node "${NODE_NAME}" -o jsonpath='{.metadata.labels.xray-public-address}')
|
|
||||||
|
|
||||||
if [ -z "${DNS_NAME}" ]; then
|
|
||||||
echo "ERROR: Node ${NODE_NAME} does not have label 'xray-public-address'"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Node: ${NODE_NAME}"
|
|
||||||
echo "DNS Name from label: ${DNS_NAME}"
|
|
||||||
|
|
||||||
# Use DNS name for ConfigMap name to ensure uniqueness
|
|
||||||
CONFIGMAP_NAME="node-uuid-ingress-${DNS_NAME//./-}"
|
|
||||||
|
|
||||||
echo "Checking ConfigMap: ${CONFIGMAP_NAME}"
|
|
||||||
|
|
||||||
# Check if ConfigMap exists and get UUID
|
|
||||||
if kubectl get configmap "${CONFIGMAP_NAME}" -n "${NAMESPACE}" &>/dev/null; then
|
|
||||||
echo "ConfigMap exists, reading UUID..."
|
|
||||||
API_KEY=$(kubectl get configmap "${CONFIGMAP_NAME}" -n "${NAMESPACE}" -o jsonpath='{.data.API_KEY}')
|
|
||||||
|
|
||||||
if [ -z "${API_KEY}" ]; then
|
|
||||||
echo "UUID not found in ConfigMap, generating new one..."
|
|
||||||
API_KEY=$(cat /proc/sys/kernel/random/uuid)
|
|
||||||
kubectl patch configmap "${CONFIGMAP_NAME}" -n "${NAMESPACE}" --type merge -p "{\"data\":{\"API_KEY\":\"${API_KEY}\"}}"
|
|
||||||
else
|
|
||||||
echo "Using existing UUID from ConfigMap"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "ConfigMap does not exist, creating new one..."
|
|
||||||
API_KEY=$(cat /proc/sys/kernel/random/uuid)
|
|
||||||
kubectl create configmap "${CONFIGMAP_NAME}" -n "${NAMESPACE}" \
|
|
||||||
--from-literal=API_KEY="${API_KEY}" \
|
|
||||||
--from-literal=NODE_NAME="${NODE_NAME}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Save UUID and node info to shared volume for the main container
|
|
||||||
echo -n "${API_KEY}" > /shared/api-key
|
|
||||||
echo -n "${NODE_NAME}" > /shared/node-name
|
|
||||||
echo -n "${CONFIGMAP_NAME}" > /shared/configmap-name
|
|
||||||
echo "UUID initialized: ${API_KEY}"
|
|
||||||
echo "Node name: ${NODE_NAME}"
|
|
||||||
echo "ConfigMap: ${CONFIGMAP_NAME}"
|
|
||||||
|
|
||||||
# Create Certificate for this node using DNS name from label
|
|
||||||
CERT_NAME="pasarguard-node-ingress-${DNS_NAME//./-}"
|
|
||||||
|
|
||||||
echo "Creating Certificate: ${CERT_NAME} for ${DNS_NAME}"
|
|
||||||
|
|
||||||
# Check if Certificate already exists
|
|
||||||
if ! kubectl get certificate "${CERT_NAME}" -n "${NAMESPACE}" &>/dev/null; then
|
|
||||||
echo "Certificate does not exist, creating..."
|
|
||||||
cat <<EOF | kubectl apply -f -
|
|
||||||
apiVersion: cert-manager.io/v1
|
|
||||||
kind: Certificate
|
|
||||||
metadata:
|
|
||||||
name: ${CERT_NAME}
|
|
||||||
namespace: ${NAMESPACE}
|
|
||||||
spec:
|
|
||||||
secretName: ${CERT_NAME}-tls
|
|
||||||
issuerRef:
|
|
||||||
name: letsencrypt
|
|
||||||
kind: ClusterIssuer
|
|
||||||
dnsNames:
|
|
||||||
- ${DNS_NAME}
|
|
||||||
EOF
|
|
||||||
else
|
|
||||||
echo "Certificate already exists"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Wait for certificate to be ready
|
|
||||||
|
|
||||||
echo "Waiting for certificate to be ready..."
|
|
||||||
for i in {1..600}; do
|
|
||||||
if kubectl get secret "${CERT_NAME}-tls" -n "${NAMESPACE}" &>/dev/null; then
|
|
||||||
echo "Certificate secret is ready!"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
echo "Waiting for certificate... ($i/600)"
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
|
|
||||||
if ! kubectl get secret "${CERT_NAME}-tls" -n "${NAMESPACE}" &>/dev/null; then
|
|
||||||
echo "WARNING: Certificate secret not ready after 600 seconds"
|
|
||||||
else
|
|
||||||
# Extract certificate and key from secret to shared volume
|
|
||||||
echo "Extracting certificate and key..."
|
|
||||||
kubectl get secret "${CERT_NAME}-tls" -n "${NAMESPACE}" -o jsonpath='{.data.tls\.crt}' | base64 -d > /shared/tls.crt
|
|
||||||
kubectl get secret "${CERT_NAME}-tls" -n "${NAMESPACE}" -o jsonpath='{.data.tls\.key}' | base64 -d > /shared/tls.key
|
|
||||||
echo "Certificate and key extracted successfully."
|
|
||||||
cat /shared/tls.crt
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create ClusterIP Service for this node (pod selector based)
|
|
||||||
NODE_SHORT_NAME="${NODE_NAME%%.*}"
|
|
||||||
SERVICE_NAME="${NODE_SHORT_NAME}-ingress"
|
|
||||||
|
|
||||||
echo "Creating Service: ${SERVICE_NAME} for node ${NODE_NAME} (short: ${NODE_SHORT_NAME})"
|
|
||||||
|
|
||||||
# Create Service with pod selector including node name
|
|
||||||
cat <<EOF | kubectl apply -f -
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: ${SERVICE_NAME}
|
|
||||||
namespace: ${NAMESPACE}
|
|
||||||
labels:
|
|
||||||
app: pasarguard-node-ingress
|
|
||||||
node: ${NODE_NAME}
|
|
||||||
spec:
|
|
||||||
type: ClusterIP
|
|
||||||
selector:
|
|
||||||
app: pasarguard-node-ingress
|
|
||||||
node-name: ${NODE_SHORT_NAME}
|
|
||||||
ports:
|
|
||||||
- name: proxy
|
|
||||||
port: 443
|
|
||||||
protocol: TCP
|
|
||||||
targetPort: 443
|
|
||||||
- name: api
|
|
||||||
port: 62050
|
|
||||||
protocol: TCP
|
|
||||||
targetPort: 62050
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo "Service created: ${SERVICE_NAME}.${NAMESPACE}.svc.cluster.local"
|
|
||||||
|
|
||||||
# Create IngressRouteTCP for this DNS name with TLS passthrough
|
|
||||||
INGRESS_NAME="pasarguard-tcp-${DNS_NAME//./-}"
|
|
||||||
|
|
||||||
echo "Creating IngressRouteTCP: ${INGRESS_NAME} for ${DNS_NAME}"
|
|
||||||
|
|
||||||
cat <<EOF | kubectl apply -f -
|
|
||||||
apiVersion: traefik.io/v1alpha1
|
|
||||||
kind: IngressRouteTCP
|
|
||||||
metadata:
|
|
||||||
name: ${INGRESS_NAME}
|
|
||||||
namespace: ${NAMESPACE}
|
|
||||||
labels:
|
|
||||||
app: pasarguard-node-ingress
|
|
||||||
node: ${NODE_NAME}
|
|
||||||
spec:
|
|
||||||
entryPoints:
|
|
||||||
- websecure
|
|
||||||
routes:
|
|
||||||
- match: HostSNI(\`${DNS_NAME}\`)
|
|
||||||
services:
|
|
||||||
- name: ${SERVICE_NAME}
|
|
||||||
port: 443
|
|
||||||
tls:
|
|
||||||
passthrough: true
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo "IngressRouteTCP created: ${INGRESS_NAME}"
|
|
||||||
echo "Traffic to ${DNS_NAME}:443 will be routed to ${SERVICE_NAME}:443"
|
|
||||||
|
|
||||||
# Create second IngressRouteTCP for API port 62051
|
|
||||||
INGRESS_API_NAME="pasarguard-api-${DNS_NAME//./-}"
|
|
||||||
|
|
||||||
echo "Creating IngressRouteTCP for API: ${INGRESS_API_NAME} for ${DNS_NAME}:62051"
|
|
||||||
|
|
||||||
cat <<EOF | kubectl apply -f -
|
|
||||||
apiVersion: traefik.io/v1alpha1
|
|
||||||
kind: IngressRouteTCP
|
|
||||||
metadata:
|
|
||||||
name: ${INGRESS_API_NAME}
|
|
||||||
namespace: ${NAMESPACE}
|
|
||||||
labels:
|
|
||||||
app: pasarguard-node-ingress
|
|
||||||
node: ${NODE_NAME}
|
|
||||||
spec:
|
|
||||||
entryPoints:
|
|
||||||
- pasarguard-api
|
|
||||||
routes:
|
|
||||||
- match: HostSNI(\`${DNS_NAME}\`)
|
|
||||||
services:
|
|
||||||
- name: ${SERVICE_NAME}
|
|
||||||
port: 62050
|
|
||||||
tls:
|
|
||||||
passthrough: true
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo "IngressRouteTCP API created: ${INGRESS_API_NAME}"
|
|
||||||
echo "Traffic to ${DNS_NAME}:62051 will be routed to ${SERVICE_NAME}:62050"
|
|
||||||
|
|
||||||
pasarguard-start.sh: |
|
|
||||||
#!/bin/sh
|
|
||||||
# Read API_KEY from shared volume created by init container
|
|
||||||
if [ -f /shared/api-key ]; then
|
|
||||||
export API_KEY=$(cat /shared/api-key)
|
|
||||||
echo "Loaded API_KEY from shared volume"
|
|
||||||
else
|
|
||||||
echo "WARNING: API_KEY file not found, using default"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd /app
|
|
||||||
exec ./main
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ServiceAccount
|
|
||||||
metadata:
|
|
||||||
name: pasarguard-node-ingress
|
|
||||||
labels:
|
|
||||||
app: pasarguard-node-ingress
|
|
||||||
---
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
kind: Role
|
|
||||||
metadata:
|
|
||||||
name: pasarguard-node-ingress-configmap
|
|
||||||
labels:
|
|
||||||
app: pasarguard-node-ingress
|
|
||||||
rules:
|
|
||||||
- apiGroups: [""]
|
|
||||||
resources: ["configmaps"]
|
|
||||||
verbs: ["get", "list", "create", "update", "patch"]
|
|
||||||
- apiGroups: ["cert-manager.io"]
|
|
||||||
resources: ["certificates"]
|
|
||||||
verbs: ["get", "list", "create", "update", "patch", "delete"]
|
|
||||||
- apiGroups: [""]
|
|
||||||
resources: ["secrets"]
|
|
||||||
verbs: ["get", "list"]
|
|
||||||
- apiGroups: [""]
|
|
||||||
resources: ["services", "endpoints"]
|
|
||||||
verbs: ["get", "list", "create", "update", "patch", "delete"]
|
|
||||||
- apiGroups: ["traefik.io", "traefik.containo.us"]
|
|
||||||
resources: ["ingressroutetcps"]
|
|
||||||
verbs: ["get", "list", "create", "update", "patch", "delete"]
|
|
||||||
- apiGroups: [""]
|
|
||||||
resources: ["pods"]
|
|
||||||
verbs: ["get", "list", "patch", "update"]
|
|
||||||
---
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
kind: RoleBinding
|
|
||||||
metadata:
|
|
||||||
name: pasarguard-node-ingress-configmap
|
|
||||||
labels:
|
|
||||||
app: pasarguard-node-ingress
|
|
||||||
roleRef:
|
|
||||||
apiGroup: rbac.authorization.k8s.io
|
|
||||||
kind: Role
|
|
||||||
name: pasarguard-node-ingress-configmap
|
|
||||||
subjects:
|
|
||||||
- kind: ServiceAccount
|
|
||||||
name: pasarguard-node-ingress
|
|
||||||
---
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
kind: ClusterRole
|
|
||||||
metadata:
|
|
||||||
name: pasarguard-node-ingress-reader
|
|
||||||
labels:
|
|
||||||
app: pasarguard-node-ingress
|
|
||||||
rules:
|
|
||||||
- apiGroups: [""]
|
|
||||||
resources: ["nodes"]
|
|
||||||
verbs: ["get", "list"]
|
|
||||||
---
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
kind: ClusterRoleBinding
|
|
||||||
metadata:
|
|
||||||
name: pasarguard-node-ingress-reader
|
|
||||||
labels:
|
|
||||||
app: pasarguard-node-ingress
|
|
||||||
roleRef:
|
|
||||||
apiGroup: rbac.authorization.k8s.io
|
|
||||||
kind: ClusterRole
|
|
||||||
name: pasarguard-node-ingress-reader
|
|
||||||
subjects:
|
|
||||||
- kind: ServiceAccount
|
|
||||||
name: pasarguard-node-ingress
|
|
||||||
namespace: pasarguard
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: DaemonSet
|
|
||||||
metadata:
|
|
||||||
name: pasarguard-node-ingress
|
|
||||||
labels:
|
|
||||||
app: pasarguard-node-ingress
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: pasarguard-node-ingress
|
|
||||||
revisionHistoryLimit: 3
|
|
||||||
updateStrategy:
|
|
||||||
type: RollingUpdate
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: pasarguard-node-ingress
|
|
||||||
spec:
|
|
||||||
serviceAccountName: pasarguard-node-ingress
|
|
||||||
affinity:
|
|
||||||
nodeAffinity:
|
|
||||||
requiredDuringSchedulingIgnoredDuringExecution:
|
|
||||||
nodeSelectorTerms:
|
|
||||||
- matchExpressions:
|
|
||||||
- key: xray-public-address
|
|
||||||
operator: Exists
|
|
||||||
initContainers:
|
|
||||||
- name: label-pod
|
|
||||||
image: bitnami/kubectl:latest
|
|
||||||
env:
|
|
||||||
- name: POD_NAME
|
|
||||||
valueFrom:
|
|
||||||
fieldRef:
|
|
||||||
fieldPath: metadata.name
|
|
||||||
- name: POD_NAMESPACE
|
|
||||||
valueFrom:
|
|
||||||
fieldRef:
|
|
||||||
fieldPath: metadata.namespace
|
|
||||||
- name: NODE_NAME
|
|
||||||
valueFrom:
|
|
||||||
fieldRef:
|
|
||||||
fieldPath: spec.nodeName
|
|
||||||
command:
|
|
||||||
- /bin/bash
|
|
||||||
- -c
|
|
||||||
- |
|
|
||||||
# Add node label to pod
|
|
||||||
NODE_SHORT=$(echo ${NODE_NAME} | cut -d. -f1)
|
|
||||||
kubectl label pod ${POD_NAME} -n ${POD_NAMESPACE} node-name=${NODE_SHORT} --overwrite
|
|
||||||
- name: init-uuid
|
|
||||||
image: bitnami/kubectl:latest
|
|
||||||
env:
|
|
||||||
- name: GODEBUG
|
|
||||||
value: "x509sha1=1"
|
|
||||||
- name: NODE_NAME
|
|
||||||
valueFrom:
|
|
||||||
fieldRef:
|
|
||||||
fieldPath: spec.nodeName
|
|
||||||
command:
|
|
||||||
- /bin/bash
|
|
||||||
- /scripts/init-uuid-ingress.sh
|
|
||||||
volumeMounts:
|
|
||||||
- name: shared-data
|
|
||||||
mountPath: /shared
|
|
||||||
- name: scripts
|
|
||||||
mountPath: /scripts
|
|
||||||
containers:
|
|
||||||
- name: pasarguard-node
|
|
||||||
image: 'pasarguard/node:v0.1.3'
|
|
||||||
imagePullPolicy: Always
|
|
||||||
command:
|
|
||||||
- /bin/sh
|
|
||||||
- /scripts/pasarguard-start.sh
|
|
||||||
ports:
|
|
||||||
- name: api
|
|
||||||
containerPort: 62050
|
|
||||||
protocol: TCP
|
|
||||||
- name: proxy
|
|
||||||
containerPort: 443
|
|
||||||
protocol: TCP
|
|
||||||
env:
|
|
||||||
- name: NODE_NAME
|
|
||||||
valueFrom:
|
|
||||||
fieldRef:
|
|
||||||
fieldPath: spec.nodeName
|
|
||||||
- name: NODE_HOST
|
|
||||||
value: "0.0.0.0"
|
|
||||||
- name: SERVICE_PORT
|
|
||||||
value: "62050"
|
|
||||||
- name: SERVICE_PROTOCOL
|
|
||||||
value: "grpc"
|
|
||||||
- name: DEBUG
|
|
||||||
value: "true"
|
|
||||||
- name: SSL_CERT_FILE
|
|
||||||
value: "/shared/tls.crt"
|
|
||||||
- name: SSL_KEY_FILE
|
|
||||||
value: "/shared/tls.key"
|
|
||||||
- name: XRAY_EXECUTABLE_PATH
|
|
||||||
value: "/usr/local/bin/xray"
|
|
||||||
- name: XRAY_ASSETS_PATH
|
|
||||||
value: "/usr/local/share/xray"
|
|
||||||
- name: API_KEY
|
|
||||||
value: "change-this-to-a-secure-uuid"
|
|
||||||
livenessProbe:
|
|
||||||
tcpSocket:
|
|
||||||
port: 62050
|
|
||||||
initialDelaySeconds: 30
|
|
||||||
periodSeconds: 10
|
|
||||||
timeoutSeconds: 5
|
|
||||||
failureThreshold: 3
|
|
||||||
readinessProbe:
|
|
||||||
tcpSocket:
|
|
||||||
port: 62050
|
|
||||||
initialDelaySeconds: 10
|
|
||||||
periodSeconds: 5
|
|
||||||
timeoutSeconds: 3
|
|
||||||
failureThreshold: 3
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: "128Mi"
|
|
||||||
cpu: "300m"
|
|
||||||
limits:
|
|
||||||
memory: "512Mi"
|
|
||||||
cpu: "1000m"
|
|
||||||
volumeMounts:
|
|
||||||
- name: shared-data
|
|
||||||
mountPath: /shared
|
|
||||||
readOnly: false
|
|
||||||
- name: scripts
|
|
||||||
mountPath: /scripts
|
|
||||||
volumes:
|
|
||||||
- name: shared-data
|
|
||||||
emptyDir: {}
|
|
||||||
- name: scripts
|
|
||||||
configMap:
|
|
||||||
name: pasarguard-scripts-ingress
|
|
||||||
defaultMode: 0755
|
|
||||||
@@ -9,6 +9,3 @@ resources:
|
|||||||
- ./certificate.yaml
|
- ./certificate.yaml
|
||||||
- ./configmap-scripts.yaml
|
- ./configmap-scripts.yaml
|
||||||
- ./servicemonitor.yaml
|
- ./servicemonitor.yaml
|
||||||
- ./configmap-scripts-ingress.yaml
|
|
||||||
# - ./daemonset-ingress.yaml
|
|
||||||
# - ./traefik-pasarguard-entrypoint.yaml
|
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: traefik
|
|
||||||
namespace: kube-system
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: traefik
|
|
||||||
args:
|
|
||||||
- --entryPoints.metrics.address=:9100/tcp
|
|
||||||
- --entryPoints.traefik.address=:8080/tcp
|
|
||||||
- --entryPoints.web.address=:8000/tcp
|
|
||||||
- --entryPoints.websecure.address=:8443/tcp
|
|
||||||
- --entryPoints.pasarguard-api.address=:62051/tcp
|
|
||||||
- --api.dashboard=true
|
|
||||||
- --ping=true
|
|
||||||
- --metrics.prometheus=true
|
|
||||||
- --metrics.prometheus.entrypoint=metrics
|
|
||||||
- --providers.kubernetescrd
|
|
||||||
- --providers.kubernetescrd.allowEmptyServices=true
|
|
||||||
- --providers.kubernetesingress
|
|
||||||
- --providers.kubernetesingress.allowEmptyServices=true
|
|
||||||
- --providers.kubernetesingress.ingressendpoint.publishedservice=kube-system/traefik
|
|
||||||
- --entryPoints.websecure.http.tls=true
|
|
||||||
- --log.level=INFO
|
|
||||||
- --entryPoints.web.transport.respondingTimeouts.readTimeout=0s
|
|
||||||
- --entryPoints.websecure.transport.respondingTimeouts.readTimeout=0s
|
|
||||||
ports:
|
|
||||||
- containerPort: 9100
|
|
||||||
name: metrics
|
|
||||||
protocol: TCP
|
|
||||||
- containerPort: 8080
|
|
||||||
name: traefik
|
|
||||||
protocol: TCP
|
|
||||||
- containerPort: 8000
|
|
||||||
name: web
|
|
||||||
protocol: TCP
|
|
||||||
- containerPort: 8443
|
|
||||||
name: websecure
|
|
||||||
protocol: TCP
|
|
||||||
- containerPort: 62051
|
|
||||||
name: pasarguard-api
|
|
||||||
protocol: TCP
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: traefik
|
|
||||||
namespace: kube-system
|
|
||||||
spec:
|
|
||||||
ports:
|
|
||||||
- name: web
|
|
||||||
port: 80
|
|
||||||
protocol: TCP
|
|
||||||
targetPort: web
|
|
||||||
- name: websecure
|
|
||||||
port: 443
|
|
||||||
protocol: TCP
|
|
||||||
targetPort: websecure
|
|
||||||
- name: pasarguard-api
|
|
||||||
port: 62051
|
|
||||||
protocol: TCP
|
|
||||||
targetPort: pasarguard-api
|
|
||||||
Reference in New Issue
Block a user