lon-tool/cmd/deploy.go

432 lines
13 KiB
Go

package cmd
import (
"io"
"io/fs"
"net"
"os"
"regexp"
"strconv"
"strings"
"time"
"git.timoxa0.su/timoxa0/lon-tool/image"
"git.timoxa0.su/timoxa0/lon-tool/utils"
"github.com/pterm/pterm"
"github.com/spf13/cobra"
"github.com/timoxa0/goadb/adb"
"github.com/timoxa0/gofastboot/fastboot"
)
var username string
var password string
var serail string
var partsize string
var nosimpleinit bool
var partpercent int
var deployCmd = &cobra.Command{
Use: "deploy <rootfs.lni>",
Short: "Deploy system to device",
Long: "Deploy system to device in fastboot mode",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
var msg string
req_repartition := partsize != ""
image, close, err := image.ReadImage(args[0])
defer close()
if err != nil {
logger.Fatal("Failed to read image info:", logger.Args("Error", err))
}
adbc, err := adb.New()
if err != nil {
logger.Fatal("Failed to get adb client", logger.Args("Error", err))
}
fb_devs, err := fastboot.FindDevices()
logger.Debug("Devices", logger.Args("Devices", fb_devs, "err", err))
if err != nil {
logger.Fatal("Failed to get fastboot device", logger.Args("Error", err))
}
if len(fb_devs) < 1 {
logger.Fatal("No fastboot devices found")
os.Exit(170)
}
if serail == "autodetect" {
for _, dev := range fb_devs {
product, err := dev.GetVar("product")
devSerial, _ := dev.Device.SerialNumber()
if err != nil {
logger.Warn("Unable to communicate with device", logger.Args("Serial", devSerial))
logger.Debug("Unable to communicate with device", logger.Args("err", err))
continue
}
logger.Debug("Found device", logger.Args("Product", product, "Sraial", devSerial, "object", dev))
if product == "nabu" {
logger.Debug("Nabu found", logger.Args("Serial", devSerial))
serail = devSerial
if !req_repartition {
_, err1 := dev.GetVar("partition-type:linux")
_, err2 := dev.GetVar("partition-type:esp")
req_repartition = err1 != nil && err2 != nil
}
break
}
}
for _, dev := range fb_devs {
dev.Close()
}
if serail == "autodetect" {
logger.Fatal("Nabu in fastboot mode not found")
os.Exit(170)
}
}
for {
if username != "" {
break
}
username, _ = pterm.DefaultInteractiveTextInput.Show("Linux username")
if r, _ := regexp.MatchString(`[A-z0-9]+`, username); !r {
username = ""
logger.Error("Username must contain only latin letter or digits")
}
}
for {
if password != "" {
break
}
password, _ = pterm.DefaultInteractiveTextInput.WithMask("*").Show("Linux password")
if r, _ := regexp.MatchString(`[A-z0-9$%#@!]+`, password); !r {
password = ""
logger.Error("Password must contain only latin letter, digits or $%#@!")
}
}
var run_repartition bool
if req_repartition {
run_repartition, _ = pterm.DefaultInteractiveConfirm.WithDefaultValue(true).Show("Found incompatible partition table. Do you want to change it?")
} else {
run_repartition, _ = pterm.DefaultInteractiveConfirm.Show("Found compatible partition table. Do you want to change it?")
}
if req_repartition && !run_repartition {
pterm.Println("Bye")
os.Exit(0)
}
for {
if partsize != "" || !run_repartition {
break
}
partsize, _ = pterm.DefaultInteractiveTextInput.WithDefaultValue("50%").Show("Partition size [20;90]%")
v, err := strconv.Atoi(strings.TrimRight(partsize, "%"))
partpercent = v
if !(err == nil && 20 <= v && v <= 90 && partsize[len(partsize)-1] == '%') {
partsize = ""
logger.Error("Invalid partsize. Parsize must be in percents")
}
}
msg, _ = pterm.DefaultTree.WithRoot(pterm.TreeNode{
Text: pterm.Green("System"),
Children: []pterm.TreeNode{
{Text: pterm.Sprintf("Name: %s", image.Name)},
{Text: pterm.Sprintf("Version: %s", image.Version)},
},
}).Srender()
pterm.Print(msg)
msg, _ = pterm.DefaultTree.WithRoot(pterm.TreeNode{
Text: pterm.Green("Settings"),
Children: []pterm.TreeNode{
{Text: pterm.Sprintf("Username: %s", username)},
{Text: pterm.Sprintf("Password: %s", password)},
{Text: pterm.Sprintf("Device serial: %s", serail)},
{Text: pterm.Sprintf("Run repatition: %v", map[bool]string{
true: pterm.Green("Yes"),
false: pterm.Red("No"),
}[run_repartition])},
{Text: pterm.Sprintf("Partition size: %s", map[bool]string{
true: "Not changed",
false: partsize,
}[partsize == ""])},
},
}).Srender()
pterm.Print(msg)
var isok bool
if run_repartition {
isok, _ = pterm.DefaultInteractiveConfirm.Show("Start installation? All user data will be ERASED")
} else {
isok, _ = pterm.DefaultInteractiveConfirm.Show("Start installation?")
}
if !isok {
pterm.Println("Bye")
os.Exit(253)
}
fb_dev, err := fastboot.FindDevice(serail)
if err != nil {
logger.Error("Unable to find device", logger.Args("object", fb_dev, "err", err))
fb_devs, err := fastboot.FindDevices()
logger.Debug("Devices", logger.Args("Devices", fb_devs, "err", err))
os.Exit(255)
}
adbd := adbc.Device(adb.DeviceWithSerial(serail))
bootdata, err := utils.Files.OrangeFox.Get(*pbar.WithTitle("Downloading orangefox"))
if err != nil {
if bootdata != nil {
logger.Warn("Unable to verify recovery image")
} else {
logger.Error("Unable to download recovery image")
os.Exit(179)
}
}
if run_repartition {
gpt, err := utils.Files.GPT.Get(*pbar.WithTitle("Downloading default partition table"))
if err != nil {
if gpt != nil {
logger.Warn("Unable to verify gpt image")
} else {
logger.Error("Unable to download gpt image")
os.Exit(179)
}
}
userdata, err := utils.Files.CleanUserdata.Get(*pbar.WithTitle("Downloading empty userdata"))
if err != nil {
if userdata != nil {
logger.Warn("Unable to verify userdata image")
} else {
logger.Error("Unable to download userdata image")
os.Exit(179)
}
}
if e := fb_dev.Flash("partition:0", gpt); e != nil {
logger.Error("Failed to flash GPT. Reflash stock rom and try again")
logger.Debug("Error", logger.Args("e", e))
os.Exit(180)
}
if e := fb_dev.Flash("userdata", userdata); e != nil {
logger.Error("Failed to clean userdata. Reflash stock rom and try again")
logger.Debug("Error", logger.Args("e", e))
os.Exit(180)
}
ofoxSpinner, _ := spinner.Start("Booting recovery")
if e := fb_dev.BootImage(bootdata); e != nil {
logger.Error("Failed to boot recovery. Reflash stock rom and try again")
logger.Debug("Error", logger.Args("e", e))
os.Exit(177)
}
for i := 0; i <= 120; i++ {
if i == 120 {
ofoxSpinner.Stop()
logger.Error("Recovery device timeout")
os.Exit(173)
}
if s, _ := adbd.State(); s == adb.StateRecovery {
ofoxSpinner.Stop()
break
}
time.Sleep(time.Second)
}
block_size, _ := adbd.RunCommand("blockdev --getsize64 /dev/block/sda")
block_size = strings.TrimRight(block_size, "\n")
for _, cmd := range utils.GenRepartCommands(partpercent, block_size) {
adbd.RunCommand(cmd)
logger.Debug("Executed command", logger.Args("cmd", cmd))
}
adbd.RunCommand("reboot bootloader")
fbSpinner, _ := spinner.Start("Rebooting to bootloader")
for i := 0; i <= 120; i++ {
if i == 120 {
fbSpinner.Stop()
logger.Error("Fastboot device timeout")
os.Exit(172)
}
if d, e := fastboot.FindDevice(serail); e == nil {
fbSpinner.Stop()
fb_dev = d
break
}
time.Sleep(time.Second)
}
if e := fb_dev.Flash("userdata", userdata); e != nil {
logger.Error("Failed to clean userdata. Reflash stock rom and try again")
logger.Debug("Error", logger.Args("e", e))
os.Exit(180)
}
logger.Info("Repartiton done")
}
ofoxSpinner, _ := spinner.Start("Booting recovery")
fb_dev.BootImage(bootdata)
for i := 0; i <= 120; i++ {
if i == 120 {
ofoxSpinner.Stop()
logger.Error("Recovery device timeout")
os.Exit(173)
}
if s, _ := adbd.State(); s == adb.StateRecovery {
ofoxSpinner.Stop()
break
}
time.Sleep(time.Second)
}
port, err := utils.GetFreePort()
if err != nil {
logger.Error("Failled to find free tcp port")
os.Exit(181)
}
logger.Debug("Flasher started", logger.Args("port", port))
forwards, _ := adbd.ListForwards()
adbd.Forward(pterm.Sprintf("tcp:%v", port), "tcp:4444")
logger.Debug("Forward started", logger.Args("forwards", forwards))
doneChan1 := make(chan bool)
doneChan2 := make(chan bool)
go func() {
adbd.RunCommand("busybox nc -w 10 -l 127.0.0.1:4444 > /dev/block/platform/soc/1d84000.ufshc/by-name/linux 2> /tmp/nclog.txt")
logger.Debug("Busybox closed")
doneChan1 <- true
}()
time.Sleep(time.Second * 3)
go func() {
conn, err := net.Dial("tcp", pterm.Sprintf("127.0.0.1:%v", port))
if err != nil {
logger.Fatal("Failled to connect to device")
}
buf := make([]byte, 409600)
bar, _ := pbar.WithTotal(int(image.ImgSize)).WithTitle("Flashing rootfs").WithRemoveWhenDone(false).Start()
dataLegth := 0
for {
n, err := image.Reader.Read(buf)
dataLegth += n
if err != nil && err != io.EOF && dataLegth < int(image.ImgSize) {
logger.Error("Failed to read from GZ reader", logger.Args("error", err))
return
}
if n == 0 {
break
}
bar.Add(n)
n, err = conn.Write(buf[:n])
if n == 0 {
logger.Error("Failed to write to device. Reflash stock rom and try again", logger.Args("err", err))
adbd.RunCommand("reboot bootloader")
os.Exit(179)
break
}
}
conn.Close()
<-doneChan1
doneChan2 <- true
}()
<-doneChan2
adbd.KillForwardAll()
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")
logger.Debug("Postinstall", logger.Args("cmd", pi_cmd, "out", out, "err", err))
if out != "0" || err != nil {
logger.Error("Postinstall failed. Reflash stock rom and try again", logger.Args("Device error", out, "Go error", err))
} else {
logger.Info("System cofigured")
}
var uefi_out string
if nosimpleinit {
logger.Info("-Q flag present. Skipping simpleinit install")
uefi_out = "0"
} else {
adbd.RunCommand("mkdir /tmp/uefi-install")
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)
if err != nil {
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)
}
}
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()
uefiSpinner, _ := spinner.Start("Patching UEFI")
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()
}
switch uefi_out {
case "1":
logger.Error("Failed to install uefi. Reflash stock rom and try again", logger.Args("Error", err))
adbd.RunCommand("reboot bootloader")
os.Exit(176)
case "2":
adbd.RunCommand("reboot")
logger.Info("Bootimage already patched")
case "0":
adbd.RunCommand("reboot")
logger.Info("Installation done!")
}
},
}
func init() {
rootCmd.AddCommand(deployCmd)
deployCmd.Flags().StringVarP(&username, "username", "u", "", "User name")
deployCmd.Flags().StringVarP(&password, "password", "p", "", "User password")
deployCmd.Flags().StringVarP(&serail, "serial", "s", "autodetect", "Device serial")
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")
}