Skip to content

Commit bfe3a69

Browse files
committed
Added uninstallation logic to remove monokit if the associated redmine project is inactive.
1 parent 46c361b commit bfe3a69

File tree

2 files changed

+309
-0
lines changed

2 files changed

+309
-0
lines changed

main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/monobilisim/monokit/shutdownNotifier"
2727
"github.com/monobilisim/monokit/sshNotifier"
2828
"github.com/monobilisim/monokit/ufwApply"
29+
"github.com/monobilisim/monokit/uninstall"
2930
"github.com/monobilisim/monokit/winHealth"
3031
)
3132

@@ -412,6 +413,9 @@ func main() {
412413
sshNotifierCmd.Flags().BoolP("login", "1", false, "Login")
413414
sshNotifierCmd.Flags().BoolP("logout", "0", false, "Logout")
414415

416+
// Uninstall
417+
uninstall.Uninstall()
418+
415419
if err := RootCmd.Execute(); err != nil {
416420
fmt.Println(err)
417421
// Plugin cleanup will be handled by defer

uninstall/uninstall.go

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
package uninstall
2+
3+
import (
4+
"bufio"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
"os"
10+
"os/exec"
11+
"strings"
12+
"time"
13+
14+
"github.com/monobilisim/monokit/common"
15+
"github.com/rs/zerolog/log"
16+
)
17+
18+
type Project struct {
19+
ID int `json:"id"`
20+
Name string `json:"name"`
21+
Identifier string `json:"identifier"`
22+
Description string `json:"description"`
23+
Status int `json:"status"`
24+
CreatedOn time.Time `json:"created_on"`
25+
}
26+
27+
type RedmineResponse struct {
28+
Projects []Project `json:"projects"`
29+
TotalCount int `json:"total_count"`
30+
Offset int `json:"offset"`
31+
Limit int `json:"limit"`
32+
}
33+
34+
var ProjectIdentifier string
35+
36+
func Uninstall() {
37+
common.Init()
38+
39+
checkDir := "/tmp/mono"
40+
checkFile := checkDir + "/uninstall_check"
41+
42+
if err := os.MkdirAll(checkDir, 0755); err != nil {
43+
log.Error().Err(err).Msg("Failed to create check directory")
44+
}
45+
46+
info, err := os.Stat(checkFile)
47+
if err == nil {
48+
if time.Since(info.ModTime()) < 24*time.Hour {
49+
return
50+
}
51+
}
52+
53+
if err := os.WriteFile(checkFile, []byte(time.Now().String()), 0644); err != nil {
54+
log.Error().Err(err).Msg("Failed to update check file timestamp")
55+
}
56+
57+
if isProjectInactive() {
58+
notify("Uninstalling monokit for host " + common.Config.Identifier)
59+
removeCron()
60+
removeFromSSHD()
61+
removeShutdownNotifier()
62+
deleteMonokit()
63+
notify("monokit uninstallation for host " + common.Config.Identifier + " completed.")
64+
os.Exit(0)
65+
}
66+
}
67+
68+
func isProjectInactive() bool {
69+
baseURL := strings.TrimSuffix(common.Config.Redmine.Url, "/") + "/projects.json"
70+
apiKey := common.Config.Redmine.Api_key
71+
client := &http.Client{}
72+
73+
limit := 100
74+
offset := 0
75+
76+
for {
77+
url := fmt.Sprintf("%s?status=5&limit=%d&offset=%d", baseURL, limit, offset)
78+
req, err := http.NewRequest("GET", url, nil)
79+
if err != nil {
80+
log.Error().Err(err).Msg("Failed to create request to get the inactive projects from redmine for host " + common.Config.Identifier + " while uninstalling monokit.")
81+
return false
82+
}
83+
84+
req.Header.Set("Content-Type", "application/json")
85+
req.Header.Set("User-Agent", "Monokit/devel")
86+
req.Header.Set("X-Redmine-Api-Key", apiKey)
87+
88+
resp, err := client.Do(req)
89+
if err != nil {
90+
log.Error().Err(err).Msg("Failed to make request to get the inactive projects from redmine for host " + common.Config.Identifier + " while uninstalling monokit.")
91+
return false
92+
}
93+
defer resp.Body.Close()
94+
body, _ := io.ReadAll(resp.Body)
95+
if resp.StatusCode != http.StatusOK {
96+
log.Error().Int("statusCode", resp.StatusCode).Str("body", string(body)).Msg("API returned error for host " + common.Config.Identifier + " while uninstalling monokit.")
97+
return false
98+
}
99+
100+
var result RedmineResponse
101+
if err := json.Unmarshal(body, &result); err != nil {
102+
log.Error().Err(err).Msg("Failed to parse JSON for host " + common.Config.Identifier + " while uninstalling monokit.")
103+
return false
104+
}
105+
106+
projectMap := make(map[string]bool)
107+
for _, p := range result.Projects {
108+
projectMap[p.Identifier] = true
109+
}
110+
111+
parts := strings.Split(common.Config.Identifier, "-")
112+
for i := len(parts); i > 0; i-- {
113+
candidate := strings.Join(parts[:i], "-")
114+
if projectMap[candidate] {
115+
ProjectIdentifier = candidate
116+
return true
117+
}
118+
}
119+
120+
if len(result.Projects) < limit {
121+
break
122+
}
123+
offset += limit
124+
}
125+
126+
return false
127+
}
128+
129+
func notify(message string) {
130+
common.Alarm(message, "alarm", ProjectIdentifier+"-self-destruct", true)
131+
}
132+
133+
func removeCron() {
134+
output, err := exec.Command("crontab", "-l").Output()
135+
if err != nil {
136+
log.Error().Err(err).Msg("Failed to get crontab")
137+
notify("Failed to get crontab for host " + common.Config.Identifier + " while uninstalling monokit. Skipping cron removal.")
138+
return
139+
}
140+
var newCrontab []string
141+
scanner := bufio.NewScanner(strings.NewReader(string(output)))
142+
for scanner.Scan() {
143+
line := scanner.Text()
144+
145+
if strings.HasPrefix(line, "#Ansible:") {
146+
if scanner.Scan() {
147+
nextLine := scanner.Text()
148+
if !strings.Contains(nextLine, "/usr/local/bin/monokit") {
149+
newCrontab = append(newCrontab, line, nextLine)
150+
}
151+
continue
152+
}
153+
newCrontab = append(newCrontab, line)
154+
break
155+
}
156+
if !strings.Contains(line, "/usr/local/bin/monokit") {
157+
newCrontab = append(newCrontab, line)
158+
}
159+
}
160+
161+
if err := scanner.Err(); err != nil {
162+
log.Error().Err(err).Msg("Error scanning crontab output")
163+
}
164+
165+
cmd := exec.Command("crontab", "-")
166+
cmd.Stdin = strings.NewReader(strings.Join(newCrontab, "\n") + "\n")
167+
if err := cmd.Run(); err != nil {
168+
log.Error().Err(err).Msg("Failed to update crontab")
169+
notify("Failed to update crontab for host " + common.Config.Identifier)
170+
}
171+
notify("Successfully removed cron for host " + common.Config.Identifier + " while uninstalling monokit.")
172+
}
173+
174+
func removeFromSSHD() {
175+
path := "/etc/pam.d/sshd"
176+
file, err := os.Open(path)
177+
if err != nil {
178+
log.Error().Err(err).Msg("Failed to open " + path)
179+
notify("Failed to open " + path + " for host " + common.Config.Identifier + " while uninstalling monokit.")
180+
return
181+
}
182+
defer file.Close()
183+
184+
var newLines []string
185+
scanner := bufio.NewScanner(file)
186+
for scanner.Scan() {
187+
line := scanner.Text()
188+
if !strings.Contains(line, "monokit") {
189+
newLines = append(newLines, line)
190+
}
191+
}
192+
193+
if err := scanner.Err(); err != nil {
194+
log.Error().Err(err).Msg("Error scanning " + path)
195+
notify("Error scanning " + path + " for host " + common.Config.Identifier + " while uninstalling monokit.")
196+
return
197+
}
198+
199+
outFile, err := os.Create(path)
200+
if err != nil {
201+
log.Error().Err(err).Msg("Failed to open " + path + " for writing")
202+
notify("Failed to open " + path + " for writing for host " + common.Config.Identifier + " while uninstalling monokit.")
203+
return
204+
}
205+
defer outFile.Close()
206+
207+
writer := bufio.NewWriter(outFile)
208+
for _, line := range newLines {
209+
if _, err := writer.WriteString(line + "\n"); err != nil {
210+
log.Error().Err(err).Msg("Failed to write to " + path)
211+
notify("Failed to write to " + path + " for host " + common.Config.Identifier + " while uninstalling monokit.")
212+
return
213+
}
214+
}
215+
if err := writer.Flush(); err != nil {
216+
log.Error().Err(err).Msg("Failed to flush writer for " + path)
217+
notify("Failed to flush writer for " + path + " for host " + common.Config.Identifier + " while uninstalling monokit.")
218+
return
219+
}
220+
notify("Successfully removed monokit references from " + path + " for host " + common.Config.Identifier + " while uninstalling monokit.")
221+
}
222+
223+
func removeShutdownNotifier() {
224+
cmd := exec.Command("systemctl", "list-unit-files", "shutdown-notifier.service")
225+
output, err := cmd.Output()
226+
if err != nil {
227+
log.Error().Err(err).Msg("Failed to check if shutdown-notifier service exists")
228+
notify("Failed to check if shutdown-notifier service exists for host " + common.Config.Identifier + " while uninstalling monokit.")
229+
return
230+
}
231+
232+
if strings.Contains(string(output), "shutdown-notifier.service") {
233+
cmd = exec.Command("systemctl", "stop", "shutdown-notifier.service")
234+
if err := cmd.Run(); err != nil {
235+
log.Error().Err(err).Msg("Failed to stop shutdown-notifier service")
236+
notify("Failed to stop shutdown-notifier service for host " + common.Config.Identifier + " while uninstalling monokit.")
237+
}
238+
239+
cmd = exec.Command("systemctl", "disable", "shutdown-notifier.service")
240+
if err := cmd.Run(); err != nil {
241+
log.Error().Err(err).Msg("Failed to disable shutdown-notifier service")
242+
notify("Failed to disable shutdown-notifier service for host " + common.Config.Identifier + " while uninstalling monokit.")
243+
}
244+
245+
path := "/etc/systemd/system/shutdown-notifier.service"
246+
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
247+
log.Error().Err(err).Msg("Failed to remove " + path)
248+
notify("Failed to remove " + path + " for host " + common.Config.Identifier + " while uninstalling monokit.")
249+
} else {
250+
notify("Successfully removed shutdown-notifier service for host " + common.Config.Identifier + " while uninstalling monokit.")
251+
}
252+
253+
cmd = exec.Command("systemctl", "daemon-reload")
254+
if err := cmd.Run(); err != nil {
255+
log.Error().Err(err).Msg("Failed to reload daemon")
256+
}
257+
}
258+
}
259+
260+
func deleteMonokit() {
261+
xdgStateHome := os.Getenv("XDG_STATE_HOME")
262+
if xdgStateHome == "" {
263+
xdgStateHome = os.Getenv("HOME") + "/.local/state"
264+
}
265+
paths := []string{
266+
"/var/lib/monokit",
267+
"/etc/mono",
268+
"/etc/mono.sh",
269+
"/tmp/mono",
270+
"/tmp/monokit",
271+
"/tmp/mono.sh",
272+
"/var/log/monokit",
273+
"/var/log/monokit.log",
274+
xdgStateHome + "/monokit",
275+
"/usr/local/bin/monokit",
276+
}
277+
for _, path := range paths {
278+
exists, err := exists(path)
279+
if err != nil {
280+
log.Error().Err(err).Msg("Failed to check if " + path + " exists")
281+
notify("Failed to check if " + path + " exists for host " + common.Config.Identifier + " while uninstalling monokit.")
282+
return
283+
}
284+
if exists {
285+
err = os.RemoveAll(path)
286+
if err != nil {
287+
log.Error().Err(err).Msg("Failed to remove " + path)
288+
notify("Failed to remove " + path + " for host " + common.Config.Identifier + " while uninstalling monokit.")
289+
} else {
290+
notify("Successfully removed " + path + " for host " + common.Config.Identifier + " while uninstalling monokit.")
291+
}
292+
}
293+
}
294+
}
295+
296+
func exists(path string) (bool, error) {
297+
_, err := os.Stat(path)
298+
if err == nil {
299+
return true, nil
300+
}
301+
if os.IsNotExist(err) {
302+
return false, nil
303+
}
304+
return false, err
305+
}

0 commit comments

Comments
 (0)