Skip to content

feat: add support for flatpak, snap. Fix debian native packages, also… #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added sysreplicate
Binary file not shown.
29 changes: 8 additions & 21 deletions system/output/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,20 @@ import (
)

// BuildSystemJSON creates a well-structured JSON object for the system info and packages.
func BuildSystemJSON(osType, distro, baseDistro string, packages []string) ([]byte, error) {
type ArchPackages struct {
Official []string `json:"official_packages"`
AUR []string `json:"aur_packages"`
}
func BuildSystemJSON(osType, distro, baseDistro string, packages map[string][]string) ([]byte, error) {

type SystemInfo struct {
OS string `json:"os"`
Distro string `json:"distro"`
BaseDistro string `json:"base_distro"`
Packages interface{} `json:"packages"`
}
if baseDistro == "arch" {
official, aur := SplitArchPackages(packages)
archPkgs := ArchPackages{Official: official, AUR: aur}
info := SystemInfo{
OS: osType,
Distro: distro,
BaseDistro: baseDistro,
Packages: archPkgs,
}
return json.MarshalIndent(info, "", " ")
OS string `json:"os"`
Distro string `json:"distro"`
BaseDistro string `json:"base_distro"`
Packages map[string][]string `json:"packages"`
}

info := SystemInfo{
OS: osType,
Distro: distro,
BaseDistro: baseDistro,
Packages: packages,
}
return json.MarshalIndent(info, "", " ")
}
}
141 changes: 75 additions & 66 deletions system/output/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,103 +5,112 @@ import (
"os"
)

// splitArchPackages splits the combined package list into official and AUR packages for Arch-based distros.
func SplitArchPackages(packages []string) (official, aur []string) {
isAUR := false
for _, pkg := range packages {
if pkg == "YayPackages" {
isAUR = true
continue
}
if isAUR {
if pkg != "" {
aur = append(aur, pkg)
}
} else {
if pkg != "" {
official = append(official, pkg)
}
}
}
return
}

// generateInstallScript creates a shell script to install all packages for the given distro.
// Returns an error if the script cannot be created or written.
func GenerateInstallScript(baseDistro string, packages []string, scriptPath string) error {
func GenerateInstallScript(baseDistro string, packages map[string][]string, scriptPath string) error {
f, err := os.Create(scriptPath)
if err != nil {
return err
}
defer f.Close()

_, err = f.WriteString("#!/bin/bash\nset -e\necho 'Starting package installation...'\n")
_, err = fmt.Fprintln(f, "#!/bin/bash\nset -e\necho 'Starting package installation...'")
if err != nil {
return err
}

var installCmd string
var officialInstallCmd string
switch baseDistro {
case "debian":
installCmd = "sudo apt-get install -y"
officialInstallCmd = "sudo apt-get install -y"
case "arch":
installCmd = "sudo pacman -S --noconfirm"
officialInstallCmd = "sudo pacman -S --noconfirm"
case "rhel", "fedora":
installCmd = "sudo dnf install -y"
officialInstallCmd = "sudo dnf install -y"
case "void":
installCmd = "sudo xbps-install -y"
officialInstallCmd = "sudo xbps-install -y"
default:
_, _ = f.WriteString("echo 'Unsupported distro for script generation.'\n")
_, _ = fmt.Fprintln(f, "echo 'Unsupported distro for script generation.'")
return nil
}

if baseDistro == "arch" {
official, aur := SplitArchPackages(packages)
_, err = f.WriteString("echo 'Installing official packages with pacman...'\n")
if err != nil {
return err
}
for _, pkg := range official {
if pkg == "" {
continue
for repo, pkgs := range packages {
switch repo {
case "official_packages":
_, err = fmt.Fprintf(f, "echo 'Installing packages with %s...'\n", officialInstallCmd)
if err != nil {
return err
}
_, err = f.WriteString(fmt.Sprintf("%s %s || true\n", installCmd, pkg))
for _, pkg := range pkgs {
if pkg == "" {
continue
}
_, err = fmt.Fprintf(f, "%s %s || true\n", officialInstallCmd, pkg)
if err != nil {
return err
}
}
case "yay_packages":
_, err = fmt.Fprintln(f, "if ! command -v yay >/dev/null; then\n echo 'yay not found, installing yay...'\n sudo pacman -S --noconfirm yay\nfi")
if err != nil {
return err
}
}
_, err = f.WriteString("if ! command -v yay >/dev/null; then\n echo 'yay not found, installing yay...'\n sudo pacman -S --noconfirm yay\nfi\n")
if err != nil {
return err
}
_, err = f.WriteString("echo 'Installing AUR packages with yay...'\n")
if err != nil {
return err
}
for _, pkg := range aur {
if pkg == "" {
continue
_, err = fmt.Fprintln(f, "echo 'Installing AUR packages with yay...'")
if err != nil {
return err
}
for _, pkg := range pkgs {
if pkg == "" {
continue
}
_, err = fmt.Fprintf(f, "yay -S --noconfirm %s || true\n", pkg)
if err != nil {
return err
}
}
_, err = f.WriteString(fmt.Sprintf("yay -S --noconfirm %s || true\n", pkg))

case "flatpak_packages":
_, err = fmt.Fprintf(f, "if ! command -v flatpak >/dev/null; then\n echo 'flatpak not found, installing flatpak...'\n %s flatpak\nfi\n", officialInstallCmd)
if err != nil {
return err
}
}
return nil
}
_, err = fmt.Fprintln(f, "echo 'Installing Flatpak packages...'")
if err != nil {
return err
}
for _, pkg := range pkgs {
if pkg == "" {
continue
}
_, err = fmt.Fprintf(f, "sudo flatpak install --noninteractive %s || true\n", pkg)
if err != nil {
return err
}
}

case "snap_packages":
_, err = fmt.Fprintf(f, "if ! command -v snap >/dev/null; then\n echo 'snap not found, installing snapd...'\n %s snapd\nsudo systemctl enable --now snapd.socket\nfi\n", officialInstallCmd)
// this limits it to systemctl, but need to replace this in future to support non systemd systems
if err != nil {
return err
}
_, err = fmt.Fprintln(f, "echo 'Installing Snap packages...'")
if err != nil {
return err
}
for _, pkg := range pkgs {
if pkg == "" {
continue
}
_, err = fmt.Fprintf(f, "sudo snap install %s || true\n", pkg)
if err != nil {
return err
}
}

_, err = f.WriteString(fmt.Sprintf("echo 'Installing packages with %s...'\n", installCmd))
if err != nil {
return err
}
for _, pkg := range packages {
if pkg == "" {
continue
}
_, err = f.WriteString(fmt.Sprintf("%s %s || true\n", installCmd, pkg))
if err != nil {
return err
}
}

return nil

}
66 changes: 39 additions & 27 deletions system/utils/fetch_packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,55 @@ import (
)

// FetchPackages returns a list of installed packages for the given base distro.
func FetchPackages(baseDistro string) []string {
var cmd *exec.Cmd
var cmdYay *exec.Cmd
func FetchPackages(baseDistro string) map[string][]string {
cmds := make(map[string]*exec.Cmd)

switch baseDistro {
case "debian":
cmd = exec.Command("dpkg", "--get-selections")
// cmds["official_packages"] = exec.Command("dpkg", "--get-selections")
cmds["official_packages"] = exec.Command("sh", "-c", `dpkg-query -W -f='${Package}\n' | sort > /tmp/all.txt
apt-mark showmanual | sort > /tmp/manual.txt
comm -12 /tmp/all.txt /tmp/manual.txt | xargs -r dpkg-query -W -f='${Package}=${Version}\n'
rm /tmp/all.txt /tmp/manual.txt
`)
cmds["flatpak_packages"] = exec.Command("flatpak", "list", "--app", "--columns=origin,application")
cmds["snap_packages"] = exec.Command("sh", "-c", "snap list | awk 'NR>1 {print $1}'")

case "arch":
cmd = exec.Command("pacman", "-Qn")
cmdYay = exec.Command("pacman", "-Qm")
// cmds["official_packages"] = exec.Command("pacman", "-Qen")
// cmds["yay_packages"] = exec.Command("pacman", "-Qem")
cmds["official_packages"] = exec.Command("sh", "-c", `pacman -Qen | cut -d' ' -f1`)
cmds["yay_packages"] = exec.Command("sh", "-c", `pacman -Qem | cut -d' ' -f1`)
cmds["flatpak_packages"] = exec.Command("flatpak", "list", "--app", "--columns=origin,application")
cmds["snap_packages"] = exec.Command("sh", "-c", "snap list | awk 'NR>1 {print $1}'")

case "rhel", "fedora":
cmd = exec.Command("rpm", "-qa")
cmds["official_packages"] = exec.Command("rpm", "-qa") // need to change this later
cmds["flatpak_packages"] = exec.Command("flatpak", "list", "--app", "--columns=origin,application")
cmds["snap_packages"] = exec.Command("sh", "-c", "snap list | awk 'NR>1 {print $1}'")

case "void":
cmd = exec.Command("xbps-query", "-l")
cmds["official_packages"] = exec.Command("xbps-query", "-l") // need to change this later
cmds["flatpak_packages"] = exec.Command("flatpak", "list", "--app", "--columns=origin,application")
cmds["snap_packages"] = exec.Command("sh", "-c", "snap list | awk 'NR>1 {print $1}'")

default:
log.Println("Your distro is unsupported, cannot identify package manager!")
return []string{"unknown"}
return map[string][]string{
"error": {"unsupported distro"},
}
}

if baseDistro != "arch" {
output, err := cmd.CombinedOutput()
packageMap := make(map[string][]string)

for key, value := range cmds {
output, err := value.CombinedOutput()
if err != nil {
log.Println("Error in retrieving packages:", err)
log.Println("Error in retrieving ", key, ": ", err)
continue
}
return strings.Split(strings.TrimSpace(string(output)), "\n")
packageMap[key] = strings.Split(strings.TrimSpace((string(output))), "\n")
}
return packageMap

outputPacman, err := cmd.CombinedOutput()
outPutYay, errYay := cmdYay.CombinedOutput()
if err != nil {
log.Println("Error in retrieving Pacman packages:", err)
}
if errYay != nil {
log.Println("Error in retrieving Yay packages:", errYay)
}
pacmanPackages := strings.Split(strings.TrimSpace(string(outputPacman)), "\n")
yayPackages := strings.Split(strings.TrimSpace(string(outPutYay)), "\n")
// Mark the split between official and AUR packages
yayPackages = append([]string{"YayPackages"}, yayPackages...)
return append(pacmanPackages, yayPackages...)
}
}