Rename the simulator to Overwatch and organize the repository for presentation.
The operator interface now uses that name. The radar design, parts list, and simulation sit in separate directories, and the earlier simulator and project license are removed. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
82
simulation/scripts/bootstrap_auth.py
Normal file
82
simulation/scripts/bootstrap_auth.py
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate stable local auth secrets. Nothing privileged is emitted to stdout."""
|
||||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import secrets
|
||||
import time
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
AUTH_DIR = ROOT / 'deploy/supabase'
|
||||
|
||||
|
||||
def read_env(path):
|
||||
return dict(line.split('=', 1) for line in path.read_text().splitlines() if line and not line.startswith('#'))
|
||||
|
||||
|
||||
def jwt(secret, role):
|
||||
def encode(value):
|
||||
return base64.urlsafe_b64encode(json.dumps(value, separators=(',', ':')).encode()).rstrip(b'=').decode()
|
||||
now = int(time.time())
|
||||
message = encode({'alg': 'HS256', 'typ': 'JWT'}) + '.' + encode({'role': role, 'iss': 'supabase', 'iat': now, 'exp': now + 10 * 365 * 86400})
|
||||
signature = base64.urlsafe_b64encode(hmac.new(secret.encode(), message.encode(), hashlib.sha256).digest()).rstrip(b'=').decode()
|
||||
return message + '.' + signature
|
||||
|
||||
|
||||
def write_private(path, content):
|
||||
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
||||
with os.fdopen(fd, 'w') as output:
|
||||
output.write(content)
|
||||
path.chmod(0o600)
|
||||
|
||||
|
||||
def main():
|
||||
env_path = AUTH_DIR / '.env'
|
||||
if not env_path.exists():
|
||||
secret = secrets.token_hex(32)
|
||||
demo_path = ROOT / 'DEMO_USER.txt'
|
||||
demo = dict(line.split(': ', 1) for line in demo_path.read_text().splitlines() if ': ' in line) if demo_path.exists() else {}
|
||||
values = {
|
||||
'POSTGRES_PASSWORD': secrets.token_hex(24),
|
||||
'JWT_SECRET': secret,
|
||||
'SUPABASE_ANON_KEY': jwt(secret, 'anon'),
|
||||
'SUPABASE_SERVICE_KEY': jwt(secret, 'service_role'),
|
||||
'SUPABASE_PORT': os.getenv('SUPABASE_PORT', '54321'),
|
||||
'KONG_BIND_ADDRESS': os.getenv('KONG_BIND_ADDRESS', '0.0.0.0'),
|
||||
'SITE_URL': os.getenv('SITE_URL', 'https://defense.naori.cloud'),
|
||||
'DEMO_EMAIL': demo.get('Username', 'demo@defense.naori.cloud'),
|
||||
'DEMO_PASSWORD': demo.get('Password', secrets.token_urlsafe(18)),
|
||||
}
|
||||
write_private(env_path, ''.join(f'{key}={value}\n' for key, value in values.items()))
|
||||
print('Generated auth secrets and demo credentials in deploy/supabase/.env')
|
||||
values = read_env(env_path)
|
||||
config = {
|
||||
'_format_version': '2.1', '_transform': True,
|
||||
'consumers': [
|
||||
{'username': 'anon', 'keyauth_credentials': [{'key': values['SUPABASE_ANON_KEY']}]},
|
||||
{'username': 'service_role', 'keyauth_credentials': [{'key': values['SUPABASE_SERVICE_KEY']}]},
|
||||
],
|
||||
'services': [
|
||||
{'name': name, 'url': url, 'routes': [{'name': name, 'paths': [path], 'strip_path': True}],
|
||||
'plugins': [{'name': 'cors'}, {'name': 'key-auth', 'config': {'hide_credentials': False}}]}
|
||||
for name, url, path in [('auth', 'http://auth:9999/', '/auth/v1/'), ('rest', 'http://rest:3000/', '/rest/v1/')]
|
||||
],
|
||||
}
|
||||
config['services'].extend([
|
||||
{'name': 'simulation', 'url': 'http://host.docker.internal:8000',
|
||||
'routes': [{'name': 'simulation', 'paths': ['/socket.io/'], 'strip_path': False}],
|
||||
'read_timeout': 60000, 'write_timeout': 60000},
|
||||
{'name': 'frontend', 'url': 'http://frontend:80',
|
||||
'routes': [{'name': 'frontend', 'paths': ['/'], 'strip_path': False}]},
|
||||
])
|
||||
# Kong runs as UID 1337; its bind-mounted config must be readable inside the container.
|
||||
write_private(AUTH_DIR / 'kong.json.local', json.dumps(config, indent=2))
|
||||
(AUTH_DIR / 'kong.json.local').chmod(0o644)
|
||||
write_private(ROOT / 'deploy/auth.env.local', f"SUPABASE_URL=http://127.0.0.1:{values['SUPABASE_PORT']}\nSUPABASE_ANON_KEY={values['SUPABASE_ANON_KEY']}\n")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
45
simulation/scripts/create_demo_user.py
Normal file
45
simulation/scripts/create_demo_user.py
Normal file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Provision and verify the demo account without replacing an existing password."""
|
||||
import json
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from bootstrap_auth import AUTH_DIR, read_env
|
||||
|
||||
|
||||
def request(url, key, method='GET', payload=None):
|
||||
headers = {'apikey': key, 'Authorization': f'Bearer {key}', 'Content-Type': 'application/json'}
|
||||
body = json.dumps(payload).encode() if payload is not None else None
|
||||
with urllib.request.urlopen(urllib.request.Request(url, data=body, headers=headers, method=method), timeout=15) as response:
|
||||
return json.load(response)
|
||||
|
||||
|
||||
def main():
|
||||
values = read_env(AUTH_DIR / '.env')
|
||||
url = f"http://127.0.0.1:{values['SUPABASE_PORT']}/auth/v1"
|
||||
credentials = {'email': values['DEMO_EMAIL'], 'password': values['DEMO_PASSWORD']}
|
||||
existing = None
|
||||
page = 1
|
||||
while True:
|
||||
users = request(f'{url}/admin/users?page={page}&per_page=100', values['SUPABASE_SERVICE_KEY']).get('users', [])
|
||||
existing = next((user for user in users if user.get('email') == credentials['email']), None)
|
||||
if existing or len(users) < 100:
|
||||
break
|
||||
page += 1
|
||||
if not existing:
|
||||
request(f'{url}/admin/users', values['SUPABASE_SERVICE_KEY'], 'POST', {
|
||||
**credentials, 'email_confirm': True, 'user_metadata': {'name': 'Demo user'},
|
||||
})
|
||||
print('Created demo account.')
|
||||
session = request(f'{url}/token?grant_type=password', values['SUPABASE_ANON_KEY'], 'POST', credentials)
|
||||
if not session.get('access_token') or not session.get('user', {}).get('email') == credentials['email']:
|
||||
raise RuntimeError('Demo login verification failed')
|
||||
print('Demo password login verified. Credentials are stored in deploy/supabase/.env.')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
main()
|
||||
except urllib.error.HTTPError as error:
|
||||
print(f'Demo account setup failed (HTTP {error.code}).', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user