proxmox:prune-individual
Diferenças
Aqui você vê as diferenças entre duas revisões dessa página.
| Ambos lados da revisão anteriorRevisão anteriorPróxima revisão | Revisão anterior | ||
| proxmox:prune-individual [2026/09/29 13:13] – admin | proxmox:prune-individual [2026/09/29 13:22] (atual) – admin | ||
|---|---|---|---|
| Linha 1: | Linha 1: | ||
| - | ====== PBS Prune Individual ====== | + | ====== PBS — Prune Individual |
| - | ===== Visão geral ===== | + | Script para configurar políticas de retenção individuais por VM ou CT no Proxmox Backup Server. |
| - | O PBS Prune Individual é um assistente em Bash para configurar retenção de backups por VM/CT no Proxmox Backup Server. | + | Exemplo: |
| - | + | ||
| - | O PBS trabalha com prune por datastore/ | + | |
| - | + | ||
| - | ===== O que o PBS Prune faz ===== | + | |
| - | + | ||
| - | Coleta e valida os dados de conexão com o PBS. | + | |
| - | + | ||
| - | Pode criar automaticamente o API Token e aplicar a permissão necessária quando executado no próprio PBS, utilizando um usuário existente. | + | |
| - | + | ||
| - | Lista os grupos de backup disponíveis. | + | |
| - | + | ||
| - | Permite configurar retenção individual para VM/CT. | + | |
| - | + | ||
| - | Executa dry-run antes do prune real. | + | |
| - | + | ||
| - | Instala o executor, configuração, | + | |
| - | + | ||
| - | Mantém logs das execuções. | + | |
| - | + | ||
| - | Utiliza lock para evitar duas execuções simultâneas. | + | |
| - | + | ||
| - | ===== Fluxo de funcionamento ===== | + | |
| < | < | ||
| - | Pré-requisitos | + | vm/ |
| - | | | + | vm/ |
| - | v | + | ct/200 30 |
| - | Conexão com PBS | + | |
| - | | | + | |
| - | v | + | |
| - | VMs / CTs + keep-last | + | |
| - | | | + | |
| - | v | + | |
| - | Dry-run | + | |
| - | | | + | |
| - | v | + | |
| - | Agendamento | + | |
| - | | | + | |
| - | v | + | |
| - | Revisão | + | |
| - | | | + | |
| - | v | + | |
| - | Instalação | + | |
| - | | | + | |
| - | v | + | |
| - | Prune automático | + | |
| </ | </ | ||
| + | |||
| + | Cada grupo pode possuir seu próprio '' | ||
| ===== Pré-requisitos ===== | ===== Pré-requisitos ===== | ||
| - | Antes de executar | + | Antes de executar: |
| - | + | ||
| - | O ambiente deve possuir: | + | |
| - | + | ||
| - | acesso ao Proxmox Backup Server; | + | |
| - | + | ||
| - | um usuário existente no PBS que será utilizado como proprietário do API Token; | + | |
| - | + | ||
| - | um API Token criado nesse usuário; | + | |
| - | + | ||
| - | permissão do API Token no datastore que será utilizado pelo prune; | + | |
| - | + | ||
| - | < | + | |
| - | + | ||
| - | < | + | |
| - | + | ||
| - | < | + | |
| - | + | ||
| - | < | + | |
| - | + | ||
| - | < | + | |
| - | + | ||
| - | ==== Usuário da API ==== | + | |
| - | O API Token sempre pertence | + | * Proxmox Backup Server acessível; |
| + | * '' | ||
| + | * acesso '' | ||
| + | * um usuário existente no PBS; | ||
| + | * API Token associado | ||
| + | * permissão | ||
| - | Antes de configurar o PBS Prune Individual, defina qual usuário será utilizado para criar o API Token. | + | O API Token sempre pertence a um usuário existente. |
| Exemplos: | Exemplos: | ||
| - | |||
| - | < | ||
| - | root@pam | ||
| - | </ | ||
| - | |||
| - | ou: | ||
| - | |||
| - | < | ||
| - | backup@pbs | ||
| - | </ | ||
| - | |||
| - | O usuário pode ser um usuário administrativo existente, como < | ||
| - | |||
| - | Importante: primeiro deve existir o usuário. O API Token será criado dentro desse usuário. | ||
| - | |||
| - | ==== API Token ==== | ||
| - | |||
| - | Depois de definir o usuário, crie um API Token vinculado a ele. | ||
| - | |||
| - | O formato do Auth-ID é: | ||
| - | |||
| - | < | ||
| - | USUARIO@REALM!NOME_DO_TOKEN | ||
| - | </ | ||
| - | |||
| - | Exemplo utilizando < | ||
| < | < | ||
| root@pam!prune | root@pam!prune | ||
| - | </ | ||
| - | |||
| - | Exemplo utilizando um usuário dedicado: | ||
| - | |||
| - | < | ||
| backup@pbs!prune | backup@pbs!prune | ||
| </ | </ | ||
| - | Nesse exemplo: | + | A permissão |
| - | + | ||
| - | < | + | |
| - | + | ||
| - | < | + | |
| - | + | ||
| - | O secret apresentado pelo PBS durante a criação do token será utilizado pelo script para autenticação. | + | |
| - | + | ||
| - | Nunca publique o secret do API Token em documentação, | + | |
| - | + | ||
| - | ==== Permissão no Datastore ==== | + | |
| - | + | ||
| - | Depois de criar o API Token, identifique o datastore onde estão os backups que serão processados. | + | |
| - | + | ||
| - | Por exemplo: | + | |
| - | + | ||
| - | < | + | |
| - | backup | + | |
| - | </ | + | |
| - | + | ||
| - | O caminho da ACL será: | + | |
| - | + | ||
| - | < | + | |
| - | / | + | |
| - | </ | + | |
| - | + | ||
| - | Para o PBS Prune Individual, o token deve possuir a permissão necessária | + | |
| - | + | ||
| - | Exemplo: | + | |
| <code bash> | <code bash> | ||
| Linha 154: | Linha 39: | ||
| </ | </ | ||
| - | Nesse exemplo: | + | Substitua '' |
| - | < | + | ===== Download ===== |
| - | < | + | Baixe o script diretamente: |
| - | < | + | [[https://cursoproxmox.com.br/prune/ |
| - | < | + | ===== Instalação ===== |
| - | Importante: a permissão deve ser aplicada ao datastore onde estão os backups que serão processados pelo script. | + | No PBS: |
| - | + | ||
| - | ==== Resumo do pré-requisito ==== | + | |
| - | + | ||
| - | O fluxo de autenticação é: | + | |
| - | + | ||
| - | < | + | |
| - | Usuário existente no PBS | + | |
| - | | | + | |
| - | v | + | |
| - | API Token criado nesse usuário | + | |
| - | | | + | |
| - | v | + | |
| - | Permissão no datastore | + | |
| - | | | + | |
| - | v | + | |
| - | PBS Prune Individual | + | |
| - | </ | + | |
| - | + | ||
| - | ===== Requisitos técnicos ===== | + | |
| - | + | ||
| - | O script realiza uma pré-checagem antes da configuração. | + | |
| - | + | ||
| - | São verificados: | + | |
| - | + | ||
| - | execução como root; | + | |
| - | + | ||
| - | proxmox-backup-client; | + | |
| - | + | ||
| - | openssl; | + | |
| - | + | ||
| - | timeout; | + | |
| - | + | ||
| - | flock; | + | |
| - | + | ||
| - | cron. | + | |
| - | + | ||
| - | Quando executado diretamente no próprio PBS, o script também detecta a presença do < | + | |
| - | + | ||
| - | ===== Como usar ===== | + | |
| - | + | ||
| - | ==== Executar o assistente ==== | + | |
| <code bash> | <code bash> | ||
| - | ./ | + | chmod +x / |
| + | /root/ | ||
| </ | </ | ||
| - | O assistente | + | O assistente |
| - | conexão com o Proxmox Backup Server; | + | * endereço do PBS; |
| + | * datastore; | ||
| + | * namespace, se utilizado; | ||
| + | * API Token; | ||
| + | * VMs/CTs; | ||
| + | * quantidade de backups a manter; | ||
| + | * agendamento. | ||
| - | seleção das VMs/CTs; | + | Antes da instalação é possível realizar um '' |
| - | definição do keep-last; | + | ===== Estrutura criada ===== |
| - | dry-run; | + | Após a instalação: |
| - | agendamento; | + | < |
| + | / | ||
| + | └── pbs-prune | ||
| - | revisão; | + | / |
| + | ├── pbs-prune.conf | ||
| + | ├── groups.conf | ||
| + | └── secret | ||
| - | instalação. | + | /etc/cron.d/ |
| + | └── pbs-prune | ||
| - | ==== Ver ajuda ==== | + | / |
| + | └── pbs-prune | ||
| - | <code bash> | + | /var/log/ |
| - | ./pbs-prune-setup.sh --help | + | └── |
| </ | </ | ||
| - | ==== Desinstalar ==== | + | Arquivos principais: |
| - | <code bash> | + | ^ Arquivo ^ Função ^ |
| - | ./pbs-prune-setup.sh --uninstall | + | | ''/ |
| - | </code> | + | | ''/ |
| + | | ''/ | ||
| + | | ''/ | ||
| + | | '' | ||
| + | | ''/ | ||
| - | A desinstalação remove o executor, configuração, | + | ===== Configuração das VMs/CTs ===== |
| - | O token existente no PBS não é removido automaticamente. | + | Arquivo: |
| - | + | ||
| - | ===== Configuração de retenção ===== | + | |
| - | + | ||
| - | O arquivo: | + | |
| < | < | ||
| / | / | ||
| </ | </ | ||
| - | |||
| - | possui uma linha para cada grupo configurado. | ||
| Exemplo: | Exemplo: | ||
| Linha 257: | Linha 113: | ||
| < | < | ||
| vm/100 7 | vm/100 7 | ||
| - | ct/ | + | vm/101 15 |
| + | ct/ | ||
| </ | </ | ||
| - | Neste exemplo: | + | Formato: |
| - | + | ||
| - | < | + | |
| - | + | ||
| - | < | + | |
| - | + | ||
| - | Também é possível informar somente o ID durante o assistente: | + | |
| < | < | ||
| - | 100 | + | GRUPO KEEP-LAST |
| </ | </ | ||
| - | O script identifica se o grupo é VM ou CT. Caso existam os dois com o mesmo ID, o usuário escolhe qual deseja configurar. | + | ===== Comandos ===== |
| - | ===== Dry-run ===== | + | Simular: |
| - | + | ||
| - | Antes da execução real, o assistente pode executar uma simulação. | + | |
| - | + | ||
| - | O objetivo é permitir a conferência da retenção antes de remover snapshots. | + | |
| - | + | ||
| - | Depois da instalação: | + | |
| <code bash> | <code bash> | ||
| Linha 286: | Linha 131: | ||
| </ | </ | ||
| - | ===== Execução ===== | + | Executar: |
| - | + | ||
| - | Para executar o prune configurado: | + | |
| <code bash> | <code bash> | ||
| Linha 294: | Linha 137: | ||
| </ | </ | ||
| - | Para visualizar os grupos | + | Listar |
| <code bash> | <code bash> | ||
| Linha 300: | Linha 143: | ||
| </ | </ | ||
| - | Para acompanhar o log: | + | Acompanhar |
| <code bash> | <code bash> | ||
| tail -f / | tail -f / | ||
| </ | </ | ||
| - | |||
| - | ===== Agendamento ===== | ||
| - | |||
| - | O assistente permite: | ||
| - | |||
| - | diário; | ||
| - | |||
| - | semanal; | ||
| - | |||
| - | a cada N horas; | ||
| - | |||
| - | expressão cron personalizada; | ||
| - | |||
| - | execução somente manual. | ||
| - | |||
| - | A recomendação incorporada ao assistente é executar o prune: | ||
| - | |||
| - | depois da janela de backup; | ||
| - | |||
| - | antes do Garbage Collection do datastore. | ||
| - | |||
| - | ===== Arquivos instalados ===== | ||
| - | |||
| - | ^ Arquivo ^ Função ^ | ||
| - | | < | ||
| - | | < | ||
| - | | < | ||
| - | | < | ||
| - | | < | ||
| - | | < | ||
| - | | < | ||
| - | |||
| - | ===== Estrutura lógica ===== | ||
| - | |||
| - | < | ||
| - | pbs-prune-setup.sh | ||
| - | | | ||
| - | +-- coleta e validação | ||
| - | | | ||
| - | +-- conexão PBS | ||
| - | | | ||
| - | +-- configuração dos grupos | ||
| - | | | ||
| - | +-- dry-run | ||
| - | | | ||
| - | +-- agendamento | ||
| - | | | ||
| - | +-- instalação | ||
| - | | | ||
| - | +-- pbs-prune | ||
| - | +-- pbs-prune.conf | ||
| - | +-- groups.conf | ||
| - | +-- secret | ||
| - | +-- cron | ||
| - | +-- logrotate | ||
| - | +-- log | ||
| - | </ | ||
| - | |||
| - | ===== Segurança ===== | ||
| - | |||
| - | O secret do token é armazenado em arquivo separado do código. | ||
| - | |||
| - | O arquivo é criado com permissão restrita. | ||
| - | |||
| - | O executor também utiliza: | ||
| - | |||
| - | < | ||
| - | |||
| - | < | ||
| - | |||
| - | log das execuções; | ||
| - | |||
| - | fingerprint SHA-256 do certificado do PBS. | ||
| - | |||
| - | Nunca publique tokens, secrets ou credenciais reais no código-fonte. | ||
| ===== Garbage Collection ===== | ===== Garbage Collection ===== | ||
| - | O prune define quais snapshots | + | O prune remove as referências dos snapshots. |
| - | O espaço | + | O espaço |
| - | ===== Licença ===== | + | Por isso, recomenda-se executar na ordem: |
| - | + | ||
| - | O script informa em seu cabeçalho: | + | |
| < | < | ||
| - | Licença : MIT | + | Backup → Prune Individual → Garbage Collection |
| </ | </ | ||
| - | ===== Script completo | + | ===== Desinstalação |
| - | + | ||
| - | O conteúdo abaixo é o script completo < | + | |
| <code bash> | <code bash> | ||
| - | #!/usr/bin/env bash | + | /root/ |
| - | # ============================================================================== | + | |
| - | # pbs-prune-setup — Assistente de prune individual por VM/CT no Proxmox Backup Server | + | |
| - | # ------------------------------------------------------------------------------ | + | |
| - | # Autor : Caio Monteiro | + | |
| - | # Site : www.projetoempresalivre.com.br | + | |
| - | # Versão | + | |
| - | # Licença : MIT | + | |
| - | # | + | |
| - | # O PBS só faz prune por datastore/ | + | |
| - | # 1. coleta e valida os dados de conexão (host, datastore, token, fingerprint) | + | |
| - | # 2. cria o token de API e a permissão automaticamente (quando roda no próprio PBS) | + | |
| - | # 3. lista os grupos existentes e deixa você escolher VM/CT + quantos manter (keep-last) | + | |
| - | # 4. simula (dry-run) antes de aplicar | + | |
| - | # 5. instala o executor, a config, o agendamento (cron) e o logrotate | + | |
| - | # | + | |
| - | # Uso: | + | |
| - | # pbs-prune-setup.sh | + | |
| - | # | + | |
| - | # pbs-prune-setup.sh --help | + | |
| - | # | + | |
| - | # Arquivos instalados: | + | |
| - | # / | + | |
| - | # / | + | |
| - | # / | + | |
| - | # / | + | |
| - | # / | + | |
| - | # / | + | |
| - | # / | + | |
| - | # ============================================================================== | + | |
| - | + | ||
| - | set -uo pipefail | + | |
| - | + | ||
| - | readonly VERSION=" | + | |
| - | readonly APP=" | + | |
| - | readonly PREFIX=" | + | |
| - | readonly CONF_DIR=" | + | |
| - | readonly CONF_FILE=" | + | |
| - | readonly GROUPS_FILE=" | + | |
| - | readonly SECRET_FILE=" | + | |
| - | readonly RUNNER=" | + | |
| - | readonly CRON_FILE=" | + | |
| - | readonly LOGROTATE_FILE=" | + | |
| - | readonly LOG_FILE=" | + | |
| - | readonly LOCK_FILE=" | + | |
| - | + | ||
| - | ------------------------------------------------------------------ estado | + | |
| - | + | ||
| - | PBS_HOST="" | + | |
| - | SECRET="" | + | |
| - | G_NAMES=() G_KEEP=() AVAILABLE=() | + | |
| - | IS_LOCAL_PBS=0 | + | |
| - | + | ||
| - | ------------------------------------------------------------------ interface | + | |
| - | + | ||
| - | if [[ -t 1 ]]; then | + | |
| - | C_0=$' | + | |
| - | else | + | |
| - | C_0="" | + | |
| - | fi | + | |
| - | + | ||
| - | hr() { printf ' | + | |
| - | info() { printf ' | + | |
| - | ok() { printf ' | + | |
| - | warn() { printf ' | + | |
| - | fail() { printf ' | + | |
| - | die() { fail " | + | |
| - | tip() { printf ' | + | |
| - | step() { printf ' | + | |
| - | + | ||
| - | banner() { | + | |
| - | printf ' | + | |
| - | cat <<' | + | |
| - | + | ||
| - | ╔════════════════════════════════════════════════════════╗ | + | |
| - | ║ PBS Prune Individual — Assistente de configuração | + | |
| - | ║ | + | |
| - | ╚════════════════════════════════════════════════════════╝ | + | |
| - | EOF | + | |
| - | printf ' | + | |
| - | } | + | |
| - | + | ||
| - | on_interrupt() { stty echo 2>/ | + | |
| - | trap on_interrupt INT | + | |
| - | + | ||
| - | ask VAR " | + | |
| - | + | ||
| - | ask() { | + | |
| - | local __var=$1 __prompt=$2 __def=${3-} __val=${4: | + | |
| - | while :; do | + | |
| - | read -rp " | + | |
| - | __ans=${__ans: | + | |
| - | if " | + | |
| - | done | + | |
| - | } | + | |
| - | + | ||
| - | ask_secret() { | + | |
| - | local __var=$1 __prompt=$2 __ans | + | |
| - | while :; do | + | |
| - | read -rsp " | + | |
| - | [[ -n $__ans ]] && { printf -v " | + | |
| - | warn "Campo obrigatório." | + | |
| - | done | + | |
| - | } | + | |
| - | + | ||
| - | confirm " | + | |
| - | + | ||
| - | confirm() { | + | |
| - | local def=${2:-N} ans hint | + | |
| - | [[ $def == S ]] && hint=" | + | |
| - | read -rp " | + | |
| - | ans=${ans: | + | |
| - | [[ ${ans,,} =~ ^(s|sim|y|yes)$ ]] | + | |
| - | } | + | |
| - | + | ||
| - | choose VAR " | + | |
| - | + | ||
| - | choose() { | + | |
| - | local __var=$1 __title=$2; shift 2 | + | |
| - | local __i=1 __opt __ans | + | |
| - | printf ' | + | |
| - | for __opt in " | + | |
| - | while :; do | + | |
| - | read -rp " | + | |
| - | if [[ $__ans =~ ^[0-9]+$ ]] && (( __ans >= 1 && __ans <= $# )); then printf -v " | + | |
| - | warn " | + | |
| - | done | + | |
| - | } | + | |
| - | + | ||
| - | ------------------------------------------------------------------ validadores | + | |
| - | + | ||
| - | v_nonempty() { [[ -n $1 ]] || { warn "Campo obrigatório."; | + | |
| - | v_any() | + | |
| - | v_host() | + | |
| - | v_port() | + | |
| - | v_name() | + | |
| - | v_ns() | + | |
| - | v_posint() | + | |
| - | v_time() | + | |
| - | v_hours() | + | |
| - | v_authid() { | + | |
| - | local re=' | + | |
| - | [[ $1 =~ $re ]] || { warn " | + | |
| - | } | + | |
| - | v_fp() { | + | |
| - | local re=' | + | |
| - | [[ $1 =~ $re ]] || { warn " | + | |
| - | } | + | |
| - | v_cron() { | + | |
| - | local -a f; read -ra f <<<" | + | |
| - | (( ${#f[@]} == 5 )) && [[ $1 =~ ^[0-9A-Za-z*/, | + | |
| - | || { warn " | + | |
| - | } | + | |
| - | + | ||
| - | ------------------------------------------------------------------ helpers PBS | + | |
| - | + | ||
| - | repo() { printf ' | + | |
| - | ns_args() { [[ -n $NAMESPACE ]] && printf -- ' | + | |
| - | + | ||
| - | pbc() { | + | |
| - | local -a ns; mapfile -t ns < < | + | |
| - | PBS_REPOSITORY=" | + | |
| - | timeout 60 proxmox-backup-client " | + | |
| - | } | + | |
| - | + | ||
| - | fetch_fingerprint() { | + | |
| - | timeout 8 openssl s_client -connect " | + | |
| - | | openssl x509 -noout -fingerprint -sha256 2>/ | + | |
| - | } | + | |
| - | + | ||
| - | is_local_host() { | + | |
| - | [[ $1 == 127.0.0.1 || $1 == localhost || $1 == " | + | |
| - | hostname -I 2>/ | + | |
| - | } | + | |
| - | + | ||
| - | ------------------------------------------------------------------ pré-checagem | + | |
| - | + | ||
| - | preflight() { | + | |
| - | step " | + | |
| - | [[ $EUID -eq 0 ]] || die " | + | |
| - | local c missing=0 | + | |
| - | for c in proxmox-backup-client openssl timeout flock; do | + | |
| - | if command -v " | + | |
| - | done | + | |
| - | (( missing )) && die " | + | |
| - | [[ -d ${PREFIX}/ | + | |
| - | ok " | + | |
| - | command -v proxmox-backup-manager >/ | + | |
| - | return 0 | + | |
| - | } | + | |
| - | + | ||
| - | ------------------------------------------------------------------ 1. conexão | + | |
| - | + | ||
| - | step_connection() { | + | |
| - | step "1/5 · Conexão com o Proxmox Backup Server" | + | |
| - | local def_host=""; | + | |
| - | ask PBS_HOST "IP ou hostname do PBS" " | + | |
| - | ask PBS_PORT "Porta da API" " | + | |
| - | + | ||
| - | if (( IS_LOCAL_PBS )) && is_local_host " | + | |
| - | info " | + | |
| - | proxmox-backup-manager datastore list 2>/ | + | |
| - | fi | + | |
| - | ask DATASTORE "Nome do datastore" | + | |
| - | ask NAMESPACE " | + | |
| - | + | ||
| - | tip " | + | |
| - | ask AUTH_ID " | + | |
| - | + | ||
| - | setup_token | + | |
| - | setup_fingerprint | + | |
| - | test_connection || step_connection | + | |
| - | } | + | |
| - | + | ||
| - | setup_token() { | + | |
| - | local user=${AUTH_ID%%!} tok=${AUTH_ID# | + | |
| - | + | ||
| - | if (( IS_LOCAL_PBS )) && is_local_host " | + | |
| - | if proxmox-backup-manager user list-tokens " | + | |
| - | warn "O token ${AUTH_ID} já existe." | + | |
| - | choose opt "O que deseja fazer?" | + | |
| - | if (( opt == 2 )); then | + | |
| - | proxmox-backup-manager user delete-token " | + | |
| - | else | + | |
| - | ask_secret SECRET " | + | |
| - | fi | + | |
| - | fi | + | |
| - | if [[ -z $SECRET ]]; then | + | |
| - | out=$(proxmox-backup-manager user generate-token " | + | |
| - | || die "Falha ao gerar o token: $out" | + | |
| - | SECRET=$(sed -n ' | + | |
| - | [[ -n $SECRET ]] || die "Não consegui ler o secret gerado: $out" | + | |
| - | ok "Token ${AUTH_ID} criado" | + | |
| - | fi | + | |
| - | acl_path="/ | + | |
| - | if proxmox-backup-manager acl update " | + | |
| - | ok " | + | |
| - | else | + | |
| - | warn "Não consegui aplicar a ACL. Aplique manualmente:" | + | |
| - | tip " | + | |
| - | fi | + | |
| - | else | + | |
| - | if [[ -n $SECRET ]] && confirm " | + | |
| - | tip "O token precisa da role DatastoreAdmin em / | + | |
| - | ask_secret SECRET " | + | |
| - | fi | + | |
| - | } | + | |
| - | + | ||
| - | setup_fingerprint() { | + | |
| - | local fp | + | |
| - | info " | + | |
| - | fp=$(fetch_fingerprint || true) | + | |
| - | if [[ -n $fp ]]; then | + | |
| - | printf ' | + | |
| - | tip " | + | |
| - | if confirm "O fingerprint confere?" | + | |
| - | else | + | |
| - | warn "Não foi possível ler o certificado automaticamente." | + | |
| - | fi | + | |
| - | ask FINGERPRINT "Cole o fingerprint SHA-256" | + | |
| - | } | + | |
| - | + | ||
| - | test_connection() { | + | |
| - | local out | + | |
| - | info " | + | |
| - | if out=$(pbc list --output-format text 2>& | + | |
| - | mapfile -t AVAILABLE < <(grep -oE ' | + | |
| - | ok " | + | |
| - | return 0 | + | |
| - | fi | + | |
| - | fail "Falha na conexão:" | + | |
| - | sed ' | + | |
| - | confirm " | + | |
| - | confirm " | + | |
| - | AVAILABLE=() | + | |
| - | return 0 | + | |
| - | } | + | |
| - | + | ||
| - | ------------------------------------------------------------------ 2. grupos | + | |
| - | + | ||
| - | print_groups() { | + | |
| - | local i | + | |
| - | if (( ${# | + | |
| - | printf ' | + | |
| - | for i in " | + | |
| - | printf ' | + | |
| - | done | + | |
| - | } | + | |
| - | + | ||
| - | group_index() { local i; for i in " | + | |
| - | in_available() { printf ' | + | |
| - | + | ||
| - | resolve_group() { # entrada: " | + | |
| - | local input=$1 opt | + | |
| - | if [[ $input =~ ^(vm|ct|host)/ | + | |
| - | [[ $input =~ ^[0-9]+$ ]] || { warn " | + | |
| - | local has_vm=0 has_ct=0 | + | |
| - | in_available " | + | |
| - | in_available " | + | |
| - | if (( has_vm && has_ct )); then | + | |
| - | choose opt " | + | |
| - | (( opt == 1 )) && echo " | + | |
| - | elif (( has_vm )); then echo " | + | |
| - | elif (( has_ct )); then echo " | + | |
| - | else | + | |
| - | warn "ID $input não encontrado no datastore${NAMESPACE: | + | |
| - | choose opt " | + | |
| - | case $opt in 1) echo " | + | |
| - | fi | + | |
| - | } | + | |
| - | + | ||
| - | step_groups() { | + | |
| - | step "2/5 · VMs/CTs e retenção" | + | |
| - | if (( ${# | + | |
| - | info " | + | |
| - | if command -v column >/ | + | |
| - | printf ' | + | |
| - | else | + | |
| - | printf ' | + | |
| - | fi | + | |
| - | fi | + | |
| - | tip " | + | |
| - | + | ||
| - | local input group keep idx opt n | + | |
| - | while :; do | + | |
| - | printf ' | + | |
| - | read -rp " | + | |
| - | input=${input// | + | |
| - | if [[ -z $input ]]; then | + | |
| - | if (( ${# | + | |
| - | printf ' | + | |
| - | choose opt "Lista concluída?" | + | |
| - | case $opt in | + | |
| - | 1) break ;; | + | |
| - | 2) continue ;; | + | |
| - | 3) ask n " | + | |
| - | if (( n <= ${# | + | |
| - | ok " | + | |
| - | unset ' | + | |
| - | else warn "Item inexistente."; | + | |
| - | 4) G_NAMES=(); G_KEEP=(); ok "Lista limpa." | + | |
| - | esac | + | |
| - | continue | + | |
| - | fi | + | |
| - | + | ||
| - | group=$(resolve_group " | + | |
| - | if idx=$(group_index " | + | |
| - | warn " | + | |
| - | confirm " | + | |
| - | ask keep " | + | |
| - | G_KEEP[$idx]=$keep | + | |
| - | else | + | |
| - | ask keep " | + | |
| - | G_NAMES+=(" | + | |
| - | fi | + | |
| - | ok " | + | |
| - | + | ||
| - | done | + | |
| - | } | + | |
| - | + | ||
| - | ------------------------------------------------------------------ 3. simulação | + | |
| - | + | ||
| - | step_dryrun() { | + | |
| - | step "3/5 · Simulação (dry-run)" | + | |
| - | confirm " | + | |
| - | local i out kept removed | + | |
| - | printf ' | + | |
| - | for i in " | + | |
| - | if out=$(pbc prune " | + | |
| - | kept=$(grep -oE '" | + | |
| - | removed=$(grep -oE '" | + | |
| - | printf ' | + | |
| - | "$( (( removed > 0 )) && echo " | + | |
| - | else | + | |
| - | printf ' | + | |
| - | fi | + | |
| - | done | + | |
| - | printf ' | + | |
| - | confirm " | + | |
| - | } | + | |
| - | + | ||
| - | ------------------------------------------------------------------ 4. agendamento | + | |
| - | + | ||
| - | step_schedule() { | + | |
| - | step "4/5 · Agendamento" | + | |
| - | tip " | + | |
| - | local opt t h m d dname | + | |
| - | local -a days=(" | + | |
| - | choose opt " | + | |
| - | case $opt in | + | |
| - | 1) ask t " | + | |
| - | CRON_EXPR=" | + | |
| - | 2) choose d "Dia da semana:" | + | |
| - | ask t " | + | |
| - | CRON_EXPR=" | + | |
| - | 3) ask h " | + | |
| - | CRON_EXPR=" | + | |
| - | 4) ask CRON_EXPR " | + | |
| - | CRON_DESC=" | + | |
| - | 5) CRON_EXPR=""; | + | |
| - | esac | + | |
| - | ok " | + | |
| - | } | + | |
| - | + | ||
| - | ------------------------------------------------------------------ 5. instalação | + | |
| - | + | ||
| - | write_runner() { | + | |
| - | mkdir -p " | + | |
| - | cat >" | + | |
| - | # | + | |
| - | + | ||
| - | pbs-prune — executor de prune individual por VM/CT (gerado pelo pbs-prune-setup) | + | |
| - | + | ||
| - | Uso: pbs-prune [--dry-run] [--list] | + | |
| - | + | ||
| - | set -uo pipefail | + | |
| - | + | ||
| - | CONF=" | + | |
| - | [[ -r $CONF ]] || { echo " | + | |
| - | + | ||
| - | shellcheck source=/ | + | |
| - | + | ||
| - | source " | + | |
| - | + | ||
| - | DRY=() | + | |
| - | case " | + | |
| - | -n|--dry-run) DRY=(--dry-run) ;; | + | |
| - | -l|--list) | + | |
| - | -h|--help) | + | |
| - | "" | + | |
| - | *) echo " | + | |
| - | esac | + | |
| - | + | ||
| - | exec 9>" | + | |
| - | flock -n 9 || { echo "Já existe uma execução em andamento." | + | |
| - | + | ||
| - | log() { | + | |
| - | local line; line=" | + | |
| - | printf ' | + | |
| - | [[ -t 1 ]] && printf ' | + | |
| - | logger -t pbs-prune -- " | + | |
| - | return 0 | + | |
| - | } | + | |
| - | + | ||
| - | export PBS_REPOSITORY=" | + | |
| - | export PBS_FINGERPRINT=" | + | |
| - | PBS_PASSWORD=" | + | |
| - | export PBS_PASSWORD | + | |
| - | + | ||
| - | NS=(); [[ -n ${NAMESPACE: | + | |
| - | + | ||
| - | total=0 errors=0 removed_total=0 start=$SECONDS | + | |
| - | log "===== início${DRY: | + | |
| - | while read -r group keep _; do | + | |
| - | [[ -z ${group:-} || $group == #* ]] && continue | + | |
| - | total=$((total + 1)) | + | |
| - | if out=$(timeout 600 proxmox-backup-client prune " | + | |
| - | " | + | |
| - | removed=$(grep -oE '" | + | |
| - | removed_total=$((removed_total + removed)) | + | |
| - | log " | + | |
| - | else | + | |
| - | errors=$((errors + 1)) | + | |
| - | log " | + | |
| - | while IFS= read -r l; do log " | + | |
| - | fi | + | |
| - | done <" | + | |
| - | log "===== fim · grupos=${total} erros=${errors} snapshots removidos=${removed_total} · $((SECONDS - start))s =====" | + | |
| - | + | ||
| - | (( errors == 0 )) | + | |
| - | RUNNER_EOF | + | |
| - | sed -i " | + | |
| - | chmod 750 " | + | |
| - | } | + | |
| - | + | ||
| - | write_config() { | + | |
| - | install -d -m 700 " | + | |
| - | umask 077 | + | |
| - | { | + | |
| - | echo "# Gerado por pbs-prune-setup v${VERSION} em $(date '+%F %T'). Rode o setup novamente para alterar." | + | |
| - | printf ' | + | |
| - | " | + | |
| - | printf ' | + | |
| - | printf ' | + | |
| - | " | + | |
| - | } >" | + | |
| - | printf ' | + | |
| - | chmod 600 " | + | |
| - | umask 022 | + | |
| - | } | + | |
| - | + | ||
| - | write_groups() { | + | |
| - | local i | + | |
| - | install -d -m 700 " | + | |
| - | { | + | |
| - | echo "# grupo keep-last | + | |
| - | echo "# Pode editar à mão; uma linha por grupo." | + | |
| - | for i in " | + | |
| - | } >" | + | |
| - | chmod 600 " | + | |
| - | } | + | |
| - | + | ||
| - | write_cron() { | + | |
| - | if [[ -z $CRON_EXPR ]]; then rm -f " | + | |
| - | mkdir -p " | + | |
| - | cat >" | + | |
| - | + | ||
| - | pbs-prune — ${CRON_DESC} (gerado por pbs-prune-setup) | + | |
| - | + | ||
| - | SHELL=/ | + | |
| - | PATH=/ | + | |
| - | ${CRON_EXPR} root ${RUNNER} >/ | + | |
| - | EOF | + | |
| - | chmod 644 " | + | |
| - | } | + | |
| - | + | ||
| - | write_logrotate() { | + | |
| - | mkdir -p " | + | |
| - | cat >" | + | |
| - | ${LOG_FILE} { | + | |
| - | monthly | + | |
| - | rotate 12 | + | |
| - | compress | + | |
| - | missingok | + | |
| - | notifempty | + | |
| - | } | + | |
| - | EOF | + | |
| - | } | + | |
| - | + | ||
| - | step_review_install() { | + | |
| - | step "5/5 · Revisão e instalação" | + | |
| - | printf ' | + | |
| - | " | + | |
| - | printf ' | + | |
| - | confirm " | + | |
| - | + | ||
| - | write_config; | + | |
| - | write_groups; | + | |
| - | ok " | + | |
| - | write_runner; | + | |
| - | write_cron; | + | |
| - | write_logrotate; | + | |
| - | } | + | |
| - | + | ||
| - | finish() { | + | |
| - | printf ' | + | |
| - | if confirm " | + | |
| - | " | + | |
| - | fi | + | |
| - | step " | + | |
| - | printf ' | + | |
| - | " | + | |
| - | " | + | |
| - | " | + | |
| - | "tail -f ${LOG_FILE}" | + | |
| - | " | + | |
| - | tip " | + | |
| - | printf ' | + | |
| - | } | + | |
| - | + | ||
| - | ------------------------------------------------------------------ carregar / desinstalar | + | |
| - | + | ||
| - | load_existing() { | + | |
| - | + | ||
| - | shellcheck source=/ | + | |
| - | + | ||
| - | source " | + | |
| - | [[ -r $SECRET_FILE ]] && SECRET=$(<" | + | |
| - | G_NAMES=() G_KEEP=() | + | |
| - | local g k | + | |
| - | while read -r g k _; do | + | |
| - | [[ -z ${g:-} || $g == #* ]] && continue | + | |
| - | G_NAMES+=(" | + | |
| - | done <" | + | |
| - | } | + | |
| - | + | ||
| - | uninstall() { | + | |
| - | step " | + | |
| - | [[ $EUID -eq 0 ]] || die " | + | |
| - | confirm " | + | |
| - | rm -f " | + | |
| - | rm -rf " | + | |
| - | tip "O log ${LOG_FILE} foi mantido. O token no PBS não foi removido:" | + | |
| - | tip " | + | |
| - | } | + | |
| - | + | ||
| - | existing_menu() { | + | |
| - | local opt | + | |
| - | load_existing | + | |
| - | step " | + | |
| - | printf ' | + | |
| - | printf ' | + | |
| - | choose opt "O que deseja fazer?" | + | |
| - | " | + | |
| - | " | + | |
| - | case $opt in | + | |
| - | 1) test_connection || true; step_groups; | + | |
| - | 2) step_schedule; | + | |
| - | 3) full_flow; return ;; | + | |
| - | 4) " | + | |
| - | 5) uninstall; exit 0 ;; | + | |
| - | 6) exit 0 ;; | + | |
| - | esac | + | |
| - | finish | + | |
| - | } | + | |
| - | + | ||
| - | full_flow() { | + | |
| - | step_connection | + | |
| - | step_groups | + | |
| - | step_dryrun | + | |
| - | step_schedule | + | |
| - | step_review_install | + | |
| - | finish | + | |
| - | } | + | |
| - | + | ||
| - | ------------------------------------------------------------------ main | + | |
| - | + | ||
| - | case " | + | |
| - | -h|--help) | + | |
| - | -v|--version) echo " | + | |
| - | --uninstall) | + | |
| - | "" | + | |
| - | *) die " | + | |
| - | esac | + | |
| - | + | ||
| - | banner | + | |
| - | preflight | + | |
| - | if [[ -f $CONF_FILE && -f $GROUPS_FILE ]]; then existing_menu; | + | |
| </ | </ | ||
| - | ===== Referência ===== | + | A desinstalação remove o executor, configurações, |
| - | [[https:// | + | O API Token criado no PBS não é removido automaticamente. |
| - | + | ||
| - | ===== Download ===== | + | |
| - | + | ||
| - | O arquivo do script pode ser disponibilizado separadamente na própria página da Wiki usando a função de mídia do DokuWiki. | + | |
| - | + | ||
| - | Para uma página de documentação, | + | |
| - | + | ||
| - | esta página para documentação; | + | |
| - | + | ||
| - | o arquivo < | + | |
| - | + | ||
| - | o código completo acima como referência. | + | |
| - | + | ||
| - | ===== Comandos rápidos ===== | + | |
| - | + | ||
| - | <code bash> | + | |
| - | # Assistente | + | |
| - | ./ | + | |
| - | + | ||
| - | Ajuda | + | |
| - | + | ||
| - | ./ | + | |
| - | + | ||
| - | Dry-run | + | |
| - | + | ||
| - | pbs-prune --dry-run | + | |
| - | + | ||
| - | Executar | + | |
| - | + | ||
| - | pbs-prune | + | |
| - | + | ||
| - | Listar grupos | + | |
| - | + | ||
| - | pbs-prune --list | + | |
| - | + | ||
| - | Log | + | |
| - | + | ||
| - | tail -f / | + | |
| - | + | ||
| - | Desinstalar | + | |
| - | + | ||
| - | ./ | + | |
| - | </ | + | |
proxmox/prune-individual.1790698386.txt.gz · Última modificação: por admin
