Verificado no butterfly/GLPI 11.0.8: CheckPluginsStates desativa o plugin quando a version do setup.php muda; sintoma engana como falha de licença. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
460 lines
15 KiB
Markdown
460 lines
15 KiB
Markdown
---
|
||
id: KB-PLUGIN-013
|
||
title: Runbook — Publicação de plugin no Mindplace (procedimento padrão)
|
||
domain: plugin-dev
|
||
tags:
|
||
- mindplace
|
||
- runbook
|
||
- publish
|
||
- license
|
||
- forgejo
|
||
- release
|
||
- kill-switch
|
||
- workflow
|
||
- automation
|
||
- script
|
||
status: active
|
||
severity: high
|
||
created_at: 2026-05-09
|
||
updated_at: 2026-05-21
|
||
applies_to:
|
||
- GLPI 11.0.x
|
||
- Mindplace 1.0+
|
||
- Forgejo (servicedesk.mindtek.com.br/git)
|
||
---
|
||
|
||
# Runbook — Publicação de plugin no Mindplace
|
||
|
||
Procedimento padrão completo para publicar um novo plugin proprietário no Mindplace (marketplace privado da Mindtek). Cobre desde a integração do kill switch de licença no código até a entrega do serial ao cliente.
|
||
|
||
> **⚡ TL;DR — Publicação automatizada:** As Fases 2, 3 e 4 (Forgejo + ZIP + Release + plugins.json) estão **100% automatizadas** pelo script `bin/mindplace-release.sh`. Leia a seção **Publicação Automatizada** antes de executar os passos manuais.
|
||
|
||
## Pré-requisitos
|
||
|
||
- [ ] Plugin está em estado funcional e testado localmente
|
||
- [ ] Acesso de admin ao Forgejo (`servicedesk.mindtek.com.br/git`)
|
||
- [ ] Acesso ao GLPI de produção da Mindtek (`servicedesk.mindtek.com.br`) com perfil que crie licenças de software
|
||
- [ ] Plugin já tem `setup.php` válido com `plugin_version_<name>()` retornando metadata
|
||
- [ ] `version` no `setup.php` segue SemVer (ex: `1.0.0`)
|
||
- [ ] **Para publicação automatizada:** `FORGEJO_TOKEN` disponível (PAT com escopo `write:repository`)
|
||
|
||
---
|
||
|
||
## ⚡ Publicação Automatizada (Fases 2–5)
|
||
|
||
As fases de publicação no Forgejo e catálogo são cobertas pelo script `bin/mindplace-release.sh` localizado na raiz do projeto GLPI11.
|
||
|
||
### Pré-requisito único: Personal Access Token (PAT)
|
||
|
||
Gere em: `https://servicedesk.mindtek.com.br/git/user/settings/applications`
|
||
- Nome sugerido: `antigravity-publisher`
|
||
- Escopo necessário: `write:repository`
|
||
|
||
Exporte como variável de ambiente (ou edite a variável `FORGEJO_TOKEN` diretamente no script):
|
||
```bash
|
||
export FORGEJO_TOKEN="seu_token_aqui"
|
||
```
|
||
|
||
### Uso
|
||
|
||
```bash
|
||
# Na raiz do projeto GLPI11:
|
||
./bin/mindplace-release.sh /home/glpi/glpi_dev/plugins/<nome-do-plugin>
|
||
```
|
||
|
||
### O que o script faz automaticamente
|
||
|
||
| Fase | Ação |
|
||
|---|---|
|
||
| **1** | Cria repositório no Forgejo (se não existir) |
|
||
| **2** | `git init` + commit + push para o Forgejo |
|
||
| **3** | Gera ZIP limpo do plugin (exclui `.git`, `vendor`, `node_modules`, `*.DS_Store`) |
|
||
| **4** | Cria tag `v{versão}` + Release no Forgejo + faz upload do ZIP como asset |
|
||
| **5** | Atualiza `plugins.json` no repo `mindplace` com a entrada do novo plugin |
|
||
|
||
### Comportamento idempotente
|
||
|
||
O script é seguro para rodar múltiplas vezes:
|
||
- ✅ Repositório já existe → pula criação
|
||
- ✅ Release já existe → pula criação
|
||
- ✅ Plugin já no `plugins.json` → pula atualização
|
||
- ✅ Sempre faz push do código mais recente
|
||
|
||
### Fluxo de nova versão
|
||
|
||
```bash
|
||
# 1. Bump da versão no setup.php
|
||
# 'version' => '1.0.0' → '1.1.0'
|
||
|
||
# 2. Rodar o script — ele detecta a nova versão e cria nova Release
|
||
./bin/mindplace-release.sh /home/glpi/glpi_dev/plugins/meu-plugin
|
||
```
|
||
|
||
### Notas importantes
|
||
|
||
- O script lê a versão diretamente da constante `PLUGIN_XXXVERSION` no `setup.php`
|
||
- Arquivos de teste (`ajax/test*.php`, `front/test*.php`) devem estar no `.gitignore` do plugin para não serem publicados
|
||
- O `.gitignore` padrão para plugins está documentado abaixo
|
||
- O ZIP do asset pode acumular múltiplas versões na mesma Release se o script for rodado mais de uma vez sem bump de versão (não causa erro, apenas duplicata)
|
||
|
||
### .gitignore padrão para plugins
|
||
|
||
```gitignore
|
||
# 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
|
||
```
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## Fase 1 — Kill switch de licença no código
|
||
|
||
Cada plugin do Mindplace **precisa** validar a licença antes de fazer qualquer coisa. O padrão do "Kill Switch" funciona em duas camadas:
|
||
|
||
1. `plugin_<name>_check_config()` impede ativação se licença inválida
|
||
2. `plugin_init_<name>()` aborta silenciosamente se licença inválida (proteção em profundidade)
|
||
|
||
### Template do `setup.php`
|
||
|
||
```php
|
||
<?php
|
||
|
||
use Glpi\Plugin\Hooks;
|
||
|
||
function plugin_version_<name>(): array
|
||
{
|
||
return [
|
||
'name' => 'Plugin Name',
|
||
'version' => '1.0.0',
|
||
'author' => 'Mindtek Tecnologia',
|
||
'license' => 'GPLv2+',
|
||
'requirements' => ['glpi' => ['min' => '11.0', 'max' => '12.0']],
|
||
];
|
||
}
|
||
|
||
function plugin_<name>_check_prerequisites(): bool
|
||
{
|
||
if (version_compare(GLPI_VERSION, '11.0', '<')) {
|
||
echo 'This plugin requires GLPI >= 11.0';
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Carrega a classe License do Mindplace caso o autoload ainda não tenha rodado.
|
||
* Necessário porque o plugin pode inicializar antes do mindplace.
|
||
*/
|
||
function plugin_<name>_load_license(): void
|
||
{
|
||
if (class_exists('\GlpiPlugin\Mindplace\License')) {
|
||
return;
|
||
}
|
||
foreach ([
|
||
GLPI_ROOT . '/plugins/mindplace/src/License.php',
|
||
GLPI_ROOT . '/marketplace/mindplace/src/License.php',
|
||
] as $path) {
|
||
if (file_exists($path)) {
|
||
include_once $path;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
function plugin_<name>_check_config($verbose = false): bool
|
||
{
|
||
plugin_<name>_load_license();
|
||
|
||
if (!class_exists('\GlpiPlugin\Mindplace\License') || !\GlpiPlugin\Mindplace\License::isValid()) {
|
||
if ($verbose && !isCommandLine()) {
|
||
echo "<div class='alert alert-danger'>Licença Mindtek inativa ou Mind Place ausente. O plugin foi desativado por segurança.</div>";
|
||
}
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
function plugin_init_<name>(): void
|
||
{
|
||
global $PLUGIN_HOOKS;
|
||
|
||
$PLUGIN_HOOKS['csrf_compliant']['<name>'] = true;
|
||
|
||
// ──────────────────────────────────────────────────────────────────
|
||
// KILL SWITCH — abort silently if license invalid
|
||
// ──────────────────────────────────────────────────────────────────
|
||
plugin_<name>_load_license();
|
||
|
||
if (!class_exists('\GlpiPlugin\Mindplace\License') || !\GlpiPlugin\Mindplace\License::isValid()) {
|
||
return;
|
||
}
|
||
|
||
// ─── A partir daqui, registrar todos os hooks/classes do plugin ───
|
||
// $PLUGIN_HOOKS[Hooks::ADD_CSS]['<name>'] = [...];
|
||
// Plugin::registerClass(...);
|
||
}
|
||
```
|
||
|
||
### Comportamento do Kill Switch
|
||
|
||
| Cenário | Resultado |
|
||
|---|---|
|
||
| Mindplace não instalado | `check_config` retorna `false` → plugin não pode ativar |
|
||
| Mindplace instalado mas licença inválida/expirada | mesmo do anterior |
|
||
| Plugin já estava ativo e licença expirou | `init` aborta sem registrar hooks → GLPI ignora o plugin como se não existisse |
|
||
| Tudo OK | Plugin funciona normalmente |
|
||
|
||
### Checklist da Fase 1
|
||
|
||
- [ ] `plugin_<name>_load_license()` adicionada
|
||
- [ ] `plugin_<name>_check_config()` valida licença
|
||
- [ ] `plugin_init_<name>()` aborta se inválida
|
||
- [ ] Testado: desativar mindplace → tentar ativar plugin → deve falhar com aviso
|
||
- [ ] Testado: licença válida → plugin ativa e funciona
|
||
|
||
---
|
||
|
||
## Fase 2 — Publicação no Forgejo
|
||
|
||
Forgejo é onde o código-fonte e os releases ficam. URL base: `https://servicedesk.mindtek.com.br/git/`.
|
||
|
||
### 2.1 — Criar repositório
|
||
|
||
1. Acesse o Forgejo logado como admin (`rodolpho.lopes`)
|
||
2. **+** → **New Repository**
|
||
- Owner: `rodolpho.lopes`
|
||
- Repository name: nome do plugin (ex: `tilesections`)
|
||
- Visibility: **Public**
|
||
- Initialize repository: ✅ (com README mínimo — releases exigem ao menos um commit)
|
||
3. **Create Repository**
|
||
|
||
### 2.2 — Push do código-fonte
|
||
|
||
```bash
|
||
cd /caminho/local/do/plugin
|
||
|
||
# Se ainda não é um repo git
|
||
git init
|
||
git branch -M main
|
||
|
||
# Adicionar Forgejo como remote
|
||
git remote add forgejo https://servicedesk.mindtek.com.br/git/rodolpho.lopes/<plugin>.git
|
||
|
||
# Commit + push
|
||
git add .
|
||
git commit -m "Initial commit"
|
||
git push forgejo main
|
||
```
|
||
|
||
Vai pedir credenciais do Forgejo na primeira vez.
|
||
|
||
### 2.3 — Criar release com ZIP
|
||
|
||
1. No Forgejo, no repo do plugin: **Releases** → **New Release**
|
||
2. **Tag name**: `v1.0.0` (deve bater com `version` em `setup.php`)
|
||
3. **Title**: `v1.0.0`
|
||
4. **Description**: changelog resumido
|
||
5. Em **Attachments**, fazer upload do ZIP gerado:
|
||
```bash
|
||
cd /caminho/local/do/plugin
|
||
zip -r <plugin>-1.0.0.zip . \
|
||
--exclude "*.git*" \
|
||
--exclude "node_modules/*" \
|
||
--exclude "vendor/*" \
|
||
--exclude "*.DS_Store"
|
||
```
|
||
6. **Publish Release**
|
||
|
||
### Checklist da Fase 2
|
||
|
||
- [ ] Repositório criado público no Forgejo
|
||
- [ ] Código-fonte push para `main`
|
||
- [ ] Logo `logo.png` na raiz do plugin (será exibido no Mindplace)
|
||
- [ ] Tag `v<versão>` criada
|
||
- [ ] ZIP do plugin anexado como asset da release
|
||
- [ ] Versão do ZIP bate com `setup.php`
|
||
|
||
---
|
||
|
||
## Fase 3 — Cadastro no catálogo Mindplace
|
||
|
||
O Mindplace lê `plugins.json` do repositório `rodolpho.lopes/mindplace` no Forgejo. Cada plugin é uma entrada nesse array.
|
||
|
||
### 3.1 — Editar `plugins.json`
|
||
|
||
Acesse: `https://servicedesk.mindtek.com.br/git/rodolpho.lopes/mindplace`
|
||
|
||
Edite o arquivo `plugins.json` (ícone do lápis na visualização do arquivo) e adicione a nova entrada:
|
||
|
||
```json
|
||
{
|
||
"key": "<plugin>",
|
||
"repo": "rodolpho.lopes/<plugin>",
|
||
"name": "Nome Bonito do Plugin",
|
||
"description": "Descrição curta de uma frase.",
|
||
"authors": [{"name": "Rodolpho O Lopes"}],
|
||
"license": "GPL-2.0-or-later",
|
||
"logo_url": null,
|
||
"homepage_url": "https://servicedesk.mindtek.com.br/git/rodolpho.lopes/<plugin>",
|
||
"issues_url": "https://servicedesk.mindtek.com.br/git/rodolpho.lopes/<plugin>/issues",
|
||
"note": 5
|
||
}
|
||
```
|
||
|
||
**Campos importantes:**
|
||
- `key`: igual ao nome do diretório do plugin (sem espaços, lowercase)
|
||
- `repo`: caminho `<owner>/<repo>` no Forgejo
|
||
- `logo_url: null`: faz o Mindplace buscar `logo.png` da raiz do repo automaticamente
|
||
|
||
Commit direto pelo Forgejo (Commit Changes na interface).
|
||
|
||
### 3.2 — Validação
|
||
|
||
No GLPI do cliente (com mindplace instalado e licença ativa), abra a aba **Mind Place**. O novo plugin deve aparecer no grid em até 1 hora (cache TTL). Para forçar refresh imediato, clica no botão de refresh do Mindplace.
|
||
|
||
### Checklist da Fase 3
|
||
|
||
- [ ] Entrada adicionada em `plugins.json`
|
||
- [ ] Commit feito
|
||
- [ ] Plugin aparece no grid do Mindplace
|
||
- [ ] Logo carrega corretamente (se `logo.png` existe na raiz do repo)
|
||
- [ ] Botão **Download/Install** funciona
|
||
|
||
---
|
||
|
||
## Fase 4 — Cadastro de licença no GLPI de produção
|
||
|
||
Cada cliente recebe um serial único. O serial fica em `glpi_softwarelicenses` do GLPI da Mindtek e é validado pelo `mindtek_validador.php`.
|
||
|
||
### 4.1 — Criar entrada de licença
|
||
|
||
No GLPI de produção (`servicedesk.mindtek.com.br`):
|
||
|
||
1. **Ativos → Licenças de Software → +**
|
||
2. Preencher:
|
||
- **Nome**: `Licença <Plugin> — <Cliente>` (ex: `Licença Tilesections — Empresa Acme`)
|
||
- **Software**: associar ao software do plugin (criar se não existir)
|
||
- **Número de série**: gerar hash hex aleatório de 32 caracteres
|
||
```bash
|
||
openssl rand -hex 16 | tr 'a-z' 'A-Z'
|
||
# exemplo: 7C9F1E3A4B5D6F8E2C0A1B3D4E5F6071
|
||
```
|
||
- **Tipo**: livre (sugestão: `Mindplace Plugin`)
|
||
- **Validade**: opcional, conforme contrato
|
||
3. Salvar
|
||
|
||
### 4.2 — Entregar o serial ao cliente
|
||
|
||
Mensagem padrão para o cliente:
|
||
|
||
> Olá,
|
||
>
|
||
> Sua licença do plugin **\<Plugin\>** está ativa. Use o serial abaixo para ativar no Mindplace do seu GLPI:
|
||
>
|
||
> `7C9F1E3A4B5D6F8E2C0A1B3D4E5F6071`
|
||
>
|
||
> **Como ativar:**
|
||
> 1. No GLPI, acesse **Configurar → Plugins → Mind Place** (ícone de chave)
|
||
> 2. Cole o serial no campo **API Token** e salve
|
||
> 3. Aguarde o status mudar para "Ativo" (alguns segundos)
|
||
> 4. Pronto, o plugin pode ser ativado normalmente
|
||
|
||
### Checklist da Fase 4
|
||
|
||
- [ ] Software cadastrado em `Ativos → Software` (se primeira vez)
|
||
- [ ] Licença criada com serial único de 32 chars hex
|
||
- [ ] Serial enviado ao cliente
|
||
- [ ] Cliente confirmou ativação no Mindplace dele
|
||
|
||
---
|
||
|
||
## Fase 5 — Manutenção contínua
|
||
|
||
### Lançar nova versão do plugin
|
||
|
||
1. Atualizar `version` no `setup.php` (ex: `1.0.0` → `1.1.0`)
|
||
2. Commit + push para `main` no Forgejo
|
||
3. Criar nova release no Forgejo com tag `v1.1.0` + novo ZIP
|
||
4. **Não** atualiza `plugins.json` — o Mindplace pega automaticamente a release mais recente
|
||
5. Clientes verão o botão **Atualizar** no Mindplace dentro de 1 hora (cache TTL)
|
||
|
||
> **Gotcha (verificado no butterfly, GLPI 11.0.8, 2026-07-03):** ao mudar a
|
||
> `version` do setup.php, o `CheckPluginsStates` do boot **desativa o plugin
|
||
> na hora** ("version changed... has to be launched") — status vira
|
||
> "Para atualizar", o setup.php deixa de ser carregado e os hooks somem
|
||
> (funções do plugin ficam indefinidas). No ambiente de dev, rodar
|
||
> `bin/console glpi:plugin:install <key> -u glpi && bin/console glpi:plugin:activate <key>`
|
||
> logo após o bump. Sintoma enganoso: parece que o kill switch/licença
|
||
> derrubou o plugin, mas é só o fluxo de update pendente.
|
||
|
||
### Revogar licença de um cliente
|
||
|
||
1. No GLPI de produção: **Ativos → Licenças de Software** → encontrar o serial
|
||
2. Marcar `is_deleted = 1` (mover para a lixeira) **OU** deletar permanentemente
|
||
3. No próximo cron (1x por dia) ou na próxima vez que o cliente salvar a config do mindplace, `License::cronCheck()` vai retornar `expired`
|
||
4. Plugin do cliente desativa automaticamente
|
||
|
||
### Verificar status de licença de um cliente
|
||
|
||
```sql
|
||
-- No GLPI de produção
|
||
SELECT id, name, serial, is_deleted, completename
|
||
FROM glpi_softwarelicenses
|
||
WHERE serial = 'SERIAL_DO_CLIENTE';
|
||
```
|
||
|
||
Ou consultar o validador diretamente:
|
||
```bash
|
||
curl "https://servicedesk.mindtek.com.br/mindtek_validador.php?token=SERIAL_DO_CLIENTE"
|
||
# {"status":"active"} ou {"status":"expired"}
|
||
```
|
||
|
||
---
|
||
|
||
## Referências cruzadas
|
||
|
||
- [KB-PLUGIN-011](KB-PLUGIN-011-mindplace-release-zip-procedure.md) — procedimento de ZIP via GitHub (legado, antes da migração para Forgejo)
|
||
- [KB-PLUGIN-012](KB-PLUGIN-012-helpdesk-tiles-extension-patterns.md) — padrões para estender o sistema de tiles do Helpdesk
|
||
|
||
## Script de automação
|
||
|
||
| Arquivo | Localização | Função |
|
||
|---|---|---|
|
||
| `mindplace-release.sh` | `bin/mindplace-release.sh` na raiz do projeto GLPI11 | Script Bash que automatiza as Fases 2–5 completas via API do Forgejo |
|
||
|
||
## Arquivos críticos
|
||
|
||
| Arquivo | Localização | Função |
|
||
|---|---|---|
|
||
| `mindtek_validador.php` | `/var/www/html/glpi/public/` no servidor de produção | Endpoint que valida serials contra `glpi_softwarelicenses` |
|
||
| `License.php` | `plugins/mindplace/src/` no GLPI do cliente | Cron + `isValid()` que outros plugins consultam |
|
||
| `plugins.json` | repo `rodolpho.lopes/mindplace` no Forgejo | Catálogo do Mindplace |
|
||
| `secrets.php` | NÃO USAR no setup atual com Forgejo (era para PAT do GitHub) | — |
|
||
|
||
## Notas importantes
|
||
|
||
- **Forgejo não tem rate limit** — não precisa de token de autenticação. Repositórios são públicos
|
||
- **`License::isValid()` é zero-latency** — lê cache no banco, não bate em API. Pode ser chamado em cada page load sem custo
|
||
- **Cron de validação roda 1x/dia** — se o cliente quer validar agora, salvar a config do mindplace força o check imediato
|
||
- **Sem licença, sem prejuízo** — o kill switch é silencioso; o GLPI continua funcional sem o plugin, sem mensagens de erro
|