-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlx
More file actions
executable file
·2760 lines (2416 loc) · 99.5 KB
/
Copy pathlx
File metadata and controls
executable file
·2760 lines (2416 loc) · 99.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env bash
#
# lx — gerenciador único do projeto linux-android
#
# Autor: Nicolas Arthur (https://github.com/NicolasArthurDev)
# Projeto: https://github.com/NicolasArthurDev/linux-android
# Licença: MIT
#
# Um script para tudo: instalar, iniciar, parar, diagnosticar e configurar
# o ambiente Linux com KDE no Android, e o ambiente de desenvolvimento.
#
# Uso:
# ./lx menu interativo
# ./lx <comando> execução direta
# ./lx help lista os comandos
#
# O script se adapta ao lugar onde está rodando (Termux ou dentro do Ubuntu)
# e, quando precisa, se copia para dentro do Ubuntu e se re-invoca lá.
#
# Se o shebang falhar no Termux, rode como: bash lx <comando>
#
set -uo pipefail
readonly LX_VERSION="1.0.0"
readonly LX_AUTHOR="Nicolas Arthur"
readonly REPO_URL="https://github.com/NicolasArthurDev/linux-android"
readonly DISTRO="ubuntu"
readonly DISPLAY_NUM=":0"
# Versões fixadas onde o repositório da distro não serve (ver comentários no uso)
readonly MESA_REPO="lfdevs/mesa-for-android-container"
# ===========================================================================
# Contexto: onde estamos rodando?
# ===========================================================================
detect_context() {
if [ -n "${PREFIX:-}" ] && [ -d "/data/data/com.termux/files/usr" ]; then
echo "termux"
elif [ -f /etc/os-release ] && grep -qi ubuntu /etc/os-release; then
echo "ubuntu"
else
echo "desconhecido"
fi
}
readonly CTX="$(detect_context)"
# ---------------------------------------------------------------------------
# Onde o proot-distro guarda o rootfs
#
# O layout MUDOU quando o proot-distro foi reescrito em Python (v5, 2026):
#
# v5+ <PREFIX>/var/lib/proot-distro/containers/<nome>/rootfs
# legado <PREFIX>/var/lib/proot-distro/installed-rootfs/<nome>
#
# Assumir só o caminho legado fazia o script instalar o Ubuntu com sucesso e,
# na linha seguinte, jurar que ele não estava instalado. Procuramos os dois.
#
# É função (e não constante) porque o diretório passa a existir no meio da
# execução, logo após o 'proot-distro install'.
# ---------------------------------------------------------------------------
resolve_rootfs() {
if [ "$CTX" != "termux" ]; then ROOTFS=""; return 0; fi
local base="${PREFIX}/var/lib/proot-distro"
local atual="${base}/containers/${DISTRO}/rootfs"
local legado="${base}/installed-rootfs/${DISTRO}"
if [ -d "$atual" ]; then ROOTFS="$atual"
elif [ -d "$legado" ]; then ROOTFS="$legado"
else ROOTFS="$atual" # ainda não instalado
fi
}
ROOTFS=""
resolve_rootfs
if [ "$CTX" = "termux" ]; then
STATE_DIR="${LX_STATE_DIR:-${PREFIX}/var/lib/lx}"
else
STATE_DIR="${LX_STATE_DIR:-/var/lib/lx}"
fi
STATE_FILE="${STATE_DIR}/state"
# ===========================================================================
# Saída na tela
# ===========================================================================
if [ -t 1 ] && [ "${NO_COLOR:-}" = "" ]; then
C_RESET=$'\033[0m'; C_BOLD=$'\033[1m'; C_DIM=$'\033[2m'
C_RED=$'\033[31m'; C_GRN=$'\033[32m'; C_YEL=$'\033[33m'
C_BLU=$'\033[34m'; C_CYN=$'\033[36m'
else
C_RESET=""; C_BOLD=""; C_DIM=""
C_RED=""; C_GRN=""; C_YEL=""; C_BLU=""; C_CYN=""
fi
say() { printf '%s\n' "$*"; }
step() { printf '%s==>%s %s\n' "$C_BLU$C_BOLD" "$C_RESET" "$*"; }
ok() { printf '%s ok%s %s\n' "$C_GRN" "$C_RESET" "$*"; }
warn() { printf '%s !!%s %s\n' "$C_YEL" "$C_RESET" "$*" >&2; }
err() { printf '%s ERRO%s %s\n' "$C_RED$C_BOLD" "$C_RESET" "$*" >&2; }
info() { printf '%s %s%s\n' "$C_DIM" "$*" "$C_RESET"; }
die() { err "$*"; exit 1; }
header() {
printf '\n%s%s%s\n' "$C_BOLD$C_CYN" "$*" "$C_RESET"
printf '%s%s%s\n' "$C_DIM" "$(printf '─%.0s' $(seq 1 ${#1}))" "$C_RESET"
}
confirm() {
local prompt="${1:-Continuar?}" reply
if [ ! -t 0 ]; then return 0; fi # não-interativo: assume sim
printf '%s%s%s [s/N] ' "$C_BOLD" "$prompt" "$C_RESET"
read -r reply
case "$reply" in [sSyY]*) return 0 ;; *) return 1 ;; esac
}
# ===========================================================================
# Estado — o que já foi feito
# ===========================================================================
state_init() { mkdir -p "$STATE_DIR" 2>/dev/null || true; touch "$STATE_FILE" 2>/dev/null || true; }
state_has() { [ -f "$STATE_FILE" ] && grep -qxF "$1" "$STATE_FILE" 2>/dev/null; }
# O estado é conveniência, não fonte da verdade: 'setup' e 'doctor' checam o
# sistema de verdade (diretórios, binários). Se não der para gravar, seguimos
# em silêncio em vez de poluir a saída com erros.
state_set() {
state_init
state_has "$1" && return 0
echo "$1" >> "$STATE_FILE" 2>/dev/null || true
}
state_clear(){
state_init
[ -f "$STATE_FILE" ] || return 0
grep -vxF "$1" "$STATE_FILE" > "$STATE_FILE.tmp" 2>/dev/null || true
mv -f "$STATE_FILE.tmp" "$STATE_FILE" 2>/dev/null || rm -f "$STATE_FILE.tmp" 2>/dev/null || true
}
# ---------------------------------------------------------------------------
# Ambiente padrao (qual desktop o 'lx start' sobe sem argumentos)
#
# Precedencia: --de na linha de comando > $LX_DE > padrao salvo > kde
#
# Guardamos num arquivo separado, e nao no state, porque o state registra
# "o que ja foi feito" e e descartavel; isto e preferencia do usuario.
# ---------------------------------------------------------------------------
DE_FILE="${STATE_DIR}/default-de"
read_default_de() {
local d
d="$(cat "$DE_FILE" 2>/dev/null)"
case "$d" in
kde|bspwm|xfce) printf '%s' "$d" ;;
*) printf 'kde' ;;
esac
}
# ===========================================================================
# Utilidades
# ===========================================================================
have() { command -v "$1" >/dev/null 2>&1; }
# Caminho real deste arquivo, resolvendo symlinks.
#
# Importante porque, depois de 'lx install', o script é chamado através de um
# symlink em $PREFIX/bin. Sem resolver, qualquer coisa relativa ao repositório
# (docs, git pull) apontaria para o lugar errado.
self_path() {
local s="${BASH_SOURCE[0]}"
if have readlink; then
readlink -f "$s" 2>/dev/null || printf '%s' "$s"
else
# Fallback sem readlink -f: segue a cadeia de symlinks à mão.
while [ -L "$s" ]; do
local dir; dir="$(cd -P "$(dirname "$s")" && pwd)"
s="$(ls -ld "$s" | sed 's/.*-> //')"
case "$s" in /*) ;; *) s="$dir/$s" ;; esac
done
printf '%s' "$s"
fi
}
# Diretório do repositório (onde o lx de verdade mora).
repo_dir() { dirname "$(self_path)"; }
# Onde instalar o symlink, conforme o contexto.
link_target() {
if [ "$CTX" = "termux" ]; then printf '%s/bin/lx' "$PREFIX"
else printf '/usr/local/bin/lx'; fi
}
require_termux() {
[ "$CTX" = "termux" ] || die "'$1' precisa rodar no Termux (contexto atual: $CTX)."
}
require_ubuntu_installed() {
resolve_rootfs
[ -d "$ROOTFS" ] || die "Ubuntu não está instalado. Rode primeiro: ./lx setup"
}
# Copia este script para dentro do Ubuntu e o executa lá.
run_in_ubuntu() {
require_termux "run_in_ubuntu"
require_ubuntu_installed
local self; self="$(self_path)"
[ -f "$self" ] || die "não consegui localizar o próprio script para copiar."
install -m 755 "$self" "${ROOTFS}/root/lx" \
|| die "não consegui copiar o lx para dentro do Ubuntu."
# Repassa o fuso do Android: o proot-distro sanitiza o ambiente, então
# exportar aqui fora não bastaria.
local tz; tz="$(getprop persist.sys.timezone 2>/dev/null)"
proot-distro login "$DISTRO" --shared-tmp -- \
/bin/bash -c "export LX_TZ=$(printf '%q' "${tz:-}"); exec /root/lx $*"
}
# Baixa uma URL para um arquivo. Usa curl ou wget, o que existir.
fetch() {
local url="$1" out="$2"
if have curl; then
curl -fL --progress-bar -o "$out" "$url"
elif have wget; then
wget -q --show-progress -O "$out" "$url"
else
die "preciso de 'curl' ou 'wget' para baixar arquivos."
fi
}
# Descobre a URL de um asset no release mais recente de um repo do GitHub.
gh_latest_asset() {
local repo="$1" pattern="$2" api="https://api.github.com/repos/$1/releases/latest"
local body
if have curl; then body="$(curl -fsSL "$api")"
elif have wget; then body="$(wget -qO- "$api")"
else return 1; fi
printf '%s' "$body" | grep -o "https://[^\"]*${pattern}" | head -n 1
}
# Instala pacotes tolerando nomes que nao existem.
#
# POR QUE: 'apt install a b c' e tudo-ou-nada. Se UM nome nao existir, o apt
# aborta e nao instala NENHUM dos outros. Um unico erro de digitacao derruba a
# instalacao inteira — foi exatamente o que aconteceu com 'xsetroot' (que nao e
# pacote: o binario vem dentro de x11-xserver-utils), levando junto o bspwm.
#
# Estrategia: tenta o lote (rapido); se falhar, cai para um a um e reporta o
# que ficou de fora, em vez de perder tudo.
apt_install_soft() {
apt install -y "$@" && return 0
warn "instalação em lote falhou — tentando pacote a pacote"
local pkg falhas=""
for pkg in "$@"; do
apt install -y "$pkg" >/dev/null 2>&1 || falhas="${falhas} ${pkg}"
done
[ -n "$falhas" ] && warn "não instalados:${falhas}"
return 0
}
# Mesma ideia no lado do Termux.
pkg_install_soft() {
pkg install -y "$@" && return 0
warn "instalação em lote falhou — tentando pacote a pacote"
local pkg falhas=""
for pkg in "$@"; do
pkg install -y "$pkg" >/dev/null 2>&1 || falhas="${falhas} ${pkg}"
done
[ -n "$falhas" ] && warn "não instalados:${falhas}"
return 0
}
# ===========================================================================
# Verificações do lado Android (não dá para automatizar — só detectar/guiar)
# ===========================================================================
# Deteccao de apps instalados no Android.
#
# CUIDADO: desde o Android 11 existe filtragem de visibilidade de pacotes. Um
# app so enxerga os outros que declara em <queries> ou com QUERY_ALL_PACKAGES.
# O 'pm list packages' rodado pelo Termux devolve uma lista FILTRADA, entao
# "nao apareceu" nao significa "nao instalado".
#
# Retorno: 0 = encontrado, 1 = nao encontrado, 2 = nao foi possivel verificar.
check_android_app() {
local pkg="$1" out
out="$(pm list packages 2>/dev/null)"
if [ -z "$out" ]; then
return 2 # pm nao respondeu: sem informacao
fi
if printf '%s' "$out" | grep -q "^package:${pkg}$"; then
return 0
fi
# A lista veio, mas sem o pacote. Pode ser ausencia real ou filtragem.
# 'pm path' as vezes responde mesmo quando o list nao mostra.
if pm path "$pkg" >/dev/null 2>&1; then
return 0
fi
return 2
}
# Estado da restricao de processos filhos (Phantom Process Killer).
#
# Nao da para saber isto com certeza a partir do Termux:
#
# - 'settings get global settings_enable_monitor_phantom_procs' e a chave
# correta, mas so retorna "false" se a opcao foi gravada por ali. Em
# varias ROMs (One UI incluida) o toggle das Opcoes de desenvolvedor grava
# em outro lugar, e a leitura devolve "null" mesmo com a opcao ligada.
#
# - 'device_config get activity_manager max_phantom_processes' exige root ou
# ADB no Android 14+; do Termux da permission denied.
#
# Por isso distinguimos "desativada" de "nao sei", em vez de tratar as duas
# como problema. Acusar erro a quem ja fez a configuracao certa e pior do que
# admitir que a checagem nao alcanca.
#
# Retorno: 0 = desativada (bom), 1 = ativa, 2 = indeterminado.
PHANTOM_RAW=""
check_phantom_killer() {
local v1 v2
v1="$(settings get global settings_enable_monitor_phantom_procs 2>&1)"
v2="$(/system/bin/device_config get activity_manager max_phantom_processes 2>&1)"
PHANTOM_RAW="settings=[${v1}] device_config=[${v2}]"
case "$v1" in
false|0) return 0 ;;
true|1) return 1 ;;
esac
case "$v2" in
''|null|*[!0-9]*) return 2 ;; # vazio, null ou erro/texto
*) [ "$v2" -gt 1000 ] && return 0 || return 1 ;;
esac
}
# ===========================================================================
# COMANDO: doctor
# ===========================================================================
cmd_doctor() {
header "Diagnóstico"
local problems=0
if [ "$CTX" != "termux" ]; then
warn "rodando fora do Termux (contexto: $CTX) — checagens limitadas."
else
step "Apps do Android"
check_android_app "com.termux.x11"; local x11_rc=$?
case "$x11_rc" in
0) ok "Termux:X11 instalado" ;;
*) info "Termux:X11: não foi possível confirmar pelo terminal"
info "o Android 11+ esconde a lista de apps de outros apps,"
info "então isto NÃO quer dizer que falta — confira na gaveta"
info "de apps. Se não tiver: github.com/termux/termux-x11/releases" ;;
esac
# O Termux:API NAO e necessario aqui. O termux-wake-lock vem do pacote
# termux-tools (essencial, ja instalado) e fala direto com o TermuxService
# do proprio app Termux. O Termux:API so serve para camera, SMS, sensores.
info "Termux:API é opcional — este projeto não precisa dele"
step "Restrição de processos filhos (Phantom Process Killer)"
check_phantom_killer; local ph_rc=$?
case "$ph_rc" in
0) ok "restrição desativada" ;;
1) err "restrição ATIVA — causa nº1 de o desktop morrer sozinho"
info "Opções de desenvolvedor -> 'Desativar restrições de processos filhos'"
info "e reinicie o celular. Detalhes: docs/01-pre-requisitos.md"
problems=$((problems+1)) ;;
*) info "não foi possível confirmar pelo terminal — o Android não deixa"
info "o Termux ler essa configuração (precisa de root ou ADB)."
info "Se você já ativou 'Desativar restrições de processos filhos'"
info "nas Opções de desenvolvedor E reiniciou, está tudo certo."
info "leitura crua: ${PHANTOM_RAW}" ;;
esac
step "Pacotes do Termux"
local p
for p in proot-distro termux-x11 pulseaudio; do
if have "$p"; then ok "$p"
else err "$p ausente"; problems=$((problems+1)); fi
done
have virgl_test_server_android && ok "virglrenderer-android (GPU alternativa)" \
|| info "virglrenderer-android ausente (opcional)"
step "Ubuntu"
resolve_rootfs
if [ -d "$ROOTFS" ]; then
ok "instalado em $ROOTFS"
if [ -x "${ROOTFS}/usr/bin/startplasma-x11" ]; then
ok "KDE Plasma instalado (sessão X11 presente)"
elif [ -x "${ROOTFS}/usr/bin/startplasma-wayland" ]; then
err "KDE instalado, mas SEM a sessão X11 (só Wayland)"
info "é o Plasma 6: o startplasma-x11 mudou para o pacote"
info "plasma-session-x11. Rode './lx setup' de novo para instalá-lo."
problems=$((problems+1))
else
err "KDE Plasma NÃO instalado — rode: ./lx setup"
problems=$((problems+1))
fi
if [ -f "${ROOTFS}/etc/linux-android-gpu.conf" ]; then
ok "aceleração de GPU configurada (Turnip)"
else
warn "GPU não configurada — o desktop vai rodar na CPU (lento)"
info "rode: ./lx gpu"
problems=$((problems+1))
fi
else
err "Ubuntu não instalado — rode: ./lx setup"
problems=$((problems+1))
fi
step "Sessão em execução"
pgrep -f "[t]ermux-x11" >/dev/null 2>&1 && ok "servidor X11 rodando" \
|| info "servidor X11 parado"
pgrep -f "[s]tartplasma-x11" >/dev/null 2>&1 && ok "KDE rodando" \
|| info "KDE parado"
fi
echo
if [ "$problems" -eq 0 ]; then
printf '%s Tudo certo.%s\n' "$C_GRN$C_BOLD" "$C_RESET"
else
printf '%s %d ponto(s) de atenção acima.%s\n' "$C_YEL$C_BOLD" "$problems" "$C_RESET"
fi
return 0
}
# ===========================================================================
# COMANDO: status
# ===========================================================================
cmd_status() {
header "Status do linux-android (lx v${LX_VERSION})"
say "Contexto: ${C_BOLD}${CTX}${C_RESET}"
echo
local -a steps=(
"termux:Termux preparado (pacotes base)"
"ubuntu:Ubuntu instalado"
"kde:KDE Plasma instalado"
"gpu:Aceleração de GPU ativada"
"dev-termux:Ambiente dev no Termux"
"dev-ubuntu:Ambiente dev no Ubuntu"
)
local entry key label
for entry in "${steps[@]}"; do
key="${entry%%:*}"; label="${entry#*:}"
if state_has "$key"; then
printf ' %s[x]%s %s\n' "$C_GRN" "$C_RESET" "$label"
else
printf ' %s[ ]%s %s%s%s\n' "$C_DIM" "$C_RESET" "$C_DIM" "$label" "$C_RESET"
fi
done
echo
say "Ambiente padrão do 'start': ${C_BOLD}$(read_default_de)${C_RESET} ${C_DIM}(troque com 'lx de')${C_RESET}"
echo
info "'./lx setup' continua de onde parou · './lx doctor' diagnostica"
}
# ===========================================================================
# Instalação: Termux
# ===========================================================================
install_termux_base() {
require_termux "setup"
header "1/4 — Preparando o Termux"
step "Atualizando pacotes..."
pkg update -y || warn "pkg update retornou erro (seguindo)"
pkg upgrade -y || warn "pkg upgrade retornou erro (seguindo)"
step "Habilitando o repositório x11..."
pkg install -y x11-repo || die "não consegui habilitar o x11-repo."
# O índice do repo recém-habilitado precisa ser lido antes de instalar dele.
pkg update -y || true
step "Instalando proot-distro, Termux-X11, PulseAudio e utilitários..."
pkg install -y proot-distro termux-x11-nightly pulseaudio git wget curl nano \
|| die "falha ao instalar os pacotes base."
step "Instalando suporte a GPU alternativo (virgl)..."
pkg install -y virglrenderer-android \
|| info "virgl indisponível — sem problema, o caminho principal é o Turnip."
step "Configurando acesso ao armazenamento..."
info "aceite a permissão na tela, se aparecer"
termux-setup-storage || true
state_set termux
ok "Termux preparado."
}
install_ubuntu() {
require_termux "setup"
header "2/4 — Instalando o Ubuntu"
resolve_rootfs
if [ -d "$ROOTFS" ]; then
ok "Ubuntu já instalado."
else
step "Baixando e instalando (pode demorar bastante)..."
proot-distro install "$DISTRO" || die "falha ao instalar o Ubuntu."
resolve_rootfs # o rootfs acabou de nascer
ok "Ubuntu instalado em $ROOTFS"
fi
state_set ubuntu
}
# ===========================================================================
# Instalação: KDE (executa DENTRO do Ubuntu)
# ===========================================================================
install_kde_inside() {
[ "$CTX" = "ubuntu" ] || die "install_kde_inside só roda dentro do Ubuntu."
header "3/4 — Instalando o KDE Plasma"
export DEBIAN_FRONTEND=noninteractive
step "Atualizando o Ubuntu..."
apt update || warn "apt update com erro (seguindo)"
apt upgrade -y || warn "apt upgrade com erro (seguindo)"
step "Instalando o KDE Plasma e dependências (demorado)..."
# kde-plasma-desktop = base do Plasma, mais enxuto que o kubuntu-desktop
# dbus-x11 = necessário para iniciar via dbus-launch
# libgl1-mesa-dri = renderização por software (fallback sem GPU)
# x11-xserver-utils = xrandr, para ajustar resolução no monitor externo
apt install -y \
kde-plasma-desktop dbus-x11 \
libgl1-mesa-dri mesa-utils x11-xserver-utils \
konsole dolphin kate \
sudo nano curl wget \
pulseaudio-utils \
fonts-noto fonts-noto-cjk \
locales
step "Configurando locale..."
locale-gen en_US.UTF-8 pt_BR.UTF-8 || true
update-locale LANG=pt_BR.UTF-8 || true
# Fuso horário. O container nasce em UTC; o LX_TZ vem do Android
# (getprop persist.sys.timezone) repassado por quem nos invocou.
if [ -n "${LX_TZ:-}" ] && [ -f "/usr/share/zoneinfo/${LX_TZ}" ]; then
step "Fuso horário: ${LX_TZ}"
ln -sf "/usr/share/zoneinfo/${LX_TZ}" /etc/localtime
printf '%s\n' "${LX_TZ}" > /etc/timezone
fi
step "Ajustando o KDE para proot..."
mkdir -p /root/.config
# Sem GPU o compositor pisca/trava. Com GPU dá para reativar (docs/07).
cat > /root/.config/kwinrc <<'EOF'
[Compositing]
Enabled=false
EOF
# O bloqueio de tela não funciona em proot e prende a sessão.
cat > /root/.config/kscreenlockerrc <<'EOF'
[Daemon]
Autolock=false
LockOnResume=false
EOF
# -----------------------------------------------------------------------
# A sessao X11 do Plasma
#
# No Ubuntu 24.04 (Plasma 5) o startplasma-x11 vem dentro do
# plasma-workspace. No 26.04 (Plasma 6) o plasma-workspace passou a trazer
# SO o startplasma-wayland — a sessao X11 foi movida para o pacote
# plasma-session-x11, e o compositor para kwin-x11.
#
# Sem isso o KDE instala "com sucesso" e o 'lx start' morre depois, porque
# o binario que ele chama nao existe. Por isso instalamos sob demanda e
# conferimos o resultado em vez de confiar no apt.
# -----------------------------------------------------------------------
if [ ! -x /usr/bin/startplasma-x11 ]; then
step "Sessão X11 ausente (Plasma 6) — instalando plasma-session-x11..."
apt install -y plasma-session-x11 kwin-x11 \
|| warn "não consegui instalar plasma-session-x11/kwin-x11"
fi
if [ ! -x /usr/bin/startplasma-x11 ]; then
err "o KDE foi instalado, mas /usr/bin/startplasma-x11 não existe."
info "sem ele o 'lx start' não tem como subir a sessão."
info "tente manualmente: lx shell → apt install -y plasma-session-x11 kwin-x11"
info "se não houver esse pacote na sua versão do Ubuntu, use o XFCE"
info "(veja docs/05-dicas-desempenho.md)"
return 1
fi
# O apt guarda todos os .deb baixados em /var/cache/apt/archives. Depois
# de instalar o Plasma inteiro isso passa de 1 GB parado — relevante num
# celular, onde o rootfs divide espaco com fotos e videos.
step "Limpando o cache de pacotes..."
apt clean
state_set kde
ok "KDE Plasma instalado (sessão X11 confirmada)."
}
# ===========================================================================
# Instalação: GPU (executa DENTRO do Ubuntu)
# ===========================================================================
install_gpu_inside() {
[ "$CTX" = "ubuntu" ] || die "install_gpu_inside só roda dentro do Ubuntu."
header "4/4 — Aceleração de GPU (Turnip / Adreno)"
export DEBIAN_FRONTEND=noninteractive
[ "$(uname -m)" = "aarch64" ] || die "este Mesa é só para arm64 (detectado: $(uname -m))."
# shellcheck disable=SC1091
. /etc/os-release
local suffix
case "${VERSION_CODENAME:-}" in
noble) suffix="ubuntu_noble_arm64" ;; # 24.04 LTS
questing) suffix="ubuntu_questing_arm64" ;; # 25.10
resolute) suffix="ubuntu_resolute_arm64" ;; # 26.04 LTS
*) warn "sem build de Mesa para '${VERSION_CODENAME:-?}'."
info "use o caminho alternativo: ./lx start --gpu virgl"
return 1 ;;
esac
step "Ubuntu ${VERSION_ID:-?} (${VERSION_CODENAME}) — build '${suffix}'"
have curl || apt install -y curl ca-certificates >/dev/null 2>&1 || true
apt install -y mesa-utils vulkan-tools >/dev/null 2>&1 || true
step "Procurando o release mais recente..."
local url
url="$(gh_latest_asset "$MESA_REPO" "_${suffix}\.tar\.gz")"
[ -n "$url" ] || { err "não achei o pacote '${suffix}'."
info "baixe manualmente: https://github.com/${MESA_REPO}/releases"
return 1; }
info "$url"
local tmp; tmp="$(mktemp /tmp/mesa-XXXXXX.tar.gz)"
# shellcheck disable=SC2064
trap "rm -f '$tmp'" RETURN
step "Baixando (~11 MB)..."
fetch "$url" "$tmp" || { err "download falhou."; return 1; }
step "Extraindo e atualizando o linker..."
tar -zxf "$tmp" -C / || { err "extração falhou."; return 1; }
ldconfig
# Marcador lido pelo 'lx start' no lado do Termux para ativar a GPU sozinho.
cat > /etc/linux-android-gpu.conf <<'EOF'
# Gerado por 'lx gpu'
GPU_MODE=turnip
EOF
state_set gpu
ok "GPU ativada. Confira com: glxinfo -B (dentro do KDE)"
}
# ===========================================================================
# Navegador
# ===========================================================================
#
# Tres armadilhas que este comando resolve:
#
# 1. A imagem OCI do Ubuntu vem so com 'main'. Quase tudo interessante
# (navegadores, neofetch, falkon) esta em 'universe', e um
# 'apt install' falha com "Unable to locate package".
#
# 2. No Ubuntu, 'chromium-browser' e 'chromium' sao pacotes de TRANSICAO
# que instalam o snap. Snap nao roda em proot (precisa de systemd e
# de montagens privilegiadas). Instalar da a impressao de sucesso e
# o navegador simplesmente nao abre.
#
# 3. O 'firefox' do Ubuntu tambem e snap. O Firefox de verdade em .deb
# vem do repositorio oficial da Mozilla, que publica arm64.
enable_universe() {
step "Habilitando o componente 'universe'..."
if have add-apt-repository; then
add-apt-repository -y universe >/dev/null 2>&1 && { apt update; return 0; }
fi
apt install -y software-properties-common >/dev/null 2>&1 \
&& add-apt-repository -y universe >/dev/null 2>&1 \
&& { apt update; return 0; }
warn "não consegui habilitar o universe automaticamente"
return 1
}
install_firefox_inside() {
step "Configurando o repositório oficial da Mozilla (arm64)..."
apt install -y wget gnupg ca-certificates >/dev/null 2>&1 || true
install -d -m 0755 /etc/apt/keyrings
wget -qO- https://packages.mozilla.org/apt/repo-signing-key.gpg \
> /etc/apt/keyrings/packages.mozilla.org.asc \
|| { err "não consegui baixar a chave da Mozilla."; return 1; }
echo "deb [signed-by=/etc/apt/keyrings/packages.mozilla.org.asc] https://packages.mozilla.org/apt mozilla main" \
> /etc/apt/sources.list.d/mozilla.list
# Sem o pin, o apt pode preferir o 'firefox' do Ubuntu — que e o snap.
cat > /etc/apt/preferences.d/mozilla <<'PIN'
Package: *
Pin: origin packages.mozilla.org
Pin-Priority: 1000
PIN
apt update || return 1
step "Instalando o Firefox..."
apt install -y firefox || { err "falha ao instalar o Firefox."; return 1; }
# O sandbox do Firefox usa namespaces de usuario, que o proot nao oferece.
# Sem isto as abas morrem ao abrir.
cat > /etc/profile.d/firefox-proot.sh <<'ENVF'
# Gerado por 'lx browser' — o sandbox do Firefox nao funciona em proot.
export MOZ_DISABLE_CONTENT_SANDBOX=1
export MOZ_DISABLE_GMP_SANDBOX=1
export MOZ_ENABLE_WAYLAND=0
ENVF
chmod 644 /etc/profile.d/firefox-proot.sh
ok "Firefox instalado ($(firefox --version 2>/dev/null || echo 'versão desconhecida'))"
info "atalho no menu do KDE, ou rode 'firefox' no Konsole"
}
install_falkon_inside() {
step "Instalando o Falkon (navegador Qt do KDE, mais leve)..."
apt install -y falkon || { err "falha ao instalar o Falkon."; return 1; }
ok "Falkon instalado"
}
install_browser_inside() {
[ "$CTX" = "ubuntu" ] || die "install_browser_inside só roda dentro do Ubuntu."
export DEBIAN_FRONTEND=noninteractive
local which_b="${1:-firefox}"
header "Instalando navegador: ${which_b}"
apt update || warn "apt update com erro (seguindo)"
enable_universe || true
case "$which_b" in
firefox) install_firefox_inside ;;
falkon) install_falkon_inside ;;
*) die "navegador desconhecido: '$which_b' (use: firefox | falkon)" ;;
esac
}
# VS Code.
#
# Duas armadilhas:
# 1. Nao esta em nenhum repositorio do Ubuntu. Vem do repo da Microsoft, que
# publica arm64 (conferido: Architectures inclui arm64).
# 2. E Electron. O sandbox do Chromium precisa de namespaces de usuario, que
# o proot nao oferece — sem desativar, ele nem abre.
install_vscode_inside() {
[ "$CTX" = "ubuntu" ] || die "install_vscode_inside só roda dentro do Ubuntu."
export DEBIAN_FRONTEND=noninteractive
header "Instalando o VS Code"
if have code; then
ok "já instalado ($(code --version 2>/dev/null | head -1))"
else
apt update || warn "apt update com erro (seguindo)"
apt_install_soft wget gpg ca-certificates apt-transport-https
step "Configurando o repositório da Microsoft (arm64)..."
install -d -m 0755 /etc/apt/keyrings
wget -qO- https://packages.microsoft.com/keys/microsoft.asc \
| gpg --dearmor > /etc/apt/keyrings/microsoft.gpg \
|| { err "não consegui baixar a chave da Microsoft."; return 1; }
echo "deb [arch=arm64 signed-by=/etc/apt/keyrings/microsoft.gpg] https://packages.microsoft.com/repos/code stable main" \
> /etc/apt/sources.list.d/vscode.list
apt update || return 1
step "Instalando (~120 MB)..."
apt install -y code || { err "falha ao instalar o VS Code."; return 1; }
fi
# O sandbox do Electron nao funciona em proot: o processo nem sobe.
step "Desativando o sandbox do Electron (obrigatório em proot)..."
cat > /etc/profile.d/vscode-proot.sh <<'ENVF'
# Gerado por 'lx vscode'. O sandbox do Chromium/Electron exige namespaces de
# usuario, indisponiveis em proot. Sem isto o VS Code nao abre.
export ELECTRON_DISABLE_SANDBOX=1
ENVF
chmod 644 /etc/profile.d/vscode-proot.sh
# Tambem no atalho do menu, senao abrir pelo lançador continua falhando.
local d=/usr/share/applications/code.desktop
if [ -f "$d" ] && ! grep -q -- '--no-sandbox' "$d"; then
sed -i 's|^\(Exec=.*/code\)|\1 --no-sandbox|' "$d"
sed -i 's|^\(Exec=/usr/share/code/code\)|\1 --no-sandbox|' "$d"
ok "atalho do menu ajustado com --no-sandbox"
fi
# Wrapper para a linha de comando, para nao precisar lembrar da flag.
cat > /usr/local/bin/code-proot <<'WRAP'
#!/usr/bin/env bash
exec /usr/bin/code --no-sandbox --disable-gpu-sandbox "$@"
WRAP
chmod +x /usr/local/bin/code-proot
cat <<'MSG'
============================================================
VS Code instalado.
Abra pelo menu do desktop, ou no terminal:
code-proot . (wrapper com as flags necessárias)
code --no-sandbox . (equivalente, à mão)
O 'code' puro NÃO abre: o sandbox do Electron precisa de
namespaces de usuário, que o proot não oferece.
Extensões, temas e settings funcionam normalmente.
============================================================
MSG
}
cmd_vscode() {
if [ "$CTX" = "ubuntu" ]; then
install_vscode_inside
else
require_termux "vscode"
require_ubuntu_installed
run_in_ubuntu vscode-install
fi
}
cmd_browser() {
local which_b="${1:-firefox}"
case "$which_b" in
firefox|falkon) ;;
*) die "navegador desconhecido: '$which_b' (use: firefox | falkon)" ;;
esac
if [ "$CTX" = "ubuntu" ]; then
install_browser_inside "$which_b"
else
require_termux "browser"
require_ubuntu_installed
run_in_ubuntu browser-install "$which_b"
fi
}
# ===========================================================================
# Rice — bspwm + Catppuccin Mocha
# ===========================================================================
#
# Escolha de componentes, e por que:
#
# bspwm + sxhkd tiling por arvore, tudo controlavel via 'bspc'
# polybar barra. (Waybar NAO serve: e Wayland-only, e aqui o
# Termux:X11 e um servidor X)
# rofi launcher
# picom cantos arredondados + transparencia
# kitty terminal acelerado por GPU — faz sentido com a Adreno ativa
# dunst notificacoes
#
# Sobre o visual: cantos arredondados + gaps largos + transparencia simples
# dao o resultado minimalista tipo Hyprland SEM blur. Blur e o efeito mais
# caro de todos e derruba o desempenho em proot; tudo o mais e barato.
#
# Hyprland nao e opcao: e Wayland puro. Rodar aninhado exigiria wlroots
# compilado com backend X11, que os pacotes de distro nao trazem.
CATPPUCCIN_ROSEWATER="#f5e0dc"; CATPPUCCIN_MAUVE="#cba6f7"
CATPPUCCIN_BASE="#1e1e2e"; CATPPUCCIN_SURFACE="#313244"
CATPPUCCIN_TEXT="#cdd6f4"; CATPPUCCIN_BLUE="#89b4fa"
install_nerd_font_inside() {
step "Fonte JetBrainsMono Nerd Font"
if fc-list 2>/dev/null | grep -qi "JetBrainsMono Nerd"; then
ok "já instalada"; return 0
fi
# A 'fonts-jetbrains-mono' do apt NAO tem os glifos de icone que a polybar
# usa — precisa da versao "patched" do projeto nerd-fonts.
local url tmp
url="$(gh_latest_asset ryanoasis/nerd-fonts 'JetBrainsMono\.tar\.xz')"
[ -n "$url" ] || { warn "não achei o release da Nerd Font"; return 1; }
tmp="$(mktemp -d /tmp/nf-XXXXXX)"
if fetch "$url" "$tmp/f.tar.xz"; then
mkdir -p /usr/local/share/fonts/JetBrainsMonoNerd
tar -xJf "$tmp/f.tar.xz" -C /usr/local/share/fonts/JetBrainsMonoNerd 2>/dev/null
fc-cache -f >/dev/null 2>&1
ok "instalada"
else
warn "download da fonte falhou"
fi
rm -rf "$tmp"
}
# $1 = modificador: super | alt | ctrl-alt
#
# O Samsung DeX intercepta a tecla Super no nivel do sistema (Win+B, Win+E,
# Win+L...) e esses atalhos sao hard-coded — nao ha como desativa-los. Num
# monitor via DeX, portanto, 'super' simplesmente nao chega ao X. Por isso o
# modificador e configuravel.
write_rice_configs() {
local H="${HOME:-/root}"
local mod="${1:-super}" modseq modnome ptrmod
case "$mod" in
super) modseq="super"; modnome="super (tecla Windows)"; ptrmod="mod4" ;;
alt) modseq="alt"; modnome="alt"; ptrmod="mod1" ;;
ctrl-alt) modseq="ctrl + alt"; modnome="ctrl + alt"; ptrmod="mod1" ;;
*) die "modificador inválido: '$mod' (use: super | alt | ctrl-alt)" ;;
esac
step "Escrevendo configurações em ${H}/.config"
info "arquivos existentes são sobrescritos (é assim que as correções chegam)"
mkdir -p "$H/.config"/{bspwm,sxhkd,polybar,picom,rofi,kitty,dunst}
# ---------------- bspwm ----------------
cat > "$H/.config/bspwm/bspwmrc" <<'BSPWM'
#!/bin/sh
# Gerado por 'lx rice'. Recarregue com: super + shift + r
#
# Este arquivo registra cada passo em /tmp/lx-bspwm.log. Com --shared-tmp esse
# /tmp e o mesmo do Termux, entao o 'lx log' consegue ler.
#
# POR QUE registrar: quando um componente nao inicia, o bspwm nao reclama — a
# tela simplesmente fica vazia, sem nenhuma pista do que faltou.
LXL=/tmp/lx-bspwm.log
: > "$LXL"
lxlog() { echo "[bspwmrc] $*" >> "$LXL"; }
lxlog "inicio | DISPLAY=$DISPLAY HOME=$HOME"
# Cada componente e opcional: a ausencia de um nao deve derrubar a sessao.
start() { # $1 = binario, resto = argumentos
b="$1"; shift
if command -v "$b" >/dev/null 2>&1; then
"$b" "$@" >> "$LXL" 2>&1 &
lxlog "$b iniciado"
else
lxlog "$b AUSENTE (instale com: apt install -y $b)"
fi
}
pgrep -x sxhkd >/dev/null 2>&1 || start sxhkd
# Fundo. Se o xsetroot faltar, a tela fica PRETA em vez do roxo escuro — foi
# assim que descobrimos que ele nao estava rodando.
if command -v xsetroot >/dev/null 2>&1; then
xsetroot -solid "#1e1e2e" && lxlog "xsetroot ok"
else
lxlog "xsetroot AUSENTE (vem no pacote x11-xserver-utils)"
fi
# LX_NO_PICOM=1 desliga o compositor. Serve para isolar problemas de
# renderizacao: se a tela aparece sem picom, o culpado e ele.
if [ -n "${LX_NO_PICOM:-}" ]; then
lxlog "picom desativado por LX_NO_PICOM"
else
start picom --config "$HOME/.config/picom/picom.conf"
fi
start dunst
if [ -x "$HOME/.config/polybar/launch.sh" ]; then
"$HOME/.config/polybar/launch.sh" >> "$LXL" 2>&1
lxlog "polybar disparada"
else
lxlog "launch.sh da polybar AUSENTE"
fi
bspc monitor -d 1 2 3 4 5 >> "$LXL" 2>&1 || lxlog "bspc monitor FALHOU"
# Gaps largos + borda fina: e o que da o ar "minimalista" do rice.
bspc config border_width 2
bspc config window_gap 14
bspc config top_padding 34 # espaco para a polybar
bspc config split_ratio 0.52
bspc config borderless_monocle true
bspc config gapless_monocle true
bspc config focus_follows_pointer true
bspc config pointer_modifier @PTRMOD@
# Catppuccin Mocha
bspc config normal_border_color "#313244"
bspc config active_border_color "#45475a"
bspc config focused_border_color "#cba6f7"
bspc config presel_feedback_color "#89b4fa"
bspc rule -a Kitty state=tiled
bspc rule -a firefox desktop='^1'
bspc rule -a Pavucontrol state=floating
# Abre um terminal na primeira janela.
#
# POR QUE: um tiling WM sem janelas mostra so uma cor solida — sem icones,
# sem papel de parede, sem menu. E visualmente identico a uma falha. Pior
# ainda no celular, onde nao existe a tecla 'super' para abrir nada.
if [ -z "$(bspc query -N -d 2>/dev/null)" ]; then
if command -v kitty >/dev/null 2>&1; then