mirror of
https://github.com/migatu/conjurer.git
synced 2026-07-14 21:38:38 +00:00
Generated scripts
This commit is contained in:
Executable
+99
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import json
|
||||
import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# — CONFIGURATION — adjust as needed —
|
||||
APACHE_LOG = '/var/log/apache2/share_access.log' # <- point to your share-specific log!
|
||||
SHARE_DIR = Path('/var/www/html/share')
|
||||
METADATA_FILE = SHARE_DIR / '.downloads.json'
|
||||
STATE_FILE = SHARE_DIR / '.logpos'
|
||||
# Regex to match lines like: "GET /share/abcdef1234... HTTP/1.1"
|
||||
TOKEN_REGEX = re.compile(r'"\s*GET\s+/share/([0-9a-f]{32})\s+HTTP/')
|
||||
|
||||
def debug(msg, args):
|
||||
if args.debug:
|
||||
print(f"[DEBUG] {msg}", file=sys.stderr)
|
||||
|
||||
def load_metadata(args):
|
||||
if METADATA_FILE.exists():
|
||||
data = json.loads(METADATA_FILE.read_text())
|
||||
debug(f"Loaded metadata: {data}", args)
|
||||
return data
|
||||
return {}
|
||||
|
||||
def save_metadata(md, args):
|
||||
METADATA_FILE.write_text(json.dumps(md))
|
||||
debug(f"Saved metadata: {md}", args)
|
||||
|
||||
def load_state(args):
|
||||
if STATE_FILE.exists():
|
||||
pos = int(STATE_FILE.read_text())
|
||||
debug(f"Loaded last log position: {pos}", args)
|
||||
return pos
|
||||
return 0
|
||||
|
||||
def save_state(pos, args):
|
||||
STATE_FILE.write_text(str(pos))
|
||||
debug(f"Saved new log position: {pos}", args)
|
||||
|
||||
def scan_log(since_pos, args):
|
||||
try:
|
||||
with open(APACHE_LOG, 'r') as f:
|
||||
f.seek(since_pos)
|
||||
lines = f.readlines()
|
||||
newpos = f.tell()
|
||||
debug(f"Read {len(lines)} new lines", args)
|
||||
return lines, newpos
|
||||
except Exception as e:
|
||||
print(f"Error reading log: {e}", file=sys.stderr)
|
||||
return [], since_pos
|
||||
|
||||
def record_downloads(lines, metadata, args):
|
||||
now = time.time()
|
||||
for line in lines:
|
||||
m = TOKEN_REGEX.search(line)
|
||||
if m:
|
||||
tok = m.group(1)
|
||||
if tok not in metadata:
|
||||
metadata[tok] = now
|
||||
debug(f"Recorded download of token {tok} at {now}", args)
|
||||
return metadata
|
||||
|
||||
def revoke_old(metadata, args):
|
||||
now = time.time()
|
||||
to_remove = [tok for tok, ts in metadata.items() if now - ts >= 120]
|
||||
for tok in to_remove:
|
||||
link = SHARE_DIR / tok
|
||||
try:
|
||||
link.unlink()
|
||||
debug(f"Unlinked token {tok}", args)
|
||||
except FileNotFoundError:
|
||||
debug(f"Link {link} not found when revoking", args)
|
||||
metadata.pop(tok, None)
|
||||
return bool(to_remove)
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser(description="Revoke share links 2min after download")
|
||||
p.add_argument('--debug', action='store_true', help='print debug info to stderr')
|
||||
args = p.parse_args()
|
||||
|
||||
SHARE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
metadata = load_metadata(args)
|
||||
state = load_state(args)
|
||||
|
||||
lines, newpos = scan_log(state, args)
|
||||
metadata = record_downloads(lines, metadata, args)
|
||||
save_metadata(metadata, args) # always persist new downloads
|
||||
|
||||
if revoke_old(metadata, args):
|
||||
save_metadata(metadata, args) # persist removals
|
||||
|
||||
save_state(newpos, args)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
# Configuration
|
||||
SCAN_PATH = '/mnt/shares'
|
||||
OUTPUT_FILE = '/var/log/share_scan.json'
|
||||
|
||||
def scan_directory(root):
|
||||
"""Recursively walk `root` and collect metadata."""
|
||||
entries = []
|
||||
for dirpath, dirs, files in os.walk(root):
|
||||
for name in dirs + files:
|
||||
full = os.path.join(dirpath, name)
|
||||
try:
|
||||
stat = os.stat(full)
|
||||
entries.append({
|
||||
'path': full,
|
||||
'is_dir': os.path.isdir(full),
|
||||
'size_bytes': stat.st_size,
|
||||
'mtime': time.strftime('%Y-%m-%dT%H:%M:%S%z',
|
||||
time.localtime(stat.st_mtime)),
|
||||
})
|
||||
except Exception as e:
|
||||
# skip items we can't stat
|
||||
continue
|
||||
return entries
|
||||
|
||||
def main():
|
||||
data = {
|
||||
'scanned_at': time.strftime('%Y-%m-%dT%H:%M:%S%z', time.localtime()),
|
||||
'root': SCAN_PATH,
|
||||
'entries': scan_directory(SCAN_PATH),
|
||||
}
|
||||
# Write atomically
|
||||
temp = OUTPUT_FILE + '.tmp'
|
||||
with open(temp, 'w') as f:
|
||||
json.dump(data, f, indent=2)
|
||||
os.replace(temp, OUTPUT_FILE)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,6 @@
|
||||
[Unit]
|
||||
Description=One‑shot scan of /mnt/shares → JSON
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/scan_shares.py
|
||||
@@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=Hourly timer for share‑scanner
|
||||
|
||||
[Timer]
|
||||
# delay 2min after boot, then every hour
|
||||
OnBootSec=20min
|
||||
OnUnitActiveSec=24h
|
||||
Unit=scan_shares.service
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
Executable
+93
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
# CONFIGURATION
|
||||
JSON_DB = '/var/log/share_scan.json'
|
||||
SHARE_DIR = Path('/var/www/html/share')
|
||||
BASE_URL = 'https://czernobog.pl/share'
|
||||
|
||||
# Ensure share directory exists
|
||||
SHARE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def load_db():
|
||||
with open(JSON_DB) as f:
|
||||
return json.load(f)['entries']
|
||||
|
||||
def relevancy(path, keywords):
|
||||
score = 0
|
||||
low = path.lower()
|
||||
for kw in keywords:
|
||||
if kw.lower() in low:
|
||||
score += low.count(kw.lower())
|
||||
return score
|
||||
|
||||
def find_matches(entries, keywords):
|
||||
scored = []
|
||||
for e in entries:
|
||||
score = relevancy(e['path'], keywords)
|
||||
if score > 0:
|
||||
scored.append((score, e['path']))
|
||||
scored.sort(reverse=True, key=lambda x: x[0])
|
||||
return [p for _, p in scored]
|
||||
|
||||
def interactive_select(candidates):
|
||||
print("Search results:")
|
||||
for idx, p in enumerate(candidates, 1):
|
||||
print(f" {idx:3d}. {p}")
|
||||
sel = input("\nSelect files (e.g. 1,3-5): ").strip()
|
||||
nums = set()
|
||||
for part in sel.split(','):
|
||||
if '-' in part:
|
||||
a,b = part.split('-',1)
|
||||
nums.update(range(int(a), int(b)+1))
|
||||
else:
|
||||
nums.add(int(part))
|
||||
return [candidates[i-1] for i in sorted(nums) if 1 <= i <= len(candidates)]
|
||||
|
||||
def publish(paths):
|
||||
urls = []
|
||||
for path in paths:
|
||||
token = uuid.uuid4().hex
|
||||
link = SHARE_DIR / token
|
||||
try:
|
||||
os.symlink(path, link)
|
||||
except FileExistsError:
|
||||
pass
|
||||
urls.append(f"{BASE_URL}/{token}")
|
||||
return urls
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Search and share files from JSON DB")
|
||||
parser.add_argument('keywords', nargs='*',
|
||||
help='Search keywords (interactive if omitted)')
|
||||
parser.add_argument('-n','--count', type=int, default=10,
|
||||
help='How many top results to share')
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.keywords:
|
||||
# Interactive: ask for query
|
||||
q = input("Enter search keywords (space-separated): ").strip().split()
|
||||
args.keywords = q
|
||||
|
||||
entries = load_db()
|
||||
matches = find_matches(entries, args.keywords)
|
||||
top_n = matches[:args.count]
|
||||
|
||||
if sys.stdin.isatty():
|
||||
# interactive selection
|
||||
sel = interactive_select(top_n)
|
||||
else:
|
||||
# non-interactive: share all top_n
|
||||
sel = top_n
|
||||
|
||||
urls = publish(sel)
|
||||
for u in urls:
|
||||
print(u)
|
||||
|
||||
if __name__=='__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user