Nasazení#
Naplánované úlohy#
Bez nich systém nefunguje správně. Úlohy jsou presentery v app/UI/Cron/, spouštějí
se HTTP požadavkem s tokenem.
| Skupina | Co dělá | Jak často |
|---|---|---|
| Fronta e-mailů | rozesílá | každých pár minut |
| Expirace | upozornění a mazání prošlých inzerátů a zápisů | denně |
| Přepočty | počty v kategoriích, ceny | 10–15 min |
| Hlídací psi | rozesílá upozornění | průběžně nebo denně |
| Platby | dofakturace, vyhodnocení | průběžně |
| Plánovač faktur | opakovaná fakturace | denně |
| Elasticsearch | synchronizace a noční reindex | 10 min / denně |
| Sitemapy | generování, včetně e-shopových a indexu | denně v noci, index jako poslední |
| Úklid notifikací | /cron/system/notification/clean-orphans — odpojí notifikace, jejichž cílový záznam už neexistuje (zůstanou jako text bez odkazu, jinak by vedly v administraci na 404); každý běh projde dalších 500 starších notifikací (pozici si pamatuje v cache, po smazání cache začne od nejnovějších); sám se nespustí častěji než 1× za 20 h |
denně |
| Retence notifikací | /cron/system/notification/purge-read — maže přečtené notifikace starší než notification.readRetentionDays (výchozí 90 dní), nepřečtené nikdy |
denně |
| Auth log | /cron/system/auth-log/purge-old — maže staré záznamy přihlášení (výchozí 90 dní) |
denně |
| Fronta e-mailů — retence | /cron/system/email-queue/purge-old — maže vyřízené e-maily starší než email.queue.retentionDays (výchozí 30 dní, Nastavení → E-mail); těla e-mailů nemají zůstávat v databázi donekonečna |
denně |
| Dočasné soubory uploaderu | /cron/system/dropzone-temp/clean — maže v temp/dropzone soubory a složky starší než 24 h (nedokončené uploady) |
denně |
| Zrušení účtů | /cron/system/user/anonymize-deleted — anonymizuje účty, kterým uplynula lhůta po žádosti o zrušení (account.deletion.graceDays, výchozí 7 dní); bez něj žádosti jen čekají |
denně |
| Logy | /cron/system/log/purge-old — maže výpisy chyb Tracy starší než log.retentionDays (výchozí 30) a *.log nad log.maxSizeMb (výchozí 50) odkládá na .log.1 (3 generace); Nastavení → Cron |
denně |
Co nastavit před nasazením
Kromě modulových úloh (fronta e-mailů, expirace, přepočty, Elasticsearch, sitemapy) patří do plánovače vždy těchto pět
úklidových úloh pod /cron/system/…: notification/clean-orphans, auth-log/purge-old, email-queue/purge-old,
dropzone-temp/clean, log/purge-old, a k nim user/anonymize-deleted (zrušení účtů). Každá se spouští s ?token=<cron.token>.
notification/clean-orphans, dropzone-temp/clean, log/purge-old a user/anonymize-deleted se samy hlídají,
aby neběžely častěji než jednou za 20 h; auth-log/purge-old a email-queue/purge-old ne, ty zavěs jen denně.
Bez naplánovaných úloh se problémy projeví až za týdny
Inzeráty nevyprší, počty přestanou sedět, e-maily se hromadí. Nic to nehlásí. Po nasazení zkontrolujte, že úlohy skutečně běží.
Souběh úloh je ošetřený zámkem
Dvě instance téže úlohy se nespustí. Nespouštějte je ale zbytečně často — dlouhá úloha zablokuje tu následující.
Nasazení kódu#
Kód se na server dostává jediným krokem: skriptem deploy/deploy-demo.sh, který se
spouští ručně z vlastního počítače a míří na váš vlastní server. Push do main sám nic nenasadí (workflow
.github/workflows/deploy-demo.yml v repozitáři zůstal, ale nespouští se). Skript vezme
přesně obsah commitu (git archive HEAD), nainstaluje composer závislosti bez dev balíčků,
přes rsync přes SSH zrcadlí kód na server a v téže SSH relaci hned smaže cache.
deploy/deploy-demo.sh # nasadí aktuální commit větve main
deploy/deploy-demo.sh --dry-run # jen ukáže, co by rsync nahrál a smazal
deploy/deploy-demo.sh --allow-dirty # dovolí neuložené změny (nasadí se stejně jen commit)
deploy/deploy-demo.sh --skip-tests # nouzově nasadí i commit bez zelených testů
Přístup k vašemu serveru (uživatel, cesta, klíč) se bere z deploy/deploy-demo.local.env,
který není v gitu (vzor deploy/deploy-demo.local.env.example); připnutý klíč serveru
(KNOWN_HOSTS_LINE) ve skriptu nahraďte klíčem svého serveru, stejně jako adresu webu, proti
které běží smoke test. Skript odmítne běžet mimo větev main a s neuloženými změnami.
Nasadí se jen commit, který prošel testy: tests/run.sh T1 (nebo vyšší úroveň) po zeleném běhu
na čistém stromu zapíše <commit> <úroveň> do tests/.last-run a skript bez záznamu pro
aktuální HEAD skončí dřív, než cokoli nahraje. Úrovně testů popisuje
Testy a nástroje.
| Krok | Provedení |
|---|---|
| výměna souborů | rsync -rlptz --delete --delay-updates — soubory se nahrají stranou a přejmenují až na konci, takže výměna kódu trvá zlomek sekundy místo celého přenosu |
| co se nikdy nepřepíše ani nesmaže | deploy/rsync-exclude.txt: config/local.neon, config/Shared/local.neon, config/debug-mode.flag, uploads, uploads_thumbs, feeds, sitemaps, private/pdf, private/eshop, temp, log, var |
cache v temp/ |
smažou se jen soubory (find temp -mindepth 1 -type f -delete), adresáře zůstanou |
| SQL migrace | bin/migrate.php po smazání temp/ a před mazáním Redisu; viz Migrace SQL |
| Redis | klíče app:cache:q:* (query cache) a app:cache:route:* (route tabulka a mapy slugů) v databázi DEMO_REDIS_DB (výchozí 0, viz Více instalací na jednom serveru); relace ani verze tagů se nemažou |
| klíč serveru | připnutý ve skriptu (KNOWN_HOSTS_LINE, StrictHostKeyChecking=yes); nepoužívá se ssh-keyscan, takže podvržený server nedostane deploy klíč. Nesedí-li klíč, ssh/rsync skončí dřív, než cokoli pošle |
| smoke test | po nasazení skript spustí php tests/Manual/Verify/demo_smoke.php <adresa-webu> (jen čtení, bez přihlášení a bez zápisů): úvodní stránky všech jazyků, výpis každého modulu a z něj jeden detail, přihlašovací stránka administrace, robots.txt a sitemapa, na kterou odkazuje. Selhání = jiný stav než 200 (po přesměrování), chybová stránka nebo Tracy; pomalé stránky jen vypíše. Chybějící sitemapa je jen upozornění (generuje ji cron) |
| výsledek | selže-li smoke test, kód je už nasazený, ale skript skončí s kódem 4 a nasazení hlásí jako neúspěšné; selhání migrace vrátí kód migrace |
| první požadavek | trvá kolem 9 s (studená cache), další 0,4–1,1 s; smoke test web zároveň zahřeje, takže ho zaplatí skript, ne návštěvník |
První požadavky po smazání cache mohou krátce selhat
Smoke test běží hned po smazání cache, kdy se DI kontejner teprve kompiluje, a první
požadavky mohou krátce vrátit 500 nebo 503. Jednotlivé selhání proto ještě neznamená
chybu nasazení: spusť smoke test znovu
(php tests/Manual/Verify/demo_smoke.php <adresa-webu>). Teprve když chyba trvá, hledej
příčinu v log/.
Mazání klíčů v Redisu vždy s xargs -r -d '\n'
Klíče nesou názvy tříd se zpětnými lomítky (app:cache:q:App\UI\…) a výchozí
xargs je z argumentů zahodí. Dřívější redis-cli --scan … | xargs redis-cli del proto
nenašlo klíč a smazalo 12 ze 4 457; cache jen doexpirovala po TTL. Správně:
Po mazání ověř počet (po nasazení mají zůstat jen čerstvé klíče). Totéž platí pro každé ruční mazání.
Nahrání a smazání cache musí být JEDEN krok
Dřív to byly dva kroky a mezi nimi běžel nový kód se starým zkompilovaným DI
kontejnerem: každá nově injektovaná závislost vyhodila Typed property … must not be
accessed before initialization a RobotLoader: Unable to create …. Trvá sekundy a
samo se srovná, ale je to na stránkách vidět. Smazaný adresář pod rukama běžícímu
požadavku házel RobotLoader chybu, proto se mažou jen soubory.
SQL migrace pouští nasazení samo
Po nahrání souborů a smazání temp/ spustí skript bin/migrate.php; selže-li migrace,
nasazení skončí chybou. Postup, pravidla a první běh viz Migrace SQL.
Serverový local.neon si drží vlastní enabledServices
Nová cachovaná služba se musí doplnit i tam, jinak se čte z databáze (jeden dotaz navíc na požadavek). Viz Cache.
Rotace logů#
Adresář log/ bez rotace roste a nikdo to nehlásí; nejvíc přibývají debug.log
a auth.log. Zdroje, které psaly
řádek na každý požadavek, byly odstraněny (whitelisty rout při každém sestavení routeru,
INTERNAL_AUTH no_token při každém interním čtení, „Dynamic routes" a „Lazy warmup triggered"
v překladačích); rozhodnutí RBAC, vadné tokeny, chyby a výjimky se logují dál.
Nastav logrotate
Denně, 3 komprimované generace, dřív při 200 MB; výpisy výjimek *--*.html|md starší 14
dní se mažou. Bez rotace log roste do zaplnění disku.
Migrace SQL#
Schéma se udržuje ručně psanými opakovatelnými skripty v sql/. Při nasazení je na
databázi pouští bin/migrate.php — nic se už nespouští ručně a nikdo nemusí pamatovat
pořadí.
Co nasazení udělá#
Po nahrání souborů a smazání temp/ spustí SSH krok bin/migrate.php (PHP 8.5, jinak 8.4,
jinak výchozí php) a teprve potom maže Redis cache.
- Získá databázový zámek
coolcms_sql_migrations(GET_LOCK, čeká 30 s) — teprve pod ním čte evidenci, takže dva souběžné běhy nikdy nepustí tentýž soubor dvakrát. - Přečte, které soubory už na této databázi běžely (tabulka
cms_system_sql_migrations:filename,checksum= SHA-256 obsahu,applied_at). - Ověří checksumy už aplikovaných souborů. Liší-li se obsah některého od evidence, vypíše jeho název a skončí kódem 1 — nic nepustí.
- Vybere čekající migrace — soubory
sql/YYYY-MM-DD_NN_popis.sql, které v evidenci nejsou — a spustí je jednu po druhé, podle názvu souboru. - Po každém úspěšném souboru ho zapíše do evidence. Soubor, který je v evidenci, ale ve složce chybí, jen ohlásí varováním.
Výběr řeší App\Core\Utils\Helpers\SqlMigrationPlanner:
| Soubor | Pustí se? |
|---|---|
YYYY-MM-DD_NN_popis.sql |
ano, jednou |
*_rollback.sql |
nikdy — rollback se pouští ručně |
*_dev_pii_anonymization* |
nikdy — vývojové skripty |
cokoli jiného v sql/ (jiný tvar jména) |
ne |
Kód se nahraje PŘED migracemi
Pořadí kroků je: nahrát soubory → smazat temp/ → migrace → smazat Redis cache. Mezi
nahráním kódu a koncem migrace proto krátce běží nový kód nad starým schématem. Migrace,
která přidává sloupec, který nový kód čte (dřív se jim říkalo „před kódem“), způsobí
po tu dobu chyby na dotčených stránkách. Má-li být okno nulové, rozděl změnu na dvě
nasazení: nejdřív migrace, kterou starý kód nepozná, a v dalším nasazení kód, který ji
používá.
První běh a základní stav#
Na databázi, kde evidence ještě není, se nedá zjistit, co se dřív pustilo ručně. Bez
evidence proto bin/migrate.php skončí kódem 1 a jen vypíše, co by vzal za hotové — nic
neoznačí potichu. První běh se pouští ručně s --baseline:
Ten tabulku založí a všechny migrace, jejichž název je před 2026-10-02_08 (konstanta
SqlMigrationPlanner::BASELINE), jen zapíše jako hotové — nespustí je. Od 2026-10-02_08
dál se spouští vše. Na databázi, kde evidence už je, se --baseline nepoužije.
Nový server: první běh ne ze skriptu nasazení
Nasazení --baseline nepředává. Na novém serveru bez evidence by krok migrací skončil
chybou — první běh tam pusť ručně a teprve pak nasazuj.
Databáze musí být před prvním během dorovnaná ručně
Co je před základním stavem, se už nikdy samo nespustí. Databáze vzniklá ze staršího
dumpu proto musí mít všechny starší migrace aplikované ručně dřív, než poprvé
poběží nasazení. Týká se to i 2026-10-02_06_remove_google_settings.sql — spadá pod
základní stav, takže ho na databázi, kde ještě neběžel, pusť ručně až po nasazení kódu.
Postup: zjistit, co nasazení pustí#
Vypíše čekající migrace v pořadí spuštění (nebo „žádná čekající migrace“). Do databáze
nezapíše nic, ani evidenci; na databázi bez evidence napíše, kolik starších souborů by se
při prvním běhu vzalo za hotové. Bez přepínače se migrace pustí; příkaz jde spustit i ručně
(např. po opravě selhané migrace). Připojení čte z parameters.mariadb v konfiguraci
(config/Shared/parameters.neon, přebité config/Shared/local.neon a config/local.neon).
Co se stane při chybě#
| Situace | Chování |
|---|---|
| migrace selže | skript vypíše soubor a chybu, skončí kódem 1, další migrace nepustí a selhaný soubor nezapíše do evidence; nasazení skončí chybou |
| migrace selže uprostřed souboru | změny struktury (DDL) se nevrátí; soubor se při dalším nasazení pustí znovu od začátku |
| soubor proběhl, ale zápis do evidence selhal | skript skončí chybou; soubor se příště pustí znovu |
| migrace běží zároveň ještě jinde | skript čeká na zámek coolcms_sql_migrations 30 s, pak skončí chybou |
| obsah už aplikované migrace se změnil (checksum nesedí) | skript vypíše soubory, skončí kódem 1 a nespustí nic; nasazení skončí chybou |
| evidence na databázi neexistuje | bez --baseline skončí kódem 1, viz První běh |
| na serveru není PHP | migrace se nespustí a nasazení skončí chybou |
Po každém běhu, který spustil alespoň jednu migraci, skript sám smaže query cache a routovací
cache v Redisu (<prefix>cache:q:*, <prefix>cache:route:*, předvolba app:; host, port a
prefix čte z konfigurace). Migrace mění data mimo uložení přes managery, takže se žádný tag
nezneplatní a služby s delší platností cache (viz Platnost po službách)
by držely starý obsah až hodinu. Relace a verze tagů se nemažou. Bez Redisu (nebo s vypnutým
úložištěm) skript jen vypíše, že cache doexpiruje sama. Běh bez čekající migrace cache nemaže.
Selhání migrace nezabrání smazání Redis cache; jen celý krok skončí chybou, aby si
nasazení někdo všiml. Po opravě stačí nasadit znovu (nebo pustit php bin/migrate.php).
Pravidla pro psaní migrace#
| Pravidlo | Proč |
|---|---|
jméno YYYY-MM-DD_NN_popis.sql, rollback YYYY-MM-DD_<vyšší NN>_popis_rollback.sql |
rollbacky mají prefixy od 99 dolů a runner je přeskakuje |
migrace je opakovatelná (IF NOT EXISTS, INSERT IGNORE, NOT EXISTS) |
selhaný soubor se pouští znovu od začátku; DDL se v MariaDB potvrzuje implicitně, transakce nic nehlídá |
žádný příkaz DELIMITER |
soubor se posílá serveru vcelku (multi_query), DELIMITER je příkaz klienta a tam nefunguje |
prefix YYYY-MM-DD_NN je jednoznačný |
pořadí se řídí celým názvem souboru; dva skripty se stejným prefixem se řadí podle zbytku názvu, což je náhoda |
| už nasazený soubor neupravuj | runner porovná checksum a při neshodě zastaví celé nasazení. Oprava = nová migrace; změnu bez vlivu na databázi (komentář) přijmi vědomě php bin/migrate.php --accept-changed — přepíše jen checksum v evidenci, nic nepouští znovu |
soubory s osobními údaji do sql/ nepatří |
rollbacky s původními daty (*_dev_pii_anonymization*) jsou mimo index gitu a v .gitignore; runner je navíc nikdy nepustí |
tests/Unit/Sql/SqlMigrationPrefixTest.php shodí test při nové kolizi prefixu
(historické kolize jsou v seznamu ALLOWLIST) a tests/Unit/Sql/SqlMigrationPlannerTest.php
hlídá výběr, pořadí, základní stav a hlášení změněných i chybějících souborů.
Závislost mezi migracemi vyjadřuje jen číslo
Pořadí řídí jen název. Potřebuje-li migrace B proběhnout po migraci A, dej jí vyšší NN
— nic jiného závislosti nevyjadřuje.
Migrace z 1.–2. 10. 2026#
Řazeno tak, jak je runner pustí (podle názvu). _01 až _07 spadají pod základní stav —
na čerstvě založené evidenci se nespouštějí, jen zapisují.
| Soubor | Poznámka |
|---|---|
2026-10-01_01_blog_article_news_region.sql |
region u článků a novinek |
2026-10-02_01 … _05 |
externí připojení: tabulka, Api oprávnění, admin stránka, recaptcha_v3, čtyři Google typy |
2026-10-02_06_remove_google_settings.sql |
smaže staré řádky nastavení; po kódu, ručně (viz výš) |
2026-10-02_07_admin_discussion_message_reports.sql |
admin stránka nahlášených příspěvků |
2026-10-02_08_response_mark_read_pages.sql |
stránky 2011 a 2012, routy — první, kterou runner pouští sám |
…_09_response_mark_read_privilege.sql |
klíče markReadByKey |
…_10_response_mark_read_email_button.sql |
tlačítko v e-mailových šablonách |
…_11_response_read_key_column.sql |
sloupec read_key u reakcí |
…_12_response_receiver_role.sql |
role receiver; nahrazuje grant z _09 |
…_13_rating_save_reply_receiver.sql |
Rating:saveReply z owner na receiver |
…_14_message_thread_owner_receiver.sql |
role nad vlákny zpráv |
…_15_message_read_receipts_setting.sql |
přepínač potvrzení o přečtení |
…_16_message_thread_participants_admin.sql |
správa účastníků vlákna v administraci |
…_17_demo_admin_password_repair.sql |
vrací heslo správce veřejnému demu; běží jen na jeho databázi a jen když je heslo prázdné |
…_18_admin_roles_page_href_lowercase.sql |
adresy rolí, oprávnění a skupin malými písmeny |
Rollback k migraci najdeš podle popisu v názvu: …_<vyšší číslo>_popis_rollback.sql
(pro tuhle dávku 2026-10-01_91 a 2026-10-02_82 až _99).
Po ruční aplikaci smaž temp/cache a vyprázdni Redis
Nasazení maže cache samo. Pustíš-li migraci ručně (rollback, oprava), RBAC, router,
mapa hezkých URL a metadata Doctrine se cachují dál. Skripty, které přidávají stránky
nebo routy, neinvalidují route-table cache (klíč app:cache:route:table) — bez
smazání se nová routa nenačte. Mazání klíčů viz výš.
Provozní požadavky serveru#
| Co | Požadavek | Proč |
|---|---|---|
PHP-FPM memory_limit |
aspoň 256M; pro plný strop obrázků 40 Mpx kolem 320M | kompilace DI kontejneru po nasazení má ve webovém požadavku špičku ~128 MB — se 128M padaly první požadavky na 500. Obrázek se ověřuje odhadem paměti proti volné části limitu; se 128M projdou obrázky jen do ~12–15 Mpx |
CLI memory_limit |
vyšší než FPM, nebo -1 |
kompilace v CLI (bin/migrate.php, jiný kontejner) potřebuje ~200 MB; pád bin/*.php na 128M není chyba aplikace |
ffmpeg / ffprobe |
nainstalované | video se před uložením ověřuje obsahem; bez ffprobe se video odmítne (zápis do log/assets.log), obrázky fungují dál |
| Redis | Restart=always v systemd a hlídání |
výpadek už web neshodí — viz níž — ale query cache a ES jedou bez něj pomaleji |
logrotate |
druhá pojistka vedle cronu log/purge-old |
viz Rotace logů |
Relace přežijí výpadek Redisu
Když Redis při čtení relace selže, TieredTtlSessionHandler přepne relace toho požadavku
do souborů v temp/sessions (volba sessionTtl.fallbackDir) a další požadavek zkusí Redis
znovu. Do warning.log jde jeden řádek za hodinu, po návratu Redisu řádek do info.log.
Přihlášení z doby výpadku po návratu propadnou. Podrobně v
Cache.
Webroot se neodvozuje
App\Bootstrap nastavuje %wwwDir% napevno na <kořen>/www. Dřív ho nette/bootstrap
dopočítával z nejvnějšího rámce zásobníku, takže skript spuštěný odjinud (harness, runner)
skládal cesty o úroveň vedle a zapisoval do www/www/. Existující soubor pod www/www/
vrací www/.htaccess jako 403.
Více instalací na jednom serveru#
Na jednom serveru může běžet víc instalací CoolCMS, třeba ostrý web a jeho veřejné demo.
Databázi, soubory, temp/ a logy má každá instalace vlastní. Redis, Elasticsearch
a cookies v prohlížeči ale sdílejí, dokud se v local.neon neoddělí. Klíče v Redisu
totiž nenesou žádné označení instalace: jwt:denylist:…, ratelimit:…, app:cache:…,
Contributte.Storage:… i relace pod holým session ID jsou u všech instalací stejné.
Před nasazením další instalace na tentýž server zkontroluj local.neon
Se stejnou konfigurací si instalace přepisují cache a navzájem odhlašují uživatele. Zneplatňují si tokeny API, sčítají si limity požadavků a zapisují do stejných vyhledávacích indexů: dokument s id 5 jedné instalace přepíše dokument s id 5 druhé. Nic z toho se nehlásí, projeví se to až podivným chováním webu.
Nastavení v parameters |
Výchozí | Co odděluje | Kdy změnit |
|---|---|---|---|
redis.database |
0 |
celý obsah Redisu: cache, relace, JWT deny-list, limity požadavků, stav Elasticsearch | každá instalace se zapnutým Redisem má jiné číslo (Redis jich má ve výchozím stavu 16, 0–15) |
elasticsearch.indexPrefix |
cms |
indexy a aliasy <prefix>_<zdroj>, klíče es:<prefix>:… v Redisu |
každá instalace se zapnutým Elasticsearch; jen malá písmena a číslice |
sessionName |
coolsoftcms |
jméno cookie relace | instalace na stejné doméně druhého řádu (firma.cz a demo.firma.cz), i bez Redisu |
sessionCookieDomain |
domain |
domain posílá cookie relace celé doméně druhého řádu včetně subdomén, null jen aktuálnímu hostu |
u druhé a další instalace na téže doméně stačí null |
Druhá instalace na stejném serveru a stejné doméně má v config/Shared/local.neon například:
parameters:
redis:
database: 1
elasticsearch:
indexPrefix: 'firma'
sessionName: 'firmaweb'
sessionCookieDomain: null
První instalace může zůstat na výchozích hodnotách. Změna redis.database nebo indexPrefix
u instalace, která už běží, znamená prázdnou cache, odhlášení všech uživatelů a nový reindex
všech zdrojů Elasticsearch.
Volbu prefix z Predisu nepoužívej
Predis umí ke všem klíčům přidat prefix (redis.connection.default.options.prefix), ale
nepřidá ho do vzoru u SCAN. Mazání vlastní cache by pak nic nenašlo a mazání podle
KEYS by prefix přidalo dvakrát. Instalace se oddělují jen číslem databáze.
Instalace bez Redisu a Elasticsearch#
Menší web Redis ani Elasticsearch nepotřebuje. S tímto nastavením se k nim instalace vůbec nepřipojí, takže sdílený Redis a cluster sousedních instalací nijak neovlivní:
parameters:
redis:
storage: false # Nette cache do souborů v temp/
sessions: false # relace jako běžné PHP sessions
jwt:
useRedis: false # deny-list tokenů v databázi; limity požadavků se přepnou s ním
elasticsearch:
enabled: false
cache:
config:
enabled: false
routerEnabled: false
cache.config.enabled a routerEnabled jsou ve výchozím stavu vypnuté, uvedené jsou pro
jistotu. Když jsou obě vypnuté, nezapisují se do Redisu ani verze tagů a statistiky cache.
sessionName je potřeba i tady, protože jde o cookie v prohlížeči, ne o Redis.
Relace v souborech: každý web vlastní složku
Bez Redisu ukládá PHP relace do session.save_path. Sdílejí-li dva weby jednu složku,
úklid starých relací webu s kratší životností smaže relace i druhému webu. Ověř, že má
každý PHP-FPM pool vlastní php_admin_value[session.save_path]:
Nasazení a ruční mazání cache#
| Kde | Jak zná číslo databáze |
|---|---|
deploy/deploy-demo.sh |
proměnná DEMO_REDIS_DB v deploy/deploy-demo.local.env (výchozí 0); maže redis-cli -n <číslo> |
bin/migrate.php |
čte redis.database sám z konfigurace |
| ruční mazání | vždy s -n <číslo>: bez něj redis-cli pracuje s databází 0, tedy s jinou instalací |
Naplánované úlohy si každá instalace zakládá zvlášť, se svým tokenem. Týká se to i synchronizace a nočního reindexu Elasticsearch.
Sdílená kapacita serveru#
| Co | Proč to hlídat |
|---|---|
| OPcache | instalace na stejné verzi PHP-FPM sdílejí jednu OPcache. Jedna instalace má i se závislostmi kolem 15–20 tisíc souborů PHP, k tomu zkompilované šablony a kontejner. Pro dvě instalace zvyš opcache.max_accelerated_files aspoň na 65407 a opcache.memory_consumption úměrně, jinak se OPcache neustále přeplňuje a resetuje |
| paměť Redisu | maxmemory platí pro celý Redis. Nastav politiku noeviction: allkeys-lru by při zaplnění vyhazovala i relace a deny-list tokenů |
| heap Elasticsearch | indexy všech instalací jsou v jednom clusteru a jedné paměti |
| PHP-FPM | každý web má vlastní pool a vlastní pm.max_children; součet pracovních procesů se musí vejít do paměti serveru |
Kontrola oddělení#
redis-cli INFO keyspace # každá instalace má vlastní řádek db<číslo>
curl -s -u elastic 'http://127.0.0.1:9200/_cat/aliases?v' # aliasy začínají prefixem instalace
V prohlížeči se přihlas do obou instalací a přejdi mezi nimi. Ani jedna nesmí uživatele odhlásit a každá musí mít cookie relace s vlastním jménem.
PHP 8.5#
Kód je ověřen na PHP 8.4 a 8.5: php8.5 -l nad celým app/ (5 821 souborů) bez chyb,
composer why-not php 8.5.0 bez překážky a po opravě níž prošel proklik frontu i
administrace. Unit testy pod 8.5 zatím nebyly spuštěny (lokál běží na 8.4).
Redeklarace injektované vlastnosti v traitu shodí všechny stránky presenteru
AddAdvertFormTrait (Bazaar, Comcat) deklaroval public CurrencyManager $currencyManager;
bez #[Inject], zatímco BasePresenter tutéž vlastnost dědí s #[Inject]. PHP 8.5
při reflexi vrací deklaraci potomka (bez atributu), takže ji Nette nenaplnilo a každá
stránka AdvertPresenter padla na must not be accessed before initialization. Na 8.4 to
fungovalo. Oprava: deklaraci z traitu odstranit. Sken celého app/ jiný případ nenašel.
Pravidlo: trait nebo potomek nesmí znovu deklarovat vlastnost, kterou předek injektuje. Potřebuješ-li ji, použij tu zděděnou.
Při podezření na „PHP 8.5“ nejdřív vylučuj zastaralý kontejner
Stejná hláška vzniká i při nasazení se starým DI kontejnerem (viz výš). Rozliší se tím, že
po smazání temp/ zmizí; skutečná redeklarace zůstane.
Produkční nastavení#
| Věc | Nastavení |
|---|---|
| Ladicí režim | vypnutý |
| Zobrazování chyb | vypnuté, logovat |
| Mezipaměť | zapnutá |
docs/sql/ |
nedostupné zvenčí |
| Přístupy k branám | jen v local.neon |
www/uploads/ |
bez spouštění skriptů |
Ladicí panel vypisuje konfiguraci včetně přístupových údajů
Zapnutý na produkci znamená, že klíče k platebním branám a dalším službám jsou ve zdroji stránky. Ověřte, že je vypnutý.
V www/uploads/ se nikdy nespouští kód
Nahrané soubory leží ve veřejném stromu a názvy si zachovávají příponu. Aplikace
odmítá nebezpečné přípony (php, phtml, phar, cgi, sh, .htaccess …) v kterékoli
části názvu, ale webový server je druhá pojistka.
Apache čte verzovaný soubor www/uploads/.htaccess (vypnutý PHP engine, odmítnuté
skriptové přípony). Vyžaduje AllowOverride All (nebo alespoň FileInfo Options Limit)
pro adresář webu.
nginx .htaccess nečte. Do bloku server doplňte před obecné location ~ \.php$:
Po nasazení ověřte: nahraný test.php.txt v uploads/ se vrátí jako text, ne jako
výstup skriptu.
Jak se rozhoduje o ladicím režimu#
Produkční výchozí stav je vypnuto; zapíná se explicitně. Rozhoduje
App\Bootstrap::resolveDebugMode() v tomhle pořadí:
- proměnná prostředí
NETTE_DEBUG—1,true,on,yeszapnou, cokoli jiného včetně0afalsevypne, - existence souboru
config/debug-mode.flag(v repu není; na dev serveru ho vytvořítetouchem), - jinak vypnuto.
Proměnná prostředí přebíjí flag soubor
Když je NETTE_DEBUG nastavená na cokoli mimo povolenou čtveřici, flag soubor už
se nečte. Na stroji, kde je proměnná nastavená omylem, proto touch nepomůže.
Sestavení assetů#
Používejte globální sass, ne npx sass
Jsou to jiné verze a produkují jiný výstup. Rozdíl se projeví jako drobné posuny v rozvržení, které nikdo nespojí s nasazením.
Nasazení na existující databázi#
Když se povyšuje starší instalace, nedědí se jen data, ale i vady schématu,
které v ní léta nikdo neviděl. Projděte proto před nasazením
Kontrolu databáze — jsou to samé SELECTy, dají se
pustit i na produkci.
Nejdůležitější je první dotaz: vypíše překladové tabulky *_texts, kterým chybí
složený primární klíč. Bez něj databáze pouští duplicitní překlady a každý
další běh seedu je přidává.
Automatické kontroly tuhle vadu nenajdou
SchemaValidator::validateClass porovnává sloupce a typy, ne indexy —
chybějící primární klíč mu projde. Zjistí se to jedině tímhle dotazem, nebo
až tím, že se na webu ukazuje jiný text než v administraci.
Postup, když audit něco najde, i vzorové opravné skripty jsou v té kapitole.
Kontrola po nasazení#
Smoke test ze skriptu ověří jen to, že se stránky vykreslí. Po nasazení projdi ještě:
- Titulní stránka a přihlášení.
- Jeden výpis v administraci a jeden formulář.
- Zkušební objednávka až po odeslání.
- Fronta e-mailů — odchází?
- Log chyb — přibylo něco?
- Kontrola databáze — u nasazení na existující databázi znovu, ať se ověří, že migrace nezavedly duplicitní překlady.
- Běží-li na serveru další instalace, kontrola oddělení — vlastní databáze Redisu, prefix indexů a jméno cookie relace.
Zkušební objednávka je nejlepší test
Projde přes košík, platbu, sklad, fakturaci a e-maily naráz. Jeden průchod otestuje víc než hodina klikání po obrazovkách.