diff --git a/client/src/app.rs b/client/src/app.rs index 615f34e..4769e12 100644 --- a/client/src/app.rs +++ b/client/src/app.rs @@ -68,11 +68,11 @@ impl LesavkaClientApp { let kbd_loop = self.stream_loop_keyboard(hid_ep.clone()); let mou_loop = self.stream_loop_mouse(hid_ep.clone()); - /*────────── optional 30 s auto‑exit in dev mode */ + /*────────── optional 30 s auto-exit in dev mode */ let suicide = async { if self.dev_mode { tokio::time::sleep(Duration::from_secs(30)).await; - warn!("💀 dev‑mode timeout"); + warn!("💀 dev-mode timeout"); std::process::exit(0); } else { std::future::pending::<()>().await @@ -142,7 +142,7 @@ impl LesavkaClientApp { info!("⌨️ dial {}", self.server_addr); // LESAVKA-client let mut cli = RelayClient::new(ep.clone()); - // ✅ use kbd_tx here – fixes E0271 + // ✅ use kbd_tx here - fixes E0271 let outbound = BroadcastStream::new(self.kbd_tx.subscribe()) .filter_map(|r| r.ok()); diff --git a/client/src/input/inputs.rs b/client/src/input/inputs.rs index 960917d..d97a83a 100644 --- a/client/src/input/inputs.rs +++ b/client/src/input/inputs.rs @@ -3,7 +3,7 @@ use anyhow::{bail, Context, Result}; use evdev::{Device, EventType, KeyCode, RelativeAxisCode}; use tokio::{sync::broadcast::Sender, time::{interval, Duration}}; -use tracing::{debug, info}; +use tracing::{debug, info, warn}; use lesavka_common::lesavka::{KeyboardReport, MouseReport}; @@ -14,6 +14,7 @@ pub struct InputAggregator { kbd_tx: Sender, mou_tx: Sender, dev_mode: bool, + released: bool, keyboards: Vec, mice: Vec, camera: Option, @@ -24,7 +25,7 @@ impl InputAggregator { pub fn new(dev_mode: bool, kbd_tx: Sender, mou_tx: Sender) -> Self { - Self { kbd_tx, mou_tx, dev_mode, + Self { kbd_tx, mou_tx, dev_mode, released: false, keyboards: Vec::new(), mice: Vec::new(), camera: None, mic: None } } @@ -55,7 +56,7 @@ impl InputAggregator { } }; - // non‑blocking so fetch_events never stalls the whole loop + // non-blocking so fetch_events never stalls the whole loop dev.set_nonblocking(true).with_context(|| format!("set_non_blocking {:?}", path))?; match classify_device(&dev) { @@ -104,9 +105,20 @@ impl InputAggregator { // Example approach: poll each aggregator in a simple loop let mut tick = interval(Duration::from_millis(10)); loop { + let mut want_toggle = false; + let mut want_kill = false; for kbd in &mut self.keyboards { kbd.process_events(); + want_toggle |= kbd.magic_grab(); + want_kill |= kbd.magic_kill(); } + + if want_toggle { self.toggle_grab(); } + if want_kill { + warn!("🧙 magic chord - killing 🪄 AVADA KEDAVRA!!! 💥💀⚰️"); + std::process::exit(0); + } + for mouse in &mut self.mice { mouse.process_events(); } @@ -114,6 +126,19 @@ impl InputAggregator { tick.tick().await; } } + + fn toggle_grab(&mut self) { + if self.released { + for k in &mut self.keyboards { k.dev.grab().ok(); } + for m in &mut self.mice { m.dev.grab().ok(); } + tracing::info!("🧙 magic chord - restricting devices 🪄 IMPERIUS!!! 🎮🔒"); + } else { + for k in &mut self.keyboards { k.dev.ungrab().ok(); } + for m in &mut self.mice { m.dev.ungrab().ok(); } + tracing::info!("🧙 magic chord - freeing devices 🪄 EXPELLIARMUS!!! 🔓🕊️"); + } + self.released = !self.released; + } } #[derive(Debug)] diff --git a/client/src/input/keyboard.rs b/client/src/input/keyboard.rs index 1778919..ec38bb5 100644 --- a/client/src/input/keyboard.rs +++ b/client/src/input/keyboard.rs @@ -17,13 +17,13 @@ pub struct KeyboardAggregator { } /*───────── helpers ───────────────────────────────────────────────────*/ -/// Monotonically‑increasing ID that can be logged on server & client. +/// Monotonically-increasing ID that can be logged on server & client. static SEQ: AtomicU32 = AtomicU32::new(0); impl KeyboardAggregator { pub fn new(dev: Device, dev_mode: bool, tx: Sender) -> Self { let _ = dev.set_nonblocking(true); - Self { dev, tx, dev_mode, pressed_keys: HashSet::new() } + Self { dev, tx, dev_mode, pressed_keys: HashSet::new(), released: false} } pub fn process_events(&mut self) { @@ -48,17 +48,12 @@ impl KeyboardAggregator { } let report = self.build_report(); - // Generate a local sequence number for debugging/log‑merge only. + // Generate a local sequence number for debugging/log-merge only. let id = SEQ.fetch_add(1, Ordering::Relaxed); if self.dev_mode { debug!(seq = id, ?report, "kbd"); } let _ = self.tx.send(KeyboardReport { data: report.to_vec() }); - - if self.is_magic() { - warn!("🧙 magic chord – exiting 🪄 AVADA KEDAVRA!!! 💥💀"); - std::process::exit(0); - } } } @@ -77,9 +72,16 @@ impl KeyboardAggregator { out } - #[inline] - fn is_magic(&self) -> bool { - self.pressed_keys.contains(&KeyCode::KEY_LEFTCTRL) - && self.pressed_keys.contains(&KeyCode::KEY_ESC) + pub fn has_key(&self, kc: KeyCode) -> bool { self.pressed_keys.contains(&kc) } + + pub fn magic_grab(&self) -> bool { + self.has_key(KeyCode::KEY_LEFTCTRL) + && self.has_key(KeyCode::KEY_LEFTSHIFT) + && self.has_key(KeyCode::KEY_G) + } + + pub fn magic_kill(&self) -> bool { + self.has_key(KeyCode::KEY_LEFTCTRL) + && self.has_key(KeyCode::KEY_ESC) } } diff --git a/client/src/input/keymap.rs b/client/src/input/keymap.rs index 3df59b7..388d54f 100644 --- a/client/src/input/keymap.rs +++ b/client/src/input/keymap.rs @@ -79,7 +79,7 @@ pub fn keycode_to_usage(key: KeyCode) -> Option { KeyCode::KEY_F12 => Some(0x45), // --- Navigation / editing cluster --------------------------------- - KeyCode::KEY_SYSRQ => Some(0x46), // Print‑Screen + KeyCode::KEY_SYSRQ => Some(0x46), // Print-Screen KeyCode::KEY_SCROLLLOCK => Some(0x47), KeyCode::KEY_PAUSE => Some(0x48), KeyCode::KEY_INSERT => Some(0x49), @@ -93,7 +93,7 @@ pub fn keycode_to_usage(key: KeyCode) -> Option { KeyCode::KEY_DOWN => Some(0x51), KeyCode::KEY_UP => Some(0x52), - // --- Keypad / Num‑lock block -------------------------------------- + // --- Keypad / Num-lock block -------------------------------------- KeyCode::KEY_NUMLOCK => Some(0x53), KeyCode::KEY_KPSLASH => Some(0x54), KeyCode::KEY_KPASTERISK => Some(0x55), diff --git a/client/src/input/mouse.rs b/client/src/input/mouse.rs index d44ce25..f2abc3a 100644 --- a/client/src/input/mouse.rs +++ b/client/src/input/mouse.rs @@ -7,7 +7,7 @@ use tracing::{debug, error, warn, trace}; use lesavka_common::lesavka::MouseReport; -const SEND_INTERVAL: Duration = Duration::from_micros(16); +const SEND_INTERVAL: Duration = Duration::from_micros(10); pub struct MouseAggregator { dev: Device, diff --git a/client/src/main.rs b/client/src/main.rs index 5e13f9b..2c7cbe0 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -48,7 +48,7 @@ async fn main() -> Result<()> { if dev_mode { let log_path = Path::new("/tmp").join("lesavka-client.log"); - // file → non‑blocking writer (+ guard) + // file → non-blocking writer (+ guard) let file = OpenOptions::new() .create(true) .write(true) diff --git a/client/src/output/video.rs b/client/src/output/video.rs index 1190dff..0b47a60 100644 --- a/client/src/output/video.rs +++ b/client/src/output/video.rs @@ -41,7 +41,7 @@ impl MonitorWindow { // let pipeline = gst::parse::launch(DESC)? // .downcast::() - // .expect("pipeline down‑cast"); + // .expect("pipeline down-cast"); let pipeline: gst::Pipeline = gst::parse::launch(DESC)? .downcast() .expect("pipeline"); @@ -50,7 +50,7 @@ impl MonitorWindow { // .by_name("src") // .expect("appsrc element not found") // .downcast::() - // .expect("appsrc down‑cast"); + // .expect("appsrc down-cast"); let src: gst_app::AppSrc = pipeline.by_name("src") .expect("appsrc") .downcast() @@ -68,7 +68,7 @@ impl MonitorWindow { Ok(Self { id, _window: window, src }) } - /// Feed one H.264 access‑unit into the pipeline. + /// Feed one H.264 access-unit into the pipeline. pub fn push_packet(&self, pkt: VideoPacket) { // Mutable so we can set the PTS: let mut buf = gst::Buffer::from_slice(pkt.data); diff --git a/scripts/daemon/lesavka-core.sh b/scripts/daemon/lesavka-core.sh index e239642..dd2009a 100644 --- a/scripts/daemon/lesavka-core.sh +++ b/scripts/daemon/lesavka-core.sh @@ -3,7 +3,7 @@ # Proven Pi-5 configfs gadget: HID keyboard+mouse # Still need Web Cam Support + stereo UAC2 -# lesavka-core – one-shot gadget bring-up for Pi-5 / Arch-ARM +# lesavka-core - one-shot gadget bring-up for Pi-5 / Arch-ARM set -euo pipefail # 1) Ensure the dwc2 peripheral overlay is active exactly once @@ -29,7 +29,7 @@ for _ in {1..100}; do # 100 × 100ms = 10s done if [[ -z $UDC ]]; then - echo "[lesavka-core] ⚠️ UDC still absent – trying manual bind" + echo "[lesavka-core] ⚠️ UDC still absent - trying manual bind" for drv in dwc2 dwc3; do drv_root="/sys/bus/platform/drivers/$drv" [[ -d $drv_root ]] || continue @@ -38,7 +38,7 @@ if [[ -z $UDC ]]; then echo "$node" >"$drv_root/bind" 2>/dev/null || continue done done - # re‑check for another 5 s + # re-check for another 5 s for i in {1..50}; do UDC=$(ls /sys/class/udc 2>/dev/null | head -n1) && [[ -n $UDC ]] && break sleep 0.1 diff --git a/scripts/install-server.sh b/scripts/install-server.sh new file mode 100644 index 0000000..09c088b --- /dev/null +++ b/scripts/install-server.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash +# install-server.sh - install and setup all server related apps and environments +set -euo pipefail +ORIG_USER=${SUDO_USER:-$(id -un)} + +echo "==> 1a. Base packages" +sudo pacman -Syq --needed --noconfirm git rustup protobuf gcc pipewire pipewire-pulse tailscale base-devel gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav +if ! command -v yay >/dev/null 2>&1; then + echo "==> 1b. installing yay from AUR ..." + sudo -u "$ORIG_USER" bash -c ' + cd /tmp && git clone --depth 1 https://aur.archlinux.org/yay.git && + cd yay && makepkg -si --noconfirm' +fi +yay -S --noconfirm grpcurl-bin + +echo "==> 2a. Kernel-driver tweaks" +cat <<'EOF' | sudo tee /etc/modprobe.d/gc311-stream.conf >/dev/null +options uvcvideo quirks=0x200 timeout=10000 +EOF + +echo "==> 2b. Predictable /dev names for each capture card" +# probe all v4l2 devices, keep only the two GC311 capture cards +mapfile -t GC_VIDEOS < <( + sudo v4l2-ctl --list-devices | + awk '/Live Gamer MINI/{getline; print $1}' +) + +if [ "${#GC_VIDEOS[@]}" -ne 2 ]; then + echo "❌ Exactly two GC311 capture cards (index0) must be attached!" >&2 + printf ' Detected: %s\n' "${GC_VIDEOS[@]}" + exit 1 +fi + +mapfile -t TAGS < <( + for v in "${GC_VIDEOS[@]}"; do + sudo udevadm info -q property -n "$v" | + awk -F= '/^ID_PATH_TAG=/{print $2}' + done +) + +printf ' ↪ Left card: %s (%s)\n' "${GC_VIDEOS[0]}" "${TAGS[0]}" +printf ' ↪ Right card: %s (%s)\n' "${GC_VIDEOS[1]}" "${TAGS[1]}" + +LEFT_TAG=${TAGS[0]} +RIGHT_TAG=${TAGS[1]} + +sudo tee /etc/udev/rules.d/85-gc311.rules >/dev/null < 3. Rust toolchain" +sudo rustup default stable +sudo -u "$ORIG_USER" rustup default stable + +echo "==> 4a. Source checkout" +SRC_DIR=/var/src/lesavka +REPO_URL=ssh://git@scm.bstein.dev:2242/brad_stein/lesavka.git +if [[ ! -d $SRC_DIR ]]; then + sudo mkdir -p /var/src + sudo chown "$ORIG_USER":"$ORIG_USER" /var/src +fi +if [[ -d $SRC_DIR/.git ]]; then + sudo -u "$ORIG_USER" git -C "$SRC_DIR" pull --ff-only +else + sudo -u "$ORIG_USER" git clone "$REPO_URL" "$SRC_DIR" +fi + +echo "==> 4b. Source build" +sudo -u "$ORIG_USER" bash -c "cd '$SRC_DIR/server' && cargo clean && cargo build --release" + +echo "==> 5. Install binaries" +sudo install -Dm755 "$SRC_DIR/server/target/release/lesavka-server" /usr/local/bin/lesavka-server +sudo install -Dm755 "$SRC_DIR/scripts/lesavka-core.sh" /usr/local/bin/lesavka-core.sh + +echo "==> 6a. Systemd units - lesavka-core" +cat <<'UNIT' | sudo tee /etc/systemd/system/lesavka-core.service >/dev/null +[Unit] +Description=lesavka USB gadget bring-up +After=sys-kernel-config.mount +Requires=sys-kernel-config.mount + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/lesavka-core.sh +RemainAfterExit=yes +CapabilityBoundingSet=CAP_SYS_ADMIN +MountFlags=slave + +[Install] +WantedBy=multi-user.target +UNIT + +echo "==> 6b. Systemd units - lesavka-server" +cat <<'UNIT' | sudo tee /etc/systemd/system/lesavka-server.service >/dev/null +[Unit] +Description=lesavka gRPC relay +After=network.target lesavka-core.service + +[Service] +ExecStart=/usr/local/bin/lesavka-server +Restart=always +Environment=RUST_LOG=lesavka_server=debug,lesavka_server::usb_gadget=info +Environment=RUST_BACKTRACE=1 +Restart=always +RestartSec=5 +StandardError=append:/tmp/lesavka-server.log +StartLimitIntervalSec=30 +StartLimitBurst=10 +User=root + +[Install] +WantedBy=multi-user.target +UNIT + +echo "==> 6c. Systemd units - initialization" +sudo truncate -s 0 /tmp/lesavka-server.log +sudo systemctl daemon-reload +sudo systemctl enable --now lesavka-core +sudo systemctl restart lesavka-core +echo "✅ lesavka-core installed and restarted..." + +sudo systemctl enable --now lesavka-server +sudo systemctl restart lesavka-server +echo "✅ lesavka-server installed and restarted..." diff --git a/scripts/install/server.sh b/scripts/install/server.sh index 6cbd177..0ff952e 100755 --- a/scripts/install/server.sh +++ b/scripts/install/server.sh @@ -13,7 +13,7 @@ if ! command -v yay >/dev/null 2>&1; then fi # yay -S --noconfirm grpcurl-bin -echo "==> 2a. Kernel‑driver tweaks" +echo "==> 2a. Kernel-driver tweaks" cat <<'EOF' | sudo tee /etc/modprobe.d/gc311-stream.conf >/dev/null options uvcvideo quirks=0x200 timeout=10000 EOF diff --git a/scripts/lesavka-core.sh b/scripts/lesavka-core.sh new file mode 100644 index 0000000..dd2009a --- /dev/null +++ b/scripts/lesavka-core.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +# lesavka-core.sh - background stealth daemon to present gadget as usb hub of genuine devices +# Proven Pi-5 configfs gadget: HID keyboard+mouse +# Still need Web Cam Support + stereo UAC2 + +# lesavka-core - one-shot gadget bring-up for Pi-5 / Arch-ARM +set -euo pipefail + +# 1) Ensure the dwc2 peripheral overlay is active exactly once +CFG=/boot/config.txt +grep -q 'dtoverlay=dwc2,dr_mode=peripheral' "$CFG" || echo 'dtoverlay=dwc2,dr_mode=peripheral' >> "$CFG" + +# 2) Load kernel modules (idempotent) +modprobe dwc2 || { echo "dwc2 not in kernel; abort" >&2; exit 1; } +modprobe libcomposite || { echo "libcomposite not in kernel; abort" >&2; exit 1; } + +modprobe -r uvcvideo 2>/dev/null || true +modprobe uvcvideo || { echo "uvcvideo not in kernel; abort" >&2; exit 1; } + +udevadm control --reload +udevadm trigger --subsystem-match=video4linux +udevadm settle + +echo "[lesavka-core] ⏳ waiting for UDC to register ..." +UDC="" +for _ in {1..100}; do # 100 × 100ms = 10s + UDC=$(ls /sys/class/udc 2>/dev/null | head -n1) && [[ -n $UDC ]] && break + sleep 0.1 +done + +if [[ -z $UDC ]]; then + echo "[lesavka-core] ⚠️ UDC still absent - trying manual bind" + for drv in dwc2 dwc3; do + drv_root="/sys/bus/platform/drivers/$drv" + [[ -d $drv_root ]] || continue + for node in /sys/bus/platform/devices/*usb*; do + node=${node##*/} # strip path → “1000480000.usb” + echo "$node" >"$drv_root/bind" 2>/dev/null || continue + done + done + # re-check for another 5 s + for i in {1..50}; do + UDC=$(ls /sys/class/udc 2>/dev/null | head -n1) && [[ -n $UDC ]] && break + sleep 0.1 + done +fi + +[[ -n $UDC ]] || { echo "❌ UDC not present after manual bind"; exit 1; } +echo "[lesavka-core] ✅ UDC detected: $UDC" + +# 3) Mount configfs once +mountpoint -q /sys/kernel/config || mount -t configfs none /sys/kernel/config +G=/sys/kernel/config/usb_gadget/lesavka + +# 4) Tear down any previous half-built gadget +if [[ -d $G ]]; then + echo '' >"$G/UDC" 2>/dev/null || true + sleep 0.2 + find "$G/configs" -type l -delete 2>/dev/null || true + rm -rf "$G" 2>/dev/null || true +fi + +# 5) Create gadget (boot-keyboard + UAC2 mic/spkr, 500 mA max) +mkdir -p "$G" +echo 0x1d6b >"$G/idVendor" # Linux Foundation +echo 0x0104 >"$G/idProduct" # Multifunction Composite Gadget +echo 0x0200 >"$G/bcdUSB" + +mkdir -p "$G/strings/0x409" +echo "$(cat /proc/sys/kernel/random/uuid)" >"$G/strings/0x409/serialnumber" +echo "Lesavka" >"$G/strings/0x409/manufacturer" +echo "Lesavka Composite" >"$G/strings/0x409/product" + +# ----------------------- HID keyboard (usb0) ----------------------- +mkdir -p "$G/functions/hid.usb0" +echo 1 >"$G/functions/hid.usb0/protocol" +echo 1 >"$G/functions/hid.usb0/subclass" +echo 8 >"$G/functions/hid.usb0/report_length" +printf '\x05\x01\x09\x06\xa1\x01\x05\x07\x19\xe0\x29\xe7\x15\x00\x25\x01'\ +'\x75\x01\x95\x08\x81\x02\x95\x01\x75\x08\x81\x01\x95\x05\x75\x01\x05'\ +'\x08\x19\x01\x29\x05\x91\x02\x95\x01\x75\x03\x91\x01\x95\x06\x75\x08'\ +'\x15\x00\x25\x65\x05\x07\x19\x00\x29\x65\x81\x00\xc0' \ + >"$G/functions/hid.usb0/report_desc" + +# ----------------------- HID mouse (usb1) -------------------------- +mkdir -p "$G/functions/hid.usb1" +echo 2 > "$G/functions/hid.usb1/protocol" # Boot mouse +echo 1 > "$G/functions/hid.usb1/subclass" +echo 4 > "$G/functions/hid.usb1/report_length" +printf '\x05\x01\x09\x02\xa1\x01\x09\x01\xa1\x00'\ +'\x05\x09\x19\x01\x29\x03\x15\x00\x25\x01\x95\x03\x75\x01\x81\x02'\ +'\x95\x01\x75\x05\x81\x03'\ +'\x05\x01\x09\x30\x09\x31\x09\x38\x15\x81\x25\x7f\x75\x08\x95\x03\x81\x06'\ +'\xc0\xc0' >"$G/functions/hid.usb1/report_desc" + +# # -- UAC2 Audio +# mkdir -p $G/functions/uac2.usb0 +# echo 48000 > $G/functions/uac2.usb0/c_srate +# echo 2 > $G/functions/uac2.usb0/c_ssize +# echo 2 > $G/functions/uac2.usb0/p_chmask + +# ----------------------- configuration ----------------------------- +mkdir -p "$G/configs/c.1/strings/0x409" +echo 500 > "$G/configs/c.1/MaxPower" +echo "Config 1" > "$G/configs/c.1/strings/0x409/configuration" + +# 6) Finally bind to first available UDC +ln -s $G/functions/hid.usb0 $G/configs/c.1/ +ln -s $G/functions/hid.usb1 $G/configs/c.1/ +# ln -s $G/functions/uac2.usb0 $G/configs/c.1/ + +# 7) Finally bind to first available UDC +echo "$UDC" > "$G/UDC" +echo "[lesavka-core] 🎉 gadget ready on $UDC (keyboard: hidg0, mouse: hidg1)" diff --git a/scripts/manual/usb-reset.sh b/scripts/manual/usb-reset.sh index dc305aa..553184a 100755 --- a/scripts/manual/usb-reset.sh +++ b/scripts/manual/usb-reset.sh @@ -1,3 +1,6 @@ +#!/usr/bin/env bash +# scripts/manual/usb-reset.sh + grpcurl \ -plaintext \ -import-path ./../../common/proto \ diff --git a/scripts/manual/video-frame-fetch.sh b/scripts/manual/video-frame-fetch.sh index 24c9415..c3e3c8b 100755 --- a/scripts/manual/video-frame-fetch.sh +++ b/scripts/manual/video-frame-fetch.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash -PI_HOST="nikto@192.168.42.253" # user@IP‑of lesavka +# scripts/manual/video-stream.sh + +PI_HOST="nikto@192.168.42.253" # user@IP-of lesavka REMOTE_DIR="/tmp" # where eye*-idr.h264 are written +FIRST_FEW=10 set -eu WORKDIR="$(mktemp -d)" @@ -9,9 +12,11 @@ scp "${PI_HOST}:${REMOTE_DIR}/eye*.h264" "$WORKDIR/" echo "🎞️ converting to PNG ..." for h264 in "$WORKDIR"/eye*.h264; do + (( FIRST_FEW == 0 )) && break png="${h264%.h264}.png" ffmpeg -loglevel error -y -f h264 -i "$h264" -frames:v 1 "$png" echo "🖼️ $(basename "$png") ready" xdg-open "$png" >/dev/null 2>&1 & + (( FIRST_FEW-- )) done echo "✅ done - images are opening (directory: $WORKDIR)" diff --git a/scripts/manual/video-stream.sh b/scripts/manual/video-stream.sh index 9dcc60e..0d5d1c3 100755 --- a/scripts/manual/video-stream.sh +++ b/scripts/manual/video-stream.sh @@ -1,3 +1,6 @@ +#!/usr/bin/env bash +# scripts/manual/video-stream.sh + grpcurl -plaintext \ -d '{"id":0,"max_bitrate":6000}' \ -import-path ./../../common/proto -proto lesavka.proto \