-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
daemon.py
executable file
·155 lines (131 loc) · 4.6 KB
/
daemon.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#! /usr/bin/env python3
from typing import Callable
import os
import platform
import functools
import sqlite3
import argparse
import sys
import ast
CSV_FMT = "pwd,start_time,duration,pipestatus,entered_cmd,expanded_cmd"
insert_command = """
INSERT INTO log (user,hostname,purpose,pwd,start_time,duration,pipestatus,entered_cmd,expanded_cmd)
VALUES (?,?,?,?,?,?,?,?,?)
"""
# TODO: I think hagd.bash handles this from schema.sql
create_tables = """
CREATE TABLE IF NOT EXISTS log (
-- low/no variety
user TEXT, -- cached here
hostname TEXT, -- cached here
-- all remaining via pipe
-- little variety
purpose TEXT,
pwd TEXT,
-- much variety
start_time INTEGER, -- this might be the PK?
duration INTEGER,
pipestatus TEXT, -- space-delimited ints (single for basic commands, multiple with pipes)
entered_cmd TEXT,
expanded_cmd TEXT,
-- not inserted; changed later on export
exported INTEGER NOT NULL DEFAULT 0
); -- may want WITHOUT ROWID; worth testing! https://www.sqlite.org/withoutrowid.html
-- CREATE TABLE commands ();
-- tables/views/indexes:
-- distinct abstract commands as entered
-- - annotations could go here?
-- distinct executables
-- - annotations could go here?
-- annotations as a table keyed against others?
--
"""
# NOTES:
# - this is defined by the nix-darwin launchd service
# - updates will be meaningless without rebuilding nix-darwin config
# TODO: should these have a default? just fall over?
HAG_PIPE = os.environ.get("HAG_PIPE")
USER = os.environ.get("USER")
HOSTNAME = platform.node()
INGESTERS = dict()
Ingester = Callable[[], callable]
DB = None
def chomp_row_v1(
user,
hostname,
purpose,
pwd,
start_time,
duration,
pipestatus,
entered_cmd,
expanded_cmd,
):
with DB:
try:
DB.execute(
insert_command,
(
user,
hostname,
purpose,
pwd,
start_time,
duration,
pipestatus,
entered_cmd.strip() if entered_cmd else entered_cmd,
expanded_cmd.strip() if expanded_cmd else expanded_cmd,
),
# TODO this could be optimized
)
except sqlite3.IntegrityError as e:
# we probably already have this record. We've already printed the ingest; let's just print the message and swallow the error
print("errybody", e)
def add_ingester(header: str, ingester: Ingester) -> Ingester:
curried = functools.partial(ingester, USER, HOSTNAME)
INGESTERS[header] = curried
return curried
CURRENT_INGESTER = add_ingester(CSV_FMT, chomp_row_v1)
# TODO: not sure how much work this should do
def ingest_transitfile(transitfile):
with open(transitfile, "r") as f, csv.reader(f, quotechar="'") as log:
fields = ",".join(next(x))
ingester = INGESTERS[fields]
for line in log: # 2
ingester(*line)
def connect_sqlite3():
return sqlite3.connect(os.environ.get("HAG_DB"))
import traceback
if __name__ == "__main__":
DB = connect_sqlite3()
with DB:
# TODO: I think daemon.sh may handle this now, but I'm not certain how it behaves on a bare start. Confirm behavior here with tests and remove this if possible.
DB.execute(create_tables)
try:
# until the heat-death of the universe:
# 1. open the pipe, which blocks for input
# until someone writes to it
# 2. exhaust whatever was written, treating each
# line as a command
# * yes, yes, this may well be a source of errors when commands contain a newline, but let's get it all wired up and working without more fundamental problems before we sweat this *
# 3. exhausting auto-closes the pipe
#
# CAUTION: non-interactive! block-buffered!
# *IF WE WRITE TO STDOUT* we have to flush
# WE DO FOR DEBUG, BUT CAN RM/DISABLE LATER
while True:
with open(HAG_PIPE, "r") as hagpipe: # 1
for line in hagpipe: # 2
print("ingesting", line)
CURRENT_INGESTER(*ast.literal_eval(line))
sys.stdout.flush()
except BaseException as e:
# DEBUG
traceback.print_exc()
print(repr(e))
sys.stdout.flush()
raise
finally:
# anything critical on shutdown?
# flushing just in case there's stray debug
sys.stdout.flush()