# Instalação limpa do hub-mkp-core

O Core é o primeiro projeto da instalação. Conclua este arquivo até a criação
da instância e do usuário de suporte; depois instale o App e, por último, o
Worker.

## Ordem completa dos projetos

1. Neste repositório: instalar o Core, criar seu banco, executar migrations e
   seed, criar o suporte e registrar a instância do App.
2. Mudar para `/var/www/hub-mkp-app`: instalar dependências, `.env`, imagens e
   VirtualHost. Não criar banco de tenant manualmente.
3. Voltar ao Core: executar `bin/create_worker.php` e guardar o token exibido
   uma única vez.
4. Mudar para `/var/www/hub-mkp-worker`: configurar o token, o acesso
   administrativo ao MariaDB e validar Core e runners do App.
5. Revisar `config/worker_tasks.php` e somente então instalar o crontab.
6. Criar o primeiro tenant pelo cadastro oficial do Core. O Worker executará,
   nessa ordem, `CRIAR_DATABASE_CLIENTE`, `RODAR_MIGRATIONS_CLIENTE` e
   `FINALIZAR_TENANT`.
7. Validar o login entregue pelo Core e o dashboard no App.

O App vem antes da ativação do Worker porque os runners de migration e
finalização precisam existir no path configurado por `HUB_MKP_PATH`.

## 1. Requisitos do servidor

Referência validada: Debian/Ubuntu, Apache 2.4, PHP CLI/FPM ou módulo Apache
7.4 e MariaDB 10.11. O código é compatível com PHP 7.4 ou superior e usa
somente `utf8mb4_unicode_ci`, sem collation exclusiva do MySQL 8.

```bash
sudo apt-get update
sudo apt-get install apache2 mariadb-client composer unzip \
  php7.4 php7.4-cli php7.4-common php7.4-curl php7.4-json \
  php7.4-mbstring php7.4-mysql php7.4-openssl
sudo a2enmod rewrite
php7.4 -v
php7.4 -m | grep -E 'curl|json|mbstring|openssl|PDO|pdo_mysql'
php7.4 "$(command -v composer)" --version
```

`ext-pdo`, `ext-pdo_mysql`, `ext-json` e `ext-openssl` são obrigatórias.
`ext-curl` é usada por CAPTCHA e integrações; `mbstring` preserva validações de
texto. Sodium é recomendada para envelopes `enc:v1:`; o código possui fallback
AES-256-GCM via OpenSSL.

## 2. Código, dependências e permissões

```bash
cd /var/www/hub-mkp-core
php7.4 "$(command -v composer)" \
  install --no-dev --prefer-dist --optimize-autoloader
install -d -m 0770 storage/logs
sudo chown -R <usuario-deploy>:www-data /var/www/hub-mkp-core
sudo find /var/www/hub-mkp-core -type d -exec chmod 0750 {} \;
sudo find /var/www/hub-mkp-core -type f -exec chmod 0640 {} \;
sudo chmod 0770 storage storage/logs
```

O usuário do Apache precisa ler o projeto e escrever apenas em
`storage/logs`. O `.env` deve permanecer fora do DocumentRoot e com `0640` ou
mais restritivo.

## 3. Criar o banco do Core

Entre no MariaDB com uma conta administrativa somente para este passo:

```sql
CREATE DATABASE `hub_mkp_core`
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

CREATE USER 'hub_mkp_core_app'@'<host-core>'
  IDENTIFIED BY '<senha-forte-e-exclusiva>';

GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX, DROP, REFERENCES
  ON `hub_mkp_core`.* TO 'hub_mkp_core_app'@'<host-core>';
```

Esses são os privilégios usados pela aplicação e pelas migrations atuais. A
conta normal do Core não recebe privilégios globais, `CREATE USER` nem acesso
a bancos de tenants. Se a política separar execução e runtime, use a conta
acima para migrations e uma segunda conta somente com
`SELECT, INSERT, UPDATE, DELETE` para o processo web.

Valide:

```sql
SHOW CREATE DATABASE `hub_mkp_core`;
SHOW GRANTS FOR 'hub_mkp_core_app'@'<host-core>';
```

O resultado do primeiro comando deve conter `utf8mb4` e
`utf8mb4_unicode_ci`.

## 4. Criar e proteger o `.env`

```bash
cd /var/www/hub-mkp-core
cp .env.example .env
chmod 0600 .env

1) Gere uma chave Base64 exclusiva para APP_KEY:

   php7.4 -r 'echo base64_encode(random_bytes(32)), PHP_EOL;'

2) Execute novamente o mesmo comando para gerar uma chave diferente para HUB_ENCRYPTION_KEY_BASE64:

   php7.4 -r 'echo base64_encode(random_bytes(32)), PHP_EOL;'

Cada saída representa 32 bytes aleatórios codificados em Base64 e normalmente
possui 44 caracteres. Não reutilize o mesmo valor nas duas variáveis.

3) Gere um token hexadecimal exclusivo para HUB_INTERNAL_API_TOKEN:

   php7.4 -r 'echo bin2hex(random_bytes(32)), PHP_EOL;'

4) Execute novamente para gerar outro valor, diferente, para HUB_OAUTH_BROKER_SECRET:

   php7.4 -r 'echo bin2hex(random_bytes(32)), PHP_EOL;'

Preencha o .env desta forma:

APP_KEY=valor_base64_da_primeira_execucao
HUB_ENCRYPTION_KEY_BASE64=valor_base64_da_segunda_execucao
HUB_INTERNAL_API_TOKEN=valor_hexadecimal_da_terceira_execucao
HUB_OAUTH_BROKER_SECRET=valor_hexadecimal_da_quarta_execucao
```

### Variáveis básicas, banco e instância

| Variável | Uso | Exemplo fictício | Relação |
|---|---|---|---|
| `APP_ENV` | Obrigatória; ambiente | `production` | Core |
| `APP_DEBUG` | Obrigatória; `false` fora do desenvolvimento | `false` | Core |
| `APP_URL` | Obrigatória; URL pública do Core | `https://core.exemplo.com` | Apache/DNS |
| `APP_TIMEZONE` | Opcional, padrão `America/Sao_Paulo` | `America/Sao_Paulo` | PHP |
| `DB_HOST` | Obrigatória | `10.0.0.10` | MariaDB Core |
| `DB_PORT` | Obrigatória | `3306` | MariaDB Core |
| `DB_DATABASE` | Obrigatória | `hub_mkp_core` | MariaDB Core |
| `DB_USERNAME` | Obrigatória | `hub_mkp_core_app` | MariaDB Core |
| `DB_PASSWORD` | Obrigatória e secreta | `<senha-forte>` | MariaDB Core |
| `APP_KEY` | Obrigatória; Base64 de 32 bytes | `<base64-32-bytes>` | hashes e segredos de tenants |
| `HUB_ENCRYPTION_KEY_BASE64` | Obrigatória quando houver credenciais `enc:v1:`; Base64 de 32 bytes | `<base64-32-bytes>` | Core/App |
| `CORE_INSTANCE_CODE` | Obrigatória; código estável | `hub-prod-01` | Core/Worker/App |
| `CORE_INSTANCE_NAME` | Obrigatória | `Hub Produção 01` | `hub_instances` |
| `CORE_INSTANCE_ENVIRONMENT` | Obrigatória | `PRODUCTION` | `hub_instances` |
| `CORE_INSTANCE_LOGIN_URL` | Obrigatória; endpoint `/admin/core-login` do App | `https://app.exemplo.com` | App |
| `CORE_INSTANCE_BASE_URL` | Obrigatória; URL do App | `https://app.exemplo.com` | App |

### Autenticação, comunicação interna e cadastro

| Variável | Uso | Exemplo fictício | Relação |
|---|---|---|---|
| `HUB_SITE_URL` | Obrigatória para redirects e e-mails | `https://hub.exemplo.com` | Site |
| `HUB_MKP_APP_URL` | Obrigatória para login no tenant | `https://app.exemplo.com` | App |
| `HUB_APP_INTERNAL_HTTP_TIMEOUT_SECONDS` | Opcional, padrão `10` | `10` | Core → App |
| `HUB_INTERNAL_API_TOKEN` | Obrigatória; HMAC compartilhado | `<hex-64>` | Core ↔ App |
| `HUB_OAUTH_BROKER_SECRET` | Obrigatória para broker OAuth | `<hex-64>` | OAuth |
| `AUTH_TOKEN_TTL_MINUTES` | Opcional, padrão `720` | `720` | autenticação |
| `TENANT_LOGIN_TOKEN_TTL_SECONDS` | Opcional, 30–600 | `120` | Core → App |
| `PASSWORD_MAX_AGE_DAYS` | Opcional, padrão `90` | `90` | usuários |
| `PASSWORD_RESET_TOKEN_TTL_MINUTES` | Opcional, padrão `30` | `30` | usuários |
| `LOGIN_MAX_ATTEMPTS` | Opcional | `10` | login |
| `LOGIN_ATTEMPT_WINDOW_MINUTES` | Opcional | `15` | login |
| `LOGIN_BLOCK_MINUTES` | Opcional | `15` | login |
| `PUBLIC_SIGNUP_ENABLED` | Obrigatória por decisão operacional | `true` | cadastro |
| `PUBLIC_SIGNUP_REQUIRE_EMAIL_CONFIRMATION` | Obrigatória por decisão operacional | `true` | cadastro/e-mail |
| `PUBLIC_EMAIL_CONFIRMATION_TTL_MINUTES` | Opcional | `1440` | cadastro |
| `EMAIL_CONFIRMATION_RESEND_COOLDOWN_SECONDS` | Opcional | `60` | cadastro |
| `SIGNUP_LIMIT_IP_PER_HOUR` | Opcional | `5` | antiabuso |
| `SIGNUP_LIMIT_EMAIL_PER_DAY` | Opcional | `3` | antiabuso |
| `SIGNUP_LIMIT_CNPJ_PER_DAY` | Opcional | `3` | antiabuso |
| `CAPTCHA_ENABLED` | Opcional | `false` | Turnstile |
| `CAPTCHA_PROVIDER` | Opcional; implementado: `turnstile` | `turnstile` | CAPTCHA |
| `CAPTCHA_TURNSTILE_SECRET_KEY` | Condicional se CAPTCHA ativo; `CAPTCHA_SECRET` é apenas o fallback legado | `<segredo>` | Turnstile |
| `CAPTCHA_TIMEOUT_SECONDS` | Opcional | `10` | Turnstile |

### E-mail, provisionamento e suporte

| Variável | Uso | Exemplo fictício | Relação |
|---|---|---|---|
| `MAIL_ENABLED` | Obrigatória por decisão operacional | `true` | SMTP |
| `MAIL_FROM_EMAIL` | Condicional se e-mail ativo | `no-reply@exemplo.com` | SMTP |
| `MAIL_FROM_NAME` | Condicional | `Hub Marketplace` | SMTP |
| `MAIL_HOST` | Condicional | `smtp.exemplo.com` | SMTP |
| `MAIL_PORT` | Condicional | `587` | SMTP |
| `MAIL_USERNAME` | Condicional | `no-reply@exemplo.com` | SMTP |
| `MAIL_PASSWORD` | Condicional e secreta | `<senha-smtp>` | SMTP |
| `MAIL_ENCRYPTION` | Condicional | `tls` | SMTP |
| `SUPPORT_DEFAULT_NAME` | Opcional para CLI | `Suporte` | usuário inicial |
| `SUPPORT_DEFAULT_EMAIL` | Opcional para CLI | `suporte@exemplo.com` | usuário inicial |
| `SUPPORT_INITIAL_PASSWORD` | Efêmera e opcional para automação controlada; prefira o prompt oculto e remova-a logo após o comando | `<senha-temporaria>` | CLI de suporte |
| `PROVISIONING_RETRY_DELAY_MINUTES` | Opcional | `5` | fila Core |
| `PROVISIONING_REAL_ENABLED` | Legada; mantenha `false` no fluxo distribuído | `false` | Core |
| `ALLOW_TENANT_TEST_RESET` | Somente sandbox controlado | `false` | testes |
| `PROVISION_DB_HOST` | Obrigatória; host onde tenants serão criados | `10.0.0.20` | Worker/MariaDB |
| `PROVISION_DB_PORT` | Obrigatória | `3306` | Worker/MariaDB |
| `PROVISION_DB_NAME_PREFIX` | Obrigatória; padrão aceito pelo Worker | `hub_mkp_cli_` | tenants |
| `PROVISION_DB_USER_PREFIX` | Obrigatória | `hmkpc` | tenants |
| `PROVISION_DB_USER_HOST` | Obrigatória; origem real do App | `10.0.0.%` | MariaDB tenant |
| `PROVISION_DB_CHARSET` | Obrigatória | `utf8mb4` | tenants |
| `PROVISION_DB_COLLATION` | Obrigatória | `utf8mb4_unicode_ci` | tenants |
| `PROVISION_DB_ADMIN_USER`, `PROVISION_DB_ADMIN_PASSWORD` | Legadas no Core; deixe vazias no fluxo Worker | vazio | não usadas pelo Core web |
| `HUB_MKP_PATH`, `HUB_MKP_TENANT_MIGRATE_SCRIPT`, `HUB_MKP_TENANT_FINALIZE_SCRIPT`, `PHP_CLI_BINARY` | Legadas para execução local pelo Core; não ativar | defaults do exemplo | fluxo antigo |
| `TENANT_MIGRATIONS_ENABLED`, `TENANT_FINALIZE_ENABLED` | Legadas; mantenha `false` | `false` | fluxo antigo |
| `TENANT_MIGRATIONS_TIMEOUT_SECONDS`, `TENANT_FINALIZE_TIMEOUT_SECONDS` | Opcionais no fluxo antigo | `300` | fluxo antigo |

### Marketplaces

Estas variáveis são condicionais: preencha somente o ambiente que será
habilitado. Partner keys e app secrets podem ser protegidos com
`php7.4 bin/encrypt_secret.php`; o comando solicita o valor sem colocá-lo nos
argumentos.

| Grupo | Variáveis | Exemplo/uso |
|---|---|---|
| Shopee comum | `SHOPEE_OAUTH_FALLBACK_RETURN_URL`, `SHOPEE_OAUTH_DEFAULT_RETURN_PATH`, `SHOPEE_OAUTH_ALLOWED_RETURN_URLS`, `SHOPEE_OAUTH_ALLOWED_RETURN_HOSTS`, `SHOPEE_OAUTH_HUB_CALLBACK_URL`, `SHOPEE_OAUTH_STATE_TTL_SECONDS`, `SHOPEE_OAUTH_REQUEST_TOLERANCE_SECONDS`, `SHOPEE_OAUTH_HTTP_TIMEOUT_SECONDS` | URLs de `https://app.exemplo.com`, TTL `600`, tolerância `300`, timeout `15` |
| Shopee sandbox | `SHOPEE_SANDBOX_PARTNER_ID`, `SHOPEE_SANDBOX_PARTNER_KEY`, `SHOPEE_SANDBOX_API_BASE_URL`, `SHOPEE_SANDBOX_AUTH_BASE_URL`, `SHOPEE_SANDBOX_REDIRECT_URL` | credenciais fictícias e URLs oficiais do `.env.example` |
| Shopee produção | `SHOPEE_PRODUCTION_PARTNER_ID`, `SHOPEE_PRODUCTION_PARTNER_KEY`, `SHOPEE_PRODUCTION_API_BASE_URL`, `SHOPEE_PRODUCTION_AUTH_BASE_URL`, `SHOPEE_PRODUCTION_REDIRECT_URL` | callback no Core |
| TikTok credenciais | `TIKTOK_SERVICE_ID`, `TIKTOK_APP_KEY`, `TIKTOK_APP_SECRET` | valores do app TikTok |
| TikTok URLs | `TIKTOK_OAUTH_CALLBACK_URL`, `TIKTOK_AUTH_AUTHORIZE_URL`, `TIKTOK_AUTH_BASE_URL`, `TIKTOK_OPEN_API_BASE_URL`, `TIKTOK_OAUTH_HUB_CALLBACK_URL`, `TIKTOK_OAUTH_FALLBACK_RETURN_URL`, `TIKTOK_OAUTH_DEFAULT_RETURN_PATH`, `TIKTOK_OAUTH_ALLOWED_RETURN_URLS`, `TIKTOK_OAUTH_ALLOWED_RETURN_HOSTS` | callbacks do Core e App |
| TikTok limites | `TIKTOK_OAUTH_STATE_TTL_SECONDS`, `TIKTOK_OAUTH_REQUEST_TOLERANCE_SECONDS`, `TIKTOK_OAUTH_HTTP_TIMEOUT_SECONDS` | `600`, `300`, `15` |

## 5. VirtualHost Apache

Sandbox: `sandbox-hub-mkp-core.totalseller.com.br`. Produção: substitua por
seu domínio real. O acesso não deve registrar query strings, pois callbacks
OAuth carregam códigos temporários.

```apache
<VirtualHost *:80>
    ServerName core.exemplo.com
    DocumentRoot /var/www/hub-mkp-core/public

    <Directory /var/www/hub-mkp-core/public>
        Options -Indexes +FollowSymLinks
        AllowOverride None
        Require all granted
        RewriteEngine On
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteCond %{REQUEST_FILENAME} !-d
        RewriteRule ^ index.php [QSA,L]
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/hub-mkp-core-error.log
    LogFormat "%h %l %u %t \"%m %U %H\" %>s %b %D" hub_core_noquery
    CustomLog ${APACHE_LOG_DIR}/hub-mkp-core-access.log hub_core_noquery
</VirtualHost>
```

```bash
sudo a2ensite <vhost-core>.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
```

Ative HTTPS com o mecanismo de certificados adotado pelo servidor e redirecione
HTTP para HTTPS. Não coloque o `.env` sob `public/`.

## 6. Migrations, instância e suporte inicial

```bash
cd /var/www/hub-mkp-core
php7.4 bin/migrate.php
php7.4 bin/seed.php
php7.4 bin/criar_suporte.php \
  --nome="Suporte Inicial" \
  --email="suporte@exemplo.com"
```

`bin/seed.php` faz upsert idempotente em `hub_instances` usando as variáveis
`CORE_INSTANCE_*`; esse é o registro oficial do App no Core. O comando de
suporte solicita e confirma a senha sem eco, exige ao menos 12 caracteres e
letras maiúsculas, minúsculas, número e especial. Ele cria UUID, hash
`PASSWORD_DEFAULT`, `is_support=1`, usuário ativo e troca obrigatória no
primeiro acesso.

Confirme:

```sql
SELECT code, environment, login_url, base_url, is_active, status
FROM hub_instances
WHERE code = 'hub-prod-01';

SELECT email, is_support, is_active, must_change_password
FROM users
WHERE email = 'suporte@exemplo.com';
```

## 7. Registrar o Worker

Faça este passo somente depois de instalar o App. O token aparece uma única
vez; copie-o diretamente para o `.env` do Worker e não o registre em arquivos
de log.

```bash
cd /var/www/hub-mkp-core
php7.4 bin/create_worker.php \
  --instance-code=hub-prod-01 \
  --name=worker-prod-01
```

Para uma rotação deliberada, repita com `--rotate-token`; isso invalida o token
anterior. O Worker fica ativo ao ser criado. A autenticação é confirmada por
`php7.4 bin/check.php` no repositório do Worker.

## 8. Primeiro tenant

Não insira tenants diretamente no banco. Use `POST /api/public/register` pelo
site/tela oficial. Com confirmação de e-mail ativa, confirme pelo link antes
de esperar a fila. O Core cria o grupo, o usuário administrador, o contexto do
banco e o primeiro job. O Worker deve concluir três execuções:

1. `CRIAR_DATABASE_CLIENTE`;
2. `RODAR_MIGRATIONS_CLIENTE`;
3. `FINALIZAR_TENANT`.

Cada execução de `php7.4 bin/worker.php` processa no máximo um job; o crontab
faz as execuções recorrentes. Não use os comandos legados de provisionamento
local do Core numa instalação distribuída.

## Checklist de validação

```bash
cd /var/www/hub-mkp-core
php7.4 "$(command -v composer)" validate --no-check-publish
find app bin config public -name '*.php' -print0 | xargs -0 -n1 php7.4 -l
php7.4 bin/migrate.php
curl --fail --silent https://core.exemplo.com/api/health
tail -n 50 storage/logs/$(date +%F).log
```

O health deve responder JSON com `success: true`, `app: hub-mkp-core` e
`database: connected`. 