From b47ba06b5db1b04d82c1fd87ff20dfda64638f43 Mon Sep 17 00:00:00 2001 From: Trevor Fitzgerald Date: Wed, 6 Dec 2023 18:12:36 -0500 Subject: [PATCH] Mobile app setup (#34) --- .github/workflows/rust.yml | 11 +- .gitignore | 3 + README.md | 55 +++- command/Cargo.lock | 316 +++++++++++++------- command/Cargo.toml | 5 +- command/src/main.rs | 365 +++++++++++++++++++---- conf/lila-ws.conf | 2 +- conf/lila.conf | 6 +- conf/nginx.conf | 12 +- docker-compose.yml | 19 +- docker/mobile.Dockerfile | 28 ++ lila-docker | 43 +-- nginx/errors/{5xx.html => 502/lila.html} | 3 + nginx/errors/502/picfit.html | 1 + 14 files changed, 663 insertions(+), 206 deletions(-) create mode 100644 docker/mobile.Dockerfile rename nginx/errors/{5xx.html => 502/lila.html} (95%) create mode 100644 nginx/errors/502/picfit.html diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 56e51672..e25ed3bf 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -8,11 +8,16 @@ env: jobs: build: runs-on: ubuntu-latest + defaults: + run: + working-directory: ./command steps: - uses: actions/checkout@v4 - name: Build - run: cargo build --verbose --manifest-path command/Cargo.toml + run: cargo build --verbose - name: Run tests - run: cargo test --verbose --manifest-path command/Cargo.toml + run: cargo test --verbose + - name: Clippy + run: cargo clippy --all -- -D warnings - name: Check formatting - run: cargo fmt --manifest-path command/Cargo.toml -- --check + run: cargo fmt -- --check diff --git a/.gitignore b/.gitignore index 52d6e16a..80d6d83d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ command/.cargo command/repos command/target + +settings.env +settings.toml diff --git a/README.md b/README.md index a10c0b53..ebfb9ba1 100644 --- a/README.md +++ b/README.md @@ -176,16 +176,13 @@ docker compose run --rm -w /scalachess --entrypoint="sbt package" lila ```bash ## run formatter -docker run --rm -v $(pwd)/repos/dartchess:/mnt --workdir /mnt dart:3.1.5-sdk \ - dart format . +docker compose run --rm -w /dartchess mobile dart format . ## analyze -docker run --rm -v $(pwd)/repos/dartchess:/mnt --workdir /mnt dart:3.1.5-sdk \ - bash -c "dart pub get && dart analyze" +docker compose run --rm -w /dartchess mobile bash -c "dart pub get && dart analyze" ## run tests -docker run --rm -v $(pwd)/repos/dartchess:/mnt --workdir /mnt dart:3.1.5-sdk \ - bash -c "dart pub get && dart test -x full_perft" +docker compose run --rm -w /dartchess mobile bash -c "dart pub get && dart test -x full_perft" ``` ### bbpPairings: @@ -247,3 +244,49 @@ curl --get http://localhost:8086/query \ --data-urlencode "db=kamon" \ --data-urlencode "q=show measurements;" ``` + +### Mobile + +1. On your Android phone: + 1. Connect your phone to the same wifi network as your host machine + 2. Enable Developer Mode + 3. In Developer Options, enable Wireless Debugging and tap into its menu +2. On your host machine: + + 1. Have the lila-docker services running, with the `Mobile` optional service started + 2. Configure lila to run with your host's IP address or hostname instead of localhost + + ```bash + ./lila-docker hostname + ``` + + - Then verify that your phone can access the site at `http://[your-selection]:8080` + + 3. Connect to your phone + + ```bash + ./lila-docker mobile + ``` + + 4. Get a shell on the container: + + ```bash + docker compose exec -it mobile bash + + # verify your phone is listed + adb devices + ``` + + 5. Install the app dependencies: + ```bash + flutter pub get + dart run build_runner build + ``` + 6. Run the app: + ```bash + flutter run -v \ + --dart-define LICHESS_HOST=$LILA_URL \ + --dart-define LICHESS_WS_HOST=$LILA_URL + ``` + - No substitutions necessary. The `$LILA_URL` environment variable will already be set on the container. + - First time you run it, it might take a while diff --git a/command/Cargo.lock b/command/Cargo.lock index a48c924b..cfa54473 100644 --- a/command/Cargo.lock +++ b/command/Cargo.lock @@ -3,10 +3,10 @@ version = 3 [[package]] -name = "bitflags" -version = "2.4.1" +name = "byteorder" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cfg-if" @@ -27,23 +27,15 @@ dependencies = [ "zeroize", ] -[[package]] -name = "colored" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" -dependencies = [ - "is-terminal", - "lazy_static", - "windows-sys 0.48.0", -] - [[package]] name = "command" version = "0.1.0" dependencies = [ "cliclack", - "colored", + "local-ip-address", + "serde", + "struct_iterable", + "toml", ] [[package]] @@ -59,6 +51,12 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + [[package]] name = "encode_unicode" version = "0.3.6" @@ -66,20 +64,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] -name = "errno" -version = "0.3.8" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "erased-serde" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" dependencies = [ - "libc", - "windows-sys 0.52.0", + "serde", ] [[package]] -name = "hermit-abi" -version = "0.3.3" +name = "hashbrown" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] [[package]] name = "indicatif" @@ -103,17 +116,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "is-terminal" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi", - "rustix", - "windows-sys 0.48.0", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -127,10 +129,53 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] -name = "linux-raw-sys" -version = "0.4.12" +name = "local-ip-address" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66357e687a569abca487dc399a9c9ac19beb3f13991ed49f00c144e02cbd42ab" +dependencies = [ + "libc", + "neli", + "thiserror", + "windows-sys 0.48.0", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "neli" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "1100229e06604150b3becd61a4965d5c70f3be1759544ea7274166f4be41ef43" +dependencies = [ + "byteorder", + "libc", + "log", + "neli-proc-macros", +] + +[[package]] +name = "neli-proc-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c168194d373b1e134786274020dae7fc5513d565ea2ebb9bc9ff17ffb69106d4" +dependencies = [ + "either", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", +] [[package]] name = "number_prefix" @@ -169,16 +214,32 @@ dependencies = [ ] [[package]] -name = "rustix" -version = "0.38.26" +name = "serde" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9470c4bf8246c8daf25f9598dca807fb6510347b1e1cfa55749113850c79d88a" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "serde_spanned" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" +dependencies = [ + "serde", ] [[package]] @@ -187,6 +248,46 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +[[package]] +name = "struct_iterable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "849a064c6470a650b72e41fa6c057879b68f804d113af92900f27574828e7712" +dependencies = [ + "struct_iterable_derive", + "struct_iterable_internal", +] + +[[package]] +name = "struct_iterable_derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb939ce88a43ea4e9d012f2f6b4cc789deb2db9d47bad697952a85d6978662c" +dependencies = [ + "erased-serde", + "proc-macro2", + "quote", + "struct_iterable_internal", + "syn 2.0.39", +] + +[[package]] +name = "struct_iterable_internal" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9426b2a0c03e6cc2ea8dbc0168dbbf943f88755e409fb91bcb8f6a268305f4a" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.39" @@ -209,6 +310,60 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "toml" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "unicode-ident" version = "1.0.12" @@ -245,15 +400,6 @@ dependencies = [ "windows-targets 0.48.5", ] -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.0", -] - [[package]] name = "windows-targets" version = "0.42.2" @@ -284,21 +430,6 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] -[[package]] -name = "windows-targets" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" -dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -311,12 +442,6 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" - [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -329,12 +454,6 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" - [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -347,12 +466,6 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" -[[package]] -name = "windows_i686_gnu" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" - [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -365,12 +478,6 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" -[[package]] -name = "windows_i686_msvc" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" - [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -383,12 +490,6 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" - [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -401,12 +502,6 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" - [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -420,10 +515,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] -name = "windows_x86_64_msvc" -version = "0.52.0" +name = "winnow" +version = "0.5.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "b7e87b8dfbe3baffbe687eef2e164e32286eff31a5ee16463ce03d991643ec94" +dependencies = [ + "memchr", +] [[package]] name = "zeroize" @@ -442,5 +540,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] diff --git a/command/Cargo.toml b/command/Cargo.toml index 263d9b27..0247344e 100644 --- a/command/Cargo.toml +++ b/command/Cargo.toml @@ -7,4 +7,7 @@ edition = "2021" [dependencies] cliclack = "0.1.6" -colored = "2.0.4" +local-ip-address = "0.5.6" +serde = { version = "1.0.193", features = ["derive"] } +struct_iterable = "0.1.1" +toml = "0.8.8" diff --git a/command/src/main.rs b/command/src/main.rs index 985aab56..2f653ca8 100644 --- a/command/src/main.rs +++ b/command/src/main.rs @@ -1,9 +1,16 @@ -use cliclack::{confirm, input, intro, log, multiselect, spinner}; -use colored::Colorize; +use cliclack::{ + confirm, input, intro, + log::{self, info}, + multiselect, note, outro, select, spinner, +}; +use local_ip_address::local_ip; +use serde::{Deserialize, Serialize}; use std::{ + collections::HashMap, io::Error, path::{Path, PathBuf}, }; +use struct_iterable::Iterable; const BANNER: &str = r" |\_ _ _ _ @@ -14,25 +21,93 @@ const BANNER: &str = r" |___/ "; +#[derive(Serialize, Deserialize, Iterable, Debug)] struct Config { - profiles: Vec, - setup_database: bool, - su_password: String, - password: String, - enable_monitoring: bool, + compose_profiles: Option>, + setup_database: Option, + enable_monitoring: Option, + su_password: Option, + password: Option, + lila_domain: Option, + lila_url: Option, + phone_ip: Option, + connection_port: Option, + pairing_code: Option, + pairing_port: Option, } impl Config { - fn to_env(&self) -> String { - let mut env = String::new(); + const SETTINGS_TOML: &'static str = "settings.toml"; + const SETTINGS_ENV: &'static str = "settings.env"; + + fn default() -> Self { + Self { + compose_profiles: None, + setup_database: None, + enable_monitoring: None, + su_password: None, + password: None, + lila_domain: None, + lila_url: None, + phone_ip: None, + connection_port: None, + pairing_code: None, + pairing_port: None, + } + } + + fn load() -> Self { + if !Path::new(Self::SETTINGS_TOML).exists() { + return Self::default(); + } + + let toml = std::fs::read_to_string(Self::SETTINGS_TOML).unwrap(); + toml::from_str(&toml).unwrap() + } - env.push_str(&format!("COMPOSE_PROFILES={}\n", self.profiles.join(","))); - env.push_str(&format!("SETUP_DATABASE={}\n", self.setup_database)); - env.push_str(&format!("SU_PASSWORD={}\n", self.su_password)); - env.push_str(&format!("PASSWORD={}\n", self.password)); - env.push_str(&format!("ENABLE_MONITORING={}\n", self.enable_monitoring)); + fn save(&self) { + std::fs::write(Self::SETTINGS_TOML, self.to_toml()).unwrap(); + std::fs::write(Self::SETTINGS_ENV, self.to_env()).unwrap(); + } - env + fn to_toml(&self) -> String { + toml::to_string(&self).unwrap() + } + + fn to_env(&self) -> String { + let mut contents: HashMap<&str, String> = HashMap::new(); + + for (key, value) in self.iter() { + if let Some(string_opt) = value.downcast_ref::>() { + if let Some(string_opt) = string_opt { + contents.insert(key, string_opt.to_string()); + } + } else if let Some(bool_opt) = value.downcast_ref::>() { + if let Some(bool_opt) = bool_opt { + contents.insert(key, bool_opt.to_string()); + } + } else if let Some(u16_opt) = value.downcast_ref::>() { + if let Some(u16_opt) = u16_opt { + contents.insert(key, u16_opt.to_string()); + } + } else if let Some(u32_opt) = value.downcast_ref::>() { + if let Some(u32_opt) = u32_opt { + contents.insert(key, u32_opt.to_string()); + } + } else if let Some(vec_string) = value.downcast_ref::>>() { + if let Some(vec_string) = vec_string { + contents.insert(key, vec_string.join(",")); + } + } else { + panic!("Unsupported type: Could not write [{key}] to env"); + } + } + + contents + .iter() + .map(|(k, v)| format!("{}={}", k.to_uppercase(), v)) + .collect::>() + .join("\n") } } @@ -63,18 +138,23 @@ impl Repository { } } -#[cfg(test)] -mod tests { - use super::*; +struct Gitpod { + domain: String, + url: String, +} - #[test] - fn test_repository() { - let repo = Repository::new("lichess-org", "lila"); - assert_eq!(repo.org, "lichess-org"); - assert_eq!(repo.project, "lila"); - assert_eq!(repo.full_name(), "lichess-org/lila"); - assert_eq!(repo.url(), "https://github.com/lichess-org/lila"); - assert_eq!(repo.clone_path(), Path::new("repos/lila")); +impl Gitpod { + fn load() -> Self { + let workspace_url = std::env::var("GITPOD_WORKSPACE_URL").expect("Not running in Gitpod"); + + Self { + domain: workspace_url.replace("https://", "8080-"), + url: workspace_url.replace("https://", "https://8080-"), + } + } + + fn is_host() -> bool { + std::env::var("GITPOD_WORKSPACE_URL").is_ok() } } @@ -86,20 +166,20 @@ struct OptionalService<'a> { fn main() -> std::io::Result<()> { let args: Vec = std::env::args().collect(); - assert!(args.len() > 1, "Missing command"); + let config = Config::load(); + match args[1].as_str() { - "setup" => setup(), - "gitpod-welcome" => { - gitpod_welcome(); - Ok(()) - } + "setup" => setup(config), + "hostname" => hostname(config), + "mobile" => mobile_setup(config), + "welcome" => welcome(), _ => panic!("Unknown command"), } } -fn setup() -> std::io::Result<()> { +fn setup(mut config: Config) -> std::io::Result<()> { intro(BANNER)?; let services = prompt_for_optional_services()?; @@ -126,20 +206,30 @@ fn setup() -> std::io::Result<()> { (String::new(), String::new()) }; - let config = Config { - profiles: services + config.compose_profiles = Some( + services .iter() .filter_map(|service| service.compose_profile.clone()) .flatten() .map(std::string::ToString::to_string) .collect(), - setup_database, - su_password, - password, - enable_monitoring: services + ); + config.setup_database = Some(setup_database); + config.enable_monitoring = Some( + services .iter() .any(|service| service.compose_profile == Some(vec!["monitoring"])), - }; + ); + config.su_password = Some(su_password); + config.password = Some(password); + + if Gitpod::is_host() { + let gitpod = Gitpod::load(); + config.lila_domain = Some(gitpod.domain); + config.lila_url = Some(gitpod.url); + } + + config.save(); create_placeholder_dirs(); @@ -165,7 +255,7 @@ fn setup() -> std::io::Result<()> { progress.start(&format!("Cloning {}...", repo.full_name())); if repo.clone_path().read_dir()?.next().is_some() { - progress.stop(format!("Clone {} ✓", repo.full_name())); + progress.stop(format!("Already cloned {} ✓", repo.full_name())); continue; } @@ -187,11 +277,10 @@ fn setup() -> std::io::Result<()> { output ); - progress.stop(format!("Clone {} ✓", repo.full_name())); + progress.stop(format!("Cloned {} ✓", repo.full_name())); } - std::fs::write(".env", config.to_env())?; - log::success("Wrote .env") + outro("Starting services...") } fn create_placeholder_dirs() { @@ -211,6 +300,7 @@ fn create_placeholder_dirs() { Repository::new("lichess-org", "chessground"), Repository::new("lichess-org", "pgn-viewer"), Repository::new("lichess-org", "scalachess"), + Repository::new("lichess-org", "mobile"), Repository::new("lichess-org", "dartchess"), Repository::new("lichess-org", "berserk"), Repository::new("cyanfish", "bbpPairings"), @@ -310,12 +400,20 @@ fn prompt_for_optional_services() -> Result>, Error ) .item( OptionalService { - compose_profile: None, + compose_profile: None , repositories: vec![Repository::new("lichess-org", "scalachess")].into(), }, "Scalachess", "standalone chess logic library", ) + .item( + OptionalService { + compose_profile: vec!["mobile"].into(), + repositories: vec![Repository::new("lichess-org", "mobile")].into(), + }, + "Mobile app", + "Flutter-based mobile app", + ) .item( OptionalService { compose_profile: None, @@ -351,19 +449,166 @@ fn prompt_for_optional_services() -> Result>, Error .interact() } -fn gitpod_welcome() { - println!("{}", "################".green()); - println!( - "{}", - "Your Lichess development environment is starting!".green() - ); - println!( - "{}", - "Monitor the progress in the 'lila' container with the command:".green() - ); - println!("{}", " docker compose logs lila --follow".green().bold()); - println!( - "{}", - "For full documentation, see: https://lichess-org.github.io/lila-gitpod/".green() - ); +fn hostname(mut config: Config) -> std::io::Result<()> { + if Gitpod::is_host() { + return log::error("Setting of hostname not available on Gitpod"); + } + + let local_ip = match local_ip() { + Ok(ip) => ip.to_string(), + _ => "127.0.0.1".to_string(), + }; + + let hostname: String = match select("Select a hostname to access your local Lichess instance:") + .initial_value("localhost") + .item("localhost", "localhost", "default") + .item( + local_ip.as_str(), + local_ip.as_str(), + "Your private IP address, for accessing from other devices on your local network", + ) + .item( + "10.0.2.2", + "10.0.2.2", + "For accessing from an Android emulator running on this machine", + ) + .item("other", "Other", "Enter a custom hostname") + .interact()? + { + "other" => input("Enter a custom hostname: (It must be resolvable)").interact()?, + selection => selection.to_string(), + }; + + config.lila_domain = Some(format!("{hostname}:8080")); + config.lila_url = Some(format!("http://{hostname}:8080")); + config.save(); + + outro(format!("✔ Local Lichess URL set to http://{hostname}:8080")) +} + +fn mobile_setup(mut config: Config) -> std::io::Result<()> { + intro("On your Android phone, open Developer Options > Wireless Debugging")?; + + let phone_ip = match config.phone_ip { + Some(ip) => input("Your phone's private IP address") + .default_input(&ip) + .interact()?, + None => input("Your phone's private IP address") + .placeholder("192.168.x.x or 10.x.x.x") + .interact()?, + }; + + let connection_port: u16 = input("Connection port") + .validate(|input: &String| validate_string_length(input, 5)) + .interact()?; + + info("Tap `Pair device with pairing code`")?; + + let pairing_code: u32 = input("Pairing code") + .validate(|input: &String| validate_string_length(input, 6)) + .interact()?; + let pairing_port: u16 = input("Pairing port") + .validate(|input: &String| validate_string_length(input, 5)) + .interact()?; + + config.phone_ip = Some(phone_ip); + config.connection_port = Some(connection_port); + config.pairing_code = Some(pairing_code); + config.pairing_port = Some(pairing_port); + config.save(); + + outro("Pairing and connecting to phone...") +} + +fn validate_string_length(input: &String, length: usize) -> Result<(), String> { + match input.len() { + len if len == length => Ok(()), + _ => Err(format!("Value should be {length} digits in length")), + } +} + +fn welcome() -> std::io::Result<()> { + intro("Your Lichess development environment is starting!")?; + + if Gitpod::is_host() { + info("For full documentation, see: https://lichess-org.github.io/lila-gitpod/")?; + } else { + info("For full documentation, see: https://github.com/lichess-org/lila-docker")?; + } + + note( + "To monitor the progress:", + "docker compose logs lila --follow", + )?; + + outro("🚀") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_repository() { + let repo = Repository::new("lichess-org", "lila"); + assert_eq!(repo.org, "lichess-org"); + assert_eq!(repo.project, "lila"); + assert_eq!(repo.full_name(), "lichess-org/lila"); + assert_eq!(repo.url(), "https://github.com/lichess-org/lila"); + assert_eq!(repo.clone_path(), Path::new("repos/lila")); + } + + #[test] + fn test_set_env_vars_from_struct() { + let contents = Config { + compose_profiles: Some(vec!["foo".to_string(), "bar".to_string()]), + setup_database: Some(true), + enable_monitoring: Some(false), + su_password: Some("foo".to_string()), + password: Some("bar".to_string()), + lila_domain: Some("baz:8080".to_string()), + lila_url: Some("http://baz:8080".to_string()), + phone_ip: Some("1.2.3.4".to_string()), + connection_port: Some(1234), + pairing_code: Some(901234), + pairing_port: Some(5678), + } + .to_env(); + + let vars = contents + .split("\n") + .map(|line| line.split("=")) + .map(|mut parts| (parts.next().unwrap(), parts.next().unwrap())) + .collect::>(); + + assert_eq!(vars["COMPOSE_PROFILES"], "foo,bar"); + assert_eq!(vars["SETUP_DATABASE"], "true"); + assert_eq!(vars["ENABLE_MONITORING"], "false"); + assert_eq!(vars["SU_PASSWORD"], "foo"); + assert_eq!(vars["PASSWORD"], "bar"); + assert_eq!(vars["LILA_DOMAIN"], "baz:8080"); + assert_eq!(vars["LILA_URL"], "http://baz:8080"); + assert_eq!(vars["PHONE_IP"], "1.2.3.4"); + assert_eq!(vars["CONNECTION_PORT"], "1234"); + assert_eq!(vars["PAIRING_CODE"], "901234"); + assert_eq!(vars["PAIRING_PORT"], "5678"); + } + + #[test] + fn test_gitpod_lila_url() { + std::env::set_var( + "GITPOD_WORKSPACE_URL", + "https://lichessorg-liladocker-abc123.ws-us123.gitpod.io", + ); + + let gitpod = Gitpod::load(); + assert_eq!( + gitpod.domain, + "8080-lichessorg-liladocker-abc123.ws-us123.gitpod.io" + ); + assert_eq!( + gitpod.url, + "https://8080-lichessorg-liladocker-abc123.ws-us123.gitpod.io" + ); + } } diff --git a/conf/lila-ws.conf b/conf/lila-ws.conf index 291d2b3f..5fccc489 100644 --- a/conf/lila-ws.conf +++ b/conf/lila-ws.conf @@ -3,4 +3,4 @@ include "application" mongo.uri = "mongodb://mongodb:27017/lichess?appName=lila-ws" redis.uri = "redis://redis" -csrf.origin = ${?SCHEME}"://"${?LILA_DOMAIN} +csrf.origin = ${?LILA_URL} diff --git a/conf/lila.conf b/conf/lila.conf index 4fa8a08b..2e2f05e6 100644 --- a/conf/lila.conf +++ b/conf/lila.conf @@ -6,9 +6,9 @@ user.password.bpass.secret = "9qEYN0ThHer1KWLNekA76Q==" net.site.name = "lila" net.domain = ${?LILA_DOMAIN} net.socket.domains = [ ${?LILA_DOMAIN} ] -net.asset.base_url = ${?SCHEME}"://"${?LILA_DOMAIN} +net.asset.base_url = ${?LILA_URL} net.asset.base_url_internal = "http://nginx" -net.base_url = ${?SCHEME}"://"${?LILA_DOMAIN} +net.base_url = ${?LILA_URL} mongodb.uri = "mongodb://mongodb?appName=lila" redis.uri = "redis://redis" @@ -18,7 +18,7 @@ game.gifUrl = "http://lila_gif:6175" search.enabled = true search.endpoint = "http://lila_search:9673" -memo.picfit.endpointGet = ${?SCHEME}"://"${?PICFIT_DOMAIN} +memo.picfit.endpointGet = ${?LILA_URL} memo.picfit.endpointPost = "http://picfit:3001" mailer.primary.mock = false diff --git a/conf/nginx.conf b/conf/nginx.conf index 04668c4e..7fbc8819 100644 --- a/conf/nginx.conf +++ b/conf/nginx.conf @@ -13,6 +13,13 @@ server { alias /lifat; } + location /display { + set $picfit_target "http://picfit:3001"; + proxy_pass $picfit_target; + + error_page 502 /502/picfit.html; + } + location / { try_files $uri @$http_upgrade; } @@ -35,10 +42,11 @@ server { proxy_set_header X-Forwarded-Proto $scheme; # proxy_set_header X-Lichess-KidMode 1; + + error_page 502 /502/lila.html; } - error_page 502 /5xx.html; - location = /5xx.html { + location /502 { ssi on; internal; root /nginx/errors; diff --git a/docker-compose.yml b/docker-compose.yml index 822a63e9..1be61b70 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,9 +22,8 @@ services: networks: - lila-network environment: - - SCHEME=${SCHEME:-http} - LILA_DOMAIN=${LILA_DOMAIN:-localhost:8080} - - PICFIT_DOMAIN=${PICFIT_DOMAIN:-localhost:3001} + - LILA_URL=${LILA_URL:-http://localhost:8080} - ENABLE_MONITORING=${ENABLE_MONITORING:-false} volumes: - ./repos/lila:/lila @@ -42,8 +41,7 @@ services: networks: - lila-network environment: - - SCHEME=${SCHEME:-http} - - LILA_DOMAIN=${LILA_DOMAIN:-localhost:8080} + - LILA_URL=${LILA_URL:-http://localhost:8080} volumes: - ./repos/lila-ws:/lila-ws - ./conf/lila-ws.conf:/lila-ws.conf @@ -63,6 +61,19 @@ services: - lila - lila_ws + mobile: + build: + context: docker + dockerfile: mobile.Dockerfile + environment: + - LILA_URL=${LILA_URL:-http://localhost:8080} + tty: true + volumes: + - ./repos/dartchess:/dartchess + - ./repos/mobile:/app + profiles: + - mobile + api_docs: build: context: docker diff --git a/docker/mobile.Dockerfile b/docker/mobile.Dockerfile new file mode 100644 index 00000000..d6ba3e87 --- /dev/null +++ b/docker/mobile.Dockerfile @@ -0,0 +1,28 @@ +FROM ghcr.io/cirruslabs/flutter:3.16.0 + +RUN apt-get update +RUN apt install --yes \ + clang \ + cmake \ + libgtk-3-dev \ + ninja-build \ + pkg-config + +RUN dart --disable-analytics +RUN flutter precache +RUN sdkmanager \ + "build-tools;30.0.3" \ + "emulator" \ + "ndk;23.1.7779620" \ + "platforms;android-29" \ + "platforms;android-30" \ + "platforms;android-31" +RUN flutter doctor -v + +# Pre-install mobile app + Flutter dependencies +RUN git clone --depth 1 https://github.com/lichess-org/mobile.git /opt/mobile && \ + cd /opt/mobile && \ + flutter pub get && \ + dart run build_runner build + +WORKDIR /app diff --git a/lila-docker b/lila-docker index b8579f7b..59504e5f 100755 --- a/lila-docker +++ b/lila-docker @@ -1,31 +1,18 @@ #!/bin/bash -e -if [ ! -z "$GITPOD_WORKSPACE_ID" ]; then - export IS_GITPOD=true - export SCHEME=https - export LILA_DOMAIN=$(gp url 8080 | cut -c9-) - export PICFIT_DOMAIN=$(gp url 3001 | cut -c9-) -fi - run_setup() { rust_cmd setup - export $(cat .env | xargs) docker compose build docker compose --profile utils build - docker compose up -d - - echo "Compiling js/css..." docker compose run --rm ui /lila/ui/build if [ "$SETUP_DATABASE" = "true" ]; then setup_database fi - if [ "$IS_GITPOD" = "true" ]; then - rust_cmd gitpod-welcome - fi + rust_cmd welcome } run_start() { @@ -59,7 +46,6 @@ build_all_profiles() { } setup_database() { - # wait for mongodb to be ready while ! docker compose exec mongodb mongo --eval "db.adminCommand('ping')" > /dev/null 2>&1; do echo "Waiting for mongodb to be ready..." sleep 1 @@ -83,20 +69,37 @@ run_formatter() { docker compose exec lila sbt scalafmtAll || docker compose run --rm --entrypoint "sbt scalafmtAll" lila } +run_hostname() { + rust_cmd hostname + + if [ ! -z "$(docker compose ps -a --services | xargs)" ]; then + docker compose down lila lila_ws mobile nginx + docker compose up -d lila lila_ws mobile nginx + fi +} + +run_mobile() { + rust_cmd mobile + docker compose exec mobile adb pair $PHONE_IP:$PAIRING_PORT $PAIRING_CODE + docker compose exec mobile adb connect $PHONE_IP:$CONNECTION_PORT +} + rust_cmd() { if command -v rustup &> /dev/null; then # if the host has Rust installed, use it directly cargo build --release --manifest-path command/Cargo.toml ./command/target/release/command "$@" elif [ "$(uname)" = "Darwin" ]; then - docker run --rm -v "$PWD/command:/command" -w /command messense/cargo-zigbuild:0.18.0 \ + docker run --rm -v "$PWD/command:/command" -w /command messense/cargo-zigbuild:0.18.1 \ cargo zigbuild --release --target universal2-apple-darwin ./command/target/universal2-apple-darwin/release/command "$@" else - docker run --rm -v "$PWD/command:/command" -w /command messense/cargo-zigbuild:0.18.0 \ + docker run --rm -v "$PWD/command:/command" -w /command messense/cargo-zigbuild:0.18.1 \ cargo zigbuild --release ./command/target/release/command "$@" fi + + export $(cat settings.env | xargs) } show_help() { @@ -126,6 +129,12 @@ case $1 in format) run_formatter ;; + hostname) + run_hostname + ;; + mobile) + run_mobile + ;; *) show_help exit 1 diff --git a/nginx/errors/5xx.html b/nginx/errors/502/lila.html similarity index 95% rename from nginx/errors/5xx.html rename to nginx/errors/502/lila.html index b36ac566..ebe23118 100644 --- a/nginx/errors/5xx.html +++ b/nginx/errors/502/lila.html @@ -63,6 +63,9 @@

To Fix:

window.location.reload(); } }; + xhr.onerror = function() { + window.location.reload(); + }; xhr.send(); }, 5*1000); diff --git a/nginx/errors/502/picfit.html b/nginx/errors/502/picfit.html new file mode 100644 index 00000000..3db19f5d --- /dev/null +++ b/nginx/errors/502/picfit.html @@ -0,0 +1 @@ +picfit service is not available