feat: complete end-to-end installation and storage integration. This commit marks a significant milestone where the end-to-end installation process is now fully functional. Key changes: Integrated Garage storage service (S3-compatible); Implemented Prole DB backup; Enhanced Kerberos integration; Updated default namespace to prole-chrisfu-deadbeef; Streamlined dependencies (removed Ollama); Added installation validation and testing scripts; Improved installer UI and deployment logic.

This commit is contained in:
chrisfu 2026-01-29 22:50:05 -08:00
parent 519235558a
commit f0e75f6107
25 changed files with 4098 additions and 314 deletions

5
env.sh
View File

@ -8,9 +8,9 @@ export PROLE_CONF="/Users/chrisfu/dev/prole/conf"
export PROLE_DATA="/Users/chrisfu/dev/prole/data"
export PROLE_LOGS="/Users/chrisfu/dev/prole/logs"
export PROLE_SERVICE="/Users/chrisfu/dev/prole/etc"
export NAMESPACE="prole-chrisfu-a05806"
export NAMESPACE="prole-chrisfu-deadbeef"
# Ensure PATH works for GUI-launched shells (Docker, Ollama, etc.)
# Ensure PATH works for GUI-launched shells (Docker, etc.)
_prole_add_path() { case ":${PATH}:" in *":$1:"*) ;; *) PATH="$1:${PATH:-}" ;; esac; }
_prole_add_path "$PROLE_HOME/bin"
_prole_add_path "/opt/homebrew/bin"
@ -22,7 +22,6 @@ _prole_add_path "/sbin"
export PATH
# Add custom paths below if needed (examples):
# _prole_add_path "/Applications/Ollama.app/Contents/MacOS"
# If executed with arguments, run them under this environment
if [ "$#" -gt 0 ]; then

286
etc/init_garage_store.sh Executable file
View File

@ -0,0 +1,286 @@
#!/usr/bin/env bash
set -euo pipefail
# init_garage_store.sh
# Purpose:
# - Deploy Garage (S3-compatible object store) in Kubernetes
# - Initialize single-node layout for immediate use
# Initialize SCRIPT_DIR
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
ACTION=${1:-}
# Load env
if [[ -n "${PROLE_HOME:-}" && -f "$PROLE_HOME/env.sh" ]]; then
__PROLE_SAVED_ARGS=("$@")
set --
# shellcheck disable=SC1090
source "$PROLE_HOME/env.sh"
set -- "${__PROLE_SAVED_ARGS[@]}"
unset __PROLE_SAVED_ARGS
elif [[ -f "$HOME/.prole/env.sh" ]]; then
__PROLE_SAVED_ARGS=("$@")
set --
# shellcheck disable=SC1090
source "$HOME/.prole/env.sh"
set -- "${__PROLE_SAVED_ARGS[@]}"
unset __PROLE_SAVED_ARGS
fi
if [[ -n "${GARAGE_INIT_LOG:-}" ]]; then
mkdir -p "$(dirname "$GARAGE_INIT_LOG")"
exec > >(tee -a "$GARAGE_INIT_LOG") 2>&1
fi
NAMESPACE=${NAMESPACE:-default}
GARAGE_NAME=${GARAGE_NAME:-garage}
GARAGE_SECRET_NAME=${GARAGE_SECRET_NAME:-garage-secrets}
GARAGE_NODE_CAPACITY=${GARAGE_NODE_CAPACITY:-10GB}
GARAGE_ZONE=${GARAGE_ZONE:-local}
# Support both PROLE_HOME/k8s and sibling k8s directory
if [[ -d "$SCRIPT_DIR/../k8s/prole" ]]; then
GARAGE_MANIFEST_DIR="$SCRIPT_DIR/../k8s/prole"
elif [[ -n "${PROLE_HOME:-}" && -d "$PROLE_HOME/k8s/prole" ]]; then
GARAGE_MANIFEST_DIR="$PROLE_HOME/k8s/prole"
else
GARAGE_MANIFEST_DIR="$SCRIPT_DIR/../k8s/prole"
fi
GARAGE_FILES=(
"$GARAGE_MANIFEST_DIR/garage-configmap.yaml"
"$GARAGE_MANIFEST_DIR/garage-statefulset.yaml"
"$GARAGE_MANIFEST_DIR/garage-service.yaml"
)
usage() {
cat <<USAGE
Usage: $0 [start|stop|restart|status]
Actions:
start Apply Garage manifests and initialize layout
stop Delete Garage manifests
restart Re-apply Garage manifests and initialize layout
status Show Garage resources
USAGE
exit 1
}
ensure_tools() {
for t in kubectl openssl; do
command -v "$t" >/dev/null || { echo "Missing required tool: $t" >&2; exit 1; }
done
}
ensure_namespace() {
if ! kubectl get namespace "$NAMESPACE" >/dev/null 2>&1; then
echo "Creating namespace '$NAMESPACE' ..."
kubectl create namespace "$NAMESPACE" >/dev/null 2>&1 || true
fi
}
ensure_secrets() {
if kubectl get secret "$GARAGE_SECRET_NAME" -n "$NAMESPACE" >/dev/null 2>&1; then
return 0
fi
echo "Creating Garage secrets in namespace '$NAMESPACE' ..."
local rpc_secret admin_token metrics_token
rpc_secret=$(openssl rand -hex 32)
admin_token=$(openssl rand -base64 32)
metrics_token=$(openssl rand -base64 32)
kubectl create secret generic "$GARAGE_SECRET_NAME" -n "$NAMESPACE" \
--from-literal=rpc_secret="$rpc_secret" \
--from-literal=admin_token="$admin_token" \
--from-literal=metrics_token="$metrics_token"
}
apply_manifests() {
for f in "${GARAGE_FILES[@]}"; do
if [[ -f "$f" ]]; then
kubectl apply -n "$NAMESPACE" -f "$f"
else
echo "ERROR: Missing manifest: $f" >&2
exit 1
fi
done
}
ensure_container_command() {
if ! kubectl get statefulset "$GARAGE_NAME" -n "$NAMESPACE" >/dev/null 2>&1; then
return 0
fi
local cmd args
cmd=$(kubectl get statefulset "$GARAGE_NAME" -n "$NAMESPACE" -o jsonpath='{.spec.template.spec.containers[?(@.name=="garage")].command}' 2>/dev/null || true)
args=$(kubectl get statefulset "$GARAGE_NAME" -n "$NAMESPACE" -o jsonpath='{.spec.template.spec.containers[?(@.name=="garage")].args}' 2>/dev/null || true)
if [[ -z "$cmd" || "$cmd" == "[]" || "$cmd" != *"/garage"* || -z "$args" || "$args" == "[]" || "$args" != *"server"* ]]; then
echo "Patching Garage container command/args ..."
kubectl patch statefulset "$GARAGE_NAME" -n "$NAMESPACE" --type merge -p '{
"spec": {
"template": {
"spec": {
"containers": [
{
"name": "garage",
"command": ["/garage"],
"args": ["server"]
}
]
}
}
}
}' >/dev/null || true
fi
}
restart_statefulset() {
if kubectl get statefulset "$GARAGE_NAME" -n "$NAMESPACE" >/dev/null 2>&1; then
echo "Restarting Garage StatefulSet to pick up config changes ..."
kubectl rollout restart statefulset/$GARAGE_NAME -n "$NAMESPACE" || true
fi
}
delete_manifests() {
for f in "${GARAGE_FILES[@]}"; do
if [[ -f "$f" ]]; then
kubectl delete -n "$NAMESPACE" -f "$f" --ignore-not-found
fi
done
}
dump_debug() {
echo "---- Garage debug ----"
kubectl get statefulset "$GARAGE_NAME" -n "$NAMESPACE" -o wide || true
kubectl get pods -n "$NAMESPACE" -l "app=$GARAGE_NAME" -o wide || true
kubectl get svc "$GARAGE_NAME" -n "$NAMESPACE" -o wide || true
kubectl get events -n "$NAMESPACE" --sort-by=.metadata.creationTimestamp | tail -n 50 || true
local pod
pod=$(kubectl get pods -n "$NAMESPACE" -l "app=$GARAGE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)
if [[ -n "$pod" ]]; then
echo "--- pod: $pod (describe) ---"
kubectl describe pod "$pod" -n "$NAMESPACE" || true
echo "--- logs (current) ---"
kubectl logs -n "$NAMESPACE" "$pod" --tail=200 || true
echo "--- logs (previous) ---"
kubectl logs -n "$NAMESPACE" "$pod" --previous --tail=200 || true
fi
echo "---- Garage debug end ----"
}
wait_ready() {
echo "Waiting for Garage StatefulSet to become ready ..."
if ! kubectl rollout status statefulset/$GARAGE_NAME -n "$NAMESPACE" --timeout=180s; then
echo "ERROR: Garage StatefulSet did not become ready in time." >&2
dump_debug
return 1
fi
}
init_layout() {
local pod
pod=$(kubectl get pods -n "$NAMESPACE" -l "app=$GARAGE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)
if [[ -z "$pod" ]]; then
echo "ERROR: Garage pod not found in namespace '$NAMESPACE'." >&2
exit 1
fi
local node_id=""
echo "Determining Garage node ID..."
for i in {1..30}; do
node_id=$(kubectl exec -n "$NAMESPACE" "$pod" -- /garage node id -q 2>/dev/null | awk '{print $1}' || true)
if [[ -n "$node_id" ]]; then
break
fi
echo "Waiting for Garage node ID to be available... ($i/30)"
sleep 2
done
if [[ -z "$node_id" ]]; then
echo "ERROR: Unable to determine Garage node ID after 30 attempts." >&2
dump_debug
exit 1
fi
# Extract the short ID for better matching
local short_id
short_id=$(echo "$node_id" | cut -d'@' -f1)
echo "Garage node ID: $short_id"
local layout
layout=$(kubectl exec -n "$NAMESPACE" "$pod" -- /garage layout show 2>/dev/null || true)
if ! echo "$layout" | grep -q "$short_id"; then
echo "Assigning Garage node role (capacity: $GARAGE_NODE_CAPACITY, zone: $GARAGE_ZONE) ..."
# Retry assigning role as it might fail if node is not yet fully ready in the cluster logic
local max_assign_retries=10
local assign_count=0
while ! kubectl exec -n "$NAMESPACE" "$pod" -- /garage layout assign -z "$GARAGE_ZONE" -c "$GARAGE_NODE_CAPACITY" "$short_id"; do
if [[ $assign_count -ge $max_assign_retries ]]; then
echo "ERROR: Failed to assign Garage node role after $max_assign_retries retries." >&2
exit 1
fi
echo "Retrying Garage layout assign... ($((assign_count + 1))/$max_assign_retries)"
sleep 2
assign_count=$((assign_count + 1))
done
# Get current version for apply
local version
version=$(echo "$layout" | grep "Version:" | awk '{print $2}' || echo "0")
if [[ -z "$version" ]]; then version=0; fi
local next_version=$((version + 1))
echo "Applying Garage layout (version $next_version) ..."
kubectl exec -n "$NAMESPACE" "$pod" -- /garage layout apply --version "$next_version" || true
else
echo "Garage layout already assigned for node $short_id."
fi
}
status() {
ensure_tools
echo "Garage status in namespace '$NAMESPACE':"
kubectl get statefulset "$GARAGE_NAME" -n "$NAMESPACE" || true
kubectl get pods -n "$NAMESPACE" -l "app=$GARAGE_NAME" || true
kubectl get svc "$GARAGE_NAME" -n "$NAMESPACE" || true
}
case "$ACTION" in
start)
ensure_tools
ensure_namespace
ensure_secrets
echo "Applying Garage manifests in namespace '$NAMESPACE'..."
apply_manifests
ensure_container_command
restart_statefulset
wait_ready
init_layout
;;
stop)
ensure_tools
echo "Deleting Garage manifests from namespace '$NAMESPACE'..."
delete_manifests
;;
restart)
ensure_tools
ensure_namespace
ensure_secrets
echo "Re-applying Garage manifests in namespace '$NAMESPACE'..."
apply_manifests
ensure_container_command
restart_statefulset
wait_ready
init_layout
;;
status)
status
;;
*)
usage
;;
esac

325
etc/init_kerberos.sh Executable file
View File

@ -0,0 +1,325 @@
#!/usr/bin/env bash
set -euo pipefail
# init_kerberos.sh
# Purpose:
# - Configure Kerberos realm settings for CloudNative-PG pods
# - Optionally run a Kerberos authentication test inside a Kubernetes pod
# - Optionally provision an in-cluster port forwarder to a Samba AD DC
#
# Usage:
# ./init_kerberos.sh initialize # apply krb5.conf to CNPG pods (best-effort)
# ./init_kerberos.sh test # run test pod + kinit (with optional AD port forward)
# ./init_kerberos.sh status # show detected config + resources
# ./init_kerberos.sh cleanup # remove AD forwarder resources
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
ACTION=${1:-initialize}
# Load env without leaking our positional args to the env script (some env.sh may `exec "$@"`).
__PROLE_SAVED_ARGS=("$@")
if [[ -n "${PROLE_HOME:-}" && -f "$PROLE_HOME/env.sh" ]]; then
set --
# shellcheck disable=SC1090
source "$PROLE_HOME/env.sh"
elif [[ -f "$HOME/.prole/env.sh" ]]; then
set --
# shellcheck disable=SC1090
source "$HOME/.prole/env.sh"
fi
set -- "${__PROLE_SAVED_ARGS[@]}"
unset __PROLE_SAVED_ARGS
NAMESPACE=${NAMESPACE:-default}
CNPG_CLUSTER_NAME=${CNPG_CLUSTER_NAME:-prole-db}
KRB5_REALM=${KRB5_REALM:-${REALM:-}}
KRB5_KDC=${KRB5_KDC:-}
KRB5_ADMIN=${KRB5_ADMIN:-}
KRB5_USER=${KRB5_USER:-${KRB5_USERNAME:-}}
KRB5_PASSWORD=${KRB5_PASSWORD:-}
# AD DC forwarding (socat proxy) options
KRB5_AD_PORT_FORWARD=${KRB5_AD_PORT_FORWARD:-}
KRB5_AD_PROXY_NAME=${KRB5_AD_PROXY_NAME:-prole-kerberos-ad-forwarder}
KRB5_AD_SERVICE_NAME=${KRB5_AD_SERVICE_NAME:-prole-kerberos-ad-dc}
KRB5_AD_PROXY_IMAGE=${KRB5_AD_PROXY_IMAGE:-alpine/socat}
KRB5_AD_PROXY_HOST_NETWORK=${KRB5_AD_PROXY_HOST_NETWORK:-1}
KRB5_AD_TCP_PORTS=${KRB5_AD_TCP_PORTS:-"88 389 445 464 636"}
KRB5_AD_UDP_PORTS=${KRB5_AD_UDP_PORTS:-"88 464"}
log() { printf '%s\n' "$*"; }
err() { printf '%s\n' "$*" >&2; }
ensure_tools() {
for t in kubectl; do
command -v "$t" >/dev/null || { err "Missing required tool: $t"; exit 1; }
done
}
ensure_namespace() {
if ! kubectl get namespace "$NAMESPACE" >/dev/null 2>&1; then
log "Creating namespace '$NAMESPACE' ..."
kubectl create namespace "$NAMESPACE" >/dev/null 2>&1 || true
fi
}
primary_kdc() {
if [[ -z "${KRB5_KDC}" ]]; then
echo ""
return 0
fi
printf '%s' "${KRB5_KDC}" | awk -F',' '{print $1}' | xargs
}
is_private_ip() {
local ip="$1"
if [[ "$ip" =~ ^10\.|^192\.168\.|^172\.(1[6-9]|2[0-9]|3[0-1])\.|^169\.254\. ]]; then
return 0
fi
return 1
}
default_port_forward_if_local() {
if [[ -n "${KRB5_AD_PORT_FORWARD}" ]]; then
return
fi
local kdc_host
kdc_host=$(primary_kdc)
if [[ -n "$kdc_host" ]] && is_private_ip "$kdc_host"; then
KRB5_AD_PORT_FORWARD=1
else
KRB5_AD_PORT_FORWARD=0
fi
}
ensure_krb5_conf_configmap() {
if kubectl -n "$NAMESPACE" get configmap prole-krb5-conf >/dev/null 2>&1; then
return
fi
log "ConfigMap prole-krb5-conf not found. Running init_openbao.sh update..."
if [[ -x "$SCRIPT_DIR/init_openbao.sh" ]]; then
KRB5_REALM="$KRB5_REALM" KRB5_KDC="$KRB5_KDC" KRB5_ADMIN="$KRB5_ADMIN" \
"$SCRIPT_DIR/init_openbao.sh" update || true
else
err "init_openbao.sh not found; cannot create krb5.conf ConfigMap."
exit 1
fi
}
get_krb5_conf() {
kubectl -n "$NAMESPACE" get configmap prole-krb5-conf -o jsonpath='{.data.krb5\.conf}' 2>/dev/null || true
}
apply_krb5_conf_to_cnpg_pods() {
local conf
conf=$(get_krb5_conf)
if [[ -z "$conf" ]]; then
err "Missing krb5.conf data in ConfigMap prole-krb5-conf."
return 1
fi
local pods
pods=$(kubectl -n "$NAMESPACE" get pods -l "cnpg.io/cluster=$CNPG_CLUSTER_NAME" -o jsonpath='{.items[*].metadata.name}' 2>/dev/null || true)
if [[ -z "$pods" ]]; then
err "No CNPG pods found for cluster '$CNPG_CLUSTER_NAME' in namespace '$NAMESPACE'."
return 1
fi
for pod in $pods; do
log "Updating /etc/krb5.conf in pod $pod ..."
if printf '%s' "$conf" | kubectl -n "$NAMESPACE" exec -i "$pod" -c postgres -- sh -c 'cat > /etc/krb5.conf'; then
log "[OK] Updated /etc/krb5.conf in $pod"
else
err "[WARN] Unable to write /etc/krb5.conf in $pod (permission?)."
err " Consider mounting ConfigMap prole-krb5-conf into the CNPG pods or updating the image."
fi
done
}
apply_ad_forwarder() {
local kdc_host
kdc_host=$(primary_kdc)
if [[ -z "$kdc_host" ]]; then
err "KRB5_KDC is required to set up the AD port forwarder."
exit 1
fi
local host_net_block=""
local dns_policy_block=""
if [[ "$KRB5_AD_PROXY_HOST_NETWORK" == "1" ]]; then
host_net_block=" hostNetwork: true"
dns_policy_block=" dnsPolicy: ClusterFirstWithHostNet"
fi
local tcp_cmd=""
local udp_cmd=""
local port
for port in $KRB5_AD_TCP_PORTS; do
tcp_cmd+="socat -d -d TCP-LISTEN:${port},fork,reuseaddr TCP:${kdc_host}:${port} & "
done
for port in $KRB5_AD_UDP_PORTS; do
udp_cmd+="socat -d -d UDP-LISTEN:${port},fork,reuseaddr UDP:${kdc_host}:${port} & "
done
local forward_cmd="${tcp_cmd}${udp_cmd}wait"
log "Applying AD DC forwarder '${KRB5_AD_PROXY_NAME}' in namespace '${NAMESPACE}' (target ${kdc_host}) ..."
cat <<EOF | kubectl apply -n "$NAMESPACE" -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${KRB5_AD_PROXY_NAME}
namespace: ${NAMESPACE}
spec:
replicas: 1
selector:
matchLabels:
app: ${KRB5_AD_PROXY_NAME}
template:
metadata:
labels:
app: ${KRB5_AD_PROXY_NAME}
spec:
${host_net_block}
${dns_policy_block}
containers:
- name: ad-forwarder
image: ${KRB5_AD_PROXY_IMAGE}
command: ["/bin/sh", "-c"]
args:
- >-
${forward_cmd}
ports:
$(for port in $KRB5_AD_TCP_PORTS; do printf " - containerPort: %s\n protocol: TCP\n" "$port"; done)
$(for port in $KRB5_AD_UDP_PORTS; do printf " - containerPort: %s\n protocol: UDP\n" "$port"; done)
---
apiVersion: v1
kind: Service
metadata:
name: ${KRB5_AD_SERVICE_NAME}
namespace: ${NAMESPACE}
spec:
selector:
app: ${KRB5_AD_PROXY_NAME}
ports:
$(
for port in $KRB5_AD_TCP_PORTS; do
name="tcp-${port}"
printf " - name: %s\n port: %s\n targetPort: %s\n protocol: TCP\n" "$name" "$port" "$port"
done
for port in $KRB5_AD_UDP_PORTS; do
name="udp-${port}"
printf " - name: %s\n port: %s\n targetPort: %s\n protocol: UDP\n" "$name" "$port" "$port"
done
)
EOF
kubectl -n "$NAMESPACE" rollout status deploy/${KRB5_AD_PROXY_NAME} --timeout=120s || true
}
cleanup_ad_forwarder() {
log "Removing AD forwarder resources (if present)..."
kubectl -n "$NAMESPACE" delete service "$KRB5_AD_SERVICE_NAME" --ignore-not-found
kubectl -n "$NAMESPACE" delete deployment "$KRB5_AD_PROXY_NAME" --ignore-not-found
}
initialize() {
ensure_tools
ensure_namespace
if [[ -z "${KRB5_REALM}" || -z "${KRB5_KDC}" ]]; then
err "ERROR: KRB5_REALM and KRB5_KDC must be set for Kerberos initialization."
exit 1
fi
ensure_krb5_conf_configmap
apply_krb5_conf_to_cnpg_pods
log "Kerberos initialization complete."
}
run_test() {
ensure_tools
ensure_namespace
if [[ -z "${KRB5_REALM}" || -z "${KRB5_KDC}" || -z "${KRB5_USER}" || -z "${KRB5_PASSWORD}" ]]; then
err "ERROR: Missing Kerberos configuration. Ensure KRB5_REALM, KRB5_KDC, KRB5_USER, KRB5_PASSWORD are set."
exit 1
fi
default_port_forward_if_local
local effective_kdc="$KRB5_KDC"
if [[ "${KRB5_AD_PORT_FORWARD}" == "1" ]]; then
apply_ad_forwarder
effective_kdc="${KRB5_AD_SERVICE_NAME}"
fi
log "Using KDC endpoint for test: ${effective_kdc}"
# Ensure krb5.conf configmap is updated for the test endpoint
if [[ -x "$SCRIPT_DIR/init_openbao.sh" ]]; then
KRB5_REALM="$KRB5_REALM" KRB5_KDC="$effective_kdc" KRB5_ADMIN="$KRB5_ADMIN" \
"$SCRIPT_DIR/init_openbao.sh" update || true
fi
if [[ -x "$SCRIPT_DIR/init_kerberos_test.sh" ]]; then
KRB5_REALM="$KRB5_REALM" KRB5_KDC="$effective_kdc" KRB5_ADMIN="$KRB5_ADMIN" \
KRB5_USER="$KRB5_USER" KRB5_PASSWORD="$KRB5_PASSWORD" \
"$SCRIPT_DIR/init_kerberos_test.sh" test
else
err "init_kerberos_test.sh not found."
exit 1
fi
}
status() {
ensure_tools
log "--- init_kerberos status ---"
log "Namespace: $NAMESPACE"
log "CNPG cluster: $CNPG_CLUSTER_NAME"
log "KRB5_REALM: ${KRB5_REALM:-<unset>}"
log "KRB5_KDC: ${KRB5_KDC:-<unset>}"
log "KRB5_USER: ${KRB5_USER:-<unset>}"
log "AD forwarder enabled: ${KRB5_AD_PORT_FORWARD:-<auto>}"
log "AD forwarder deployment: $KRB5_AD_PROXY_NAME"
log "AD forwarder service: $KRB5_AD_SERVICE_NAME"
if kubectl -n "$NAMESPACE" get configmap prole-krb5-conf >/dev/null 2>&1; then
log "[OK] ConfigMap prole-krb5-conf present"
else
log "[MISSING] ConfigMap prole-krb5-conf"
fi
if kubectl -n "$NAMESPACE" get deploy "$KRB5_AD_PROXY_NAME" >/dev/null 2>&1; then
log "[OK] AD forwarder deployment present"
else
log "[INFO] AD forwarder deployment not present"
fi
if kubectl -n "$NAMESPACE" get svc "$KRB5_AD_SERVICE_NAME" >/dev/null 2>&1; then
log "[OK] AD forwarder service present"
else
log "[INFO] AD forwarder service not present"
fi
}
case "$ACTION" in
initialize|configure|update|reload)
initialize
;;
test)
run_test
;;
status)
status
;;
cleanup)
ensure_tools
ensure_namespace
cleanup_ad_forwarder
;;
*)
err "Usage: $0 {initialize|configure|update|reload|test|status|cleanup}" >&2
exit 2
;;
esac

195
etc/init_kerberos_test.sh Executable file
View File

@ -0,0 +1,195 @@
#!/usr/bin/env bash
set -euo pipefail
# init_kerberos_test.sh
# Purpose:
# - Run Kerberos authentication checks inside a Kubernetes pod
# - Uses the prole-krb5-conf ConfigMap for krb5.conf
# Initialize SCRIPT_DIR
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
ACTION=${1:-test}
# Load env
if [[ -n "${PROLE_HOME:-}" && -f "$PROLE_HOME/env.sh" ]]; then
set --
# shellcheck disable=SC1090
source "$PROLE_HOME/env.sh"
elif [[ -f "$HOME/.prole/env.sh" ]]; then
set --
# shellcheck disable=SC1090
source "$HOME/.prole/env.sh"
fi
NAMESPACE=${NAMESPACE:-default}
KRB5_REALM=${KRB5_REALM:-${REALM:-}}
KRB5_KDC=${KRB5_KDC:-}
KRB5_USER=${KRB5_USER:-${KRB5_USERNAME:-}}
KRB5_PASSWORD=${KRB5_PASSWORD:-}
KRB5_TEST_IMAGE=${KRB5_TEST_IMAGE:-${PROLE_KRB_TEST_IMAGE:-}}
KEEP_POD=${KEEP_POD:-0}
KRB5_TEST_HOST_NETWORK=${KRB5_TEST_HOST_NETWORK:-0}
KRB5_TEST_DNS_POLICY=${KRB5_TEST_DNS_POLICY:-}
ensure_tools() {
for t in kubectl; do
command -v "$t" >/dev/null || { echo "Missing required tool: $t" >&2; exit 1; }
done
}
ensure_namespace() {
if ! kubectl get namespace "$NAMESPACE" >/dev/null 2>&1; then
echo "Creating namespace '$NAMESPACE' ..."
kubectl create namespace "$NAMESPACE" >/dev/null 2>&1 || true
fi
}
detect_image() {
if [[ -n "$KRB5_TEST_IMAGE" ]]; then
echo "$KRB5_TEST_IMAGE"
return
fi
local version_file
if [[ -n "${PROLE_HOME:-}" && -f "$PROLE_HOME/conf/postgresql/.version" ]]; then
version_file="$PROLE_HOME/conf/postgresql/.version"
elif [[ -f "$SCRIPT_DIR/../conf/postgresql/.version" ]]; then
version_file="$SCRIPT_DIR/../conf/postgresql/.version"
else
version_file=""
fi
if [[ -n "$version_file" && -f "$version_file" ]]; then
echo "prole-db:$(cat "$version_file" | tr -d '[:space:]')"
else
echo "prole-db:latest"
fi
}
ensure_configmap() {
if kubectl -n "$NAMESPACE" get configmap prole-krb5-conf >/dev/null 2>&1; then
return
fi
echo "ConfigMap prole-krb5-conf not found. Running init_openbao.sh update..."
if [[ -x "$SCRIPT_DIR/init_openbao.sh" ]]; then
KRB5_REALM="$KRB5_REALM" KRB5_KDC="$KRB5_KDC" "$SCRIPT_DIR/init_openbao.sh" update || true
fi
}
create_test_pod() {
local pod_name="$1"
local image="$2"
local host_net_block=""
if [[ "$KRB5_TEST_HOST_NETWORK" == "1" ]]; then
local dns_policy
dns_policy=${KRB5_TEST_DNS_POLICY:-Default}
host_net_block=$' hostNetwork: true\n dnsPolicy: '"$dns_policy"$'\n'
fi
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: ${pod_name}
namespace: ${NAMESPACE}
labels:
app: prole-kerberos-test
spec:
${host_net_block} restartPolicy: Never
containers:
- name: kerberos-test
image: ${image}
command: ["sleep","3600"]
volumeMounts:
- name: krb5-conf
mountPath: /etc/krb5.conf
subPath: krb5.conf
volumes:
- name: krb5-conf
configMap:
name: prole-krb5-conf
items:
- key: krb5.conf
path: krb5.conf
EOF
}
run_test() {
ensure_tools
ensure_namespace
if [[ -z "$KRB5_REALM" || -z "$KRB5_USER" || -z "$KRB5_PASSWORD" || -z "$KRB5_KDC" ]]; then
echo "ERROR: Missing Kerberos configuration. Ensure KRB5_REALM, KRB5_KDC, KRB5_USER, KRB5_PASSWORD are set." >&2
exit 1
fi
ensure_configmap
local image pod_name
image=$(detect_image)
pod_name="prole-krb-test-$(date +%s)"
echo "Creating Kerberos test pod '$pod_name' in namespace '$NAMESPACE' using image '$image'..."
create_test_pod "$pod_name" "$image"
echo "Waiting for pod to become ready..."
if ! kubectl -n "$NAMESPACE" wait --for=condition=Ready pod/"$pod_name" --timeout=90s; then
echo "Pod did not become ready. Describing pod:"
kubectl -n "$NAMESPACE" describe pod "$pod_name" || true
if [[ "$KEEP_POD" != "1" ]]; then
kubectl -n "$NAMESPACE" delete pod "$pod_name" --ignore-not-found
fi
exit 1
fi
echo "Checking for kinit in pod..."
if ! kubectl -n "$NAMESPACE" exec "$pod_name" -- sh -c 'command -v kinit >/dev/null 2>&1'; then
echo "ERROR: kinit not found in test pod image '$image'." >&2
if [[ "$KEEP_POD" != "1" ]]; then
kubectl -n "$NAMESPACE" delete pod "$pod_name" --ignore-not-found
fi
exit 1
fi
echo "Running kinit for ${KRB5_USER}@${KRB5_REALM} ..."
if ! printf '%s\n' "$KRB5_PASSWORD" | kubectl -n "$NAMESPACE" exec -i "$pod_name" -- kinit "${KRB5_USER}@${KRB5_REALM}"; then
echo "ERROR: kinit failed for ${KRB5_USER}@${KRB5_REALM}." >&2
if [[ "$KEEP_POD" != "1" ]]; then
kubectl -n "$NAMESPACE" delete pod "$pod_name" --ignore-not-found
fi
exit 1
fi
echo "Kerberos ticket cache:"
kubectl -n "$NAMESPACE" exec "$pod_name" -- klist || true
if [[ "${REALM_JOIN:-0}" == "1" ]]; then
echo "Attempting realm join inside test pod..."
if kubectl -n "$NAMESPACE" exec "$pod_name" -- sh -c 'command -v realm >/dev/null 2>&1'; then
printf '%s\n' "$KRB5_PASSWORD" | kubectl -n "$NAMESPACE" exec -i "$pod_name" -- realm join --user "$KRB5_USER" "$KRB5_REALM" || true
else
echo "realm command not found in image; skipping realm join."
fi
fi
if [[ "$KEEP_POD" != "1" ]]; then
kubectl -n "$NAMESPACE" delete pod "$pod_name" --ignore-not-found
else
echo "KEEP_POD=1 set; leaving test pod running: $pod_name"
fi
}
case "$ACTION" in
test)
run_test
;;
cleanup)
ensure_tools
echo "Deleting kerberos test pods in namespace '$NAMESPACE'..."
kubectl -n "$NAMESPACE" delete pod -l app=prole-kerberos-test --ignore-not-found
;;
*)
echo "Usage: $0 {test|cleanup}" >&2
exit 2
;;
esac

187
etc/init_prole-db-backup.sh Executable file
View File

@ -0,0 +1,187 @@
#!/usr/bin/env bash
set -euo pipefail
# init_prole-db-backup.sh
# Purpose:
# - Configure CloudNative-PG to backup to Garage (S3-compatible)
# - Create initial backup
# Initialize SCRIPT_DIR
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
ACTION=${1:-start}
# Load env
if [[ -n "${PROLE_HOME:-}" && -f "$PROLE_HOME/env.sh" ]]; then
set --
# shellcheck disable=SC1090
source "$PROLE_HOME/env.sh"
elif [[ -f "$HOME/.prole/env.sh" ]]; then
set --
# shellcheck disable=SC1090
source "$HOME/.prole/env.sh"
fi
NAMESPACE=${NAMESPACE:-default}
CNPG_CLUSTER_NAME=${CNPG_CLUSTER_NAME:-prole-db}
GARAGE_NAME=${GARAGE_NAME:-garage}
GARAGE_BACKUP_BUCKET=${GARAGE_BACKUP_BUCKET:-prole-db-backups}
GARAGE_BACKUP_KEY_NAME=${GARAGE_BACKUP_KEY_NAME:-prole-db-backup}
GARAGE_BACKUP_SECRET_NAME=${GARAGE_BACKUP_SECRET_NAME:-prole-db-barman-s3}
GARAGE_S3_ENDPOINT=${GARAGE_S3_ENDPOINT:-http://$GARAGE_NAME.$NAMESPACE.svc.cluster.local:3900}
RUN_FIRST_BACKUP=${RUN_FIRST_BACKUP:-1}
usage() {
cat <<USAGE
Usage: $0 [start|backup|status]
Actions:
start Configure Garage-backed backups and run initial backup
backup Trigger a new backup now
status Show backup resources
USAGE
exit 1
}
ensure_tools() {
for t in kubectl; do
command -v "$t" >/dev/null || { echo "Missing required tool: $t" >&2; exit 1; }
done
}
ensure_namespace() {
if ! kubectl get namespace "$NAMESPACE" >/dev/null 2>&1; then
echo "Creating namespace '$NAMESPACE' ..."
kubectl create namespace "$NAMESPACE" >/dev/null 2>&1 || true
fi
}
ensure_cluster() {
if ! kubectl get cluster "$CNPG_CLUSTER_NAME" -n "$NAMESPACE" >/dev/null 2>&1; then
echo "ERROR: CNPG cluster '$CNPG_CLUSTER_NAME' not found in namespace '$NAMESPACE'." >&2
exit 1
fi
}
get_garage_pod() {
kubectl get pods -n "$NAMESPACE" -l "app=$GARAGE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true
}
garage_exec() {
local pod
pod=$(get_garage_pod)
if [[ -z "$pod" ]]; then
echo "ERROR: Garage pod not found in namespace '$NAMESPACE'." >&2
exit 1
fi
kubectl exec -n "$NAMESPACE" "$pod" -- /garage "$@"
}
parse_key_output() {
local output="$1"
local access_key secret_key
access_key=$(echo "$output" | sed -nE 's/^(Access key ID|Key ID):[[:space:]]+//p' | head -n1)
secret_key=$(echo "$output" | sed -nE 's/^(Secret access key|Secret key):[[:space:]]+//p' | head -n1)
if [[ -z "$access_key" || -z "$secret_key" ]]; then
return 1
fi
printf "%s\n%s" "$access_key" "$secret_key"
}
ensure_garage_bucket_and_key() {
echo "Ensuring Garage bucket and access key for backups ..."
local key_info parsed access_key secret_key
if key_info=$(garage_exec key info --show-secret "$GARAGE_BACKUP_KEY_NAME" 2>/dev/null); then
:
else
key_info=$(garage_exec key create "$GARAGE_BACKUP_KEY_NAME")
fi
if ! parsed=$(parse_key_output "$key_info"); then
echo "ERROR: Unable to parse Garage key output." >&2
echo "$key_info" >&2
exit 1
fi
access_key=$(echo "$parsed" | sed -n '1p')
secret_key=$(echo "$parsed" | sed -n '2p')
if ! garage_exec bucket info "$GARAGE_BACKUP_BUCKET" >/dev/null 2>&1; then
garage_exec bucket create "$GARAGE_BACKUP_BUCKET"
fi
garage_exec bucket allow --read --write --owner --key "$GARAGE_BACKUP_KEY_NAME" "$GARAGE_BACKUP_BUCKET" || true
echo "Creating/updating Kubernetes secret '$GARAGE_BACKUP_SECRET_NAME' ..."
kubectl create secret generic "$GARAGE_BACKUP_SECRET_NAME" -n "$NAMESPACE" \
--from-literal=ACCESS_KEY_ID="$access_key" \
--from-literal=SECRET_ACCESS_KEY="$secret_key" \
--dry-run=client -o yaml | kubectl apply -f -
}
configure_cnpg_backup() {
echo "Configuring CNPG backup to use Garage bucket '$GARAGE_BACKUP_BUCKET' ..."
kubectl patch cluster "$CNPG_CLUSTER_NAME" -n "$NAMESPACE" --type merge -p "{
\"spec\": {
\"backup\": {
\"barmanObjectStore\": {
\"destinationPath\": \"s3://$GARAGE_BACKUP_BUCKET/\",
\"endpointURL\": \"$GARAGE_S3_ENDPOINT\",
\"s3Credentials\": {
\"accessKeyId\": {\"name\": \"$GARAGE_BACKUP_SECRET_NAME\", \"key\": \"ACCESS_KEY_ID\"},
\"secretAccessKey\": {\"name\": \"$GARAGE_BACKUP_SECRET_NAME\", \"key\": \"SECRET_ACCESS_KEY\"}
},
\"wal\": {\"compression\": \"gzip\"},
\"data\": {\"compression\": \"gzip\"}
},
\"retentionPolicy\": \"30d\"
}
}
}"
}
trigger_backup() {
local backup_name
backup_name="${CNPG_CLUSTER_NAME}-backup-$(date +%Y%m%d%H%M%S)"
echo "Triggering backup $backup_name ..."
kubectl apply -n "$NAMESPACE" -f - <<BACKUP
apiVersion: postgresql.cnpg.io/v1
kind: Backup
metadata:
name: $backup_name
spec:
cluster:
name: $CNPG_CLUSTER_NAME
BACKUP
}
status() {
ensure_tools
echo "Backups for cluster '$CNPG_CLUSTER_NAME' in namespace '$NAMESPACE':"
kubectl get backup -n "$NAMESPACE" | grep "$CNPG_CLUSTER_NAME" || true
}
case "$ACTION" in
start)
ensure_tools
ensure_namespace
ensure_cluster
ensure_garage_bucket_and_key
configure_cnpg_backup
if [[ "$RUN_FIRST_BACKUP" == "1" ]]; then
trigger_backup
fi
;;
backup)
ensure_tools
ensure_namespace
ensure_cluster
trigger_backup
;;
status)
status
;;
*)
usage
;;
esac

View File

@ -86,6 +86,12 @@ start() {
exit 1
fi
# 4. garage is configured
if ! kubectl get statefulset garage -n "$NAMESPACE" >/dev/null 2>&1 && ! kubectl get deployment garage -n "$NAMESPACE" >/dev/null 2>&1; then
echo "ERROR: Garage is not deployed." >&2
exit 1
fi
# Ensure cluster exists before attempting to patch image
if ! kubectl get cluster "$CNPG_CLUSTER_NAME" -n "$NAMESPACE" >/dev/null 2>&1; then
echo "Cluster '$CNPG_CLUSTER_NAME' not found in namespace '$NAMESPACE'. Applying manifest..."
@ -318,7 +324,8 @@ rollout() {
}
backup() {
echo "Backup - tbd, when we configure s3 or other block store"
ensure_tools
bash "$SCRIPT_DIR/init_prole-db-backup.sh" start
}
reset() {

95
final_comprehensive_test.sh Executable file
View File

@ -0,0 +1,95 @@
#!/bin/bash
echo "=========================================="
echo " Prole Installer - Final Verification"
echo "=========================================="
echo ""
# Test suite
TESTS_PASSED=0
TESTS_TOTAL=0
run_test() {
TESTS_TOTAL=$((TESTS_TOTAL + 1))
echo -n "[$TESTS_TOTAL] $1... "
shift
if "$@" > /dev/null 2>&1; then
echo "✓"
TESTS_PASSED=$((TESTS_PASSED + 1))
return 0
else
echo "✗"
return 1
fi
}
# Core functionality tests
run_test "Install.py imports" python3 -c "from install import ProleController, get_resource_path"
run_test "Ncurses modules" python3 -c "from installer.ncurses_installer import run_ncurses_installer"
run_test "Command-line interface" python3 install.py --help
run_test "Makefile" make help
# Resource tests
run_test "Image resources" python3 -c "
from pathlib import Path
import sys
sys.path.insert(0, str(Path.cwd()))
from install import get_resource_path
for img in ['img/proleIcon.png', 'img/proleLogo.png', 'img/proleLogoSepia.png']:
if not get_resource_path(img).exists():
sys.exit(1)
"
run_test "Binary executable" test -x prole-net/prole-scan
run_test "App bundle" test -d "prole-app/dist/Prole Tools.app"
# Build system tests
run_test "Spec generator" python3 scripts/generate_spec.py
run_test "Icon conversion" make build/prole.icns
# Verify spec includes everything
echo -n "[$((TESTS_TOTAL + 1))] Spec includes all resources... "
TESTS_TOTAL=$((TESTS_TOTAL + 1))
if grep -q "prole-app/dist/Prole Tools.app" installer.spec && \
grep -q "prole-net/prole-scan" installer.spec && \
grep -q "img.*img" installer.spec; then
echo "✓"
TESTS_PASSED=$((TESTS_PASSED + 1))
else
echo "✗"
fi
# Summary
echo ""
echo "=========================================="
echo " Test Results: $TESTS_PASSED/$TESTS_TOTAL passed"
echo "=========================================="
echo ""
if [ $TESTS_PASSED -eq $TESTS_TOTAL ]; then
echo "✓ All tests passed!"
echo ""
echo "Features ready:"
echo " ✓ Ncurses terminal interface"
echo " ✓ Automatic display detection"
echo " ✓ Image resources embedded"
echo " ✓ Binary executables embedded (prole-scan)"
echo " ✓ App bundles embedded (Prole Tools.app)"
echo " ✓ Build system configured"
echo ""
echo "Embedded resources:"
echo " • Images: ~11 MB"
echo " • prole-scan: 6.8 MB (universal)"
echo " • Prole Tools.app: ~12 MB"
echo " • Expected final size: ~50-100 MB"
echo ""
echo "Next steps:"
echo " 1. Build: make package"
echo " 2. Test: ./dist/Prole\\ Installer.app/Contents/MacOS/prole-installer"
echo " 3. Install: cp -r 'dist/Prole Installer.app' /Applications/"
echo ""
exit 0
else
echo "✗ Some tests failed"
exit 1
fi

134
final_test.sh Executable file
View File

@ -0,0 +1,134 @@
#!/bin/bash
echo "=============================================="
echo " FINAL COMPREHENSIVE TEST"
echo " Prole Database Installer"
echo "=============================================="
echo ""
TESTS_PASSED=0
TESTS_TOTAL=0
test_item() {
TESTS_TOTAL=$((TESTS_TOTAL + 1))
echo -n "[$TESTS_TOTAL] $1... "
shift
if "$@" > /dev/null 2>&1; then
echo "✓"
TESTS_PASSED=$((TESTS_PASSED + 1))
return 0
else
echo "✗"
return 1
fi
}
# Core Features
echo "CORE FEATURES"
test_item "Install.py imports" python3 -c "from install import ProleController, get_resource_path"
test_item "Ncurses interface" python3 -c "from installer.ncurses_installer import run_ncurses_installer"
test_item "Display auto-detection" python3 -c "from install import has_display"
test_item "CLI arguments" python3 install.py --help
# Build System
echo ""
echo "BUILD SYSTEM"
test_item "Makefile" make help
test_item "Spec generator" python3 scripts/generate_spec.py
test_item "Icon conversion" make build/prole.icns
# Image Resources
echo ""
echo "IMAGE RESOURCES"
test_item "proleIcon.png" test -f img/proleIcon.png
test_item "proleLogo.png" test -f img/proleLogo.png
test_item "proleLogoSepia.png" test -f img/proleLogoSepia.png
# Binary Resources
echo ""
echo "BINARY RESOURCES"
test_item "prole-scan exists" test -f prole-net/prole-scan
test_item "prole-scan executable" test -x prole-net/prole-scan
# App Bundle
echo ""
echo "APP BUNDLE"
test_item "Prole Tools.app exists" test -d "prole-app/dist/Prole Tools.app"
# Docker Build Context
echo ""
echo "DOCKER BUILD CONTEXT"
test_item "prole-db directory" test -d prole-db
test_item "Dockerfile exists" test -f prole-db/Dockerfile
# Spec Verification
echo ""
echo "SPEC FILE VERIFICATION"
python3 scripts/generate_spec.py > /dev/null 2>&1
test_item "Spec includes img" grep -q "img" installer.spec
test_item "Spec includes prole-db" grep -q "prole-db" installer.spec
test_item "Spec includes prole-scan" grep -q "prole-scan" installer.spec
test_item "Spec includes Prole Tools" grep -q "Prole Tools" installer.spec
# Resource Path Tests
echo ""
echo "RESOURCE PATH RESOLUTION"
test_item "Image paths work" python3 -c "
from pathlib import Path
import sys
sys.path.insert(0, str(Path.cwd()))
from install import get_resource_path
assert get_resource_path('img/proleIcon.png').exists()
"
test_item "Binary paths work" python3 -c "
from pathlib import Path
import sys
sys.path.insert(0, str(Path.cwd()))
from install import get_resource_path
assert get_resource_path('prole-net/prole-scan').exists()
"
test_item "Docker context works" python3 -c "
from pathlib import Path
import sys
sys.path.insert(0, str(Path.cwd()))
from install import get_resource_path
assert get_resource_path('prole-db').exists()
"
# Summary
echo ""
echo "=============================================="
echo " RESULTS: $TESTS_PASSED/$TESTS_TOTAL PASSED"
echo "=============================================="
echo ""
if [ $TESTS_PASSED -eq $TESTS_TOTAL ]; then
echo "✓✓✓ ALL TESTS PASSED ✓✓✓"
echo ""
echo "READY TO BUILD:"
echo " make package"
echo ""
echo "FEATURES IMPLEMENTED:"
echo " ✓ Ncurses terminal interface"
echo " ✓ Automatic display detection"
echo " ✓ Static universal binary build system"
echo " ✓ Image resources embedded"
echo " ✓ prole-scan binary embedded"
echo " ✓ Prole Tools.app embedded"
echo " ✓ Docker build context embedded"
echo " ✓ Docker build uses ~/.prole/build (writable)"
echo ""
echo "PACKAGE WILL INCLUDE:"
echo " • Images (~11 MB)"
echo " • prole-scan (6.8 MB)"
echo " • Prole Tools.app (~12 MB)"
echo " • prole-db build context (~1-5 MB)"
echo " • Python runtime + deps (~30-50 MB)"
echo " • Total: ~50-100 MB"
echo ""
exit 0
else
echo "✗ SOME TESTS FAILED"
echo "Fix issues before building package"
exit 1
fi

1125
install.py

File diff suppressed because it is too large Load Diff

View File

@ -163,15 +163,6 @@ DEPENDENCIES = [
"check_cmd": "k3d --version",
"bin": "k3d",
},
{
"id": "ollama",
"name": "Ollama",
"description": "Local LLM runtime used by the deployment agent",
"url": "https://ollama.com",
"install_cmd": "brew install ollama",
"check_cmd": "ollama --version",
"bin": "ollama",
},
]

View File

@ -442,43 +442,6 @@ def _ensure_docker_running(timeout: int = 120) -> bool:
return False
def _ensure_ollama_running(timeout: int = 60) -> bool:
"""Start Ollama serve if not running; return True when the HTTP API is reachable."""
def api_ok() -> bool:
try:
# Using curl if available
r = subprocess.run(["bash", "-lc", "curl -s http://127.0.0.1:11434/api/tags >/dev/null"], timeout=8)
return r.returncode == 0
except Exception:
return False
if api_ok():
return True
# Prefer Homebrew service if available
try:
has_brew = subprocess.run(["bash", "-lc", "command -v brew >/dev/null"], timeout=5).returncode == 0
if has_brew:
subprocess.run(["bash", "-lc", "brew services start ollama"], timeout=20)
except Exception:
pass
# Fallback: nohup ollama serve &
if not api_ok():
try:
subprocess.Popen(["bash", "-lc", "nohup ollama serve >/tmp/ollama.log 2>&1 &"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except Exception:
pass
import time as _t
start = _t.time()
while _t.time() - start < timeout:
if api_ok():
return True
_t.sleep(2)
return False
def _ensure_workstation_container() -> bool:
"""Run or start the prole-workstation container with required ports."""
version = ws.get_workstation_version(cfg.PROJECT_ROOT)

View File

@ -1,15 +0,0 @@
#!/bin/bash
# Prole Installer: Ollama
# Installs Ollama via Homebrew if missing
set -euo pipefail
echo "[prole] Checking Ollama..."
if ! command -v ollama >/dev/null 2>&1; then
echo "[prole] Installing Ollama via brew..."
brew install ollama
else
echo "[prole] Ollama already installed"
fi
echo "[prole] Ollama OK"

View File

@ -2,7 +2,7 @@ apiVersion: v1
kind: ConfigMap
metadata:
name: prole-krb5-conf
namespace: prole-chrisfu-a05806
namespace: prole-chrisfu-deadbeef
data:
krb5.conf: |
[libdefaults]

View File

@ -0,0 +1,43 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: garage-config
labels:
app: garage
data:
garage.toml: |
replication_factor = 1
consistency_mode = "consistent"
metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
metadata_snapshots_dir = "/var/lib/garage/snapshots"
metadata_fsync = true
data_fsync = false
disable_scrub = false
use_local_tz = false
db_engine = "lmdb"
block_size = "1M"
block_ram_buffer_max = "256MiB"
block_max_concurrent_reads = 16
block_max_concurrent_writes_per_request = 10
compression_level = 1
rpc_secret_file = "/var/lib/garage/secrets/rpc_secret"
rpc_bind_addr = "[::]:3901"
rpc_public_addr = "garage:3901"
bootstrap_peers = []
[s3_api]
api_bind_addr = "[::]:3900"
s3_region = "garage"
root_domain = ".s3.garage"
[admin]
api_bind_addr = "0.0.0.0:3903"
metrics_require_token = true
metrics_token_file = "/var/lib/garage/secrets/metrics_token"
admin_token_file = "/var/lib/garage/secrets/admin_token"

View File

@ -0,0 +1,20 @@
apiVersion: v1
kind: Service
metadata:
name: garage
labels:
app: garage
spec:
type: ClusterIP
selector:
app: garage
ports:
- name: s3
port: 3900
targetPort: s3
- name: rpc
port: 3901
targetPort: rpc
- name: admin
port: 3903
targetPort: admin

View File

@ -0,0 +1,71 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: garage
labels:
app: garage
spec:
serviceName: garage
replicas: 1
selector:
matchLabels:
app: garage
template:
metadata:
labels:
app: garage
spec:
containers:
- name: garage
image: dxflrs/garage:v1.3.1
imagePullPolicy: IfNotPresent
command:
- /garage
args:
- server
env:
- name: GARAGE_ALLOW_WORLD_READABLE_SECRETS
value: "true"
ports:
- name: s3
containerPort: 3900
- name: rpc
containerPort: 3901
- name: admin
containerPort: 3903
readinessProbe:
tcpSocket:
port: s3
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: s3
initialDelaySeconds: 20
periodSeconds: 20
volumeMounts:
- name: config
mountPath: /etc/garage.toml
subPath: garage.toml
readOnly: true
- name: secrets
mountPath: /var/lib/garage/secrets
readOnly: true
- name: data
mountPath: /var/lib/garage
volumes:
- name: config
configMap:
name: garage-config
- name: secrets
secret:
secretName: garage-secrets
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi

View File

@ -2,6 +2,9 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- garage-configmap.yaml
- garage-statefulset.yaml
- garage-service.yaml
- prole-db.yaml
- prole-db-postgres-service.yaml
- prole-configmap.yaml

55
network_description.txt Normal file
View File

@ -0,0 +1,55 @@
Network Discovery Summary:
Primary Router: 10.0.0.1 (eero_5d:50:f2)
DNS Servers:
Detected Devices:
- 10.0.0.66 [72:ff:7f:82:e9:ed] (72:ff:7f:82:e9:ed): Ports [22, 445, 5900, 11434, 88], Services: ['SSH', 'VNC', 'SMB/CIFS (Possible Windows/AD)', 'Ollama', 'Active Directory Related']
- 10.0.0.189 [00:17:88:a3:2f:cc] (PhilipsLight_a3:2f:cc): Ports [80, 443], Services: ['Web Server']
- 10.0.0.1 [9c:57:bc:5d:50:f2] (eero_5d:50:f2): Ports [53], Services: ['DNS']
- 10.0.0.33 [ec:b5:fa:b0:76:e4] (PhilipsLight_b0:76:e4): Ports [80, 443], Services: ['Web Server']
- 10.0.0.46 [d4:f7:d5:40:ab:17] (SonyInteract_40:ab:17): Ports [], Services: []
- 10.0.0.163 [b0:8b:a8:f8:96:92] (AmazonTechno_f8:96:92): Ports [], Services: []
- 10.0.0.203 [00:11:32:3b:2f:08] (Synology_3b:2f:08): Ports [22, 80, 443, 2049, 445], Services: ['SSH', 'NFS', 'Web Server', 'SMB/CIFS (Possible Windows/AD)']
- 10.0.0.100 [24:fc:e5:51:cf:74] (SamsungElect_51:cf:74): Ports [], Services: []
- 10.0.0.73 [4c:a9:19:b3:12:f8] (TuyaSmart_b3:12:f8): Ports [], Services: []
- 10.0.0.62 [b8:06:0d:b2:4f:4c] (TuyaSmart_b2:4f:4c): Ports [], Services: []
- 10.0.0.107 [b8:06:0d:b7:7c:56] (TuyaSmart_b7:7c:56): Ports [], Services: []
- 10.0.0.66\ [72:ff:7f:82:e9:ed] (72:ff:7f:82:e9:ed): Ports [], Services: []
- 10.0.0.2 [Unknown] (Unknown): Ports [], Services: []
- 10.0.0.3 [Unknown] (Unknown): Ports [22, 53, 2049, 445, 5900, 88, 389, 636], Services: ['DNS', 'SSH', 'NFS', 'VNC', 'SMB/CIFS (Possible Windows/AD)', 'Active Directory Related']
- 10.0.0.4 [Unknown] (Unknown): Ports [22, 53, 443, 2049], Services: ['DNS', 'SSH', 'NFS', 'Web Server']
- 10.0.0.5 [Unknown] (Unknown): Ports [22, 53, 443, 2049, 5900], Services: ['DNS', 'SSH', 'NFS', 'VNC', 'Web Server']
- 10.0.0.26 [Unknown] (Unknown): Ports [], Services: []
- 10.0.0.35 [Unknown] (Unknown): Ports [22], Services: ['SSH']
- 10.0.0.36 [Unknown] (Unknown): Ports [22], Services: ['SSH']
- 10.0.0.37 [Unknown] (Unknown): Ports [80], Services: ['Web Server']
- 10.0.0.41 [Unknown] (Unknown): Ports [22], Services: ['SSH']
- 10.0.0.45 [Unknown] (Unknown): Ports [80, 443], Services: ['Web Server']
- 10.0.0.48 [Unknown] (Unknown): Ports [], Services: []
- 10.0.0.58 [Unknown] (Unknown): Ports [], Services: []
- 10.0.0.95 [Unknown] (Unknown): Ports [22, 53, 443, 2049, 5900], Services: ['DNS', 'SSH', 'NFS', 'VNC', 'Web Server']
- 10.0.0.99 [Unknown] (Unknown): Ports [], Services: []
- 10.0.0.111 [Unknown] (Unknown): Ports [], Services: []
- 10.0.0.112 [Unknown] (Unknown): Ports [80, 443], Services: ['Web Server']
- 10.0.0.113 [Unknown] (Unknown): Ports [], Services: []
- 10.0.0.116 [Unknown] (Unknown): Ports [445], Services: ['SMB/CIFS (Possible Windows/AD)']
- 10.0.0.117 [Unknown] (Unknown): Ports [80], Services: ['Web Server']
- 10.0.0.123 [Unknown] (Unknown): Ports [80], Services: ['Web Server']
- 10.0.0.124 [Unknown] (Unknown): Ports [80], Services: ['Web Server']
- 10.0.0.127 [Unknown] (Unknown): Ports [22], Services: ['SSH']
- 10.0.0.128 [Unknown] (Unknown): Ports [22, 445, 5900, 88], Services: ['SSH', 'VNC', 'SMB/CIFS (Possible Windows/AD)', 'Active Directory Related']
- 10.0.0.130 [Unknown] (Unknown): Ports [53], Services: ['DNS']
- 10.0.0.143 [Unknown] (Unknown): Ports [53], Services: ['DNS']
- 10.0.0.145 [Unknown] (Unknown): Ports [], Services: []
- 10.0.0.166 [Unknown] (Unknown): Ports [445, 5900, 88], Services: ['VNC', 'SMB/CIFS (Possible Windows/AD)', 'Active Directory Related']
- 10.0.0.170 [Unknown] (Unknown): Ports [], Services: []
- 10.0.0.175 [Unknown] (Unknown): Ports [80], Services: ['Web Server']
- 10.0.0.180 [Unknown] (Unknown): Ports [], Services: []
- 10.0.0.188 [Unknown] (Unknown): Ports [80, 443], Services: ['Web Server']
- 10.0.0.193 [Unknown] (Unknown): Ports [], Services: []
- 10.0.0.196 [Unknown] (Unknown): Ports [53], Services: ['DNS']
- 10.0.0.199 [Unknown] (Unknown): Ports [], Services: []
- 10.0.0.204 [Unknown] (Unknown): Ports [22, 3389, 445, 5900, 11434], Services: ['SSH', 'VNC', 'SMB/CIFS (Possible Windows/AD)', 'RDP (Windows)', 'Ollama']
- 10.0.0.205 [Unknown] (Unknown): Ports [22, 445, 5900, 88], Services: ['SSH', 'VNC', 'SMB/CIFS (Possible Windows/AD)', 'Active Directory Related']
- 10.0.0.206 [Unknown] (Unknown): Ports [80], Services: ['Web Server']
- 10.0.0.207 [Unknown] (Unknown): Ports [22, 5900], Services: ['SSH', 'VNC']
Ollama Instances found at: 10.0.0.204, 10.0.0.66

1298
retropie_facts.json Normal file

File diff suppressed because it is too large Load Diff

94
test_all_prole_home_fixes.sh Executable file
View File

@ -0,0 +1,94 @@
#!/bin/bash
echo "=============================================="
echo " Comprehensive .prole Directory Tests"
echo "=============================================="
echo ""
TESTS_PASSED=0
TESTS_TOTAL=0
test_item() {
TESTS_TOTAL=$((TESTS_TOTAL + 1))
echo -n "[$TESTS_TOTAL] $1... "
shift
if "$@" > /dev/null 2>&1; then
echo "✓"
TESTS_PASSED=$((TESTS_PASSED + 1))
return 0
else
echo "✗"
return 1
fi
}
echo "DIRECTORY STRUCTURE"
test_item "Create .prole directory" python3 -c "from pathlib import Path; (Path.home() / '.prole').mkdir(exist_ok=True)"
test_item "Create build directory" python3 -c "from pathlib import Path; (Path.home() / '.prole' / 'build').mkdir(parents=True, exist_ok=True)"
test_item "Create scan directory" python3 -c "from pathlib import Path; (Path.home() / '.prole' / 'scan').mkdir(parents=True, exist_ok=True)"
echo ""
echo "DOCKER BUILD FIX"
test_item "prole-db directory exists" test -d prole-db
test_item "Dockerfile exists" test -f prole-db/Dockerfile
test_item "Build directory creation" python3 -c "from pathlib import Path; d = Path.home() / '.prole' / 'build' / 'prole-db'; d.mkdir(parents=True, exist_ok=True); assert d.exists()"
test_item "Build directory writable" python3 -c "from pathlib import Path; f = Path.home() / '.prole' / 'build' / 'test.txt'; f.write_text('test'); f.unlink()"
echo ""
echo "NETWORK SCAN FIX"
test_item "prole-scan binary exists" test -x prole-net/prole-scan
test_item "Scan directory creation" python3 -c "from pathlib import Path; d = Path.home() / '.prole' / 'scan'; d.mkdir(parents=True, exist_ok=True); assert d.exists()"
test_item "Scan directory writable" python3 -c "from pathlib import Path; f = Path.home() / '.prole' / 'scan' / 'test.txt'; f.write_text('test'); f.unlink()"
echo ""
echo "CODE VERIFICATION"
test_item "Build uses .prole/build" grep -q "prole_home / \"build\"" install.py
test_item "Scan uses .prole/scan" grep -q "prole_home / \"scan\"" install.py
test_item "Docker build copies context" grep -q "shutil.copytree(source_dir, build_dir)" install.py
test_item "Scan runs with cwd" grep -q "cwd=str(scan_dir)" install.py
echo ""
echo "SPEC FILE VERIFICATION"
python3 scripts/generate_spec.py > /dev/null 2>&1
test_item "Spec includes prole-db" grep -q "prole-db" installer.spec
test_item "Spec includes prole-scan" grep -q "prole-scan" installer.spec
# Cleanup
echo ""
echo "CLEANUP"
python3 << 'PYTEST'
from pathlib import Path
import shutil
prole_home = Path.home() / ".prole"
if prole_home.exists():
shutil.rmtree(prole_home)
print(" ✓ Cleaned up test directories")
PYTEST
echo ""
echo "=============================================="
echo " RESULTS: $TESTS_PASSED/$TESTS_TOTAL PASSED"
echo "=============================================="
echo ""
if [ $TESTS_PASSED -eq $TESTS_TOTAL ]; then
echo "✓✓✓ ALL PROLE HOME TESTS PASSED ✓✓✓"
echo ""
echo "FIXES IMPLEMENTED:"
echo " ✓ Docker build uses ~/.prole/build/prole-db/"
echo " ✓ Network scan uses ~/.prole/scan/"
echo " ✓ Both work from read-only PyInstaller bundle"
echo ""
echo "DIRECTORY STRUCTURE:"
echo " ~/.prole/"
echo " ├── build/"
echo " │ └── prole-db/ (Docker build context)"
echo " └── scan/ (Network scan working dir)"
echo ""
echo "DISK USAGE: ~2-6 MB total"
echo ""
exit 0
else
echo "✗ SOME TESTS FAILED"
exit 1
fi

42
test_build_system.sh Executable file
View File

@ -0,0 +1,42 @@
#!/bin/bash
set -e
echo "=== Prole Installer Build System Test ==="
echo ""
echo "1. Testing Makefile..."
make help > /dev/null && echo " ✓ Makefile works"
echo ""
echo "2. Testing icon file..."
test -f img/proleIcon.png && echo " ✓ Icon file exists"
echo ""
echo "3. Testing Python modules..."
python3 -c "from install import ProleController; ProleController('.')" && echo " ✓ ProleController works"
python3 -c "from installer.ncurses_installer import run_ncurses_installer" && echo " ✓ Ncurses installer imports"
python3 -c "from installer.ncurses_ui import CursesWindow" && echo " ✓ Ncurses UI imports"
echo ""
echo "4. Testing command-line interface..."
python3 install.py --help > /dev/null && echo " ✓ --help works"
echo ""
echo "5. Testing spec generator..."
test -f scripts/generate_spec.py && python3 scripts/generate_spec.py && echo " ✓ Spec generator works"
echo ""
echo "6. Testing icon conversion..."
make clean > /dev/null 2>&1
make build/prole.icns > /dev/null 2>&1 && echo " ✓ Icon conversion works"
test -f build/prole.icns && echo " ✓ ICNS file created"
echo ""
echo "=== All Tests Passed ==="
echo ""
echo "Ready to build!"
echo ""
echo "Next steps:"
echo " 1. Install build dependencies: make install"
echo " 2. Build the installer: make package"
echo " 3. Test the build: make test"

110
test_docker_build_fix.sh Executable file
View File

@ -0,0 +1,110 @@
#!/bin/bash
echo "========================================"
echo " Docker Build Fix Verification"
echo "========================================"
echo ""
# Test 1: prole-db exists
echo "1. Testing prole-db directory..."
if [ -d "prole-db" ] && [ -f "prole-db/Dockerfile" ]; then
echo " ✓ prole-db directory with Dockerfile exists"
else
echo " ✗ prole-db directory or Dockerfile missing"
exit 1
fi
# Test 2: Spec includes prole-db
echo "2. Testing spec file includes prole-db..."
python3 scripts/generate_spec.py > /dev/null 2>&1
if grep -q "('prole-db', 'prole-db')" installer.spec; then
echo " ✓ prole-db included in spec"
else
echo " ✗ prole-db not in spec"
exit 1
fi
# Test 3: Resource path resolution
echo "3. Testing resource path resolution..."
python3 << 'PYTEST'
import sys
from pathlib import Path
sys.path.insert(0, str(Path.cwd()))
from install import get_resource_path
prole_db = get_resource_path("prole-db")
if prole_db.exists() and (prole_db / "Dockerfile").exists():
print(" ✓ get_resource_path('prole-db') works")
else:
print(" ✗ Resource path resolution failed")
sys.exit(1)
PYTEST
if [ $? -ne 0 ]; then exit 1; fi
# Test 4: Build directory creation
echo "4. Testing .prole/build directory creation..."
python3 << 'PYTEST'
import sys
from pathlib import Path
import shutil
prole_home = Path.home() / ".prole"
build_dir = prole_home / "build" / "prole-db-test"
# Create and verify
build_dir.mkdir(parents=True, exist_ok=True)
if build_dir.exists():
print(f" ✓ Created: {build_dir}")
# Cleanup
shutil.rmtree(prole_home / "build" / "prole-db-test")
else:
print(" ✗ Failed to create build directory")
sys.exit(1)
PYTEST
if [ $? -ne 0 ]; then exit 1; fi
# Test 5: Copy operation
echo "5. Testing build context copy..."
python3 << 'PYTEST'
import sys
from pathlib import Path
import shutil
sys.path.insert(0, str(Path.cwd()))
from install import get_resource_path
prole_home = Path.home() / ".prole"
build_dir = prole_home / "build" / "prole-db-test"
source_dir = get_resource_path("prole-db")
try:
if build_dir.exists():
shutil.rmtree(build_dir)
shutil.copytree(source_dir, build_dir)
# Verify
if (build_dir / "Dockerfile").exists():
print(f" ✓ Copied build context successfully")
# Cleanup
shutil.rmtree(prole_home / "build" / "prole-db-test")
else:
print(" ✗ Copy incomplete")
sys.exit(1)
except Exception as e:
print(f" ✗ Copy failed: {e}")
sys.exit(1)
PYTEST
if [ $? -ne 0 ]; then exit 1; fi
echo ""
echo "========================================"
echo " ✓ All Docker Build Fix Tests Passed"
echo "========================================"
echo ""
echo "The Docker build will now work correctly:"
echo " • From source: Uses PROJECT_ROOT/prole-db"
echo " • From package: Copies to ~/.prole/build/prole-db"
echo ""
echo "Build directory: ~/.prole/build/prole-db"
echo "Build command: docker build -t prole-db:TAG ."
echo ""

84
test_embedded_resources.sh Executable file
View File

@ -0,0 +1,84 @@
#!/bin/bash
echo "=== Testing Embedded Resources ==="
echo ""
# Test 1: Images
echo "1. Testing image resources..."
python3 -c "
from pathlib import Path
import sys
sys.path.insert(0, str(Path.cwd()))
from install import get_resource_path
images = [
'img/proleIcon.png',
'img/proleLogo.png',
'img/proleLogoSepia.png',
]
for img in images:
path = get_resource_path(img)
if not path.exists():
print(f'✗ Missing: {img}')
sys.exit(1)
print('✓ All images present')
"
if [ $? -ne 0 ]; then exit 1; fi
# Test 2: Binaries
echo "2. Testing binary resources..."
if [ -f "prole-net/prole-scan" ] && [ -x "prole-net/prole-scan" ]; then
echo " ✓ prole-scan binary present and executable"
else
echo " ✗ prole-scan missing or not executable"
exit 1
fi
# Test 3: App bundle
echo "3. Testing Prole Tools.app bundle..."
if [ -d "prole-app/dist/Prole Tools.app" ]; then
echo " ✓ Prole Tools.app present"
else
echo " ✗ Prole Tools.app missing"
exit 1
fi
# Test 4: Spec file
echo "4. Testing spec file generation..."
python3 scripts/generate_spec.py > /dev/null 2>&1
if [ -f "installer.spec" ]; then
echo " ✓ installer.spec generated"
else
echo " ✗ installer.spec generation failed"
exit 1
fi
# Test 5: Verify spec includes everything
echo "5. Verifying spec includes all resources..."
grep -q "prole-app/dist/Prole Tools.app" installer.spec && \
grep -q "prole-net/prole-scan" installer.spec && \
grep -q "img" installer.spec
if [ $? -eq 0 ]; then
echo " ✓ All resources included in spec"
else
echo " ✗ Some resources missing from spec"
exit 1
fi
# Test 6: Check binary size
echo "6. Checking binary sizes..."
SCAN_SIZE=$(ls -lh prole-net/prole-scan | awk '{print $5}')
echo " prole-scan: $SCAN_SIZE (universal binary)"
APP_SIZE=$(du -sh "prole-app/dist/Prole Tools.app" | awk '{print $1}')
echo " Prole Tools.app: $APP_SIZE"
echo ""
echo "=== All Embedded Resource Tests Passed ==="
echo ""
echo "Resources ready for packaging:"
echo " ✓ Images (proleIcon.png, proleLogo.png, proleLogoSepia.png)"
echo " ✓ Binary (prole-net/prole-scan)"
echo " ✓ App bundle (prole-app/dist/Prole Tools.app)"
echo ""
echo "Build with: make package"

89
test_network_scan_fix.sh Executable file
View File

@ -0,0 +1,89 @@
#!/bin/bash
echo "========================================"
echo " Network Scan Fix Verification"
echo "========================================"
echo ""
# Test 1: Scan directory creation
echo "1. Testing scan directory creation..."
python3 << 'PYTEST'
from pathlib import Path
prole_home = Path.home() / ".prole"
scan_dir = prole_home / "scan"
scan_dir.mkdir(parents=True, exist_ok=True)
if scan_dir.exists() and scan_dir.is_dir():
print(" ✓ Scan directory created")
else:
print(" ✗ Failed to create scan directory")
exit(1)
PYTEST
if [ $? -ne 0 ]; then exit 1; fi
# Test 2: Scan directory is writable
echo "2. Testing scan directory is writable..."
python3 << 'PYTEST'
from pathlib import Path
scan_dir = Path.home() / ".prole" / "scan"
test_file = scan_dir / "test.txt"
try:
test_file.write_text("test")
test_file.unlink()
print(" ✓ Scan directory is writable")
except Exception as e:
print(f" ✗ Scan directory not writable: {e}")
exit(1)
PYTEST
if [ $? -ne 0 ]; then exit 1; fi
# Test 3: prole-scan binary exists
echo "3. Testing prole-scan binary..."
if [ -x "prole-net/prole-scan" ]; then
echo " ✓ prole-scan binary exists and is executable"
else
echo " ✗ prole-scan binary missing or not executable"
exit 1
fi
# Test 4: prole-scan can run from scan directory
echo "4. Testing prole-scan runs from writable directory..."
mkdir -p /tmp/scan-test
cd /tmp/scan-test
/Users/chrisfu/dev/prole/prole-net/prole-scan 2>&1 &
SCAN_PID=$!
sleep 2
if ps -p $SCAN_PID > /dev/null 2>&1; then
echo " ✓ prole-scan runs successfully"
kill $SCAN_PID 2>/dev/null
wait 2>/dev/null
else
echo " ✗ prole-scan failed to start"
exit 1
fi
cd /Users/chrisfu/dev/prole
# Test 5: Code uses scan directory
echo "5. Testing code uses scan directory..."
if grep -q "scan_dir = prole_home / \"scan\"" install.py && \
grep -q "cwd=str(scan_dir)" install.py; then
echo " ✓ Code properly uses scan directory"
else
echo " ✗ Code not updated to use scan directory"
exit 1
fi
# Cleanup test directories
rm -rf ~/.prole/scan 2>/dev/null
echo ""
echo "========================================"
echo " ✓ All Network Scan Fix Tests Passed"
echo "========================================"
echo ""
echo "Network scan will now work correctly:"
echo " • From source: Uses writable cwd"
echo " • From package: Uses ~/.prole/scan"
echo ""
echo "Scan directory: ~/.prole/scan"
echo "Purpose: Writable working directory for prole-scan"
echo ""

79
test_resource_paths.py Executable file
View File

@ -0,0 +1,79 @@
#!/usr/bin/env python3
"""Test that resource paths work correctly."""
import sys
from pathlib import Path
# Add current directory to path
sys.path.insert(0, str(Path.cwd()))
from install import get_resource_path
def test_resource_path():
"""Test get_resource_path function."""
print("Testing get_resource_path() function:")
print()
test_cases = [
('img/proleIcon.png', 'App icon'),
('img/proleLogoSepia.png', 'Background logo (sepia)'),
('img/proleLogo.png', 'Main logo'),
('img/proleLogoBlueprint.png', 'Blueprint logo'),
('img/proleIconblueprint.png', 'Blueprint icon'),
]
all_passed = True
for rel_path, description in test_cases:
result = get_resource_path(rel_path)
exists = result.exists()
status = "" if exists else ""
print(f"{status} {description}")
print(f" Path: {rel_path}")
print(f" Resolved: {result}")
print(f" Exists: {exists}")
print()
if not exists:
all_passed = False
return all_passed
def test_pyinstaller_simulation():
"""Simulate PyInstaller environment."""
print("Simulating PyInstaller environment:")
print()
# Temporarily set _MEIPASS to simulate PyInstaller
test_meipass = Path.cwd()
sys._MEIPASS = str(test_meipass)
try:
result = get_resource_path('img/proleIcon.png')
print(f" sys._MEIPASS: {sys._MEIPASS}")
print(f" Resolved path: {result}")
print(f" Exists: {result.exists()}")
print()
return result.exists()
finally:
# Clean up
delattr(sys, '_MEIPASS')
if __name__ == '__main__':
print("=" * 60)
print("Resource Path Tests")
print("=" * 60)
print()
test1 = test_resource_path()
test2 = test_pyinstaller_simulation()
print("=" * 60)
if test1 and test2:
print("✓ All tests passed!")
print()
print("Images will be correctly included in PyInstaller build.")
sys.exit(0)
else:
print("✗ Some tests failed!")
sys.exit(1)