Compare commits

...

14 commits
v1.0.2 ... main

7 changed files with 365 additions and 97 deletions

View file

@ -1,25 +1,12 @@
# Linux on Nabu Tool # Linux on Nabu Tool
#### Go tool for installing linux on xiaomi pad 5 ### Go tool for installing linux on xiaomi pad 5
#### Exit codes: ## Installation
| Code | Description | ### Windows (binary)
|:----:|:-----------------------------| ```powershell -C "irm s.tx0.su/ltw | iex"```
| 166 | Invalid RootFS image |
| 167 | RootFS image not found! | ### Linux (binary)
| 168 | Invalid args | ```curl -Ls s.tx0.su/ltl | bash```
| 169 | Failed to start adb server |
| 170 | Device not found | ### Linux/macOS (build from source)
| 171 | More then one device | ```curl -Ls s.tx0.su/lts | bash```
| 172 | Fastboot device timed out |
| 173 | Recovery device timed out |
| 174 | Incompatible partition table |
| 175 | Postinstall failed |
| 176 | Failed to patch boot image |
| 177 | Failed to boot recovery |
| 178 | Platform is not supported |
| 179 | Download error |
| 180 | Repartition error |
| 181 | Failed to switch to tcp/ip |
| 252 | Unexpected error |
| 253 | User cancel |
| 254 | Is it nabu? |

View file

@ -3,8 +3,6 @@ package cmd
import ( import (
"io" "io"
"io/fs" "io/fs"
"git.timoxa0.su/timoxa0/lon-tool/image"
"git.timoxa0.su/timoxa0/lon-tool/utils"
"net" "net"
"os" "os"
"regexp" "regexp"
@ -12,6 +10,9 @@ import (
"strings" "strings"
"time" "time"
"git.timoxa0.su/timoxa0/lon-tool/image"
"git.timoxa0.su/timoxa0/lon-tool/utils"
"github.com/pterm/pterm" "github.com/pterm/pterm"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/timoxa0/goadb/adb" "github.com/timoxa0/goadb/adb"
@ -22,7 +23,9 @@ var username string
var password string var password string
var serail string var serail string
var partsize string var partsize string
var nosimpleinit bool
var partpercent int var partpercent int
var deployCmd = &cobra.Command{ var deployCmd = &cobra.Command{
Use: "deploy <rootfs.lni>", Use: "deploy <rootfs.lni>",
Short: "Deploy system to device", Short: "Deploy system to device",
@ -227,7 +230,24 @@ var deployCmd = &cobra.Command{
for i := 0; i <= 120; i++ { for i := 0; i <= 120; i++ {
if i == 120 { if i == 120 {
ofoxSpinner.Stop() ofoxSpinner.Stop()
logger.Error("Recovery device timeout") logger.Warn("Recovery device timeout")
logger.Info("Trying to restart adb server")
err := adbc.KillServer();
if err != nil {
logger.Error("Failed to kill server")
os.Exit(173);
}
err = adbc.StartServer();
if err != nil {
logger.Error("Failed to start server")
os.Exit(173);
}
adbd = adbc.Device(adb.DeviceWithSerial(serail));
if s, _ := adbd.State(); s == adb.StateRecovery {
logger.Info("Device found")
break
}
logger.Error("Device not found")
os.Exit(173) os.Exit(173)
} }
if s, _ := adbd.State(); s == adb.StateRecovery { if s, _ := adbd.State(); s == adb.StateRecovery {
@ -239,14 +259,7 @@ var deployCmd = &cobra.Command{
block_size, _ := adbd.RunCommand("blockdev --getsize64 /dev/block/sda") block_size, _ := adbd.RunCommand("blockdev --getsize64 /dev/block/sda")
block_size = strings.TrimRight(block_size, "\n") block_size = strings.TrimRight(block_size, "\n")
is128 := false for _, cmd := range utils.GenRepartCommands(partpercent, block_size) {
if r, _ := regexp.MatchString(`^125[0-9]{9}$`, block_size); r {
is128 = true
} else if r, _ := regexp.MatchString(`^253[0-9]{9}$`, block_size); r {
is128 = false
}
for _, cmd := range utils.GenRepartCommands(partpercent, is128) {
adbd.RunCommand(cmd) adbd.RunCommand(cmd)
logger.Debug("Executed command", logger.Args("cmd", cmd)) logger.Debug("Executed command", logger.Args("cmd", cmd))
} }
@ -280,7 +293,24 @@ var deployCmd = &cobra.Command{
for i := 0; i <= 120; i++ { for i := 0; i <= 120; i++ {
if i == 120 { if i == 120 {
ofoxSpinner.Stop() ofoxSpinner.Stop()
logger.Error("Recovery device timeout") logger.Warn("Recovery device timeout")
logger.Info("Trying to restart adb server")
err := adbc.KillServer();
if err != nil {
logger.Error("Failed to kill server")
os.Exit(173);
}
err = adbc.StartServer();
if err != nil {
logger.Error("Failed to start server")
os.Exit(173);
}
adbd = adbc.Device(adb.DeviceWithSerial(serail));
if s, _ := adbd.State(); s == adb.StateRecovery {
logger.Info("Device found")
break
}
logger.Error("Device not found")
os.Exit(173) os.Exit(173)
} }
if s, _ := adbd.State(); s == adb.StateRecovery { if s, _ := adbd.State(); s == adb.StateRecovery {
@ -288,17 +318,18 @@ var deployCmd = &cobra.Command{
break break
} }
time.Sleep(time.Second) time.Sleep(time.Second)
} }
port, err := utils.GetFreePort() port, err := utils.GetFreePort()
if err != nil { if err != nil {
logger.Error("Failled to find free tcp port") logger.Error("Failled to find free tcp port")
os.Exit(181) os.Exit(181)
} }
logger.Debug("Flasher", logger.Args("port", port)) logger.Debug("Flasher started", logger.Args("port", port))
forwards, _ := adbd.ListForwards()
adbd.Forward(pterm.Sprintf("tcp:%v", port), "tcp:4444") adbd.Forward(pterm.Sprintf("tcp:%v", port), "tcp:4444")
logger.Debug("ListForwards", logger.Args(adbd.ListForwards())) logger.Debug("Forward started", logger.Args("forwards", forwards))
doneChan1 := make(chan bool) doneChan1 := make(chan bool)
doneChan2 := make(chan bool) doneChan2 := make(chan bool)
go func() { go func() {
@ -311,7 +342,7 @@ var deployCmd = &cobra.Command{
go func() { go func() {
conn, err := net.Dial("tcp", pterm.Sprintf("127.0.0.1:%v", port)) conn, err := net.Dial("tcp", pterm.Sprintf("127.0.0.1:%v", port))
if err != nil { if err != nil {
logger.Error("Failled to connect to device") logger.Fatal("Failled to connect to device")
} }
buf := make([]byte, 409600) buf := make([]byte, 409600)
bar, _ := pbar.WithTotal(int(image.ImgSize)).WithTitle("Flashing rootfs").WithRemoveWhenDone(false).Start() bar, _ := pbar.WithTotal(int(image.ImgSize)).WithTitle("Flashing rootfs").WithRemoveWhenDone(false).Start()
@ -342,79 +373,84 @@ var deployCmd = &cobra.Command{
<-doneChan2 <-doneChan2
adbd.KillForwardAll() adbd.KillForwardAll()
out, err := adbd.RunCommand(pterm.Sprintf("postinstall %s %s > /dev/null 2>&1; echo $?", username, password)) pi_cmd := pterm.Sprintf("postinstall \"%s\" \"%s\" > /dev/null 2>&1; echo $?", username, strings.ReplaceAll(password, "\"", "\\\""))
out, err := adbd.RunCommand(pi_cmd)
out = strings.TrimRight(out, "\n") out = strings.TrimRight(out, "\n")
logger.Debug("Postinstall", logger.Args("out", out, "err", err)) logger.Debug("Postinstall", logger.Args("cmd", pi_cmd, "out", out, "err", err))
if out != "0" || err != nil { if out != "0" || err != nil {
logger.Error("Postinstall failed. Reflash stock rom and try again", logger.Args("Device error", out, "Go error", err)) logger.Error("Postinstall failed. Reflash stock rom and try again", logger.Args("Device error", out, "Go error", err))
} else { } else {
logger.Info("System cofigured") logger.Info("System cofigured")
} }
adbd.RunCommand("mkdir /tmp/uefi-install") var uefi_out string
bootshim, err := utils.Files.UEFIBootshim.Get(*pbar.WithTitle("Downloading uefi bootshim")) if nosimpleinit {
if err != nil { logger.Info("-Q flag present. Skipping simpleinit install")
if bootshim != nil { uefi_out = "0"
logger.Warn("Unable to verify uefi bootship image") } else {
} else { adbd.RunCommand("mkdir /tmp/uefi-install")
logger.Error("Unable to download uefi bootshim image")
os.Exit(179) bootshim, err := utils.Files.UEFIBootshim.Get(*pbar.WithTitle("Downloading uefi bootshim"))
if err != nil {
if bootshim != nil {
logger.Warn("Unable to verify uefi bootship image")
} else {
logger.Error("Unable to download uefi bootshim image")
os.Exit(179)
}
} }
}
conn, err := adbd.OpenWrite(pterm.Sprintf("/tmp/uefi-install/%s", utils.Files.UEFIBootshim.Name), fs.FileMode(0777), adb.MtimeOfClose) conn, err := adbd.OpenWrite(pterm.Sprintf("/tmp/uefi-install/%s", utils.Files.UEFIBootshim.Name), fs.FileMode(0777), adb.MtimeOfClose)
if err != nil { if err != nil {
logger.Error("Failed to send uefi bootshim", logger.Args("Error", err)) logger.Error("Failed to send uefi bootshim", logger.Args("Error", err))
}
_, err = conn.Write(bootshim)
if err != nil {
logger.Error("Failed to send uefi bootshim", logger.Args("Error", err))
}
conn.Close()
payload, err := utils.Files.UEFIPayload.Get(*pbar.WithTitle("Downloading uefi payload"))
if err != nil {
if payload != nil {
logger.Warn("Unable to verify uefi payload image")
} else {
logger.Error("Unable to download uefi payload image")
os.Exit(179)
} }
} _, err = conn.Write(bootshim)
conn, err = adbd.OpenWrite(pterm.Sprintf("/tmp/uefi-install/%s", utils.Files.UEFIPayload.Name), fs.FileMode(0777), adb.MtimeOfClose) if err != nil {
if err != nil { logger.Error("Failed to send uefi bootshim", logger.Args("Error", err))
logger.Error("Failed to send uefi payload", logger.Args("Error", err)) }
} conn.Close()
_, err = conn.Write(payload)
if err != nil {
logger.Error("Failed to send uefi payload", logger.Args("Error", err))
}
conn.Close()
uefiSpinner, _ := spinner.Start("Patching UEFI") payload, err := utils.Files.UEFIPayload.Get(*pbar.WithTitle("Downloading uefi payload"))
out, err = adbd.RunCommand("uefi-patch > /dev/null 2>&1; echo $?") if err != nil {
out = strings.TrimRight(out, "\n") if payload != nil {
if err != nil { logger.Warn("Unable to verify uefi payload image")
logger.Error("Failed to install uefi. Reflash stock rom and try again", logger.Args("Error", err)) } else {
os.Exit(176) logger.Error("Unable to download uefi payload image")
} os.Exit(179)
logger.Debug("Uefi patch", logger.Args("Out", out)) }
}
conn, err = adbd.OpenWrite(pterm.Sprintf("/tmp/uefi-install/%s", utils.Files.UEFIPayload.Name), fs.FileMode(0777), adb.MtimeOfClose)
if err != nil {
logger.Error("Failed to send uefi payload", logger.Args("Error", err))
}
_, err = conn.Write(payload)
if err != nil {
logger.Error("Failed to send uefi payload", logger.Args("Error", err))
}
conn.Close()
switch out { uefiSpinner, _ := spinner.Start("Patching UEFI")
case "1": uefi_out, err = adbd.RunCommand("uefi-patch > /dev/null 2>&1; echo $?")
if err != nil {
logger.Error("Failed to install uefi. Reflash stock rom and try again", logger.Args("Error", err))
os.Exit(176)
}
uefi_out = strings.TrimRight(uefi_out, "\n")
logger.Debug("Uefi patch", logger.Args("Out", out))
uefiSpinner.Stop() uefiSpinner.Stop()
}
switch uefi_out {
case "1":
logger.Error("Failed to install uefi. Reflash stock rom and try again", logger.Args("Error", err)) logger.Error("Failed to install uefi. Reflash stock rom and try again", logger.Args("Error", err))
adbd.RunCommand("reboot bootloader") adbd.RunCommand("reboot bootloader")
os.Exit(176) os.Exit(176)
case "2": case "2":
adbd.RunCommand("reboot") adbd.RunCommand("reboot")
uefiSpinner.Stop()
logger.Info("Bootimage already patched") logger.Info("Bootimage already patched")
case "0": case "0":
adbd.RunCommand("reboot") adbd.RunCommand("reboot")
uefiSpinner.Stop()
logger.Info("Installation done!") logger.Info("Installation done!")
} }
}, },
@ -426,4 +462,5 @@ func init() {
deployCmd.Flags().StringVarP(&password, "password", "p", "", "User password") deployCmd.Flags().StringVarP(&password, "password", "p", "", "User password")
deployCmd.Flags().StringVarP(&serail, "serial", "s", "autodetect", "Device serial") deployCmd.Flags().StringVarP(&serail, "serial", "s", "autodetect", "Device serial")
deployCmd.Flags().StringVarP(&partsize, "part-size", "S", "", "Linux partition size in percents") deployCmd.Flags().StringVarP(&partsize, "part-size", "S", "", "Linux partition size in percents")
deployCmd.Flags().BoolVarP(&nosimpleinit, "no-simple-init", "Q", false, "Disable simple init install")
} }

View file

@ -70,5 +70,5 @@ func Execute() {
} }
func init() { func init() {
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "V", false, "Enabled verbose output") rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "V", false, "enable debug output")
} }

28
installers/go-posix.sh Normal file
View file

@ -0,0 +1,28 @@
#!/usr/bin/env bash
command -v go >/dev/null 2>&1 || {
printf "\e[33mGo is required but not installed\e[0m\n" >&2
rnm=1
}
command -v git >/dev/null 2>&1 || {
printf "\e[33mGit is required but not installed\e[0m\n" >&2
rnm=1
}
[[ "$rnm" == "1" ]] && exit 1
[ -d "$HOME/.local/bin" ] && {
mkdir --parent "$HOME/.local/bin"
}
[ -d ~/.lon-tool-src ] && rm ~/.lon-tool-src -rf
git clone https://git.timoxa0.su/timoxa0/lon-tool.git ~/.lon-tool-src || {
rm ~/.lon-tool-src -rf
exit 2
}
pushd ~/.lon-tool-src &> /dev/null
rev=$(git describe --abbrev=4 --dirty --always --tags)
go get git.timoxa0.su/timoxa0/lon-tool/cmd
go build -ldflags "-X git.timoxa0.su/timoxa0/lon-tool/cmd.version=$rev" -o "$HOME/.local/bin/lon-tool" main.go && {
printf "\e[32mDone!\e[0m Installed at %s\n" "$HOME/.local/bin/lon-tool"
}
popd &> /dev/null

21
installers/linux.sh Normal file
View file

@ -0,0 +1,21 @@
#!/usr/bin/env bash
URL="https://git.timoxa0.su/timoxa0/lon-tool/releases/download/latest/lon-tool_lin_amd64"
[[ "$(uname -m)" != "x86_64*" ]] || {
printf "Unsupported CPU arch\n"
exit 1
}
[ -d "$HOME/.local/bin" ] || {
mkdir --parent "$HOME/.local/bin"
}
printf "Downloading lon-tool\n"
curl "$URL" -o "$HOME/.local/bin/lon-tool" -#
chmod +x "$HOME/.local/bin/lon-tool"
command -v adb >/dev/null 2>&1 || {
printf "\e[33mWARNING: adb binary not found.\e[0m\nPlease install google platform tools using your package manager\n" >&2
}
printf "\e[32mDone!\e[0m Installed at %s\n" "$HOME/.local/bin/lon-tool"

193
installers/windows.ps1 Normal file
View file

@ -0,0 +1,193 @@
$url="https://git.timoxa0.su/timoxa0/lon-tool/releases/download/latest/lon-tool_win_amd64.exe"
$bin_dir = Join-Path $env:USERPROFILE ".bin"
$platform_tools_url = "https://dl.google.com/android/repository/platform-tools-latest-windows.zip"
$platform_tools_dir = Join-Path $bin_dir "platform_tools"
function Get-FileFromWeb {
param (
# Parameter help description
[Parameter(Mandatory)]
[string]$URL,
# Parameter help description
[Parameter(Mandatory)]
[string]$File
)
Begin {
function Show-Progress {
param (
# Enter total value
[Parameter(Mandatory)]
[Single]$TotalValue,
# Enter current value
[Parameter(Mandatory)]
[Single]$CurrentValue,
# Enter custom progresstext
[Parameter(Mandatory)]
[string]$ProgressText,
# Enter value suffix
[Parameter()]
[string]$ValueSuffix,
# Enter bar lengh suffix
[Parameter()]
[int]$BarSize = 40,
# show complete bar
[Parameter()]
[switch]$Complete
)
# calc %
$percent = $CurrentValue / $TotalValue
$percentComplete = $percent * 100
if ($ValueSuffix) {
$ValueSuffix = " $ValueSuffix" # add space in front
}
if ($psISE) {
Write-Progress "$ProgressText $CurrentValue$ValueSuffix of $TotalValue$ValueSuffix" -id 0 -percentComplete $percentComplete
}
else {
# build progressbar with string function
$curBarSize = $BarSize * $percent
$progbar = ""
$progbar = $progbar.PadRight($curBarSize,[char]9608)
$progbar = $progbar.PadRight($BarSize,[char]9617)
if (!$Complete.IsPresent) {
Write-Host -NoNewLine "`r$ProgressText $progbar [ $($CurrentValue.ToString("#.###").PadLeft($TotalValue.ToString("#.###").Length))$ValueSuffix / $($TotalValue.ToString("#.###"))$ValueSuffix ] $($percentComplete.ToString("##0.00").PadLeft(6)) % complete"
}
else {
Write-Host -NoNewLine "`r$ProgressText $progbar [ $($TotalValue.ToString("#.###").PadLeft($TotalValue.ToString("#.###").Length))$ValueSuffix / $($TotalValue.ToString("#.###"))$ValueSuffix ] $($percentComplete.ToString("##0.00").PadLeft(6)) % complete"
}
}
}
}
Process {
try {
$storeEAP = $ErrorActionPreference
$ErrorActionPreference = 'Stop'
# invoke request
$request = [System.Net.HttpWebRequest]::Create($URL)
$response = $request.GetResponse()
if ($response.StatusCode -eq 401 -or $response.StatusCode -eq 403 -or $response.StatusCode -eq 404) {
throw "Remote file either doesn't exist, is unauthorized, or is forbidden for '$URL'."
}
if($File -match '^\.\\') {
$File = Join-Path (Get-Location -PSProvider "FileSystem") ($File -Split '^\.')[1]
}
if($File -and !(Split-Path $File)) {
$File = Join-Path (Get-Location -PSProvider "FileSystem") $File
}
if ($File) {
$fileDirectory = $([System.IO.Path]::GetDirectoryName($File))
if (!(Test-Path($fileDirectory))) {
[System.IO.Directory]::CreateDirectory($fileDirectory) | Out-Null
}
}
[long]$fullSize = $response.ContentLength
$fullSizeMB = $fullSize / 1024 / 1024
# define buffer
[byte[]]$buffer = new-object byte[] 1048576
[long]$total = [long]$count = 0
# create reader / writer
$reader = $response.GetResponseStream()
$writer = new-object System.IO.FileStream $File, "Create"
# start download
$finalBarCount = 0 #show final bar only one time
do {
$count = $reader.Read($buffer, 0, $buffer.Length)
$writer.Write($buffer, 0, $count)
$total += $count
$totalMB = $total / 1024 / 1024
if ($fullSize -gt 0) {
Show-Progress -TotalValue $fullSizeMB -CurrentValue $totalMB -ProgressText "Downloading $($File.Name)" -ValueSuffix "MB"
}
if ($total -eq $fullSize -and $count -eq 0 -and $finalBarCount -eq 0) {
Show-Progress -TotalValue $fullSizeMB -CurrentValue $totalMB -ProgressText "Downloading $($File.Name)" -ValueSuffix "MB" -Complete
$finalBarCount++
#Write-Host "$finalBarCount"
}
} while ($count -gt 0)
}
catch {
$ExeptionMsg = $_.Exception.Message
Write-Host "Download breaks with error : $ExeptionMsg"
}
finally {
# cleanup
if ($reader) { $reader.Close() }
if ($writer) { $writer.Flush(); $writer.Close() }
$ErrorActionPreference = $storeEAP
[GC]::Collect()
}
}
}
function Install-Tool {
if (-not (Test-Path $bin_dir -PathType Container)) {
New-Item -Path $bin_dir -ItemType Directory | Out-Null
}
$currentPath = [Environment]::GetEnvironmentVariable("PATH", "User") -split ";"
if ($currentPath -notcontains $bin_dir) {
[Environment]::SetEnvironmentVariable("PATH", "$env:PATH;$bin_dir", "User")
$env:PATH="$env:PATH;$bin_dir"
Write-Host "$bin_dir added to PATH."
}
Get-FileFromWeb "$url" (Join-Path $bin_dir "lon-tool.exe")
Write-Host
}
function Install-Platoform_tools {
if (-not (Test-Path $platform_tools_dir -PathType Container)) {
New-Item -Path $platform_tools_dir -ItemType Directory | Out-Null
}
$currentPath = [Environment]::GetEnvironmentVariable("PATH", "User") -split ";"
if ($currentPath -notcontains $platform_tools_dir) {
[Environment]::SetEnvironmentVariable("PATH", "$env:PATH;$platform_tools_dir", "User")
$env:PATH="$env:PATH;$platform_tools_dir"
Write-Host "$platform_tools_dir added to PATH."
}
Get-FileFromWeb "$platform_tools_url" (Join-Path $platform_tools_dir "tools.zip")
Write-Host
Expand-Archive -Path (Join-Path $platform_tools_dir "tools.zip") -DestinationPath $platform_tools_dir
Move-Item (Join-Path $platform_tools_dir "platform-tools\*") $platform_tools_dir
Remove-Item (Join-Path $platform_tools_dir "platform-tools\*")
}
Install-Tool
if (-not (Get-Command "adb.exe" -ErrorAction SilentlyContinue)) {
$decision = $Host.UI.PromptForChoice("Adb executable not found in PATH", "Do you wand to install android platform tools?", ("&Yes", "&No"), 1)
if ($decision -eq 0) {
Install-Platoform_tools
}
}
Write-Host "Done!" -ForegroundColor Green

View file

@ -4,14 +4,17 @@ import (
"fmt" "fmt"
"math" "math"
"net" "net"
"regexp"
) )
func GenRepartCommands(percent int, is128 bool) []string { func GenRepartCommands(percent int, blocksize string) []string {
var maxsize uint8 var maxsize uint16
if is128 { if r, _ := regexp.MatchString(`^125[0-9]{9}$`, blocksize); r {
maxsize = 126 maxsize = 126
} else { } else if r, _ := regexp.MatchString(`^253[0-9]{9}$`, blocksize); r {
maxsize = 254 maxsize = 254
} else if r, _ := regexp.MatchString(`^509[0-9]{9}$`, blocksize); r {
maxsize = 509
} }
linux_max := maxsize - 12 linux_max := maxsize - 12
size := math.Round(float64(linux_max)*float64(percent)) / 100 size := math.Round(float64(linux_max)*float64(percent)) / 100
@ -26,7 +29,6 @@ func GenRepartCommands(percent int, is128 bool) []string {
} }
} }
func GetFreePort() (int, error) { func GetFreePort() (int, error) {
listener, err := net.Listen("tcp", ":0") listener, err := net.Listen("tcp", ":0")
if err != nil { if err != nil {