Lehký PHP deployer pro nahrávání aplikací na server přes FTP/SFTP. Depka porovnává soubory pomocí MD5 kontrolních součtů, takže nahrává jen to, co se od posledního deploye změnilo. Bez externích závislostí — jen nativní PHP funkce (žádné WinSCP, žádný rsync, žádný composer balík za běhu).
Název je zkratka Deploy Appka.
- Vlastnosti
- Požadavky
- Instalace
- Rychlý start
- Použití a volby
- Jak to funguje
- Konfigurace
deploy.neon— kompletní reference - Maintenance mód
- Bezpečnost
- Testy
- Licence
- Inkrementální deploy — porovnání přes MD5 checksum, nahraje se jen změněné
- Atomický dvoufázový upload — soubory se nejdřív nahrají pod dočasným názvem
.depkatmpa teprve pak hromadně přejmenují; při chybě zůstanou ostré soubory nedotčené - Mazání sirotků — soubory smazané lokálně se smažou i na serveru (volitelné)
- Přejmenování / přesun — nahrání souboru na serveru pod jiným názvem (např. env‑specifický config →
config/local.neon) - Purge složek — promazání např. cache po nahrání
- After‑upload hooky — zavolání HTTP URL po úspěšném deployi (cache clear apod.)
- Maintenance mód — po dobu deploye drží na serveru soubor, podle kterého aplikace vrací stránku údržby
- Ověření host key — ochrana SFTP proti MITM
- Potvrzení produkce — server pojmenovaný
productionse před nahráním zeptá - Upozornění na migrace — pokud jsou mezi nahranými soubory Doctrine migrace, Depka to připomene
- Dvojjazyčné hlášky — čeština a angličtina (
--lang=cs|en)
- PHP 8.0+ (CLI)
- Pro SFTP: rozšíření
ext-ssh2(pecl install ssh2,apt install php-ssh2,brewbuild…) - Pro FTP/FTPS: rozšíření
ext-ftp(obvykle už součást PHP)
Ověř dostupnost:
php -m | grep -E 'ssh2|ftp'Depka počítá s tím, že leží v podsložce projektu (typicky deploy/) — kořen projektu určuje jako nadřazenou složku (dirname(__DIR__)), takže local: . míří na kořen projektu.
muj-projekt/
├── deploy/
│ ├── depka.php
│ ├── DepkaDeployer.php
│ ├── NeonParser.php
│ ├── Lang.php
│ ├── lang/
│ │ ├── cs.php
│ │ └── en.php
│ ├── deploy.sample.neon
│ └── deploy.neon ← tvoje konfigurace (v .gitignore!)
├── app/
└── ...
Varianta A — kopie souborů (nejjednodušší):
# v kořeni projektu
mkdir -p deploy
# zkopíruj do deploy/ soubory: depka.php, DepkaDeployer.php, NeonParser.php,
# Lang.php a složku lang/ (např. stažením z GitHub Releases)Varianta B — git:
git clone https://github.com/svatekr70/depka.git deploy
rm -rf deploy/.git # nechceš vnořený gitPak vytvoř konfiguraci z ukázky a vyplň ji:
cp deploy/deploy.sample.neon deploy/deploy.neon
chmod 600 deploy/deploy.neon # obsahuje hesla
$EDITOR deploy/deploy.neonA přidej deploy/deploy.neon a deploy/checksums_*.json do .gitignore svého projektu.
První nasazení má dva kroky, aby Depka zbytečně nenahrála celý projekt:
# 1) Náhled – co by se nahrálo/smazalo (nic nezmění)
php deploy/depka.php staging --test
# 2) Ostré nasazení
php deploy/depka.php stagingPokud už soubory na serveru jsou (nasadils je dřív ručně), řekni Depce, ať aktuální stav vezme jako výchozí — a nahraje pak jen budoucí změny:
php deploy/depka.php staging --init # jen uloží checksumy, nic nenahrajephp deploy/depka.php <server> [volby]# Náhled změn (nic se nenahraje)
php deploy/depka.php staging --test
# Ostrý deploy
php deploy/depka.php staging
# Deploy na produkci (vyžádá si potvrzení "ano/yes")
php deploy/depka.php production
# Nahrát vše znovu (ignorovat uložené checksumy)
php deploy/depka.php staging --full
# Synchronizovat jen podsložku (nemaže sirotky, nepurguje)
php deploy/depka.php staging --path=modules/Client
# Podrobný výstup (vypíše každý soubor)
php deploy/depka.php staging -v
# Seznam serverů z deploy.neon
php deploy/depka.php --list
# Anglicky
php deploy/depka.php staging --lang=en| Volba | Popis |
|---|---|
--test, -t |
Pouze náhled změn, bez nahrání |
--init |
Inicializace checksumů bez nahrání (naučí Depku aktuální stav) |
--full |
Ignorovat uložené checksumy, nahrát vše |
--path=PATH |
Synchronizovat pouze podsložku (vypne mazání sirotků i purge) |
--verbose, -v |
Podrobný výstup |
--list |
Vypsat dostupné servery |
--config=FILE |
Použít jiný konfigurační soubor (výchozí deploy.neon) |
--lang=cs|en |
Jazyk hlášek |
--no-purge |
Přeskočit promazání složek (purge) |
--no-hooks |
Přeskočit after‑upload hooky (afterUpload) |
--purge-only |
Provést jen promazání složek (purge) — nic se neskenuje ani nenahrává, baseline zůstává nedotčená |
--help, -h |
Nápověda |
Návratový kód: 0 = úspěch, 1 = chyba (vhodné pro CI/skripty).
Depka si pro každý server ukládá checksums_<server>.json (MD5 každého nahraného souboru) do své složky. Při každém spuštění:
- naskenuje lokální soubory (s ohledem na
ignore), - spočítá jejich MD5,
- porovná s uloženými checksumy → rozhodne, co nahrát / smazat / přeskočit,
- soubory nahraje dvoufázově (
.depkatmp→ hromadné přejmenování = atomický swap), - uloží nové checksumy, pak spustí
purgea až potéafterUpload(vizafterUploadapurge).
Celkové pořadí kroků: upload → rename → purge → afterUpload.
Se zapnutým maintenance módem celý tenhle blok obalí zapnutí a vypnutí stránky údržby: maintenance ON → upload → rename → purge → afterUpload → maintenance OFF.
Proto po prvním nasazení (nebo na čerstvé kopii projektu) spusť --init — jinak Depka nemá s čím porovnávat a nahraje vše.
Poznámka: checksumy jsou lokální stav. Když deployuješ stejný projekt z více strojů, každý má vlastní
checksums_*.json. Buď je sdílej, nebo na novém stroji jednou spusť--init.
Konfigurace je v NEON formátu (odsazení mezerami, ne taby). Kompletní komentovaná ukázka: deploy.sample.neon.
| Klíč | Typ | Výchozí | Popis |
|---|---|---|---|
lang |
cs | en |
cs |
Jazyk hlášek (přepíše ho --lang) |
tempDir |
cesta | složka s depka.php |
Kam ukládat checksums_*.json |
defaults |
blok | — | Výchozí hodnoty společné všem serverům (server je může přepsat) |
servers |
mapa | — | Definice serverů: název: { … } |
Hodnoty z defaults se slijí s konkrétním serverem; server má přednost.
| Klíč | Typ | Výchozí | Povinné | Popis |
|---|---|---|---|---|
protocol |
sftp | ftp | ftps |
sftp |
Přenosový protokol. sftp = přes ext-ssh2, ftp/ftps = přes ext-ftp |
|
host |
string | — | ✅ | Hostname nebo IP serveru |
port |
int | 22 (sftp) / 21 (ftp) |
Port | |
user |
string | — | ✅ | Přihlašovací jméno |
password |
string | — | ⬥ | Heslo. Vyžadováno, pokud nepoužíváš privateKey |
privateKey |
cesta | — | ⬥ | SSH privátní klíč (jen SFTP). Veřejný klíč se odvodí (.pub). Má přednost před heslem |
hostKey |
MD5/SHA1 hex | — | Otisk serveru pro ověření identity (jen SFTP) — viz Bezpečnost | |
local |
cesta | — | ✅ | Lokální složka k nahrání. . = kořen projektu; relativní k němu, nebo absolutní |
remote |
cesta | — | ✅ | Cílová složka na serveru |
ignore |
seznam | [] |
Co nenahrávat (viz níže) | |
rename |
seznam | [] |
Nahrát soubor pod jiným názvem (viz níže) | |
purge |
seznam složek | [] |
Složky, jejichž obsah se po nahrání smaže (např. cache). Běží před afterUpload |
|
afterUpload |
seznam URL | [] |
HTTP(S) URL zavolané po nahrání a po purge (webhooky / zahřátí aplikace) |
|
afterUploadTimeout |
int (s) | 60 |
Timeout jednoho afterUpload requestu |
|
delete |
bool | true |
Mazat na serveru soubory, které lokálně už nejsou (mirror) | |
permissions |
octal | blok | — | Práva nahraných souborů/složek (viz níže) | |
maintenance |
blok | — | Stránka údržby po dobu deploye (viz níže) |
⬥ = použij password nebo privateKey.
Podporuje wildcardy * a ?. Pozor: ignorované položky jsou vyloučené i z mazání sirotků — Depka je na serveru nechá být.
ignore:
- .git # přesná shoda názvu (soubor i složka)
- vendor # celá složka a její obsah
- temp/* # jen OBSAH složky temp (samotnou temp ne)
- "*.log" # wildcard – všechny .log soubory
- config/local.neon # konkrétní soubor
- deploy*.neon # wildcard v názvuUžitečné pro env‑specifické configy. Depka reálně řeší režim move (zdroj se nahraje rovnou pod cílovým názvem); copy se ignoruje.
rename:
- from: config/app.staging.neon # lokální soubor
to: config/local.neon # název na serveru
mode: moveDvě varianty:
# a) skalární – na složky 0755, soubory automaticky bez execute (0644)
permissions: 0755
# b) strukturované (doporučeno) – složky a soubory zvlášť
permissions:
dir: 0755
file: 0644Když permissions vynecháš, Depka chmod neprovádí (nechá výchozí práva serveru).
| Hodnota | Přenos | Port | Poznámka |
|---|---|---|---|
sftp (výchozí) |
ext-ssh2 (SSH) |
22 |
Doporučeno. Podporuje privateKey a hostKey |
ftp |
ext-ftp |
21 |
Plain FTP (nešifrované) |
ftps |
ext-ftp + TLS |
21 |
Explicit AUTH TLS — šifrované FTP na standardním portu 21 |
FTPS (protocol: ftps) se hodí, když je SSH port zablokovaný (fail2ban, firewall), ale šifrovaný přenos je potřeba. Depka naváže explicit TLS (AUTH TLS) na řídicím kanálu, přenosy jedou v pasivním módu.
servers:
production:
protocol: ftps # explicit AUTH TLS na portu 21
host: 185.111.88.183
port: 21
user: deploy
password: "..."
local: .
remote: /var/www/app
# hostKey se u ftp/ftps NEPOUŽÍVÁ – je jen pro sftpDatový kanál u FTPS: pokud login proběhne, ale přenosy (upload/výpis) v pasivním módu selžou, jde o TLS session na datovém kanálu:
data_accept: failed to retrieve the existing SSL session— server na řídicím kanálu nenabídl znovupoužitelnou TLS session. PHP (ext-ftp) ji pro datový kanál vyžaduje, takže s takovým serverem FTPS přes PHP nefunguje; použijsftp, případně na serveru zapni TLS session tickety.425 Unable to build data connection/Operation not permittedu ProFTPD — server naopak reuse vynucuje způsobem, který PHP nezvládne; povolTLSOptions NoSessionReuseRequired.Certifikát bývá self-signed — Depka ho neověřuje proti CA, což je pro tento účel v pořádku.
Pořadí je pevné: nejdřív purge, pak afterUpload. Depka smaže obsah purge složek a teprve potom zavolá afterUpload URL.
# Smaže OBSAH těchto složek na serveru po nahrání (běží PRVNÍ)
purge:
- temp/cache
# HTTP GET zavolané po nahrání a PO purge (běží DRUHÉ)
afterUpload:
- https://staging.example.com/deploy-hook.php?action=clear-cacheObojí lze pro jeden běh přeskočit: --no-hooks, resp. --no-purge.
Občas potřebuješ jen promazat cache na serveru bez nasazování (i když se nic nezměnilo). Na to je --purge-only — provede pouze větev purge, nic neskenuje ani nenahrává a nesahá na baseline:
php deploy/depka.php production --purge-onlyFrameworky drží v temp/ zkompilovaný DI container a cache. Po nasazení nového kódu je potřeba je promazat (purge: [temp/cache]) — jenže první HTTP request pak container znovu kompiluje: je pomalý a během kompilace může vracet HTTP 500. Aby to netrefil reálný uživatel, ať web GETne sama Depka hned po purge. Kompilace proběhne na její request; reální uživatelé už dostanou „zahřátý" web.
Proto se URL webu uvádí typicky dvakrát — první request spustí kompilaci, druhý potvrdí, že je aplikace „warm":
purge:
- temp/cache
afterUpload:
- https://app.example.com/ # spustí rekompilaci (může vrátit 500 – to je OK)
- https://app.example.com/ # potvrdí, že je container zahřátý
# Volitelně, pokud kompilace trvá dlouho (výchozí 60 s):
afterUploadTimeout: 120Pozn.:
afterUploadpoužíváignore_errors, takže i odpověď 500 se počítá jako úspěch — cílem je spustit kompilaci, ne dostat 200. Jako selhání se bere jen nedostupné spojení / timeout.Se zapnutým maintenance módem by tím ale jako úspěch prošel i 503 ze stránky údržby — na to je
warmupToken.
Deploy trvá desítky sekund a po celou tu dobu je na serveru půlka starého a půlka nového kódu. Maintenance mód tomu předchází: Depka na začátku vytvoří na serveru soubor a na konci ho zase smaže. Vlastní vypnutí webu si řeší aplikace — Depka ten soubor jen vytvoří a smaže, co na něj web naváže ji nezajímá.
maintenance:
enabled: true # výchozí false — bez tohohle klíče se nic nezmění
file: .maintenance # cesta relativní k `remote`Blok patří k serveru nebo do defaults. Když ho vynecháš (nebo necháš enabled: false), chová se Depka přesně jako dosud a žádný soubor nevytváří.
Typické pravidlo na straně aplikace (Apache, .htaccess):
RewriteCond %{DOCUMENT_ROOT}/.maintenance -f
RewriteCond %{QUERY_STRING} !(^|&)warmup=TAJNY-TOKEN($|&)
RewriteRule ^ /maintenance.php [R=503,L]- Zapíná se hned po úspěšném připojení, ještě před první fází uploadu.
- Vypíná se až úplně nakonec, po
afterUploadhoocích. Ne dřív:purgesmažetemp/cache, takže první request po něm musí znovu zkompilovat DI container. Celý smyslafterUploadje, že si tenhle pomalý request vezme na sebe Depka — kdyby maintenance zhasl před zahřátím, uživatelé by trefili právě ten studený container.
Vypnutí běží vždy, i když deploy spadne. Spadlé spojení, timeout, chyba při přejmenování ve fázi 2 — ve všech případech soubor zmizí. (Spojení už v tu chvíli neexistuje, purge ho na svém konci zavírá, takže si ho krok vypnutí zakládá znovu.)
Když se soubor nepodaří smazat ani na druhý pokus, skončí deploy chybou (ne varováním) a do logu i do souhrnu se vypíše přesná cesta:
!!! POZOR: WEB ZUSTAVA V MAINTENANCE REZIMU !!!
Smaz na serveru rucne tento soubor: /var/www/app/.maintenance
Když se maintenance naopak nepodaří zapnout, deploy se vůbec nerozjede — půlka nahraného kódu na živém webu je horší než neproběhlý deploy.
afterUpload používá ignore_errors, takže za selhání se počítá jen nedostupné spojení. Se zapnutým maintenance módem tím vzniká tichá past: zahřívací GET dostane 503 ze stránky údržby, Depka to vyhodnotí jako OK — ale container zahřátý není.
Zahřívání proto potřebuje z maintenance pravidla výjimku. Výjimka na IP administrátora je křehká (změní se IP a zahřívání se tiše přestane dít); warmupToken na IP nezávisí:
maintenance:
enabled: true
file: .maintenance
warmupToken: "nahodny-dlouhy-retezec"Depka token připojí do všech afterUpload URL jako ?warmup=<token> (existující query i #fragment zůstanou v pořádku). Web si na něj udělá výjimku — viz RewriteCond %{QUERY_STRING} v ukázce výše. V logu se token vypisuje maskovaný (?warmup=***), aby se tajemství neválelo v konzoli a CI výstupu.
Token se do URL doplňuje nezávisle na enabled, aby se tvar afterUpload URL neměnil podle toho, jestli je zrovna mód zapnutý. Když ho nenastavíš a zahřívací hook trefí stránku údržby, Depka na to aspoň upozorní varováním.
--test,--inita--purge-onlyse ho nedotknou — první dva nic nenahrávají,--purge-onlyvrací výsledek dřív, než se vůbec dojde k uploadu.--path=…ho vypíná (a řekne to).remotev tom režimu míří do podsložky, takže by soubor vznikl tam a web by ho v kořeni nenašel — mód by byl tiše neúčinný.- Když není co nahrávat ani mazat, deploy končí dřív než u připojení; nespustí se
purgeaniafterUpload, takže není co chránit. - Soubor se nikdy nedostane do
checksums_*.json. Mazání vzdálených souborů se odvozuje z uložených checksumů, ne z výpisu serveru — kdyby v baseline skončil, příští deploy by ho vyhodnotil jako „soubor, co lokálně zmizel". Když stejně pojmenovaný soubor leží i lokálně, Depka ho z deploye vynechá a napíše varování.
deploy.neonobsahuje hesla v plaintextu. Je v.gitignore— nikdy ho necommituj. Do repozitáře patří jendeploy.sample.neons placeholdery.- Nastav souboru restriktivní práva:
chmod 600 deploy/deploy.neon. - Preferuj autentizaci SSH klíčem (
privateKey) před heslem. - Ověření host key (SFTP, ochrana proti MITM): nastav u serveru
hostKeyna MD5/SHA1 otisk — Depka ho před přihlášením ověří a při neshodě deploy zastaví. KdyžhostKeynevyplníš, Depka při připojení otisky serveru jen vypíše, ať si je můžeš „pinnout" do configu.
Testy používají Nette Tester (jen dev závislost):
composer install # nainstaluje Nette Tester do vendor/
composer test # spustí neseťovou sadu (nebo: vendor/bin/tester -C tests)Pokryté je celé jádro bez sítě: parser NEON konfigurace, jazyková vrstva, porovnávání ignorovaných vzorů, mapování rename, řešení cest, převod práv, detekce migrací, diff engine (režimy --init/--test), perzistence checksumů a CLI (nápověda, --list, validace, exit kódy).
Síťové cesty (připojení, upload, přepis, mazání, host key) se testují naostro proti reálným serverům v Dockeru. Lokálně se automaticky přeskočí, dokud servery neběží:
docker compose up -d # nastartuje SFTP + FTP server
DEPKA_INTEGRATION=1 composer test:integration # spustí integrační sadu
docker compose downBez DEPKA_INTEGRATION=1 (nebo když servery neběží) se integrační testy jen skípnou, takže composer test zůstává rychlý a bez závislostí. V CI (GitHub Actions, viz .github/workflows/ci.yml) běží obě sady na každý push.
MIT © Rudolf Svátek