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#
| Elemento | Versão | Porquê |
|---|---|---|
| PHP | 8.1 ou mais recente | Tipos de união, enum, readonly, never: o código usa-os. |
pdo_sqlite | — | A base inteira. Sem ela não corre nada. |
mbstring | — | Tratamento correto das cadeias em dezasseis idiomas. |
openssl | recomendada | Cifra dos segredos na base (palavras-passe SMTP, símbolos de externalização). |
curl | recomendada | Webhooks de saída e envio das cópias para terceiros. |
intl | recomendada | Datas 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.
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
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,
];
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#
| Caminho | Direitos | Quem escreve |
|---|---|---|
data/ | 0770 | O servidor web, e só ele |
data/app.sqlite | 0660 | Criado pela instalação |
config.php | 0640 | Ninguém — leitura apenas para o servidor |
app/, public/ | 0755 | Ningué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 faz | Com que ritmo |
|---|---|
| Fecha as contas cujo contrato chegou ao fim | em cada passagem |
| Emite as faturas das subscrições vencidas | em cada passagem, sem nunca faturar duas vezes |
| Transforma os prazos em notificações | em cada passagem, sem duplicados |
| Limpa as notificações lidas e o registo de auditoria | conforme 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ções | em cada passagem |
| Cria a cópia automática e externaliza-a | quando 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.
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:
- Os pré-requisitos, verificados e mostrados — o que bloqueia está marcado como tal.
- O símbolo de instalação, se estiver configurado.
- 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.
- O idioma predefinido da instância, entre os 16.
- 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.