Compare commits

21 Commits

Author SHA1 Message Date
3a4c42f9c0 [readme] fix config example 2023-11-12 01:14:55 +01:00
99529579fe rm binary 2023-11-11 22:07:41 +01:00
49f6a3d26a [triggers/receive_telegram] fix type 2023-11-11 22:04:53 +01:00
60598566d0 [readme] update 2023-11-11 22:04:44 +01:00
15f8793e2f [actions/unix_command] fix typo 2023-11-11 22:04:31 +01:00
d574902919 [actions/remove_files] init 2023-11-11 22:03:26 +01:00
a08de8a553 [go mod] rename module 2023-11-11 15:28:35 +01:00
0b2bba5d77 [gitignore] update && rm binary file 2023-11-11 03:22:10 +01:00
398e16e724 [readme] WIP 2023-11-11 03:20:03 +01:00
e93346f646 [nixpkgs] update 2023-11-11 03:19:41 +01:00
3e3725ec22 [actions/send_telegram] fix chatId type 2023-11-09 14:15:42 +01:00
9fa2c02c9d [triggers/receive_telegram] init 2023-11-09 14:15:31 +01:00
cbf41e0842 [actions/send_telegram] disable debug logs of external lb 2023-11-04 16:54:08 +01:00
44b1bcb634 [actions/send_telegram] fix token 2023-11-03 14:33:01 +01:00
ae72957a1a [docs] update readme 2023-11-03 14:32:52 +01:00
2a530681df [internal/logging] add logging lib 2023-11-03 14:32:44 +01:00
7256763c20 [actions/send_matrix] fix typo 2023-11-02 00:51:15 +01:00
54ddafd0fc [nix] support outputs for different architectures 2023-11-02 00:50:36 +01:00
baeab6daf2 [actions/send_telegram] init 2023-11-02 00:49:06 +01:00
5818349d91 [nix] nixosModule: add testRun option 2023-11-01 11:04:16 +01:00
b0723ae708 [actions/shutdown] fix missing default value 2023-10-31 21:01:54 +01:00
25 changed files with 752 additions and 166 deletions

3
.gitignore vendored
View File

@@ -2,6 +2,7 @@
.envrc
result
example.json
./gokill
gokill
output.md
thoughts.md
deb-single*

107
README.md
View File

@@ -1,27 +1,52 @@
# gokill
'gokill' is a tool that completes some actions when a certain event occurs.
actions can vary from shuting down the machine to sending mails over erasing data.
actions can be triggert by certain conditions like specific outcomes of unix
comands or not having internet connection.
actions and triggers should be easy to extend and handled like plugins. they
also should be self documenting.
every action and trigger should be testable at anytime as a 'dry-run'.
actions can have a 'stage' defined. the lowest stage is started first,
and only when all actions on that stage are finished next stage is triggered
gokill is a [software dead man's switch](https://en.wikipedia.org/wiki/Dead_man%27s_switch#Software) that empowers users to configure various events. If these events occur, they trigger predefined actions.
The tool is designed for activists, journalists, and individuals who require robust protection for their data, ensuring it remains inaccessible under any circumstances. It belongs to the category of anti-forensic tools, providing a means to safeguard against potential repression. It is specifically crafted for worst-case scenarios, such as when intruders gain physical access to a device. In these intense situations, gokill can automatically perform tasks to enhance your security. Those could be:
- locking the screen
- sending chat messages
- deleting data
- encrypting partitions
- destroying encrypted partitions
- ect
gokill should run as daemon. config should be read from /etc/somename/config.json
#### documentation
A full list of Triggers and Actions with all their configuration options can be found here:
## usage
If you use NixOS gokill can easily be integrated into your system configuration - scroll down for more info on that.
For all other linux distributions gokill currently needs to be built and setup manually. This is supposed to change.
Iam currently working/researching on publishing gokill as [ppa](https://help.launchpad.net/Packaging/PPA) and as snap.
If you have other recommendations let me know.
``` bash
# Clone the gokill repository
git clone https://github.com/k4lipso/gokill
cd gokill
# Build gokill - requires libolm
go build github.com/k4lipso/gokill
# Create a config.json and run gokill
./gokill -c config.json
# Running gokill manually is annoying, it is acutally meant to run as systemd unit.
```
## Config Example
gokill is configured using a json file. it consists of a list of triggers, where each of the triggers as a list of
actions that will be executed once triggered.
``` json
[ //list of triggers
{
"type": "UsbDisconnect",
"type": "UsbDisconnect", //triggers when the given device is disconnected
"name": "First Trigger",
"options": {
"deviceId": "ata-Samsung_SSD_860_EVO_1TB_S4AALKWJDI102",
"waitTillConnected": true //only trigger when usb drive was actually attached before
}
},
"actions": [ //list of actions that will be executed when triggered
{
"name": "unixCommand",
@@ -31,17 +56,15 @@ gokill should run as daemon. config should be read from /etc/somename/config.jso
"stage": 2 // defines the order in which actions are triggered.
},
{
"type": "sendMail",
"options": {
"smtpserver": "domain.org",
"port": 667,
"recipients": [ "mail1@host.org", "mail2@host.org" ],
"message": "kill switch was triggered",
"attachments": [ "/path/atachments" ],
"pubkeys": "/path/to/keys.pub"
},
"type": "SendTelegram",
"options": {
"token": "3345823487:FFGdEFxc1pA18d02Akslw-lkwjdA92KAH2",
"chatId": -832325872,
"message": "attention, intruders got my device!",
"testMessage": "this is just a test, no worries"
},
"stage": 1 //this event is triggered first, then the shutdown
},
}
]
},
{
@@ -49,12 +72,12 @@ gokill should run as daemon. config should be read from /etc/somename/config.jso
"name": "Second Trigger",
"options": {
"interfaceName": "eth0",
}
},
"actions": [
{
"name": "unixCommand",
"options": {
"command": "env DISPLAY=:0 sudo su -c i3lock someUser"
"command": "env DISPLAY=:0 sudo su -c i3lock someUser" //example of locking someUser's screen as root
}
}
]
@@ -63,9 +86,10 @@ gokill should run as daemon. config should be read from /etc/somename/config.jso
```
## nix support
gokill enjoys full nix support. gokill exposes a nix flakes that outputs a gokill package, a nixosModule and more.
That means you can super easily incorporate gokill into your existing nixosConfigurations.
### NixOS Module
Here is a small example config:
``` nix
@@ -94,25 +118,48 @@ Here is a small example config:
This will automatically configure and enable a systemd running gokill as root user in the background
## actions
### Build Documentation locally
``` bash
nix run github:k4lipso/gokill#docs
```
### Run integrations tests
``` bash
nix flake check github:k4lipso/gokill
```
## todos
- export snap
- export ppa
### actions
- [x] shutdown
- [ ] wipe ram
- [ ] send mail
- [ ] ~~send mail~~
- send chat message
- [x] telegram
- [x] matrix
- [ ] delete data
- [ ] shred area
- [x] random command
- [x] run command
- [ ] wordpress post
- [ ] ipfs command
- [ ] [buskill 'triggers'](https://github.com/BusKill/awesome-buskill-triggers)
- [buskill 'triggers'](https://github.com/BusKill/awesome-buskill-triggers)
- [x] [lock-screen](https://github.com/BusKill/buskill-linux/tree/master/triggers)
- [x] shutdown
- [ ] luks header shredder
- [ ] veracrypt self-destruct
## Triggers
### triggers
- [ ] no internet
- [x] [pull usb stick](https://github.com/deepakjois/gousbdrivedetector/blob/master/usbdrivedetector_linux.go)
- [x] ethernet unplugged
- receive specific chat message
- [x] telegram
- [ ] matrix
- [ ] power adapter disconnected
- [ ] unix command
- anyOf

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"sort"
"unknown.com/gokill/internal"
"github.com/k4lipso/gokill/internal"
)
type ActionResultChan chan error
@@ -36,7 +36,7 @@ func (a StagedActions) executeInternal(f func(Action)) {
continue
}
fmt.Printf("Execute Stage %v\n", idx+1)
internal.Log.Infof("Execute Stage %v", idx+1)
for actionidx, _ := range stage.Actions {
go f(stage.Actions[actionidx])
}
@@ -45,7 +45,7 @@ func (a StagedActions) executeInternal(f func(Action)) {
err := <-a.ActionChan
if err != nil {
fmt.Printf("Error occured on Stage %d: %s\n", idx+1, err)
internal.Log.Errorf("Error occured on Stage %d: %s", idx+1, err)
}
}
}
@@ -121,9 +121,11 @@ func GetAllActions() []DocumentedAction {
return []DocumentedAction{
Command{},
Printer{},
RemoveFiles{},
ShellScript{},
Shutdown{},
SendMatrix{},
SendTelegram{},
TimeOut{},
}
}

View File

@@ -2,9 +2,8 @@ package actions
import (
"encoding/json"
"fmt"
"unknown.com/gokill/internal"
"github.com/k4lipso/gokill/internal"
)
type Printer struct {
@@ -13,12 +12,12 @@ type Printer struct {
}
func (p Printer) Execute() {
fmt.Printf("Print action fires. Message: %s\n", p.Message)
internal.LogDoc(p).Infof("Print action fires. Message: %s", p.Message)
p.ActionChan <- nil
}
func (p Printer) DryExecute() {
fmt.Printf("Print action fire test. Message: %s\n", p.Message)
internal.LogDoc(p).Infof("Print action fire test. Message: %s", p.Message)
p.ActionChan <- nil
}

130
actions/remove_files.go Normal file
View File

@@ -0,0 +1,130 @@
package actions
import (
"encoding/json"
"fmt"
"os/exec"
"github.com/k4lipso/gokill/internal"
)
type RemoveFiles struct {
Files []string `json:"files"`
Directories []string `json:"directories"`
ActionChan ActionResultChan
}
func (c RemoveFiles) getRemoveCommand() string {
command := "srm"
isAvailable := isCommandAvailable(command)
if !isAvailable {
internal.LogDoc(c).Warningf("Command %s not found, falling back to 'rm'", command)
command = "rm"
}
return command
}
func (c RemoveFiles) DryExecute() {
internal.LogDoc(c).Infof("Test Execute")
command := c.getRemoveCommand()
internal.LogDoc(c).Info("The following commands would have been executed:")
for _, file := range c.Files {
internal.LogDoc(c).Noticef("%s -f %s", command, file)
}
for _, dir := range c.Directories {
internal.LogDoc(c).Noticef("%s -rf %s", command, dir)
}
c.ActionChan <- nil
}
func (c RemoveFiles) Execute() {
internal.LogDoc(c).Infof("Execute")
command := c.getRemoveCommand()
for _, file := range c.Files {
cmd := exec.Command(command, "-fv", file)
stdout, err := cmd.Output()
if err != nil {
internal.LogDoc(c).Errorf("%s", err.Error())
}
internal.LogDoc(c).Notice(string(stdout))
}
for _, dir := range c.Directories {
cmd := exec.Command(command, "-rfv", dir)
stdout, err := cmd.Output()
if err != nil {
internal.LogDoc(c).Errorf("%s", err.Error())
}
internal.LogDoc(c).Notice(string(stdout))
}
c.ActionChan <- nil
}
func CreateRemoveFiles(config internal.ActionConfig, c ActionResultChan) (RemoveFiles, error) {
result := RemoveFiles{}
err := json.Unmarshal(config.Options, &result)
if err != nil {
return RemoveFiles{}, fmt.Errorf("Error parsing RemoveFiles: %s", err)
}
result.ActionChan = c
return result, nil
}
func (cc RemoveFiles) Create(config internal.ActionConfig, c ActionResultChan) (Action, error) {
return CreateRemoveFiles(config, c)
}
func (p RemoveFiles) GetName() string {
return "RemoveFiles"
}
func (p RemoveFiles) GetDescription() string {
return `
RemoveFiles deletes the given files and directories.
If available "srm" is used, otherwise RemoveFiles falls back to "rm"
`
}
func (p RemoveFiles) GetExample() string {
return `
{
"type": "RemoveFiles",
"options": {
"files": [
"/home/user/secrets.txt"
],
"directories": [
"/home/user/.gpg",
"/home/user/.ssh",
"/home/user/.thunderbird"
]
}
}
`
}
func (p RemoveFiles) GetOptions() []internal.ConfigOption {
return []internal.ConfigOption{
{"files", "[]string", "list of absolute paths of files that should be deleted.", ""},
{"directories", "[]string", "list of absolute paths of directories that should be deleted.", ""},
}
}

View File

@@ -15,7 +15,7 @@ import (
"maunium.net/go/mautrix/id"
"maunium.net/go/mautrix/crypto/cryptohelper"
"unknown.com/gokill/internal"
"github.com/k4lipso/gokill/internal"
)
type SendMatrix struct {
@@ -53,7 +53,7 @@ func (s SendMatrix) sendMessage(message string) error {
client.Crypto = cryptoHelper
fmt.Println("Matrix Client Now running")
internal.LogDoc(s).Info("Matrix Client Now running")
syncCtx, cancelSync := context.WithCancel(context.Background())
var syncStopWait sync.WaitGroup
syncStopWait.Add(1)
@@ -72,8 +72,8 @@ func (s SendMatrix) sendMessage(message string) error {
if err != nil {
return fmt.Errorf("Failed to send event")
} else {
fmt.Println("Matrix Client: Message sent")
fmt.Printf("Matrix Client: event_id: %s", resp.EventID.String())
internal.LogDoc(s).Info("Matrix Client: Message sent")
internal.LogDoc(s).Infof("Matrix Client: event_id: %s", resp.EventID.String())
}
cancelSync()
@@ -87,22 +87,22 @@ func (s SendMatrix) sendMessage(message string) error {
}
func (s SendMatrix) DryExecute() {
fmt.Println("SendMatrix: Trying to send test message")
internal.LogDoc(s).Info("SendMatrix: Trying to send test message")
err := s.sendMessage(s.TestMessage)
if err != nil {
fmt.Println("SendMatrix: failed to send test message")
internal.LogDoc(s).Info("SendMatrix: failed to send test message")
}
s.ActionChan <- err
}
func (s SendMatrix) Execute() {
fmt.Println("SendMatrix: Trying to send test message")
internal.LogDoc(s).Info("SendMatrix: Trying to send message")
err := s.sendMessage(s.Message)
if err != nil {
fmt.Println("SendMatrix: failed to send test message")
internal.LogDoc(s).Info("SendMatrix: failed to send message")
}
s.ActionChan <- err

148
actions/send_telegram.go Normal file
View File

@@ -0,0 +1,148 @@
package actions
import (
"fmt"
"encoding/json"
//"strconv"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/k4lipso/gokill/internal"
)
type SendTelegram struct {
Token string `json:"token"`
ChatId int64 `json:"chatId"`
Message string `json:"message"`
TestMessage string `json:"testMessage"`
ActionChan ActionResultChan
}
func (s SendTelegram) sendMessage(message string) error {
bot, err := tgbotapi.NewBotAPI(s.Token)
if err != nil {
return fmt.Errorf("SendTelegram sendMessage error: %s", err)
}
bot.Debug = false
u := tgbotapi.NewUpdate(0)
u.Timeout = 60
chatId := s.ChatId
msg := tgbotapi.NewMessage(chatId, message)
_, err = bot.Send(msg)
if err != nil {
return fmt.Errorf("SendTelegram sendMessage error: %s", err)
}
return nil
}
func (s SendTelegram) DryExecute() {
internal.LogDoc(s).Info("SendTelegram: Trying to send test message")
err := s.sendMessage(s.TestMessage)
if err != nil {
internal.LogDoc(s).Info("SendTelegram: failed to send test message")
}
s.ActionChan <- err
}
func (s SendTelegram) Execute() {
internal.LogDoc(s).Info("SendTelegram: Trying to send message")
err := s.sendMessage(s.Message)
if err != nil {
internal.LogDoc(s).Info("SendTelegram: failed to send message")
}
s.ActionChan <- err
}
func CreateSendTelegram(config internal.ActionConfig, c ActionResultChan) (SendTelegram, error) {
result := SendTelegram{
ChatId: 0,
}
err := json.Unmarshal(config.Options, &result)
if err != nil {
return SendTelegram{}, err
}
if result.Token == "" {
return SendTelegram{}, internal.OptionMissingError{"token"}
}
if result.ChatId == 0 {
return SendTelegram{}, internal.OptionMissingError{"chatId"}
}
if result.Message == "" {
return SendTelegram{}, internal.OptionMissingError{"message"}
}
if result.TestMessage == "" {
return SendTelegram{}, internal.OptionMissingError{"testMessage"}
}
result.ActionChan = c
return result, nil
}
func (s SendTelegram) Create(config internal.ActionConfig, c ActionResultChan) (Action, error) {
return CreateSendTelegram(config, c)
}
func (p SendTelegram) GetName() string {
return "SendTelegram"
}
func (p SendTelegram) GetDescription() string {
return "Sends a message to a given room. The user needs to be part of that room already."
}
func (p SendTelegram) GetExample() string {
return `
{
"type": "SendTelegram",
"options": {
"token": "5349923487:FFGrETxa0pA29d02Akslw-lkwjdA92KAH2",
"chatId": -832345892,
"message": "attention, intruders got my device!",
"testMessage": "this is just a test, no worries"
}
}
`
}
func (p SendTelegram) GetOptions() []internal.ConfigOption {
return []internal.ConfigOption{
{
Name: "token",
Type: "string",
Description: "telegram bot token (ask botfather)",
Default: "",
},
{
Name: "chatId",
Type: "int",
Description: "chatId of group or chat you want the message be sent to.",
Default: "",
},
{
Name: "message",
Type: "string",
Description: "actual message that should be sent",
Default: "",
},
{
Name: "testMessage",
Type: "string",
Description: "message sent during test run",
Default: "",
},
}
}

View File

@@ -6,7 +6,7 @@ import (
"os/exec"
"os"
"unknown.com/gokill/internal"
"github.com/k4lipso/gokill/internal"
)
type ShellScript struct {
@@ -18,7 +18,6 @@ func isExecutableFile(path string) bool {
fi, err := os.Lstat(path)
if err != nil {
fmt.Println("Test executing Shellscript Failed.")
return false
}
@@ -33,18 +32,18 @@ func isExecutableFile(path string) bool {
}
func (c ShellScript) DryExecute() {
fmt.Printf("Test Executing ShellScript:\n%s\n", c.Path)
internal.LogDoc(c).Infof("Test Executing ShellScript:\n%s", c.Path)
_, err := os.Open(c.Path)
if err != nil {
fmt.Println("Test executing Shellscript Failed.")
internal.LogDoc(c).Warning("Test executing Shellscript Failed.")
c.ActionChan <- err
return
}
if !isExecutableFile(c.Path) {
fmt.Println("Test executing Shellscript Failed.")
internal.LogDoc(c).Warning("Test executing Shellscript Failed.")
c.ActionChan <- fmt.Errorf("File is not executable: %s", c.Path)
return
}
@@ -54,7 +53,7 @@ func (c ShellScript) DryExecute() {
func (c ShellScript) Execute() {
if !isExecutableFile(c.Path) {
fmt.Println("Test executing Shellscript Failed.")
internal.LogDoc(c).Warning("Executing Shellscript Failed.")
c.ActionChan <- fmt.Errorf("File is not executable: %s", c.Path)
return
}
@@ -64,11 +63,11 @@ func (c ShellScript) Execute() {
stdout, err := cmd.Output()
if err != nil {
fmt.Println(err.Error())
c.ActionChan <- err
internal.LogDoc(c).Warning("Failed to execute Shellscript")
c.ActionChan <- fmt.Errorf("Error during ShellScript execute: %s", err)
}
fmt.Println(string(stdout[:]))
internal.LogDoc(c).Infof("Shellscript output:\n%s", string(stdout[:]))
c.ActionChan <- nil
}

View File

@@ -1,11 +1,10 @@
package actions
import (
"fmt"
"os/exec"
"encoding/json"
"unknown.com/gokill/internal"
"github.com/k4lipso/gokill/internal"
)
type Shutdown struct {
@@ -14,27 +13,30 @@ type Shutdown struct {
}
func (s Shutdown) DryExecute() {
fmt.Printf("shutdown -h %s\n", s.Timeout)
fmt.Println("Test Shutdown executed...")
internal.LogDoc(s).Infof("shutdown -h %s", s.Timeout)
internal.LogDoc(s).Info("Test Shutdown executed...")
s.ActionChan <- nil
}
func (s Shutdown) Execute() {
if err := exec.Command("shutdown", "-h", s.Timeout).Run(); err != nil {
fmt.Println("Failed to initiate shutdown:", err)
internal.LogDoc(s).Errorf("Failed to initiate shutdown: %s", err)
}
fmt.Println("Shutdown executed...")
internal.LogDoc(s).Notice("Shutdown executed...")
s.ActionChan <- nil
}
func (s Shutdown) Create(config internal.ActionConfig, c ActionResultChan) (Action, error) {
var result Shutdown
result := Shutdown{
Timeout: "now",
}
err := json.Unmarshal(config.Options, &result)
if err != nil {
fmt.Println("Parsing Shutdown options failed.")
internal.LogDoc(s).Warning("Parsing Shutdown options failed.")
return Shutdown{}, err
}

View File

@@ -2,10 +2,9 @@ package actions
import (
"encoding/json"
"fmt"
"time"
"unknown.com/gokill/internal"
"github.com/k4lipso/gokill/internal"
)
type TimeOut struct {
@@ -18,7 +17,7 @@ func (t TimeOut) DryExecute() {
}
func (t TimeOut) Execute() {
fmt.Printf("Waiting %d seconds\n", t.Duration)
internal.LogDoc(t).Infof("Waiting %d seconds", t.Duration)
time.Sleep(time.Duration(t.Duration) * time.Second)
t.ActionChan <- nil
}

View File

@@ -6,7 +6,7 @@ import (
"os/exec"
"strings"
"unknown.com/gokill/internal"
"github.com/k4lipso/gokill/internal"
)
type Command struct {
@@ -24,19 +24,19 @@ func isCommandAvailable(name string) bool {
}
func (c Command) DryExecute() {
fmt.Printf("Test Executing Command:\n%s\n", c.Command)
internal.LogDoc(c).Infof("Test Executing Command: %s", c.Command)
command, _, err := c.splitCommandString()
if err != nil {
fmt.Printf("Error during argument parsing of command '%s'\n", c.Command)
fmt.Println(err)
internal.LogDoc(c).Errorf("Error during argument parsing of command '%s'", c.Command)
c.ActionChan <- fmt.Errorf("Error on Command action: %s", err)
return
}
isAvailable := isCommandAvailable(command)
if !isAvailable {
fmt.Printf("Command %s not found\n", command)
internal.LogDoc(c).Warningf("Command %s not found", command)
c.ActionChan <- fmt.Errorf("Command %s not found!", command)
return
}
@@ -60,7 +60,7 @@ func (c Command) splitCommandString() (string, []string, error) {
func (c Command) Execute() {
command, args, err := c.splitCommandString()
fmt.Println("Executing command: ", c.Command)
internal.LogDoc(c).Infof("Executing command: ", c.Command)
if err != nil {
c.ActionChan <- err
@@ -72,11 +72,11 @@ func (c Command) Execute() {
stdout, err := cmd.Output()
if err != nil {
fmt.Println(err.Error())
c.ActionChan <- err
internal.LogDoc(c).Errorf("%s", err.Error())
c.ActionChan <- fmt.Errorf("Executing Command '%s' failed: %s", c.Command, err)
}
fmt.Println(string(stdout[:]))
internal.LogDoc(c).Infof("Command Output:\n%s", string(stdout[:]))
c.ActionChan <- nil
}
@@ -108,7 +108,7 @@ func (p Command) GetName() string {
}
func (p Command) GetDescription() string {
return "Invoces given command using exec."
return "Invokes given command using exec."
}
func (p Command) GetExample() string {

View File

@@ -6,9 +6,9 @@ import (
"os"
"flag"
"unknown.com/gokill/actions"
"unknown.com/gokill/triggers"
"unknown.com/gokill/internal"
"github.com/k4lipso/gokill/actions"
"github.com/k4lipso/gokill/triggers"
"github.com/k4lipso/gokill/internal"
)
func getMarkdown(documenter internal.Documenter) string {
@@ -37,7 +37,7 @@ func writeToFile(path string, documenter internal.Documenter) error {
f, err := os.Create(fileName)
if err != nil {
fmt.Println(err)
internal.Log.Errorf("Error during writeToFile: %s", err)
return err
}
@@ -46,7 +46,7 @@ func writeToFile(path string, documenter internal.Documenter) error {
_, err = f.WriteString(getMarkdown(documenter))
if err != nil {
fmt.Println(err)
internal.Log.Errorf("Error during writeToFile: %s", err)
return err
}
@@ -86,9 +86,12 @@ func printDocumentersSummary() {
func main() {
outputPath := flag.String("output", "", "path where docs/ shoud be created")
verbose := flag.Bool("verbose", false, "log debug information")
flag.Parse()
internal.InitLogger()
internal.SetVerbose(*verbose)
if *outputPath == "" {
printDocumentersSummary()
return

View File

@@ -6,9 +6,9 @@ import (
"fmt"
"os"
"unknown.com/gokill/actions"
"unknown.com/gokill/internal"
"unknown.com/gokill/triggers"
"github.com/k4lipso/gokill/actions"
"github.com/k4lipso/gokill/internal"
"github.com/k4lipso/gokill/triggers"
)
func GetDocumentation() string {
@@ -37,19 +37,24 @@ func GetDocumentation() string {
}
func main() {
configFilePath := flag.String("c", "", "path to config file")
showDoc := flag.Bool("d", false, "show doc")
testRun := flag.Bool("t", false, "test run")
verbose := flag.Bool("verbose", false, "log debug info")
flag.Parse()
internal.InitLogger()
internal.SetVerbose(*verbose)
if *showDoc {
fmt.Print(GetDocumentation())
return
}
if *configFilePath == "" {
fmt.Println("No config file given. Use --help to show usage.")
internal.Log.Warning("No config file given. Use --help to show usage.")
return
}
@@ -58,7 +63,7 @@ func main() {
configFile, err := os.ReadFile(*configFilePath)
if err != nil {
fmt.Println("Error loading config file: ", err)
internal.Log.Errorf("Error loading config file: %s", err)
return
}
@@ -66,7 +71,7 @@ func main() {
err = json.Unmarshal(configFile, &f)
if err != nil {
fmt.Println(err)
internal.Log.Errorf("Error pasing json file: %s", err)
return
}
@@ -75,7 +80,7 @@ func main() {
trigger, err := triggers.NewTrigger(cfg)
if err != nil {
fmt.Println(err)
internal.Log.Errorf("%s", err)
return
}

42
flake.lock generated
View File

@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1698553279,
"narHash": "sha256-T/9P8yBSLcqo/v+FTOBK+0rjzjPMctVymZydbvR/Fak=",
"lastModified": 1699343069,
"narHash": "sha256-s7BBhyLA6MI6FuJgs4F/SgpntHBzz40/qV0xLPW6A1Q=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "90e85bc7c1a6fc0760a94ace129d3a1c61c3d035",
"rev": "ec750fd01963ab6b20ee1f0cb488754e8036d89d",
"type": "github"
},
"original": {
@@ -18,7 +18,41 @@
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
"nixpkgs": "nixpkgs",
"utils": "utils"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
}
},

115
flake.nix
View File

@@ -4,13 +4,18 @@
#nixpkgs for testing framework
inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
outputs = { self, nixpkgs, ... }:
inputs.utils.url = "github:numtide/flake-utils";
outputs = { self, nixpkgs, utils, ... }:
nixpkgs.lib.attrsets.recursiveUpdate
(utils.lib.eachSystem (utils.lib.defaultSystems) ( system:
let
forAllSystems = nixpkgs.lib.genAttrs [ "x86_64-linux" ];
pkgs = nixpkgs.legacyPackages."x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
currentVendorHash = "sha256-Q14p7L2Ez/kvBhMUxlyMA1I/XEIxgSXOp4dpmH/SQyI=";
in
{
devShell."x86_64-linux" = pkgs.mkShell {
devShells.default = pkgs.mkShell {
packages = with pkgs; [
go
gotools
@@ -19,40 +24,52 @@
];
};
packages.x86_64-linux.gokill = nixpkgs.legacyPackages.x86_64-linux.buildGoModule rec {
pname = "gokill";
version = "1.0";
vendorHash = "sha256-MVIvXxASUO33Ca34ruIz4S0QDJcW2unaG4+Zo73g/9o=";
src = ./.;
packages = {
gokill = pkgs.buildGoModule rec {
pname = "gokill";
version = "1.0";
vendorHash = currentVendorHash;
src = ./.;
buildInputs = [
pkgs.olm
];
buildInputs = [
pkgs.olm
];
postInstall = ''
'';
};
gokill-docbuilder = pkgs.buildGoModule rec {
pname = "docbuilder";
version = "1.0";
vendorHash = currentVendorHash;
buildFLags = "-o . $dest/cmd/gokill/docbuilder";
src = ./.;
buildInputs = [
pkgs.olm
];
postInstall = ''
'';
};
docs = pkgs.callPackage (import ./docs/default.nix) { self = self; };
default = self.packages.${system}.gokill;
postInstall = ''
'';
};
packages.x86_64-linux.gokill-docbuilder = nixpkgs.legacyPackages.x86_64-linux.buildGoModule rec {
pname = "docbuilder";
version = "1.0";
vendorHash = "sha256-MVIvXxASUO33Ca34ruIz4S0QDJcW2unaG4+Zo73g/9o=";
buildFLags = "-o . $dest/cmd/gokill/docbuilder";
src = ./.;
buildInputs = [
pkgs.olm
];
postInstall = ''
'';
apps = {
docs = {
type = "app";
program = builtins.toString (pkgs.writeScript "docs" ''
${pkgs.python3}/bin/python3 -m http.server --directory ${self.packages."${system}".docs}/share/doc'');
};
};
packages.x86_64-linux.docs = pkgs.callPackage (import ./docs/default.nix) { self = self; };
packages.x86_64-linux.default = self.packages.x86_64-linux.gokill;
})) ({
nixosModules.gokill = import ./nixos-modules/gokill.nix { self = self; };
packages.x86_64-linux.testVm =
@@ -64,6 +81,7 @@
self.nixosModules.gokill
{
services.gokill.enable = true;
services.gokill.testRun = false;
services.gokill.triggers = [
{
type = "Timeout";
@@ -72,19 +90,12 @@
duration = 10;
};
actions = [
{
type = "Timeout";
options = {
duration = 5;
};
stage = 1;
}
{
type = "Shutdown";
options = {
};
stage = 2;
}
{
type = "Shutdown";
options = {
};
stage = 2;
}
];
}
];
@@ -104,19 +115,13 @@
'');
};
apps.x86_64-linux.docs = {
type = "app";
program = builtins.toString (nixpkgs.legacyPackages."x86_64-linux".writeScript "docs" ''
${pkgs.python3}/bin/python3 -m http.server --directory ${self.packages."x86_64-linux".docs}/share/doc'');
};
checks = forAllSystems (system: let
checks.x86_64-linux = let
checkArgs = {
pkgs = nixpkgs.legacyPackages.${system};
pkgs = nixpkgs.legacyPackages."x86_64-linux";
inherit self;
};
in {
gokill = import ./test/test.nix checkArgs;
});
};
};
}) ;
}

4
go.mod
View File

@@ -1,8 +1,10 @@
module unknown.com/gokill
module github.com/k4lipso/gokill
go 1.21.3
require (
github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
github.com/mattn/go-sqlite3 v1.14.17
maunium.net/go/mautrix v0.16.1
)

4
go.sum
View File

@@ -1,8 +1,12 @@
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6 h1:qISSdUEX4sjDHfdD/vf65fhuCh3pIhiILDB7ktjJrqU=
github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=

63
internal/logging.go Normal file
View File

@@ -0,0 +1,63 @@
package internal
import (
"github.com/apsdehal/go-logger"
"fmt"
"os"
)
var Log *logger.Logger
var documenterLoggers map[string]*logger.Logger
var verbose bool
var formatNormal string = "%{message}"
var formatVerbose string = "%{time} %{level}: %{message}"
var formatModuleNormal string = "%{module}: %{message}"
var formatModuleVerbose string = "%{time} %{level} %{module}: %{message}"
func InitLogger() error {
var err error
Log, err = logger.New("UsbDisconnect", 1, os.Stdout)
if err != nil {
return fmt.Errorf("Logger initialization error: %s", err)
}
documenterLoggers = make(map[string]*logger.Logger)
verbose = false
Log.SetLogLevel(logger.InfoLevel)
return nil
}
func LogDoc(documenter Documenter) *logger.Logger {
documenterLogger, ok := documenterLoggers[documenter.GetName()]
if !ok {
documenterLogger, _ = logger.New(documenter.GetName(), 1, os.Stdout)
documenterLoggers[documenter.GetName()] = documenterLogger
}
SetVerbose(verbose)
//documenterLogger.SetFormat("%{message}")
return documenterLogger
}
func SetVerbose(value bool) {
verbose = value
toggleVerbosity := func(l *logger.Logger, formatN string, formatV string) {
if value {
l.SetLogLevel(logger.DebugLevel)
l.SetFormat(formatV)
} else {
l.SetLogLevel(logger.InfoLevel)
l.SetFormat(formatN)
}
}
toggleVerbosity(Log, formatNormal, formatVerbose)
for _, documenterLogger := range documenterLoggers {
toggleVerbosity(documenterLogger, formatModuleNormal, formatModuleVerbose)
}
}

View File

@@ -3,6 +3,7 @@ let
cfg = config.services.gokill;
configFile = pkgs.writeText "config.json" (builtins.toJSON cfg.triggers);
gokill-pkg = self.packages.x86_64-linux.gokill;
testRun = if cfg.testRun then "-t" else "";
in
{
options = with lib; {
@@ -15,6 +16,14 @@ in
'';
};
testRun = mkOption {
default = false;
type = types.bool;
description = mdDoc ''
When set to true gokill is performing a test run
'';
};
triggers = mkOption {
description = "list of triggers";
default = [];
@@ -61,7 +70,7 @@ in
description = "gokill daemon";
serviceConfig = {
Type = "simple";
ExecStart = "${gokill-pkg}/bin/gokill -c ${configFile}";
ExecStart = "${gokill-pkg}/bin/gokill -c ${configFile} ${testRun}";
Restart = "on-failure";
};

View File

@@ -6,8 +6,8 @@ import (
"os"
"time"
"unknown.com/gokill/actions"
"unknown.com/gokill/internal"
"github.com/k4lipso/gokill/actions"
"github.com/k4lipso/gokill/internal"
)
type EthernetDisconnect struct {
@@ -20,7 +20,7 @@ func isEthernetConnected(deviceName string) bool {
content, err := os.ReadFile(fmt.Sprintf("/sys/class/net/%s/operstate", deviceName))
if err != nil {
fmt.Println(err)
internal.LogDoc(EthernetDisconnect{}).Errorf("Cant read devices operstate. Check the deviceName. error: %s", err)
return false
}

View File

@@ -3,7 +3,7 @@ package triggers
import (
"testing"
"unknown.com/gokill/internal"
"github.com/k4lipso/gokill/internal"
)
func TestEthernetDisconnetConfig(t *testing.T) {

View File

@@ -0,0 +1,135 @@
package triggers
import (
"fmt"
"encoding/json"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/k4lipso/gokill/internal"
"github.com/k4lipso/gokill/actions"
)
type ReceiveTelegram struct {
Token string `json:"token"`
ChatId int64 `json:"chatId"`
Message string `json:"message"`
action actions.Action
}
func (s ReceiveTelegram) Listen() {
bot, err := tgbotapi.NewBotAPI(s.Token)
if err != nil {
return //fmt.Errorf("ReceiveTelegram waitForMessage error: %s", err)
}
bot.Debug = false
u := tgbotapi.NewUpdate(0)
u.Timeout = 60
msg := tgbotapi.NewMessage(-746157642, "BOT TEST MESSAGE")
bot.Send(msg)
chatId := s.ChatId
updates := bot.GetUpdatesChan(u)
for update := range updates {
if update.Message != nil { // If we got a message
if(update.Message.Chat.ID != chatId) {
internal.LogDoc(s).Debugf("ReceiveTelegram received wrong ChatId. Got %d, wanted %d", update.Message.Chat.ID, s.ChatId)
continue
}
if(update.Message.Text != s.Message) {
internal.LogDoc(s).Debug("ReceiveTelegram received wrong Message")
continue
}
internal.LogDoc(s).Info("ReceiveTelegram received secret message")
actions.Fire(s.action)
}
}
}
func CreateReceiveTelegram(config internal.KillSwitchConfig) (ReceiveTelegram, error) {
result := ReceiveTelegram{
ChatId: 0,
}
err := json.Unmarshal(config.Options, &result)
if err != nil {
return ReceiveTelegram{}, fmt.Errorf("Error during CreateReceiveTelegram: %s", err)
}
if result.Token == "" {
return ReceiveTelegram{}, internal.OptionMissingError{"token"}
}
if result.ChatId == 0 {
return ReceiveTelegram{}, internal.OptionMissingError{"chadId"}
}
if result.Message == "" {
return ReceiveTelegram{}, internal.OptionMissingError{"message"}
}
action, err := actions.NewAction(config.Actions)
if err != nil {
return ReceiveTelegram{}, fmt.Errorf("Error during CreateReceiveTelegram: %s", err)
}
result.action = action
return result, nil
}
func (e ReceiveTelegram) Create(config internal.KillSwitchConfig) (Trigger, error) {
return CreateReceiveTelegram(config)
}
func (p ReceiveTelegram) GetName() string {
return "ReceiveTelegram"
}
func (p ReceiveTelegram) GetDescription() string {
return "Waits for a specific message in a given chat. Once the message is received, the trigger fires."
}
func (p ReceiveTelegram) GetExample() string {
return `
{
"type": "ReceiveTelegram",
"options": {
"token": "5349923487:FFGrETxa0pA29d02Akslw-lkwjdA92KAH2",
"chatId": -832345892,
"message": "secretmessagethatfiresthetrigger"
}
}
`
}
func (p ReceiveTelegram) GetOptions() []internal.ConfigOption {
return []internal.ConfigOption{
{
Name: "token",
Type: "string",
Description: "telegram bot token (ask botfather)",
Default: "",
},
{
Name: "chatId",
Type: "int",
Description: "chatId of group or chat you want the message be received from.",
Default: "",
},
{
Name: "message",
Type: "string",
Description: "actual message that, when received, fires the trigger",
Default: "",
},
}
}

View File

@@ -1,11 +1,10 @@
package triggers
import (
"encoding/json"
"fmt"
"time"
"unknown.com/gokill/actions"
"unknown.com/gokill/internal"
"github.com/k4lipso/gokill/actions"
"github.com/k4lipso/gokill/internal"
)
type TimeOut struct {
@@ -14,10 +13,10 @@ type TimeOut struct {
}
func (t TimeOut) Listen() {
fmt.Println("TimeOut listens")
fmt.Println(t.Duration)
internal.LogDoc(t).Info("TimeOut listens")
internal.LogDoc(t).Infof("%d", t.Duration)
time.Sleep(time.Duration(t.Duration) * time.Second)
fmt.Println("TimeOut fires")
internal.LogDoc(t).Notice("TimeOut fires")
actions.Fire(t.action)
}

View File

@@ -3,7 +3,7 @@ package triggers
import (
"fmt"
"unknown.com/gokill/internal"
"github.com/k4lipso/gokill/internal"
)
type Trigger interface {
@@ -29,6 +29,7 @@ func NewTrigger(config internal.KillSwitchConfig) (Trigger, error) {
func GetAllTriggers() []DocumentedTrigger {
return []DocumentedTrigger{
EthernetDisconnect{},
ReceiveTelegram{},
TimeOut{},
UsbDisconnect{},
}

View File

@@ -3,12 +3,11 @@ package triggers
import (
"encoding/json"
"errors"
"fmt"
"os"
"time"
"unknown.com/gokill/actions"
"unknown.com/gokill/internal"
"github.com/k4lipso/gokill/actions"
"github.com/k4lipso/gokill/internal"
)
type UsbDisconnect struct {
@@ -35,8 +34,8 @@ func (t UsbDisconnect) Listen() {
time.Sleep(1 * time.Second)
}
fmt.Printf("Device %s detected.\n", t.DeviceName)
fmt.Println("UsbDisconnect Trigger is Armed")
internal.LogDoc(t).Infof("Device %s detected.", t.DeviceName)
internal.LogDoc(t).Notice("Trigger is Armed")
}
for {