Compare commits

...

3 Commits

Author SHA1 Message Date
Paul Payne
46002ff273 feat: update NFS documentation and manifest version for improved clarity and configuration 2026-05-23 11:25:50 +00:00
Paul Payne
acec744df8 feat: update NFS configuration and add check-nfs script for server validation 2026-05-23 11:24:21 +00:00
Paul Payne
12e87635c6 docs: Update ADDING-APPS.md to remove cloud.smtp references
SMTP config is now at apps.smtp.* via the SMTP infrastructure app,
not cloud.smtp.*. Remove the old variable listing and update the
configuration flow documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-22 23:31:14 +00:00
5 changed files with 134 additions and 263 deletions

View File

@@ -62,13 +62,32 @@ requiredSecrets:
| `name` | Yes | App identifier (must match directory name) |
| `is` | Yes | Unique id for this app. Used for `requires` mapping |
| `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 |
| `requires` | No | List of dependency apps with optional aliases |
| `defaultConfig` | Yes | Default configuration values merged into operator's `config.yaml` |
| `defaultSecrets` | No | This app's secrets (no 'default' = auto-generated) |
| `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
- Each dependency in `requires` can have:
@@ -121,15 +140,6 @@ Here's a comprehensive rundown of all config variables that get set during clust
- cloud.dockerRegistryHost - Docker registry hostname (e.g., "registry.internal.cloud2.payne.io")
##### SMTP Configuration (SMTP Service):
- cloud.smtp.host - SMTP server hostname
- cloud.smtp.port - SMTP port (typically "465" or "587")
- cloud.smtp.user - SMTP username
- cloud.smtp.from - Default 'from' email address
- cloud.smtp.tls - Enable TLS (true/false)
- cloud.smtp.startTls - Enable STARTTLS (true/false)
###### Backup Configuration:
- cloud.backup.root - Root path for backups
@@ -214,8 +224,7 @@ Configuration Flow
- ExternalDNS → cluster.externalDns.ownerId
- NFS → cloud.nfs.*
- Docker Registry → cloud.dockerRegistryHost, cluster.dockerRegistry.storage
- SMTP → cloud.smtp.*
4. Apps: Each app adds its configuration under apps.<name>.* based on its manifest
4. Apps: Each app adds its configuration under apps.<name>.* based on its manifest (including SMTP as an infrastructure app at apps.smtp.*)
#### Manifest App Reference Resolution:

View File

@@ -1,10 +1,10 @@
# NFS Setup (Optional)
# NFS Client Provisioner
The infrastructure supports optional NFS (Network File System) for shared media storage across the cluster. If your config.yaml contains the `cloud.nfs` section, the NFS server will be set up automatically.
Provides shared NFS storage to the cluster by creating a StorageClass and PersistentVolume backed by an external NFS server. This is an infrastructure app — it has no pods or namespace, just cluster-scoped resources.
## Host Setup
## Prerequisites
First, set up the NFS server on your chosen host.
You need an NFS server already running and exporting a path. To set one up on a host:
```bash
./setup-nfs-host.sh <host> <media-path>
@@ -16,30 +16,33 @@ Example:
./setup-nfs-host.sh box-01 /srv/nfs
```
## Cluster Integration
This SSHs into the host, installs `nfs-kernel-server`, and configures the export.
Add to your `config.yaml`:
## Configuration
When added to an instance, the default config is merged into `config.yaml`:
```yaml
cloud:
apps:
nfs:
host: box-01
mediaPath: /srv/nfs
storageCapacity: 250Gi # Max size for PersistentVolume
namespace: nfs
host: "192.168.1.100"
mediaPath: "/mnt/storage/media"
storageCapacity: "1Ti"
```
And now you can run the nfs cluster setup:
Update `host` and `mediaPath` to match your NFS server before deploying.
```bash
setup/setup-nfs-host.sh
```
## What Gets Deployed
## Features
- **StorageClass** (`nfs`) — allows PVCs to request NFS-backed storage
- **PersistentVolume** (`nfs-media-pv`) — a cluster-wide volume pointing to the NFS export
- Automatic IP detection - Uses network IP even when hostname resolves to localhost
- Cluster-wide access - Any pod can mount the NFS share regardless of node placement
- Configurable capacity - Set PersistentVolume size via `NFS_STORAGE_CAPACITY`
- ReadWriteMany - Multiple pods can simultaneously access the same storage
No namespace, pods, or services are created.
## Scripts
- **check-nfs** — Verifies the NFS server is reachable, the export path exists, and checks whether the StorageClass and PersistentVolume are present in the cluster. Run from the app detail panel in the web UI.
## Usage
@@ -58,3 +61,10 @@ spec:
requests:
storage: 100Gi
```
## Features
- Cluster-wide access — any pod can mount the NFS share regardless of node placement
- ReadWriteMany — multiple pods can simultaneously read and write
- Configurable capacity — set PersistentVolume size via `storageCapacity`
- Retain policy — data is preserved when volumes are released

View File

@@ -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 "$@"

View File

@@ -1,10 +1,12 @@
name: nfs
is: nfs
description: NFS client provisioner for external NFS storage
version: v4.0.18
deploymentName: ""
storageClassName: "nfs"
version: v4.0.18-3
category: infrastructure
scripts:
- name: check-nfs
path: scripts/check-nfs.sh
description: Verify NFS server is reachable and the export path is available
defaultConfig:
namespace: nfs
host: "192.168.1.100"

79
nfs/scripts/check-nfs.sh Executable file
View 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."