Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6fcaa46f7a | |||
| 2505181291 | |||
| 8e67177c85 | |||
| 172cacb5fe | |||
| db9d5adb06 | |||
| a7faccef8a |
+3
-3
@@ -2,7 +2,7 @@
|
|||||||
bin
|
bin
|
||||||
dist
|
dist
|
||||||
logs
|
logs
|
||||||
gosentry.yaml
|
gosentry.json
|
||||||
pysentry.yaml
|
jobs.json
|
||||||
jobs.yaml
|
*.yaml
|
||||||
*.exe
|
*.exe
|
||||||
|
|||||||
+3
-3
@@ -9,9 +9,9 @@ cmd/gosentry/*.syso
|
|||||||
*.test
|
*.test
|
||||||
|
|
||||||
# Runtime files created next to the executable during local runs.
|
# Runtime files created next to the executable during local runs.
|
||||||
gosentry.yaml
|
gosentry.json
|
||||||
pysentry.yaml
|
jobs.json
|
||||||
jobs.yaml
|
*.yaml
|
||||||
logs/
|
logs/
|
||||||
|
|
||||||
# Go workspace/cache files that should stay local if a developer creates them.
|
# Go workspace/cache files that should stay local if a developer creates them.
|
||||||
|
|||||||
@@ -2,6 +2,15 @@
|
|||||||
|
|
||||||
All notable GoSentry changes are recorded in this file.
|
All notable GoSentry changes are recorded in this file.
|
||||||
|
|
||||||
|
## 0.6.0 - 2026-06-22
|
||||||
|
|
||||||
|
**PySentry legacy code removed.**
|
||||||
|
|
||||||
|
- Removed all PySentry registry autostart entries (Windows), systemd and desktop file cleanup (Linux), and associated legacy code paths.
|
||||||
|
- Updated `.gitignore` and `.dockerignore` to ignore `gosentry.json` / `jobs.json` instead of the old YAML filenames;
|
||||||
|
added `*.yaml` wildcard to ignore legacy files during the import window.
|
||||||
|
- No observable behavior changes; codebase cleanup after migration from PySentry naming.
|
||||||
|
|
||||||
## 0.5.0 - 2026-06-22
|
## 0.5.0 - 2026-06-22
|
||||||
|
|
||||||
**Storage migrated from YAML to JSON; exit-code flexibility removed.**
|
**Storage migrated from YAML to JSON; exit-code flexibility removed.**
|
||||||
|
|||||||
@@ -88,10 +88,10 @@ These land together because both edit `domain/job.go` and `storage/store.go`.
|
|||||||
- [x] P1.6 — Update storage/runner/format tests + TESTS.md
|
- [x] P1.6 — Update storage/runner/format tests + TESTS.md
|
||||||
|
|
||||||
### Phase 2 — PySentry legacy removal
|
### Phase 2 — PySentry legacy removal
|
||||||
- [ ] P2.1 — Windows autostart legacy code
|
- [x] P2.1 — Windows autostart legacy code
|
||||||
- [ ] P2.2 — Linux autostart legacy code
|
- [x] P2.2 — Linux autostart legacy code
|
||||||
- [ ] P2.3 — Delete legacy autostart tests
|
- [x] P2.3 — Delete legacy autostart tests
|
||||||
- [ ] P2.4 — `.gitignore` / `.dockerignore`
|
- [x] P2.4 — `.gitignore` / `.dockerignore`
|
||||||
|
|
||||||
### Phase 3 — Task-queue model + settings
|
### Phase 3 — Task-queue model + settings
|
||||||
- [ ] P3.1 — Config/runtime fields + defaults
|
- [ ] P3.1 — Config/runtime fields + defaults
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@ package app
|
|||||||
// Version is the application version shown in the GUI and used by build
|
// Version is the application version shown in the GUI and used by build
|
||||||
// scripts in artifact names. It is a var rather than a const so release builds
|
// scripts in artifact names. It is a var rather than a const so release builds
|
||||||
// can override it with Go ldflags when CI tags a build.
|
// can override it with Go ldflags when CI tags a build.
|
||||||
var Version = "0.5.0"
|
var Version = "0.6.0"
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ package autostart
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -27,25 +26,12 @@ func (linuxManager) Status(expectedEnabled bool, executablePath string) (bool, s
|
|||||||
}
|
}
|
||||||
|
|
||||||
const autostartDesktopFileName = "gosentry.desktop"
|
const autostartDesktopFileName = "gosentry.desktop"
|
||||||
const legacyAutostartDesktopFileName = "pysentry.desktop"
|
|
||||||
|
|
||||||
func SetAutostart(enabled bool, executablePath string, iconPath string) error {
|
func SetAutostart(enabled bool, executablePath string, iconPath string) error {
|
||||||
desktopPath, err := autostartDesktopPath()
|
desktopPath, err := autostartDesktopPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// A desktop scheduler with a tray icon belongs to the graphical session, so
|
|
||||||
// Linux autostart is implemented through XDG Autostart instead of a systemd
|
|
||||||
// user service. systemd is tempting because it is explicit and scriptable,
|
|
||||||
// but it is the wrong owner for a windowed app that should inherit the
|
|
||||||
// desktop session environment and appear in the tray predictably.
|
|
||||||
if err := cleanupLegacySystemdAutostart(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := cleanupLegacyDesktopAutostart(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if enabled {
|
if enabled {
|
||||||
if err := os.MkdirAll(filepath.Dir(desktopPath), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(desktopPath), 0o755); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -73,12 +59,6 @@ func AutostartStatus(expectedEnabled bool, executablePath string) (bool, string)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "Cannot resolve XDG autostart directory"
|
return false, "Cannot resolve XDG autostart directory"
|
||||||
}
|
}
|
||||||
if legacySystemdAutostartExists() {
|
|
||||||
return false, "Legacy systemd autostart entry still exists"
|
|
||||||
}
|
|
||||||
if legacyDesktopAutostartExists() {
|
|
||||||
return false, "Legacy desktop autostart entry still exists"
|
|
||||||
}
|
|
||||||
data, readErr := os.ReadFile(desktopPath)
|
data, readErr := os.ReadFile(desktopPath)
|
||||||
|
|
||||||
if !expectedEnabled {
|
if !expectedEnabled {
|
||||||
@@ -109,18 +89,6 @@ func autostartDesktopPath() (string, error) {
|
|||||||
return filepath.Join(configHome, "autostart", autostartDesktopFileName), nil
|
return filepath.Join(configHome, "autostart", autostartDesktopFileName), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func legacyAutostartDesktopPath() (string, error) {
|
|
||||||
configHome := os.Getenv("XDG_CONFIG_HOME")
|
|
||||||
if configHome == "" {
|
|
||||||
home, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
configHome = filepath.Join(home, ".config")
|
|
||||||
}
|
|
||||||
return filepath.Join(configHome, "autostart", legacyAutostartDesktopFileName), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func quoteDesktopExec(path string) string {
|
func quoteDesktopExec(path string) string {
|
||||||
return strconv.Quote(path)
|
return strconv.Quote(path)
|
||||||
}
|
}
|
||||||
@@ -132,67 +100,3 @@ func desktopIconLine(iconPath string) string {
|
|||||||
return "Icon=" + iconPath
|
return "Icon=" + iconPath
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanupLegacySystemdAutostart() error {
|
|
||||||
unitPath, err := legacySystemdUnitPath()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(unitPath); os.IsNotExist(err) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Older PySentry builds used a systemd user unit for autostart. The current
|
|
||||||
// GoSentry implementation uses XDG Autostart because it is a GUI/tray
|
|
||||||
// application and should be launched by the desktop session. Disable and
|
|
||||||
// remove the old unit so the two mechanisms do not fight or start duplicates.
|
|
||||||
_ = exec.Command("systemctl", "--user", "disable", "pysentry.service").Run()
|
|
||||||
if err := os.Remove(unitPath); err != nil && !os.IsNotExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_ = exec.Command("systemctl", "--user", "daemon-reload").Run()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanupLegacyDesktopAutostart() error {
|
|
||||||
desktopPath, err := legacyAutostartDesktopPath()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// The old PySentry desktop file is removed proactively instead of tolerated
|
|
||||||
// alongside the new one. Leaving both files in place would risk duplicate
|
|
||||||
// launches or confusing status diagnostics after the rename.
|
|
||||||
if err := os.Remove(desktopPath); err != nil && !os.IsNotExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func legacyDesktopAutostartExists() bool {
|
|
||||||
desktopPath, err := legacyAutostartDesktopPath()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
_, err = os.Stat(desktopPath)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func legacySystemdAutostartExists() bool {
|
|
||||||
unitPath, err := legacySystemdUnitPath()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
_, err = os.Stat(unitPath)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func legacySystemdUnitPath() (string, error) {
|
|
||||||
configHome := os.Getenv("XDG_CONFIG_HOME")
|
|
||||||
if configHome == "" {
|
|
||||||
home, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
configHome = filepath.Join(home, ".config")
|
|
||||||
}
|
|
||||||
return filepath.Join(configHome, "systemd", "user", "pysentry.service"), nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ package autostart
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -34,24 +33,3 @@ func TestLinuxAutostartStartsInTray(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLinuxAutostartRemovesLegacyDesktopEntry(t *testing.T) {
|
|
||||||
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
|
|
||||||
|
|
||||||
legacyPath, err := legacyAutostartDesktopPath()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("resolve legacy desktop path: %v", err)
|
|
||||||
}
|
|
||||||
if err := os.MkdirAll(filepath.Dir(legacyPath), 0o755); err != nil {
|
|
||||||
t.Fatalf("create legacy desktop directory: %v", err)
|
|
||||||
}
|
|
||||||
if err := os.WriteFile(legacyPath, []byte("[Desktop Entry]\nName=PySentry\n"), 0o644); err != nil {
|
|
||||||
t.Fatalf("write legacy desktop entry: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := SetAutostart(true, "/opt/gosentry/gosentry", ""); err != nil {
|
|
||||||
t.Fatalf("enable autostart: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
|
|
||||||
t.Fatalf("legacy desktop entry still exists or cannot be checked: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ func (windowsManager) Status(expectedEnabled bool, executablePath string) (bool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
const autostartName = "GoSentry"
|
const autostartName = "GoSentry"
|
||||||
const legacyAutostartName = "PySentry"
|
|
||||||
const startupShortcutFile = autostartName + ".lnk"
|
const startupShortcutFile = autostartName + ".lnk"
|
||||||
|
|
||||||
func SetAutostart(enabled bool, executablePath string, iconPath string) error {
|
func SetAutostart(enabled bool, executablePath string, iconPath string) error {
|
||||||
@@ -34,10 +33,6 @@ func SetAutostart(enabled bool, executablePath string, iconPath string) error {
|
|||||||
// the picture. A Startup-folder shortcut stores target path and arguments as
|
// the picture. A Startup-folder shortcut stores target path and arguments as
|
||||||
// separate structured fields, so it avoids quoting bugs and more closely
|
// separate structured fields, so it avoids quoting bugs and more closely
|
||||||
// matches how a user would configure a GUI app by hand.
|
// matches how a user would configure a GUI app by hand.
|
||||||
if err := cleanupLegacyRegistryAutostart(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcutPath, err := startupShortcutPath()
|
shortcutPath, err := startupShortcutPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -57,9 +52,6 @@ func AutostartStatus(expectedEnabled bool, executablePath string) (bool, string)
|
|||||||
_, statErr := os.Stat(shortcutPath)
|
_, statErr := os.Stat(shortcutPath)
|
||||||
if !expectedEnabled {
|
if !expectedEnabled {
|
||||||
if os.IsNotExist(statErr) {
|
if os.IsNotExist(statErr) {
|
||||||
if legacyRegistryAutostartExists() {
|
|
||||||
return false, "Legacy registry autostart exists; save settings to repair"
|
|
||||||
}
|
|
||||||
return true, "Autostart is off"
|
return true, "Autostart is off"
|
||||||
}
|
}
|
||||||
if statErr != nil {
|
if statErr != nil {
|
||||||
@@ -69,9 +61,6 @@ func AutostartStatus(expectedEnabled bool, executablePath string) (bool, string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if os.IsNotExist(statErr) {
|
if os.IsNotExist(statErr) {
|
||||||
if legacyRegistryAutostartExists() {
|
|
||||||
return false, "Legacy registry autostart exists; save settings to repair"
|
|
||||||
}
|
|
||||||
return false, "Autostart shortcut is missing"
|
return false, "Autostart shortcut is missing"
|
||||||
}
|
}
|
||||||
if statErr != nil {
|
if statErr != nil {
|
||||||
@@ -153,11 +142,6 @@ func readShortcut(shortcutPath string) (string, string, error) {
|
|||||||
return target, arguments, nil
|
return target, arguments, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readShortcutTarget(shortcutPath string) (string, error) {
|
|
||||||
target, _, err := readShortcut(shortcutPath)
|
|
||||||
return target, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeIfExists(path string) error {
|
func removeIfExists(path string) error {
|
||||||
err := os.Remove(path)
|
err := os.Remove(path)
|
||||||
if err == nil || os.IsNotExist(err) {
|
if err == nil || os.IsNotExist(err) {
|
||||||
@@ -166,40 +150,6 @@ func removeIfExists(path string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanupLegacyRegistryAutostart() error {
|
|
||||||
for _, name := range []string{legacyAutostartName, autostartName} {
|
|
||||||
command := exec.Command("reg.exe", "delete", `HKCU\Software\Microsoft\Windows\CurrentVersion\Run`, "/v", name, "/f")
|
|
||||||
winproc.ConfigureHiddenWindow(command)
|
|
||||||
_ = command.Run()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func legacyRegistryAutostartExists() bool {
|
|
||||||
for _, name := range []string{legacyAutostartName, autostartName} {
|
|
||||||
command := exec.Command("reg.exe", "query", `HKCU\Software\Microsoft\Windows\CurrentVersion\Run`, "/v", name)
|
|
||||||
winproc.ConfigureHiddenWindow(command)
|
|
||||||
if command.Run() == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseRegistryRunValue(output string) (string, bool) {
|
|
||||||
for _, line := range strings.Split(output, "\n") {
|
|
||||||
fields := strings.Fields(strings.TrimSpace(line))
|
|
||||||
for index, field := range fields {
|
|
||||||
if field == "REG_SZ" && index+1 < len(fields) {
|
|
||||||
value := strings.Join(fields[index+1:], " ")
|
|
||||||
value = strings.Trim(value, `"`)
|
|
||||||
return value, value != ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
func sameWindowsPath(left string, right string) bool {
|
func sameWindowsPath(left string, right string) bool {
|
||||||
left = normalizeWindowsPath(left)
|
left = normalizeWindowsPath(left)
|
||||||
right = normalizeWindowsPath(right)
|
right = normalizeWindowsPath(right)
|
||||||
|
|||||||
@@ -11,20 +11,6 @@ import (
|
|||||||
"gitea.mixdep.ru/mix/gosentry/src/domain"
|
"gitea.mixdep.ru/mix/gosentry/src/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseRegistryRunValue(t *testing.T) {
|
|
||||||
output := `
|
|
||||||
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
|
|
||||||
GoSentry REG_SZ "D:\Apps\GoSentry\gosentry.exe"
|
|
||||||
`
|
|
||||||
value, ok := parseRegistryRunValue(output)
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("expected registry value to parse")
|
|
||||||
}
|
|
||||||
if value != `D:\Apps\GoSentry\gosentry.exe` {
|
|
||||||
t.Fatalf("unexpected value: %q", value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSameWindowsPathIgnoresCaseAndQuotes(t *testing.T) {
|
func TestSameWindowsPathIgnoresCaseAndQuotes(t *testing.T) {
|
||||||
if !sameWindowsPath(`"D:\Apps\GoSentry\gosentry.exe"`, `d:\apps\gosentry\gosentry.exe`) {
|
if !sameWindowsPath(`"D:\Apps\GoSentry\gosentry.exe"`, `d:\apps\gosentry\gosentry.exe`) {
|
||||||
t.Fatal("expected paths to match")
|
t.Fatal("expected paths to match")
|
||||||
|
|||||||
Reference in New Issue
Block a user