mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-02-12 16:12:44 +00:00
510 lines
14 KiB
Go
510 lines
14 KiB
Go
package install
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"embed"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"text/template"
|
|
"time"
|
|
)
|
|
|
|
//go:embed systemd.service
|
|
var installFiles embed.FS
|
|
|
|
const (
|
|
// Installation paths
|
|
installDir = "/opt/phishingclub"
|
|
binaryName = "phishingclub"
|
|
dataDir = "data"
|
|
|
|
// User and group
|
|
serviceUser = "phishingclub"
|
|
serviceGroup = "phishingclub"
|
|
|
|
// Service
|
|
serviceName = "phishingclub"
|
|
)
|
|
|
|
// Install handles the complete installation process interactively
|
|
func Install() error {
|
|
return RunInteractiveInstall()
|
|
}
|
|
|
|
// InstallNonInteractive handles the non-interactive installation process
|
|
func InstallNonInteractive() error {
|
|
if os.Geteuid() != 0 {
|
|
return fmt.Errorf("installation must be run as root")
|
|
}
|
|
|
|
steps := []struct {
|
|
name string
|
|
fn func() error
|
|
}{
|
|
{"check sqlite dependency", checkSQLiteDependency},
|
|
{"create user and group", createUserAndGroup},
|
|
{"create directories", createDirectories},
|
|
{"install binary", installBinary},
|
|
{"install systemd service", installSystemdService},
|
|
{"set permissions", setPermissions},
|
|
{"enable service", enableService},
|
|
{"start service", startService},
|
|
{"print info", outputCredentialsAndInfo},
|
|
}
|
|
|
|
for _, step := range steps {
|
|
fmt.Printf("Step: %s\n", step.name)
|
|
if err := step.fn(); err != nil {
|
|
return fmt.Errorf("%s: %w", step.name, err)
|
|
}
|
|
}
|
|
|
|
fmt.Println()
|
|
fmt.Println("Installer completed successfully! 🐟")
|
|
fmt.Println()
|
|
fmt.Println("If the service is restarted before the first used is setup, the password will change! - Check the logs")
|
|
fmt.Println()
|
|
fmt.Println("# Tips")
|
|
fmt.Println("'journalctl -u phishingclub.service -f' to see logs")
|
|
fmt.Println("'systemctl status phishingclub' to check status of the service")
|
|
fmt.Println("")
|
|
|
|
fmt.Println()
|
|
|
|
return nil
|
|
}
|
|
|
|
// Update handles the update process of the application
|
|
func Update() error {
|
|
if os.Geteuid() != 0 {
|
|
return fmt.Errorf("update must be run as root")
|
|
}
|
|
|
|
// Check if service exists
|
|
if err := checkServiceExists(); err != nil {
|
|
return fmt.Errorf("service check failed: %w", err)
|
|
}
|
|
|
|
steps := []struct {
|
|
name string
|
|
fn func() error
|
|
}{
|
|
{"stop service", stopService},
|
|
{"backup current binary", backupCurrentBinary},
|
|
{"update binary", updateBinary},
|
|
{"start service", startService},
|
|
}
|
|
|
|
for _, step := range steps {
|
|
fmt.Printf("Step: %s\n", step.name)
|
|
if err := step.fn(); err != nil {
|
|
return fmt.Errorf("%s: %w", step.name, err)
|
|
}
|
|
}
|
|
|
|
fmt.Println()
|
|
|
|
fmt.Println("# Post-update Status Check")
|
|
// Give the service a moment to stabilize
|
|
time.Sleep(2 * time.Second)
|
|
|
|
cmd := exec.Command("systemctl", "status", serviceName)
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
fmt.Println("⚠️ Warning: Service may not be running properly after update")
|
|
fmt.Printf("Service status output:\n%s\n", string(output))
|
|
fmt.Printf("Check status with: systemctl status %s\n", serviceName)
|
|
return nil
|
|
}
|
|
|
|
if !strings.Contains(string(output), "active (running)") {
|
|
fmt.Println("⚠️ Warning: Service is not in 'active' state after update")
|
|
fmt.Printf("Service status output:\n%s\n", string(output))
|
|
return nil
|
|
}
|
|
|
|
fmt.Println("✅ Service is running")
|
|
fmt.Println()
|
|
fmt.Println("Update completed! 🐟")
|
|
fmt.Println()
|
|
|
|
return nil
|
|
}
|
|
|
|
// checkServiceExists verifies that the service is installed
|
|
func checkServiceExists() error {
|
|
servicePath := filepath.Join("/etc/systemd/system", serviceName+".service")
|
|
if _, err := os.Stat(servicePath); os.IsNotExist(err) {
|
|
return fmt.Errorf("service is not installed. Please run --install first")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// stopService stops the running service
|
|
func stopService() error {
|
|
cmd := exec.Command("systemctl", "stop", serviceName)
|
|
if output, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("failed to stop service: %s, error: %w", string(output), err)
|
|
}
|
|
|
|
// Wait a moment to ensure the service is fully stopped
|
|
time.Sleep(2 * time.Second)
|
|
return nil
|
|
}
|
|
|
|
// backupCurrentBinary creates a backup of the current binary
|
|
func backupCurrentBinary() error {
|
|
currentBinary := filepath.Join(installDir, binaryName)
|
|
backupBinary := filepath.Join(installDir, binaryName+".backup")
|
|
// #nosec
|
|
input, err := os.ReadFile(currentBinary)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read current binary: %w", err)
|
|
}
|
|
|
|
if err := os.WriteFile(backupBinary, input, 0600); err != nil {
|
|
return fmt.Errorf("failed to write backup binary: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// updateBinary updates the binary with the new version
|
|
func updateBinary() error {
|
|
executable, err := os.Executable()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get executable path: %w", err)
|
|
}
|
|
// #nosec
|
|
input, err := os.ReadFile(executable)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read new binary: %w", err)
|
|
}
|
|
|
|
binaryPath := filepath.Join(installDir, binaryName)
|
|
if err := os.WriteFile(binaryPath, input, 0600); err != nil {
|
|
return fmt.Errorf("failed to write new binary: %w", err)
|
|
}
|
|
|
|
// Set proper ownership
|
|
if err := setPermissions(); err != nil {
|
|
return fmt.Errorf("failed to set permissions: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func checkSQLiteDependency() error {
|
|
// Check if sqlite3 is installed
|
|
if _, err := exec.LookPath("sqlite3"); err != nil {
|
|
fmt.Println("SQLite3 is not installed. Attempting to install...")
|
|
|
|
// Detect package manager and install sqlite
|
|
if err := installSQLite(); err != nil {
|
|
return fmt.Errorf("failed to install sqlite: %w", err)
|
|
}
|
|
}
|
|
|
|
// Verify sqlite installation
|
|
cmd := exec.Command("sqlite3", "--version")
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("sqlite verification failed: %w", err)
|
|
}
|
|
|
|
fmt.Printf("SQLite version: %s", output)
|
|
return nil
|
|
}
|
|
|
|
func installSQLite() error {
|
|
// Detect the package manager and install sqlite
|
|
var cmd *exec.Cmd
|
|
|
|
// Check for apt (Debian/Ubuntu)
|
|
if _, err := exec.LookPath("apt"); err == nil {
|
|
cmd = exec.Command("apt", "update")
|
|
err := cmd.Run() // Update package list
|
|
if err != nil {
|
|
fmt.Printf("ERR: %s\n", err)
|
|
}
|
|
cmd = exec.Command("apt", "install", "-y", "sqlite3")
|
|
} else if _, err := exec.LookPath("yum"); err == nil {
|
|
// Check for yum (RHEL/CentOS)
|
|
cmd = exec.Command("yum", "install", "-y", "sqlite")
|
|
} else if _, err := exec.LookPath("dnf"); err == nil {
|
|
// Check for dnf (Fedora)
|
|
cmd = exec.Command("dnf", "install", "-y", "sqlite")
|
|
} else if _, err := exec.LookPath("pacman"); err == nil {
|
|
// Check for pacman (Arch)
|
|
cmd = exec.Command("pacman", "-S", "--noconfirm", "sqlite")
|
|
} else {
|
|
return fmt.Errorf("no supported package manager found (apt, yum, dnf, or pacman)")
|
|
}
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to install sqlite: %s, error: %w", string(output), err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// enableService enables the systemd service so it gets started on boot
|
|
func enableService() error {
|
|
if err := exec.Command("systemctl", "enable", serviceName).Run(); err != nil {
|
|
return fmt.Errorf("failed to enable service: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// startService starts the systemd service
|
|
func startService() error {
|
|
if err := exec.Command("systemctl", "start", serviceName).Run(); err != nil {
|
|
return fmt.Errorf("failed to start service: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func Uninstall() error {
|
|
if os.Geteuid() != 0 {
|
|
return fmt.Errorf("uninstallation must be run as root")
|
|
}
|
|
|
|
// Display warning and confirmation prompt
|
|
fmt.Println("⚠️ WARNING: Uninstallation will remove ALL components of Phishing Club, including:")
|
|
fmt.Println(" • The application binary and its service")
|
|
fmt.Println(" • ALL configuration files")
|
|
fmt.Println(" • ALL data, including the database")
|
|
fmt.Println(" • The phishingclub user and group")
|
|
fmt.Println("\nThis operation CANNOT be undone!")
|
|
|
|
reader := bufio.NewReader(os.Stdin)
|
|
fmt.Print("\nType 'YES' (all caps) to confirm uninstallation: ")
|
|
confirmation, _ := reader.ReadString('\n')
|
|
confirmation = strings.TrimSpace(confirmation)
|
|
|
|
if confirmation != "YES" {
|
|
fmt.Println("Uninstallation cancelled.")
|
|
return fmt.Errorf("uninstallation cancelled by user")
|
|
}
|
|
|
|
return performUninstall()
|
|
}
|
|
|
|
// UninstallNonInteractive performs uninstallation without confirmation prompts
|
|
func UninstallNonInteractive() error {
|
|
if os.Geteuid() != 0 {
|
|
return fmt.Errorf("uninstallation must be run as root")
|
|
}
|
|
|
|
return performUninstall()
|
|
}
|
|
|
|
// performUninstall handles the actual uninstallation process
|
|
func performUninstall() error {
|
|
fmt.Println("Uninstalling Phishing Club...")
|
|
|
|
// Stop and disable service
|
|
err := exec.Command("systemctl", "stop", serviceName).Run()
|
|
if err != nil {
|
|
fmt.Printf("Warning: Failed to stop service: %v\n", err)
|
|
// Continue with uninstallation
|
|
}
|
|
|
|
err = exec.Command("systemctl", "disable", serviceName).Run()
|
|
if err != nil {
|
|
fmt.Printf("Warning: Failed to disable service: %v\n", err)
|
|
// Continue with uninstallation
|
|
}
|
|
|
|
// Remove service file
|
|
servicePath := filepath.Join("/etc/systemd/system", serviceName+".service")
|
|
err = os.Remove(servicePath)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
fmt.Printf("Warning: Failed to remove service unit file: %v\n", err)
|
|
// Continue with uninstallation
|
|
}
|
|
|
|
// Reload systemd
|
|
err = exec.Command("systemctl", "daemon-reload").Run()
|
|
if err != nil {
|
|
fmt.Printf("Warning: Failed to reload systemctl daemon: %v\n", err)
|
|
// Continue with uninstallation
|
|
}
|
|
|
|
// Remove installation directory
|
|
err = os.RemoveAll(installDir)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return fmt.Errorf("failed to remove install directory: %w", err)
|
|
}
|
|
|
|
// Remove user and group
|
|
fmt.Printf("Removing user and group: %s\n", serviceUser)
|
|
err = exec.Command("userdel", serviceUser).Run()
|
|
if err != nil {
|
|
fmt.Printf("Warning: Failed to delete user %s: %v\n", serviceUser, err)
|
|
// Continue with uninstallation
|
|
}
|
|
|
|
_ = exec.Command("groupdel", serviceGroup).Run()
|
|
// Group deletion errors are not critical
|
|
|
|
fmt.Println("\n✅ Uninstallation completed successfully!")
|
|
fmt.Println("All Phishing Club components have been removed from your system.")
|
|
|
|
return nil
|
|
}
|
|
|
|
func createUserAndGroup() error {
|
|
// Check if group exists
|
|
if err := exec.Command("getent", "group", serviceGroup).Run(); err != nil {
|
|
cmd := exec.Command("groupadd", serviceGroup)
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("failed to create group: %w", err)
|
|
}
|
|
}
|
|
|
|
// Check if user exists
|
|
if err := exec.Command("getent", "passwd", serviceUser).Run(); err != nil {
|
|
cmd := exec.Command("useradd",
|
|
"-r",
|
|
"-g", serviceGroup,
|
|
"-s", "/bin/false",
|
|
serviceUser,
|
|
)
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("failed to create user: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func createDirectories() error {
|
|
dirs := []string{
|
|
installDir,
|
|
filepath.Join(installDir, dataDir),
|
|
filepath.Join(installDir, dataDir, "assets"),
|
|
filepath.Join(installDir, dataDir, "assets", "shared"),
|
|
filepath.Join(installDir, dataDir, "attachments"),
|
|
filepath.Join(installDir, dataDir, "attachments", "shared"),
|
|
filepath.Join(installDir, dataDir, "certs"),
|
|
filepath.Join(installDir, dataDir, "certs", "own-managed"),
|
|
}
|
|
|
|
for _, dir := range dirs {
|
|
if err := os.MkdirAll(dir, 0750); err != nil {
|
|
return fmt.Errorf("failed to create directory %s: %w", dir, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func installBinary() error {
|
|
executable, err := os.Executable()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get executable path: %w", err)
|
|
}
|
|
// #nosec
|
|
input, err := os.ReadFile(executable)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read executable: %w", err)
|
|
}
|
|
|
|
binaryPath := filepath.Join(installDir, binaryName)
|
|
// #nosec
|
|
if err := os.WriteFile(binaryPath, input, 0750); err != nil {
|
|
return fmt.Errorf("failed to write binary: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func installSystemdService() error {
|
|
serviceTemplate, err := installFiles.ReadFile("systemd.service")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read service template: %w", err)
|
|
}
|
|
|
|
// Create template data with all required fields
|
|
data := struct {
|
|
User string
|
|
Group string
|
|
InstallDir string
|
|
BinaryPath string
|
|
ConfigPath string
|
|
DataDir string
|
|
}{
|
|
User: serviceUser,
|
|
Group: serviceGroup,
|
|
InstallDir: installDir,
|
|
BinaryPath: filepath.Join(installDir, binaryName),
|
|
ConfigPath: filepath.Join(installDir, "config.json"),
|
|
DataDir: filepath.Join(installDir, dataDir),
|
|
}
|
|
|
|
// Parse and execute the template
|
|
tmpl, err := template.New("service").Parse(string(serviceTemplate))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse service template: %w", err)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err := tmpl.Execute(&buf, data); err != nil {
|
|
return fmt.Errorf("failed to execute service template: %w", err)
|
|
}
|
|
|
|
servicePath := filepath.Join("/etc/systemd/system", serviceName+".service")
|
|
// #nosec
|
|
if err := os.WriteFile(servicePath, buf.Bytes(), 0644); err != nil {
|
|
return fmt.Errorf("failed to write service file: %w", err)
|
|
}
|
|
|
|
if err := exec.Command("systemctl", "daemon-reload").Run(); err != nil {
|
|
return fmt.Errorf("failed to reload systemd: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func setPermissions() error {
|
|
// #nosec
|
|
cmd := exec.Command("chown", "-R",
|
|
fmt.Sprintf("%s:%s", serviceUser, serviceGroup),
|
|
installDir,
|
|
)
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("failed to set ownership: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func outputCredentialsAndInfo() error {
|
|
time.Sleep(3 * time.Second)
|
|
fmt.Println()
|
|
fmt.Println("<<< IMPORTANT >>>")
|
|
|
|
steps := []string{
|
|
"journalctl -u phishingclub.service -r -n 5000 --no-pager --output=cat | grep 'Username:' -m1",
|
|
"journalctl -u phishingclub.service -r -n 5000 --no-pager --output=cat | grep 'Password:' -m1",
|
|
"journalctl -u phishingclub.service -r -n 5000 --no-pager --output=cat | grep 'Phishing HTTPS' -m1 -B1 | tac",
|
|
"journalctl -u phishingclub.service -r -n 5000 --no-pager --output=cat | grep 'Phishing HTTP server' -m1 -B1 | tac",
|
|
"journalctl -u phishingclub.service -r -n 5000 --no-pager --output=cat | grep 'Admin server' -m1 -B1 | tac",
|
|
}
|
|
for _, t := range steps {
|
|
cmd := exec.Command("sh", "-c", t)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("failed to get all install information: %w", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|