package main import ( "fmt" "os" "os/exec" "path/filepath" "strings" ) func isAutoStartEnabled(appPath string) bool { serviceFile := autoStartServiceFile(appPath) kioskInfof("autostart check begin service=%s expectedExec=%s expectedWorkdir=%s", serviceFile, appPath, filepath.Dir(appPath)) content, err := os.ReadFile(serviceFile) if err != nil { kioskWarnf("autostart service read failed service=%s err=%v", serviceFile, err) return false } matches := autoStartServiceMatches(string(content), appPath) kioskInfof("autostart service match result service=%s matches=%v", serviceFile, matches) return matches } func enableAutoStart(appPath string) error { serviceFile := autoStartServiceFile(appPath) kioskInfof("autostart setup begin service=%s exec=%s workdir=%s", serviceFile, appPath, filepath.Dir(appPath)) content := autoStartServiceContent(appPath) kioskDebugf("autostart service content:\n%s", content) cmd := exec.Command("sudo", "bash", "-c", fmt.Sprintf("echo '%s' > %s", content, serviceFile)) if err := cmd.Run(); err != nil { kioskErrorf("write autostart service failed service=%s err=%v", serviceFile, err) return err } kioskInfof("write autostart service success service=%s", serviceFile) cmd = exec.Command("sudo", "systemctl", "daemon-reload") if err := cmd.Run(); err != nil { kioskErrorf("systemctl daemon-reload failed err=%v", err) return err } kioskInfof("systemctl daemon-reload success") cmd = exec.Command("sudo", "systemctl", "enable", autoStartServiceName(appPath)) if err := cmd.Run(); err != nil { kioskErrorf("systemctl enable failed service=%s err=%v", autoStartServiceName(appPath), err) return err } kioskInfof("systemctl enable success service=%s", autoStartServiceName(appPath)) return nil } func autoStartServiceName(appPath string) string { return strings.TrimSuffix(filepath.Base(appPath), filepath.Ext(appPath)) + ".service" } func autoStartServiceFile(appPath string) string { return filepath.Join("/etc/systemd/system", autoStartServiceName(appPath)) } func autoStartServiceContent(appPath string) string { appName := strings.TrimSuffix(filepath.Base(appPath), filepath.Ext(appPath)) return fmt.Sprintf(`[Unit] Description=%s Background Service After=network.target [Service] Type=simple ExecStart=%s Restart=on-failure RestartSec=10 User=root WorkingDirectory=%s [Install] WantedBy=multi-user.target `, appName, appPath, filepath.Dir(appPath)) } func autoStartServiceMatches(content string, appPath string) bool { execStart := normalizeSystemdValue(systemdUnitValue(content, "ExecStart")) workingDirectory := normalizeSystemdValue(systemdUnitValue(content, "WorkingDirectory")) expectedWorkdir := filepath.Dir(appPath) matches := execStart == appPath && workingDirectory == expectedWorkdir kioskInfof("autostart service fields execStart=%s expectedExec=%s workingDirectory=%s expectedWorkdir=%s matches=%v", execStart, appPath, workingDirectory, expectedWorkdir, matches) return matches } func systemdUnitValue(content string, key string) string { for _, line := range strings.Split(content, "\n") { line = strings.TrimSpace(line) if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") { continue } name, value, ok := strings.Cut(line, "=") if !ok || strings.TrimSpace(name) != key { continue } return strings.TrimSpace(value) } return "" } func normalizeSystemdValue(value string) string { return strings.Trim(strings.TrimSpace(value), `"'`) }