diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 0752088..380bf83 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -34,6 +34,8 @@ nfpms: license: MIT formats: [deb, rpm] bindir: /usr/bin + scripts: + postinstall: "apt/postinstall.sh" changelog: sort: asc diff --git a/apt/postinstall.sh b/apt/postinstall.sh new file mode 100644 index 0000000..2d20343 --- /dev/null +++ b/apt/postinstall.sh @@ -0,0 +1,58 @@ +#!/bin/bash +set -e + +# Check if the script is running on Ubuntu, Debian, or Pop!_OS +if [[ -f /etc/os-release ]]; then + . /etc/os-release + if [[ "$ID" == "ubuntu" || "$ID" == "debian" || "$ID" == "pop" ]]; then + # Create keyrings directory + sudo mkdir -p --mode=0755 /usr/share/keyrings + + # Download and install GPG key + curl -fsSL https://pkg.paretosecurity.com/paretosecurity.gpg | sudo tee /usr/share/keyrings/paretosecurity.gpg >/dev/null + + # Add Pareto repository + echo 'deb [signed-by=/usr/share/keyrings/paretosecurity.gpg] https://pkg.paretosecurity.com/debian stable main' | sudo tee /etc/apt/sources.list.d/pareto.list + + # Check for systemd + if command -v systemctl >/dev/null 2>&1; then + # Create socket unit + cat << 'EOF' | sudo tee /etc/systemd/system/pareto-linux.socket > /dev/null +[Unit] +Description=Socket for pareto-linux + +[Socket] +ListenStream=/var/run/pareto-linux.sock +SocketMode=0666 +Accept=no + +[Install] +WantedBy=sockets.target +EOF + + # Create service unit + cat << 'EOF' | sudo tee /etc/systemd/system/pareto-linux.service > /dev/null +[Unit] +Description=Service for pareto-linux +Requires=pareto-linux.socket + +[Service] +ExecStart=/usr/bin/paretosecurity helper +User=root +Group=root +StandardInput=socket +Type=oneshot +RemainAfterExit=no + +[Install] +WantedBy=multi-user.target +EOF + + # Reload systemd and enable socket + systemctl daemon-reload + systemctl enable pareto-linux.socket + systemctl start pareto-linux.socket + fi +fi + + diff --git a/cmd/helper.go b/cmd/helper.go index c4b3305..93f76c5 100644 --- a/cmd/helper.go +++ b/cmd/helper.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "net" "os" "os/exec" "path/filepath" @@ -21,13 +22,12 @@ Accept=no [Install] WantedBy=sockets.target` -func getServiceContent() string { - return fmt.Sprintf(`[Unit] +const serviceContent = `[Unit] Description=Service for pareto-linux Requires=pareto-linux.socket [Service] -ExecStart=%s +ExecStart=/usr/bin/paretosecurity helper User=root Group=root StandardInput=socket @@ -35,19 +35,60 @@ Type=oneshot RemainAfterExit=no [Install] -WantedBy=multi-user.target`, os.Args[0]) +WantedBy=multi-user.target` + +func runHelper() { + // Get the socket from file descriptor 0 + file := os.NewFile(0, "socket") + listener, err := net.FileListener(file) + if err != nil { + log.Debugf("Failed to create listener: %v\n", err) + os.Exit(1) + } + defer listener.Close() + + log.Info("Server is listening on Unix domain socket...") + + for { + conn, err := listener.Accept() + if err != nil { + log.Debugf("Failed to accept connection: %v\n", err) + continue + } + + handleConnection(conn) + break + } +} + +func handleConnection(conn net.Conn) { + defer conn.Close() + log.Info("Connection received") + + // Handle the request + _, err := conn.Write([]byte("Hello from Go app!\n")) + if err != nil { + log.Debugf("Failed to write to connection: %v\n", err) + } } var helperCmd = &cobra.Command{ - Use: "helper", - Short: "install root helper", + Use: "helper [--install]", + Short: "A root helper", + Long: `A root helper that listens on a Unix domain socket and responds to authenticated requests.`, Run: func(cmd *cobra.Command, args []string) { - installSystemdHelper() + installFlag, _ := cmd.Flags().GetBool("install") + if installFlag { + installSystemdHelper() + return + } + runHelper() }, } func init() { rootCmd.AddCommand(helperCmd) + helperCmd.Flags().Bool("install", false, "install root helper") } func installSystemdHelper() { @@ -68,7 +109,7 @@ func installSystemdHelper() { // Create service file servicePath := filepath.Join(systemdPath, "pareto-linux@.service") - if err := os.WriteFile(servicePath, []byte(getServiceContent()), 0644); err != nil { + if err := os.WriteFile(servicePath, []byte(serviceContent), 0644); err != nil { fmt.Printf("Failed to create service file: %v\n", err) return } diff --git a/cmd/timer.go b/cmd/timer.go index 3549f1f..c09240e 100644 --- a/cmd/timer.go +++ b/cmd/timer.go @@ -1,7 +1,6 @@ package cmd import ( - "fmt" "os" "os/exec" "path/filepath" @@ -19,18 +18,16 @@ Persistent=true [Install] WantedBy=timers.target` -func getLocalServiceContent() string { - return fmt.Sprintf(`[Unit] +const localServiceContent = `[Unit] Description=Service for pareto-linux [Service] Type=oneshot -ExecStart=%s +ExecStart=/usr/bin/paretosecurity StandardInput=null [Install] -WantedBy=timers.target`, os.Args[0]) -} +WantedBy=timers.target` func isUserTimerInstalled() bool { homeDir, err := os.UserHomeDir() @@ -68,7 +65,7 @@ func installUserTimer() { // Create service file servicePath := filepath.Join(systemdPath, "pareto-linux.service") - if err := os.WriteFile(servicePath, []byte(getLocalServiceContent()), 0644); err != nil { + if err := os.WriteFile(servicePath, []byte(localServiceContent), 0644); err != nil { log.Fatalf("Failed to create service file:", err) return }