diff --git a/.gitpod.yml b/.gitpod.yml index e86bafd4..21c613b6 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -6,19 +6,38 @@ tasks: ports: - port: 8080 name: lichess - description: Your Lichess development site + description: Lichess dev site onOpen: open-preview - port: 8025 name: mailpit - description: For debugging emails + description: Email inbox onOpen: ignore - port: 8081 name: mongo-express - description: "For exploring database schema and contents (u: admin / p: pass)" + description: "Database explorer (u: admin / p: pass)" + onOpen: ignore + - port: 8089 + name: API docs + onOpen: ignore + - port: 8090 + name: chessground + onOpen: ignore + - port: 8091 + name: pgn-viewer + onOpen: ignore + - port: 9666 + name: lila-engine + description: External engine + onOpen: ignore + - port: 6175 + name: lila-gif + onOpen: ignore + - port: 3001 + name: picfit onOpen: ignore - port: 5601 name: kibana - description: For debugging Elasticsearch + description: Elasticsearch manager onOpen: ignore vscode: diff --git a/README.md b/README.md index 0ce9d43f..b3a4f303 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,10 @@ The only requirements for running on your local machine are `git` and Docker Des As an alternative to running it on your local machine, you can use Gitpod (a free, online, VS Code-like IDE) for contributing. With a single click, it will launch a workspace and automatically: -- Clone the necessary Lichess repositories -- Install all the dependencies -- Seed your database with test data -- Start your development site +- Clone the necessary Lichess repositories +- Install all the dependencies +- Seed your database with test data +- Start your development site Click here to create a workspace: @@ -56,12 +56,11 @@ To remove the containers: Always available: -| Service | URL | -| ------------------ | ------------------------------------------- | -| Main lila instance | http://localhost:8080/ | -| Chessground demo | http://localhost:8080/chessground/demo.html | -| Mongodb manager | http://localhost:8081/ (admin/pass) | -| Email inbox | http://localhost:8025/ | +| Service | URL | +| ------------------ | ----------------------------------- | +| Main lila instance | http://localhost:8080/ | +| Mongodb manager | http://localhost:8081/ (admin/pass) | +| Email inbox | http://localhost:8025/ | Depending on which optional services you start: @@ -71,7 +70,8 @@ Depending on which optional services you start: | Picfit | http://localhost:3001/healthcheck | | Elasticsearch manager | http://localhost:5601/ | | API docs | http://localhost:8089/ | -| PGN Viewer | http://localhost:8090/ | +| Chessground | http://localhost:8090/demo.html | +| PGN Viewer | http://localhost:8091/ | ## Usage @@ -91,6 +91,14 @@ To watch for Typescript/SCSS changes and automatically recompile: docker compose run --rm ui bash -c "/lila/ui/build -w" ``` +## Updating Routes + +If you edit the `conf/routes` file, you'll need to update the route cache. + +```bash +docker compose exec lila bash -c "./lila playRoutes" +``` + ### To add translation keys: After modifying a `translation/source/*.xml` file, run: @@ -102,14 +110,7 @@ docker compose run --rm ui bash -c "/lila/bin/trans-dump" ### Code formatting: ```bash -docker compose run --rm ui bash -c "cd /lila && pnpm install && pnpm run format" -docker compose run --rm ui bash -c "cd /chessground && pnpm install && pnpm run format" -docker compose run --rm ui bash -c "cd /pgn-viewer && pnpm install && pnpm run format" - -# sbt scalafmtAll -docker run --rm -v $(pwd)/repos/lila:/lila \ - sbtscala/scala-sbt:eclipse-temurin-jammy-21_35_1.9.7_3.3.1 \ - bash -c "cd /lila && sbt scalafmtAll" +./lila-docker format ``` ### Berserk (Python library): @@ -216,7 +217,7 @@ By default, your local lila instance will use the version of chessground that is docker compose run --rm ui bash -c "/lila/ui/build -w" ``` -Then you can see the updated chessground demo at http://localhost:8080/chessground/demo.html and when you refresh lila, it will use the local copy of chessground. +Then you can see the updated chessground demo at http://localhost:8090/demo.html and when you refresh lila, it will use the local copy of chessground. ### Developing PGN Viewer locally @@ -226,4 +227,4 @@ To re-compile the PGN Viewer after making changes: docker compose run --rm ui bash -c "cd /pgn-viewer && pnpm run sass-dev && pnpm run bundle-dev" ``` -See the changes on the PGN Viewer demo page: http://localhost:8090/ +See the changes on the PGN Viewer demo page: http://localhost:8091/ diff --git a/command/Cargo.lock b/command/Cargo.lock index 3c60decc..315320b8 100644 --- a/command/Cargo.lock +++ b/command/Cargo.lock @@ -26,6 +26,7 @@ name = "command" version = "0.1.0" dependencies = [ "cliclack", + "strum", ] [[package]] @@ -47,6 +48,12 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "indicatif" version = "0.17.7" @@ -117,12 +124,40 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "smawk" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "syn" version = "2.0.38" diff --git a/command/Cargo.toml b/command/Cargo.toml index 69ea3ace..e7c64d50 100644 --- a/command/Cargo.toml +++ b/command/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" [dependencies] cliclack = "0.1.6" +strum = { version = "0.25.0", features = ["derive", "strum_macros"] } diff --git a/command/src/main.rs b/command/src/main.rs index d59e27ee..a5d3cdf4 100644 --- a/command/src/main.rs +++ b/command/src/main.rs @@ -1,48 +1,61 @@ +use std::io::Error; + use cliclack::{confirm, input, intro, multiselect}; +use strum::{EnumIter, EnumString, IntoEnumIterator}; -const BANNER: &str = r#" +const BANNER: &str = r" |\_ _ _ _ /o \ | (_) ___| |__ ___ ___ ___ ___ _ __ __ _ (_. || | | |/ __| '_ \ / _ \/ __/ __| / _ \| '__/ _` | /__\ | | | (__| | | | __/\__ \__ \| (_) | | | (_| | )___( |_|_|\___|_| |_|\___||___/___(_)___/|_| \__, | |___/ -"#; +"; const ENV_PATH: &str = "/.env"; +#[derive(Default, Clone, Eq, PartialEq, Debug)] +struct OptionalService { + compose_profile: Option, + repositories: Option>, +} + +#[derive(Debug, Clone, PartialEq, EnumString, strum::Display, Eq, EnumIter)] +#[strum(serialize_all = "kebab-case")] +enum ComposeProfile { + StockfishPlay, + StockfishAnalysis, + ExternalEngine, + Search, + Gifs, + Thumbnails, + ApiDocs, + Chessground, + PgnViewer, +} + +#[derive(Debug, Clone, PartialEq, EnumString, strum::Display, Eq, EnumIter)] +#[strum(serialize_all = "kebab-case")] +enum Repository { + Lila, + LilaWs, + LilaDbSeed, + Lifat, + LilaFishnet, + LilaEngine, + LilaSearch, + LilaGif, + Api, + Chessground, + PgnViewer, + Scalachess, + Berserk, +} + fn main() -> std::io::Result<()> { intro(BANNER)?; - let profiles = multiselect( - "Select which optional services to run:\n (Use to toggle, to confirm)", - ) - .required(false) - .item( - "stockfish-play", - "Stockfish (for playing against the computer)", - "", - ) - .item( - "stockfish-analysis", - "Stockfish (for requesting computer analysis of games)", - "", - ) - .item( - "external-engine", - "External Engine (for connecting a local chess engine to the analysis board)", - "", - ) - .item( - "search", - "Search (for searching games, forum posts, etc)", - "", - ) - .item("gifs", "GIFs (for generating animated GIFs of games)", "") - .item("thumbnails", "Thumbnailer (for resizing images)", "") - .item("api-docs", "API docs", "") - .item("pgn-viewer", "PGN Viewer (Standalone)", "") - .interact()?; + let services = prompt_for_optional_services()?; let setup_database = confirm("Do you want to seed the database with test users, games, etc? (Recommended)") @@ -63,25 +76,155 @@ fn main() -> std::io::Result<()> { .interact()?, ) } else { - (String::from(""), String::from("")) + (String::new(), String::new()) }; - let env_contents = format!( - "COMPOSE_PROFILES={}\nSETUP_DB={}\nSU_PASSWORD={}\nPASSWORD={}\n", - profiles.join(","), - setup_database, - su_password, - password - ); + let env_contents = [ + format!( + "DIRS={}", + Repository::iter() + .map(|repo| repo.to_string()) + .collect::>() + .join(",") + ), + format!( + "REPOS={}", + [ + vec![ + Repository::Lila, + Repository::LilaWs, + Repository::LilaDbSeed, + Repository::Lifat, + ], + services + .iter() + .filter_map(|service| service.repositories.clone()) + .flatten() + .collect::>(), + ] + .concat() + .iter() + .map(std::string::ToString::to_string) + .collect::>() + .join(",") + ), + format!( + "COMPOSE_PROFILES={}", + services + .iter() + .filter_map(|service| service.compose_profile.clone()) + .collect::>() + .iter() + .map(std::string::ToString::to_string) + .collect::>() + .join(",") + ), + format!("SETUP_DB={setup_database}"), + format!("SU_PASSWORD={su_password}"), + format!("PASSWORD={password}"), + ] + .join("\n"); match std::fs::metadata(ENV_PATH) { - Ok(_) => { - std::fs::write(ENV_PATH, env_contents)?; - } - Err(_) => { - println!(".env contents:\n{}", env_contents); - } + Ok(_) => std::fs::write(ENV_PATH, env_contents)?, + Err(_) => println!(".env contents:\n{env_contents}"), } Ok(()) } + +fn prompt_for_optional_services() -> Result, Error> { + multiselect( + "Select which optional services to include:\n (Use arrows, to toggle, to continue)\n", + ) + .required(false) + .item( + OptionalService { + compose_profile: Some(ComposeProfile::StockfishPlay), + repositories: vec![Repository::LilaFishnet].into(), + }, + "Stockfish Play", + "for playing against the computer", + ) + .item( + OptionalService { + compose_profile: Some(ComposeProfile::StockfishAnalysis), + repositories: None, + }, + "Stockfish Analysis", + "for requesting computer analysis of games", + ) + .item( + OptionalService { + compose_profile: Some(ComposeProfile::ExternalEngine), + repositories: vec![Repository::LilaEngine].into(), + }, + "External Engine", + "for connecting a local chess engine to the analysis board", + ) + .item( + OptionalService { + compose_profile: Some(ComposeProfile::Search), + repositories: vec![Repository::LilaSearch].into(), + }, + "Search", + "for searching games, forum posts, etc", + ) + .item( + OptionalService { + compose_profile: Some(ComposeProfile::Gifs), + repositories: vec![Repository::LilaGif].into(), + }, + "GIFs", + "for generating animated GIFs of games", + ) + .item( + OptionalService { + compose_profile: Some(ComposeProfile::Thumbnails), + repositories: None, + }, + "Thumbnail generator", + "for resizing blog/streamer images", + ) + .item( + OptionalService { + compose_profile: Some(ComposeProfile::ApiDocs), + repositories: vec![Repository::Api].into(), + }, + "API docs", + "standalone API documentation", + ) + .item( + OptionalService { + compose_profile: Some(ComposeProfile::Chessground), + repositories: vec![Repository::Chessground].into(), + }, + "Chessground", + "standalone board UI", + ) + .item( + OptionalService { + compose_profile: Some(ComposeProfile::PgnViewer), + repositories: vec![Repository::PgnViewer].into(), + }, + "PGN Viewer", + "standalone PGN viewer", + ) + .item( + OptionalService { + compose_profile: None, + repositories: vec![Repository::Scalachess].into(), + }, + "Scalachess", + "standalone chess logic library", + ) + .item( + OptionalService { + compose_profile: None, + repositories: vec![Repository::Berserk].into(), + }, + "Berserk", + "Python API client", + ) + .interact() +} diff --git a/conf/nginx.conf b/conf/nginx.conf index 02f22a90..3c617fd9 100644 --- a/conf/nginx.conf +++ b/conf/nginx.conf @@ -9,10 +9,6 @@ server { alias /lifat; } - location /chessground { - alias /chessground; - } - location / { try_files $uri @$http_upgrade; } diff --git a/docker-compose.yml b/docker-compose.yml index faaec740..3e2c4c76 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -47,7 +47,6 @@ services: - ./conf/nginx.conf:/etc/nginx/conf.d/default.conf - ./repos/lila/public:/lila/public - ./repos/lifat:/lifat - - ./repos/chessground:/chessground - ./nginx:/nginx depends_on: - lila @@ -64,12 +63,23 @@ services: profiles: - api-docs + chessground: + build: + context: docker + dockerfile: chessground.Dockerfile + ports: + - 8090:8080 + volumes: + - ./repos/chessground:/chessground + profiles: + - chessground + pgn_viewer: build: context: docker dockerfile: pgn-viewer.Dockerfile ports: - - 8090:8080 + - 8091:8080 volumes: - ./repos/pgn-viewer:/pgn-viewer profiles: diff --git a/docker/api-docs.Dockerfile b/docker/api-docs.Dockerfile index b3989def..ae06f41d 100644 --- a/docker/api-docs.Dockerfile +++ b/docker/api-docs.Dockerfile @@ -1,4 +1,4 @@ -FROM node:20.8.1-bookworm +FROM node:20.8.1-bookworm-slim WORKDIR /api/doc diff --git a/docker/chessground.Dockerfile b/docker/chessground.Dockerfile new file mode 100644 index 00000000..ce1c3447 --- /dev/null +++ b/docker/chessground.Dockerfile @@ -0,0 +1,7 @@ +FROM node:20.8.1-bookworm-slim + +RUN npm install -g pnpm + +WORKDIR /chessground + +ENTRYPOINT pnpm install && pnpm run compile && pnpx http-server -p 8080 diff --git a/docker/pgn-viewer.Dockerfile b/docker/pgn-viewer.Dockerfile index 0578d1c0..0a16d19a 100644 --- a/docker/pgn-viewer.Dockerfile +++ b/docker/pgn-viewer.Dockerfile @@ -1,4 +1,4 @@ -FROM node:20.8.1-bookworm +FROM node:20.8.1-bookworm-slim RUN npm install -g pnpm diff --git a/docker/python.Dockerfile b/docker/python.Dockerfile index af98036f..3d268862 100644 --- a/docker/python.Dockerfile +++ b/docker/python.Dockerfile @@ -2,4 +2,6 @@ FROM eclipse-temurin:21_35-jdk-alpine COPY --from=python:3.12.0-alpine3.18 / / +RUN pip install pymongo + WORKDIR /lila-db-seed diff --git a/docker/ui.Dockerfile b/docker/ui.Dockerfile index 594ca2b7..85618775 100644 --- a/docker/ui.Dockerfile +++ b/docker/ui.Dockerfile @@ -1,6 +1,10 @@ -FROM node:20.8.1-bookworm +FROM node:20.8.1-bookworm-slim + +RUN apt-get update +RUN apt-get install -y git -RUN git config --global --add safe.directory /lila RUN git config --global --add safe.directory /chessground +RUN git config --global --add safe.directory /lila RUN git config --global --add safe.directory /pgn-viewer + RUN npm install -g pnpm diff --git a/lila-docker b/lila-docker index 06ecef9d..fde4cbaf 100755 --- a/lila-docker +++ b/lila-docker @@ -6,25 +6,33 @@ run_setup() { docker compose run --rm -it lila_docker_rs bash -c "cargo run --manifest-path /mnt/Cargo.toml" export $(cat .env | xargs) - echo "Cloning repos..." - repos=(lila lila-ws lila-db-seed lila-engine lila-fishnet lila-gif lila-search lifat scalachess api pgn-viewer chessground berserk) + # create a placeholder directory for each of the repos + # otherwise the directories will be created by Docker + # when the volumes are mounted and they may be owned by root + dirs=($(echo $DIRS | tr ',' ' ')) + for dir in "${dirs[@]}"; do + mkdir -p repos/$dir + done + + repos=($(echo $REPOS | tr ',' ' ')) + echo "Cloning repos ${repos[@]}..." for repo in "${repos[@]}"; do - [ ! -d repos/$repo ] && git clone --depth 1 https://github.com/lichess-org/$repo.git repos/$repo + [ ! -d repos/$repo/.git ] && git clone --depth 1 --origin lichess-org https://github.com/lichess-org/$repo repos/$repo done git -C repos/lila submodule update --init run_setup_config - echo "Compiling lila js/css..." - docker compose run --rm ui bash -c "/lila/ui/build" - - echo "Compiling chessground..." - docker compose run --rm ui bash -c "cd /chessground && pnpm install && pnpm run compile" - docker compose build + # separate build specifically for utils profile otherwise its Dockerfile changes won't be detected + docker compose --profile utils build + docker compose up -d + echo "Compiling js/css..." + docker compose run --rm ui bash -c "/lila/ui/build" + if [ "$SETUP_DB" = "true" ]; then setup_database fi @@ -86,7 +94,7 @@ setup_database() { "mongo --host mongodb lichess /lila/bin/mongodb/indexes.js" docker compose run --rm python bash -c \ - "pip install pymongo && python /lila-db-seed/spamdb/spamdb.py --uri=mongodb://mongodb/lichess --password=$PASSWORD --su-password=$SU_PASSWORD --es --es-host=elasticsearch:9200" + "python /lila-db-seed/spamdb/spamdb.py --uri=mongodb://mongodb/lichess --password=$PASSWORD --su-password=$SU_PASSWORD --es --es-host=elasticsearch:9200" docker compose run --rm -v $(pwd)/scripts:/scripts mongodb bash -c \ "mongosh --host mongodb lichess --file /scripts/mongodb/users.js" @@ -94,12 +102,9 @@ setup_database() { run_formatter() { docker compose run --rm ui bash -c "\ - cd /lila && \ - pnpm install && pnpm run format && \ - cd /chessground && \ - pnpm install && pnpm run format && \ - cd /pgn-viewer && \ - pnpm install && pnpm run format" + cd /lila && pnpm install && pnpm run format && \ + (test -f /chessground/package.json && cd /chessground && pnpm install && pnpm run format) || echo 'Skipping chessground' && \ + (test -f /pgn-viewer/package.json && cd /pgn-viewer && pnpm install && pnpm run format) || echo 'Skipping pgn-viewer'" docker run --rm -v $(pwd)/repos/lila:/lila \ sbtscala/scala-sbt:eclipse-temurin-jammy-21_35_1.9.7_3.3.1 \