Ferramentas do usuário

Ferramentas do site


proxmox:prune-individual

Diferenças

Aqui você vê as diferenças entre duas revisões dessa página.

Link para esta página de comparações

Ambos lados da revisão anteriorRevisão anterior
Próxima revisão
Revisão anterior
proxmox:prune-individual [2026/09/29 13:13] – adminproxmox:prune-individual [2026/09/29 13:22] (atual) – admin
Linha 1: Linha 1:
-====== PBS Prune Individual ======+====== PBS — Prune Individual por VM/CT ======
  
-===== 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/namespace. Este assistente permite selecionar grupos específicos, definir o keep-last, realizar uma simulação antes da execução e instalar o processo para execução recorrente. +
- +
-===== 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, cron e logrotate. +
- +
-Mantém logs das execuções. +
- +
-Utiliza lock para evitar duas execuções simultâneas. +
- +
-===== Fluxo de funcionamento =====+
  
 <code> <code>
-Pré-requisitos +vm/100   7 
-      | +vm/101   15 
-      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+
 </code> </code>
 +
 +Cada grupo pode possuir seu próprio ''keep-last''.
  
 ===== Pré-requisitos ===== ===== Pré-requisitos =====
  
-Antes de executar o assistente, é necessário preparar o acesso ao Proxmox Backup Server. +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; +
- +
-<code>proxmox-backup-client</code> instalado; +
- +
-<code>openssl</code> instalado; +
- +
-<code>timeout</code> disponível; +
- +
-<code>flock</code> disponível; +
- +
-<code>cron</code> instalado e funcionando. +
- +
-==== Usuário da API ====+
  
-O API Token sempre pertence a um usuário existente no Proxmox Backup Server.+  * Proxmox Backup Server acessível; 
 +  * ''proxmox-backup-client'' instalado; 
 +  * acesso ''root'' ao servidor onde o script será instalado; 
 +  * um usuário existente no PBS; 
 +  * API Token associado a esse usuário; 
 +  * permissão no datastore que será utilizado.
  
-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:
- 
-<code> 
-root@pam 
-</code> 
- 
-ou: 
- 
-<code> 
-backup@pbs 
-</code> 
- 
-O usuário pode ser um usuário administrativo existente, como <code>root@pam</code>, ou um usuário dedicado ao serviço, como <code>backup@pbs</code>. 
- 
-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 é: 
- 
-<code> 
-USUARIO@REALM!NOME_DO_TOKEN 
-</code> 
- 
-Exemplo utilizando <code>root@pam</code>: 
  
 <code> <code>
 root@pam!prune root@pam!prune
-</code> 
- 
-Exemplo utilizando um usuário dedicado: 
- 
-<code> 
 backup@pbs!prune backup@pbs!prune
 </code> </code>
  
-Nesse exemplo: +A permissão deve ser aplicada no datastore utilizado:
- +
-<code>backup@pbs</code> é o usuário existente; +
- +
-<code>prune</code> é o nome do API Token. +
- +
-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, scripts públicos, prints ou repositórios. +
- +
-==== Permissão no Datastore ==== +
- +
-Depois de criar o API Token, identifique o datastore onde estão os backups que serão processados. +
- +
-Por exemplo: +
- +
-<code> +
-backup +
-</code> +
- +
-O caminho da ACL será: +
- +
-<code> +
-/datastore/backup +
-</code> +
- +
-Para o PBS Prune Individual, o token deve possuir a permissão necessária no datastore. +
- +
-Exemplo:+
  
 <code bash> <code bash>
Linha 154: Linha 39:
 </code> </code>
  
-Nesse exemplo:+Substitua ''backup'' pelo nome do seu datastore e utilize o usuário/token criado no seu ambiente.
  
-<code>backup@pbs</code> → usuário existente;+===== Download =====
  
-<code>prune</code> → API Token;+Baixe o script diretamente:
  
-<code>/datastore/backup</code> → datastore;+[[https://cursoproxmox.com.br/prune/pbs-prune-setup.sh|pbs-prune-setup.sh]]
  
-<code>DatastoreAdmin</code> → permissão atribuída ao token.+===== 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 é: +
- +
-<code> +
-Usuário existente no PBS +
-        | +
-        v +
-API Token criado nesse usuário +
-        | +
-        v +
-Permissão no datastore +
-        | +
-        v +
-PBS Prune Individual +
-</code> +
- +
-===== 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 <code>proxmox-backup-manager</code> e pode oferecer a criação/ajuste do token e da permissão automaticamente. +
- +
-===== Como usar ===== +
- +
-==== Executar o assistente ====+
  
 <code bash> <code bash>
-./pbs-prune-setup.sh+chmod +x /root/pbs-prune-setup.sh 
 +/root/pbs-prune-setup.sh
 </code> </code>
  
-O assistente conduz a configuração em etapas:+O assistente solicitará:
  
-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 ''dry-run''.
  
-definição do keep-last;+===== Estrutura criada =====
  
-dry-run;+Após a instalação:
  
-agendamento;+<code> 
 +/usr/local/sbin/ 
 +└── pbs-prune
  
-revisão;+/etc/pbs-prune/ 
 +├── pbs-prune.conf 
 +├── groups.conf 
 +└── secret
  
-instalação.+/etc/cron.d/ 
 +└── pbs-prune
  
-==== Ver ajuda ====+/etc/logrotate.d/ 
 +└── pbs-prune
  
-<code bash> +/var/log/ 
-./pbs-prune-setup.sh --help+└── pbs-prune.log
 </code> </code>
  
-==== Desinstalar ====+Arquivos principais:
  
-<code bash> +^ Arquivo ^ Função ^ 
-./pbs-prune-setup.sh --uninstall +| ''/usr/local/sbin/pbs-prune'' | Executor do prune | 
-</code>+| ''/etc/pbs-prune/pbs-prune.conf'' | Configuração | 
 +| ''/etc/pbs-prune/groups.conf'' | Retenção por VM/CT | 
 +| ''/etc/pbs-prune/secret'' | Secret do API Token | 
 +| ''/etc/cron.d/pbs-prune'' | Agendamento | 
 +| ''/var/log/pbs-prune.log'' | Log das execuções |
  
-A desinstalação remove o executor, configuração, agendamento e logrotate locais.+===== Configuração das VMs/CTs =====
  
-O token existente no PBS não é removido automaticamente. +Arquivo:
- +
-===== Configuração de retenção ===== +
- +
-O arquivo:+
  
 <code> <code>
 /etc/pbs-prune/groups.conf /etc/pbs-prune/groups.conf
 </code> </code>
- 
-possui uma linha para cada grupo configurado. 
  
 Exemplo: Exemplo:
Linha 257: Linha 113:
 <code> <code>
 vm/100 7 vm/100 7
-ct/200 14+vm/101 15 
 +ct/200 30
 </code> </code>
  
-Neste exemplo: +Formato:
- +
-<code>vm/100</code> mantém os últimos 7 backups. +
- +
-<code>ct/200</code> mantém os últimos 14 backups. +
- +
-Também é possível informar somente o ID durante o assistente:+
  
 <code> <code>
-100+GRUPO KEEP-LAST
 </code> </code>
  
-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:
 </code> </code>
  
-===== Execução ===== +Executar:
- +
-Para executar o prune configurado:+
  
 <code bash> <code bash>
Linha 294: Linha 137:
 </code> </code>
  
-Para visualizar os grupos configurados:+Listar grupos:
  
 <code bash> <code bash>
Linha 300: Linha 143:
 </code> </code>
  
-Para acompanhar o log:+Acompanhar log:
  
 <code bash> <code bash>
 tail -f /var/log/pbs-prune.log tail -f /var/log/pbs-prune.log
 </code> </code>
- 
-===== 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 ^ 
-| <code>/usr/local/sbin/pbs-prune</code> | Executor | 
-| <code>/etc/pbs-prune/pbs-prune.conf</code> | Configuração de conexão e agendamento | 
-| <code>/etc/pbs-prune/groups.conf</code> | Grupos e retenção | 
-| <code>/etc/pbs-prune/secret</code> | Secret do token | 
-| <code>/etc/cron.d/pbs-prune</code> | Agendamento | 
-| <code>/etc/logrotate.d/pbs-prune</code> | Rotação do log | 
-| <code>/var/log/pbs-prune.log</code> | Log de execução | 
- 
-===== Estrutura lógica ===== 
- 
-<code> 
-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 
-</code> 
- 
-===== 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: 
- 
-<code>flock</code> para impedir execuções concorrentes; 
- 
-<code>timeout</code> para limitar a execução do cliente PBS; 
- 
-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 deixam de ser mantidos.+O prune remove as referências dos snapshots.
  
-O espaço do datastore é efetivamente liberado quando o Garbage Collection do datastore for executado.+O espaço físico no datastore será efetivamente recuperado após a execução do **Garbage Collection (GC)**.
  
-===== Licença ===== +Por isso, recomenda-se executar na ordem:
- +
-O script informa em seu cabeçalho:+
  
 <code> <code>
-Licença : MIT+Backup → Prune Individual → Garbage Collection
 </code> </code>
  
-===== Script completo ===== +===== Desinstalação =====
- +
-O conteúdo abaixo é o script completo <code>pbs-prune-setup.sh</code>.+
  
 <code bash> <code bash>
-#!/usr/bin/env bash +/root/pbs-prune-setup.sh --uninstall
-# ============================================================================== +
-#  pbs-prune-setup — Assistente de prune individual por VM/CT no Proxmox Backup Server +
-# ------------------------------------------------------------------------------ +
-#  Autor   : Caio Monteiro +
-#  Site    : www.projetoempresalivre.com.br +
-#  Versão  : 1.0.0 (25/09/2026) +
-#  Licença : MIT +
-# +
-#  O PBS só faz prune por datastore/namespace. Este assistente: +
-#    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              assistente interativo +
-#    pbs-prune-setup.sh --uninstall  remove tudo que foi instalado +
-#    pbs-prune-setup.sh --help +
-# +
-#  Arquivos instalados: +
-#    /usr/local/sbin/pbs-prune          executor (roda pelo cron ou manualmente) +
-#    /etc/pbs-prune/pbs-prune.conf      conexão e agendamento +
-#    /etc/pbs-prune/groups.conf         lista "grupo keep-last"  (ex.: vm/100 7) +
-#    /etc/pbs-prune/secret              secret do token (600) +
-#    /etc/cron.d/pbs-prune              agendamento +
-#    /etc/logrotate.d/pbs-prune         rotação do log +
-#    /var/log/pbs-prune.log             log de execução +
-# ============================================================================== +
- +
-set -uo pipefail +
- +
-readonly VERSION="1.0.0" +
-readonly APP="pbs-prune" +
-readonly PREFIX="${PBS_PRUNE_PREFIX:-}"        # só para testes; em produção fica vazio +
-readonly CONF_DIR="${PREFIX}/etc/${APP}" +
-readonly CONF_FILE="${CONF_DIR}/${APP}.conf" +
-readonly GROUPS_FILE="${CONF_DIR}/groups.conf" +
-readonly SECRET_FILE="${CONF_DIR}/secret" +
-readonly RUNNER="${PREFIX}/usr/local/sbin/${APP}" +
-readonly CRON_FILE="${PREFIX}/etc/cron.d/${APP}" +
-readonly LOGROTATE_FILE="${PREFIX}/etc/logrotate.d/${APP}" +
-readonly LOG_FILE="${PREFIX}/var/log/${APP}.log" +
-readonly LOCK_FILE="${PREFIX}/run/${APP}.lock" +
- +
------------------------------------------------------------------- estado +
- +
-PBS_HOST="" PBS_PORT="8007" DATASTORE="" NAMESPACE="" AUTH_ID="root@pam!prune" +
-SECRET="" FINGERPRINT="" CRON_EXPR="" CRON_DESC="" +
-G_NAMES=() G_KEEP=() AVAILABLE=() +
-IS_LOCAL_PBS=0 +
- +
------------------------------------------------------------------- interface +
- +
-if [[ -t 1 ]]; then +
-C_0=$'\e[0m' C_B=$'\e[1m' C_DIM=$'\e[2m' C_R=$'\e[31m' C_G=$'\e[32m' C_Y=$'\e[33m' C_C=$'\e[36m' +
-else +
-C_0="" C_B="" C_DIM="" C_R="" C_G="" C_Y="" C_C="" +
-fi +
- +
-hr()   { printf '%s%s%s\n' "$C_DIM" "────────────────────────────────────────────────────────────" "$C_0"; } +
-info() { printf '  %s➜%s %s\n' "$C_C" "$C_0" "$"; } +
-ok()   { printf '  %s✔%s %s\n' "$C_G" "$C_0" "$"; } +
-warn() { printf '  %s⚠%s %s\n' "$C_Y" "$C_0" "$" >&2; } +
-fail() { printf '  %s✖%s %s\n' "$C_R" "$C_0" "$" >&2; } +
-die()  { fail "$"; exit 1; } +
-tip()  { printf '  %s%s%s\n' "$C_DIM" "$" "$C_0"; } +
-step() { printf '\n%s%s▌ %s%s\n' "$C_B" "$C_C" "$1" "$C_0"; hr; } +
- +
-banner() { +
-printf '%s%s' "$C_B" "$C_C" +
-cat <<'EOF' +
- +
-╔════════════════════════════════════════════════════════╗ +
-║   PBS Prune Individual — Assistente de configuração    ║ +
-║   Retenção por VM/CT no Proxmox Backup Server          ║ +
-╚════════════════════════════════════════════════════════╝ +
-EOF +
-printf '%s  %sv%s · Projeto Empresa Livre%s\n' "$C_0" "$C_DIM" "$VERSION" "$C_0" +
-} +
- +
-on_interrupt() { stty echo 2>/dev/null; printf '\n'; warn "Cancelado pelo usuário. Nenhuma alteração pendente foi aplicada."; exit 130; } +
-trap on_interrupt INT +
- +
-ask VAR "pergunta" "padrão" [validador] +
- +
-ask() { +
-local __var=$1 __prompt=$2 __def=${3-} __val=${4:-v_nonempty} __ans +
-while :; do +
-read -rp "  ${__prompt}${__def:+ ${C_DIM}[${__def}]${C_0}}: " __ans || die "Entrada encerrada." +
-__ans=${__ans:-$__def} +
-if "$__val" "$__ans"; then printf -v "$__var" '%s' "$__ans"; return 0; fi +
-done +
-} +
- +
-ask_secret() { +
-local __var=$1 __prompt=$2 __ans +
-while :; do +
-read -rsp "  ${__prompt}: " __ans || die "Entrada encerrada."; printf '\n' +
-[[ -n $__ans ]] && { printf -v "$__var" '%s' "$__ans"; return 0; } +
-warn "Campo obrigatório." +
-done +
-} +
- +
-confirm "pergunta" [S|N]  -> 0 = sim +
- +
-confirm() { +
-local def=${2:-N} ans hint +
-[[ $def == S ]] && hint="S/n" || hint="s/N" +
-read -rp "  $1 ${C_DIM}[${hint}]${C_0}: " ans || die "Entrada encerrada." +
-ans=${ans:-$def} +
-[[ ${ans,,} =~ ^(s|sim|y|yes)$ ]] +
-} +
- +
-choose VAR "título" "opção 1" "opção 2" ...   -> VAR recebe o número escolhido +
- +
-choose() { +
-local __var=$1 __title=$2; shift 2 +
-local __i=1 __opt __ans +
-printf '  %s%s%s\n' "$C_B" "$__title" "$C_0" +
-for __opt in "$@"; do printf '   %s%2d)%s %s\n' "$C_C" "$__i" "$C_0" "$__opt"; __i=$((__i+1)); done +
-while :; do +
-read -rp "  Opção: " __ans || die "Entrada encerrada." +
-if [[ $__ans =~ ^[0-9]+$ ]] && (( __ans >= 1 && __ans <= $# )); then printf -v "$__var" '%s' "$__ans"; return 0; fi +
-warn "Escolha um número entre 1 e $#." +
-done +
-} +
- +
------------------------------------------------------------------- validadores +
- +
-v_nonempty() { [[ -n $1 ]] || { warn "Campo obrigatório."; return 1; }; } +
-v_any()      { return 0; } +
-v_host()     { [[ $1 =~ ^[A-Za-z0-9.-]+$ ]] || { warn "Host/IP inválido (use nome DNS ou IPv4)."; return 1; }; } +
-v_port()     { [[ $1 =~ ^[0-9]+$ ]] && (( $1 >= 1 && $1 <= 65535 )) || { warn "Porta inválida (1-65535)."; return 1; }; } +
-v_name()     { [[ $1 =~ ^[A-Za-z0-9.-]+$ ]] || { warn "Use apenas letras, números, ponto, hífen e sublinhado."; return 1; }; } +
-v_ns()       { [[ -z $1 || $1 =~ ^[A-Za-z0-9./-]+$ ]] || { warn "Namespace inválido (ex.: producao ou clientes/acme)."; return 1; }; } +
-v_posint()   { [[ $1 =~ ^[1-9][0-9]*$ ]] || { warn "Informe um inteiro maior ou igual a 1."; return 1; }; } +
-v_time()     { [[ $1 =~ ^([01]?[0-9]|2[0-3]):[0-5][0-9]$ ]] || { warn "Horário no formato HH (ex.: 03:30)."; return 1; }; } +
-v_hours()    { [[ $1 =~ ^([1-9]|1[0-9]|2[0-3])$ ]] || { warn "Informe de 1 a 23 horas."; return 1; }; } +
-v_authid() { +
-local re='^[A-Za-z0-9.-]+@[A-Za-z0-9.-]+![A-Za-z0-9.-]+$' +
-[[ $1 =~ $re ]] || { warn "Formato: usuario@realm!token  (ex.: root@pam!prune)"; return 1; } +
-} +
-v_fp() { +
-local re='^([0-9A-Fa-f]{2}:){31}[0-9A-Fa-f]{2}$' +
-[[ $1 =~ $re ]] || { warn "Fingerprint SHA-256 inválido (32 pares hex separados por ':')."; return 1; } +
-} +
-v_cron() { +
-local -a f; read -ra f <<<"$1" +
-(( ${#f[@]} == 5 )) && [[ $1 =~ ^[0-9A-Za-z*/,\ -]+$ ]]  +
-|| { warn "Expressão cron precisa de 5 campos (min hora dia mês dia-semana)."; return 1; } +
-} +
- +
------------------------------------------------------------------- helpers PBS +
- +
-repo() { printf '%s@%s:%s:%s' "$AUTH_ID" "$PBS_HOST" "$PBS_PORT" "$DATASTORE"; } +
-ns_args() { [[ -n $NAMESPACE ]] && printf -- '--ns\n%s\n' "$NAMESPACE"; } +
- +
-pbc() { +
-local -a ns; mapfile -t ns < <(ns_args) +
-PBS_REPOSITORY="$(repo)" PBS_PASSWORD="$SECRET" PBS_FINGERPRINT="$FINGERPRINT"  +
-timeout 60 proxmox-backup-client "$@" "${ns[@]}" </dev/null +
-} +
- +
-fetch_fingerprint() { +
-timeout 8 openssl s_client -connect "${PBS_HOST}:${PBS_PORT}" -servername "$PBS_HOST" </dev/null 2>/dev/null  +
-| openssl x509 -noout -fingerprint -sha256 2>/dev/null | cut -d= -f2 +
-} +
- +
-is_local_host() { +
-[[ $1 == 127.0.0.1 || $1 == localhost || $1 == "$(hostname)" || $1 == "$(hostname -f 2>/dev/null)" ]] && return 0 +
-hostname -I 2>/dev/null | tr ' ' '\n' | grep -qxF "$1" +
-} +
- +
------------------------------------------------------------------- pré-checagem +
- +
-preflight() { +
-step "Verificando pré-requisitos" +
-[[ $EUID -eq 0 ]] || die "Execute como root." +
-local c missing=0 +
-for c in proxmox-backup-client openssl timeout flock; do +
-if command -v "$c" >/dev/null 2>&1; then ok "$c"; else fail "$c não encontrado"; missing=1; fi +
-done +
-(( missing )) && die "Instale os pacotes faltando (proxmox-backup-client vem no PVE/PBS; em Debian puro use o repositório do PBS)." +
-[[ -d ${PREFIX}/etc/cron.d ]] || die "Diretório /etc/cron.d não existe — o cron está instalado?" +
-ok "cron" +
-command -v proxmox-backup-manager >/dev/null 2>&1 && { IS_LOCAL_PBS=1; ok "Rodando no próprio PBS — criação automática de token disponível"; } +
-return 0 +
-} +
- +
------------------------------------------------------------------- 1. conexão +
- +
-step_connection() { +
-step "1/5 · Conexão com o Proxmox Backup Server" +
-local def_host=""; (( IS_LOCAL_PBS )) && def_host="127.0.0.1" +
-ask PBS_HOST "IP ou hostname do PBS" "${PBS_HOST:-$def_host}" v_host +
-ask PBS_PORT "Porta da API" "$PBS_PORT" v_port +
- +
-if (( IS_LOCAL_PBS )) && is_local_host "$PBS_HOST"; then +
-info "Datastores disponíveis:" +
-proxmox-backup-manager datastore list 2>/dev/null | sed 's/^/     /' +
-fi +
-ask DATASTORE "Nome do datastore" "$DATASTORE" v_name +
-ask NAMESPACE "Namespace (Enter = raiz)" "$NAMESPACE" v_ns +
- +
-tip "Recomendado: token dedicado, ex.: root@pam!prune" +
-ask AUTH_ID "Auth-ID do token (usuario@realm!token)" "$AUTH_ID" v_authid +
- +
-setup_token +
-setup_fingerprint +
-test_connection || step_connection +
-} +
- +
-setup_token() { +
-local user=${AUTH_ID%%!} tok=${AUTH_ID#!} opt out acl_path +
- +
-if (( IS_LOCAL_PBS )) && is_local_host "$PBS_HOST" && confirm "Criar/ajustar o token e a permissão automaticamente?" S; then +
-if proxmox-backup-manager user list-tokens "$user" --output-format json 2>/dev/null | grep -qF ""${AUTH_ID}""; then +
-warn "O token ${AUTH_ID} já existe." +
-choose opt "O que deseja fazer?" "Usar o token existente (vou digitar o secret)" "Regenerar o token (o secret atual deixa de funcionar)" +
-if (( opt == 2 )); then +
-proxmox-backup-manager user delete-token "$user" "$tok" >/dev/null 2>&1 || die "Falha ao remover o token antigo." +
-else +
-ask_secret SECRET "Secret do token" +
-fi +
-fi +
-if [[ -z $SECRET ]]; then +
-out=$(proxmox-backup-manager user generate-token "$user" "$tok" --output-format json 2>&1)  +
-|| die "Falha ao gerar o token: $out" +
-SECRET=$(sed -n 's/."value"[[:space:]]:[[:space:]]"([^"])".*/\1/p' <<<"$out") +
-[[ -n $SECRET ]] || die "Não consegui ler o secret gerado: $out" +
-ok "Token ${AUTH_ID} criado" +
-fi +
-acl_path="/datastore/${DATASTORE}${NAMESPACE:+/$NAMESPACE}" +
-if proxmox-backup-manager acl update "$acl_path" DatastoreAdmin --auth-id "$AUTH_ID" >/dev/null 2>&1; then +
-ok "Permissão DatastoreAdmin em ${acl_path}" +
-else +
-warn "Não consegui aplicar a ACL. Aplique manualmente:" +
-tip "proxmox-backup-manager acl update ${acl_path} DatastoreAdmin --auth-id '${AUTH_ID}'" +
-fi +
-else +
-if [[ -n $SECRET ]] && confirm "Manter o secret já configurado?" S; then return 0; fi +
-tip "O token precisa da role DatastoreAdmin em /datastore/${DATASTORE} (prune de grupos de outros donos)." +
-ask_secret SECRET "Secret do token" +
-fi +
-} +
- +
-setup_fingerprint() { +
-local fp +
-info "Obtendo o fingerprint do certificado de ${PBS_HOST}:${PBS_PORT}..." +
-fp=$(fetch_fingerprint || true) +
-if [[ -n $fp ]]; then +
-printf '     %s%s%s\n' "$C_B" "$fp" "$C_0" +
-tip "Confira em: PBS → Painel → Mostrar Fingerprint (ou: proxmox-backup-manager cert info)" +
-if confirm "O fingerprint confere?" S; then FINGERPRINT=$fp; return 0; fi +
-else +
-warn "Não foi possível ler o certificado automaticamente." +
-fi +
-ask FINGERPRINT "Cole o fingerprint SHA-256" "$FINGERPRINT" v_fp +
-} +
- +
-test_connection() { +
-local out +
-info "Testando conexão em $(repo)${NAMESPACE:+ (ns: $NAMESPACE)}..." +
-if out=$(pbc list --output-format text 2>&1); then +
-mapfile -t AVAILABLE < <(grep -oE '\b(vm|ct|host)/[A-Za-z0-9._-]+' <<<"$out" | sort -u -V) +
-ok "Conectado — ${#AVAILABLE[@]} grupo(s) de backup encontrados" +
-return 0 +
-fi +
-fail "Falha na conexão:" +
-sed 's/^/     /' <<<"$out" | tail -n 5 >&2 +
-confirm "Revisar os dados de conexão?" S && return 1 +
-confirm "Continuar mesmo assim?" N || die "Abortado." +
-AVAILABLE=() +
-return 0 +
-} +
- +
------------------------------------------------------------------- 2. grupos +
- +
-print_groups() { +
-local i +
-if (( ${#G_NAMES[@]} == 0 )); then tip "(nenhum grupo configurado)"; return; fi +
-printf '  %s%-4s %-18s %s%s\n' "$C_B" "#" "GRUPO" "MANTER (keep-last)" "$C_0" +
-for i in "${!G_NAMES[@]}"; do +
-printf '  %-4s %-18s %s\n' "$((i+1))" "${G_NAMES[$i]}" "${G_KEEP[$i]}" +
-done +
-} +
- +
-group_index() { local i; for i in "${!G_NAMES[@]}"; do [[ ${G_NAMES[$i]} == "$1" ]] && { echo "$i"; return 0; }; done; return 1; } +
-in_available() { printf '%s\n' "${AVAILABLE[@]}" | grep -qxF "$1"; } +
- +
-resolve_group() {   # entrada: "100", "vm/100", "ct/200"  -> saída: grupo +
-local input=$1 opt +
-if [[ $input =~ ^(vm|ct|host)/[A-Za-z0-9._-]+$ ]]; then echo "$input"; return 0; fi +
-[[ $input =~ ^[0-9]+$ ]] || { warn "Informe o ID (ex.: 100) ou o grupo (ex.: ct/200)."; return 1; } +
-local has_vm=0 has_ct=0 +
-in_available "vm/$input" && has_vm=1 +
-in_available "ct/$input" && has_ct=1 +
-if (( has_vm && has_ct )); then +
-choose opt "Existem vm/$input e ct/$input. Qual?" "vm/$input (QEMU)" "ct/$input (LXC)" >&2 +
-(( opt == 1 )) && echo "vm/$input" || echo "ct/$input" +
-elif (( has_vm )); then echo "vm/$input" +
-elif (( has_ct )); then echo "ct/$input" +
-else +
-warn "ID $input não encontrado no datastore${NAMESPACE:+ / ns $NAMESPACE}." +
-choose opt "Adicionar mesmo assim como:" "vm/$input (QEMU)" "ct/$input (LXC)" "Cancelar" >&2 +
-case $opt in 1) echo "vm/$input";; 2) echo "ct/$input";; *) return 1;; esac +
-fi +
-} +
- +
-step_groups() { +
-step "2/5 · VMs/CTs e retenção" +
-if (( ${#AVAILABLE[@]} )); then +
-info "Grupos encontrados no datastore:" +
-if command -v column >/dev/null 2>&1; then +
-printf '%s\n' "${AVAILABLE[@]}" | column -c 70 | sed 's/^/     /' +
-else +
-printf '     %s\n' "${AVAILABLE[@]}" +
-fi +
-fi +
-tip "Digite o ID (100) ou o grupo (ct/200). Enter vazio finaliza." +
- +
-local input group keep idx opt n +
-while :; do +
-printf '\n' +
-read -rp "  ID da VM/CT: " input || die "Entrada encerrada." +
-input=${input// /} +
-if [[ -z $input ]]; then +
-if (( ${#G_NAMES[@]} == 0 )); then warn "Adicione pelo menos um grupo."; continue; fi +
-printf '\n'; print_groups; printf '\n' +
-choose opt "Lista concluída?" "Sim, continuar" "Adicionar mais" "Remover um item" "Limpar a lista" +
-case $opt in +
-1) break ;; +
-2) continue ;; +
-3) ask n "Número do item a remover" "" v_posint +
-if (( n <= ${#G_NAMES[@]} )); then +
-ok "Removido ${G_NAMES[$((n-1))]}" +
-unset 'G_NAMES[n-1]' 'G_KEEP[n-1]'; G_NAMES=("${G_NAMES[@]}"); G_KEEP=("${G_KEEP[@]}") +
-else warn "Item inexistente."; fi ;; +
-4) G_NAMES=(); G_KEEP=(); ok "Lista limpa." ;; +
-esac +
-continue +
-fi +
- +
-group=$(resolve_group "$input") || continue +
-if idx=$(group_index "$group"); then +
-  warn "$group já está na lista (keep-last ${G_KEEP[$idx]})." +
-  confirm "Alterar a retenção?" S || continue +
-  ask keep "Quantos backups manter de $group" "${G_KEEP[$idx]}" v_posint +
-  G_KEEP[$idx]=$keep +
-else +
-  ask keep "Quantos backups manter de $group" "7" v_posint +
-  G_NAMES+=("$group"); G_KEEP+=("$keep") +
-fi +
-ok "$group → manter os últimos $keep" +
- +
-done +
-} +
- +
------------------------------------------------------------------- 3. simulação +
- +
-step_dryrun() { +
-step "3/5 · Simulação (dry-run)" +
-confirm "Simular agora o que seria mantido/removido?" S || { tip "Simulação pulada."; return 0; } +
-local i out kept removed +
-printf '  %s%-18s %-10s %-9s %s%s\n' "$C_B" "GRUPO" "KEEP-LAST" "MANTÉM" "REMOVE" "$C_0" +
-for i in "${!G_NAMES[@]}"; do +
-if out=$(pbc prune "${G_NAMES[$i]}" --keep-last "${G_KEEP[$i]}" --dry-run --output-format json 2>&1); then +
-kept=$(grep -oE '"keep":[[:space:]]*true' <<<"$out" | wc -l) +
-removed=$(grep -oE '"keep":[[:space:]]*false' <<<"$out" | wc -l) +
-printf '  %-18s %-10s %-9s %s%s%s\n' "${G_NAMES[$i]}" "${G_KEEP[$i]}" "$kept"  +
-"$( (( removed > 0 )) && echo "$C_Y" )" "$removed" "$C_0" +
-else +
-printf '  %-18s %-10s %s%s%s\n' "${G_NAMES[$i]}" "${G_KEEP[$i]}" "$C_R" "erro: $(tail -n1 <<<"$out")" "$C_0" +
-fi +
-done +
-printf '\n' +
-confirm "Resultado OK? (N volta para editar a lista)" S || { step_groups; step_dryrun; } +
-} +
- +
------------------------------------------------------------------- 4. agendamento +
- +
-step_schedule() { +
-step "4/5 · Agendamento" +
-tip "Agende DEPOIS da janela de backup e ANTES do Garbage Collection do datastore." +
-local opt t h m d dname +
-local -a days=("Domingo" "Segunda" "Terça" "Quarta" "Quinta" "Sexta" "Sábado") +
-choose opt "Frequência:" "Diário" "Semanal" "A cada N horas" "Expressão cron personalizada" "Não agendar (só executar manualmente)" +
-case $opt in +
-1) ask t "Horário (HH)" "03:30" v_time; h=$((10#${t%%:})); m=$((10#${t##:})) +
-CRON_EXPR="$m $h * * "; CRON_DESC="Diário às $(printf '%02d:%02d' "$h" "$m")" ;; +
-2) choose d "Dia da semana:" "${days[@]}"; d=$((d-1)); dname=${days[$d]} +
-ask t "Horário (HH)" "03:30" v_time; h=$((10#${t%%:})); m=$((10#${t##*:})) +
-CRON_EXPR="$m $h * * $d"; CRON_DESC="Toda ${dname} às $(printf '%02d:%02d' "$h" "$m")" ;; +
-3) ask h "Intervalo em horas" "6" v_hours +
-CRON_EXPR="15 */$h * * *"; CRON_DESC="A cada $h hora(s), no minuto 15" ;; +
-4) ask CRON_EXPR "Expressão (min hora dia mês dia-semana)" "${CRON_EXPR:-30 3 * * *}" v_cron +
-CRON_DESC="Personalizado" ;; +
-5) CRON_EXPR=""; CRON_DESC="Sem agendamento" ;; +
-esac +
-ok "${CRON_DESC}${CRON_EXPR:+  (${CRON_EXPR})}" +
-} +
- +
------------------------------------------------------------------- 5. instalação +
- +
-write_runner() { +
-mkdir -p "$(dirname "$RUNNER")" +
-cat >"$RUNNER" <<'RUNNER_EOF' +
-#!/usr/bin/env bash +
- +
-pbs-prune — executor de prune individual por VM/CT (gerado pelo pbs-prune-setup) +
- +
-Uso: pbs-prune [--dry-run] [--list] +
- +
-set -uo pipefail +
- +
-CONF="${PBS_PRUNE_CONF:-@CONF_FILE@}" +
-[[ -r $CONF ]] || { echo "Configuração não encontrada: $CONF" >&2; exit 2; } +
- +
-shellcheck source=/dev/null +
- +
-source "$CONF" +
- +
-DRY=() +
-case "${1:-}" in +
--n|--dry-run) DRY=(--dry-run) ;; +
--l|--list)    grep -vE '^\s*(#|$)' "$GROUPS_FILE"; exit 0 ;; +
--h|--help)    echo "Uso: $(basename "$0") [--dry-run] [--list]"; exit 0 ;; +
-"") ;; +
-*) echo "Opção inválida: $1" >&2; exit 2 ;; +
-esac +
- +
-exec 9>"$LOCK_FILE" +
-flock -n 9 || { echo "Já existe uma execução em andamento." >&2; exit 3; } +
- +
-log() { +
-local line; line="$(date '+%F %T') $" +
-printf '%s\n' "$line" >>"$LOG_FILE" +
-[[ -t 1 ]] && printf '%s\n' "$line" +
-logger -t pbs-prune -- "$" 2>/dev/null +
-return 0 +
-} +
- +
-export PBS_REPOSITORY="${AUTH_ID}@${PBS_HOST}:${PBS_PORT}:${DATASTORE}" +
-export PBS_FINGERPRINT="$FINGERPRINT" +
-PBS_PASSWORD="$(<"$SECRET_FILE")" || { log "ERRO: não foi possível ler $SECRET_FILE"; exit 2; } +
-export PBS_PASSWORD +
- +
-NS=(); [[ -n ${NAMESPACE:-} ]] && NS=(--ns "$NAMESPACE") +
- +
-total=0 errors=0 removed_total=0 start=$SECONDS +
-log "===== início${DRY:+ (dry-run)} · ${PBS_REPOSITORY}${NAMESPACE:+ · ns=$NAMESPACE} =====" +
-while read -r group keep _; do +
-[[ -z ${group:-} || $group == #* ]] && continue +
-total=$((total + 1)) +
-if out=$(timeout 600 proxmox-backup-client prune "$group" "${NS[@]}" --keep-last "$keep"  +
-"${DRY[@]}" --output-format json </dev/null 2>&1); then +
-removed=$(grep -oE '"keep":[[:space:]]*false' <<<"$out" | wc -l) +
-removed_total=$((removed_total + removed)) +
-log "OK    ${group}  keep-last=${keep}  removidos=${removed}" +
-else +
-errors=$((errors + 1)) +
-log "ERRO  ${group}  keep-last=${keep}" +
-while IFS= read -r l; do log "      $l"; done <<<"$out" +
-fi +
-done <"$GROUPS_FILE" +
-log "===== fim · grupos=${total} erros=${errors} snapshots removidos=${removed_total} · $((SECONDS - start))s =====" +
- +
-(( errors == 0 )) +
-RUNNER_EOF +
-sed -i "s|@CONF_FILE@|${CONF_FILE}|" "$RUNNER" +
-chmod 750 "$RUNNER" +
-} +
- +
-write_config() { +
-install -d -m 700 "$CONF_DIR" +
-umask 077 +
-{ +
-echo "# Gerado por pbs-prune-setup v${VERSION} em $(date '+%F %T'). Rode o setup novamente para alterar." +
-printf 'PBS_HOST=%q\nPBS_PORT=%q\nDATASTORE=%q\nNAMESPACE=%q\nAUTH_ID=%q\nFINGERPRINT=%q\n'  +
-"$PBS_HOST" "$PBS_PORT" "$DATASTORE" "$NAMESPACE" "$AUTH_ID" "$FINGERPRINT" +
-printf 'CRON_EXPR=%q\nCRON_DESC=%q\n' "$CRON_EXPR" "$CRON_DESC" +
-printf 'SECRET_FILE=%q\nGROUPS_FILE=%q\nLOG_FILE=%q\nLOCK_FILE=%q\n'  +
-"$SECRET_FILE" "$GROUPS_FILE" "$LOG_FILE" "$LOCK_FILE" +
-} >"$CONF_FILE" +
-printf '%s' "$SECRET" >"$SECRET_FILE" +
-chmod 600 "$CONF_FILE" "$SECRET_FILE" +
-umask 022 +
-} +
- +
-write_groups() { +
-local i +
-install -d -m 700 "$CONF_DIR" +
-{ +
-echo "# grupo      keep-last   (vm/ID = QEMU · ct/ID = LXC)" +
-echo "# Pode editar à mão; uma linha por grupo." +
-for i in "${!G_NAMES[@]}"; do printf '%-12s %s\n' "${G_NAMES[$i]}" "${G_KEEP[$i]}"; done +
-} >"$GROUPS_FILE" +
-chmod 600 "$GROUPS_FILE" +
-} +
- +
-write_cron() { +
-if [[ -z $CRON_EXPR ]]; then rm -f "$CRON_FILE"; return 0; fi +
-mkdir -p "$(dirname "$CRON_FILE")" "$(dirname "$LOG_FILE")" "$(dirname "$LOCK_FILE")" +
-cat >"$CRON_FILE" <<EOF +
- +
-pbs-prune — ${CRON_DESC} (gerado por pbs-prune-setup) +
- +
-SHELL=/bin/bash +
-PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +
-${CRON_EXPR} root ${RUNNER} >/dev/null 2>&1 +
-EOF +
-chmod 644 "$CRON_FILE" +
-} +
- +
-write_logrotate() { +
-mkdir -p "$(dirname "$LOGROTATE_FILE")" +
-cat >"$LOGROTATE_FILE" <<EOF +
-${LOG_FILE} { +
-monthly +
-rotate 12 +
-compress +
-missingok +
-notifempty +
-} +
-EOF +
-} +
- +
-step_review_install() { +
-step "5/5 · Revisão e instalação" +
-printf '  %-14s %s\n' "Repositório:" "$(repo)" "Namespace:" "${NAMESPACE:-(raiz)}"  +
-"Fingerprint:" "${FINGERPRINT:0:23}…" "Agendamento:" "${CRON_DESC}${CRON_EXPR:+ (${CRON_EXPR})}" +
-printf '\n'; print_groups; printf '\n' +
-confirm "Instalar com essas configurações?" S || die "Instalação cancelada. Nada foi alterado." +
- +
-write_config;    ok "Configuração     ${CONF_FILE}" +
-write_groups;    ok "Lista de grupos  ${GROUPS_FILE}" +
-ok "Secret           ${SECRET_FILE} (600)" +
-write_runner;    ok "Executor         ${RUNNER}" +
-write_cron;      [[ -n $CRON_EXPR ]] && ok "Agendamento      ${CRON_FILE}" || tip "Sem agendamento instalado." +
-write_logrotate; ok "Logrotate        ${LOGROTATE_FILE}" +
-} +
- +
-finish() { +
-printf '\n' +
-if confirm "Executar o prune agora (valendo)?" N; then +
-"$RUNNER" && ok "Execução concluída." || warn "Execução terminou com erros — veja o log." +
-fi +
-step "Pronto" +
-printf '  %-34s %s\n'  +
-"pbs-prune --dry-run" "simular"  +
-"pbs-prune" "executar agora"  +
-"pbs-prune --list" "ver grupos configurados"  +
-"tail -f ${LOG_FILE}" "acompanhar o log"  +
-"$(basename "$0")" "editar lista / agendamento / desinstalar" +
-tip "Lembrete: o espaço só é liberado quando o Garbage Collection do datastore rodar." +
-printf '\n' +
-} +
- +
------------------------------------------------------------------- carregar / desinstalar +
- +
-load_existing() { +
- +
-shellcheck source=/dev/null +
- +
-source "$CONF_FILE" +
-[[ -r $SECRET_FILE ]] && SECRET=$(<"$SECRET_FILE") +
-G_NAMES=() G_KEEP=() +
-local g k +
-while read -r g k _; do +
-[[ -z ${g:-} || $g == #* ]] && continue +
-G_NAMES+=("$g"); G_KEEP+=("$k") +
-done <"$GROUPS_FILE" +
-} +
- +
-uninstall() { +
-step "Desinstalação" +
-[[ $EUID -eq 0 ]] || die "Execute como root." +
-confirm "Remover executor, configuração, agendamento e logrotate?" N || die "Cancelado." +
-rm -f "$RUNNER" "$CRON_FILE" "$LOGROTATE_FILE" && ok "Executor, cron e logrotate removidos" +
-rm -rf "$CONF_DIR" && ok "Configuração e secret removidos" +
-tip "O log ${LOG_FILE} foi mantido. O token no PBS não foi removido:" +
-tip "proxmox-backup-manager user delete-token usuario@realm <token>" +
-} +
- +
-existing_menu() { +
-local opt +
-load_existing +
-step "Instalação existente encontrada" +
-printf '  %-14s %s\n' "Repositório:" "$(repo)" "Agendamento:" "${CRON_DESC:-—}${CRON_EXPR:+ (${CRON_EXPR})}" +
-printf '\n'; print_groups; printf '\n' +
-choose opt "O que deseja fazer?"  +
-"Editar a lista de VMs/CTs" "Alterar o agendamento" "Reconfigurar tudo"  +
-"Simular agora (dry-run)" "Desinstalar" "Sair" +
-case $opt in +
-1) test_connection || true; step_groups; step_dryrun; write_groups; ok "Lista atualizada: ${GROUPS_FILE}" ;; +
-2) step_schedule; write_config; write_cron; ok "Agendamento atualizado" ;; +
-3) full_flow; return ;; +
-4) "$RUNNER" --dry-run ;; +
-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 "${1:-}" in +
--h|--help)    sed -n '2,32p' "$0" | sed 's/^# {0,1}//'; exit 0 ;; +
--v|--version) echo "pbs-prune-setup $VERSION"; exit 0 ;; +
---uninstall)  banner; uninstall; exit 0 ;; +
-"") ;; +
-*) die "Opção inválida: $1 (use --help)" ;; +
-esac +
- +
-banner +
-preflight +
-if [[ -f $CONF_FILE && -f $GROUPS_FILE ]]; then existing_menu; else full_flow; fi+
 </code> </code>
  
-===== Referência =====+A desinstalação remove o executor, configurações, cron e logrotate.
  
-[[https://wiki.projetoempresalivre.com.br/doku.php?id=proxmox|PBS Prune Individual na Wiki]] +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, recomenda-se manter: +
- +
-esta página para documentação; +
- +
-o arquivo <code>pbs-prune-setup.sh</code> como download; +
- +
-o código completo acima como referência. +
- +
-===== Comandos rápidos ===== +
- +
-<code bash> +
-# Assistente +
-./pbs-prune-setup.sh +
- +
-Ajuda +
- +
-./pbs-prune-setup.sh --help +
- +
-Dry-run +
- +
-pbs-prune --dry-run +
- +
-Executar +
- +
-pbs-prune +
- +
-Listar grupos +
- +
-pbs-prune --list +
- +
-Log +
- +
-tail -f /var/log/pbs-prune.log +
- +
-Desinstalar +
- +
-./pbs-prune-setup.sh --uninstall +
-</code>+
proxmox/prune-individual.1790698386.txt.gz · Última modificação: por admin

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki