Compare commits

10 Commits

Author SHA1 Message Date
06534e0bbd [readme] init 2023-10-30 23:08:02 +01:00
d4a660383e [docs] rm unused file 2023-10-30 23:00:32 +01:00
e96bbb5f49 [gitignore] update 2023-10-30 23:00:22 +01:00
ad9060c8f6 [gokill] mv main 2023-10-30 23:00:05 +01:00
58946000e1 [docs] add docbuilder.go 2023-10-30 22:57:34 +01:00
b08446bbff [gitignore] update 2023-10-30 22:57:34 +01:00
6c750a947f [docs] setup flake and dir 2023-10-30 22:57:34 +01:00
b2e20c5d75 [nix] add devShell 2023-10-30 22:57:34 +01:00
737b90d597 [actions] handle errors via channel 2023-10-30 22:57:34 +01:00
e9969abf1c [docs] init 2023-10-30 19:34:16 +01:00
19 changed files with 298 additions and 30 deletions

3
.gitignore vendored
View File

@@ -1,8 +1,9 @@
*.qcow2
.envrc
result
example.json
go.sum
go.mod
gokill
./gokill
output.md
thoughts.md

84
README.md Normal file
View File

@@ -0,0 +1,84 @@
# gokill
'gokill' is a daemon 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
the killswitch will run as daemon. config should be read from
/etc/somename/config.json
many devices can be connected to each other over ipfs. that makes it possible
to send triggers to each other. for example device A can trigger an event on
device B. no matter where they are, no zentralized service necessary.
it should be evaluated if and how smartphones could be included to that.
## actions
- [x]shutdown
- [ ] wipe ram
- [ ]send mail
- [ ]delete data
- [ ]shred area
- [x]random command
- [ ]wordpress post
- [ ]ipfs command
- [ ] [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
- [ ] no internet
- [x] [pull usb stick](https://github.com/deepakjois/gousbdrivedetector/blob/master/usbdrivedetector_linux.go)
- [x] ethernet unplugged
- [ ] power adapter disconnected
- [ ] unix command
- anyOf
- trigger wrapper containing many triggers and fires as soon as one of them
is triggered
- allOf
- [ ] ipfs trigger
## Config
##### Example
``` json
[ //list of triggers
{
"type": "command", //actual trigger
"name": "custom name",
"options": {
"command": "true",
"interval": "1m"
},
"actions": [
{
"name": "unixCommand",
"options": {
"command": "shutdown -h now"
},
"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"
},
"stage": 1 //this event is triggered first, then the shutdown
},
]
}
]
```

View File

@@ -7,10 +7,12 @@ import (
"unknown.com/gokill/internal"
)
type ActionResultChan chan error
type Action interface {
Execute()
DryExecute()
Create(internal.ActionConfig, chan bool) (Action, error)
Create(internal.ActionConfig, ActionResultChan) (Action, error)
}
type DocumentedAction interface {
@@ -23,7 +25,7 @@ type Stage struct {
}
type StagedActions struct {
ActionChan chan bool
ActionChan ActionResultChan
StageCount int
Stages []Stage
}
@@ -40,7 +42,11 @@ func (a StagedActions) executeInternal(f func(Action)) {
}
for range stage.Actions {
<-a.ActionChan
err := <-a.ActionChan
if err != nil {
fmt.Printf("Error occured on Stage %d: %s", idx+1, err)
}
}
}
}
@@ -64,11 +70,11 @@ func (a StagedActions) Execute() {
a.executeInternal(func(a Action) { a.Execute() })
}
func (a StagedActions) Create(config internal.ActionConfig, c chan bool) (Action, error) {
func (a StagedActions) Create(config internal.ActionConfig, c ActionResultChan) (Action, error) {
return StagedActions{}, nil
}
func NewSingleAction(config internal.ActionConfig, c chan bool) (Action, error) {
func NewSingleAction(config internal.ActionConfig, c ActionResultChan) (Action, error) {
for _, availableAction := range GetAllActions() {
if config.Type == availableAction.GetName() {
return availableAction.Create(config, c)
@@ -83,7 +89,7 @@ func NewAction(config []internal.ActionConfig) (Action, error) {
return config[i].Stage < config[j].Stage
})
stagedActions := StagedActions{make(chan bool), 0, []Stage{}}
stagedActions := StagedActions{make(ActionResultChan), 0, []Stage{}}
stageMap := make(map[int][]Action)

View File

@@ -9,20 +9,20 @@ import (
type Printer struct {
Message string
ActionChan chan bool
ActionChan ActionResultChan
}
func (p Printer) Execute() {
fmt.Printf("Print action fires. Message: %s\n", p.Message)
p.ActionChan <- true
p.ActionChan <- nil
}
func (p Printer) DryExecute() {
fmt.Printf("Print action fire test. Message: %s\n", p.Message)
p.ActionChan <- true
p.ActionChan <- nil
}
func (p Printer) Create(config internal.ActionConfig, c chan bool) (Action, error) {
func (p Printer) Create(config internal.ActionConfig, c ActionResultChan) (Action, error) {
var result Printer
err := json.Unmarshal(config.Options, &result)
@@ -44,6 +44,11 @@ func (p Printer) GetDescription() string {
func (p Printer) GetOptions() []internal.ConfigOption {
return []internal.ConfigOption{
{"message", "string", "Message that should be printed", "\"\""},
{
Name: "message",
Type: "string",
Description: "Message that should be printed",
Default: "\"\"",
},
}
}

View File

@@ -8,13 +8,13 @@ import (
)
type Shutdown struct {
ActionChan chan bool
ActionChan ActionResultChan
}
func (s Shutdown) DryExecute() {
fmt.Println("Test Shutdown executed...")
s.ActionChan <- true
s.ActionChan <- nil
}
@@ -25,10 +25,10 @@ func (s Shutdown) Execute() {
fmt.Println("Shutdown executed...")
s.ActionChan <- true
s.ActionChan <- nil
}
func (s Shutdown) Create(config internal.ActionConfig, c chan bool) (Action, error) {
func (s Shutdown) Create(config internal.ActionConfig, c ActionResultChan) (Action, error) {
return Shutdown{c}, nil
}

View File

@@ -10,7 +10,7 @@ import (
type TimeOut struct {
Duration time.Duration
ActionChan chan bool
ActionChan ActionResultChan
}
func (t TimeOut) DryExecute() {
@@ -20,10 +20,10 @@ func (t TimeOut) DryExecute() {
func (t TimeOut) Execute() {
fmt.Printf("Waiting %d seconds\n", t.Duration)
time.Sleep(time.Duration(t.Duration) * time.Second)
t.ActionChan <- true
t.ActionChan <- nil
}
func (t TimeOut) Create(config internal.ActionConfig, c chan bool) (Action, error) {
func (t TimeOut) Create(config internal.ActionConfig, c ActionResultChan) (Action, error) {
var result TimeOut
err := json.Unmarshal(config.Options, &result)

View File

@@ -11,12 +11,12 @@ import (
type Command struct {
Command string `json:"command"`
ActionChan chan bool
ActionChan ActionResultChan
}
func (c Command) DryExecute() {
fmt.Printf("Test Executing Command:\n%s ", c.Command)
c.ActionChan <- true
c.ActionChan <- nil
}
func (c Command) splitCommandString() (string, []string, error) {
@@ -38,8 +38,7 @@ func (c Command) Execute() {
fmt.Println("Executing command: ", c.Command)
if err != nil {
fmt.Println(err)
c.ActionChan <- false
c.ActionChan <- err
return
}
@@ -53,10 +52,10 @@ func (c Command) Execute() {
fmt.Println(string(stdout[:]))
c.ActionChan <- true
c.ActionChan <- nil
}
func CreateCommand(config internal.ActionConfig, c chan bool) (Command, error) {
func CreateCommand(config internal.ActionConfig, c ActionResultChan) (Command, error) {
result := Command{}
err := json.Unmarshal(config.Options, &result)
@@ -74,7 +73,7 @@ func CreateCommand(config internal.ActionConfig, c chan bool) (Command, error) {
return result, nil
}
func (cc Command) Create(config internal.ActionConfig, c chan bool) (Action, error) {
func (cc Command) Create(config internal.ActionConfig, c ActionResultChan) (Action, error) {
return CreateCommand(config, c)
}

View File

@@ -0,0 +1,94 @@
package main
import (
"fmt"
"strings"
"os"
"flag"
"unknown.com/gokill/actions"
"unknown.com/gokill/triggers"
"unknown.com/gokill/internal"
)
func getMarkdown(documenter internal.Documenter) string {
var result string
result += fmt.Sprintf("# %v\n%v\n## Options:\n", documenter.GetName(), documenter.GetDescription())
for _, opt := range documenter.GetOptions() {
result += fmt.Sprintf("### %v\n%v \n\n*Type:* %v \n\n*Default:* ```%v``` \n",
opt.Name, opt.Description, opt.Type, opt.Default)
}
return result
}
func writeToFile(path string, documenter internal.Documenter) error {
fileName := fmt.Sprintf("%s/%s.md", path, documenter.GetName())
f, err := os.Create(fileName)
if err != nil {
fmt.Println(err)
return err
}
defer f.Close()
_, err = f.WriteString(getMarkdown(documenter))
if err != nil {
fmt.Println(err)
return err
}
return nil
}
func writeDocumentersToFiles(destination string) {
writeFolder := func(typeName string, documenters []internal.Documenter) {
path := fmt.Sprintf("%s/%s", destination, typeName)
_ = os.Mkdir(path, os.ModePerm)
for _, documenter := range documenters {
writeToFile(path, documenter)
}
}
actions := actions.GetDocumenters()
writeFolder("actions", actions)
triggers := triggers.GetDocumenters()
writeFolder("triggers", triggers)
}
func printDocumentersSummary() {
result := fmt.Sprintf("- [Triggers](triggers/README.md)\n")
for _, trigger := range triggers.GetDocumenters() {
result += fmt.Sprintf("\t- [%s](triggers/%s.md)\n", trigger.GetName(), trigger.GetName())
}
result += fmt.Sprintf("- [Actions](actions/README.md)\n")
for _, action := range actions.GetDocumenters() {
result += fmt.Sprintf("\t- [%s](actions/%s.md)\n", action.GetName(), action.GetName())
}
fmt.Print(result)
}
func main() {
outputPath := flag.String("output", "", "path where docs/ shoud be created")
flag.Parse()
if *outputPath == "" {
printDocumentersSummary()
return
}
if len(*outputPath) > 1 {
*outputPath = strings.TrimSuffix(*outputPath, "/")
}
writeDocumentersToFiles(*outputPath)
}

1
docs/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
book/*

4
docs/SUMMARY.md Normal file
View File

@@ -0,0 +1,4 @@
# Summary
- [Introduction](./gokill.md)
@GOKILL_OPTIONS@

1
docs/actions/README.md Normal file
View File

@@ -0,0 +1 @@
# Actions

10
docs/book.toml Normal file
View File

@@ -0,0 +1,10 @@
[book]
authors = []
language = "en"
multilingual = false
src = "."
title = "gokill docs"
[output.html.fold]
enable = true
level = 0

32
docs/default.nix Normal file
View File

@@ -0,0 +1,32 @@
{ pkgs, lib, self, ... }:
with lib;
let
docbuilder = self.packages.x86_64-linux.gokill-docbuilder;
prepareMD = ''
# Copy inputs into the build directory
cp -r --no-preserve=all $inputs/* ./
${docbuilder}/bin/docbuilder --output ./
substituteInPlace ./SUMMARY.md \
--replace "@GOKILL_OPTIONS@" "$(${docbuilder}/bin/docbuilder)"
cat ./SUMMARY.md
'';
in
pkgs.stdenv.mkDerivation {
name = "gokill-docs";
phases = [ "buildPhase" ];
buildInputs = [ pkgs.mdbook ];
inputs = sourceFilesBySuffices ./. [ ".md" ".toml" ];
buildPhase = ''
dest=$out/share/doc
mkdir -p $dest
${prepareMD}
mdbook build
cp -r ./book/* $dest
'';
}

1
docs/gokill.md Normal file
View File

@@ -0,0 +1 @@
# Introduction

1
docs/triggers/README.md Normal file
View File

@@ -0,0 +1 @@
# Triggers

6
flake.lock generated
View File

@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1697915759,
"narHash": "sha256-WyMj5jGcecD+KC8gEs+wFth1J1wjisZf8kVZH13f1Zo=",
"lastModified": 1698553279,
"narHash": "sha256-T/9P8yBSLcqo/v+FTOBK+0rjzjPMctVymZydbvR/Fak=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "51d906d2341c9e866e48c2efcaac0f2d70bfd43e",
"rev": "90e85bc7c1a6fc0760a94ace129d3a1c61c3d035",
"type": "github"
},
"original": {

View File

@@ -7,8 +7,17 @@
outputs = { self, nixpkgs, ... }:
let
forAllSystems = nixpkgs.lib.genAttrs [ "x86_64-linux" ];
pkgs = nixpkgs.legacyPackages."x86_64-linux";
in
{
devShell."x86_64-linux" = pkgs.mkShell {
packages = with pkgs; [
go
gotools
mdbook
];
};
packages.x86_64-linux.gokill = nixpkgs.legacyPackages.x86_64-linux.buildGoModule rec {
pname = "gokill";
version = "1.0";
@@ -19,6 +28,20 @@
'';
};
packages.x86_64-linux.gokill-docbuilder = nixpkgs.legacyPackages.x86_64-linux.buildGoModule rec {
pname = "docbuilder";
version = "1.0";
vendorHash = null;
buildFLags = "-o . $dest/cmd/gokill/docbuilder";
src = ./.;
postInstall = ''
'';
};
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 = { config, lib, pkgs, ... }:
@@ -131,5 +154,11 @@
${self.packages."x86_64-linux".testVm}/bin/run-nixos-vm
'');
};
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'');
};
};
}

2
go.mod
View File

@@ -1,3 +1,3 @@
module unknown.com/gokill
go 1.20
go 1.21.3