mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 10:13:58 +00:00
iscsi/installer: provision merlin d004; fix etc script runtime
- Add merlin iSCSI config for PROLE-DATA-4 mounted at /prole/d004 (xfs, _netdev,noatime) - Refine iscsi role login/mount flow (device resolution, mkfs-if-missing, UUID fstab, tags) - Ensure installer run_script stages lib/shell into PROLE_HOME so etc scripts can source common libs; add regression test - Add init scripts for Forgejo/GitLab; ignore generated conf/prole.cfg Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
parent
5dcd3b9581
commit
f051d1d42a
1
.gitignore
vendored
1
.gitignore
vendored
@ -47,6 +47,7 @@ etc/secrets/
|
||||
*-password.txt
|
||||
*secret.yaml
|
||||
.vault_pass
|
||||
/conf/prole.cfg
|
||||
/secrets/
|
||||
/data/
|
||||
/logs/
|
||||
|
||||
8
etc/init_forego.sh
Normal file
8
etc/init_forego.sh
Normal file
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Backward-compat wrapper for a common misspelling.
|
||||
# Prefer: etc/init_forgejo.sh
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
exec bash "$SCRIPT_DIR/init_forgejo.sh" "$@"
|
||||
248
etc/init_forgejo.sh
Normal file
248
etc/init_forgejo.sh
Normal file
@ -0,0 +1,248 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PROG="init_forgejo"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
source "$SCRIPT_DIR/prole_cfg.sh"
|
||||
|
||||
MODE="$(prole_normalize_mode "${PROLE_MODE:-${DEPLOYMENT_MODE:-k3d}}")"
|
||||
NAMESPACE="${FORGEJO_NAMESPACE:-}"
|
||||
CFG_PATH=""
|
||||
FORCE=0
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage:
|
||||
$PROG [options] [deploy]
|
||||
|
||||
Options:
|
||||
--mode <k3d|k3s|k8s|local> Deployment mode (default: ${MODE:-k3d})
|
||||
-n, --namespace <name> Target namespace (default: forgejo)
|
||||
-c, --config <prole.cfg> Path to prole.cfg (defaults to detected)
|
||||
--force Delete existing Forgejo resources before deploy
|
||||
--help Show this help
|
||||
|
||||
Behavior:
|
||||
- Applies a minimal Forgejo Deployment/Service manifest.
|
||||
- Uses CloudNativePG (CNPG) Postgres as the database backend.
|
||||
- By default, creates a dedicated DB + role inside the CNPG cluster.
|
||||
EOF
|
||||
}
|
||||
|
||||
die() { echo "[ERROR] $*" >&2; exit 2; }
|
||||
log() { echo "[INFO] $*"; }
|
||||
warn() { echo "[WARN] $*" >&2; }
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--mode) MODE="$(prole_normalize_mode "${2:-}")"; shift 2 ;;
|
||||
--mode=*) MODE="$(prole_normalize_mode "${1#*=}")"; shift 1 ;;
|
||||
-n|--namespace) NAMESPACE="${2:-}"; shift 2 ;;
|
||||
--namespace=*) NAMESPACE="${1#*=}"; shift 1 ;;
|
||||
-c|--config) CFG_PATH="${2:-}"; shift 2 ;;
|
||||
--config=*) CFG_PATH="${1#*=}"; shift 1 ;;
|
||||
--force) FORCE=1; shift 1 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Resolve config path and namespace defaults from prole.cfg when present
|
||||
if [[ -z "$CFG_PATH" && -n "${PROLE_CONF:-}" && -f "${PROLE_CONF}/prole.cfg" ]]; then
|
||||
CFG_PATH="${PROLE_CONF}/prole.cfg"
|
||||
elif [[ -z "$CFG_PATH" && -f "$SCRIPT_DIR/../conf/prole.cfg" ]]; then
|
||||
CFG_PATH="$SCRIPT_DIR/../conf/prole.cfg"
|
||||
fi
|
||||
|
||||
if [[ -z "$NAMESPACE" && -n "$CFG_PATH" ]]; then
|
||||
maybe_ns="$(_prole_cfg_extract_key "$CFG_PATH" "GITOPS_NAMESPACE")"
|
||||
[[ -z "$maybe_ns" ]] && maybe_ns="$(_prole_cfg_extract_key "$CFG_PATH" "FORGEJO_NAMESPACE")"
|
||||
NAMESPACE="$maybe_ns"
|
||||
fi
|
||||
NAMESPACE="${NAMESPACE:-forgejo}"
|
||||
export FORGEJO_NAMESPACE="$NAMESPACE"
|
||||
|
||||
case "$MODE" in
|
||||
k3d|k3s|k8s|local) ;;
|
||||
*) die "Unsupported mode '$MODE' (use k3d, k3s, k8s, or local)" ;;
|
||||
esac
|
||||
export PROLE_MODE="$MODE"
|
||||
|
||||
command -v kubectl >/dev/null || die "kubectl not found"
|
||||
|
||||
DB_NAMESPACE="${PROLE_NAMESPACE:-}"
|
||||
if [[ -z "$DB_NAMESPACE" && -n "$CFG_PATH" ]]; then
|
||||
DB_NAMESPACE="$(_prole_cfg_extract_key "$CFG_PATH" "NAMESPACE")"
|
||||
fi
|
||||
DB_NAMESPACE="${DB_NAMESPACE:-default}"
|
||||
|
||||
CNPG_CLUSTER_NAME="${CNPG_CLUSTER_NAME:-prole-db}"
|
||||
FORGEJO_DB_NAME="${FORGEJO_DB_NAME:-forgejo}"
|
||||
FORGEJO_DB_USER="${FORGEJO_DB_USER:-forgejo}"
|
||||
FORGEJO_DB_PASSWORD="${FORGEJO_DB_PASSWORD:-}"
|
||||
if [[ -z "$FORGEJO_DB_PASSWORD" ]]; then
|
||||
# 24 chars alnum for broad chart compatibility.
|
||||
FORGEJO_DB_PASSWORD=$(LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom 2>/dev/null | head -c 24 || true)
|
||||
fi
|
||||
|
||||
DB_HOST="${CNPG_CLUSTER_NAME}-rw.${DB_NAMESPACE}.svc.cluster.local"
|
||||
DB_PORT="${FORGEJO_DB_PORT:-5432}"
|
||||
|
||||
kubectl get ns "$NAMESPACE" >/dev/null 2>&1 || kubectl create namespace "$NAMESPACE" >/dev/null 2>&1
|
||||
|
||||
if [[ "$FORCE" -eq 1 ]]; then
|
||||
warn "--force specified; removing existing Forgejo resources in namespace $NAMESPACE"
|
||||
kubectl -n "$NAMESPACE" delete deploy/forgejo svc/forgejo-http svc/forgejo-ssh ingress/forgejo >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
ensure_db() {
|
||||
local primary_pod=""
|
||||
primary_pod=$(kubectl -n "$DB_NAMESPACE" get pods \
|
||||
-l "cnpg.io/cluster=${CNPG_CLUSTER_NAME},role=primary" \
|
||||
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)
|
||||
if [[ -z "$primary_pod" ]]; then
|
||||
primary_pod=$(kubectl -n "$DB_NAMESPACE" get pods \
|
||||
-l "cnpg.io/cluster=${CNPG_CLUSTER_NAME}" \
|
||||
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)
|
||||
fi
|
||||
if [[ -z "$primary_pod" ]]; then
|
||||
die "Could not find CNPG pod for cluster '${CNPG_CLUSTER_NAME}' in namespace '${DB_NAMESPACE}'"
|
||||
fi
|
||||
|
||||
log "Ensuring database '${FORGEJO_DB_NAME}' and role '${FORGEJO_DB_USER}' exist in CNPG cluster '${CNPG_CLUSTER_NAME}' (ns=${DB_NAMESPACE})"
|
||||
|
||||
# Create role/db idempotently.
|
||||
kubectl -n "$DB_NAMESPACE" exec "$primary_pod" -- bash -lc "psql -v ON_ERROR_STOP=1 -U postgres -d postgres" <<SQL
|
||||
DO \$\$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = '${FORGEJO_DB_USER}') THEN
|
||||
CREATE ROLE ${FORGEJO_DB_USER} LOGIN PASSWORD '${FORGEJO_DB_PASSWORD}';
|
||||
END IF;
|
||||
END
|
||||
\$\$;
|
||||
|
||||
DO \$\$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = '${FORGEJO_DB_NAME}') THEN
|
||||
CREATE DATABASE ${FORGEJO_DB_NAME} OWNER ${FORGEJO_DB_USER};
|
||||
END IF;
|
||||
END
|
||||
\$\$;
|
||||
SQL
|
||||
|
||||
# Store app connection info in the Forgejo namespace.
|
||||
kubectl -n "$NAMESPACE" create secret generic forgejo-db \
|
||||
--from-literal=host="$DB_HOST" \
|
||||
--from-literal=port="$DB_PORT" \
|
||||
--from-literal=database="$FORGEJO_DB_NAME" \
|
||||
--from-literal=username="$FORGEJO_DB_USER" \
|
||||
--from-literal=password="$FORGEJO_DB_PASSWORD" \
|
||||
--dry-run=client -o yaml | kubectl apply -f - >/dev/null
|
||||
}
|
||||
|
||||
ensure_db
|
||||
|
||||
FORGEJO_IMAGE_REPO="${FORGEJO_IMAGE_REPO:-codeberg.org/forgejo/forgejo}"
|
||||
FORGEJO_IMAGE_TAG="${FORGEJO_IMAGE_TAG:-latest}"
|
||||
|
||||
log "Deploying Forgejo to namespace '$NAMESPACE' (mode=$MODE)"
|
||||
|
||||
cat <<EOF | kubectl apply -n "$NAMESPACE" -f -
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: forgejo
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: forgejo
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: forgejo
|
||||
spec:
|
||||
containers:
|
||||
- name: forgejo
|
||||
image: ${FORGEJO_IMAGE_REPO}:${FORGEJO_IMAGE_TAG}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 3000
|
||||
- name: ssh
|
||||
containerPort: 2222
|
||||
env:
|
||||
# Prefer Forgejo prefix, but also set Gitea-style keys for compatibility.
|
||||
- name: FORGEJO__database__DB_TYPE
|
||||
value: postgres
|
||||
- name: FORGEJO__database__HOST
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: forgejo-db
|
||||
key: host
|
||||
- name: FORGEJO__database__NAME
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: forgejo-db
|
||||
key: database
|
||||
- name: FORGEJO__database__USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: forgejo-db
|
||||
key: username
|
||||
- name: FORGEJO__database__PASSWD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: forgejo-db
|
||||
key: password
|
||||
- name: GITEA__database__DB_TYPE
|
||||
value: postgres
|
||||
- name: GITEA__database__HOST
|
||||
value: ${DB_HOST}:${DB_PORT}
|
||||
- name: GITEA__database__NAME
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: forgejo-db
|
||||
key: database
|
||||
- name: GITEA__database__USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: forgejo-db
|
||||
key: username
|
||||
- name: GITEA__database__PASSWD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: forgejo-db
|
||||
key: password
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: forgejo-http
|
||||
spec:
|
||||
selector:
|
||||
app: forgejo
|
||||
ports:
|
||||
- name: http
|
||||
port: 3000
|
||||
targetPort: 3000
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: forgejo-ssh
|
||||
spec:
|
||||
selector:
|
||||
app: forgejo
|
||||
ports:
|
||||
- name: ssh
|
||||
port: 2222
|
||||
targetPort: 2222
|
||||
EOF
|
||||
|
||||
log "Waiting for deployment rollout..."
|
||||
kubectl -n "$NAMESPACE" rollout status deploy/forgejo --timeout=10m
|
||||
|
||||
log "Done. Service endpoints:"
|
||||
kubectl -n "$NAMESPACE" get svc forgejo-http forgejo-ssh
|
||||
174
etc/init_gitlab.sh
Normal file
174
etc/init_gitlab.sh
Normal file
@ -0,0 +1,174 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PROG="init_gitlab"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
source "$SCRIPT_DIR/prole_cfg.sh"
|
||||
|
||||
MODE="$(prole_normalize_mode "${PROLE_MODE:-${DEPLOYMENT_MODE:-k3d}}")"
|
||||
NAMESPACE="${GITLAB_NAMESPACE:-}"
|
||||
CFG_PATH=""
|
||||
FORCE=0
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage:
|
||||
$PROG [options] [deploy]
|
||||
|
||||
Options:
|
||||
--mode <k3d|k3s|k8s|local> Deployment mode (default: ${MODE:-k3d})
|
||||
-n, --namespace <name> Target namespace (default: gitlab)
|
||||
-c, --config <prole.cfg> Path to prole.cfg (defaults to detected)
|
||||
--force Uninstall existing GitLab release before deploy
|
||||
--help Show this help
|
||||
|
||||
Behavior:
|
||||
- Installs self-hosted GitLab via the official Helm chart.
|
||||
- Uses CloudNativePG (CNPG) Postgres as the database backend (external DB).
|
||||
EOF
|
||||
}
|
||||
|
||||
die() { echo "[ERROR] $*" >&2; exit 2; }
|
||||
log() { echo "[INFO] $*"; }
|
||||
warn() { echo "[WARN] $*" >&2; }
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--mode) MODE="$(prole_normalize_mode "${2:-}")"; shift 2 ;;
|
||||
--mode=*) MODE="$(prole_normalize_mode "${1#*=}")"; shift 1 ;;
|
||||
-n|--namespace) NAMESPACE="${2:-}"; shift 2 ;;
|
||||
--namespace=*) NAMESPACE="${1#*=}"; shift 1 ;;
|
||||
-c|--config) CFG_PATH="${2:-}"; shift 2 ;;
|
||||
--config=*) CFG_PATH="${1#*=}"; shift 1 ;;
|
||||
--force) FORCE=1; shift 1 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Resolve config path and namespace defaults from prole.cfg when present
|
||||
if [[ -z "$CFG_PATH" && -n "${PROLE_CONF:-}" && -f "${PROLE_CONF}/prole.cfg" ]]; then
|
||||
CFG_PATH="${PROLE_CONF}/prole.cfg"
|
||||
elif [[ -z "$CFG_PATH" && -f "$SCRIPT_DIR/../conf/prole.cfg" ]]; then
|
||||
CFG_PATH="$SCRIPT_DIR/../conf/prole.cfg"
|
||||
fi
|
||||
|
||||
if [[ -z "$NAMESPACE" && -n "$CFG_PATH" ]]; then
|
||||
maybe_ns="$(_prole_cfg_extract_key "$CFG_PATH" "GITOPS_NAMESPACE")"
|
||||
[[ -z "$maybe_ns" ]] && maybe_ns="$(_prole_cfg_extract_key "$CFG_PATH" "GITLAB_NAMESPACE")"
|
||||
NAMESPACE="$maybe_ns"
|
||||
fi
|
||||
NAMESPACE="${NAMESPACE:-gitlab}"
|
||||
export GITLAB_NAMESPACE="$NAMESPACE"
|
||||
|
||||
case "$MODE" in
|
||||
k3d|k3s|k8s|local) ;;
|
||||
*) die "Unsupported mode '$MODE' (use k3d, k3s, k8s, or local)" ;;
|
||||
esac
|
||||
export PROLE_MODE="$MODE"
|
||||
|
||||
command -v kubectl >/dev/null || die "kubectl not found"
|
||||
command -v helm >/dev/null || die "helm not found (required for GitLab install)"
|
||||
|
||||
DB_NAMESPACE="${PROLE_NAMESPACE:-}"
|
||||
if [[ -z "$DB_NAMESPACE" && -n "$CFG_PATH" ]]; then
|
||||
DB_NAMESPACE="$(_prole_cfg_extract_key "$CFG_PATH" "NAMESPACE")"
|
||||
fi
|
||||
DB_NAMESPACE="${DB_NAMESPACE:-default}"
|
||||
|
||||
CNPG_CLUSTER_NAME="${CNPG_CLUSTER_NAME:-prole-db}"
|
||||
GITLAB_DB_NAME="${GITLAB_DB_NAME:-gitlabhq_production}"
|
||||
GITLAB_DB_USER="${GITLAB_DB_USER:-gitlab}"
|
||||
GITLAB_DB_PASSWORD="${GITLAB_DB_PASSWORD:-}"
|
||||
if [[ -z "$GITLAB_DB_PASSWORD" ]]; then
|
||||
GITLAB_DB_PASSWORD=$(LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom 2>/dev/null | head -c 32 || true)
|
||||
fi
|
||||
|
||||
DB_HOST="${CNPG_CLUSTER_NAME}-rw.${DB_NAMESPACE}.svc.cluster.local"
|
||||
DB_PORT="${GITLAB_DB_PORT:-5432}"
|
||||
|
||||
kubectl get ns "$NAMESPACE" >/dev/null 2>&1 || kubectl create namespace "$NAMESPACE" >/dev/null 2>&1
|
||||
|
||||
ensure_db() {
|
||||
local primary_pod=""
|
||||
primary_pod=$(kubectl -n "$DB_NAMESPACE" get pods \
|
||||
-l "cnpg.io/cluster=${CNPG_CLUSTER_NAME},role=primary" \
|
||||
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)
|
||||
if [[ -z "$primary_pod" ]]; then
|
||||
primary_pod=$(kubectl -n "$DB_NAMESPACE" get pods \
|
||||
-l "cnpg.io/cluster=${CNPG_CLUSTER_NAME}" \
|
||||
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)
|
||||
fi
|
||||
if [[ -z "$primary_pod" ]]; then
|
||||
die "Could not find CNPG pod for cluster '${CNPG_CLUSTER_NAME}' in namespace '${DB_NAMESPACE}'"
|
||||
fi
|
||||
|
||||
log "Ensuring database '${GITLAB_DB_NAME}' and role '${GITLAB_DB_USER}' exist in CNPG cluster '${CNPG_CLUSTER_NAME}' (ns=${DB_NAMESPACE})"
|
||||
|
||||
kubectl -n "$DB_NAMESPACE" exec "$primary_pod" -- bash -lc "psql -v ON_ERROR_STOP=1 -U postgres -d postgres" <<SQL
|
||||
DO \$\$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = '${GITLAB_DB_USER}') THEN
|
||||
CREATE ROLE ${GITLAB_DB_USER} LOGIN PASSWORD '${GITLAB_DB_PASSWORD}';
|
||||
END IF;
|
||||
END
|
||||
\$\$;
|
||||
|
||||
DO \$\$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = '${GITLAB_DB_NAME}') THEN
|
||||
CREATE DATABASE ${GITLAB_DB_NAME} OWNER ${GITLAB_DB_USER};
|
||||
END IF;
|
||||
END
|
||||
\$\$;
|
||||
SQL
|
||||
|
||||
kubectl -n "$NAMESPACE" create secret generic gitlab-db \
|
||||
--from-literal=host="$DB_HOST" \
|
||||
--from-literal=port="$DB_PORT" \
|
||||
--from-literal=database="$GITLAB_DB_NAME" \
|
||||
--from-literal=username="$GITLAB_DB_USER" \
|
||||
--from-literal=password="$GITLAB_DB_PASSWORD" \
|
||||
--dry-run=client -o yaml | kubectl apply -f - >/dev/null
|
||||
}
|
||||
|
||||
ensure_db
|
||||
|
||||
RELEASE_NAME="${GITLAB_RELEASE_NAME:-gitlab}"
|
||||
|
||||
if [[ "$FORCE" -eq 1 ]]; then
|
||||
warn "--force specified; uninstalling existing release '$RELEASE_NAME' in namespace '$NAMESPACE' (if present)"
|
||||
helm -n "$NAMESPACE" uninstall "$RELEASE_NAME" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
GITLAB_DOMAIN="${GITLAB_DOMAIN:-${DOMAIN:-${PROLE_DOMAIN:-}}}"
|
||||
GITLAB_DOMAIN="${GITLAB_DOMAIN:-example.local}"
|
||||
|
||||
log "Adding/updating GitLab Helm repo"
|
||||
helm repo add gitlab https://charts.gitlab.io/ >/dev/null 2>&1 || true
|
||||
helm repo update >/dev/null
|
||||
|
||||
log "Installing GitLab (release=$RELEASE_NAME, ns=$NAMESPACE, domain=$GITLAB_DOMAIN)"
|
||||
|
||||
# Notes:
|
||||
# - We keep the chart install minimal to avoid assuming ingress/cert-manager setup.
|
||||
# - We disable the bundled PostgreSQL and point GitLab to CNPG.
|
||||
helm upgrade --install "$RELEASE_NAME" gitlab/gitlab \
|
||||
-n "$NAMESPACE" \
|
||||
--timeout 30m \
|
||||
--wait \
|
||||
--set global.hosts.domain="$GITLAB_DOMAIN" \
|
||||
--set global.hosts.https=false \
|
||||
--set certmanager.install=false \
|
||||
--set prometheus.install=false \
|
||||
--set postgresql.install=false \
|
||||
--set global.psql.host="$DB_HOST" \
|
||||
--set global.psql.port="$DB_PORT" \
|
||||
--set global.psql.username="$GITLAB_DB_USER" \
|
||||
--set global.psql.database="$GITLAB_DB_NAME" \
|
||||
--set global.psql.password.secret=gitlab-db \
|
||||
--set global.psql.password.key=password
|
||||
|
||||
log "Done. Inspect services/pods with: kubectl -n $NAMESPACE get pods,svc"
|
||||
@ -49,8 +49,19 @@ k3s_service_node_labels: []
|
||||
|
||||
# iSCSI
|
||||
# `/var/lib/rancher` is local host storage (do not manage it via iSCSI).
|
||||
iscsi_portal: 10.0.0.203
|
||||
iscsi_portal: 10.0.0.203:3260
|
||||
|
||||
iscsi_targets: []
|
||||
iscsi_targets:
|
||||
# PROLE-DATA-4
|
||||
# Repurposed from myrddin's former `/var/lib/rancher` Synology LUN.
|
||||
- iqn: "iqn.2000-01.com.synology:synology.Target-15.292d45194a1"
|
||||
chap_user: "prole"
|
||||
chap_password: "{{ vault_iscsi_prole_password }}"
|
||||
mounts:
|
||||
- name: d004
|
||||
path: /prole/d004
|
||||
fstype: xfs
|
||||
opts: "_netdev,noatime"
|
||||
mkfs_if_missing: true
|
||||
|
||||
iscsi_absent_mounts: []
|
||||
|
||||
@ -4,6 +4,121 @@
|
||||
path: "{{ m.path }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mount
|
||||
- iscsi_storage
|
||||
|
||||
- name: Reset computed mount source
|
||||
ansible.builtin.set_fact:
|
||||
_iscsi_mount_src: ""
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mount
|
||||
- iscsi_storage
|
||||
|
||||
- name: Decide whether a block device is required for this mount
|
||||
ansible.builtin.set_fact:
|
||||
_iscsi_need_blockdev: >-
|
||||
{{ (m.mkfs_once | default(false) | bool)
|
||||
or (m.mkfs_if_missing | default(false) | bool)
|
||||
or (m.device is defined and (m.device | string | length) > 0)
|
||||
or (m.src is not defined) or ((m.src | default('') | string | length) == 0) }}
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mount
|
||||
- iscsi_storage
|
||||
|
||||
- name: Parse iSCSI portal (host/port)
|
||||
ansible.builtin.set_fact:
|
||||
_iscsi_portal_host: "{{ (iscsi_portal | string).split(':')[0] }}"
|
||||
_iscsi_portal_port: "{{ ((iscsi_portal | string).split(':') | length > 1) | ternary((iscsi_portal | string).split(':')[1], '3260') }}"
|
||||
when:
|
||||
- _iscsi_need_blockdev | default(false) | bool
|
||||
- iscsi_portal is defined
|
||||
- (iscsi_portal | string | length) > 0
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mount
|
||||
- iscsi_storage
|
||||
|
||||
- name: Use explicitly provided block device (if any)
|
||||
ansible.builtin.set_fact:
|
||||
_iscsi_blockdev: "{{ m.device }}"
|
||||
when:
|
||||
- _iscsi_need_blockdev | default(false) | bool
|
||||
- m.device is defined
|
||||
- (m.device | string | length) > 0
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mount
|
||||
- iscsi_storage
|
||||
|
||||
- name: Compute iSCSI by-path pattern
|
||||
ansible.builtin.set_fact:
|
||||
_iscsi_by_path_pattern: >-
|
||||
ip-{{ _iscsi_portal_host }}:{{ _iscsi_portal_port }}-iscsi-{{ iscsi_target.iqn }}-lun-{{ (m.lun is defined) | ternary((m.lun | string), '*') }}
|
||||
when:
|
||||
- _iscsi_need_blockdev | default(false) | bool
|
||||
- (_iscsi_blockdev is not defined) or ((_iscsi_blockdev | string | length) == 0)
|
||||
- iscsi_target is defined
|
||||
- iscsi_target.iqn is defined
|
||||
- _iscsi_portal_host is defined
|
||||
- (_iscsi_portal_host | string | length) > 0
|
||||
- _iscsi_portal_port is defined
|
||||
- (_iscsi_portal_port | string | length) > 0
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mount
|
||||
- iscsi_storage
|
||||
|
||||
- name: Wait for iSCSI by-path device to appear
|
||||
ansible.builtin.find:
|
||||
paths:
|
||||
- /dev/disk/by-path
|
||||
patterns:
|
||||
- "{{ _iscsi_by_path_pattern }}"
|
||||
file_type: any
|
||||
register: _iscsi_by_path_find
|
||||
until: (_iscsi_by_path_find.matched | default(0) | int) > 0
|
||||
retries: "{{ ((m.device_timeout | default(60)) | int // 5) + 1 }}"
|
||||
delay: 5
|
||||
when:
|
||||
- _iscsi_need_blockdev | default(false) | bool
|
||||
- (_iscsi_blockdev is not defined) or ((_iscsi_blockdev | string | length) == 0)
|
||||
- _iscsi_by_path_pattern is defined
|
||||
- (_iscsi_by_path_pattern | string | length) > 0
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mount
|
||||
- iscsi_storage
|
||||
|
||||
- name: Select iSCSI block device from by-path matches
|
||||
ansible.builtin.set_fact:
|
||||
_iscsi_blockdev: "{{ (_iscsi_by_path_find.files | map(attribute='path') | list | sort | first) }}"
|
||||
when:
|
||||
- _iscsi_need_blockdev | default(false) | bool
|
||||
- (_iscsi_blockdev is not defined) or ((_iscsi_blockdev | string | length) == 0)
|
||||
- _iscsi_by_path_find is defined
|
||||
- (_iscsi_by_path_find.matched | default(0) | int) > 0
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mount
|
||||
- iscsi_storage
|
||||
|
||||
- name: Read existing filesystem type (if any)
|
||||
ansible.builtin.command: "blkid -o value -s TYPE {{ _iscsi_blockdev }}"
|
||||
register: _iscsi_blkid_type
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
when:
|
||||
- _iscsi_need_blockdev | default(false) | bool
|
||||
- _iscsi_blockdev is defined
|
||||
- (_iscsi_blockdev | string | length) > 0
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mount
|
||||
- iscsi_storage
|
||||
|
||||
- name: Ensure marker directory exists (mkfs_once)
|
||||
ansible.builtin.file:
|
||||
@ -13,22 +128,33 @@
|
||||
group: root
|
||||
mode: "0755"
|
||||
when: m.mkfs_once | default(false) | bool
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mkfs
|
||||
- iscsi_storage
|
||||
|
||||
- name: Compute mkfs marker path (mkfs_once)
|
||||
ansible.builtin.set_fact:
|
||||
_iscsi_mkfs_marker: >-
|
||||
/var/lib/prole/iscsi/mkfs-
|
||||
{{ (m.src | regex_replace('^UUID=', 'uuid-')
|
||||
| regex_replace('^LABEL=', 'label-')
|
||||
| regex_replace('[^A-Za-z0-9_.-]', '_')) }}
|
||||
{{ (((iscsi_target.iqn | default('unknown-iqn')) ~ '-' ~ (m.path | default('unknown-path')))
|
||||
| regex_replace('[^A-Za-z0-9_.-]', '_')) }}
|
||||
.done
|
||||
when: m.mkfs_once | default(false) | bool
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mkfs
|
||||
- iscsi_storage
|
||||
|
||||
- name: Check mkfs marker presence (mkfs_once)
|
||||
ansible.builtin.stat:
|
||||
path: "{{ _iscsi_mkfs_marker }}"
|
||||
register: _iscsi_mkfs_marker_stat
|
||||
when: m.mkfs_once | default(false) | bool
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mkfs
|
||||
- iscsi_storage
|
||||
|
||||
- name: Fail if mountpoint is already mounted but mkfs_once requested
|
||||
ansible.builtin.command: "findmnt -n {{ m.path }}"
|
||||
@ -38,43 +164,98 @@
|
||||
when:
|
||||
- m.mkfs_once | default(false) | bool
|
||||
- _iscsi_mkfs_marker_stat.stat.exists is not defined or not _iscsi_mkfs_marker_stat.stat.exists
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mkfs
|
||||
- iscsi_storage
|
||||
|
||||
- name: Refuse to mkfs when mountpoint is mounted
|
||||
ansible.builtin.fail:
|
||||
msg: "Refusing to mkfs {{ m.src }} because {{ m.path }} is already mounted ({{ _iscsi_findmnt_before_mkfs.stdout | default('') }})."
|
||||
msg: "Refusing to mkfs for {{ m.path }} because it is already mounted ({{ _iscsi_findmnt_before_mkfs.stdout | default('') }})."
|
||||
when:
|
||||
- m.mkfs_once | default(false) | bool
|
||||
- _iscsi_mkfs_marker_stat.stat.exists is not defined or not _iscsi_mkfs_marker_stat.stat.exists
|
||||
- _iscsi_findmnt_before_mkfs.rc == 0
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mkfs
|
||||
- iscsi_storage
|
||||
|
||||
- name: Resolve block device for mkfs (mkfs_once)
|
||||
ansible.builtin.command: >-
|
||||
{{ (m.src | regex_search('^UUID=') )
|
||||
| ternary('blkid -U ' ~ (m.src | regex_replace('^UUID=', '')),
|
||||
(m.src | regex_search('^LABEL=') )
|
||||
| ternary('blkid -L ' ~ (m.src | regex_replace('^LABEL=', '')),
|
||||
'echo ' ~ (m.src))) }}
|
||||
register: _iscsi_mkfs_dev
|
||||
changed_when: false
|
||||
when:
|
||||
- m.mkfs_once | default(false) | bool
|
||||
- _iscsi_mkfs_marker_stat.stat.exists is not defined or not _iscsi_mkfs_marker_stat.stat.exists
|
||||
|
||||
- name: Require resolved device to look like /dev/* (mkfs_once)
|
||||
- name: Require iSCSI block device path for mkfs_once
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (_iscsi_mkfs_dev.stdout | default('') | trim) is match('^/dev/')
|
||||
fail_msg: "Unable to resolve a /dev/* block device for mkfs from src={{ m.src }} (got: {{ _iscsi_mkfs_dev.stdout | default('') | trim }})."
|
||||
- _iscsi_blockdev is defined
|
||||
- (_iscsi_blockdev | string | length) > 0
|
||||
fail_msg: "mkfs_once requested for {{ m.path }} but no block device was computed (set m.device or ensure iscsi_target+iscsi_portal are available)."
|
||||
when:
|
||||
- m.mkfs_once | default(false) | bool
|
||||
- _iscsi_mkfs_marker_stat.stat.exists is not defined or not _iscsi_mkfs_marker_stat.stat.exists
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mkfs
|
||||
- iscsi_storage
|
||||
|
||||
- name: Re-initialize filesystem (mkfs.ext4) (mkfs_once)
|
||||
ansible.builtin.command: "mkfs.ext4 -F {{ _iscsi_mkfs_dev.stdout | trim }}"
|
||||
- name: Re-initialize filesystem (mkfs_once)
|
||||
ansible.builtin.command: >-
|
||||
{{ ((m.fstype | default('ext4')) == 'xfs')
|
||||
| ternary(
|
||||
'mkfs.xfs -f ' ~ _iscsi_blockdev,
|
||||
(((m.fstype | default('ext4')) == 'ext4')
|
||||
| ternary(
|
||||
'mkfs.ext4 -F ' ~ _iscsi_blockdev,
|
||||
'mkfs -t ' ~ (m.fstype | default('ext4')) ~ ' ' ~ _iscsi_blockdev
|
||||
)
|
||||
)
|
||||
) }}
|
||||
changed_when: true
|
||||
when:
|
||||
- m.mkfs_once | default(false) | bool
|
||||
- _iscsi_mkfs_marker_stat.stat.exists is not defined or not _iscsi_mkfs_marker_stat.stat.exists
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mkfs
|
||||
- iscsi_storage
|
||||
|
||||
- name: Create filesystem when missing (mkfs_if_missing)
|
||||
ansible.builtin.command: >-
|
||||
{{ ((m.fstype | default('ext4')) == 'xfs')
|
||||
| ternary(
|
||||
'mkfs.xfs -f ' ~ _iscsi_blockdev,
|
||||
(((m.fstype | default('ext4')) == 'ext4')
|
||||
| ternary(
|
||||
'mkfs.ext4 -F ' ~ _iscsi_blockdev,
|
||||
'mkfs -t ' ~ (m.fstype | default('ext4')) ~ ' ' ~ _iscsi_blockdev
|
||||
)
|
||||
)
|
||||
) }}
|
||||
changed_when: true
|
||||
when:
|
||||
- m.mkfs_if_missing | default(false) | bool
|
||||
- _iscsi_blockdev is defined
|
||||
- (_iscsi_blockdev | string | length) > 0
|
||||
- _iscsi_blkid_type is not defined or (_iscsi_blkid_type.stdout | default('') | trim) == ''
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mkfs
|
||||
- iscsi_storage
|
||||
|
||||
- name: Refuse to mount when existing filesystem type does not match
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
Existing filesystem type on {{ _iscsi_blockdev }} is
|
||||
'{{ _iscsi_blkid_type.stdout | default('') | trim }}' but mount {{ m.path }}
|
||||
requests fstype='{{ m.fstype | default('ext4') }}'.
|
||||
Wipe the device signature (e.g. wipefs) or set mkfs_once=true if you intend
|
||||
to reformat this LUN.
|
||||
when:
|
||||
- _iscsi_blkid_type is defined
|
||||
- (_iscsi_blkid_type.stdout | default('') | trim) != ''
|
||||
- (_iscsi_blkid_type.stdout | trim) != (m.fstype | default('ext4'))
|
||||
- not (m.mkfs_once | default(false) | bool)
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mount
|
||||
- iscsi_storage
|
||||
|
||||
- name: Write mkfs marker (mkfs_once)
|
||||
ansible.builtin.file:
|
||||
@ -86,11 +267,64 @@
|
||||
when:
|
||||
- m.mkfs_once | default(false) | bool
|
||||
- _iscsi_mkfs_marker_stat.stat.exists is not defined or not _iscsi_mkfs_marker_stat.stat.exists
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mkfs
|
||||
- iscsi_storage
|
||||
|
||||
- name: Resolve mount source
|
||||
ansible.builtin.set_fact:
|
||||
_iscsi_mount_src: "{{ m.src }}"
|
||||
when:
|
||||
- m.src is defined
|
||||
- (m.src | string | length) > 0
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mount
|
||||
- iscsi_storage
|
||||
|
||||
- name: Resolve mount source UUID from block device
|
||||
ansible.builtin.command: "blkid -o value -s UUID {{ _iscsi_blockdev }}"
|
||||
register: _iscsi_blkid_uuid
|
||||
changed_when: false
|
||||
when:
|
||||
- (_iscsi_mount_src | default('') | string | length) == 0
|
||||
- _iscsi_blockdev is defined
|
||||
- (_iscsi_blockdev | string | length) > 0
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mount
|
||||
- iscsi_storage
|
||||
|
||||
- name: Require resolved UUID for mounting
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- _iscsi_blkid_uuid is defined
|
||||
- (_iscsi_blkid_uuid.stdout | default('') | trim) != ''
|
||||
fail_msg: "Unable to resolve a filesystem UUID for {{ m.path }} from {{ _iscsi_blockdev }}."
|
||||
when: (_iscsi_mount_src | default('') | string | length) == 0
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mount
|
||||
- iscsi_storage
|
||||
|
||||
- name: Set mount source to UUID
|
||||
ansible.builtin.set_fact:
|
||||
_iscsi_mount_src: "UUID={{ _iscsi_blkid_uuid.stdout | trim }}"
|
||||
when: (_iscsi_mount_src | default('') | string | length) == 0
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mount
|
||||
- iscsi_storage
|
||||
|
||||
- name: Mount {{ m.path }}
|
||||
ansible.builtin.mount:
|
||||
path: "{{ m.path }}"
|
||||
src: "{{ m.src }}"
|
||||
src: "{{ _iscsi_mount_src }}"
|
||||
fstype: "{{ m.fstype | default('ext4') }}"
|
||||
opts: "{{ m.opts | default('_netdev,noatime') }}"
|
||||
state: mounted
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mount
|
||||
- iscsi_storage
|
||||
|
||||
9
infrastructure/roles/iscsi/tasks/iscsi_target_mounts.yml
Normal file
9
infrastructure/roles/iscsi/tasks/iscsi_target_mounts.yml
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
- name: Mount target filesystems
|
||||
ansible.builtin.include_tasks: iscsi_mount.yml
|
||||
loop: "{{ t.mounts | default([]) }}"
|
||||
loop_control:
|
||||
loop_var: m
|
||||
label: "{{ m.path | default('<unknown-mountpoint>') }}"
|
||||
vars:
|
||||
iscsi_target: "{{ t }}"
|
||||
@ -4,13 +4,33 @@
|
||||
name:
|
||||
- open-iscsi
|
||||
- util-linux
|
||||
- xfsprogs
|
||||
state: present
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_login
|
||||
- iscsi_storage
|
||||
|
||||
- name: Ensure iscsid enabled
|
||||
ansible.builtin.service:
|
||||
name: iscsid
|
||||
state: started
|
||||
enabled: true
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_login
|
||||
- iscsi_storage
|
||||
|
||||
- name: Discover iSCSI targets
|
||||
ansible.builtin.command: >-
|
||||
iscsiadm -m discovery -t st -p {{ iscsi_portal }}
|
||||
register: _iscsi_discovery
|
||||
changed_when: false
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_discovery
|
||||
- iscsi_login
|
||||
- iscsi_storage
|
||||
|
||||
- name: Configure and login iSCSI targets
|
||||
ansible.builtin.include_tasks: iscsi_target.yml
|
||||
@ -18,3 +38,7 @@
|
||||
loop_control:
|
||||
loop_var: t
|
||||
label: "{{ t.iqn | default('<unknown-iqn>') }}"
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_login
|
||||
- iscsi_storage
|
||||
|
||||
@ -17,11 +17,11 @@
|
||||
| map(attribute='mounts') | list | flatten
|
||||
| selectattr('path', 'equalto', '/var/lib/rancher')
|
||||
| list | length) == 0
|
||||
fail_msg: >-
|
||||
Inventory attempts to mount `/var/lib/rancher` via iSCSI. K3s state storage is
|
||||
intended to be local host storage (e.g. USB3 SSD on myrddin). Remove
|
||||
`/var/lib/rancher` from `iscsi_targets[*].mounts` (or set
|
||||
`iscsi_allow_rancher_mount: true` if you are intentionally overriding this).
|
||||
fail_msg: >-
|
||||
Inventory attempts to mount `/var/lib/rancher` via iSCSI. K3s state storage is
|
||||
intended to be local host storage (e.g. USB3 SSD on myrddin). Remove
|
||||
`/var/lib/rancher` from `iscsi_targets[*].mounts` (or set
|
||||
`iscsi_allow_rancher_mount: true` if you are intentionally overriding this).
|
||||
when: not (iscsi_allow_rancher_mount | default(false) | bool)
|
||||
|
||||
- name: Remove stale iSCSI fstab entries
|
||||
@ -36,10 +36,15 @@
|
||||
- iscsi_cleanup
|
||||
|
||||
- name: Mount filesystems for iSCSI targets
|
||||
ansible.builtin.include_tasks: iscsi_mount.yml
|
||||
loop: "{{ iscsi_targets | default([]) | map(attribute='mounts') | list | flatten }}"
|
||||
ansible.builtin.include_tasks: iscsi_target_mounts.yml
|
||||
loop: "{{ iscsi_targets | default([]) }}"
|
||||
loop_control:
|
||||
loop_var: m
|
||||
loop_var: t
|
||||
label: "{{ t.iqn | default('<unknown-iqn>') }}"
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mount
|
||||
- iscsi_storage
|
||||
|
||||
- name: Guardrail - ensure required mountpoints are mounted
|
||||
ansible.builtin.command: "findmnt -n {{ item.path }}"
|
||||
@ -49,3 +54,7 @@
|
||||
loop: "{{ iscsi_targets | default([]) | map(attribute='mounts') | list | flatten }}"
|
||||
loop_control:
|
||||
label: "{{ item.path }}"
|
||||
tags:
|
||||
- iscsi
|
||||
- iscsi_mount
|
||||
- iscsi_storage
|
||||
|
||||
@ -110,6 +110,28 @@ class ProleController:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Ensure shared shell libraries are available for etc/ scripts.
|
||||
# Many scripts source `etc/common_core_lib.sh`, which in turn expects
|
||||
# `../lib/shell/common_core_lib.sh` relative to `$PROLE_HOME/etc`.
|
||||
try:
|
||||
source_lib_shell = self.project_root / "lib" / "shell"
|
||||
target_lib_shell = prole_home / "lib" / "shell"
|
||||
if source_lib_shell.exists():
|
||||
target_lib_shell.mkdir(parents=True, exist_ok=True)
|
||||
for src in source_lib_shell.rglob("*"):
|
||||
if not src.is_file():
|
||||
continue
|
||||
rel = src.relative_to(source_lib_shell)
|
||||
dst = target_lib_shell / rel
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
if (not dst.exists()) or (src.stat().st_mtime > dst.stat().st_mtime):
|
||||
shutil.copy2(src, dst)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Copy script and set permissions
|
||||
if source_script.exists():
|
||||
if source_script.resolve() != target_script.resolve():
|
||||
|
||||
@ -166,6 +166,40 @@ def test_run_script_copies_k8s_and_conf(tmp_path):
|
||||
assert (target / "conf").exists()
|
||||
|
||||
|
||||
def test_run_script_copies_lib_shell_for_etc_scripts(tmp_path):
|
||||
"""Regression: etc/ scripts may source ../lib/shell/common_core_lib.sh."""
|
||||
_scaffold_project(tmp_path)
|
||||
|
||||
(tmp_path / "lib" / "shell").mkdir(parents=True, exist_ok=True)
|
||||
(tmp_path / "lib" / "shell" / "common_core_lib.sh").write_text(
|
||||
"#!/usr/bin/env bash\ncommon_core_preparse_config() { :; }\n"
|
||||
)
|
||||
|
||||
script = tmp_path / "etc" / "needs_lib.sh"
|
||||
script.write_text(
|
||||
"#!/bin/bash\n"
|
||||
"SCRIPT_DIR=$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\n"
|
||||
"# shellcheck disable=SC1091\n"
|
||||
"source \"$SCRIPT_DIR/../lib/shell/common_core_lib.sh\"\n"
|
||||
"common_core_preparse_config\n"
|
||||
"echo ok\n"
|
||||
)
|
||||
|
||||
target = tmp_path / "prole_home_lib"
|
||||
target.mkdir()
|
||||
|
||||
ctrl = _make_controller(tmp_path)
|
||||
lines: list[str] = []
|
||||
rc = ctrl.run_script(
|
||||
"needs_lib.sh",
|
||||
env={"PROLE_HOME": str(target)},
|
||||
on_line=lambda l: lines.append(l),
|
||||
)
|
||||
assert rc == 0
|
||||
assert any("ok" in l for l in lines)
|
||||
assert (target / "lib" / "shell" / "common_core_lib.sh").exists()
|
||||
|
||||
|
||||
def test_run_script_copies_db_version(tmp_path):
|
||||
"""Covers the prole-db/.version copy path."""
|
||||
_scaffold_project(tmp_path)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user