test release
This commit is contained in:
commit
a15ff9ba5f
100
cmd/extractor/main.go
Normal file
100
cmd/extractor/main.go
Normal file
@ -0,0 +1,100 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"kisekinopureya.com.tr/updater/internal/archive"
|
||||
)
|
||||
|
||||
func sha256sum(path string) (string, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
h := sha256.New()
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 3 {
|
||||
fmt.Printf("Usage: %s <archive.tar.xz> <archive.tar.xz.sig>\n", os.Args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
archiveFile := os.Args[1]
|
||||
sig := os.Args[2]
|
||||
|
||||
// Step 1: verify GPG signature
|
||||
cmd := exec.Command("/usr/bin/gpgv", "--keyring", "/etc/updates-public.gpg", sig, archiveFile)
|
||||
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "GPG verification failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("GPG signature verified.")
|
||||
|
||||
// Step 2: open archiveFile
|
||||
f, err := os.Open(archiveFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Step 2: extract tar.xz using system tar
|
||||
cmdTar := exec.Command("tar", "-xJf", archiveFile)
|
||||
cmdTar.Stdout = os.Stdout
|
||||
cmdTar.Stderr = os.Stderr
|
||||
if err := cmdTar.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
metaFile := "metadata.json"
|
||||
f, err = os.Open(metaFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var meta archive.Output
|
||||
if err := json.NewDecoder(f).Decode(&meta); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Step 4: check sha256
|
||||
ok := true
|
||||
for _, file := range meta.Files {
|
||||
sum, err := sha256sum(file.Name)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to hash %s: %v\n", file.Name, err)
|
||||
ok = false
|
||||
continue
|
||||
}
|
||||
if sum != file.Sha256 {
|
||||
fmt.Fprintf(os.Stderr, "Checksum mismatch for %s!\nExpected: %s\nGot: %s\n",
|
||||
file.Name, file.Sha256, sum)
|
||||
ok = false
|
||||
} else {
|
||||
fmt.Printf("%s checksum OK\n", file.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if !ok {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("All files verified successfully.")
|
||||
}
|
||||
40
cmd/os-branch-select/main.go
Normal file
40
cmd/os-branch-select/main.go
Normal file
@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"kisekinopureya.com.tr/updater/internal/logger"
|
||||
"kisekinopureya.com.tr/updater/internal/version"
|
||||
)
|
||||
|
||||
func main() {
|
||||
logger.InitializeLogger("/tmp/os-select-branchlog")
|
||||
|
||||
log.Printf("%+v", os.Args)
|
||||
|
||||
if len(os.Args) != 2 {
|
||||
fmt.Println("Usage: steamos-select-branch <-stable|unstable|testing>")
|
||||
os.Exit(1)
|
||||
}
|
||||
branchPath := version.BranchPath
|
||||
|
||||
switch os.Args[1] {
|
||||
case "-c":
|
||||
branch := version.GetCurrentBranch()
|
||||
fmt.Printf("%s\n", branch)
|
||||
os.Exit(0)
|
||||
case "-l":
|
||||
fmt.Printf("%s\n%s\n%s\n", "stable", "unstable", "testing")
|
||||
os.Exit(0)
|
||||
case "stable", "unstable", "testing":
|
||||
err := os.WriteFile(branchPath, []byte(os.Args[1]), 0644)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
os.Exit(0)
|
||||
default:
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
25
cmd/packer/main.go
Normal file
25
cmd/packer/main.go
Normal file
@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"kisekinopureya.com.tr/updater/internal/archive"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 5 {
|
||||
fmt.Fprintf(os.Stderr, "usage: %s <rootfs.img> <dm-verity.img> <kernel.img> <output.tar.xz>\n", os.Args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
rootfs := os.Args[1]
|
||||
verity := os.Args[2]
|
||||
kernel := os.Args[3]
|
||||
output := os.Args[4]
|
||||
|
||||
if err := archive.Pack(rootfs, verity, kernel, output); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "packer error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
165
cmd/patcher/main.go
Normal file
165
cmd/patcher/main.go
Normal file
@ -0,0 +1,165 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"kisekinopureya.com.tr/updater/internal/archive"
|
||||
"kisekinopureya.com.tr/updater/internal/logger"
|
||||
mountfilesystem "kisekinopureya.com.tr/updater/internal/mountFilesystem"
|
||||
"kisekinopureya.com.tr/updater/internal/plymouth"
|
||||
)
|
||||
|
||||
type BlockDevice struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
PartType string `json:"parttype"`
|
||||
Children []BlockDevice `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
type LSBLK struct {
|
||||
BlockDevices []BlockDevice `json:"blockdevices"`
|
||||
}
|
||||
|
||||
type partitionScheme struct {
|
||||
EfiDevice string
|
||||
RootfsDevice string
|
||||
DmVerityDevice string
|
||||
//VarDevice string
|
||||
}
|
||||
|
||||
var efiUUID string = "c12a7328-f81f-11d2-ba4b-00a0c93ec93b"
|
||||
var rootfsUUID string = "4f68bce3-e8cd-4db1-96e7-fbcaf984b709"
|
||||
var dmverityUUID string = "2c7357ed-ebd2-46d9-aec1-23d437ec2bf5"
|
||||
|
||||
//var varUUID string = "4d21b016-b534-45c2-a9fb-5c16e091fd2d"
|
||||
|
||||
func findByPartType(devices []BlockDevice, target string) string {
|
||||
for _, dev := range devices {
|
||||
if dev.PartType != "" && dev.PartType == target {
|
||||
return dev.Name
|
||||
}
|
||||
if len(dev.Children) > 0 {
|
||||
if found := findByPartType(dev.Children, target); found != "" {
|
||||
return found
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func populatePartitionScheme() partitionScheme {
|
||||
|
||||
var generatedScheme partitionScheme = partitionScheme{}
|
||||
cmd := exec.Command("lsblk", "-J", "-o", "NAME,TYPE,PARTTYPE")
|
||||
blockDeviceTypes, err := cmd.Output()
|
||||
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
var lsblk LSBLK
|
||||
if err := json.Unmarshal(blockDeviceTypes, &lsblk); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
generatedScheme.EfiDevice = findByPartType(lsblk.BlockDevices, efiUUID)
|
||||
generatedScheme.RootfsDevice = findByPartType(lsblk.BlockDevices, rootfsUUID)
|
||||
generatedScheme.DmVerityDevice = findByPartType(lsblk.BlockDevices, dmverityUUID)
|
||||
|
||||
return generatedScheme
|
||||
}
|
||||
|
||||
func patchImage(imageFile string, partition string, name string) {
|
||||
cmd := exec.Command("dd", "if="+imageFile, "of="+"/dev/"+partition, "bs=4M")
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
plymouth.ShowPlymouthMessage("Patching " + name + " partition")
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func CopyFile(source, destDir string) (string, error) {
|
||||
if err := os.MkdirAll(destDir, 0755); err != nil {
|
||||
return "", fmt.Errorf("mkdir failed: %w", err)
|
||||
}
|
||||
|
||||
in, err := os.Open(source)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("open src failed: %w", err)
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
dstPath := filepath.Join(destDir, filepath.Base(source))
|
||||
|
||||
out, err := os.Create(dstPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("create dst failed: %w", err)
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
if _, err := io.Copy(out, in); err != nil {
|
||||
return "", fmt.Errorf("copy failed: %w", err)
|
||||
}
|
||||
|
||||
if err := out.Sync(); err != nil {
|
||||
return "", fmt.Errorf("sync failed: %w", err)
|
||||
}
|
||||
|
||||
return dstPath, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
logger.InitializeLogger("/mnt/var/log/patcher")
|
||||
|
||||
if len(os.Args) != 2 {
|
||||
fmt.Printf("Usage: %s <metadata>\n", os.Args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
f, err := os.Open(os.Args[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var meta archive.Output
|
||||
if err := json.NewDecoder(f).Decode(&meta); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
currentPartitionScheme := populatePartitionScheme()
|
||||
|
||||
fileCount := len(meta.Files)
|
||||
|
||||
// before 20 and after 80 will be filled by initramfs
|
||||
leftProgress := 60/fileCount + 20
|
||||
|
||||
for _, f := range meta.Files {
|
||||
plymouth.ShowPlymouthProgress(leftProgress)
|
||||
leftProgress += leftProgress
|
||||
|
||||
imageFile := f.Name
|
||||
imageType := f.Type
|
||||
|
||||
switch imageType {
|
||||
case "rootfs":
|
||||
patchImage(imageFile, currentPartitionScheme.RootfsDevice, imageType)
|
||||
case "dm-verity":
|
||||
patchImage(imageFile, currentPartitionScheme.DmVerityDevice, imageType)
|
||||
case "kernel":
|
||||
mountPath := "/updates-tmp/efi"
|
||||
kernelDir := "/EFI/Linux"
|
||||
mountfilesystem.MountFileSystem("/dev/"+currentPartitionScheme.EfiDevice, mountPath, "vfat", 0, "")
|
||||
CopyFile(imageFile, mountPath+kernelDir)
|
||||
mountfilesystem.UnmountFileSystem(mountPath)
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
125
cmd/steamos-update/main.go
Normal file
125
cmd/steamos-update/main.go
Normal file
@ -0,0 +1,125 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"kisekinopureya.com.tr/updater/internal/downloader"
|
||||
"kisekinopureya.com.tr/updater/internal/logger"
|
||||
"kisekinopureya.com.tr/updater/internal/version"
|
||||
)
|
||||
|
||||
/*
|
||||
The Steam client is known to call this program with the following parameter combinations:
|
||||
steamos-update --supports-duplicate-detection -- should do nothing
|
||||
steamos-update --enable-duplicate-detection check -- should check for update
|
||||
steamos-update check -- should check for update
|
||||
steamos-update --enable-duplicate-detection -- should perform an update
|
||||
steamos-update -- should perform an update
|
||||
*/
|
||||
|
||||
func main() {
|
||||
logger.InitializeLogger("/tmp/steamos-updatelog")
|
||||
|
||||
log.Printf("%+v", os.Args)
|
||||
args := os.Args[1:]
|
||||
|
||||
action := performUpdate
|
||||
|
||||
switch {
|
||||
case len(args) == 1 && args[0] == "--supports-duplicate-detection":
|
||||
os.Exit(7)
|
||||
|
||||
case len(args) >= 1 && args[0] == "--enable-duplicate-detection":
|
||||
if len(args) >= 2 && args[1] == "check" {
|
||||
action = checkForUpdate
|
||||
}
|
||||
|
||||
case len(args) >= 1 && args[0] == "check":
|
||||
action = checkForUpdate
|
||||
}
|
||||
|
||||
action()
|
||||
|
||||
os.Exit(7)
|
||||
}
|
||||
|
||||
func fetchIndex(url string) ([]string, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch index: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("bad status: %s", resp.Status)
|
||||
}
|
||||
|
||||
var lines []string
|
||||
scanner := bufio.NewScanner(resp.Body)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line != "" {
|
||||
lines = append(lines, line)
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, fmt.Errorf("reading response failed: %w", err)
|
||||
}
|
||||
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
func checkForUpdate() {
|
||||
branch := version.GetCurrentBranch()
|
||||
currentVersion := version.DetermineCurrentVersion()
|
||||
currentVersionInt, err := strconv.ParseInt(currentVersion, 10, 32)
|
||||
if err != nil {
|
||||
currentVersionInt = 0 //
|
||||
}
|
||||
|
||||
indexFile, err := fetchIndex("http://10.26.1.3/updates/index")
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
for _, s := range indexFile {
|
||||
if strings.HasPrefix(s, string(branch)) && !strings.HasSuffix(s, ".sig") {
|
||||
parts := strings.Split(s, "/")
|
||||
base := parts[len(parts)-1]
|
||||
|
||||
// Remove extension
|
||||
base = strings.TrimSuffix(base, ".tar.xz")
|
||||
|
||||
// Split by dash
|
||||
tokens := strings.Split(base, "-")
|
||||
versionFromFile := tokens[len(tokens)-1]
|
||||
versionFromFileInt, err := strconv.ParseInt(versionFromFile, 10, 32)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
if currentVersionInt < versionFromFileInt {
|
||||
fmt.Printf("Update available: %s\n", base)
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func performUpdate() {
|
||||
branch := version.GetCurrentBranch()
|
||||
err := downloader.DownloadFile("http://10.26.1.3/updates/"+branch+"/latest.tar.xz.sig", "/var/cache/updates/update.tar.xz.sig", true)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
err = downloader.DownloadFile("http://10.26.1.3/updates/"+branch+"/latest.tar.xz", "/var/cache/updates/update.tar.xz", false)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
||||
module kisekinopureya.com.tr/updater
|
||||
|
||||
go 1.24.6
|
||||
|
||||
require golang.org/x/sys v0.36.0
|
||||
2
go.sum
Normal file
2
go.sum
Normal file
@ -0,0 +1,2 @@
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
207
internal/archive/packer.go
Normal file
207
internal/archive/packer.go
Normal file
@ -0,0 +1,207 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"kisekinopureya.com.tr/updater/internal/version"
|
||||
)
|
||||
|
||||
// Output metadata structure
|
||||
type Output struct {
|
||||
Metadata struct {
|
||||
Date int64 `json:"date"`
|
||||
Version string `json:"version"`
|
||||
} `json:"metadata"`
|
||||
Files []struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Sha256 string `json:"sha256"`
|
||||
} `json:"files"`
|
||||
}
|
||||
|
||||
type fileInfo struct {
|
||||
Name string
|
||||
Type string
|
||||
Path string
|
||||
Sha256 string
|
||||
}
|
||||
|
||||
// Pack creates a tar.xz archive with metadata.json and the given images
|
||||
func Pack(rootfsPath, verityPath, kernelPath, outPath string) error {
|
||||
paths := []struct {
|
||||
Path string
|
||||
Type string
|
||||
}{
|
||||
{rootfsPath, "rootfs"},
|
||||
{verityPath, "dm-verity"},
|
||||
{kernelPath, "kernel"},
|
||||
}
|
||||
|
||||
var infos []fileInfo
|
||||
for _, p := range paths {
|
||||
log.Printf("Hashing %s...", p.Path)
|
||||
sum, err := sha256File(p.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("hashing %s: %w", p.Path, err)
|
||||
}
|
||||
infos = append(infos, fileInfo{
|
||||
Name: filepath.Base(p.Path),
|
||||
Type: p.Type,
|
||||
Path: p.Path,
|
||||
Sha256: sum,
|
||||
})
|
||||
}
|
||||
|
||||
output, err := exec.Command("blkid", rootfsPath).Output()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
versionFromImage, err := version.Parse(string(output))
|
||||
if err != nil {
|
||||
log.Fatal()
|
||||
}
|
||||
|
||||
var out Output
|
||||
out.Metadata.Date = time.Now().Unix()
|
||||
// NOTE: version should be filled by caller if needed
|
||||
out.Metadata.Version = versionFromImage
|
||||
for _, fi := range infos {
|
||||
out.Files = append(out.Files, struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Sha256 string `json:"sha256"`
|
||||
}{
|
||||
Name: fi.Name,
|
||||
Type: fi.Type,
|
||||
Sha256: fi.Sha256,
|
||||
})
|
||||
}
|
||||
|
||||
metaBytes, err := json.MarshalIndent(out, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal metadata: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("Metadata: %+v\n", out)
|
||||
|
||||
if err := writeTarXz(outPath, metaBytes, infos); err != nil {
|
||||
return fmt.Errorf("create tar.xz: %w", err)
|
||||
}
|
||||
|
||||
sigFile := outPath + ".sig"
|
||||
cmd := exec.Command("gpg", "--output", sigFile, "--detach-sign", "--armor", outPath)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(fmt.Errorf("gpg signing failed: %w", err))
|
||||
}
|
||||
|
||||
fmt.Printf("Created %s and signed as %s\n", outPath, sigFile)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func sha256File(path string) (string, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
h := sha256.New()
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
func writeTarXz(outPath string, metadata []byte, files []fileInfo) error {
|
||||
outFile, err := os.Create(outPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create output: %w", err)
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
cmd := exec.Command("xz", "-z", "-c")
|
||||
cmd.Stdout = outFile
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tw := tar.NewWriter(stdin)
|
||||
|
||||
// metadata.json
|
||||
metaHeader := &tar.Header{
|
||||
Name: "metadata.json",
|
||||
Mode: 0644,
|
||||
Size: int64(len(metadata)),
|
||||
}
|
||||
if err := tw.WriteHeader(metaHeader); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tw.Write(metadata); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// files
|
||||
for _, fi := range files {
|
||||
if err := addFileToTar(tw, fi.Path, fi.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := tw.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := stdin.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cmd.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addFileToTar(tw *tar.Writer, srcPath, destName string) error {
|
||||
f, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hdr := &tar.Header{
|
||||
Name: destName,
|
||||
Mode: int64(stat.Mode().Perm()),
|
||||
Size: stat.Size(),
|
||||
ModTime: stat.ModTime(),
|
||||
}
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(tw, f)
|
||||
return err
|
||||
}
|
||||
81
internal/downloader/downloader.go
Normal file
81
internal/downloader/downloader.go
Normal file
@ -0,0 +1,81 @@
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func DownloadFile(url, filepath string, silent bool) error {
|
||||
var start int64
|
||||
if info, err := os.Stat(filepath); err == nil {
|
||||
start = info.Size()
|
||||
}
|
||||
|
||||
out, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
if start > 0 {
|
||||
if _, err := out.Seek(start, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP request with Range header for resume
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if start > 0 {
|
||||
req.Header.Set("Range", "bytes="+strconv.FormatInt(start, 10)+"-")
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Get total size for progress calculation
|
||||
var total int64
|
||||
if resp.StatusCode == http.StatusPartialContent {
|
||||
contentRange := resp.Header.Get("Content-Range") // e.g., bytes 1234-9999/10000
|
||||
var sizeStr string
|
||||
fmt.Sscanf(contentRange, "bytes %*d-%*d/%s", &sizeStr)
|
||||
total, _ = strconv.ParseInt(sizeStr, 10, 64)
|
||||
} else if resp.ContentLength > 0 {
|
||||
total = start + resp.ContentLength
|
||||
}
|
||||
|
||||
buf := make([]byte, 32*1024)
|
||||
var downloaded = start
|
||||
for {
|
||||
n, err := resp.Body.Read(buf)
|
||||
if n > 0 {
|
||||
if _, writeErr := out.Write(buf[:n]); writeErr != nil {
|
||||
return writeErr
|
||||
}
|
||||
downloaded += int64(n)
|
||||
if total > 0 && !silent {
|
||||
fmt.Printf("\r%.2f%%", float64(downloaded)*100/float64(total))
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
23
internal/logger/logger.go
Normal file
23
internal/logger/logger.go
Normal file
@ -0,0 +1,23 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var once sync.Once
|
||||
|
||||
func InitializeLogger(filePath string) error {
|
||||
var err error
|
||||
once.Do(func() {
|
||||
logFile, e := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||
if e != nil {
|
||||
err = e
|
||||
return
|
||||
}
|
||||
log.SetOutput(logFile)
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
})
|
||||
return err
|
||||
}
|
||||
34
internal/mountFilesystem/mountfilesystem.go
Normal file
34
internal/mountFilesystem/mountfilesystem.go
Normal file
@ -0,0 +1,34 @@
|
||||
package mountfilesystem
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"kisekinopureya.com.tr/updater/internal/logger"
|
||||
)
|
||||
|
||||
func MountFileSystem(source string, target string, fsType string, flags int, data string) {
|
||||
logger.InitializeLogger("/tmp/mountLog")
|
||||
|
||||
if err := unix.Mkdir(target, 0755); err != nil && !os.IsExist(err) {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
err := unix.Mount(source, target, fsType, uintptr(flags), data)
|
||||
if err != nil {
|
||||
log.Panic(fmt.Errorf("mount failed: %w", err))
|
||||
}
|
||||
fmt.Println("Mounted successfully at", target)
|
||||
}
|
||||
|
||||
func UnmountFileSystem(target string) {
|
||||
logger.InitializeLogger("/tmp/unmountLog")
|
||||
err := unix.Unmount(target, 0)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
fmt.Println("Unmounted successfully at", target)
|
||||
|
||||
}
|
||||
24
internal/plymouth/plymouth.go
Normal file
24
internal/plymouth/plymouth.go
Normal file
@ -0,0 +1,24 @@
|
||||
package plymouth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func ShowPlymouthMessage(message string) {
|
||||
cmd := exec.Command("plymouth", "update", "--status=\""+message+"\"")
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Println(message)
|
||||
}
|
||||
}
|
||||
|
||||
func ShowPlymouthProgress(progress int) {
|
||||
if progress < 100 && progress > 0 {
|
||||
progressStr := strconv.Itoa(progress)
|
||||
cmd := exec.Command("plymouth", "system-update", "--progress="+progressStr+"\"")
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Printf("Progress: %s%%", progressStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
41
internal/version/currentVersion.go
Normal file
41
internal/version/currentVersion.go
Normal file
@ -0,0 +1,41 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetCurrentBranch() string {
|
||||
branch, err := os.ReadFile(BranchPath)
|
||||
if err != nil {
|
||||
return "stable"
|
||||
}
|
||||
return string(branch)
|
||||
}
|
||||
|
||||
func DetermineCurrentVersion() string {
|
||||
file, err := os.Open("/etc/os-release")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var lastLine string
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
lastLine = scanner.Text()
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
parts := strings.SplitN(lastLine, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
panic("invalid format")
|
||||
}
|
||||
value := parts[1]
|
||||
|
||||
currentVersion := strings.Trim(value, "\"")
|
||||
return currentVersion
|
||||
}
|
||||
18
internal/version/parse.go
Normal file
18
internal/version/parse.go
Normal file
@ -0,0 +1,18 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var BranchPath = "/var/lib/os-branch"
|
||||
|
||||
// Parse extracts version from LABEL="rootfs-XXXX"
|
||||
func Parse(label string) (string, error) {
|
||||
re := regexp.MustCompile(`LABEL="rootfs-([0-9]+)"`)
|
||||
m := re.FindStringSubmatch(label)
|
||||
if len(m) < 2 {
|
||||
return "", fmt.Errorf("version not found in: %s", label)
|
||||
}
|
||||
return m[1], nil
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user