# Web-sovelluksen tekeminen Yrttiserverin wizardille

Tämä ohje sopii uuden sovelluksen kehittämiseen ja olemassa olevan sovittamiseen.
Tavoite on tuottaa kehityskoneella tai pilviympäristössä **valmis ZIP-julkaisu**,
jonka voi tuoda wizardiin ilman palvelimella tehtävää käsityötä.
Sopimus on tarkistettu wizardin lähdekoodista 5.11.2026.

Julkinen kopio on osoitteessa
<https://ohjeet.yrttitarha.giize.com/help/application-publishing.md>.
Wizardin lähiverkkonäkymässä sama sisältö löytyy **Julkaisuohjeet ja AI-tehtävänanto** -linkistä.

Tekoälyavustajalle annettava, itsenäinen tehtävänanto on
[sovelluksen generointiohjeessa](./app-generation-prompt.md). Liitä se esimerkiksi
uuden projektin ensimmäiseen tehtävään tai olemassa olevan projektin julkaisutehtävään.
Ohjeen pitää olla avustajan saatavilla myös pilvessä; pelkkä paikallinen tiedostopolku ei riitä.

## 1. Valitse sovelluksen julkaisumalli

| Sovellus | Malli | Julkaisupaketti |
| --- | --- | --- |
| HTML/CSS/JavaScript, selaimessa toimiva peli, Vite/React ilman omaa backendia | `static` | Käännetyt selaintiedostot ja `yrtti.json` |
| Oma API, WebSocket, palvelinrenderöinti, palvelimella käytettävä salaisuus tai tietokantayhteys | `process` | Valmis Linux-palvelin, sen tarvitsemat tiedostot/riippuvuudet ja `yrtti.json` |
| Docker Compose, useita erillisiä palveluita, oma tietokantapalvelin tai taustatyöntekijä | Ei sellaisenaan tuettu | Tarvitsee erillisen ylläpitoratkaisun tai wizardin laajennuksen |

Älä lisää palvelinprosessia pelkkää paketointia varten, jos staattinen julkaisu riittää.
Älä myöskään muuta sovelluksen toimintoja julkaisemisen sivutyönä: esimerkiksi
pelihuoneet tai kirjautuminen ovat erillisiä tuotevalintoja.

Wizard ei rakenna lähdekoodia, aja `npm install`-/`pip install`-komentoja, asenna
runtimeja tai suorita paketin asennusskriptiä. Konttikuvaus voidaan tunnistaa
esitarkistuksessa, mutta kontin julkaisu on estetty.

## 2. Yhteinen pakettisopimus

ZIPin juuressa on täsmälleen nimetty `yrtti.json`. Älä lisää ympärille projektin
nimistä hakemistoa. Paketin sisäisissä poluissa käytetään `/`-merkkiä myös
Windowsissa luodussa ZIPissä. Tiedostonimien kirjainkoolla on Linuxissa merkitys.

| Kenttä | Arvo tai sääntö |
| --- | --- |
| `schemaVersion` | Kokonaisluku `1` |
| `id` | 2–31 merkkiä, ensimmäinen pieni kirjain, muut pieniä kirjaimia, numeroita tai `-`; regex `^[a-z][a-z0-9-]{1,30}$` |
| `displayName` | Ei tyhjä, enintään 100 merkkiä |
| `runtime.kind` | `static` tai `process` |
| `runtime.command` | Vain process: 1–32 ei-tyhjää argumenttia; enintään 2048 merkkiä/argumentti, ei ohjausmerkkejä |
| `healthPath` | Paikallinen HTTP-polku, oletus `/`; alkaa yhdellä `/`:llä, enintään 256 merkkiä, ei ohjausmerkkejä |
| `publicDirectory` | Static: julkisten tiedostojen suhteellinen hakemisto, oletus `public` |

Tunnukset `admin`, `deploy` ja `skillbuster` ovat varattuja. Sovellus julkaistaan
osoitteeseen `https://<id>.yrttitarha.giize.com/`. Säilytä sama `id` päivityksissä;
uusi tunnus tarkoittaa eri sovellusta. Wizard muodostaa julkaisuversiot itse.

**Älä keksi kuvaukseen lisäkenttiä.** Esimerkiksi `version`, `build`, `env`,
`ports`, `volumes`, `resources` tai `scripts` eivät kuulu sopimukseen.
Tuntematon JSON-kenttä hylkää paketin. Älä lisää myöskään kommentteja tai loppupilkkua.

Paketin enimmäiskoko on 250 MiB, purettu koko 1 GiB ja ZIP-entryjen määrä 10 000
(myös hakemistoentryt lasketaan). Kuvauksen enimmäiskoko on 64 KiB.
Linkit, erikoistiedostot, absoluuttiset polut, `..`-polut sekä päällekkäiset
nimet myös kirjainkoosta riippumatta hylätään. Kaikissa pakettipoluissa kielletään
myös `\`, `:`, ohjausmerkit ja tyhjät polkuosat. Static-hakemiston nimessä
sallitaan vain kirjaimet A–Z/a–z, numerot ja merkit `_`, `.`, `/`, `-`.

## 3. Staattinen sivusto tai selainpeli

Valmis julkaisuhakemisto:

```text
publish/
  yrtti.json
  public/
    index.html
    assets/
      ...
```

Kuvaus:

```json
{
  "schemaVersion": 1,
  "id": "oma-peli",
  "displayName": "Oma peli",
  "runtime": { "kind": "static" },
  "healthPath": "/",
  "publicDirectory": "public"
}
```

Vite-/React-projektissa aja projektin tuotantokäännös ja kopioi **`dist`-hakemiston
sisältö** julkaisun `public`-hakemistoon. Pelkkä lähdekoodi, TypeScript tai Viten
kehityspalvelin eivät ole valmis staattinen julkaisu. Vite ei ole pakollinen;
tavalliset HTML-tiedostot voi pakata suoraan.

Sovellus toimii oman verkkotunnuksensa juuressa. Käytä asset-osoitteita kuten
`/assets/kuva.png`, älä kehityskoneen osoitteita tai projektihakemiston nimeä.
Huolehdi, että build todella kopioi käytetyt kuvat, äänet ja muut resurssit mukaan.

Wizardin static-malli **ei sisällä SPA:n history-fallbackia**: suora avaus
`/pelit/123` palauttaa 404:n, ellei vastaavaa tiedostoa ole. Käytä tarvittaessa
hash-reititystä, generoi oikeat HTML-polut tai valitse omaa fallbackia tarjoava
process-palvelin. Asiakaspuolen reititys ei saa edellyttää käsin tehtyä Nginx-muutosta.

Kaikki `public`-hakemistoon pakattu sisältö on julkista. Staattisella sivulla ei
voi säilyttää API-avainta salassa. Rakennuksessa käytettävät selainasetukset ovat
myös julkisia; wizardin ympäristöasetukset eivät kirjoita jo rakennettua JS-bundlea uusiksi.

## 4. Sovellus, jolla on oma palvelin

Valmis Node-esimerkin julkaisuhakemisto:

```text
publish/
  yrtti.json
  package.json
  package-lock.json
  server.js
  server/
    ...
  dist/
    index.html
    assets/
      ...
  node_modules/
    ... tuotannossa tarvittavat Linux-yhteensopivat riippuvuudet
```

Kuvaus:

```json
{
  "schemaVersion": 1,
  "id": "oma-palvelu",
  "displayName": "Oma palvelu",
  "runtime": {
    "kind": "process",
    "command": ["/usr/bin/node", "server.js"]
  },
  "healthPath": "/health"
}
```

### Palvelimen toiminta

1. Käynnisty suoraan tuotantopalvelimena. Älä käynnistä Vite-dev-palvelinta,
   esikatselupalvelinta, paketinhallintaa tai rakentamista palvelun käynnistyksessä.
2. Lue `HOST` ja `PORT` ympäristöstä. Kuuntele wizardin antamaa HTTP-osoitetta,
   älä kiinteää porttia, `0.0.0.0`:aa tai sovelluksen omaa HTTPS-porttia.
3. Tarjoa selaintiedostot, API ja mahdollinen WebSocket yhden HTTP-portin kautta.
   Niiden sisäinen reititys saa olla sovelluksen alkuperäisen arkkitehtuurin mukainen.
4. Tarjoa nopea, ilman kirjautumista **HTTP 200** palauttava `/health` vasta kun
   palvelu on valmis. Ei HTTPS-pakotusta, kirjautumisohjausta tai ulkoista mallikutsua.
   Wizard ei määritä pakettiin säädettävää käynnistysviivettä; pitkä alustus on sovitettava erikseen.
5. Pidä pääprosessi käynnissä ja kirjoita lokit stdout/stderr-virtoihin.
   Sulje palvelin ja yhteydet hallitusti SIGTERM-signaalista.

`command` on ohjelma ja argumentit, ei shell-komento. Esimerkiksi
`["/usr/bin/node", "server.js"]` toimii; `npm install && npm start`, `$PORT`-
korvaukset tai PowerShell-komennot eivät. Työhakemisto on julkaisun juuri.
Käytä asennettua tulkkia hakemistosta `/usr/bin` tai `/bin`, tai paketin omaa
Linux-binääriä suhteellisella polulla. Oman binäärin ZIP-entry tarvitsee
Unix-executable-oikeuden; alla oleva Windows-ZIP-apuri ei aseta sitä.
Tulkin lukema JavaScript tai DLL ei tarvitse executable-oikeutta.

### Osoitteet, HTTPS ja WebSocket

Nginx hoitaa julkisen HTTPS:n ja välittää pyynnöt sovelluksen HTTP-porttiin.
Sovellus ei tarvitse omia varmenteita eikä uusia reitittimen porttiohjauksia.
Selaimen API-pyynnöissä käytä suhteellista osoitetta, esimerkiksi `/api/items`.
WebSocket-osoitteen voi johtaa nykyisestä sivusta:

```javascript
const socketUrl = new URL('/ws', window.location.href);
socketUrl.protocol = socketUrl.protocol === 'https:' ? 'wss:' : 'ws:';
const socket = new WebSocket(socketUrl);
```

Älä kirjoita selaimeen `localhost`-osoitetta, LAN-IP:tä tai sisäistä porttia.
Selain käyttää HTTPS/WSS:ää portissa 443. Luota forwarded headers -tietoihin vain
loopbackissa toimivalta välityspalvelimelta. Jos sovellus tarvitsee kiinteän
julkisen originin, lue se omasta dokumentoidusta asetuksesta kuten `PUBLIC_ORIGIN`;
wizard ei aseta sitä automaattisesti. Pitkään käyttämättömälle WebSocketille
tarvitaan tarvittaessa heartbeat: Nginxin nykyinen lukuaikaraja on 300 sekuntia.

### Asetukset, data ja resurssit

| Muuttuja | Wizardin asettama merkitys |
| --- | --- |
| `HOST` | `127.0.0.1` |
| `PORT` | Sovellukselle valittu portti väliltä 12000–12499 |
| `ASPNETCORE_URLS` | .NETin valmis `http://127.0.0.1:<portti>`-kuunteluosoite |
| `DATA_DIR` | Sovelluksen oma kirjoitettava, päivityksissä säilyvä hakemisto |

Nämä neljä nimeä on varattu. Älä aseta niitä wizardin omissa asetusruuduissa.
Älä myöskään ohita `ASPNETCORE_URLS`-arvoa .NETin kiinteällä `UseUrls`-/Kestrel-asetuksella.
Muut asetukset, esimerkiksi `PUBLIC_ORIGIN`, `DATABASE_URL` tai `NODE_ENV=production`,
syötetään wizardissa nimi/arvo-pareina. `NODE_ENV` ei tule automaattisesti.
Nimen muoto on `^[A-Z][A-Z0-9_]{0,63}$`, arvo enintään 8192 merkkiä ilman
ohjausmerkkejä ja asetuksia enintään 100. Monirivistä PEM-avainta ei voi syöttää
sellaisenaan; käytä tarvittaessa sovelluksen tukemaa yksirivistä koodausta.

Julkaisuhakemisto on vain luettava. Käytä tietokantatiedostoihin, latauksiin ja
pysyviin asetuksiin `DATA_DIR`-polkua, älä projektin juurta, käyttäjän kotihakemistoa
tai `dist`-hakemistoa. Väliaikaistiedostot eivät ole pysyvää dataa.
Sovelluksella ei ole root-oikeuksia. Nykyiset prosessirajat ovat 384 MiB muistia,
75 % yhden CPU:n ajasta ja 128 tehtävää; niitä ei säädetä manifestilla.

Wizard ei luo tietokantaa tai aja erillistä migraatiokomentoa. Dokumentoi mahdollinen
ulkoinen tietokanta ja sen käyttöoikeudet. SQLite-tiedosto voi sijaita `DATA_DIR`issä.
Suunnittele migraatiot ja varmistus erikseen: koodin palautus ei palauta dataa.

### Runtime ja riippuvuudet

Palvelin on Debian Linux x64. Ylläpito-ohjeen viimeisin 5.11.2026 kirjaus sisältää
Node 20.19.2:n sekä aiemmin varmennetun .NET/ASP.NET Core 10.0.12:n ja Python 3:n.
Tämä on päivätty havainto; varmista sovelluksen tarvitsema tarkka versio ennen julkaisua.
Wizardin process-tuki ei itsessään tarkista kirjastojen tai kieliversion yhteensopivuutta.

- **Node/TypeScript:** rakenna frontend ja palvelimen TypeScript etukäteen.
  Pakkaa tarvittava JS, selaintiedostot ja tuotantoriippuvuudet. Älä kopioi
  Windowsin `node_modules`-hakemistoa Linuxiin. Tuota riippuvuudet Linux x64
  -rakentajassa lockfilestä, esimerkiksi `npm ci --omit=dev`. Native-moduulien
  pitää vastata myös palvelimen Node-versiota ja Linux-kirjastoja. Pelkkä
  `--platform=linux` Windowsissa ei osoita paketin toimivuutta Linuxissa.
  `--ignore-scripts` sopii vain riippuvuuksiin, jotka toimivat ilman asennusskriptejä.
- **.NET:** tuota valmis Linux x64 -julkaisu, esimerkiksi
  `dotnet publish .\OmaSovellus.csproj -c Release -r linux-x64 --self-contained false -o .\publish`.
  Kuvaus käyttää `"command": ["/usr/bin/dotnet", "OmaSovellus.dll"]`.
  Pakkaa myös julkaisun runtimeconfig-, deps-, kirjasto- ja sisältötiedostot.
- **Python:** pakkaa valmis sovellus ja Linux-yhteensopivat riippuvuudet.
  `requirements.txt` yksin ei asenna niitä. Windows-venv tai toiseen polkuun
  sidottu venv ei ole siirrettävä julkaisu. Käytä tuotanto-HTTP-palvelinta,
  joka lukee `HOST`/`PORT`-arvot; älä kehityspalvelinta.

Erillinen kehityspalvelin ja backend eivät pakota kaiken koodin yhdistämiseen:
voit säilyttää moduulit ja lisätä yhden tuotannon käynnistyspisteen, joka tarjoaa
valmiin frontendin ja backendin. Jos sovellus todella tarvitsee useita erillisiä
palveluprosesseja, älä peitä tarvetta shell-käynnistimellä vaan tuo rajoitus esiin.

## 5. Toteuta toistettava paketointi

Lisää sovellukseen oma `scripts/Build-WizardPackage.ps1` tai kehitysympäristöön
sopiva vastaava ajettava paketointitoiminto. Sen tulee:

1. Ajaa projektin tarkoituksenmukaiset tarkistukset ja tuotantokäännös sekä
   pysähtyä virheeseen. Älä testaa vahingossa vanhaa `dist`-hakemistoa.
2. Luoda uusi erillinen julkaisuhakemisto ja kopioida mukaan vain sallitut
   tuotantotiedostot. Älä pakkaa koko repositoriota.
3. Lisätä oikea `yrtti.json` suoraan juureen ja tarvittavat Linux-riippuvuudet.
4. Tarkistaa, ettei mukana ole `.git`-hakemistoa, `.env`-/`.env.*`-salaisuuksia,
   avaimia, käyttäjädataa tai kehitysympäristön tiedostoja.
5. Luoda ZIP oikein päin olevilla sisäisillä poluilla. Raportoida lopuksi sen
   absoluuttinen polku, koko ja SHA-256 sekä tarvittavat wizard-asetukset ilman arvoja.

Windowsissa valmiin julkaisuhakemiston pakkaamiseen voi käyttää tämän projektin
[ZIP-apuria](./New-ReleasePackage.ps1):

```powershell
& '.\New-ReleasePackage.ps1' `
  -SourceDirectory 'D:\OmaSovellus\publish' `
  -Destination 'D:\OmaSovellus\oma-sovellus-20261105.zip'
Get-FileHash -LiteralPath 'D:\OmaSovellus\oma-sovellus-20261105.zip' -Algorithm SHA256
```

Korvaa esimerkkipolut omillasi. Kohde-ZIP ei saa olla lähdehakemiston sisällä
eikä samannimistä ZIPiä saa olla valmiina. Apuri pakkaa kaiken valitusta
hakemistosta; se ei rakenna sovellusta, suodata salaisuuksia tai tarkista koko
wizard-sopimusta. Jos avustaja toimii pilvessä tai toisella koneella, toimita
apuritiedosto erikseen tai toteuta vastaava pakkaus käytössä olevalla työkalulla.

## 6. Olemassa olevan sovelluksen sovittaminen

Tee ensin lyhyt kartoitus: build, runtime, frontend/backend, portit, WebSocket,
reititys, salaisuudet, tietokannat, kirjoitettavat polut ja tarvittavat riippuvuudet.
Säilytä toimiva kehityskäyttö ja pidä julkaisumuutokset rajattuina:

| Tavallinen lähtötilanne | Julkaisua varten tarvittava muutos |
| --- | --- |
| Selainprojekti käynnistetään Viten avulla | Tee tuotantobuild ja static-paketti |
| Palvelin kuuntelee aina porttia 3001 | Lue `HOST`/`PORT` ympäristöstä |
| Selain kutsuu `http://localhost:3001` | Käytä samaa originia ja HTTPS/WSS-yhteensopivia osoitteita |
| React-router käyttää puuttuvia palvelinpolkuja | Hash-reititys tai process-palvelimen SPA-fallback |
| API-avain korvataan Viten bundleen | Siirrä salainen kutsu palvelimelle ja asetus wizardiin |
| Data kirjoitetaan projektin juureen | Siirrä muuttuva data `DATA_DIR`-polkuun |
| Sovellus tarvitsee `npm install` palvelimella | Paketoi valmiit Linux-tuotantoriippuvuudet |
| Useita itsenäisiä palveluita tai liian suuret resurssitarpeet | Kuvaa puute; älä ilmoita nykyistä wizardia yhteensopivaksi |

Älä korvaa sovelluksen toimintaa demolla saadaksesi health-tarkistuksen läpi.
Raportoi erikseen julkaisemisen edellyttämät muutokset ja samalla löytyneet
toimintavirheet; älä lisää ominaisuuksia ilman tehtävän mukaista tarvetta.

## 7. Tarkistus ennen luovutusta ja päivitys

- [ ] Tuotantobuild ja sovelluksen olennaiset testit läpäisevät.
- [ ] ZIPin juuri sisältää `yrtti.json`:n, kentät vastaavat sopimusta ja kokorajat alittuvat.
- [ ] Static-paketti sisältää `publicDirectory/index.html`:n ja kaikki käytetyt resurssit.
- [ ] Process-paketti on purettu erilliseen hakemistoon ja käynnistetty **vain sen
      sisällöstä** Linuxissa oikealla runtimella; se ei käytä lähdekoodihakemiston riippuvuuksia.
- [ ] Process toimii valitulla vapaalla `HOST=127.0.0.1`/`PORT`-yhdistelmällä,
      kirjoittaa vain sallittuihin paikkoihin ja vastaa healthiin ilman kirjautumista.
- [ ] Selain toimii julkisella HTTPS-originilla: resurssit, reititys, API ja mahdollinen WSS.
- [ ] Salaisuuksia ei löydy ZIPistä, selainbundlesta tai lokeista.
- [ ] Pysyvä data säilyy uudelleenkäynnistyksessä; päivityksen/migraation vaikutukset on kuvattu.
- [ ] Puuttuvat varmennukset kerrotaan erikseen. Windows-koe ei todista Linux-yhteensopivuutta.

Tuo ZIP ylläpitäjän antamaan LAN-wizardiin, tarkista esitarkistuksen
tiedot ja syötä sovelluksen omat asetukset nimi/arvo-ruutuihin. Päivityksessä
valitse vanhan sovelluksen **Päivitä** ja käytä samaa `id`:tä.
Syöttämättömät vanhat asetukset säilyvät; asetuksen poistolle ei vielä ole
erillistä toimintoa. **Palauta** vaihtaa koodin ja reitityksen, ei nykyistä dataa
tai tietokantaa. Prosessin uudelleenkäynnistys voi katkaista aktiiviset yhteydet.

Paketin hyväksyminen esitarkistuksessa todistaa vain rakenteen, ei sovelluksen
toimivuutta. Julkaisu, julkinen selainkoe ja mahdollinen mobiilidatakoe raportoidaan
erillisinä tuloksina. Tämän ohjeen luominen ei itsessään julkaise sovelluksia.


Ohje muodostetaan wizardin ylläpidetystä lähdeohjeesta. Lataa ZIP-apuri yllä olevasta linkistä omaan työhakemistoosi.
