Kako deluje
Igra je sestavljena iz:
- CLI (tekstovn vmesnik), napisan v Python-u, ki ga uporablja igralec;
- v ozadju je kontrolnea skripta (checker.py), ki preverja igranje igralca in dodeljuje točke;
- strežnika v Javi (Spring Boot), ki:
- uporablja grafični vmesnik (statične spletne strani);
- prejema podatke iz kontrolne skripte;
- shranjuje rezultate v podatkovni bazi PostgreSQL;
- pošilja stanje igre grafičnemu vmesniku preko WebSocket-a;
- spletni grafični vmasnik je sestavljen iz HTML, CSS in vanilijevega JavaScript-a.
Vsaka sopnja igre zahteva:
- OCI kontejner (Docker);
- eno specifičeno skripto checker.py za vsak nivo, ki ga pokliče glavna kontrolna skripta;
- WebComponent, ki ga naloži grafični vmesnik (UI).
Pomoč! Zakaj mešati toliko tehnologij?\
Ker smo LUG in …
Naslednji diagram prikazuje komunikacijo med različnimi komponentami:
Igra se začne, ko uporabnik zažene skripto start-game.py (CLI). Skripta zažene prvo klicanje strežniku, da preveri, ali je izbrano uporabniško ime na voljo, kajti določeno uporabniško ime se lahko uporabi le enkrat. Med skripto in strežnikom ni nobenega mehanizma za avtentikacijo. Predpostavlja se, da oba programa delujeta v isti lokalni mreži.
Če je uporabniško ime na voljo, skripta obvesti strežnik, da se pripravlja na začetek nove igre. Strežnik to informacijo posreduje UI preko WebSocket-a.
Skripta nato zažene kontejner. Slika na zaslonu je odvisna od nivoja in jezika, ki ga izbere uporabnik. Kontejner se zažene brez omrežja (–network=none), da omeji domišljijo uporabnika.
Na kontejner se montirajo 3 volumni:
- datoteka /home/player/.bashrc, montirana v načinu samo za branje;
- datoteka /opt/linux-bomb/log;
- mapa /bomb
Kontejner se zažene z neprivilegiranim uporabnikom player, pri čemer se uporablja zastavica --userns=keep-id
, da se preprečijo težave s pravicami znotraj volumnov. Predpostavlja se, da se kontejner zažene z uporabnikom z ID-jem 1000.
Mapa /bomb
vsebuje datoteke, ki predstavljajo stanje bombe, s katero bo uporabnik upravljal. Inicializira se z vstopno točko “entripoint” osnovne slike iz mape /opt/linux-bomb/bomb
, ki jo določajo Dockerfile-i vsakega nivoja.
Datoteka .bashrc definira funkcionalnost, ki omogoča spremljanje ukazov, ki jih izvaja uporabnik. Ta funkcionalnost je nastavljena s pomočjo mehanizma ukazne lupie bash, ki omogoča določitev ukaza znotraj okoljske spremenljivke, imenovane PROMPT_COMMAND
. Ukaz, ki je vsebovan v tej spremenljivki, se izvede ob koncu vsakega ukaza, ki ga izvede uporabnik.
V našem primeru bi lahko nastavili PROMPT_COMMAND
, da pošlje ukaz in kodo napake direktno na strežnik. Vendar pa, ker smo raje onemogočili omrežje znotraj kontejnerja, smo našemu PROMPT_COMMAND
dodelili samo dadajanje zadnjega izvedenega ukaza in njegove izhodne kode v datoteko /opt/linux-bomb/log
. Ta datoteka, ker je montirana na zunanji volumen med ustvarjanjem kontejnerja, je berljiva tudi iz zunanjosti kontejnerja, ki omogoča verifikacijo s strani python skripte. Kot dodatni “anti-cheating” mehanizem bi lahko izvedeli chattr +a na tej datoteki, kar bi omogočilo le operacijo dodajanja.
Skripta checker.py
redno preverja vsebino datoteke dnevnika, in po vsakem izvedenem ukazu pokliče specifičeno skripto checker za vsako stopnjo, ki se ukvarja z definiranjem logike dodeljevanja točk na podlagi datotek in map, zaznanih v mapi /bomb
. Glavna skripta pošlje strežniku rezultat, pri čemer se upoštevajo morebitne kazni za napačne ukaze. Tudi v tem primeru ni nobene oblike avtentikacije s strežnikom. Strežnik prav tako ne izvaja nobenih preverjanj, ampak le zaupa prejetim podatkom od checkerja in shrani rezultat v bazo podatkov. Vse operacije so nato poslane grafičnemu vmesniku preko WebSocket-a.
Skripta checker prav tako preverja, ali je kontejner še vedno v teku, in zaključi igro (z eksplozijo bombe), če zazna, da je bil kontejner končan. Skripta je prav tako zadolžena, da ugasne kontejner v primeru zmage.
Kontejner se zažene v bash-u, katerega zažene ukaz timeout
, ki omogoča samodejno končanje kontejnerja ob dosegu največjega dovoljenega časa, določenega za posamezen nivo igre.
Ko se kontejner zaključi, se zažene druga Python skripta (end-message.py
), ki pokliče API, da preveri, ali se je igra končala z zmago ali porazom. Posledično prikaže ustrezno sporočilo uporabniku.
Kontejner in skripta end-message.py
se zaženeta istočasno znotraj nove lupine kot zadnji korak skripte start-game.py
z uporabo funkcije os.execvp()
, katera zažene nov program in nadomesti trenutni proces.