Compare commits

29 Commits

Author SHA1 Message Date
4b43bd1338 [actions/send_matrix] init 2023-10-31 20:40:43 +01:00
4fa1e6e4ef [nix] extract nixos-module/gokill 2023-10-31 16:43:52 +01:00
06c48f2927 [nix] update test to run quicker 2023-10-31 16:43:02 +01:00
54c0e8a46b [nix] add base integration test
run it using 'nix flake check -L'
2023-10-31 15:01:35 +01:00
7b4dcdd1d8 [docs] add minimal nix documentation 2023-10-31 14:47:03 +01:00
72d0aa61cd [docs] update usb 2023-10-31 14:46:45 +01:00
e37a84d77d [triggers/ethernet] fix deprecation warning 2023-10-31 13:47:14 +01:00
2d6ca2b0bd [actions] fix missing return 2023-10-31 13:12:50 +01:00
df2b9e7624 [actions/unix_command] check if commands can be found during test run 2023-10-31 13:12:34 +01:00
46d1270648 [actions/shell_script] init 2023-10-31 13:12:34 +01:00
5cbf066ccf [actions/shutdown] add time option to control delay 2023-10-31 13:12:34 +01:00
515e244592 [docs] fix typo 2023-10-31 02:50:25 +01:00
fb4322c040 [docs] fix typos 2023-10-31 02:47:12 +01:00
f376d8684b [nix] allow configuration of triggers/actions in nix 2023-10-31 02:45:25 +01:00
d0439394cf [docs] include README.md into docs 2023-10-31 00:51:36 +01:00
a91632028c [readme] WIP update 2023-10-31 00:51:36 +01:00
bccdcf2ca3 [docs] add README for triggers and actions 2023-10-31 00:51:36 +01:00
18e3a93a38 [docs] change docbuilder markdown output 2023-10-31 00:51:36 +01:00
cfb553c975 [docs] add Examples to each trigger/action 2023-10-31 00:51:36 +01:00
1466623070 [readme] init 2023-10-31 00:51:36 +01:00
c527d40721 [docs] rm unused file 2023-10-31 00:51:36 +01:00
781c096abf [gitignore] update 2023-10-31 00:51:36 +01:00
c94fbd4b48 [gokill] mv main 2023-10-31 00:51:36 +01:00
1f00713c1e [docs] add docbuilder.go 2023-10-31 00:51:36 +01:00
12cf423550 [gitignore] update 2023-10-31 00:51:36 +01:00
f99726d3b7 [docs] setup flake and dir 2023-10-31 00:51:36 +01:00
13932e572f [docs] init 2023-10-31 00:51:36 +01:00
20120785bd [nix] add devShell 2023-10-30 20:00:43 +01:00
5af4c963ea [actions] handle errors via channel 2023-10-30 19:59:21 +01:00
20 changed files with 694 additions and 112 deletions

2
.gitignore vendored
View File

@@ -2,8 +2,6 @@
.envrc
result
example.json
go.sum
go.mod
./gokill
output.md
thoughts.md

View File

@@ -62,6 +62,38 @@ 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.
Here is a small example config:
``` nix
{
services.gokill.enable = true;
services.gokill.triggers = [
{
type = "EthernetDisconnect";
name = "MainTrigger";
options = {
interfaceName = "eth1";
};
actions = [
{
type = "Command";
options = {
command = "echo hello world";
};
stage = 1;
}
];
}
];
}
```
This will automatically configure and enable a systemd running gokill as root user in the background
## actions
- [x] shutdown
- [ ] wipe ram

View File

@@ -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", idx+1, err)
fmt.Printf("Error occured on Stage %d: %s\n", idx+1, err)
}
}
}
@@ -119,10 +119,12 @@ func NewAction(config []internal.ActionConfig) (Action, error) {
func GetAllActions() []DocumentedAction {
return []DocumentedAction{
Printer{},
TimeOut{},
Command{},
Printer{},
ShellScript{},
Shutdown{},
SendMatrix{},
TimeOut{},
}
}

View File

@@ -49,7 +49,7 @@ func (p Printer) GetExample() string {
return `
{
type: "Print",
"options: {
"options": {
"message": "Hello World!"
}
}

226
actions/send_matrix.go Normal file
View File

@@ -0,0 +1,226 @@
package actions
import (
"fmt"
"encoding/json"
"context"
"errors"
"sync"
"time"
_ "github.com/mattn/go-sqlite3"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/id"
"maunium.net/go/mautrix/crypto/cryptohelper"
"unknown.com/gokill/internal"
)
type SendMatrix struct {
Homeserver string `json:"homeserver"`
Username string `json:"username"`
Password string `json:"password"`
Database string `json:"database"`
RoomId string `json:"roomId"`
Message string `json:"message"`
TestMessage string `json:"testMessage"`
ActionChan ActionResultChan
}
func (s SendMatrix) sendMessage(message string) error {
client, err := mautrix.NewClient(s.Homeserver, "", "")
if err != nil {
return err
}
cryptoHelper, err := cryptohelper.NewCryptoHelper(client, []byte("meow"), s.Database)
if err != nil {
return err
}
cryptoHelper.LoginAs = &mautrix.ReqLogin{
Type: mautrix.AuthTypePassword,
Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: s.Username},
Password: s.Password,
}
err = cryptoHelper.Init()
if err != nil {
return err
}
client.Crypto = cryptoHelper
fmt.Println("Matrix Client Now running")
syncCtx, cancelSync := context.WithCancel(context.Background())
var syncStopWait sync.WaitGroup
syncStopWait.Add(1)
go func() {
err = client.SyncWithContext(syncCtx)
defer syncStopWait.Done()
if err != nil && !errors.Is(err, context.Canceled) {
return
}
}()
time.Sleep(5 * time.Second)
resp, err := client.SendText(id.RoomID(s.RoomId), message)
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())
}
cancelSync()
syncStopWait.Wait()
err = cryptoHelper.Close()
if err != nil {
return fmt.Errorf("Error closing database")
}
return nil
}
func (s SendMatrix) DryExecute() {
fmt.Println("SendMatrix: Trying to send test message")
err := s.sendMessage(s.TestMessage)
if err != nil {
fmt.Println("SendMatrix: failed to send test message")
}
s.ActionChan <- err
}
func (s SendMatrix) Execute() {
fmt.Println("SendMatrix: Trying to send test message")
err := s.sendMessage(s.Message)
if err != nil {
fmt.Println("SendMatrix: failed to send test message")
}
s.ActionChan <- err
}
func CreateSendMatrix(config internal.ActionConfig, c ActionResultChan) (SendMatrix, error) {
result := SendMatrix{}
err := json.Unmarshal(config.Options, &result)
if err != nil {
return SendMatrix{}, err
}
if result.Homeserver == "" {
return SendMatrix{}, internal.OptionMissingError{"homeserver"}
}
if result.Username == "" {
return SendMatrix{}, internal.OptionMissingError{"username"}
}
if result.Password == "" {
return SendMatrix{}, internal.OptionMissingError{"password"}
}
if result.Database == "" {
return SendMatrix{}, internal.OptionMissingError{"database"}
}
if result.RoomId == "" {
return SendMatrix{}, internal.OptionMissingError{"roomId"}
}
if result.Message == "" {
return SendMatrix{}, internal.OptionMissingError{"message"}
}
if result.TestMessage == "" {
return SendMatrix{}, internal.OptionMissingError{"testMessage"}
}
result.ActionChan = c
return result, nil
}
func (s SendMatrix) Create(config internal.ActionConfig, c ActionResultChan) (Action, error) {
return CreateSendMatrix(config, c)
}
func (p SendMatrix) GetName() string {
return "SendMatrix"
}
func (p SendMatrix) GetDescription() string {
return "Sends a message to a given room. The user needs to be part of that room already."
}
func (p SendMatrix) GetExample() string {
return `
{
"type": "SendMatrix",
"options": {
"homeserver": "matrix.org",
"username": "testuser",
"password": "super-secret",
"database": "/etc/gokill/matrix.db",
"roomId": "!Balrthajskensaw:matrix.org",
"message": "attention, intruders got my device!",
"testMessage": "this is just a test, no worries"
}
}
`
}
func (p SendMatrix) GetOptions() []internal.ConfigOption {
return []internal.ConfigOption{
{
Name: "homeserver",
Type: "string",
Description: "homeserver address.",
Default: "",
},
{
Name: "username",
Type: "string",
Description: "username (localpart, wihout homeserver address)",
Default: "",
},
{
Name: "password",
Type: "string",
Description: "password in clear text",
Default: "",
},
{
Name: "database",
Type: "string",
Description: "path to database file, will be created if not existing. this is necessary to sync keys for encryption.",
Default: "",
},
{
Name: "roomId",
Type: "string",
Description: "",
Default: "",
},
{
Name: "message",
Type: "string",
Description: "actual message that should be sent",
Default: "",
},
{
Name: "testMessage",
Type: "string",
Description: "message sent during test run",
Default: "",
},
}
}

119
actions/shell_script.go Normal file
View File

@@ -0,0 +1,119 @@
package actions
import (
"encoding/json"
"fmt"
"os/exec"
"os"
"unknown.com/gokill/internal"
)
type ShellScript struct {
Path string `json:"path"`
ActionChan ActionResultChan
}
func isExecutableFile(path string) bool {
fi, err := os.Lstat(path)
if err != nil {
fmt.Println("Test executing Shellscript Failed.")
return false
}
mode := fi.Mode()
//TODO: should check if current user can execute
if mode&01111 == 0 {
return false
}
return true
}
func (c ShellScript) DryExecute() {
fmt.Printf("Test Executing ShellScript:\n%s\n", c.Path)
_, err := os.Open(c.Path)
if err != nil {
fmt.Println("Test executing Shellscript Failed.")
c.ActionChan <- err
return
}
if !isExecutableFile(c.Path) {
fmt.Println("Test executing Shellscript Failed.")
c.ActionChan <- fmt.Errorf("File is not executable: %s", c.Path)
return
}
c.ActionChan <- nil
}
func (c ShellScript) Execute() {
if !isExecutableFile(c.Path) {
fmt.Println("Test executing Shellscript Failed.")
c.ActionChan <- fmt.Errorf("File is not executable: %s", c.Path)
return
}
cmd := exec.Command("/bin/sh", c.Path)
stdout, err := cmd.Output()
if err != nil {
fmt.Println(err.Error())
c.ActionChan <- err
}
fmt.Println(string(stdout[:]))
c.ActionChan <- nil
}
func CreateShellScript(config internal.ActionConfig, c ActionResultChan) (ShellScript, error) {
result := ShellScript{}
err := json.Unmarshal(config.Options, &result)
if err != nil {
return ShellScript{}, err
}
if result.Path == "" {
return ShellScript{}, internal.OptionMissingError{"path"}
}
result.ActionChan = c
return result, nil
}
func (cc ShellScript) Create(config internal.ActionConfig, c ActionResultChan) (Action, error) {
return CreateShellScript(config, c)
}
func (p ShellScript) GetName() string {
return "ShellScript"
}
func (p ShellScript) GetDescription() string {
return "Executes the given shell script."
}
func (p ShellScript) GetExample() string {
return `
{
"type": "ShellScript",
"options": {
"path": "/path/to/file.sh"
}
}
`
}
func (p ShellScript) GetOptions() []internal.ConfigOption {
return []internal.ConfigOption{
{"path", "string", "path to script to execute", ""},
}
}

View File

@@ -3,23 +3,24 @@ package actions
import (
"fmt"
"os/exec"
"encoding/json"
"unknown.com/gokill/internal"
)
type Shutdown struct {
Timeout string `json:"time"`
ActionChan ActionResultChan
}
func (s Shutdown) DryExecute() {
fmt.Printf("shutdown -h %s\n", s.Timeout)
fmt.Println("Test Shutdown executed...")
s.ActionChan <- nil
}
func (s Shutdown) Execute() {
if err := exec.Command("shutdown", "-h", "now").Run(); err != nil {
if err := exec.Command("shutdown", "-h", s.Timeout).Run(); err != nil {
fmt.Println("Failed to initiate shutdown:", err)
}
@@ -29,7 +30,16 @@ func (s Shutdown) Execute() {
}
func (s Shutdown) Create(config internal.ActionConfig, c ActionResultChan) (Action, error) {
return Shutdown{c}, nil
var result Shutdown
err := json.Unmarshal(config.Options, &result)
if err != nil {
fmt.Println("Parsing Shutdown options failed.")
return Shutdown{}, err
}
result.ActionChan = c
return result, nil
}
func (p Shutdown) GetName() string {
@@ -44,10 +54,20 @@ func (p Shutdown) GetExample() string {
return `
{
"type": "Shutdown",
"options": {
"time": "+5" //wait 5 minutes before shutdown
}
}
`
}
func (p Shutdown) GetOptions() []internal.ConfigOption {
return []internal.ConfigOption{}
return []internal.ConfigOption{
{
Name: "time",
Type: "string",
Description: "TIME parameter passed to shutdown as follows ```shutdown -h TIME```",
Default: "now",
},
}
}

View File

@@ -14,8 +14,33 @@ type Command struct {
ActionChan ActionResultChan
}
func isCommandAvailable(name string) bool {
cmd := exec.Command("/bin/sh", "-c", "command -v "+name)
if err := cmd.Run(); err != nil {
return false
}
return true
}
func (c Command) DryExecute() {
fmt.Printf("Test Executing Command:\n%s ", c.Command)
fmt.Printf("Test Executing Command:\n%s\n", c.Command)
command, _, err := c.splitCommandString()
if err != nil {
fmt.Printf("Error during argument parsing of command '%s'\n", c.Command)
fmt.Println(err)
return
}
isAvailable := isCommandAvailable(command)
if !isAvailable {
fmt.Printf("Command %s not found\n", command)
c.ActionChan <- fmt.Errorf("Command %s not found!", command)
return
}
c.ActionChan <- nil
}
@@ -48,6 +73,7 @@ func (c Command) Execute() {
if err != nil {
fmt.Println(err.Error())
c.ActionChan <- err
}
fmt.Println(string(stdout[:]))

View File

@@ -15,9 +15,7 @@ Actions have the following syntax:
"type": "SomeAction",
"options": { //each action defines its own options
"firstOption": "someValue",
"Stage": 2 //this (positive) number defines the order of multiple actions
"stage": 2 //this (positive) number defines the order of multiple actions
}
}
```
To get a list of all actions and their options from the commandline run ``` gokill -d ```

View File

@@ -17,5 +17,3 @@ Triggers have the following syntax:
"actions": [] //list actions that should be executed here
}
```
To get a list of all triggers and their options from the commandline run ```gokill -d```

134
flake.nix
View File

@@ -15,15 +15,20 @@
go
gotools
mdbook
olm
];
};
packages.x86_64-linux.gokill = nixpkgs.legacyPackages.x86_64-linux.buildGoModule rec {
pname = "gokill";
version = "1.0";
vendorHash = "sha256-aKEOMeW9QVSLsSuHV4b1khqM0rRrMjJ6Eu5RjY+6V8k=";
vendorHash = "sha256-MVIvXxASUO33Ca34ruIz4S0QDJcW2unaG4+Zo73g/9o=";
src = ./.;
buildInputs = [
pkgs.olm
];
postInstall = ''
'';
};
@@ -31,10 +36,14 @@
packages.x86_64-linux.gokill-docbuilder = nixpkgs.legacyPackages.x86_64-linux.buildGoModule rec {
pname = "docbuilder";
version = "1.0";
vendorHash = null;
vendorHash = "sha256-MVIvXxASUO33Ca34ruIz4S0QDJcW2unaG4+Zo73g/9o=";
buildFLags = "-o . $dest/cmd/gokill/docbuilder";
src = ./.;
buildInputs = [
pkgs.olm
];
postInstall = ''
'';
};
@@ -44,101 +53,41 @@
packages.x86_64-linux.default = self.packages.x86_64-linux.gokill;
nixosModules.gokill = { config, lib, pkgs, ... }:
let
cfg = config.services.gokill;
configFile = pkgs.writeText "config.json" ''${cfg.extraConfig}'';
gokill-pkg = self.packages.x86_64-linux.gokill;
in
{
options = {
services.gokill = {
enable = lib.mkOption {
default = false;
type = lib.types.bool;
description = lib.mdDoc ''
Enables gokill daemon
'';
};
extraConfig = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc ''
gokill config.json
'';
};
};
};
config = lib.mkIf cfg.enable {
systemd.services.gokill = {
description = "gokill daemon";
serviceConfig = {
Type = "simple";
ExecStart = "${gokill-pkg}/bin/gokill -c ${configFile}";
Restart = "on-failure";
};
wantedBy = [ "default.target" ];
};
};
};
nixosModules.gokill = import ./nixos-modules/gokill.nix { self = self; };
packages.x86_64-linux.testVm =
let
nixos = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = { inherit self; };
modules = [
self.nixosModules.gokill
{
services.gokill.enable = true;
services.gokill.extraConfig = ''
[
{
"type": "Timeout",
"name": "custom timeout",
"options": {
"duration": 30
},
"actions": [
{
"type": "Print",
"options": {
"message": "Stage 1 triggered. Waiting 25 seconds"
},
"stage": 1
},
{
"type": "Timeout",
"options": {
"duration": 20
},
"stage": 1
},
{
"type": "Timeout",
"options": {
"duration": 5
},
"stage": 2
},
{
"type": "Print",
"options": {
"message": "Shutdown in 5 seconds..."
},
"stage": 2
},
{
"type": "Shutdown",
"options": {
},
"stage": 3
}
]
}
]
'';
services.gokill.triggers = [
{
type = "Timeout";
name = "custom timeout";
options = {
duration = 10;
};
actions = [
{
type = "Timeout";
options = {
duration = 5;
};
stage = 1;
}
{
type = "Shutdown";
options = {
};
stage = 2;
}
];
}
];
users.users.root.password = "root";
virtualisation.vmVariant.virtualisation.graphics = false;
}
@@ -160,5 +109,14 @@
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
checkArgs = {
pkgs = nixpkgs.legacyPackages.${system};
inherit self;
};
in {
gokill = import ./test/test.nix checkArgs;
});
};
}

21
go.mod
View File

@@ -1,3 +1,24 @@
module unknown.com/gokill
go 1.21.3
require (
github.com/mattn/go-sqlite3 v1.14.17
maunium.net/go/mautrix v0.16.1
)
require (
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/rs/zerolog v1.30.0 // indirect
github.com/tidwall/gjson v1.16.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
go.mau.fi/util v0.1.0 // indirect
golang.org/x/crypto v0.13.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.15.0 // indirect
golang.org/x/sys v0.12.0 // indirect
maunium.net/go/maulogger/v2 v2.4.1 // indirect
)

47
go.sum Normal file
View File

@@ -0,0 +1,47 @@
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/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/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=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c=
github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.16.0 h1:SyXa+dsSPpUlcwEDuKuEBJEz5vzTvOea+9rjyYodQFg=
github.com/tidwall/gjson v1.16.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
go.mau.fi/util v0.1.0 h1:BwIFWIOEeO7lsiI2eWKFkWTfc5yQmoe+0FYyOFVyaoE=
go.mau.fi/util v0.1.0/go.mod h1:AxuJUMCxpzgJ5eV9JbPWKRH8aAJJidxetNdUj7qcb84=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
maunium.net/go/maulogger/v2 v2.4.1 h1:N7zSdd0mZkB2m2JtFUsiGTQQAdP0YeFWT7YMc80yAL8=
maunium.net/go/maulogger/v2 v2.4.1/go.mod h1:omPuYwYBILeVQobz8uO3XC8DIRuEb5rXYlQSuqrbCho=
maunium.net/go/mautrix v0.16.1 h1:Wb3CvOCe8A/NLsFeZYxKrgXKiqeZUQEBD1zqm7n/kWk=
maunium.net/go/mautrix v0.16.1/go.mod h1:2Jf15tulVtr6LxoiRL4smRXwpkGWUNfBFhwh/aXDBuk=

73
nixos-modules/gokill.nix Normal file
View File

@@ -0,0 +1,73 @@
flake: { config, lib, pkgs, self, ... }:
let
cfg = config.services.gokill;
configFile = pkgs.writeText "config.json" (builtins.toJSON cfg.triggers);
gokill-pkg = self.packages.x86_64-linux.gokill;
in
{
options = with lib; {
services.gokill = {
enable = mkOption {
default = false;
type = types.bool;
description = mdDoc ''
Enables gokill daemon
'';
};
triggers = mkOption {
description = "list of triggers";
default = [];
type = with types; types.listOf ( submodule {
options = {
type = mkOption {
type = types.str;
};
name = mkOption {
type = types.str;
};
options = mkOption {
type = types.attrs;
};
actions = mkOption {
description = "list of actions";
type = with types; types.listOf ( submodule {
options = {
type = mkOption {
type = types.str;
};
options = mkOption {
type = types.attrs;
};
stage = mkOption {
type = types.int;
};
};
});
};
};
});
};
};
};
config = lib.mkIf cfg.enable {
systemd.services.gokill = {
description = "gokill daemon";
serviceConfig = {
Type = "simple";
ExecStart = "${gokill-pkg}/bin/gokill -c ${configFile}";
Restart = "on-failure";
};
wantedBy = [ "default.target" ];
};
};
}

21
test/lib.nix Normal file
View File

@@ -0,0 +1,21 @@
# tests/lib.nix
# based on https://blog.thalheim.io/2023/01/08/how-to-use-nixos-testing-framework-with-flakes/
# The first argument to this function is the test module itself
test:
# These arguments are provided by `flake.nix` on import, see checkArgs
{ pkgs, self}:
let
inherit (pkgs) lib;
# this imports the nixos library that contains our testing framework
nixos-lib = import (pkgs.path + "/nixos/lib") {};
in
(nixos-lib.runTest {
hostPkgs = pkgs;
# This speeds up the evaluation by skipping evaluating documentation (optional)
defaults.documentation.enable = lib.mkDefault false;
# This makes `self` available in the NixOS configuration of our virtual machines.
# This is useful for referencing modules or packages from your own flake
# as well as importing from other flakes.
node.specialArgs = { inherit self; };
imports = [ test ];
}).config.result

40
test/test.nix Normal file
View File

@@ -0,0 +1,40 @@
(import ./lib.nix) {
name = "gokill-base-test";
nodes = {
node1 = { self, pkgs, ... }: {
imports = [ self.nixosModules.gokill ];
services.gokill = {
enable = true;
triggers = [
{
type = "Timeout";
name = "custom timeout";
options = {
duration = 3;
};
actions = [
{
type = "Command";
options = {
command = "echo hello world";
};
stage = 2;
}
];
}
];
};
};
};
testScript = ''
import time
start_all() # wait for our service to start
node1.wait_for_unit("gokill")
time.sleep(4)
output = node1.succeed("journalctl -u gokill.service | tail -n 2 | head -n 1")
# Check if our webserver returns the expected result
assert "hello world" in output
'';
}

View File

@@ -3,7 +3,7 @@ package triggers
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"time"
"unknown.com/gokill/actions"
@@ -17,7 +17,7 @@ type EthernetDisconnect struct {
}
func isEthernetConnected(deviceName string) bool {
content, err := ioutil.ReadFile(fmt.Sprintf("/sys/class/net/%s/operstate", deviceName))
content, err := os.ReadFile(fmt.Sprintf("/sys/class/net/%s/operstate", deviceName))
if err != nil {
fmt.Println(err)
@@ -95,7 +95,7 @@ func (p EthernetDisconnect) GetExample() string {
"options": {
"interfaceName": "eth0",
"waitTillConnected": true
}
},
"actions": [
]
}

View File

@@ -54,7 +54,7 @@ func (p TimeOut) GetExample() string {
"name": "Example Trigger",
"options": {
"duration": 5
}
},
"actions": [
]
}

View File

@@ -28,8 +28,8 @@ func NewTrigger(config internal.KillSwitchConfig) (Trigger, error) {
func GetAllTriggers() []DocumentedTrigger {
return []DocumentedTrigger{
TimeOut{},
EthernetDisconnect{},
TimeOut{},
UsbDisconnect{},
}
}

View File

@@ -85,7 +85,10 @@ func (p UsbDisconnect) GetName() string {
}
func (p UsbDisconnect) GetDescription() string {
return "Triggers when given usb drive is disconnected"
return `
Triggers when given usb drive is disconnected.
Currently it simply checks that the file /dev/disk/by-id/$deviceId exists.
`
}
func (p UsbDisconnect) GetExample() string {
@@ -96,7 +99,7 @@ func (p UsbDisconnect) GetExample() string {
"options": {
"deviceId": "ata-Samsung_SSD_860_EVO_1TB_S4AALKWJDI102",
"waitTillConnected": true
}
},
"actions": [
]
}