Skip to content

Repository files navigation

Depka — Deploy Appka

CI License: MIT PHP

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.


Obsah


Vlastnosti

  • 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 .depkatmp a 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ý production se 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)

Požadavky

  • PHP 8.0+ (CLI)
  • Pro SFTP: rozšíření ext-ssh2 (pecl install ssh2, apt install php-ssh2, brew build…)
  • Pro FTP/FTPS: rozšíření ext-ftp (obvykle už součást PHP)

Ověř dostupnost:

php -m | grep -E 'ssh2|ftp'

Instalace

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ý git

Pak 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.neon

A přidej deploy/deploy.neon a deploy/checksums_*.json do .gitignore svého projektu.

Rychlý start

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 staging

Pokud 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 nenahraje

Použití a volby

php 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).

Jak to funguje

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í:

  1. naskenuje lokální soubory (s ohledem na ignore),
  2. spočítá jejich MD5,
  3. porovná s uloženými checksumy → rozhodne, co nahrát / smazat / přeskočit,
  4. soubory nahraje dvoufázově (.depkatmp → hromadné přejmenování = atomický swap),
  5. uloží nové checksumy, pak spustí purge a až poté afterUpload (viz afterUpload a purge).

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 deploy.neon — kompletní reference

Konfigurace je v NEON formátu (odsazení mezerami, ne taby). Kompletní komentovaná ukázka: deploy.sample.neon.

Kořenové klíče

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: { … }

Klíče serveru (a defaults)

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.

ignore — co nenahrávat

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ázvu

rename — nahrát pod jiným názvem

Už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: move

permissions — práva po nahrání

Dvě 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: 0644

Když permissions vynecháš, Depka chmod neprovádí (nechá výchozí práva serveru).

protocol — SFTP / FTP / FTPS

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 sftp

Datový 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žij sftp, případně na serveru zapni TLS session tickety.
  • 425 Unable to build data connection / Operation not permitted u ProFTPD — server naopak reuse vynucuje způsobem, který PHP nezvládne; povol TLSOptions NoSessionReuseRequired.

Certifikát bývá self-signed — Depka ho neověřuje proti CA, což je pro tento účel v pořádku.

afterUpload a purge

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-cache

Obojí 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-only

Zahřátí frameworkové aplikace (Nette / Symfony)

Frameworky 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: 120

Pozn.: afterUpload použí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.

Maintenance — stránka údržby po dobu deploye

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]

Kdy se zapíná a vypíná

  • 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 afterUpload hoocích. Ne dřív: purge smaže temp/cache, takže první request po něm musí znovu zkompilovat DI container. Celý smysl afterUpload je, ž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.

warmupToken — aby zahřívání nespadlo do vlastní pasti

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.

Co maintenance mód nedělá

  • --test, --init a --purge-only se ho nedotknou — první dva nic nenahrávají, --purge-only vrací výsledek dřív, než se vůbec dojde k uploadu.
  • --path=… ho vypíná (a řekne to). remote v 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 purge ani afterUpload, 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í.

Bezpečnost

  • deploy.neon obsahuje hesla v plaintextu. Je v .gitignore — nikdy ho necommituj. Do repozitáře patří jen deploy.sample.neon s 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 hostKey na MD5/SHA1 otisk — Depka ho před přihlášením ověří a při neshodě deploy zastaví. Když hostKey nevyplníš, Depka při připojení otisky serveru jen vypíše, ať si je můžeš „pinnout" do configu.

Testy

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).

Integrační testy (SFTP + FTP)

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 down

Bez 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.

Licence

MIT © Rudolf Svátek

About

Depka – lehký PHP deployer přes FTP/SFTP s inkrementálním nahráváním podle MD5

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages