init project

This commit is contained in:
2025-09-28 23:05:51 +03:30
commit 04e88bf3fc
8 changed files with 291 additions and 0 deletions

71
cmd/ppilot/main.go Normal file
View File

@@ -0,0 +1,71 @@
package main
import (
"flag"
"fmt"
"os"
"parch-pilot/internal/config"
"parch-pilot/internal/deploy"
"parch-pilot/internal/restore"
)
func main() {
deployFlag := flag.Bool("deploy", false, "Deploy a new system from YAML using pacstrap and related tools (assumes partitions are prepared and runs as root)")
restoreFlag := flag.Bool("restore", false, "Apply YAML configuration on an already installed system (assumes root and tools like yay/flatpak installed)")
validate := flag.Bool("validate", false, "Validate the YAML file for parsing errors")
flag.Parse()
if flag.NArg() != 1 {
fmt.Println("Usage: ppilot [--deploy|--restore|--validate] /path/to/yaml")
flag.PrintDefaults()
os.Exit(1)
}
yamlPath := flag.Arg(0)
count := 0
if *deployFlag {
count++
}
if *restoreFlag {
count++
}
if *validate {
count++
}
if count != 1 {
fmt.Println("Exactly one of --deploy, --restore, or --validate must be specified")
os.Exit(1)
}
conf, err := config.ParseConfig(yamlPath)
if err != nil {
fmt.Printf("Error parsing YAML: %v\n", err)
os.Exit(1)
}
if *validate {
fmt.Println("YAML configuration is valid.")
return
}
if *restoreFlag {
if err := restore.RestoreConfig(conf); err != nil {
fmt.Printf("Error during restore: %v\n", err)
os.Exit(1)
}
fmt.Println("Restore completed successfully.")
return
}
if *deployFlag {
if err := deploy.DeploySystem(conf, yamlPath); err != nil {
fmt.Printf("Error during deploy: %v\n", err)
os.Exit(1)
}
fmt.Println("Deploy completed. Boot into the new system and run 'ppilot --restore /root/parch-pilot.yaml' as root after installing ppilot (e.g., via 'go install').")
return
}
}

5
go.mod Normal file
View File

@@ -0,0 +1,5 @@
module parch-pilot
go 1.25.1
require gopkg.in/yaml.v3 v3.0.1

4
go.sum Normal file
View File

@@ -0,0 +1,4 @@
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

39
internal/config/config.go Normal file
View File

@@ -0,0 +1,39 @@
package config
import (
"os"
"gopkg.in/yaml.v3"
)
type Config struct {
PacmanPackages []string `yaml:"pacman_packages"`
AURPackages []string `yaml:"aur_packages"`
FlatpakPackages []string `yaml:"flatpak_packages"`
CustomCommands []string `yaml:"custom_commands"`
Services []string `yaml:"services"`
Deploy struct {
RootDevice string `yaml:"root_device"`
EfiDevice string `yaml:"efi_device"`
MountPoint string `yaml:"mount_point"`
} `yaml:"deploy"`
}
func ParseConfig(yamlPath string) (*Config, error) {
data, err := os.ReadFile(yamlPath)
if err != nil {
return nil, err
}
var conf Config
err = yaml.Unmarshal(data, &conf)
if err != nil {
return nil, err
}
if conf.Deploy.MountPoint == "" {
conf.Deploy.MountPoint = "/mnt"
}
return &conf, nil
}

90
internal/deploy/deploy.go Normal file
View File

@@ -0,0 +1,90 @@
package deploy
import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"parch-pilot/internal/config"
"parch-pilot/internal/utils"
)
func DeploySystem(conf *config.Config, yamlPath string) error {
if conf.Deploy.RootDevice == "" {
return fmt.Errorf("root_device is required for --deploy")
}
mp := conf.Deploy.MountPoint
if err := utils.ExecCmd("mount", conf.Deploy.RootDevice, mp); err != nil {
return err
}
if conf.Deploy.EfiDevice != "" {
bootDir := filepath.Join(mp, "boot")
if err := os.MkdirAll(bootDir, 0755); err != nil {
return err
}
if err := utils.ExecCmd("mount", conf.Deploy.EfiDevice, bootDir); err != nil {
return err
}
}
pacArgs := []string{mp, "base", "linux", "linux-firmware", "grub"} // Include grub for fallback, but use systemd-boot if EFI
if len(conf.PacmanPackages) > 0 {
pacArgs = append(pacArgs, conf.PacmanPackages...)
}
if err := utils.ExecCmd("pacstrap", pacArgs...); err != nil {
return err
}
fstabPath := filepath.Join(mp, "etc/fstab")
genCmd := exec.Command("genfstab", "-U", mp)
output, err := genCmd.Output()
if err != nil {
return err
}
if err := os.WriteFile(fstabPath, output, 0644); err != nil {
return err
}
if conf.Deploy.EfiDevice != "" {
if err := utils.ExecCmd("arch-chroot", mp, "bootctl", "--esp-path=/boot", "install"); err != nil {
fmt.Println("Warning: Failed to install systemd-boot; falling back to GRUB.")
if err := utils.ExecCmd("arch-chroot", mp, "grub-install", "--target=x86_64-efi", "--efi-directory=/boot", "--bootloader-id=GRUB"); err != nil {
return err
}
if err := utils.ExecCmd("arch-chroot", mp, "grub-mkconfig", "-o", "/boot/grub/grub.cfg"); err != nil {
return err
}
}
} else {
// BIOS GRUB
disk := filepath.Dir(conf.Deploy.RootDevice) // assume /dev/sda from /dev/sda2
if err := utils.ExecCmd("arch-chroot", mp, "grub-install", "--target=i386-pc", disk); err != nil {
return err
}
if err := utils.ExecCmd("arch-chroot", mp, "grub-mkconfig", "-o", "/boot/grub/grub.cfg"); err != nil {
return err
}
}
copyPath := filepath.Join(mp, "root/parch-pilot.yaml")
src, err := os.Open(yamlPath)
if err != nil {
return err
}
defer src.Close()
dst, err := os.Create(copyPath)
if err != nil {
return err
}
defer dst.Close()
if _, err := io.Copy(dst, src); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,44 @@
package restore
import (
"parch-pilot/internal/config"
"parch-pilot/internal/utils"
)
func RestoreConfig(conf *config.Config) error {
if len(conf.PacmanPackages) > 0 {
args := append([]string{"-S", "--needed", "--noconfirm"}, conf.PacmanPackages...)
if err := utils.ExecCmd("sudo", append([]string{"pacman"}, args...)...); err != nil {
return err
}
}
if len(conf.AURPackages) > 0 {
args := append([]string{"-S", "--needed", "--noconfirm"}, conf.AURPackages...)
if err := utils.ExecCmd("yay", args...); err != nil {
return err
}
}
if len(conf.FlatpakPackages) > 0 {
for _, pkg := range conf.FlatpakPackages {
if err := utils.ExecCmd("flatpak", "install", "--assumeyes", "--noninteractive", pkg); err != nil {
return err
}
}
}
for _, cmdStr := range conf.CustomCommands {
if err := utils.ExecCmd("sh", "-c", cmdStr); err != nil {
return err
}
}
for _, srv := range conf.Services {
if err := utils.ExecCmd("systemctl", "enable", "--now", srv); err != nil {
return err
}
}
return nil
}

13
internal/utils/utils.go Normal file
View File

@@ -0,0 +1,13 @@
package utils
import (
"os"
"os/exec"
)
func ExecCmd(name string, args ...string) error {
cmd := exec.Command(name, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}

25
sample/sample.yaml Normal file
View File

@@ -0,0 +1,25 @@
deploy:
root_device: /dev/sda2 # Example: Root filesystem partition
efi_device: /dev/sda1 # Example: EFI partition (optional for BIOS systems)
mount_point: /mnt # Optional: Defaults to /mnt if not specified
pacman_packages:
- vim
- git
- networkmanager
- efibootmgr # Useful for EFI setups
aur_packages:
- visual-studio-code-bin
flatpak_packages:
- com.slack.Slack
- org.signal.Signal
custom_commands:
- echo "Custom setup complete"
- timedatectl set-ntp true
services:
- NetworkManager
- sshd