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
|
#!/usr/bin/env python3
"""Append one reply to the newOS4 maintenance thread.
Hermes writes here instead of POSTing to agent-replies.php, so an expired admin cookie can
never make a reply look delivered when it was not. Same JSON schema and file the PHP endpoint
serves to the in-app `feedback-inbox` command.
Usage: newos4-reply.py "Triage 2026-10-23: ..." [--author hermes]
"""
import argparse, fcntl, json, os, time
ROOT = '/var/www/webx11.duckdns.org/spaces/newos4'
DIR = os.path.join(ROOT, 'data', 'maintenance')
FILE = os.path.join(DIR, 'replies.json')
MAX = 200
def main():
ap = argparse.ArgumentParser()
ap.add_argument('body')
ap.add_argument('--author', default='hermes', choices=['hermes', 'newos4'])
a = ap.parse_args()
body = a.body.strip()
if not body:
raise SystemExit('refusing to write an empty reply')
body = body[:2000]
os.makedirs(DIR, exist_ok=True)
lock = open(FILE + '.lock', 'a+')
fcntl.flock(lock, fcntl.LOCK_EX)
try:
try:
with open(FILE, encoding='utf-8') as f:
data = json.load(f)
except Exception:
data = []
if not isinstance(data, list):
data = []
entry = {
'id': 'REP-' + time.strftime('%Y%m%d-%H%M%S') + '-' + os.urandom(3).hex(),
'created_at': time.strftime('%Y-%m-%dT%H:%M:%S+00:00', time.gmtime()),
'author': a.author,
'body': body,
}
data.append(entry)
tmp = FILE + '.tmp'
with open(tmp, 'w', encoding='utf-8') as f:
json.dump(data[-MAX:], f, indent=2)
f.flush()
os.fsync(f.fileno())
os.replace(tmp, FILE)
os.chmod(FILE, 0o664)
try:
os.chmod(FILE, 0o664)
except Exception:
pass
print(entry['id'])
finally:
fcntl.flock(lock, fcntl.LOCK_UN)
lock.close()
if __name__ == '__main__':
main()
|