feat: update NFS configuration and add check-nfs script for server validation
This commit is contained in:
@@ -62,13 +62,32 @@ requiredSecrets:
|
|||||||
| `name` | Yes | App identifier (must match directory name) |
|
| `name` | Yes | App identifier (must match directory name) |
|
||||||
| `is` | Yes | Unique id for this app. Used for `requires` mapping |
|
| `is` | Yes | Unique id for this app. Used for `requires` mapping |
|
||||||
| `description` | Yes | Brief app description shown in listings |
|
| `description` | Yes | Brief app description shown in listings |
|
||||||
| `version` | Yes | App version (follow upstream versioning) |
|
| `version` | Yes | App version (see Versioning Convention below) |
|
||||||
| `icon` | No | URL to app icon for UI display |
|
| `icon` | No | URL to app icon for UI display |
|
||||||
| `requires` | No | List of dependency apps with optional aliases |
|
| `requires` | No | List of dependency apps with optional aliases |
|
||||||
| `defaultConfig` | Yes | Default configuration values merged into operator's `config.yaml` |
|
| `defaultConfig` | Yes | Default configuration values merged into operator's `config.yaml` |
|
||||||
| `defaultSecrets` | No | This app's secrets (no 'default' = auto-generated) |
|
| `defaultSecrets` | No | This app's secrets (no 'default' = auto-generated) |
|
||||||
| `requiredSecrets` | No | List of secrets from dependency apps (format: `<app-ref>.<key>`) |
|
| `requiredSecrets` | No | List of secrets from dependency apps (format: `<app-ref>.<key>`) |
|
||||||
|
|
||||||
|
### Versioning Convention
|
||||||
|
|
||||||
|
Wild Cloud uses a two-part version scheme inspired by Debian packaging: `<upstream>-<revision>`.
|
||||||
|
|
||||||
|
- **Upstream version** tracks the third-party software version (e.g., `v4.0.18`, `1.120.2`)
|
||||||
|
- **Packaging revision** tracks Wild Cloud packaging changes (template fixes, manifest cleanup, config restructuring) that don't change the upstream software version
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
- `v4.0.18` — initial packaging of upstream v4.0.18
|
||||||
|
- `v4.0.18-1` — first packaging fix (no upstream change)
|
||||||
|
- `v4.0.18-2` — second packaging fix
|
||||||
|
- `v4.0.19` — upstream version bump, revision resets
|
||||||
|
|
||||||
|
**When to bump the packaging revision:** Any change to the app package that doesn't correspond to an upstream software update — manifest field changes, template improvements, kustomize restructuring, security context fixes, label corrections, etc.
|
||||||
|
|
||||||
|
**When to bump the upstream version:** When updating the container image tag or deploying a new version of the third-party software.
|
||||||
|
|
||||||
|
The web UI uses version comparison to detect available updates. If the deployed version differs from the wild-directory version, operators see an update indicator and can apply it from the app detail panel.
|
||||||
|
|
||||||
### Dependency Configuration
|
### Dependency Configuration
|
||||||
|
|
||||||
- Each dependency in `requires` can have:
|
- Each dependency in `requires` can have:
|
||||||
|
|||||||
229
nfs/install.sh
229
nfs/install.sh
@@ -1,229 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
set -o pipefail
|
|
||||||
|
|
||||||
if [ -z "${WILD_INSTANCE}" ]; then
|
|
||||||
echo "ERROR: WILD_INSTANCE is not set"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "${WILD_API_DATA_DIR}" ]; then
|
|
||||||
echo "ERROR: WILD_API_DATA_DIR is not set"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "${KUBECONFIG}" ]; then
|
|
||||||
echo "ERROR: KUBECONFIG is not set"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}"
|
|
||||||
CONFIG_FILE="${INSTANCE_DIR}/config.yaml"
|
|
||||||
NFS_DIR="${INSTANCE_DIR}/apps/nfs"
|
|
||||||
|
|
||||||
echo "=== Registering NFS Server with Kubernetes Cluster ==="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
echo "Using pre-compiled NFS templates..."
|
|
||||||
if [ ! -f "${NFS_DIR}/kustomization.yaml" ]; then
|
|
||||||
echo "ERROR: Compiled templates not found at ${NFS_DIR}"
|
|
||||||
echo "Templates should be compiled before deployment."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
NFS_HOST="$(yq '.apps.nfs.host' "${CONFIG_FILE}" 2>/dev/null | tr -d '"')"
|
|
||||||
NFS_MEDIA_PATH="$(yq '.apps.nfs.mediaPath' "${CONFIG_FILE}" 2>/dev/null | tr -d '"')"
|
|
||||||
NFS_STORAGE_CAPACITY="$(yq '.apps.nfs.storageCapacity' "${CONFIG_FILE}" 2>/dev/null | tr -d '"')"
|
|
||||||
|
|
||||||
echo "NFS Configuration:"
|
|
||||||
echo " Host: ${NFS_HOST}"
|
|
||||||
echo " Media path: ${NFS_MEDIA_PATH}"
|
|
||||||
echo " Storage capacity: ${NFS_STORAGE_CAPACITY}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
if [ -z "${NFS_HOST}" ] || [ "${NFS_HOST}" = "null" ]; then
|
|
||||||
echo "ERROR: apps.nfs.host not set in config"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ -z "${NFS_MEDIA_PATH}" ] || [ "${NFS_MEDIA_PATH}" = "null" ]; then
|
|
||||||
echo "ERROR: apps.nfs.mediaPath not set in config"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ -z "${NFS_STORAGE_CAPACITY}" ] || [ "${NFS_STORAGE_CAPACITY}" = "null" ]; then
|
|
||||||
echo "ERROR: apps.nfs.storageCapacity not set in config"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
resolve_nfs_host() {
|
|
||||||
echo "Resolving NFS host: ${NFS_HOST}"
|
|
||||||
if [[ "${NFS_HOST}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
||||||
NFS_HOST_IP="${NFS_HOST}"
|
|
||||||
echo " Host is already an IP address"
|
|
||||||
else
|
|
||||||
echo " Looking up hostname..."
|
|
||||||
NFS_HOST_IP=$(getent hosts "${NFS_HOST}" 2>/dev/null | awk '{print $1}' | head -n1 || true)
|
|
||||||
echo " Resolved to: ${NFS_HOST_IP}"
|
|
||||||
if [[ -z "${NFS_HOST_IP}" ]]; then
|
|
||||||
echo "ERROR: Unable to resolve hostname ${NFS_HOST} to IP address"
|
|
||||||
echo "Make sure ${NFS_HOST} is resolvable from this cluster"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${NFS_HOST_IP}" =~ ^127\. ]]; then
|
|
||||||
echo "Warning: ${NFS_HOST} resolves to localhost (${NFS_HOST_IP})"
|
|
||||||
echo "Auto-detecting network IP for cluster access..."
|
|
||||||
|
|
||||||
local network_ip=$(ip route get 8.8.8.8 | grep -oP 'src \K\S+' 2>/dev/null)
|
|
||||||
|
|
||||||
if [[ -n "${network_ip}" && ! "${network_ip}" =~ ^127\. ]]; then
|
|
||||||
echo "Using detected network IP: ${network_ip}"
|
|
||||||
NFS_HOST_IP="${network_ip}"
|
|
||||||
else
|
|
||||||
echo "ERROR: Could not auto-detect network IP. Available IPs:"
|
|
||||||
ip addr show | grep "inet " | grep -v "127.0.0.1" | grep -v "10.42" | grep -v "172." | awk '{print " " $2}' | cut -d/ -f1
|
|
||||||
echo "Please set NFS_HOST to the correct IP address manually."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "NFS server IP: ${NFS_HOST_IP}"
|
|
||||||
export NFS_HOST_IP
|
|
||||||
}
|
|
||||||
|
|
||||||
test_nfs_accessibility() {
|
|
||||||
echo ""
|
|
||||||
echo "Testing NFS accessibility from cluster..."
|
|
||||||
|
|
||||||
if ! command -v showmount >/dev/null 2>&1; then
|
|
||||||
echo "Installing NFS client tools..."
|
|
||||||
if command -v apt-get >/dev/null 2>&1; then
|
|
||||||
sudo apt-get update && sudo apt-get install -y nfs-common
|
|
||||||
elif command -v yum >/dev/null 2>&1; then
|
|
||||||
sudo yum install -y nfs-utils
|
|
||||||
elif command -v dnf >/dev/null 2>&1; then
|
|
||||||
sudo dnf install -y nfs-utils
|
|
||||||
else
|
|
||||||
echo "Warning: Unable to install NFS client tools. Skipping accessibility test."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Testing connection to NFS server..."
|
|
||||||
if timeout 10 showmount -e "${NFS_HOST_IP}" >/dev/null 2>&1; then
|
|
||||||
echo "NFS server is accessible"
|
|
||||||
echo "Available exports:"
|
|
||||||
showmount -e "${NFS_HOST_IP}"
|
|
||||||
else
|
|
||||||
echo "ERROR: Cannot connect to NFS server at ${NFS_HOST_IP}"
|
|
||||||
echo "Make sure:"
|
|
||||||
echo " 1. NFS server is running on ${NFS_HOST}"
|
|
||||||
echo " 2. Network connectivity exists between cluster and NFS host"
|
|
||||||
echo " 3. Firewall allows NFS traffic (port 2049)"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if showmount -e "${NFS_HOST_IP}" | grep -q "${NFS_MEDIA_PATH}"; then
|
|
||||||
echo "Media path ${NFS_MEDIA_PATH} is exported"
|
|
||||||
else
|
|
||||||
echo "ERROR: Media path ${NFS_MEDIA_PATH} is not found in exports"
|
|
||||||
echo "Available exports:"
|
|
||||||
showmount -e "${NFS_HOST_IP}"
|
|
||||||
echo ""
|
|
||||||
echo "Run setup-nfs-host.sh on ${NFS_HOST} to configure the export"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
test_nfs_mount() {
|
|
||||||
echo ""
|
|
||||||
echo "Testing NFS mount functionality..."
|
|
||||||
|
|
||||||
local test_mount="/tmp/nfs-test-$$"
|
|
||||||
mkdir -p "${test_mount}"
|
|
||||||
|
|
||||||
if timeout 30 sudo mount -t nfs4 "${NFS_HOST_IP}:${NFS_MEDIA_PATH}" "${test_mount}"; then
|
|
||||||
echo "NFS mount successful"
|
|
||||||
|
|
||||||
if ls "${test_mount}" >/dev/null 2>&1; then
|
|
||||||
echo "NFS read access working"
|
|
||||||
else
|
|
||||||
echo "ERROR: NFS read access failed"
|
|
||||||
fi
|
|
||||||
|
|
||||||
sudo umount "${test_mount}" || echo "Warning: Failed to unmount test directory"
|
|
||||||
else
|
|
||||||
echo "ERROR: NFS mount failed"
|
|
||||||
echo "Check NFS server configuration and network connectivity"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
rmdir "${test_mount}" 2>/dev/null || true
|
|
||||||
}
|
|
||||||
|
|
||||||
create_k8s_resources() {
|
|
||||||
echo ""
|
|
||||||
echo "Creating Kubernetes NFS resources..."
|
|
||||||
|
|
||||||
echo "Applying NFS manifests..."
|
|
||||||
kubectl apply -k "${NFS_DIR}/"
|
|
||||||
|
|
||||||
echo "NFS PersistentVolume and StorageClass created"
|
|
||||||
|
|
||||||
echo "Verifying Kubernetes resources..."
|
|
||||||
if kubectl get storageclass nfs >/dev/null 2>&1; then
|
|
||||||
echo "StorageClass 'nfs' created"
|
|
||||||
else
|
|
||||||
echo "ERROR: StorageClass 'nfs' not found"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if kubectl get pv nfs-media-pv >/dev/null 2>&1; then
|
|
||||||
echo "PersistentVolume 'nfs-media-pv' created"
|
|
||||||
kubectl get pv nfs-media-pv
|
|
||||||
else
|
|
||||||
echo "ERROR: PersistentVolume 'nfs-media-pv' not found"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
show_usage_instructions() {
|
|
||||||
echo ""
|
|
||||||
echo "=== NFS Kubernetes Setup Complete ==="
|
|
||||||
echo ""
|
|
||||||
echo "NFS server ${NFS_HOST} (${NFS_HOST_IP}) has been registered with the cluster"
|
|
||||||
echo ""
|
|
||||||
echo "Kubernetes resources created:"
|
|
||||||
echo " - StorageClass: nfs"
|
|
||||||
echo " - PersistentVolume: nfs-media-pv (${NFS_STORAGE_CAPACITY}, ReadWriteMany)"
|
|
||||||
echo ""
|
|
||||||
echo "To use NFS storage in your applications:"
|
|
||||||
echo " 1. Set storageClassName: nfs in your PVC"
|
|
||||||
echo " 2. Use accessMode: ReadWriteMany for shared access"
|
|
||||||
echo ""
|
|
||||||
echo "Example PVC:"
|
|
||||||
echo "---"
|
|
||||||
echo "apiVersion: v1"
|
|
||||||
echo "kind: PersistentVolumeClaim"
|
|
||||||
echo "metadata:"
|
|
||||||
echo " name: my-nfs-pvc"
|
|
||||||
echo "spec:"
|
|
||||||
echo " accessModes:"
|
|
||||||
echo " - ReadWriteMany"
|
|
||||||
echo " storageClassName: nfs"
|
|
||||||
echo " resources:"
|
|
||||||
echo " requests:"
|
|
||||||
echo " storage: 10Gi"
|
|
||||||
echo ""
|
|
||||||
}
|
|
||||||
|
|
||||||
main() {
|
|
||||||
resolve_nfs_host
|
|
||||||
test_nfs_accessibility
|
|
||||||
test_nfs_mount
|
|
||||||
create_k8s_resources
|
|
||||||
show_usage_instructions
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "Starting NFS setup process..."
|
|
||||||
main "$@"
|
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
name: nfs
|
name: nfs
|
||||||
is: nfs
|
is: nfs
|
||||||
description: NFS client provisioner for external NFS storage
|
description: NFS client provisioner for external NFS storage
|
||||||
version: v4.0.18
|
version: v4.0.18-2
|
||||||
deploymentName: ""
|
|
||||||
storageClassName: "nfs"
|
|
||||||
category: infrastructure
|
category: infrastructure
|
||||||
|
scripts:
|
||||||
|
- name: check-nfs
|
||||||
|
path: scripts/check-nfs.sh
|
||||||
|
description: Verify NFS server is reachable and the export path is available
|
||||||
defaultConfig:
|
defaultConfig:
|
||||||
namespace: nfs
|
namespace: nfs
|
||||||
host: "192.168.1.100"
|
host: "192.168.1.100"
|
||||||
|
|||||||
79
nfs/scripts/check-nfs.sh
Executable file
79
nfs/scripts/check-nfs.sh
Executable file
@@ -0,0 +1,79 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Verify NFS server is reachable and the export path is available.
|
||||||
|
# Run before or after deployment to validate NFS connectivity.
|
||||||
|
set -e
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
if [ -z "${WILD_INSTANCE}" ] || [ -z "${WILD_API_DATA_DIR}" ]; then
|
||||||
|
echo "ERROR: WILD_INSTANCE and WILD_API_DATA_DIR must be set"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CONFIG_FILE="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}/config.yaml"
|
||||||
|
|
||||||
|
NFS_HOST="$(yq '.apps.nfs.host' "${CONFIG_FILE}" 2>/dev/null | tr -d '"')"
|
||||||
|
NFS_PATH="$(yq '.apps.nfs.mediaPath' "${CONFIG_FILE}" 2>/dev/null | tr -d '"')"
|
||||||
|
|
||||||
|
if [ -z "${NFS_HOST}" ] || [ "${NFS_HOST}" = "null" ]; then
|
||||||
|
echo "ERROR: apps.nfs.host not set in config"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -z "${NFS_PATH}" ] || [ "${NFS_PATH}" = "null" ]; then
|
||||||
|
echo "ERROR: apps.nfs.mediaPath not set in config"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "NFS host: ${NFS_HOST}"
|
||||||
|
echo "NFS path: ${NFS_PATH}"
|
||||||
|
|
||||||
|
# Resolve hostname to IP
|
||||||
|
if [[ "${NFS_HOST}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
NFS_IP="${NFS_HOST}"
|
||||||
|
else
|
||||||
|
NFS_IP=$(getent hosts "${NFS_HOST}" 2>/dev/null | awk '{print $1}' | head -n1 || true)
|
||||||
|
if [ -z "${NFS_IP}" ]; then
|
||||||
|
echo "ERROR: Cannot resolve hostname ${NFS_HOST}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Resolved to: ${NFS_IP}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check showmount
|
||||||
|
if ! command -v showmount >/dev/null 2>&1; then
|
||||||
|
echo "WARNING: showmount not available, skipping export check"
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo "Checking NFS exports..."
|
||||||
|
if timeout 10 showmount -e "${NFS_IP}" >/dev/null 2>&1; then
|
||||||
|
if showmount -e "${NFS_IP}" | grep -q "${NFS_PATH}"; then
|
||||||
|
echo "OK: ${NFS_PATH} is exported"
|
||||||
|
else
|
||||||
|
echo "ERROR: ${NFS_PATH} not found in NFS exports:"
|
||||||
|
showmount -e "${NFS_IP}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "ERROR: Cannot reach NFS server at ${NFS_IP}:2049"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check k8s resources if KUBECONFIG is available
|
||||||
|
if [ -n "${KUBECONFIG}" ]; then
|
||||||
|
echo ""
|
||||||
|
echo "Checking Kubernetes resources..."
|
||||||
|
if kubectl get storageclass nfs >/dev/null 2>&1; then
|
||||||
|
echo "OK: StorageClass 'nfs' exists"
|
||||||
|
else
|
||||||
|
echo "WARNING: StorageClass 'nfs' not found (deploy NFS first)"
|
||||||
|
fi
|
||||||
|
if kubectl get pv nfs-media-pv >/dev/null 2>&1; then
|
||||||
|
echo "OK: PersistentVolume 'nfs-media-pv' exists"
|
||||||
|
kubectl get pv nfs-media-pv --no-headers
|
||||||
|
else
|
||||||
|
echo "WARNING: PersistentVolume 'nfs-media-pv' not found (deploy NFS first)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "NFS check complete."
|
||||||
Reference in New Issue
Block a user