knowledge-base/records/plugin-dev/KB-PLUGIN-018-plugin-release-zip-wrapper-convention.md
Gemini d49d5bbaa6 KB-PLUGIN-018: ZIP via python zipfile precisa de permissões explícitas
Entradas criadas com ZipInfo() nascem com external_attr=0; o unzip cria
os diretórios como drw------- e o webserver não consegue atravessá-los —
plugin some da lista ou dá 'Unable to load plugin information' (Plugin.php
linha 882). Inclui forma correta, sintoma do Jan 1 1980, validação do ZIP
e comando de correção no servidor. Diagnosticado no build VIP do butterfly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 14:15:25 -03:00

6.4 KiB

id title domain tags status severity created_at updated_at applies_to related
KB-PLUGIN-018 Convenção de wrapper directory na geração de ZIP de release de plugin (script mindplace-release.sh) plugin-dev
mindplace
forgejo
release
zip
wrapper
mindplace-release-sh
active high 2026-05-21 2026-05-21
Qualquer plugin publicado via bin/mindplace-release.sh
Plugins distribuídos pelo catálogo Mindplace
KB-PLUGIN-013
KB-PLUGIN-017

KB-PLUGIN-018 — Convenção de wrapper directory para ZIPs de release

Contexto

ZIPs de release de plugin GLPI distribuídos via Mindplace devem ter um wrapper directory consistente no topo (uma pasta com o nome do plugin envolvendo todos os arquivos). Sem isso, o extrator do Mindplace podia mutilar paths em versões anteriores ao fix descrito em KB-PLUGIN-017.

Mesmo após o fix do detector, manter o wrapper continua sendo a forma "correta" e mais robusta: dá ao extrator um prefix bem-definido para remover, e remove ambiguidade na inspeção do ZIP.

Estrutura esperada

mcprotocol-1.0.3.zip
└── mcprotocol/            ← wrapper directory (nome = key do plugin = basename do diretório)
    ├── setup.php
    ├── hook.php
    ├── ajax/
    │   └── mcp.php
    ├── src/
    │   ├── Boot.php
    │   └── Server.php
    └── public/
        └── logo.png

Verificação rápida:

unzip -l <plugin>-<ver>.zip | head
# Primeiro entry DEVE ser "<plugin>/"
# Todos os demais entries DEVEM começar com "<plugin>/"

Fluxo oficial (automatizado)

Use o script bin/mindplace-release.sh documentado em KB-PLUGIN-013. Desde 2026-05-21, a Fase 3 do script gera ZIPs com wrapper directory automaticamente — rodando zip do diretório pai do plugin:

# Trecho relevante de bin/mindplace-release.sh (Fase 3)
PARENT_DIR="$(dirname "$PLUGIN_DIR")"
PLUGIN_BASENAME="$(basename "$PLUGIN_DIR")"
cd "$PARENT_DIR"
zip -r "$ZIP_PATH" "$PLUGIN_BASENAME" \
  --exclude "$PLUGIN_BASENAME/.git*" \
  --exclude "$PLUGIN_BASENAME/node_modules/*" \
  --exclude "$PLUGIN_BASENAME/vendor/*" \
  --exclude "*.DS_Store" \
  --exclude "*.zip" \
  -q

# Sanity check: aborta se wrapper não aparecer no topo
FIRST_ENTRY=$(unzip -Z1 "$ZIP_PATH" | head -1)
if [ "$FIRST_ENTRY" != "$PLUGIN_BASENAME/" ]; then
  err "ZIP gerado sem wrapper directory esperado..."
fi

O sanity check no fim falha rápido se algum dia o ZIP voltar a sair sem wrapper.

Geração manual (fallback)

Se precisar gerar localmente fora do script:

# A partir do diretório PAI da pasta do plugin
cd /home/glpi/glpi_dev/plugins/
zip -r mcprotocol-1.0.3.zip mcprotocol/ \
  -x "mcprotocol/.git*" \
  -x "*.zip" \
  -x "mcprotocol/node_modules/*" \
  -x "mcprotocol/vendor/*"

Não fazer cd plugin && zip -r ...zip . — esse padrão produz ZIP sem wrapper e era o bug histórico do script.

Gotcha — ZIP gerado com python zipfile precisa de permissões explícitas

Quando o zip CLI não está disponível e o pacote é montado com zipfile (cenário comum neste ambiente, ver redmine-plugin-project), as entradas criadas com zipfile.ZipInfo(nome) nascem com external_attr = 0 — sem bits de permissão e sem a flag MS-DOS de diretório. O unzip então cria as pastas como drw------- (sem bit de execução), e o resultado no servidor é:

  • o usuário do webserver não consegue atravessar o diretório do plugin;
  • Plugin::isLoadable() não enxerga o setup.php;
  • o plugin some da lista sem nenhum erro, ou aparece Unable to load plugin "<key>" information. at Plugin.php line 882 quando já existe registro dele no banco.

Diagnosticado em 2026-07-31 no build VIP do butterfly instalado no HML de um cliente (drw------- root root na pasta do plugin).

Forma correta

DIR_ATTR  = (0o40755 << 16) | 0x10   # drwxr-xr-x + flag de diretório
FILE_ATTR = (0o100644 << 16)         # -rw-r--r--

zi = zipfile.ZipInfo(arcname, date_time=(2026, 7, 31, 12, 0, 0))
zi.external_attr = DIR_ATTR          # entradas de diretório
z.writestr(zi, '')

zi = zipfile.ZipInfo.from_file(src, arcname)
zi.external_attr = FILE_ATTR         # arquivos
zi.compress_type = zipfile.ZIP_DEFLATED

Sintoma acessório que denuncia o problema: data Jan 1 1980 no diretório extraído (timestamp default de ZipInfo sem date_time).

Validação obrigatória do ZIP

# extrair em pasta temporária e conferir os modos
unzip -q <plugin>-<ver>.zip -d /tmp/ziptest && ls -ld /tmp/ziptest/<plugin>
# esperado: drwxr-xr-x   (NUNCA drw-------)

Correção no servidor quando o ZIP defeituoso já foi extraído:

chown -R www-data:www-data <glpi>/plugins/<key>
find <glpi>/plugins/<key> -type d -exec chmod 755 {} \;
find <glpi>/plugins/<key> -type f -exec chmod 644 {} \;

.gitignore padrão do plugin

Para que o ZIP não inclua arquivos sensíveis ou de teste, garantir .gitignore na raiz do plugin:

# Dependências
vendor/
node_modules/

# macOS
.DS_Store
**/.DS_Store

# IDE
.idea/
.vscode/

# Logs
*.log

# Build artifacts
*.zip

# Arquivos de teste (não publicar)
ajax/test*.php
ajax/mcp_test.php
front/test*.php

O script mindplace-release.sh excludes .git* por padrão; o .gitignore em si pode ser commitado e publicado sem problema.

Erros comuns

Sintoma Causa Fix
Após instalar via Mindplace, arquivos com nomes mutilados (.php, p.php, pastas ic/, t/) ZIP gerado sem wrapper + Mindplace com detector buggy Atualizar Mindplace ([KB-PLUGIN-017]) e regerar ZIP com wrapper
Após instalar, pasta mcprotocol/mcprotocol/ aninhada ZIP com wrapper mas extrator versão muito antiga, ou wrapper com nome diferente do key Conferir que o basename do diretório bate com o key em plugins.json
Release não aparece no Mindplace Release sem asset .zip no Forgejo Verificar que a Fase 4 do script subiu o asset; tem rate limit ou erro?

Veja também

  • KB-PLUGIN-011 — Procedimento de release legado via GitHub (deprecated)
  • KB-PLUGIN-013 — Runbook completo de publicação no Mindplace via Forgejo
  • KB-PLUGIN-017 — Bug do detector de wrapper (motivação histórica)