Pieni lähiravintola – tekninen projektikuvaus
Tämä projekti on anonymisoitu asiakastoteutus, jossa rakensin pienen ravintolan verkkosivut alusta alkaen ilman valmista julkaisujärjestelmää. Toteutus pyörii perinteisessä webhotelliympäristössä ja koostuu kevyestä PHP/HTML/CSS-koodista, erillisestä admin-näkymästä sekä varaus- ja lomakelogiikasta.
Ympäristö ja rajoitteet
Projekti toimii jaetussa webhotelliympäristössä ilman root-oikeuksia. Tämä rajaa pois omat demonit ja kontit, joten ratkaisut on rakennettu “PHP + cron + rclone” -ajattelulla ja käyttäjän omilla shell-skripteillä.
- PHP 8.x (FPM/FastCGI), opcache käytössä suorituskyvyn tasaamiseksi
- käyttäjäkohtainen eristys (chroot-/cagefs-tyyppinen), ei pääsyä muiden tileihin
- ei systemd-timereita, ajastus
crontab-tasolla käyttäjän omilla skripteillä - HTTP/2-palvelu TLS:n päällä, reititys suoraan virtuaalihostin DocumentRootiin
- peruskovennus
.htaccess-tiedostoilla:Options -Indexes, admin- ja data-hakemistot rajattu ja merkittyX-Robots-Tag: noindex-otsikoilla - data tallennetaan SQLite-tiedostoihin, ei erillistä MySQL-instanssia tälle projektille
Arkkitehtuuri lyhyesti
Projektin perusrakenne (anonymisoitu mutta rakenteellisesti aito):
/home/<asiakas>/
public_html/ # Sivuston juuri (index.php, teemat, lomakkeet)
includes/
session_bootstrap.php # istunnot, peruskovennus, charset
astiastot_db.php # SQLite-kyselyt astiastovarauksiin
security-helpers.php # syötekäsittely, CSRF, header-sanitointi
admin/
astiavaraukset.php # admin-listaus ja hyväksyntälogiikka
login.php # yksinkertainen sessiopohjainen kirjautuminen
tools/
... # dokumentaatiot ja työkalu-skriptit
assets/
css/, img/, js/
data/
astiavaraukset.sqlite # varausdata (WAL + foreign keys päällä)
www-backups/ # päivittäiset www- ja SQLite-backupit
publattahattu-www-YYYY-MM-DD_HH-MM-SS.tar.gz
sqlite-astiavaraukset/
dr/
<home-snapshotit>
backup-www.sh # www-backup (tar + gzip)
prune-www-backups.sh # rotaatio (7 päivää)
sync-www-backups.sh # rclone-offsite (checksum + retry)
run-*-cron.sh # cron-ajojen "wrapperit" + mail-notifikaatiot
Tekniset päävalinnat
- Custom PHP 8 + HTML5 + CSS (ei CMS:ää, ei page builder -roskaa, ei ylimääräisiä plugineita)
- Responsiivinen layout ilman raskaita JS-frameworkeja – vain kevyt vanilla-JS niihin kohtiin missä sitä oikeasti tarvitaan
- Catering-tilauslomake, joka käyttää serveripuolen validointia ja lähettää tilaukset SMTP:n kautta ravintolan sähköpostiin (tekstipohjainen, helposti luettava formaatti)
- Astiasto- ja varauslogiikka SQLite-tietokannan päällä, taulurakenne normalisoitu (asiakas, varaus, astiastosetti) ja viiteavaimet enforceeraavat eheyden
- CRON-pohjainen backup- ja offsite-synkronointiketju (tar + rclone, checksum-vertailu)
- Erillinen DR-snapshot koko käyttäjäkansiosta (home) 8 viikon rotaatiolla, mikä parantaa RPO/RTO-tavoitteiden toteutumista ilman raskaita DR-työkaluja
Varmuuskopiointi ja DR-ketju
Päivittäinen backup vie talteen www-hakemiston ja varausten SQLite-datan
(RPO ≈ 24 h). Tämän päälle on rakennettu viikoittainen DR-snapshot,
joka tallettaa koko käyttäjäkansion lukuun ottamatta väliaika- ja lokihakemistoja.
Snapshot arkistoidaan www-backups/dr/-kansioon ja synkronoidaan rclonella
offsiteen, jolloin sekä levyvika että “ihmisen moka” voidaan peruuttaa.
Offsite-kopioissa hyödynnetään rclonen tarkistussummia (--checksum /
palvelukohtaiset hashit), ja ajot logitetaan erilliseen
rclone-sync.log-tiedostoon. DR-prosessi on testattu oikealla
palautusharjoituksella erilliseen test-restore/-hakemistoon.
#!/bin/bash
# run-dr-snapshot.sh – viikoittainen home-snapshot
set -euo pipefail
HOME_DIR="/home/<asiakas>"
DR_DIR="$HOME_DIR/www-backups/dr"
DATE="$(date +%F)"
TARGET="$DR_DIR/<asiakas>-home-${DATE}.tar.gz"
mkdir -p "$DR_DIR"
echo "=================================================="
echo "DR-snapshot ajo: $(date '+%Y-%m-%d %H:%M:%S')"
echo "Tallennuspolku: $TARGET"
echo "=================================================="
cd /home
tar -czf "$TARGET" \
--warning=no-file-changed \
--exclude="<asiakas>/tmp/*" \
--exclude="<asiakas>/logs/*" \
--exclude="<asiakas>/www-logs/*" \
--exclude="<asiakas>/www-backups/dr/*" \
"<asiakas>"
echo "DR-snapshot valmis: $TARGET"
Tietokanta ja varauslogiikka (PHP + SQLite)
Varausdatan käsittelyssä käytän erillistä tietokerrosta
(includes/astiastot_db.php), jossa kaikki kyselyt ovat
valmisteltuja (prepared statements) ja datatyypit rajataan tiukasti.
Tietokanta on suunniteltu niin, että se kestää pienen ravintolan arjen:
useita varauksia päivässä, peruutuksia ja tilojen päällekkäisyystarkistuksia.
<?php
// includes/astiastot_db.php (lyhennetty esimerkki)
declare(strict_types=1);
function get_astiadb(): PDO {
static $pdo = null;
if ($pdo instanceof PDO) {
return $pdo;
}
$dbPath = __DIR__ . '/../data/astiavaraukset.sqlite';
$pdo = new PDO('sqlite:' . $dbPath, null, null, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
// Peruskovennus: write-ahead logging ja viiteavaimet päälle.
$pdo->exec('PRAGMA journal_mode=WAL;');
$pdo->exec('PRAGMA foreign_keys=ON;');
$pdo->exec('PRAGMA synchronous=NORMAL;');
return $pdo;
}
function get_future_reservations(DateTimeImmutable $from): array {
$pdo = get_astiadb();
$sql = <<<SQL
SELECT id, set_id, customer_name, start_date, end_date, status
FROM reservations
WHERE date(end_date) >= :from
ORDER BY start_date ASC
SQL;
$stmt = $pdo->prepare($sql);
$stmt->execute([
':from' => $from->format('Y-m-d'),
]);
return $stmt->fetchAll();
}
Tietoturva ja laatu
- lomakkeissa CSRF-tokensuojaus + honeypot-kentät bottien varalta; serveripuolen validointi on ensisijainen, client-side vain lisätuki
- header-sanitointi (esim. sähköpostien otsikot stripataan CR/LF-merkeistä CRLF-injektion estämiseksi)
session_bootstrap.phphuolehtii istunnon asetuksista:session.cookie_httponly,session.cookie_secure,session.use_strict_modejne.- admin-näkymät rajattu salasanalla + palvelinpään IP/Directory-suojauksella (kerrosmalli, ei yksi lukko)
- perussuojaus HTTP-otsikoilla: mahdollisuus lisätä
X-Frame-Options,X-Content-Type-Options,Referrer-PolicyjaContent-Security-Policyilman, että fronttikoodi tarvitsee refaktoroida - backup-, prune-, sync- ja DR-ajot logitetaan erillisiin tekstilokeihin ja kootaan tarvittaessa zip-liitteeksi cron-sähköposteihin
- palautus on testattu oikealla “fire drill” -harjoituksella:
arkisto purettu erilliseen
test-restore/-hakemistoon ja sivusto käynnistetty sieltä
Mitä opin / missä tämä on hyödyllinen
Case toimii hyvänä esimerkkinä siitä, miten pienen yrityksen sivusto voidaan rakentaa niin, että ulkoasun lisäksi myös infra ja palautettavuus on mietitty:
- sisältö pysyy selkeänä myös ei-tekniselle henkilöstölle – hallintanäkymä on käyttöliittymältään yhtä monimutkainen kuin sähköposti
- tapahtumat ja tilaukset kulkevat yksinkertaisten lomakkeiden kautta sähköpostiin, ei monimutkaista workflow-moottoria, jota kukaan ei ylläpidä
- päivittäinen www-backup, SQLite-backup ja viikoittainen DR-snapshot muodostavat käytännössä kolmikerroksisen turvaverkon (online, nearline, offsite)
- offsite-varmistus ei ole “ehkä joskus”, vaan osa automaattista prosessia rclonen ja cron-ajojen kautta
- räätälöity ratkaisu on kevyempi ja läpinäkyvämpi kuin CMS + lisäosaviidakko, etenkin kun datamäärät ja liikenne ovat pienen ravintolan tasolla