T ToutadminDokumentacja

Instalacja edycji PHP

Edycję PHP wgrywa się taką, jaka jest, na hosting współdzielony: bez Composera, bez menedżera pakietów, bez usługi, którą trzeba utrzymywać przy życiu. Jeden katalog, jeden plik konfiguracyjny, jedno zadanie cykliczne. Ten przewodnik prowadzi od pustego katalogu do instancji produkcyjnej, łącznie z tym, co większość poradników pomija: uprawnienia, TLS, zadanie cykliczne, kopie zapasowe i aktualizacja.

Czego potrzeba#

ElementWersjaDlaczego
PHP8.1 lub nowszyTypy sumy, enum, readonly, never: kod ich używa.
pdo_sqliteCała baza. Bez tego nic nie ruszy.
mbstringPoprawna obsługa napisów w szesnastu językach.
opensslzalecaneSzyfrowanie sekretów w bazie (hasła SMTP, tokeny wysyłki na zewnątrz).
curlzalecaneWebhooki wychodzące i składanie kopii u podmiotu zewnętrznego.
intlzalecaneDaty zapisywane jak w edycji Node. Bez tego nazwy miesięcy pochodzą ze słowników.

Kreator instalacji sprawdza to wszystko i pokazuje na ekranie, zanim pozwoli iść dalej: to, co blokuje, zatrzymuje instalację; to, co opcjonalne, zostaje zgłoszone, ale nie blokuje.

Żadnych zależności

Nie ma czego instalować composerem: czytnik IMAP, archiwum tar.gz, PDF, CSV, kod QR i klient HTTP są napisane wewnątrz produktu. To właśnie pozwala wgrać go na hosting, na którym nie masz wiersza poleceń.

1. Wgrać pliki#

Pobierz archiwum edycji PHP i wgraj je. Katalog wygląda tak:

toutadmin/
├── app/            kod (serwer WWW nigdy go nie udostępnia)
│   ├── Core/       jądro: trasowanie, baza, sesje, bezpieczeństwo
│   ├── Modules/    dziedzina: kadry, finanse, projekty, jakość…
│   ├── Controllers/
│   ├── views/      szablony
│   ├── locales/    szesnaście słowników
│   └── schema.sql  schemat, identyczny jak w edycji Node
├── public/         ← katalog główny serwera wskazuje tutaj i nigdzie indziej
│   ├── index.php   kontroler wejściowy
│   ├── css/ js/
├── data/           baza, sejf, teczka podpisów, otrzymane dokumenty, kopie
├── tools/          cron.php, check-keys.php
├── tests/
├── config.sample.php
└── VERSION
Katalog główny wskazuje na public/, a nie na cały katalog

To najważniejszy punkt tej strony. Jeśli serwer udostępnia cały katalog, data/app.sqlite staje się do pobrania dla każdego: cała baza, wraz ze skrótami haseł i sejfem. Katalog data/ leży powyżej public/ celowo.

Na hostingu współdzielonym#

Bywa dwojako. Jeśli hostingodawca pozwala wybrać katalog główny domeny (cPanel, Plesk i większość hostingów w trybie „katalog główny”), wgraj katalog poza www/ i skieruj domenę na toutadmin/public.

Jeśli katalog główny jest narzucony — często www/ albo public_html/ — wgraj tam zawartość public/, a resztę powyżej, po czym popraw ścieżkę na początku public/index.php:

www/                  ← narzucony katalog główny
├── index.php
├── css/  js/
toutadmin/            ← reszta, poza zasięgiem sieci
├── app/
├── data/
└── config.php
// www/index.php — wiersz do poprawienia
require_once __DIR__ . '/../toutadmin/app/bootstrap.php';

W ostateczności, gdy nic nie da się umieścić powyżej katalogu głównego, dostarczony plik .htaccess w data/ już odmawia dostępu. To pas bezpieczeństwa, a nie rozwiązanie: zawsze wybieraj katalog poza katalogiem głównym.

2. Zapisać konfigurację#

cp config.sample.php config.php

Potem otwórz config.php:

<?php
return [
    // Baza SQLite i katalog danych: poza katalogiem głównym serwera.
    'db_path'  => __DIR__ . '/data/app.sqlite',
    'data_dir' => __DIR__ . '/data',

    // Klucz właściwy tej instancji. Wygenerować raz:
    //   php -r "echo bin2hex(random_bytes(32));"
    'session_secret' => '…',

    // Token instalacyjny: patrz następny krok.
    'install_token' => '',

    // Adres publiczny, dla odsyłaczy wysyłanych pocztą.
    'base_url' => 'https://intranet.przyklad.pl',

    // Włączać tylko za zaufanym serwerem pośredniczącym.
    'trust_proxy' => false,

    // Pułapy: próby logowania na kwadrans i na adres,
    // żądania na minutę i na adres.
    'login_rate_limit'  => 10,
    'global_rate_limit' => 300,

    // Wygasanie: bezczynność, a potem czas bezwzględny, którego nic nie przedłuża.
    'session_idle_minutes' => 60,
    'session_max_hours'    => 12,
];
Klucz instancji

Pieczętuje podpisy w teczce podpisów i szyfruje sekrety w bazie. Jego utrata unieważnia podpisy. Jeśli zostawisz wartość przykładową, produkt sam wygeneruje klucz w data/.instance-key (uprawnienia 0600) — pamiętaj wtedy, by objąć ten plik kopiami zapasowymi.

Konfigurację można też trzymać gdzie indziej: zmienna środowiskowa TOUTADMIN_CONFIG wskazuje inny plik.

3. Ustawić token instalacyjny#

Między wgraniem plików a Twoim wejściem do kreatora instancja należy do tego, kto ją znajdzie: pierwszy przybysz zakłada konto administratora. Okno jest krótkie, ale wystarczy. Ustaw więc losową wartość przed publikacją:

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

Kreator o niego prosi, porównuje go w stałym czasie — zwykłe porównanie pozwoliłoby zgadywać token znak po znaku — i zapisuje każdą odmowę. Bez tokenu nic nie powstaje. Po zainstalowaniu instancji kreator zamyka się sam: token może zostać, już do niczego nie służy.

4. Uprawnienia#

ŚcieżkaPrawaKto zapisuje
data/0770Serwer WWW i tylko on
data/app.sqlite0660Tworzone przez instalację
config.php0640Nikt — dla serwera tylko do odczytu
app/, public/0755Nikt — tylko do odczytu
# przykład na serwerze, gdzie PHP działa jako www-data
sudo chown -R ty: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

SQLite zapisuje też pliki sąsiadujące (-wal, -shm): zapisywalny musi być katalog, nie tylko sama baza.

5. Serwer WWW#

Nginx#

server {
    listen 443 ssl http2;
    server_name intranet.przyklad.pl;

    root /var/www/toutadmin/public;   # i nic innego
    index index.php;

    ssl_certificate     /etc/letsencrypt/live/intranet.przyklad.pl/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/intranet.przyklad.pl/privkey.pem;

    client_max_body_size 20M;          # wrzuty do sejfu, otrzymane dokumenty

    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;
    }

    # Wgrane pliki nigdy nie są udostępniane wprost: idą przez trasę,
    # która sprawdza sesję i skrót.
    location ~ ^/(data|app|tools|tests)/ { deny all; }
}

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

Apache#

<VirtualHost *:443>
    ServerName intranet.przyklad.pl
    DocumentRoot /var/www/toutadmin/public

    SSLEngine on
    SSLCertificateFile    /etc/letsencrypt/live/intranet.przyklad.pl/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/intranet.przyklad.pl/privkey.pem

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

Dostarczony plik public/.htaccess już kieruje wszystkie adresy do index.php. Na hostingu współdzielonym z Apache to wszystko, czego trzeba.

TLS#

Produkt ustawia nagłówek Strict-Transport-Security, gdy tylko połączenie jest szyfrowane, i nigdy otwartym tekstem: ogłoszony ze strony nieszyfrowanej nie zostałby przeczytany, a lokalną próbę zamknąłby w https na pół roku. Na serwerze, którym sam zarządzasz:

sudo certbot --nginx -d intranet.przyklad.pl

Za serwerem pośredniczącym albo rozdzielaczem obciążenia, który kończy TLS, ustaw 'trust_proxy' => true — i tylko tam: inaczej każdy mógłby ogłosić w nagłówku dowolny adres IP i dowolny protokół.

6. Zadanie cykliczne#

Witryna PHP żyje tylko w chwili żądania. To, co edycja Node robi w swoim cogodzinnym przebiegu — osiem operacji — tutaj robi cron hostingu:

* * * * * /usr/bin/php /var/www/toutadmin/tools/cron.php >> /var/log/toutadmin-cron.log 2>&1
Co robi zadanieW jakim rytmie
Zamyka konta, którym skończyła się umowaprzy każdym przebiegu
Wystawia faktury abonamentów, którym nadszedł terminprzy każdym przebiegu, nigdy dwa razy
Zamienia terminy w powiadomieniaprzy każdym przebiegu, bez powtórzeń
Czyści przeczytane powiadomienia i dziennik audytuzgodnie z wybranym okresem przechowywania
Pobiera księgową skrzynkę pocztową (IMAP)jeśli pobieranie jest skonfigurowane
Opróżnia kolejkę webhooków wraz z ponowieniamiprzy każdym przebiegu
Tworzy kopię automatyczną i wysyła ją na zewnątrzgdy minie ustalony odstęp

Wywoływanie skryptu częściej niż wynosi ustalony odstęp nie tworzy kopii częściej: nic nie ruszy przed terminem. Na hostingu, który ogranicza cron do jednego przebiegu na kwadrans, zamień * * * * * na */15 * * * * — webhooki wyruszą po prostu z tym opóźnieniem.

Na hostingu bez crona

Niektóre pakiety najniższej półki go nie mają. Wywołanie HTTP z usługi zewnętrznej (monitora dostępności i podobnych) nie wystarczy: cron.php odmawia uruchomienia poza wierszem poleceń, właśnie po to, by publiczny adres nie mógł wyzwolić kopii zapasowej. W takim razie kopię uruchamia się ręcznie z ekranu Kopie zapasowe.

7. Kreator#

Otwórz swoją domenę. Dopóki nie istnieje żadne konto, każdy adres przekierowuje do /installation. Kreator mieści się na jednej stronie:

  1. Wymagania, sprawdzone i pokazane — to, co blokuje, jest tak oznaczone.
  2. Token instalacyjny, jeśli został ustawiony.
  3. Firma: nazwa wyświetlana wszędzie oraz liczba dni urlopu rocznego przyznawana każdemu nowemu pracownikowi na etacie.
  4. Język domyślny instancji, spośród 16.
  5. Konto administratora: adres i hasło o długości co najmniej dwunastu znaków.

Wszystko zapisuje się w jednej transakcji. Gdy tylko konto istnieje, /installation odsyła do strony logowania: kreator zamknął się sam, nie ma żadnego pliku do usunięcia ręcznie.

8. Sprawdzić, że wszystko działa#

# testy, na maszynie (nie zapisują niczego w Twojej bazie)
php tests/run.php

# tłumaczenia: żaden ekran nie używa klucza, którego brak w słownikach
php tools/check-keys.php

Potem, w interfejsie: utwórz pracownika, zaloguj się na niego, wgraj załącznik, uruchom kopię ręczną i sprawdź ją na ekranie Kopie zapasowe. Te cztery gesty dotykają bazy, plików, uprawnień i archiwum — czyli wszystkiego, co hosting musi umieć.

9. Aktualizacja#

# 1. najpierw kopia zapasowa, zawsze
php tools/cron.php                     # albo ekran Kopie zapasowe

# 2. wymienić kod, nie dane
#    app/ public/ tools/ tests/ VERSION  ← wymieniane
#    data/ config.php                    ← zachowywane bez zmian

# 3. baza aktualizuje się sama przy pierwszym żądaniu

Schemat zmienia się przez migracje idempotentne: powtórzenie aktualizacji niczego nie psuje. Instancja edycji Node i instancja edycji PHP mają ten sam schemat — 142 tabel, 1311 kolumn — i te same hasła: baza przechodzi z jednej do drugiej bez konwersji.

10. Robić kopie naprawdę#

Archiwum tar.gz niesie bazę (skopiowaną przez VACUUM INTO, więc spójną nawet w trakcie zapisu) oraz pięć katalogów plików: zdjęcia profilowe, otrzymane życiorysy, sejf, teczkę podpisów, otrzymane dokumenty. Każdy plik niesie tam swój skrót SHA-256, sprawdzany przy odtwarzaniu.

Archiwum, które zostaje na serwerze, który chroni, nie chroni niczego: skonfiguruj cel zdalny (FTPS albo Google Drive) na ekranie Kopie zapasowe. Szczegóły są na stronie Kopie zapasowe i odtwarzanie.

Najpierw spróbować lokalnie#

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

Wbudowany serwer PHP wystarczy, by poznać produkt. Do produkcji się nie nadaje: jeden proces, brak TLS, brak ograniczania obciążenia.

Dokumentacja Toutadmin — zbudowana 2026-09-13. Witryna samodzielna, niezależna od oprogramowania.