#!/usr/bin/env python3
import importlib.util,json,os,secrets,subprocess,time
from pathlib import Path
from types import SimpleNamespace
import urllib.request
R=Path(__file__).resolve().parent
spec=importlib.util.spec_from_file_location('driver',R.parent/'devsecops-drone/scripts/lib/e2e_driver.py'); d=importlib.util.module_from_spec(spec); spec.loader.exec_module(d)
def cmd(*args): return subprocess.check_output(list(args),text=True,stderr=subprocess.PIPE).strip()
def pod(*args): return cmd('podman',*args)
engine=['podman','--root',str(R/'engine'),'--runroot','/run/devsecops-local-engine','--storage-driver','vfs']
def eng(*args): return cmd(*engine,*args)
def writeenv(name,values):
 p=R/'config'/name; p.write_text(''.join(f'{k}={v}\n' for k,v in values.items())); p.chmod(0o600); return str(p)
def start(name,image,extra):
 if subprocess.run(['podman','container','exists',name]).returncode==0: pod('start',name)
 else: pod('run','-d','--restart=unless-stopped','--name',name,'--pull=never',*extra,image)
def wait(url):
 for _ in range(60):
  try:
   with urllib.request.urlopen(url,timeout=2) as response:
    if response.status==200:return
  except Exception:pass
  time.sleep(1)
 raise RuntimeError('Service not ready: '+url)
pod('start','devsecops-gitea'); wait('http://localhost:3000/api/healthz')
gip=json.loads(pod('inspect','devsecops-gitea'))[0]['NetworkSettings']['Networks']['podman']['IPAddress']
previous=R/'config/public.json'
if previous.exists() and json.loads(previous.read_text()).get('gitea_ip')!=gip:
 for name in ['devsecops-drone-runner','devsecops-drone-server','devsecops-drone-localhost']:
  if subprocess.run(['podman','container','exists',name]).returncode==0: pod('rm','-f',name)
cred=json.loads((R.parent/'devsecops-gitea/credentials.json').read_text())
a=SimpleNamespace(gitea='http://localhost:3000',drone='http://localhost:8085',user=cred['username'],password=cred['password'])
state=R/'config/state.json'
if state.exists(): s=json.loads(state.read_text())
else:
 app=d.api('POST',a.gitea+'/api/v1/user/applications/oauth2',{'name':'DevSecOps localhost Drone','redirect_uris':[a.drone+'/login'],'confidential_client':True},d.basic(a.user,a.password))
 s={'client_id':app['client_id'],'client_secret':app['client_secret'],'rpc':secrets.token_hex(16),'token':secrets.token_hex(16),'database':secrets.token_hex(16),'cookie':secrets.token_hex(16)}
 state.write_text(json.dumps(s,indent=2));state.chmod(0o600)
# Persistent dedicated engine; never expose the host service engine to CI builds.
unit='''[Unit]
Description=Dedicated local DevSecOps build engine
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/podman --root /work/acb/devsecops-local-runtime/engine --runroot /run/devsecops-local-engine --storage-driver vfs system service --time=0 unix:///run/devsecops-local-engine.sock
Restart=on-failure
[Install]
WantedBy=multi-user.target
'''
Path('/etc/systemd/system/devsecops-local-engine.service').write_text(unit)
cmd('systemctl','daemon-reload');cmd('systemctl','enable','--now','devsecops-local-engine.service')
for tool,version in [('gitleaks','8.30.1'),('syft','1.51.1'),('grype','0.118.0'),('trivy','0.74.0')]:
 tag=f'localhost/devsecops/{tool}:{version}'
 if subprocess.run(engine+['image','exists',tag]).returncode:
  eng('load','-i',str(R.parent/f'my-devsecops/images/{tool}-{version}-linux-amd64.tar'))
 print('Ready image: '+tag,flush=True)
if subprocess.run(engine+['image','exists','docker.io/drone/git:latest']).returncode: eng('load','-i',str(R.parent/'devsecops-drone/images/drone-git-amd64.tar'))
eng('build','--network=none','--pull=never','--layers=false','-t','localhost/devsecops/local-clone:20260912','-f',str(R/'clone/Containerfile'),str(R/'clone'))
if subprocess.run(engine+['network','exists','devsecops-local-ci']).returncode: eng('network','create','--subnet','10.90.240.0/24','devsecops-local-ci')
serverenv=writeenv('server.env',{'DRONE_SERVER_HOST':'localhost:8085','DRONE_SERVER_PROTO':'http','DRONE_SERVER_PORT':':8085','DRONE_SERVER_ADDR':':8085','DRONE_GITEA_SERVER':a.gitea,'DRONE_GITEA_CLIENT_ID':s['client_id'],'DRONE_GITEA_CLIENT_SECRET':s['client_secret'],'DRONE_RPC_SECRET':s['rpc'],'DRONE_DATABASE_SECRET':s['database'],'DRONE_COOKIE_SECRET':s['cookie'],'DRONE_USER_CREATE':f'username:{a.user},admin:true,token:{s["token"]}','DRONE_REGISTRATION_CLOSED':'true','DRONE_DATADOG_ENABLED':'false','DRONE_DATABASE_DRIVER':'sqlite3','DRONE_DATABASE_DATASOURCE':'/data/database.sqlite'})
start('devsecops-drone-server','docker.io/drone/drone:2.24.0',['--network=container:devsecops-gitea','--env-file',serverenv,'-v',str(R/'data')+':/data:Z'])
# The proxy publishes only localhost and bridges the existing Gitea network namespace.
if subprocess.run(['podman','container','exists','devsecops-drone-localhost']).returncode==0: pod('start','devsecops-drone-localhost')
else: pod('run','-d','--restart=unless-stopped','--name','devsecops-drone-localhost','--pull=never','--network=host','-e','GITEA_IP='+gip,'-v',str(R/'proxy.py')+':/proxy.py:ro,Z','--entrypoint','python3','localhost/devsecops/gitleaks:8.30.1','/proxy.py')
wait(a.drone+'/healthz')
renv=writeenv('runner.env',{'DRONE_RPC_PROTO':'http','DRONE_RPC_HOST':'localhost:8085','DRONE_RPC_SECRET':s['rpc'],'DRONE_RUNNER_NAME':'devsecops-local-runner','DRONE_RUNNER_CAPACITY':'1','DRONE_RUNNER_CLONE_IMAGE':'localhost/devsecops/local-clone:20260912','DRONE_RUNNER_ENVIRON':'DEVSECOPS_GITEA_ADDRESS:10.90.240.1','DRONE_RUNNER_NETWORKS':'devsecops-local-ci','DRONE_LIMIT_REPOS':a.user+'/my-app','DOCKER_HOST':'unix:///var/run/docker.sock','DRONE_UI_DISABLE':'true'})
start('devsecops-drone-runner','docker.io/drone/drone-runner-docker:1.8.5',['--network=container:devsecops-gitea','--security-opt','label=disable','--env-file',renv,'-v','/run/devsecops-local-engine.sock:/var/run/docker.sock'])
print('Gitea http://localhost:3000; Drone http://localhost:8085',flush=True)
(R/'config/public.json').write_text(json.dumps({'gitea':a.gitea,'drone':a.drone,'username':a.user,'gitea_ip':gip},indent=2))
