Update documentation to centralize Vaultwarden integration details and enhance backup scripts
Refactor README, architecture, and backup documentation to emphasize the use of Vaultwarden for credential management across various services. Update scripts for Nextcloud, Gitea, Paperless, and others to reference Vaultwarden for sensitive information. Remove outdated references to previous backup strategies and ensure clarity on credential retrieval processes. This improves security practices and streamlines backup operations.
This commit is contained in:
@@ -27,7 +27,7 @@ OUTPUT="$BACKUP_DIR/nextcloud-db-$DATE.sql.gz"
|
||||
ERR=$(mktemp)
|
||||
trap "rm -f '$ERR'" EXIT
|
||||
|
||||
# PGPASSWORD из Vaultwarden (объект NEXTCLOUD: поле dbpassword или пароль). См. docs/backup/proxmox-phase1-backup.md
|
||||
# PGPASSWORD из Vaultwarden (объект NEXTCLOUD: поле dbpassword или пароль). См. docs/vaultwarden-secrets.md
|
||||
PG_ENV_ARGS=""
|
||||
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||
if [ -f "$BW_MASTER_PASSWORD_FILE" ] && command -v bw >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then
|
||||
|
||||
@@ -24,7 +24,7 @@ OUTPUT="$BACKUP_DIR/gitea-db-$DATE.sql.gz"
|
||||
ERR=$(mktemp)
|
||||
trap "rm -f '$ERR'" EXIT
|
||||
|
||||
# PGPASSWORD из Vaultwarden (объект GITEA, пароль). См. docs/backup/proxmox-phase1-backup.md
|
||||
# PGPASSWORD из Vaultwarden (объект GITEA, пароль). См. docs/vaultwarden-secrets.md
|
||||
PG_ENV_ARGS=""
|
||||
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||
if [ -f "$BW_MASTER_PASSWORD_FILE" ] && command -v bw >/dev/null 2>&1; then
|
||||
|
||||
@@ -24,7 +24,7 @@ OUTPUT="$BACKUP_DIR/paperless-db-$DATE.sql.gz"
|
||||
ERR=$(mktemp)
|
||||
trap "rm -f '$ERR'" EXIT
|
||||
|
||||
# PGPASSWORD из Vaultwarden (объект PAPERLESS, пароль). См. docs/backup/proxmox-phase1-backup.md
|
||||
# PGPASSWORD из Vaultwarden (объект PAPERLESS, пароль). См. docs/vaultwarden-secrets.md
|
||||
PG_ENV_ARGS=""
|
||||
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||
if [ -f "$BW_MASTER_PASSWORD_FILE" ] && command -v bw >/dev/null 2>&1; then
|
||||
|
||||
@@ -18,7 +18,7 @@ fi
|
||||
# Секреты из Vaultwarden (объект RESTIC)
|
||||
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||
if [ ! -f "$BW_MASTER_PASSWORD_FILE" ]; then
|
||||
echo "Нет файла с мастер-паролем Vaultwarden: $BW_MASTER_PASSWORD_FILE (chmod 600). См. docs/backup/proxmox-phase1-backup.md"
|
||||
echo "Нет файла с мастер-паролем Vaultwarden: $BW_MASTER_PASSWORD_FILE (chmod 600). См. docs/vaultwarden-secrets.md"
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v bw >/dev/null 2>&1 || ! command -v jq >/dev/null 2>&1; then
|
||||
|
||||
@@ -22,7 +22,7 @@ fi
|
||||
# Секреты из Vaultwarden (объект RESTIC)
|
||||
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||
if [ ! -f "$BW_MASTER_PASSWORD_FILE" ]; then
|
||||
echo "Нет файла с мастер-паролем Vaultwarden: $BW_MASTER_PASSWORD_FILE (chmod 600). См. docs/backup/proxmox-phase1-backup.md"
|
||||
echo "Нет файла с мастер-паролем Vaultwarden: $BW_MASTER_PASSWORD_FILE (chmod 600). См. docs/vaultwarden-secrets.md"
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v bw >/dev/null 2>&1 || ! command -v jq >/dev/null 2>&1; then
|
||||
|
||||
26
scripts/certbot-hooks/check-beget-credentials.sh
Normal file
26
scripts/certbot-hooks/check-beget-credentials.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
# Pre-hook для certbot: проверка beget.ini перед renew
|
||||
# Путь: /etc/letsencrypt/renewal-hooks/pre/check-beget-credentials.sh
|
||||
# При отсутствии файла или неверных правах — exit 1, certbot не выполнит renew.
|
||||
|
||||
BEGET_INI="/root/.secrets/certbot/beget.ini"
|
||||
|
||||
if [ ! -f "$BEGET_INI" ]; then
|
||||
echo "check-beget-credentials: $BEGET_INI not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mode=$(stat -c '%a' "$BEGET_INI" 2>/dev/null)
|
||||
owner=$(stat -c '%u' "$BEGET_INI" 2>/dev/null)
|
||||
|
||||
if [ "$mode" != "600" ]; then
|
||||
echo "check-beget-credentials: $BEGET_INI has mode $mode, expected 600" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$owner" != "0" ]; then
|
||||
echo "check-beget-credentials: $BEGET_INI owner $owner, expected root (0)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
116
scripts/deploy-beget-credentials.sh
Normal file
116
scripts/deploy-beget-credentials.sh
Normal file
@@ -0,0 +1,116 @@
|
||||
#!/bin/bash
|
||||
# deploy-beget-credentials.sh — деплой кредов Beget для certbot DNS-01 в CT 100
|
||||
# Секреты из Vaultwarden (объект beget). Атомарная запись beget.ini.
|
||||
#
|
||||
# Использование:
|
||||
# /root/scripts/deploy-beget-credentials.sh # деплой
|
||||
# /root/scripts/deploy-beget-credentials.sh --dry-run # проверка без записи
|
||||
#
|
||||
# Ротация: сменил пароль в Vaultwarden → запустил скрипт → готово.
|
||||
#
|
||||
# Требования: bw, jq, /root/.bw-master (chmod 600)
|
||||
|
||||
set -e
|
||||
|
||||
CT_ID=100
|
||||
BEGET_INI_PATH="/root/.secrets/certbot/beget.ini"
|
||||
BW_MASTER_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||
DRY_RUN=false
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--dry-run) DRY_RUN=true ;;
|
||||
esac
|
||||
done
|
||||
|
||||
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}"
|
||||
|
||||
log() { echo "[$(date -Iseconds)] $*"; }
|
||||
err() { echo "[$(date -Iseconds)] ERROR: $*" >&2; }
|
||||
|
||||
ensure_bw_unlocked() {
|
||||
local status
|
||||
status=$(bw status 2>/dev/null | jq -r '.status' 2>/dev/null || echo "unknown")
|
||||
if [ "$status" = "unlocked" ]; then
|
||||
log "bw already unlocked, reusing session"
|
||||
return 0
|
||||
fi
|
||||
if [ ! -f "$BW_MASTER_FILE" ]; then
|
||||
err "Missing $BW_MASTER_FILE"
|
||||
exit 1
|
||||
fi
|
||||
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_FILE" --raw 2>/dev/null) || {
|
||||
err "bw unlock failed"
|
||||
exit 1
|
||||
}
|
||||
log "bw unlocked"
|
||||
}
|
||||
|
||||
get_secrets() {
|
||||
BEGET_USER=$(bw get username "beget" 2>/dev/null)
|
||||
BEGET_PASS=$(bw get password "beget" 2>/dev/null)
|
||||
if [ -z "$BEGET_USER" ] || [ -z "$BEGET_PASS" ]; then
|
||||
err "beget: missing username or password in Vaultwarden"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
gen_ini() {
|
||||
local tmp
|
||||
tmp=$(mktemp)
|
||||
cat > "$tmp" << EOF
|
||||
dns_beget_api_username = ${BEGET_USER}
|
||||
dns_beget_api_password = ${BEGET_PASS}
|
||||
EOF
|
||||
echo "$tmp"
|
||||
}
|
||||
|
||||
push_ini_atomic() {
|
||||
local tmp="$1"
|
||||
local dir
|
||||
dir=$(dirname "$BEGET_INI_PATH")
|
||||
pct exec "$CT_ID" -- mkdir -p "$dir"
|
||||
pct push "$CT_ID" "$tmp" "${BEGET_INI_PATH}.tmp"
|
||||
pct exec "$CT_ID" -- bash -c "mv ${BEGET_INI_PATH}.tmp ${BEGET_INI_PATH} && chmod 600 ${BEGET_INI_PATH} && chown root:root ${BEGET_INI_PATH}"
|
||||
log "beget.ini written (atomic), chmod 600, owner root"
|
||||
}
|
||||
|
||||
deploy_pre_hook() {
|
||||
local hook_path="/etc/letsencrypt/renewal-hooks/pre/check-beget-credentials.sh"
|
||||
local hook_src
|
||||
hook_src="$(cd "$(dirname "$0")" && pwd)/certbot-hooks/check-beget-credentials.sh"
|
||||
if [ ! -f "$hook_src" ]; then
|
||||
log "pre-hook source not found ($hook_src), skip"
|
||||
return 0
|
||||
fi
|
||||
if pct exec "$CT_ID" -- test -f "$hook_path" 2>/dev/null; then
|
||||
pct push "$CT_ID" "$hook_src" "$hook_path"
|
||||
pct exec "$CT_ID" -- chmod +x "$hook_path"
|
||||
log "pre-hook updated"
|
||||
else
|
||||
pct push "$CT_ID" "$hook_src" "$hook_path"
|
||||
pct exec "$CT_ID" -- chmod +x "$hook_path"
|
||||
log "pre-hook deployed"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
log "deploy-beget-credentials start (dry_run=$DRY_RUN)"
|
||||
ensure_bw_unlocked
|
||||
get_secrets
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log "DRY-RUN: would push beget.ini and deploy pre-hook"
|
||||
log " dns_beget_api_username=$BEGET_USER"
|
||||
log " dns_beget_api_password=***"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
tmp=$(gen_ini)
|
||||
trap "rm -f $tmp" EXIT
|
||||
push_ini_atomic "$tmp"
|
||||
deploy_pre_hook
|
||||
log "done"
|
||||
}
|
||||
|
||||
main
|
||||
94
scripts/deploy-galene-credentials.sh
Normal file
94
scripts/deploy-galene-credentials.sh
Normal file
@@ -0,0 +1,94 @@
|
||||
#!/bin/bash
|
||||
# deploy-galene-credentials.sh — деплой TURN-кредов Galene в CT 108
|
||||
# Секреты из Vaultwarden (объект GALENE, поле config — JSON ice-servers).
|
||||
#
|
||||
# Использование:
|
||||
# /root/scripts/deploy-galene-credentials.sh
|
||||
# /root/scripts/deploy-galene-credentials.sh --dry-run
|
||||
#
|
||||
# Ротация: сменил TURN username/credential в Vaultwarden → запустил скрипт → systemctl restart galene
|
||||
#
|
||||
# Требования: bw, jq, /root/.bw-master (chmod 600)
|
||||
|
||||
set -e
|
||||
|
||||
CT_ID=108
|
||||
ICE_SERVERS_PATH="/opt/galene-data/data/ice-servers.json"
|
||||
BW_MASTER_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||
DRY_RUN=false
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--dry-run) DRY_RUN=true ;;
|
||||
esac
|
||||
done
|
||||
|
||||
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}"
|
||||
|
||||
log() { echo "[$(date -Iseconds)] $*"; }
|
||||
err() { echo "[$(date -Iseconds)] ERROR: $*" >&2; }
|
||||
|
||||
ensure_bw_unlocked() {
|
||||
local status
|
||||
status=$(bw status 2>/dev/null | jq -r '.status' 2>/dev/null || echo "unknown")
|
||||
if [ "$status" = "unlocked" ]; then
|
||||
log "bw already unlocked, reusing session"
|
||||
return 0
|
||||
fi
|
||||
if [ ! -f "$BW_MASTER_FILE" ]; then
|
||||
err "Missing $BW_MASTER_FILE"
|
||||
exit 1
|
||||
fi
|
||||
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_FILE" --raw 2>/dev/null) || {
|
||||
err "bw unlock failed"
|
||||
exit 1
|
||||
}
|
||||
log "bw unlocked"
|
||||
}
|
||||
|
||||
get_secrets() {
|
||||
local config
|
||||
config=$(bw get item "GALENE" 2>/dev/null | jq -r '.fields[] | select(.name=="config") | .value // empty')
|
||||
if [ -z "$config" ]; then
|
||||
err "GALENE: missing config field (JSON ice-servers)"
|
||||
exit 1
|
||||
fi
|
||||
if ! echo "$config" | jq . >/dev/null 2>&1; then
|
||||
err "GALENE config: invalid JSON"
|
||||
exit 1
|
||||
fi
|
||||
ICE_CONFIG="$config"
|
||||
}
|
||||
|
||||
push_ice_servers() {
|
||||
local tmp
|
||||
tmp=$(mktemp)
|
||||
echo "$ICE_CONFIG" | jq -c . > "$tmp"
|
||||
pct push "$CT_ID" "$tmp" "${ICE_SERVERS_PATH}.tmp"
|
||||
rm -f "$tmp"
|
||||
pct exec "$CT_ID" -- bash -c "chmod 600 ${ICE_SERVERS_PATH}.tmp && mv ${ICE_SERVERS_PATH}.tmp ${ICE_SERVERS_PATH}"
|
||||
log "ice-servers.json written (atomic), chmod 600"
|
||||
}
|
||||
|
||||
restart_galene() {
|
||||
pct exec "$CT_ID" -- systemctl restart galene
|
||||
log "galene restarted"
|
||||
}
|
||||
|
||||
main() {
|
||||
log "deploy-galene-credentials start (dry_run=$DRY_RUN)"
|
||||
ensure_bw_unlocked
|
||||
get_secrets
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log "DRY-RUN: would push ice-servers.json and restart galene"
|
||||
log " config: $(echo "$ICE_CONFIG" | jq -c .)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
push_ice_servers
|
||||
restart_galene
|
||||
log "done"
|
||||
}
|
||||
|
||||
main
|
||||
117
scripts/deploy-gitea-credentials.sh
Normal file
117
scripts/deploy-gitea-credentials.sh
Normal file
@@ -0,0 +1,117 @@
|
||||
#!/bin/bash
|
||||
# deploy-gitea-credentials.sh — деплой кредов Gitea в CT 103
|
||||
# Секреты из Vaultwarden (объект GITEA). Атомарная запись .env.
|
||||
#
|
||||
# Использование:
|
||||
# /root/scripts/deploy-gitea-credentials.sh
|
||||
# /root/scripts/deploy-gitea-credentials.sh --dry-run
|
||||
#
|
||||
# Ротация: сменил пароль/токен в Vaultwarden → запустил скрипт → docker compose up -d --force-recreate
|
||||
#
|
||||
# Требования: bw, jq, /root/.bw-master (chmod 600)
|
||||
|
||||
set -e
|
||||
|
||||
CT_ID=103
|
||||
GITEA_PATH="/opt/gitea"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
BW_MASTER_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||
DRY_RUN=false
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--dry-run) DRY_RUN=true ;;
|
||||
esac
|
||||
done
|
||||
|
||||
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}"
|
||||
|
||||
log() { echo "[$(date -Iseconds)] $*"; }
|
||||
err() { echo "[$(date -Iseconds)] ERROR: $*" >&2; }
|
||||
|
||||
ensure_bw_unlocked() {
|
||||
local status
|
||||
status=$(bw status 2>/dev/null | jq -r '.status' 2>/dev/null || echo "unknown")
|
||||
if [ "$status" = "unlocked" ]; then
|
||||
log "bw already unlocked, reusing session"
|
||||
return 0
|
||||
fi
|
||||
if [ ! -f "$BW_MASTER_FILE" ]; then
|
||||
err "Missing $BW_MASTER_FILE"
|
||||
exit 1
|
||||
fi
|
||||
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_FILE" --raw 2>/dev/null) || {
|
||||
err "bw unlock failed"
|
||||
exit 1
|
||||
}
|
||||
log "bw unlocked"
|
||||
}
|
||||
|
||||
get_secrets() {
|
||||
local item
|
||||
item=$(bw get item "GITEA" 2>/dev/null)
|
||||
POSTGRES_PASSWORD=$(bw get password "GITEA" 2>/dev/null)
|
||||
GITEA_RUNNER_REGISTRATION_TOKEN=$(echo "$item" | jq -r '.fields[] | select(.name=="GITEA_RUNNER_REGISTRATION_TOKEN") | .value // empty')
|
||||
|
||||
if [ -z "$POSTGRES_PASSWORD" ]; then
|
||||
err "GITEA: missing password (POSTGRES_PASSWORD)"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$GITEA_RUNNER_REGISTRATION_TOKEN" ]; then
|
||||
err "GITEA: missing GITEA_RUNNER_REGISTRATION_TOKEN field"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
gen_env() {
|
||||
local tmp
|
||||
tmp=$(mktemp)
|
||||
cat > "$tmp" << EOF
|
||||
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
GITEA_RUNNER_REGISTRATION_TOKEN=${GITEA_RUNNER_REGISTRATION_TOKEN}
|
||||
EOF
|
||||
echo "$tmp"
|
||||
}
|
||||
|
||||
push_env_atomic() {
|
||||
local tmp="$1"
|
||||
< "$tmp" pct exec "$CT_ID" -- bash -c "cat > ${GITEA_PATH}/.env.tmp && chmod 600 ${GITEA_PATH}/.env.tmp && mv ${GITEA_PATH}/.env.tmp ${GITEA_PATH}/.env"
|
||||
log ".env written (atomic), chmod 600"
|
||||
}
|
||||
|
||||
push_compose() {
|
||||
local compose_src="${SCRIPT_DIR}/gitea/docker-compose.yml"
|
||||
if [ -f "$compose_src" ]; then
|
||||
pct push "$CT_ID" "$compose_src" "${GITEA_PATH}/docker-compose.yml"
|
||||
log "docker-compose.yml pushed"
|
||||
else
|
||||
log "WARN: ${compose_src} not found, skipping compose push"
|
||||
fi
|
||||
}
|
||||
|
||||
run_compose() {
|
||||
pct exec "$CT_ID" -- bash -c "cd ${GITEA_PATH} && docker compose up -d --force-recreate"
|
||||
log "Gitea started"
|
||||
}
|
||||
|
||||
main() {
|
||||
log "deploy-gitea-credentials start (dry_run=$DRY_RUN)"
|
||||
ensure_bw_unlocked
|
||||
get_secrets
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log "DRY-RUN: would push .env and run compose"
|
||||
log " POSTGRES_PASSWORD=***"
|
||||
log " GITEA_RUNNER_REGISTRATION_TOKEN=***"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
tmp=$(gen_env)
|
||||
trap "rm -f $tmp" EXIT
|
||||
push_env_atomic "$tmp"
|
||||
push_compose
|
||||
run_compose
|
||||
log "done"
|
||||
}
|
||||
|
||||
main
|
||||
176
scripts/deploy-immich-credentials.sh
Normal file
176
scripts/deploy-immich-credentials.sh
Normal file
@@ -0,0 +1,176 @@
|
||||
#!/bin/bash
|
||||
# deploy-immich-credentials.sh — деплой кредов Immich и immich-deduper на VM 200
|
||||
# Секреты из Vaultwarden (объекты IMMICH, IMMICH_DEDUPER).
|
||||
#
|
||||
# Использование:
|
||||
# /root/scripts/deploy-immich-credentials.sh
|
||||
# /root/scripts/deploy-immich-credentials.sh --dry-run
|
||||
#
|
||||
# Требования: bw, jq, /root/.bw-master, SSH без пароля root@host → admin@192.168.1.200
|
||||
#
|
||||
# Vaultwarden: IMMICH — поля DB_PASSWORD, IMMICH_API_KEY, GEMINI_API_KEY и др. (см. .env).
|
||||
# IMMICH_DEDUPER — поля PSQL_PASS, DEDUP_*, IMMICH_PATH, PSQL_*.
|
||||
|
||||
set -e
|
||||
|
||||
VM_SSH="admin@192.168.1.200"
|
||||
IMMICH_PATH="/opt/immich"
|
||||
DEDUPER_PATH="/opt/immich-deduper"
|
||||
BW_MASTER_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||
DRY_RUN=false
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--dry-run) DRY_RUN=true ;;
|
||||
esac
|
||||
done
|
||||
|
||||
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}"
|
||||
|
||||
log() { echo "[$(date -Iseconds)] $*"; }
|
||||
err() { echo "[$(date -Iseconds)] ERROR: $*" >&2; }
|
||||
|
||||
ensure_bw_unlocked() {
|
||||
local status
|
||||
status=$(bw status 2>/dev/null | jq -r '.status' 2>/dev/null || echo "unknown")
|
||||
if [ "$status" = "unlocked" ]; then
|
||||
log "bw already unlocked, reusing session"
|
||||
return 0
|
||||
fi
|
||||
if [ ! -f "$BW_MASTER_FILE" ]; then
|
||||
err "Missing $BW_MASTER_FILE"
|
||||
exit 1
|
||||
fi
|
||||
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_FILE" --raw 2>/dev/null) || {
|
||||
err "bw unlock failed"
|
||||
exit 1
|
||||
}
|
||||
log "bw unlocked"
|
||||
}
|
||||
|
||||
get_field() {
|
||||
local item="$1" name="$2"
|
||||
echo "$item" | jq -r ".fields[] | select(.name==\"$name\") | .value // empty"
|
||||
}
|
||||
|
||||
get_immich_secrets() {
|
||||
local id
|
||||
id=$(bw list items --search IMMICH 2>/dev/null | jq -r '.[] | select(.name=="IMMICH") | .id' | head -1) || true
|
||||
[ -z "$id" ] && id=$(bw list items 2>/dev/null | jq -r '.[] | select(.name=="IMMICH") | .id' | head -1) || true
|
||||
[ -z "$id" ] && { err "IMMICH not found in Vaultwarden"; exit 1; }
|
||||
IMMICH_ITEM=$(bw get item "$id" 2>/dev/null) || { err "IMMICH get item failed for id=$id"; exit 1; }
|
||||
DB_PASSWORD=$(get_field "$IMMICH_ITEM" "DB_PASSWORD")
|
||||
IMMICH_API_KEY=$(get_field "$IMMICH_ITEM" "IMMICH_API_KEY")
|
||||
GEMINI_API_KEY=$(get_field "$IMMICH_ITEM" "GEMINI_API_KEY")
|
||||
if [ -z "$DB_PASSWORD" ]; then err "IMMICH: missing DB_PASSWORD field"; exit 1; fi
|
||||
if [ -z "$IMMICH_API_KEY" ]; then err "IMMICH: missing IMMICH_API_KEY field"; exit 1; fi
|
||||
}
|
||||
|
||||
get_deduper_secrets() {
|
||||
local id
|
||||
id=$(bw list items 2>/dev/null | jq -r '.[] | select(.name=="IMMICH_DEDUPER") | .id' | head -1)
|
||||
[ -z "$id" ] && { err "IMMICH_DEDUPER not found in Vaultwarden"; exit 1; }
|
||||
DEDUP_ITEM=$(bw get item "$id" 2>/dev/null) || {
|
||||
err "IMMICH_DEDUPER not found in Vaultwarden"
|
||||
exit 1
|
||||
}
|
||||
PSQL_PASS=$(get_field "$DEDUP_ITEM" "PSQL_PASS")
|
||||
[ -z "$PSQL_PASS" ] && PSQL_PASS=$(echo "$DEDUP_ITEM" | jq -r '.login.password // empty')
|
||||
DEDUP_PORT=$(get_field "$DEDUP_ITEM" "DEDUP_PORT")
|
||||
DEDUP_DATA=$(get_field "$DEDUP_ITEM" "DEDUP_DATA")
|
||||
DEDUP_IMAGE=$(get_field "$DEDUP_ITEM" "DEDUP_IMAGE")
|
||||
IMMICH_PATH_FIELD=$(get_field "$DEDUP_ITEM" "IMMICH_PATH")
|
||||
PSQL_HOST=$(get_field "$DEDUP_ITEM" "PSQL_HOST")
|
||||
PSQL_PORT=$(get_field "$DEDUP_ITEM" "PSQL_PORT")
|
||||
PSQL_DB=$(get_field "$DEDUP_ITEM" "PSQL_DB")
|
||||
[ -z "$PSQL_PASS" ] && PSQL_PASS="${DB_PASSWORD:-}"
|
||||
DEDUP_PORT="${DEDUP_PORT:-8086}"
|
||||
DEDUP_DATA="${DEDUP_DATA:-/opt/immich-deduper/data}"
|
||||
DEDUP_IMAGE="${DEDUP_IMAGE:-razgrizhsu/immich-deduper:latest-cpu}"
|
||||
IMMICH_PATH_FIELD="${IMMICH_PATH_FIELD:-/mnt/data/library}"
|
||||
PSQL_HOST="${PSQL_HOST:-database}"
|
||||
PSQL_PORT="${PSQL_PORT:-5432}"
|
||||
PSQL_DB="${PSQL_DB:-immich}"
|
||||
}
|
||||
|
||||
gen_immich_env() {
|
||||
local tmp
|
||||
tmp=$(mktemp)
|
||||
cat > "$tmp" << EOF
|
||||
# Immich .env (generated from Vaultwarden)
|
||||
UPLOAD_LOCATION=/mnt/data/library
|
||||
DB_DATA_LOCATION=/mnt/data/postgres
|
||||
IMMICH_VERSION=v2
|
||||
DB_PASSWORD=${DB_PASSWORD}
|
||||
DB_USERNAME=postgres
|
||||
DB_DATABASE_NAME=immich
|
||||
IMMICH_URL=http://immich-server:2283
|
||||
IMMICH_API_KEY=${IMMICH_API_KEY}
|
||||
DB_HOST=immich_postgres
|
||||
DB_PORT=5432
|
||||
EXTERNAL_IMMICH_URL=https://immich.katykhin.ru
|
||||
GEMINI_API_KEY=${GEMINI_API_KEY}
|
||||
EOF
|
||||
echo "$tmp"
|
||||
}
|
||||
|
||||
gen_deduper_env() {
|
||||
local tmp
|
||||
tmp=$(mktemp)
|
||||
cat > "$tmp" << EOF
|
||||
# Deduper .env (generated from Vaultwarden)
|
||||
DEDUP_PORT=${DEDUP_PORT}
|
||||
DEDUP_DATA=${DEDUP_DATA}
|
||||
DEDUP_IMAGE=${DEDUP_IMAGE}
|
||||
IMMICH_PATH=${IMMICH_PATH_FIELD}
|
||||
PSQL_HOST=${PSQL_HOST}
|
||||
PSQL_PORT=${PSQL_PORT}
|
||||
PSQL_DB=${PSQL_DB}
|
||||
PSQL_USER=postgres
|
||||
PSQL_PASS=${PSQL_PASS}
|
||||
EOF
|
||||
echo "$tmp"
|
||||
}
|
||||
|
||||
push_to_vm() {
|
||||
local local_file="$1" remote_path="$2"
|
||||
scp -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new -q "$local_file" "${VM_SSH}:/tmp/deploy-env.tmp" || {
|
||||
err "scp to ${VM_SSH} failed. Ensure SSH key from Proxmox: ssh-copy-id ${VM_SSH}"
|
||||
exit 1
|
||||
}
|
||||
ssh -o BatchMode=yes -o ConnectTimeout=10 "$VM_SSH" "sudo mv /tmp/deploy-env.tmp ${remote_path} && sudo chmod 600 ${remote_path}" || {
|
||||
err "ssh to ${VM_SSH} failed"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
run_compose() {
|
||||
ssh -o BatchMode=yes "$VM_SSH" "cd ${IMMICH_PATH} && sudo docker compose up -d --force-recreate"
|
||||
ssh -o BatchMode=yes "$VM_SSH" "cd ${DEDUPER_PATH} && sudo docker compose up -d --force-recreate"
|
||||
log "Immich and deduper started"
|
||||
}
|
||||
|
||||
main() {
|
||||
log "deploy-immich-credentials start (dry_run=$DRY_RUN)"
|
||||
ensure_bw_unlocked
|
||||
get_immich_secrets
|
||||
get_deduper_secrets
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log "DRY-RUN: would push .env files and run compose"
|
||||
log " DB_PASSWORD=*** IMMICH_API_KEY=***"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
tmp_immich=$(gen_immich_env)
|
||||
tmp_deduper=$(gen_deduper_env)
|
||||
trap "rm -f $tmp_immich $tmp_deduper" EXIT
|
||||
push_to_vm "$tmp_immich" "${IMMICH_PATH}/.env"
|
||||
log "Immich .env written"
|
||||
push_to_vm "$tmp_deduper" "${DEDUPER_PATH}/.env"
|
||||
log "Deduper .env written"
|
||||
run_compose
|
||||
log "done"
|
||||
}
|
||||
|
||||
main
|
||||
116
scripts/deploy-invidious-credentials.sh
Normal file
116
scripts/deploy-invidious-credentials.sh
Normal file
@@ -0,0 +1,116 @@
|
||||
#!/bin/bash
|
||||
# deploy-invidious-credentials.sh — деплой кредов Invidious в CT 107
|
||||
# Секреты из Vaultwarden (объект INVIDIOUS). Атомарная запись .env.
|
||||
#
|
||||
# Использование:
|
||||
# /root/scripts/deploy-invidious-credentials.sh
|
||||
# /root/scripts/deploy-invidious-credentials.sh --dry-run
|
||||
#
|
||||
# Ротация: сменил пароль/ключи в Vaultwarden → запустил скрипт → docker compose up -d --force-recreate
|
||||
#
|
||||
# Требования: bw, jq, /root/.bw-master (chmod 600)
|
||||
|
||||
set -e
|
||||
|
||||
CT_ID=107
|
||||
INVIDIOUS_PATH="/opt/invidious"
|
||||
BW_MASTER_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||
DRY_RUN=false
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--dry-run) DRY_RUN=true ;;
|
||||
esac
|
||||
done
|
||||
|
||||
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}"
|
||||
|
||||
log() { echo "[$(date -Iseconds)] $*"; }
|
||||
err() { echo "[$(date -Iseconds)] ERROR: $*" >&2; }
|
||||
|
||||
ensure_bw_unlocked() {
|
||||
local status
|
||||
status=$(bw status 2>/dev/null | jq -r '.status' 2>/dev/null || echo "unknown")
|
||||
if [ "$status" = "unlocked" ]; then
|
||||
log "bw already unlocked, reusing session"
|
||||
return 0
|
||||
fi
|
||||
if [ ! -f "$BW_MASTER_FILE" ]; then
|
||||
err "Missing $BW_MASTER_FILE"
|
||||
exit 1
|
||||
fi
|
||||
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_FILE" --raw 2>/dev/null) || {
|
||||
err "bw unlock failed"
|
||||
exit 1
|
||||
}
|
||||
log "bw unlocked"
|
||||
}
|
||||
|
||||
get_secrets() {
|
||||
local item
|
||||
item=$(bw get item "INVIDIOUS" 2>/dev/null)
|
||||
POSTGRES_USER=$(echo "$item" | jq -r '.login.username // empty')
|
||||
POSTGRES_PASSWORD=$(bw get password "INVIDIOUS" 2>/dev/null)
|
||||
INVIDIOUS_COMPANION_KEY=$(echo "$item" | jq -r '.fields[] | select(.name=="SERVER_SECRET_KEY") | .value // empty')
|
||||
HMAC_KEY=$(echo "$item" | jq -r '.fields[] | select(.name=="HMAC_KEY") | .value // empty')
|
||||
|
||||
if [ -z "$POSTGRES_USER" ] || [ -z "$POSTGRES_PASSWORD" ]; then
|
||||
err "INVIDIOUS: missing username or password"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$INVIDIOUS_COMPANION_KEY" ]; then
|
||||
err "INVIDIOUS: missing SERVER_SECRET_KEY field"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$HMAC_KEY" ]; then
|
||||
err "INVIDIOUS: missing HMAC_KEY field"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
gen_env() {
|
||||
local tmp
|
||||
tmp=$(mktemp)
|
||||
cat > "$tmp" << EOF
|
||||
POSTGRES_USER=${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB=invidious
|
||||
INVIDIOUS_COMPANION_KEY=${INVIDIOUS_COMPANION_KEY}
|
||||
HMAC_KEY=${HMAC_KEY}
|
||||
EOF
|
||||
echo "$tmp"
|
||||
}
|
||||
|
||||
push_env_atomic() {
|
||||
local tmp="$1"
|
||||
< "$tmp" pct exec "$CT_ID" -- bash -c "cat > ${INVIDIOUS_PATH}/.env.tmp && chmod 600 ${INVIDIOUS_PATH}/.env.tmp && mv ${INVIDIOUS_PATH}/.env.tmp ${INVIDIOUS_PATH}/.env"
|
||||
log ".env written (atomic), chmod 600"
|
||||
}
|
||||
|
||||
run_compose() {
|
||||
pct exec "$CT_ID" -- bash -c "cd ${INVIDIOUS_PATH} && docker compose up -d --force-recreate"
|
||||
log "Invidious started"
|
||||
}
|
||||
|
||||
main() {
|
||||
log "deploy-invidious-credentials start (dry_run=$DRY_RUN)"
|
||||
ensure_bw_unlocked
|
||||
get_secrets
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log "DRY-RUN: would push .env and run compose"
|
||||
log " POSTGRES_USER=$POSTGRES_USER"
|
||||
log " POSTGRES_PASSWORD=***"
|
||||
log " INVIDIOUS_COMPANION_KEY=***"
|
||||
log " HMAC_KEY=***"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
tmp=$(gen_env)
|
||||
trap "rm -f $tmp" EXIT
|
||||
push_env_atomic "$tmp"
|
||||
run_compose
|
||||
log "done"
|
||||
}
|
||||
|
||||
main
|
||||
125
scripts/deploy-nextcloud-credentials.sh
Normal file
125
scripts/deploy-nextcloud-credentials.sh
Normal file
@@ -0,0 +1,125 @@
|
||||
#!/bin/bash
|
||||
# deploy-nextcloud-credentials.sh — деплой кредов Nextcloud в CT 101
|
||||
# Секреты из Vaultwarden (объект NEXTCLOUD). Атомарная запись .env, обновление config.php через occ.
|
||||
#
|
||||
# Использование:
|
||||
# /root/scripts/deploy-nextcloud-credentials.sh
|
||||
# /root/scripts/deploy-nextcloud-credentials.sh --dry-run
|
||||
#
|
||||
# Ротация: сменил пароль/ключи в Vaultwarden → запустил скрипт → docker compose up -d --force-recreate
|
||||
#
|
||||
# Требования: bw, jq, /root/.bw-master (chmod 600)
|
||||
|
||||
set -e
|
||||
|
||||
CT_ID=101
|
||||
NEXTCLOUD_PATH="/opt/nextcloud"
|
||||
CONFIG_PATH="/mnt/nextcloud-data/html/config/config.php"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
BW_MASTER_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||
DRY_RUN=false
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--dry-run) DRY_RUN=true ;;
|
||||
esac
|
||||
done
|
||||
|
||||
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}"
|
||||
|
||||
log() { echo "[$(date -Iseconds)] $*"; }
|
||||
err() { echo "[$(date -Iseconds)] ERROR: $*" >&2; }
|
||||
|
||||
ensure_bw_unlocked() {
|
||||
local status
|
||||
status=$(bw status 2>/dev/null | jq -r '.status' 2>/dev/null || echo "unknown")
|
||||
if [ "$status" = "unlocked" ]; then
|
||||
log "bw already unlocked, reusing session"
|
||||
return 0
|
||||
fi
|
||||
if [ ! -f "$BW_MASTER_FILE" ]; then
|
||||
err "Missing $BW_MASTER_FILE"
|
||||
exit 1
|
||||
fi
|
||||
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_FILE" --raw 2>/dev/null) || {
|
||||
err "bw unlock failed"
|
||||
exit 1
|
||||
}
|
||||
log "bw unlocked"
|
||||
}
|
||||
|
||||
get_secrets() {
|
||||
local item
|
||||
item=$(bw get item "NEXTCLOUD" 2>/dev/null)
|
||||
POSTGRES_PASSWORD=$(bw get password "NEXTCLOUD" 2>/dev/null)
|
||||
NEXTCLOUD_TRUSTED_DOMAINS=$(echo "$item" | jq -r '.fields[] | select(.name=="NEXTCLOUD_TRUSTED_DOMAINS") | .value // empty')
|
||||
DBPASSWORD=$(echo "$item" | jq -r '.fields[] | select(.name=="dbpassword") | .value // empty')
|
||||
SECRET=$(echo "$item" | jq -r '.fields[] | select(.name=="secret") | .value // empty')
|
||||
PASSWORDSALT=$(echo "$item" | jq -r '.fields[] | select(.name=="passwordsalt") | .value // empty')
|
||||
INSTANCEID=$(echo "$item" | jq -r '.fields[] | select(.name=="instanceid") | .value // empty')
|
||||
|
||||
if [ -z "$POSTGRES_PASSWORD" ]; then
|
||||
err "NEXTCLOUD: missing password (POSTGRES_PASSWORD)"
|
||||
exit 1
|
||||
fi
|
||||
NEXTCLOUD_TRUSTED_DOMAINS="${NEXTCLOUD_TRUSTED_DOMAINS:-cloud.katykhin.ru 192.168.1.101}"
|
||||
}
|
||||
|
||||
gen_env() {
|
||||
local tmp
|
||||
tmp=$(mktemp)
|
||||
cat > "$tmp" << EOF
|
||||
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
NEXTCLOUD_TRUSTED_DOMAINS=${NEXTCLOUD_TRUSTED_DOMAINS}
|
||||
EOF
|
||||
echo "$tmp"
|
||||
}
|
||||
|
||||
push_env_atomic() {
|
||||
local tmp="$1"
|
||||
< "$tmp" pct exec "$CT_ID" -- bash -c "cat > ${NEXTCLOUD_PATH}/.env.tmp && chmod 600 ${NEXTCLOUD_PATH}/.env.tmp && mv ${NEXTCLOUD_PATH}/.env.tmp ${NEXTCLOUD_PATH}/.env"
|
||||
log ".env written (atomic), chmod 600"
|
||||
}
|
||||
|
||||
push_compose() {
|
||||
local compose_src="${SCRIPT_DIR}/nextcloud/docker-compose.yml"
|
||||
if [ -f "$compose_src" ]; then
|
||||
pct push "$CT_ID" "$compose_src" "${NEXTCLOUD_PATH}/docker-compose.yml"
|
||||
log "docker-compose.yml pushed"
|
||||
fi
|
||||
}
|
||||
|
||||
update_config_occ() {
|
||||
[ -n "$DBPASSWORD" ] && pct exec "$CT_ID" -- docker exec nextcloud-nextcloud-1 php /var/www/html/occ config:system:set dbpassword --value="$DBPASSWORD" 2>/dev/null || true
|
||||
[ -n "$SECRET" ] && pct exec "$CT_ID" -- docker exec nextcloud-nextcloud-1 php /var/www/html/occ config:system:set secret --value="$SECRET" 2>/dev/null || true
|
||||
[ -n "$PASSWORDSALT" ] && pct exec "$CT_ID" -- docker exec nextcloud-nextcloud-1 php /var/www/html/occ config:system:set passwordsalt --value="$PASSWORDSALT" 2>/dev/null || true
|
||||
[ -n "$INSTANCEID" ] && pct exec "$CT_ID" -- docker exec nextcloud-nextcloud-1 php /var/www/html/occ config:system:set instanceid --value="$INSTANCEID" 2>/dev/null || true
|
||||
log "config.php updated via occ"
|
||||
}
|
||||
|
||||
run_compose() {
|
||||
pct exec "$CT_ID" -- bash -c "cd ${NEXTCLOUD_PATH} && docker compose up -d --force-recreate"
|
||||
log "Nextcloud started"
|
||||
}
|
||||
|
||||
main() {
|
||||
log "deploy-nextcloud-credentials start (dry_run=$DRY_RUN)"
|
||||
ensure_bw_unlocked
|
||||
get_secrets
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log "DRY-RUN: would push .env, compose, update config, run compose"
|
||||
log " POSTGRES_PASSWORD=***"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
tmp=$(gen_env)
|
||||
trap "rm -f $tmp" EXIT
|
||||
push_env_atomic "$tmp"
|
||||
push_compose
|
||||
run_compose
|
||||
update_config_occ
|
||||
log "done"
|
||||
}
|
||||
|
||||
main
|
||||
129
scripts/deploy-paperless-credentials.sh
Normal file
129
scripts/deploy-paperless-credentials.sh
Normal file
@@ -0,0 +1,129 @@
|
||||
#!/bin/bash
|
||||
# deploy-paperless-credentials.sh — деплой кредов Paperless в CT 104
|
||||
# Секреты из Vaultwarden (объект PAPERLESS). Атомарная запись docker-compose.env.
|
||||
#
|
||||
# Использование:
|
||||
# /root/scripts/deploy-paperless-credentials.sh
|
||||
# /root/scripts/deploy-paperless-credentials.sh --dry-run
|
||||
#
|
||||
# Ротация: сменил пароль/ключи в Vaultwarden → запустил скрипт → docker compose up -d --force-recreate
|
||||
#
|
||||
# Требования: bw, jq, /root/.bw-master (chmod 600)
|
||||
|
||||
set -e
|
||||
|
||||
CT_ID=104
|
||||
PAPERLESS_PATH="/opt/paperless"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
BW_MASTER_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||
DRY_RUN=false
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--dry-run) DRY_RUN=true ;;
|
||||
esac
|
||||
done
|
||||
|
||||
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}"
|
||||
|
||||
log() { echo "[$(date -Iseconds)] $*"; }
|
||||
err() { echo "[$(date -Iseconds)] ERROR: $*" >&2; }
|
||||
|
||||
ensure_bw_unlocked() {
|
||||
local status
|
||||
status=$(bw status 2>/dev/null | jq -r '.status' 2>/dev/null || echo "unknown")
|
||||
if [ "$status" = "unlocked" ]; then
|
||||
log "bw already unlocked, reusing session"
|
||||
return 0
|
||||
fi
|
||||
if [ ! -f "$BW_MASTER_FILE" ]; then
|
||||
err "Missing $BW_MASTER_FILE"
|
||||
exit 1
|
||||
fi
|
||||
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_FILE" --raw 2>/dev/null) || {
|
||||
err "bw unlock failed"
|
||||
exit 1
|
||||
}
|
||||
log "bw unlocked"
|
||||
}
|
||||
|
||||
get_secrets() {
|
||||
local item
|
||||
item=$(bw get item "PAPERLESS" 2>/dev/null)
|
||||
POSTGRES_PASSWORD=$(bw get password "PAPERLESS" 2>/dev/null)
|
||||
PAPERLESS_URL=$(echo "$item" | jq -r '.fields[] | select(.name=="PAPERLESS_URL") | .value // empty')
|
||||
PAPERLESS_SECRET_KEY=$(echo "$item" | jq -r '.fields[] | select(.name=="PAPERLESS_SECRET_KEY") | .value // empty')
|
||||
PAPERLESS_TIME_ZONE=$(echo "$item" | jq -r '.fields[] | select(.name=="PAPERLESS_TIME_ZONE") | .value // empty')
|
||||
PAPERLESS_OCR_LANGUAGE=$(echo "$item" | jq -r '.fields[] | select(.name=="PAPERLESS_OCR_LANGUAGE") | .value // empty')
|
||||
PAPERLESS_OCR_LANGUAGES=$(echo "$item" | jq -r '.fields[] | select(.name=="PAPERLESS_OCR_LANGUAGES") | .value // empty')
|
||||
|
||||
if [ -z "$POSTGRES_PASSWORD" ]; then
|
||||
err "PAPERLESS: missing password (POSTGRES_PASSWORD)"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$PAPERLESS_SECRET_KEY" ]; then
|
||||
err "PAPERLESS: missing PAPERLESS_SECRET_KEY field"
|
||||
exit 1
|
||||
fi
|
||||
PAPERLESS_URL="${PAPERLESS_URL:-https://docs.katykhin.ru}"
|
||||
PAPERLESS_TIME_ZONE="${PAPERLESS_TIME_ZONE:-Europe/Moscow}"
|
||||
PAPERLESS_OCR_LANGUAGE="${PAPERLESS_OCR_LANGUAGE:-rus+eng}"
|
||||
PAPERLESS_OCR_LANGUAGES="${PAPERLESS_OCR_LANGUAGES:-rus}"
|
||||
}
|
||||
|
||||
gen_env() {
|
||||
local tmp
|
||||
tmp=$(mktemp)
|
||||
cat > "$tmp" << EOF
|
||||
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
PAPERLESS_URL=${PAPERLESS_URL}
|
||||
PAPERLESS_SECRET_KEY=${PAPERLESS_SECRET_KEY}
|
||||
PAPERLESS_TIME_ZONE=${PAPERLESS_TIME_ZONE}
|
||||
PAPERLESS_OCR_LANGUAGE=${PAPERLESS_OCR_LANGUAGE}
|
||||
PAPERLESS_OCR_LANGUAGES=${PAPERLESS_OCR_LANGUAGES}
|
||||
EOF
|
||||
echo "$tmp"
|
||||
}
|
||||
|
||||
push_env_atomic() {
|
||||
local tmp="$1"
|
||||
< "$tmp" pct exec "$CT_ID" -- bash -c "cat > ${PAPERLESS_PATH}/docker-compose.env.tmp && chmod 600 ${PAPERLESS_PATH}/docker-compose.env.tmp && mv ${PAPERLESS_PATH}/docker-compose.env.tmp ${PAPERLESS_PATH}/docker-compose.env"
|
||||
log "docker-compose.env written (atomic), chmod 600"
|
||||
}
|
||||
|
||||
push_compose() {
|
||||
local compose_src="${SCRIPT_DIR}/paperless/docker-compose.yml"
|
||||
if [ -f "$compose_src" ]; then
|
||||
pct push "$CT_ID" "$compose_src" "${PAPERLESS_PATH}/docker-compose.yml"
|
||||
log "docker-compose.yml pushed"
|
||||
else
|
||||
log "WARN: ${compose_src} not found, skipping compose push"
|
||||
fi
|
||||
}
|
||||
|
||||
run_compose() {
|
||||
pct exec "$CT_ID" -- bash -c "cd ${PAPERLESS_PATH} && docker compose up -d --force-recreate"
|
||||
log "Paperless started"
|
||||
}
|
||||
|
||||
main() {
|
||||
log "deploy-paperless-credentials start (dry_run=$DRY_RUN)"
|
||||
ensure_bw_unlocked
|
||||
get_secrets
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log "DRY-RUN: would push docker-compose.env and run compose"
|
||||
log " POSTGRES_PASSWORD=***"
|
||||
log " PAPERLESS_URL=$PAPERLESS_URL"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
tmp=$(gen_env)
|
||||
trap "rm -f $tmp" EXIT
|
||||
push_env_atomic "$tmp"
|
||||
push_compose
|
||||
run_compose
|
||||
log "done"
|
||||
}
|
||||
|
||||
main
|
||||
130
scripts/deploy-rag-credentials.sh
Normal file
130
scripts/deploy-rag-credentials.sh
Normal file
@@ -0,0 +1,130 @@
|
||||
#!/bin/bash
|
||||
# deploy-rag-credentials.sh — деплой кредов RAG-service в CT 105
|
||||
# Секреты из Vaultwarden (объект RAG_SERVICE). Атомарная запись .env.
|
||||
#
|
||||
# Использование:
|
||||
# /root/scripts/deploy-rag-credentials.sh
|
||||
# /root/scripts/deploy-rag-credentials.sh --dry-run
|
||||
#
|
||||
# Ротация: сменил RAG_API_KEY в Vaultwarden → запустил скрипт → docker compose up -d --force-recreate
|
||||
#
|
||||
# Требования: bw, jq, /root/.bw-master (chmod 600)
|
||||
# Vaultwarden: создать запись RAG_SERVICE с полем RAG_API_KEY (тип hidden).
|
||||
|
||||
set -e
|
||||
|
||||
CT_ID=105
|
||||
RAG_PATH="/home/rag-service"
|
||||
BW_MASTER_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||
DRY_RUN=false
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--dry-run) DRY_RUN=true ;;
|
||||
esac
|
||||
done
|
||||
|
||||
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}"
|
||||
|
||||
log() { echo "[$(date -Iseconds)] $*"; }
|
||||
err() { echo "[$(date -Iseconds)] ERROR: $*" >&2; }
|
||||
|
||||
ensure_bw_unlocked() {
|
||||
local status
|
||||
status=$(bw status 2>/dev/null | jq -r '.status' 2>/dev/null || echo "unknown")
|
||||
if [ "$status" = "unlocked" ]; then
|
||||
log "bw already unlocked, reusing session"
|
||||
return 0
|
||||
fi
|
||||
if [ ! -f "$BW_MASTER_FILE" ]; then
|
||||
err "Missing $BW_MASTER_FILE"
|
||||
exit 1
|
||||
fi
|
||||
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_FILE" --raw 2>/dev/null) || {
|
||||
err "bw unlock failed"
|
||||
exit 1
|
||||
}
|
||||
log "bw unlocked"
|
||||
}
|
||||
|
||||
get_secrets() {
|
||||
local item
|
||||
item=$(bw get item "RAG_SERVICE" 2>/dev/null) || {
|
||||
err "RAG_SERVICE not found in Vaultwarden. Create it: type Login, add custom field RAG_API_KEY (hidden)."
|
||||
exit 1
|
||||
}
|
||||
RAG_API_KEY=$(echo "$item" | jq -r '.fields[] | select(.name=="RAG_API_KEY") | .value // empty')
|
||||
if [ -z "$RAG_API_KEY" ]; then
|
||||
err "RAG_SERVICE: missing RAG_API_KEY field"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
gen_env() {
|
||||
local tmp
|
||||
tmp=$(mktemp)
|
||||
cat > "$tmp" << EOF
|
||||
# RAG Service Configuration (generated from Vaultwarden)
|
||||
|
||||
# Модель
|
||||
RAG_MODEL=sentence-transformers/all-MiniLM-L12-v2
|
||||
RAG_CACHE_DIR=data/models
|
||||
|
||||
# VectorStore
|
||||
RAG_VECTORS_PATH=data/vectors/vectors.npz
|
||||
RAG_MAX_EXAMPLES=10000
|
||||
RAG_SCORE_MULTIPLIER=5.0
|
||||
|
||||
# Батч-обработка
|
||||
RAG_BATCH_SIZE=16
|
||||
|
||||
# Минимальная длина текста
|
||||
RAG_MIN_TEXT_LENGTH=3
|
||||
|
||||
# API настройки
|
||||
RAG_API_HOST=0.0.0.0
|
||||
RAG_API_PORT=8000
|
||||
|
||||
# Безопасность
|
||||
RAG_API_KEY=${RAG_API_KEY}
|
||||
RAG_ALLOW_NO_AUTH=false
|
||||
|
||||
# Автосохранение векторов
|
||||
RAG_AUTOSAVE_INTERVAL=600
|
||||
|
||||
# Логирование
|
||||
LOG_LEVEL=INFO
|
||||
EOF
|
||||
echo "$tmp"
|
||||
}
|
||||
|
||||
push_env_atomic() {
|
||||
local tmp="$1"
|
||||
< "$tmp" pct exec "$CT_ID" -- bash -c "cat > ${RAG_PATH}/.env.tmp && chmod 600 ${RAG_PATH}/.env.tmp && mv ${RAG_PATH}/.env.tmp ${RAG_PATH}/.env"
|
||||
log ".env written (atomic), chmod 600"
|
||||
}
|
||||
|
||||
run_compose() {
|
||||
pct exec "$CT_ID" -- bash -c "cd ${RAG_PATH} && docker compose up -d --force-recreate"
|
||||
log "RAG-service started"
|
||||
}
|
||||
|
||||
main() {
|
||||
log "deploy-rag-credentials start (dry_run=$DRY_RUN)"
|
||||
ensure_bw_unlocked
|
||||
get_secrets
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log "DRY-RUN: would push .env and run compose"
|
||||
log " RAG_API_KEY=***"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
tmp=$(gen_env)
|
||||
trap "rm -f $tmp" EXIT
|
||||
push_env_atomic "$tmp"
|
||||
run_compose
|
||||
log "done"
|
||||
}
|
||||
|
||||
main
|
||||
41
scripts/deploy-ssh-keys-homelab.sh
Normal file
41
scripts/deploy-ssh-keys-homelab.sh
Normal file
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
# Deploy SSH public key to all LXC containers and VM 200 in homelab.
|
||||
# Run from machine that can reach Proxmox (192.168.1.150).
|
||||
# Usage: ./deploy-ssh-keys-homelab.sh [path-to-public-key]
|
||||
# Default: ~/.ssh/id_rsa.pub or ~/.ssh/id_ed25519.pub
|
||||
|
||||
set -e
|
||||
PROXMOX="${PROXMOX:-root@192.168.1.150}"
|
||||
KEY_FILE="${1:-$HOME/.ssh/id_rsa.pub}"
|
||||
[ -f "$HOME/.ssh/id_ed25519.pub" ] && [ ! -f "$KEY_FILE" ] && KEY_FILE="$HOME/.ssh/id_ed25519.pub"
|
||||
|
||||
if [ ! -f "$KEY_FILE" ]; then
|
||||
echo "Usage: $0 [path-to-public-key]"
|
||||
echo "No key found at $KEY_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CT_IDS="100 101 103 104 105 107 108 109"
|
||||
|
||||
echo "Deploying key from $KEY_FILE to homelab hosts..."
|
||||
|
||||
# Copy key to Proxmox temp, then deploy from there
|
||||
TMP_KEY="/tmp/deploy-ssh-key-$$.pub"
|
||||
scp -q "$KEY_FILE" "$PROXMOX:$TMP_KEY"
|
||||
trap "ssh $PROXMOX 'rm -f $TMP_KEY'" EXIT
|
||||
|
||||
# Proxmox host
|
||||
echo "Proxmox (192.168.1.150)..."
|
||||
ssh "$PROXMOX" "mkdir -p /root/.ssh && chmod 700 /root/.ssh && grep -qF \"\$(cat $TMP_KEY)\" /root/.ssh/authorized_keys 2>/dev/null || cat $TMP_KEY >> /root/.ssh/authorized_keys && chmod 600 /root/.ssh/authorized_keys"
|
||||
|
||||
# LXC containers
|
||||
for id in $CT_IDS; do
|
||||
echo "CT $id (192.168.1.$id)..."
|
||||
ssh "$PROXMOX" "pct exec $id -- bash -c 'mkdir -p /root/.ssh && chmod 700 /root/.ssh' && pct push $id $TMP_KEY /tmp/key.pub && pct exec $id -- bash -c 'grep -qF \"\$(cat /tmp/key.pub)\" /root/.ssh/authorized_keys 2>/dev/null || cat /tmp/key.pub >> /root/.ssh/authorized_keys && chmod 600 /root/.ssh/authorized_keys && rm /tmp/key.pub'"
|
||||
done
|
||||
|
||||
# VM 200 (admin user; root may be disabled)
|
||||
echo "VM 200 (admin@192.168.1.200)..."
|
||||
ssh "$PROXMOX" "scp -o StrictHostKeyChecking=accept-new $TMP_KEY admin@192.168.1.200:/tmp/key.pub && ssh admin@192.168.1.200 'mkdir -p /home/admin/.ssh /root/.ssh && chmod 700 /home/admin/.ssh /root/.ssh 2>/dev/null; grep -qF \"\$(cat /tmp/key.pub)\" /home/admin/.ssh/authorized_keys 2>/dev/null || cat /tmp/key.pub >> /home/admin/.ssh/authorized_keys; echo \"\$(cat /tmp/key.pub)\" | sudo tee -a /root/.ssh/authorized_keys >/dev/null; chmod 600 /home/admin/.ssh/authorized_keys /root/.ssh/authorized_keys 2>/dev/null; rm /tmp/key.pub'"
|
||||
|
||||
echo "Done. Connect: ssh root@192.168.1.{100,101,103,104,105,107,108,109}, ssh admin@192.168.1.200"
|
||||
111
scripts/deploy-vpn-route-check.sh
Normal file
111
scripts/deploy-vpn-route-check.sh
Normal file
@@ -0,0 +1,111 @@
|
||||
#!/bin/bash
|
||||
# deploy-vpn-route-check.sh — идемпотентный деплой vpn-route-check на CT 100
|
||||
# Секреты берутся из Vaultwarden (объект localhost), .env генерируется на Proxmox и пушится в CT.
|
||||
#
|
||||
# Использование:
|
||||
# /root/scripts/deploy-vpn-route-check.sh # деплой
|
||||
# /root/scripts/deploy-vpn-route-check.sh --dry-run # только проверка, без записи и compose
|
||||
#
|
||||
# Требования: bw, jq, /root/.bw-master (chmod 600)
|
||||
|
||||
set -e
|
||||
|
||||
CT_ID=100
|
||||
CT_PATH="/opt/docker/vpn-route-check"
|
||||
BW_MASTER_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||
DRY_RUN=false
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--dry-run) DRY_RUN=true ;;
|
||||
esac
|
||||
done
|
||||
|
||||
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}"
|
||||
|
||||
log() { echo "[$(date -Iseconds)] $*"; }
|
||||
err() { echo "[$(date -Iseconds)] ERROR: $*" >&2; }
|
||||
|
||||
# --- 1. Разблокировка bw (reuse session если возможно)
|
||||
ensure_bw_unlocked() {
|
||||
local status
|
||||
status=$(bw status 2>/dev/null | jq -r '.status' 2>/dev/null || echo "unknown")
|
||||
if [ "$status" = "unlocked" ]; then
|
||||
log "bw already unlocked, reusing session"
|
||||
return 0
|
||||
fi
|
||||
if [ ! -f "$BW_MASTER_FILE" ]; then
|
||||
err "Missing $BW_MASTER_FILE"
|
||||
exit 1
|
||||
fi
|
||||
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_FILE" --raw 2>/dev/null) || {
|
||||
err "bw unlock failed"
|
||||
exit 1
|
||||
}
|
||||
log "bw unlocked"
|
||||
}
|
||||
|
||||
# --- 2. Получить секреты из Vaultwarden (localhost)
|
||||
get_secrets() {
|
||||
local host user pass
|
||||
host=$(bw get item "localhost" 2>/dev/null | jq -r '.fields[] | select(.name=="ROUTER_TELNET_HOST") | .value // empty')
|
||||
user=$(bw get username "localhost" 2>/dev/null)
|
||||
pass=$(bw get password "localhost" 2>/dev/null)
|
||||
|
||||
if [ -z "$user" ] || [ -z "$pass" ]; then
|
||||
err "localhost: missing username or password in Vaultwarden"
|
||||
exit 1
|
||||
fi
|
||||
host="${host:-192.168.1.1}"
|
||||
ROUTER_TELNET_HOST="$host"
|
||||
ROUTER_TELNET_USER="$user"
|
||||
ROUTER_TELNET_PASSWORD="$pass"
|
||||
}
|
||||
|
||||
# --- 3. Сгенерировать .env во временный файл
|
||||
gen_env() {
|
||||
local tmp
|
||||
tmp=$(mktemp)
|
||||
cat > "$tmp" << EOF
|
||||
ROUTER_TELNET_HOST=${ROUTER_TELNET_HOST}
|
||||
ROUTER_TELNET_USER=${ROUTER_TELNET_USER}
|
||||
ROUTER_TELNET_PASSWORD=${ROUTER_TELNET_PASSWORD}
|
||||
EOF
|
||||
echo "$tmp"
|
||||
}
|
||||
|
||||
# --- 4. Атомарно записать .env в CT 100
|
||||
push_env_to_ct() {
|
||||
local tmp="$1"
|
||||
< "$tmp" pct exec "$CT_ID" -- bash -c "cat > ${CT_PATH}/.env.tmp && chmod 600 ${CT_PATH}/.env.tmp && mv ${CT_PATH}/.env.tmp ${CT_PATH}/.env"
|
||||
log ".env written to CT $CT_ID (atomic)"
|
||||
}
|
||||
|
||||
# --- 5. docker compose up -d
|
||||
run_compose() {
|
||||
pct exec "$CT_ID" -- bash -c "cd ${CT_PATH} && docker compose up -d --force-recreate"
|
||||
log "vpn-route-check started"
|
||||
}
|
||||
|
||||
# --- main
|
||||
main() {
|
||||
log "deploy-vpn-route-check start (dry_run=$DRY_RUN)"
|
||||
ensure_bw_unlocked
|
||||
get_secrets
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log "DRY-RUN: would push .env and run compose"
|
||||
log " ROUTER_TELNET_HOST=$ROUTER_TELNET_HOST"
|
||||
log " ROUTER_TELNET_USER=$ROUTER_TELNET_USER"
|
||||
log " ROUTER_TELNET_PASSWORD=***"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
tmp=$(gen_env)
|
||||
trap "rm -f $tmp" EXIT
|
||||
push_env_to_ct "$tmp"
|
||||
run_compose
|
||||
log "done"
|
||||
}
|
||||
|
||||
main
|
||||
95
scripts/deploy-wireguard-credentials.sh
Normal file
95
scripts/deploy-wireguard-credentials.sh
Normal file
@@ -0,0 +1,95 @@
|
||||
#!/bin/bash
|
||||
# deploy-wireguard-credentials.sh — деплой конфига WireGuard в CT 109
|
||||
# Секреты из Vaultwarden (объект LOCAL_VPN_SERVER_WG, поле wg0_conf — полный конфиг).
|
||||
#
|
||||
# Использование:
|
||||
# /root/scripts/deploy-wireguard-credentials.sh
|
||||
# /root/scripts/deploy-wireguard-credentials.sh --dry-run
|
||||
#
|
||||
# Перед первым запуском: создать в Vaultwarden запись LOCAL_VPN_SERVER_WG,
|
||||
# добавить кастомное поле wg0_conf (hidden) с полным содержимым /etc/wireguard/wg0.conf.
|
||||
#
|
||||
# Ротация: сменил ключи в Vaultwarden → запустил скрипт → systemctl restart wg-quick@wg0
|
||||
#
|
||||
# Требования: bw, jq, /root/.bw-master (chmod 600)
|
||||
|
||||
set -e
|
||||
|
||||
CT_ID=109
|
||||
WG_CONF_PATH="/etc/wireguard/wg0.conf"
|
||||
BW_MASTER_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||
DRY_RUN=false
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--dry-run) DRY_RUN=true ;;
|
||||
esac
|
||||
done
|
||||
|
||||
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}"
|
||||
|
||||
log() { echo "[$(date -Iseconds)] $*"; }
|
||||
err() { echo "[$(date -Iseconds)] ERROR: $*" >&2; }
|
||||
|
||||
ensure_bw_unlocked() {
|
||||
local status
|
||||
status=$(bw status 2>/dev/null | jq -r '.status' 2>/dev/null || echo "unknown")
|
||||
if [ "$status" = "unlocked" ]; then
|
||||
log "bw already unlocked, reusing session"
|
||||
return 0
|
||||
fi
|
||||
if [ ! -f "$BW_MASTER_FILE" ]; then
|
||||
err "Missing $BW_MASTER_FILE"
|
||||
exit 1
|
||||
fi
|
||||
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_FILE" --raw 2>/dev/null) || {
|
||||
err "bw unlock failed"
|
||||
exit 1
|
||||
}
|
||||
log "bw unlocked"
|
||||
}
|
||||
|
||||
get_secrets() {
|
||||
WG_CONF=$(bw get item "LOCAL_VPN_SERVER_WG" 2>/dev/null | jq -r '.fields[] | select(.name=="wg0_conf") | .value // empty')
|
||||
if [ -z "$WG_CONF" ]; then
|
||||
err "LOCAL_VPN_SERVER_WG not found or missing wg0_conf field. Create it in Vaultwarden, add field wg0_conf with full wg0.conf content."
|
||||
exit 1
|
||||
fi
|
||||
if ! echo "$WG_CONF" | grep -q '\[Interface\]'; then
|
||||
err "wg0_conf: invalid format (expected [Interface] section)"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
push_conf() {
|
||||
local tmp
|
||||
tmp=$(mktemp)
|
||||
echo "$WG_CONF" > "$tmp"
|
||||
pct push "$CT_ID" "$tmp" "${WG_CONF_PATH}.tmp"
|
||||
rm -f "$tmp"
|
||||
pct exec "$CT_ID" -- bash -c "chmod 600 ${WG_CONF_PATH}.tmp && mv ${WG_CONF_PATH}.tmp ${WG_CONF_PATH}"
|
||||
log "wg0.conf written (atomic), chmod 600"
|
||||
}
|
||||
|
||||
restart_wg() {
|
||||
pct exec "$CT_ID" -- systemctl restart wg-quick@wg0
|
||||
log "wg-quick@wg0 restarted"
|
||||
}
|
||||
|
||||
main() {
|
||||
log "deploy-wireguard-credentials start (dry_run=$DRY_RUN)"
|
||||
ensure_bw_unlocked
|
||||
get_secrets
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log "DRY-RUN: would push wg0.conf and restart WireGuard"
|
||||
log " wg0_conf: $(echo "$WG_CONF" | head -3)..."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
push_conf
|
||||
restart_wg
|
||||
log "done"
|
||||
}
|
||||
|
||||
main
|
||||
74
scripts/gitea/docker-compose.yml
Normal file
74
scripts/gitea/docker-compose.yml
Normal file
@@ -0,0 +1,74 @@
|
||||
# Шаблон для /opt/gitea/ на CT 103
|
||||
# Секреты в .env (генерируется deploy-gitea-credentials.sh из Vaultwarden).
|
||||
# .env не коммитить.
|
||||
|
||||
services:
|
||||
db:
|
||||
image: docker.io/library/postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
environment:
|
||||
POSTGRES_USER: gitea
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: gitea
|
||||
volumes:
|
||||
- gitea-postgres:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U gitea"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
server:
|
||||
image: docker.gitea.com/gitea:1.25
|
||||
container_name: gitea
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
env_file: .env
|
||||
environment:
|
||||
USER_UID: 1000
|
||||
USER_GID: 1000
|
||||
GITEA__database__DB_TYPE: postgres
|
||||
GITEA__database__HOST: db:5432
|
||||
GITEA__database__NAME: gitea
|
||||
GITEA__database__USER: gitea
|
||||
GITEA__database__PASSWD: ${POSTGRES_PASSWORD}
|
||||
GITEA__server__DOMAIN: 192.168.1.103
|
||||
GITEA__server__ROOT_URL: http://192.168.1.103:3000/
|
||||
GITEA__server__SSH_PORT: 2222
|
||||
volumes:
|
||||
- gitea-data:/data
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "2222:22"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
runner:
|
||||
image: docker.io/gitea/act_runner:latest
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
server:
|
||||
condition: service_healthy
|
||||
env_file: .env
|
||||
environment:
|
||||
GITEA_INSTANCE_URL: http://server:3000
|
||||
GITEA_RUNNER_REGISTRATION_TOKEN: ${GITEA_RUNNER_REGISTRATION_TOKEN}
|
||||
GITEA_RUNNER_NAME: gitea-103-runner
|
||||
GITEA_RUNNER_LABELS: docker:docker://alpine:latest
|
||||
volumes:
|
||||
- runner-data:/data
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
volumes:
|
||||
gitea-data:
|
||||
gitea-postgres:
|
||||
runner-data:
|
||||
84
scripts/invidious/docker-compose.yml
Normal file
84
scripts/invidious/docker-compose.yml
Normal file
@@ -0,0 +1,84 @@
|
||||
# Шаблон для /opt/invidious/docker-compose.yml на CT 107
|
||||
# Секреты в .env (генерируется deploy-invidious-credentials.sh из Vaultwarden).
|
||||
# .env не коммитить.
|
||||
|
||||
services:
|
||||
invidious:
|
||||
image: quay.io/invidious/invidious:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3000:3000"
|
||||
env_file: .env
|
||||
environment:
|
||||
INVIDIOUS_CONFIG: |
|
||||
db:
|
||||
dbname: invidious
|
||||
user: ${POSTGRES_USER}
|
||||
password: ${POSTGRES_PASSWORD}
|
||||
host: invidious-db
|
||||
port: 5432
|
||||
check_tables: true
|
||||
invidious_companion:
|
||||
- private_url: "http://companion:8282/companion"
|
||||
invidious_companion_key: "${INVIDIOUS_COMPANION_KEY}"
|
||||
external_port: 443
|
||||
domain: "video.katykhin.ru"
|
||||
https_only: true
|
||||
use_pubsub_feeds: true
|
||||
use_innertube_for_captions: true
|
||||
hmac_key: "${HMAC_KEY}"
|
||||
default_user_preferences:
|
||||
default_home: Popular
|
||||
dark_mode: "light"
|
||||
player_style: "youtube"
|
||||
vr_mode: false
|
||||
automatic_instance_redirect: false
|
||||
healthcheck:
|
||||
test: wget -nv --tries=1 --spider http://127.0.0.1:3000/api/v1/stats || exit 1
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 2
|
||||
logging:
|
||||
options:
|
||||
max-size: "1G"
|
||||
max-file: "4"
|
||||
depends_on:
|
||||
invidious-db:
|
||||
condition: service_healthy
|
||||
|
||||
companion:
|
||||
image: quay.io/invidious/invidious-companion:latest
|
||||
env_file: .env
|
||||
environment:
|
||||
SERVER_SECRET_KEY: ${INVIDIOUS_COMPANION_KEY}
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
options:
|
||||
max-size: "1G"
|
||||
max-file: "4"
|
||||
cap_drop:
|
||||
- ALL
|
||||
read_only: true
|
||||
volumes:
|
||||
- companioncache:/var/tmp/youtubei.js:rw
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
invidious-db:
|
||||
image: docker.io/library/postgres:14
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- postgresdata:/var/lib/postgresql/data
|
||||
- ./config/sql:/config/sql
|
||||
- ./docker/init-invidious-db.sh:/docker-entrypoint-initdb.d/init-invidious-db.sh
|
||||
env_file: .env
|
||||
environment:
|
||||
POSTGRES_DB: invidious
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
||||
|
||||
volumes:
|
||||
postgresdata:
|
||||
companioncache:
|
||||
52
scripts/nextcloud/docker-compose.yml
Normal file
52
scripts/nextcloud/docker-compose.yml
Normal file
@@ -0,0 +1,52 @@
|
||||
# Шаблон для /opt/nextcloud/ на CT 101
|
||||
# Секреты в .env (генерируется deploy-nextcloud-credentials.sh из Vaultwarden).
|
||||
# .env не коммитить.
|
||||
|
||||
services:
|
||||
db:
|
||||
image: docker.io/library/postgres:16
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /mnt/nextcloud-data/pgdata:/var/lib/postgresql/data
|
||||
env_file: .env
|
||||
environment:
|
||||
POSTGRES_DB: nextcloud
|
||||
POSTGRES_USER: nextcloud
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U nextcloud"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: docker.io/library/redis:7-alpine
|
||||
restart: unless-stopped
|
||||
command: redis-server --appendonly yes
|
||||
|
||||
nextcloud:
|
||||
image: docker.io/nextcloud:latest
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
ports:
|
||||
- "8080:80"
|
||||
volumes:
|
||||
- /mnt/nextcloud-data/html:/var/www/html
|
||||
- /mnt/nextcloud-extra:/mnt/nextcloud-extra
|
||||
- /opt/nextcloud/php-uploads.ini:/usr/local/etc/php/conf.d/zz-uploads.ini:ro
|
||||
env_file: .env
|
||||
environment:
|
||||
APACHE_BODY_LIMIT: "0"
|
||||
NEXTCLOUD_TRUSTED_DOMAINS: ${NEXTCLOUD_TRUSTED_DOMAINS}
|
||||
OVERWRITEPROTOCOL: https
|
||||
OVERWRITEHOST: cloud.katykhin.ru
|
||||
OVERWRITECLIURL: https://cloud.katykhin.ru
|
||||
REDIS_HOST: redis
|
||||
POSTGRES_HOST: db
|
||||
POSTGRES_DB: nextcloud
|
||||
POSTGRES_USER: nextcloud
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
@@ -1,15 +1,17 @@
|
||||
#!/bin/bash
|
||||
# Add vault.katykhin.ru → 192.168.1.103:8280 via NPM API + Access List (LAN + VPN only)
|
||||
# Usage: NPM_EMAIL=j3tears100@gmail.com NPM_PASSWORD=xxx ./npm-add-proxy-vault.sh
|
||||
# Usage: NPM_EMAIL=... NPM_PASSWORD=... ./npm-add-proxy-vault.sh
|
||||
# NPM credentials: Vaultwarden, объект NPM_ADMIN (username=email, password)
|
||||
# Run from host that can reach NPM, or: ssh root@192.168.1.150 "pct exec 100 -- bash -s" < scripts/npm-add-proxy-vault.sh
|
||||
# (then set NPM_URL=http://127.0.0.1:81 and NPM_EMAIL/NPM_PASSWORD in env or below)
|
||||
# NPM credentials: see docs/containers/container-100.md
|
||||
# (then set NPM_URL=http://127.0.0.1:81 and NPM_EMAIL/NPM_PASSWORD in env)
|
||||
|
||||
set -e
|
||||
NPM_URL="${NPM_URL:-http://192.168.1.100:81}"
|
||||
API="$NPM_URL/api"
|
||||
NPM_EMAIL="${NPM_EMAIL:-j3tears100@gmail.com}"
|
||||
NPM_PASSWORD="${NPM_PASSWORD:-kqEUubVq02DJTS8}"
|
||||
if [ -z "$NPM_EMAIL" ] || [ -z "$NPM_PASSWORD" ]; then
|
||||
echo "Set NPM_EMAIL and NPM_PASSWORD (from Vaultwarden, объект NPM_ADMIN)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "1. Getting token..."
|
||||
TOKEN=$(curl -s -X POST "$API/tokens" \
|
||||
|
||||
41
scripts/paperless/docker-compose.yml
Normal file
41
scripts/paperless/docker-compose.yml
Normal file
@@ -0,0 +1,41 @@
|
||||
# Шаблон для /opt/paperless/ на CT 104
|
||||
# Секреты в docker-compose.env (генерируется deploy-paperless-credentials.sh из Vaultwarden).
|
||||
# docker-compose.env не коммитить.
|
||||
|
||||
services:
|
||||
broker:
|
||||
image: docker.io/library/redis:8
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- redisdata:/data
|
||||
|
||||
db:
|
||||
image: docker.io/library/postgres:18
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /mnt/paperless-data/pgdata:/var/lib/postgresql
|
||||
env_file: docker-compose.env
|
||||
environment:
|
||||
POSTGRES_DB: paperless
|
||||
POSTGRES_USER: paperless
|
||||
|
||||
webserver:
|
||||
image: ghcr.io/paperless-ngx/paperless-ngx:latest
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- db
|
||||
- broker
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- /mnt/paperless-data/data:/usr/src/paperless/data
|
||||
- /mnt/paperless-data/media:/usr/src/paperless/media
|
||||
- ./export:/usr/src/paperless/export
|
||||
- ./consume:/usr/src/paperless/consume
|
||||
env_file: docker-compose.env
|
||||
environment:
|
||||
PAPERLESS_REDIS: redis://broker:6379
|
||||
PAPERLESS_DBHOST: db
|
||||
|
||||
volumes:
|
||||
redisdata:
|
||||
@@ -30,7 +30,7 @@ fi
|
||||
# Секреты из Vaultwarden (объект RESTIC)
|
||||
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||
if [ ! -f "$BW_MASTER_PASSWORD_FILE" ]; then
|
||||
echo "Нет файла с мастер-паролем Vaultwarden: $BW_MASTER_PASSWORD_FILE (chmod 600). См. docs/backup/proxmox-phase1-backup.md"
|
||||
echo "Нет файла с мастер-паролем Vaultwarden: $BW_MASTER_PASSWORD_FILE (chmod 600). См. docs/vaultwarden-secrets.md"
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v bw >/dev/null 2>&1 || ! command -v jq >/dev/null 2>&1; then
|
||||
|
||||
16
scripts/vpn-route-check/docker-compose.yml
Normal file
16
scripts/vpn-route-check/docker-compose.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
# Шаблон для /opt/docker/vpn-route-check/docker-compose.yml на CT 100
|
||||
# Секреты в .env (генерируется deploy-vpn-route-check.sh из Vaultwarden).
|
||||
# .env не коммитить.
|
||||
|
||||
services:
|
||||
vpn-route-check:
|
||||
build: .
|
||||
container_name: vpn-route-check
|
||||
network_mode: host
|
||||
env_file: .env
|
||||
volumes:
|
||||
- vpn-route-check-data:/data
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
vpn-route-check-data:
|
||||
Reference in New Issue
Block a user