T ToutadminDocumentação

Instalar a edição PHP

A edição PHP deposita-se tal como está num alojamento partilhado: sem Composer, sem gestor de pacotes, sem serviço para manter a correr. Uma pasta, um ficheiro de configuração, uma tarefa agendada. Este tutorial vai da pasta vazia à instância em produção, incluindo o que a maioria dos guias deixa de fora: as permissões, o TLS, a tarefa agendada, as cópias de segurança e a atualização.

O que é preciso#

ElementoVersãoPorquê
PHP8.1 ou mais recenteTipos de união, enum, readonly, never: o código usa-os.
pdo_sqliteA base inteira. Sem ela não corre nada.
mbstringTratamento correto das cadeias em dezasseis idiomas.
opensslrecomendadaCifra dos segredos na base (palavras-passe SMTP, símbolos de externalização).
curlrecomendadaWebhooks de saída e envio das cópias para terceiros.
intlrecomendadaDatas escritas como na edição Node. Sem ela, os nomes dos meses vêm dos dicionários.

O assistente de instalação verifica tudo isto e mostra-o no ecrã antes de o deixar prosseguir: o que é bloqueante trava a instalação, o que é facultativo é assinalado sem travar.

Nenhuma dependência

Não há nada para instalar com o composer: o leitor IMAP, o arquivo tar.gz, o PDF, o CSV, o código QR e o cliente HTTP estão escritos dentro do produto. É isso que permite depositá-lo num alojamento onde não tem linha de comandos.

1. Enviar os ficheiros#

Obtenha o arquivo da edição PHP e envie-o. A pasta é assim:

toutadmin/
├── app/            o código (nunca servido pelo servidor web)
│   ├── Core/       núcleo: encaminhamento, base, sessões, segurança
│   ├── Modules/    negócio: recursos humanos, gestão, projetos, qualidade…
│   ├── Controllers/
│   ├── views/      modelos
│   ├── locales/    os dezasseis dicionários
│   └── schema.sql  o esquema, idêntico ao da edição Node
├── public/         ← a raiz web aponta aqui, e em mais lado nenhum
│   ├── index.php   o controlador frontal
│   ├── css/ js/
├── data/           base, cofre, livro de assinaturas, documentos recebidos, cópias
├── tools/          cron.php, check-keys.php
├── tests/
├── config.sample.php
└── VERSION
A raiz web aponta para public/, não para a pasta

É o ponto mais importante desta página. Se o servidor servir a pasta inteira, data/app.sqlite fica descarregável por qualquer pessoa: toda a base, com resumos de palavras-passe e cofre incluídos. A pasta data/ está acima de public/ de propósito.

Num alojamento partilhado#

Surgem dois casos. Se o seu fornecedor permitir escolher a raiz de um domínio (cPanel, Plesk, e a maioria dos alojamentos em modo «pasta raiz»), deposite a pasta fora de www/ e faça o domínio apontar para toutadmin/public.

Se a raiz for imposta — muitas vezes www/ ou public_html/ — deposite lá o conteúdo de public/ e o resto acima, e corrija depois o caminho no topo de public/index.php:

www/                  ← raiz imposta
├── index.php
├── css/  js/
toutadmin/            ← o resto, fora do alcance da web
├── app/
├── data/
└── config.php
// www/index.php — a linha a ajustar
require_once __DIR__ . '/../toutadmin/app/bootstrap.php';

Em último recurso, se não puder pôr nada acima da raiz, o ficheiro .htaccess entregue dentro de data/ já recusa o acesso. É um cinto, não uma solução: prefira sempre a pasta fora da raiz.

2. Escrever a configuração#

cp config.sample.php config.php

Depois abra config.php:

<?php
return [
    // Base SQLite e pasta de dados: fora da raiz web.
    'db_path'  => __DIR__ . '/data/app.sqlite',
    'data_dir' => __DIR__ . '/data',

    // Chave própria da instância. A gerar uma vez:
    //   php -r "echo bin2hex(random_bytes(32));"
    'session_secret' => '…',

    // Símbolo de instalação: ver o passo seguinte.
    'install_token' => '',

    // Endereço público, para as ligações enviadas por correio.
    'base_url' => 'https://intranet.exemplo.pt',

    // Só ativar atrás de um proxy de confiança.
    'trust_proxy' => false,

    // Tetos: tentativas de acesso por quarto de hora e por endereço,
    // pedidos por minuto e por endereço.
    'login_rate_limit'  => 10,
    'global_rate_limit' => 300,

    // Caducidade: inatividade, depois duração absoluta que nada prolonga.
    'session_idle_minutes' => 60,
    'session_max_hours'    => 12,
];
O segredo da instância

Sela as assinaturas do livro de assinaturas e cifra os segredos na base. Perdê-lo invalida assinaturas. Se deixar o valor de exemplo, o produto gera um sozinho em data/.instance-key (permissões 0600) — lembre-se então de guardar esse ficheiro com o resto.

Também pode manter a configuração noutro sítio: a variável de ambiente TOUTADMIN_CONFIG designa outro ficheiro.

3. Pôr um símbolo de instalação#

Entre o envio dos ficheiros e a sua passagem pelo assistente, a instância é de quem a encontrar: o primeiro a chegar cria a conta de administração. A janela é curta, mas chega. Ponha, pois, um valor ao acaso antes de a pôr em linha:

php -r "echo bin2hex(random_bytes(16));"
# depois em config.php:
'install_token' => 'c3f1…',

O assistente pede-o, compara-o em tempo constante — uma comparação vulgar deixaria adivinhar o símbolo carácter a carácter — e regista cada recusa. Sem o símbolo nada é criado. Uma vez instalada a instância, o assistente fecha-se sozinho: o símbolo pode ficar, já não serve.

4. Permissões#

CaminhoDireitosQuem escreve
data/0770O servidor web, e só ele
data/app.sqlite0660Criado pela instalação
config.php0640Ninguém — leitura apenas para o servidor
app/, public/0755Ninguém — apenas leitura
# exemplo num servidor onde o PHP corre como www-data
sudo chown -R voce:www-data /var/www/toutadmin
sudo find /var/www/toutadmin -type d -exec chmod 750 {} \;
sudo find /var/www/toutadmin -type f -exec chmod 640 {} \;
sudo chmod 770 /var/www/toutadmin/data
sudo chmod 750 /var/www/toutadmin/public

O SQLite escreve também ficheiros vizinhos (-wal, -shm): é a pasta que tem de ser gravável, não apenas a base.

5. O servidor web#

Nginx#

server {
    listen 443 ssl http2;
    server_name intranet.exemplo.pt;

    root /var/www/toutadmin/public;   # e nada mais
    index index.php;

    ssl_certificate     /etc/letsencrypt/live/intranet.exemplo.pt/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/intranet.exemplo.pt/privkey.pem;

    client_max_body_size 20M;          # depósitos no cofre, documentos recebidos

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    # Os ficheiros depositados nunca são servidos diretamente: passam
    # por uma rota que verifica a sessão e o resumo.
    location ~ ^/(data|app|tools|tests)/ { deny all; }
}

server {
    listen 80;
    server_name intranet.exemplo.pt;
    return 301 https://$host$request_uri;
}

Apache#

<VirtualHost *:443>
    ServerName intranet.exemplo.pt
    DocumentRoot /var/www/toutadmin/public

    SSLEngine on
    SSLCertificateFile    /etc/letsencrypt/live/intranet.exemplo.pt/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/intranet.exemplo.pt/privkey.pem

    <Directory /var/www/toutadmin/public>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

O ficheiro public/.htaccess entregue já reencaminha todos os endereços para index.php. Num alojamento partilhado com Apache, é tudo o que é preciso.

TLS#

O produto põe o cabeçalho Strict-Transport-Security assim que a ligação está cifrada, e nunca em claro: anunciado a partir de uma página não cifrada não seria lido, e prenderia um ensaio local em https durante seis meses. Num servidor que administre:

sudo certbot --nginx -d intranet.exemplo.pt

Atrás de um proxy ou de um repartidor de carga que termina o TLS, ponha 'trust_proxy' => true — e só aí: caso contrário qualquer um poderia anunciar o endereço IP e o protocolo que quisesse num cabeçalho.

6. A tarefa agendada#

Um sítio PHP só corre no momento de um pedido. O que a edição Node faz na sua varredura de hora a hora — oito operações — faz-se aqui a partir do cron do alojamento:

* * * * * /usr/bin/php /var/www/toutadmin/tools/cron.php >> /var/log/toutadmin-cron.log 2>&1
O que a tarefa fazCom que ritmo
Fecha as contas cujo contrato chegou ao fimem cada passagem
Emite as faturas das subscrições vencidasem cada passagem, sem nunca faturar duas vezes
Transforma os prazos em notificaçõesem cada passagem, sem duplicados
Limpa as notificações lidas e o registo de auditoriaconforme a conservação escolhida
Recolhe a caixa de correio contabilística (IMAP)se a recolha estiver configurada
Esvazia a fila dos webhooks, com as suas repetiçõesem cada passagem
Cria a cópia automática e externaliza-aquando o intervalo decorreu

Chamar o guião mais vezes do que o intervalo configurado não faz cópias mais vezes: nada arranca antes do prazo. Num alojamento que limita o cron a uma passagem de quinze em quinze minutos, substitua * * * * * por */15 * * * * — os webhooks partem simplesmente com esse atraso.

Num alojamento sem cron

Alguns alojamentos de gama baixa não o oferecem. Uma chamada HTTP de um serviço terceiro (um vigilante de disponibilidade e afins) não chega: o cron.php recusa-se a ser executado fora da linha de comandos, precisamente para que um endereço público não possa desencadear uma cópia de segurança. Nesse caso, a cópia lança-se à mão no ecrã Cópias de segurança.

7. O assistente#

Abra o seu domínio. Enquanto não existir conta nenhuma, todo o endereço reencaminha para /installation. O assistente cabe numa página:

  1. Os pré-requisitos, verificados e mostrados — o que bloqueia está marcado como tal.
  2. O símbolo de instalação, se estiver configurado.
  3. A empresa: o nome mostrado em toda a parte, e o número de dias de férias anuais atribuído a cada novo trabalhador não independente.
  4. O idioma predefinido da instância, entre os 16.
  5. A conta de administração: endereço e palavra-passe de pelo menos doze caracteres.

Tudo é escrito numa só transação. Logo que exista uma conta, /installation reencaminha para a página de acesso: o assistente fechou-se sozinho, não há ficheiro nenhum para apagar à mão.

8. Verificar que tudo funciona#

# os testes, na máquina (não escrevem na sua base)
php tests/run.php

# as traduções: nenhum ecrã usa uma chave ausente dos dicionários
php tools/check-keys.php

Depois, na interface: crie um membro, entre com ele, deposite um anexo, lance uma cópia manual e verifique-a no ecrã Cópias de segurança. Estes quatro gestos tocam na base, nos ficheiros, nas permissões e no arquivo — ou seja, em tudo o que o alojamento tem de saber fazer.

9. Atualizar#

# 1. uma cópia de segurança primeiro, sempre
php tools/cron.php                     # ou o ecrã Cópias de segurança

# 2. substituir o código, não os dados
#    app/ public/ tools/ tests/ VERSION  ← substituídos
#    data/ config.php                    ← conservados tal como estão

# 3. a base atualiza-se sozinha no primeiro pedido

O esquema evolui por migrações idempotentes: repetir a atualização não parte nada. Uma instância da edição Node e uma da edição PHP partilham o mesmo esquema — 142 tabelas, 1311 colunas — e as mesmas palavras-passe: uma base passa de uma à outra sem conversão.

10. Fazer cópias a sério#

O arquivo tar.gz leva a base (copiada por VACUUM INTO, logo coerente mesmo durante uma escrita) e as cinco pastas de ficheiros: fotografias de perfil, currículos recebidos, cofre, livro de assinaturas, documentos recebidos. Cada ficheiro leva ali o seu resumo SHA-256, verificado na reposição.

Um arquivo que fica no servidor que protege não protege nada: configure um destino remoto (FTPS ou Google Drive) no ecrã Cópias de segurança. O pormenor está na página Cópias de segurança e reposição.

Experimentar primeiro localmente#

cp config.sample.php config.php
php -S localhost:8000 -t public public/index.php

O servidor integrado do PHP chega para descobrir o produto. Não serve para produção: um único processo, sem TLS, sem limite de carga.

Documentação do Toutadmin — construída em 2026-09-13. Site autónomo, independente do software.