From 5a274ec038559748a7341f3ee9e939e7d5421081 Mon Sep 17 00:00:00 2001 From: Trevor Fitzgerald Date: Sun, 26 Nov 2023 15:42:49 -0500 Subject: [PATCH] mobile wip --- README.md | 54 +++++++++-- command/Cargo.lock | 113 +++++++++++++++++++++- command/Cargo.toml | 1 + command/src/main.rs | 204 ++++++++++++++++++++++++++++++++++----- docker-compose.yml | 19 +++- docker/mobile.Dockerfile | 28 ++++++ lila-docker | 30 ++++++ nginx/errors/5xx.html | 3 + 8 files changed, 416 insertions(+), 36 deletions(-) create mode 100644 docker/mobile.Dockerfile diff --git a/README.md b/README.md index 627a2b6d..6bb169e3 100644 --- a/README.md +++ b/README.md @@ -175,16 +175,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: @@ -233,3 +230,48 @@ docker compose run --rm ui bash -c "cd /pgn-viewer && pnpm run sass-dev && pnpm ``` See the changes on the PGN Viewer demo page: http://localhost:8091/ + +### Mobile + +1. 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 + ``` + 3. Configure the mobile settings + ```bash + ./lila-docker mobile + ``` + 4. Enter the IP address, port, and pairing code from the steps below +2. On your Android phone: + 1. Connect your phone to the same wifi network as your host machine + 2. Ensure your phone and can access lila in your browser app using the host value you set above + ``` + http://[your-selection]:8080 + ``` + 3. Enable Developer Mode + 4. In Developer Options + 1. enable wireless debugging + 2. Tap into the wireless debugging settings + 1. Use the "IP address & Port" value in the prompt on your host + 2. Tap "Pair device with pairing code" + 1. Enter the pairing port and code in the prompt on your host +3. On your host machine: + 1. Get a shell on the container: + ```bash + docker compose exec -it mobile bash + + # see your phone + adb devices + ``` + 2. Install the app dependencies: + ```bash + flutter pub get + dart run build_runner build + ``` + 3. Run the app: + ```bash + flutter run -v --dart-define=LICHESS_HOST=$LICHESS_URL --dart-define=LICHESS_WS_HOST=$LICHESS_URL + ``` + - No substitutions necessary. The `$LICHESS_URL` environment variable will already be set on the container. diff --git a/command/Cargo.lock b/command/Cargo.lock index c47f68e5..07f98308 100644 --- a/command/Cargo.lock +++ b/command/Cargo.lock @@ -8,6 +8,12 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "cfg-if" version = "1.0.0" @@ -44,6 +50,7 @@ version = "0.1.0" dependencies = [ "cliclack", "colored", + "local-ip-address", ] [[package]] @@ -59,6 +66,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" @@ -132,6 +145,49 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +[[package]] +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 = "neli" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +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" version = "0.4.0" @@ -152,9 +208,9 @@ checksum = "3bccab0e7fd7cc19f820a1c8c91720af652d0c88dc9664dd72aef2614f04af3b" [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] @@ -181,12 +237,43 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "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 = "smawk" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +[[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 +296,26 @@ 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 = "unicode-ident" version = "1.0.12" @@ -376,5 +483,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] diff --git a/command/Cargo.toml b/command/Cargo.toml index 263d9b27..e4198c11 100644 --- a/command/Cargo.toml +++ b/command/Cargo.toml @@ -8,3 +8,4 @@ edition = "2021" [dependencies] cliclack = "0.1.6" colored = "2.0.4" +local-ip-address = "0.5.6" diff --git a/command/src/main.rs b/command/src/main.rs index 1c6f41c6..95e9a72c 100644 --- a/command/src/main.rs +++ b/command/src/main.rs @@ -1,6 +1,8 @@ -use cliclack::{confirm, input, intro, log, multiselect, spinner}; +use cliclack::{confirm, input, intro, log, multiselect, select, spinner}; use colored::Colorize; +use local_ip_address::local_ip; use std::{ + collections::HashMap, io::Error, path::{Path, PathBuf}, }; @@ -15,22 +17,84 @@ const BANNER: &str = r" "; struct Config { - profiles: Vec, - setup_database: bool, - su_password: String, - password: String, + profiles: Option>, + setup_database: Option, + su_password: Option, + password: Option, + hostname: Option, + phone_ip: Option, + connection_port: Option, + pairing_port: Option, + pairing_code: Option, } impl Config { - fn to_env(&self) -> String { - let mut env = String::new(); + fn to_env(&self) { + let current_env_contents = match std::fs::read_to_string(".env") { + Ok(config) => config, + Err(_) => String::new(), + }; + + let mut config_values: HashMap = HashMap::new(); + for line in current_env_contents.lines() { + let mut split = line.split('='); + let key = split.next().unwrap(); + let value = split.next().unwrap(); + config_values.insert(key.to_string(), value.to_string()); + } + + let mut new_config_values: HashMap = HashMap::new(); + + if let Some(profiles) = &self.profiles { + new_config_values.insert( + "COMPOSE_PROFILES".to_string(), + profiles.join(",").to_string(), + ); + } + + if let Some(setup_database) = &self.setup_database { + new_config_values.insert("SETUP_DATABASE".to_string(), setup_database.to_string()); + } + + if let Some(su_password) = &self.su_password { + new_config_values.insert("SU_PASSWORD".to_string(), su_password.to_string()); + } + + if let Some(password) = &self.password { + new_config_values.insert("PASSWORD".to_string(), password.to_string()); + } + + if let Some(hostname) = &self.hostname { + new_config_values.insert("LILA_HOSTNAME".to_string(), hostname.to_string()); + } + + if let Some(phone_ip) = &self.phone_ip { + new_config_values.insert("PHONE_IP".to_string(), phone_ip.to_string()); + } - 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)); + if let Some(connection_port) = &self.connection_port { + new_config_values.insert("CONNECTION_PORT".to_string(), connection_port.to_string()); + } + + if let Some(pairing_port) = &self.pairing_port { + new_config_values.insert("PAIRING_PORT".to_string(), pairing_port.to_string()); + } - env + if let Some(pairing_code) = &self.pairing_code { + new_config_values.insert("PAIRING_CODE".to_string(), pairing_code.to_string()); + } + + config_values.extend(new_config_values); + + std::fs::write( + ".env", + config_values + .iter() + .map(|(k, v)| format!("{k}={v}")) + .collect::>() + .join("\n"), + ) + .unwrap(); } } @@ -89,6 +153,8 @@ fn main() -> std::io::Result<()> { match args[1].as_str() { "setup" => setup(), + "hostname" => hostname(), + "mobile" => mobile_setup(), "gitpod-welcome" => { gitpod_welcome(); Ok(()) @@ -124,17 +190,25 @@ fn setup() -> std::io::Result<()> { (String::new(), String::new()) }; - let config = Config { - profiles: services - .iter() - .filter_map(|service| service.compose_profile.clone()) - .flatten() - .map(std::string::ToString::to_string) - .collect(), - setup_database, - su_password, - password, - }; + Config { + profiles: Some( + services + .iter() + .filter_map(|service| service.compose_profile.clone()) + .flatten() + .map(std::string::ToString::to_string) + .collect(), + ), + setup_database: Some(setup_database), + su_password: Some(su_password), + password: Some(password), + hostname: None, + phone_ip: None, + connection_port: None, + pairing_port: None, + pairing_code: None, + } + .to_env(); create_placeholder_dirs(); @@ -183,7 +257,6 @@ fn setup() -> std::io::Result<()> { progress.stop(format!("Clone {} ✓", repo.full_name())); } - std::fs::write(".env", config.to_env())?; log::success("Wrote .env") } @@ -204,6 +277,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"), @@ -301,6 +375,14 @@ fn prompt_for_optional_services() -> Result>, Error "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, @@ -328,6 +410,80 @@ fn prompt_for_optional_services() -> Result>, Error .interact() } +fn hostname() -> std::io::Result<()> { + 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", + ) + .item("other", "Other", "Enter a custom hostname") + .interact()? + { + "other" => input("Enter a custom hostname: (It must be resolvable)").interact()?, + selection => selection.to_string(), + }; + + Config { + profiles: None, + setup_database: None, + su_password: None, + password: None, + hostname: Some(hostname), + phone_ip: None, + connection_port: None, + pairing_port: None, + pairing_code: None, + } + .to_env(); + + Ok(()) +} + +fn mobile_setup() -> std::io::Result<()> { + let phone_ip: String = input("Your phone's private IP address") + .placeholder("192.168.x.x or 10.x.x.x") + .interact()?; + let connection_port: u16 = input("Wireless debugging port") + .validate(|input: &String| validate_string_length(input, 5)) + .interact()?; + let pairing_port: u16 = input("Pairing port") + .validate(|input: &String| validate_string_length(input, 5)) + .interact()?; + let pairing_code: u32 = input("Pairing code") + .validate(|input: &String| validate_string_length(input, 6)) + .interact()?; + + Config { + profiles: None, + setup_database: None, + su_password: None, + password: None, + hostname: None, + phone_ip: Some(phone_ip), + connection_port: Some(connection_port), + pairing_port: Some(pairing_port), + pairing_code: Some(pairing_code), + } + .to_env(); + + Ok(()) +} + +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 gitpod_welcome() { println!("{}", "################".green()); println!( diff --git a/docker-compose.yml b/docker-compose.yml index a55db437..de33b27c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,8 +23,8 @@ services: - lila-network environment: - SCHEME=${SCHEME:-http} - - LILA_DOMAIN=${LILA_DOMAIN:-localhost:8080} - - PICFIT_DOMAIN=${PICFIT_DOMAIN:-localhost:3001} + - LILA_DOMAIN=${LILA_DOMAIN:-${LILA_HOSTNAME:-localhost}:8080} + - PICFIT_DOMAIN=${PICFIT_DOMAIN:-${LILA_HOSTNAME:-localhost}:3001} volumes: - ./repos/lila:/lila - ./repos/chessground:/chessground @@ -42,7 +42,7 @@ services: - lila-network environment: - SCHEME=${SCHEME:-http} - - LILA_DOMAIN=${LILA_DOMAIN:-localhost:8080} + - LILA_DOMAIN=${LILA_DOMAIN:-${LILA_HOSTNAME:-localhost}:8080} volumes: - ./repos/lila-ws:/lila-ws - ./conf/lila-ws.conf:/lila-ws.conf @@ -62,6 +62,19 @@ services: - lila - lila_ws + mobile: + build: + context: docker + dockerfile: mobile.Dockerfile + environment: + - LICHESS_URL=${LICHESS_URL:-http://${LILA_HOSTNAME:-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..c3e6053a --- /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 61f22a5e..792a3a71 100755 --- a/lila-docker +++ b/lila-docker @@ -3,6 +3,7 @@ if [ ! -z "$GITPOD_WORKSPACE_ID" ]; then export IS_GITPOD=true export SCHEME=https + export LICHESS_URL=$(gp url 8080) export LILA_DOMAIN=$(gp url 8080 | cut -c9-) export PICFIT_DOMAIN=$(gp url 3001 | cut -c9-) fi @@ -79,6 +80,29 @@ run_formatter() { docker compose exec lila sbt scalafmtAll || docker compose run --rm --entrypoint "sbt scalafmtAll" lila } +run_hostname() { + if [ "$IS_GITPOD" = "true" ]; then + echo "Setting of hostname not available on Gitpod" + echo "Use $(gp url 8080)" + exit 1 + fi + + rust_cmd hostname + export $(cat .env | xargs) + + if [ ! -z "$(docker compose ps -a --services | xargs)" ]; then + docker compose down lila lila_ws nginx + docker compose up -d lila lila_ws nginx + fi +} + +run_mobile() { + rust_cmd mobile + export $(cat .env | xargs) + 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 @@ -122,6 +146,12 @@ case $1 in format) run_formatter ;; + hostname) + run_hostname + ;; + mobile) + run_mobile "${@:2}" + ;; *) show_help exit 1 diff --git a/nginx/errors/5xx.html b/nginx/errors/5xx.html index b36ac566..ebe23118 100644 --- a/nginx/errors/5xx.html +++ b/nginx/errors/5xx.html @@ -63,6 +63,9 @@

To Fix:

window.location.reload(); } }; + xhr.onerror = function() { + window.location.reload(); + }; xhr.send(); }, 5*1000);