Compare commits
102
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9e69ac9c1 | ||
|
|
b1c1e49cc5 | ||
|
|
b9e2358901 | ||
|
|
a1874c90ae | ||
|
|
19a2ce8439 | ||
|
|
f8fc7ad73b | ||
|
|
e2b9cc6849 | ||
|
|
4f4c95ffce | ||
|
|
edd3bd239e | ||
|
|
7a1e862a03 | ||
|
|
dc0ac18aea | ||
|
|
9e3dbdf146 | ||
|
|
87b1a5357c | ||
|
|
128a7d9050 | ||
|
|
e8cf0f6348 | ||
|
|
786a3b6250 | ||
|
|
b9a739fd95 | ||
|
|
0fb71cd191 | ||
|
|
3b8e74bed4 | ||
|
|
9df07a5d7d | ||
|
|
54356c0fcc | ||
|
|
4b62b18a39 | ||
|
|
bb69d76a7b | ||
|
|
27eef25d12 | ||
|
|
7b563590b4 | ||
|
|
50bceb1d45 | ||
|
|
f89619f576 | ||
|
|
365ea5d06c | ||
|
|
f15a19d525 | ||
|
|
3bdb5f9d59 | ||
|
|
4a9f3fff1a | ||
|
|
ffbc1e0e01 | ||
|
|
8365afbeb8 | ||
|
|
d34ef72f5d | ||
|
|
7f118fa205 | ||
|
|
97867386a3 | ||
|
|
e17abc12be | ||
|
|
bec612c67d | ||
|
|
19bcb8aeed | ||
|
|
79e00ef089 | ||
|
|
4833b9f93a | ||
|
|
fd715457da | ||
|
|
4325f0b7d2 | ||
|
|
b2a4f67f93 | ||
|
|
c2e4a5b078 | ||
|
|
707cfe4d60 | ||
|
|
cf97cf697d | ||
|
|
82eecc27b4 | ||
|
|
bea425052f | ||
|
|
45c26a1a4a | ||
|
|
6ea5047dbb | ||
|
|
a5fdfbf057 | ||
|
|
8a5ce63069 | ||
|
|
bce254790c | ||
|
|
14a3b84fc6 | ||
|
|
39f23b0891 | ||
|
|
034d5f0faa | ||
|
|
cb6851b751 | ||
|
|
a79010f788 | ||
|
|
95b1d82738 | ||
|
|
afa5c0eb3e | ||
|
|
ca9d7a7424 | ||
|
|
890699efaa | ||
|
|
142f827feb | ||
|
|
32949d9dac | ||
|
|
4c505d2b6a | ||
|
|
d2bcf4d5af | ||
|
|
76b33c0c6a | ||
|
|
9babbcca43 | ||
|
|
c387c83c13 | ||
|
|
a9ef9197e6 | ||
|
|
54870ed375 | ||
|
|
c850858973 | ||
|
|
d08a7043d6 | ||
|
|
c393931f64 | ||
|
|
a62d4ce2c1 | ||
|
|
5a21bd5d7e | ||
|
|
ed92b88fc0 | ||
|
|
a248142158 | ||
|
|
ecf673500b | ||
|
|
43ddb91568 | ||
|
|
0b0505a645 | ||
|
|
96c00d81a9 | ||
|
|
e7d36ad706 | ||
|
|
d6cb9d1529 | ||
|
|
c06a23f4f8 | ||
|
|
568c283aff | ||
|
|
87f9150806 | ||
|
|
14fd6ee5c5 | ||
|
|
b9328d4161 | ||
|
|
d8ae7ee6f6 | ||
|
|
2ae60299b7 | ||
|
|
c6b172dce7 | ||
|
|
d3cfef4147 | ||
|
|
7abf9f7734 | ||
|
|
45dd78ec79 | ||
|
|
29f3c1a327 | ||
|
|
a5232f8f6d | ||
|
|
6798e0fbf5 | ||
|
|
9436945487 | ||
|
|
71cf9ac60d | ||
|
|
0219bcdd86 |
@@ -0,0 +1,300 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
import hashlib, hmac, json, os, re, subprocess, sys, time, urllib.parse, uuid
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
sys.path.insert(0, str(Path(os.getenv('AIOS_GITOPS_WORKSPACE', '/data/aios-gitops/workspace')) / 'runtime' / 'agent-os'))
|
||||
from orchestrator.action_monitor import ActionMonitor
|
||||
|
||||
ROOT = Path(os.getenv('AIOS_GITOPS_ROOT', '/data/aios-gitops'))
|
||||
WS = Path(os.getenv('AIOS_GITOPS_WORKSPACE', str(ROOT / 'workspace')))
|
||||
SECRETS = Path(os.getenv('AIOS_GITOPS_SECRETS_DIR', str(ROOT / 'runtime' / 'secrets')))
|
||||
TOKEN_FILE = Path(os.getenv('AIOS_GITOPS_TOKEN_FILE', str(SECRETS / 'gitops.tokens')))
|
||||
HOST = os.getenv('AIOS_GITOPS_HOST', '127.0.0.1')
|
||||
PORT = int(os.getenv('AIOS_GITOPS_PORT', '18890'))
|
||||
OPENAPI = WS / 'docs' / 'gitops' / 'openapi-chatgpt-actions.yaml'
|
||||
BARE_REPO = Path(os.getenv('AIOS_GITOPS_BARE_REPO', str(ROOT / 'runtime' / 'gitea-bin' / 'data' / 'repositories' / 'aios' / 'aios-v23.git')))
|
||||
monitor = ActionMonitor()
|
||||
|
||||
BASE_MODELS = [
|
||||
{'id':'base.deepseekv4-flash','provider':'one-api','model':'deepseekv4-flash','display_name':'DeepSeek V4 Flash','scope':'global'},
|
||||
{'id':'base.qwen3.6-35b','provider':'one-api','model':'qwen3.6-35b','display_name':'Qwen 3.6 35B','scope':'global'},
|
||||
{'id':'base.gpt-5.5','provider':'openai','model':'gpt-5.5','display_name':'GPT-5.5','scope':'global'},
|
||||
]
|
||||
SECRET_RE = re.compile(r'(?i)(api[_-]?key|secret[_-]?access[_-]?key|secret|password|token)\s*[:=]\s*["\']?([A-Za-z0-9_./+=:-]{16,})')
|
||||
SECRET_VAL_RE = re.compile(r'(sk-[A-Za-z0-9_-]{20,}|HPUA[A-Z0-9]{10,}|SDC[A-Za-z0-9]{20,})')
|
||||
|
||||
def now() -> str: return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
|
||||
def uid(p='id') -> str: return f'{p}-{int(time.time()*1000)}-{uuid.uuid4().hex[:8]}'
|
||||
def safe(s: str, n=160) -> str: return re.sub(r'[^A-Za-z0-9_./@-]+', '-', str(s or 'unknown')).strip('/-')[:n] or 'unknown'
|
||||
def sh(args, check=False):
|
||||
p = subprocess.run(['git'] + args, cwd=str(WS), text=True, capture_output=True)
|
||||
if check and p.returncode != 0: raise RuntimeError(p.stderr or p.stdout or 'git failed')
|
||||
return {'code':p.returncode,'stdout':p.stdout.strip(),'stderr':p.stderr.strip(),'cmd':['git']+args}
|
||||
def jload(p: Path, default):
|
||||
try: return json.loads(p.read_text(encoding='utf-8'))
|
||||
except Exception: return default
|
||||
def relpath(path: str) -> str:
|
||||
x = str(path or '').replace('\\','/').strip()
|
||||
if not x or x.startswith('/') or '..' in Path(x).parts or Path(x).parts[0] == '.git': raise ValueError('invalid path')
|
||||
return str(Path(x))
|
||||
def branch_name(name: str | None, prefix='patch/chatgpt') -> str:
|
||||
b = safe(name or f'{prefix}-{uid("br")}', 180)
|
||||
if b in ('main','master') or b.startswith('main/') or b.startswith('master/'): raise ValueError('direct main/master writes are forbidden')
|
||||
return b
|
||||
def _json_has_plain_secret(obj: Any) -> bool:
|
||||
if isinstance(obj, dict):
|
||||
for k, v in obj.items():
|
||||
lk = str(k).lower()
|
||||
if lk == 'secret_ref' or lk.endswith('_secret_ref'):
|
||||
continue
|
||||
if lk in {'api_key', 'access_key', 'secret_access_key', 'secret_key', 'password', 'token', 'bearer_token'}:
|
||||
if isinstance(v, str) and v and not v.startswith('secret://') and v != 'REDACTED':
|
||||
return True
|
||||
if _json_has_plain_secret(v):
|
||||
return True
|
||||
return False
|
||||
if isinstance(obj, list):
|
||||
return any(_json_has_plain_secret(x) for x in obj)
|
||||
if isinstance(obj, str):
|
||||
return bool(obj and not obj.startswith('secret://') and obj != 'REDACTED' and SECRET_VAL_RE.search(obj))
|
||||
return False
|
||||
|
||||
def content_has_secret(text: str) -> bool:
|
||||
text = text or ''
|
||||
try:
|
||||
return _json_has_plain_secret(json.loads(text))
|
||||
except Exception:
|
||||
pass
|
||||
if SECRET_VAL_RE.search(text):
|
||||
return True
|
||||
for m in SECRET_RE.finditer(text):
|
||||
key = str(m.group(1)).lower()
|
||||
value = str(m.group(2))
|
||||
if 'secret_ref' in key or value.startswith('secret://') or value == 'REDACTED':
|
||||
continue
|
||||
return True
|
||||
return False
|
||||
def token_records():
|
||||
if not TOKEN_FILE.exists(): return []
|
||||
out=[]
|
||||
for raw in TOKEN_FILE.read_text(encoding='utf-8').splitlines():
|
||||
line=raw.strip()
|
||||
if not line or line.startswith('#'): continue
|
||||
if line.startswith('{'):
|
||||
try: out.append(json.loads(line))
|
||||
except Exception: pass
|
||||
else:
|
||||
out.append({'account':'legacy-token','role':'owner','scopes':['*'],'token_plain':line})
|
||||
return out
|
||||
def authenticate(token: str):
|
||||
if os.getenv('AIOS_GITOPS_DEV_ALLOW','0') == '1': return {'account':'dev-owner','role':'owner','scopes':['*']}
|
||||
if not token: return None
|
||||
digest=hashlib.sha256(token.encode()).hexdigest()
|
||||
for r in token_records():
|
||||
if r.get('disabled'): continue
|
||||
if r.get('token_sha256') and hmac.compare_digest(str(r['token_sha256']), digest): return r
|
||||
if r.get('token_plain') and hmac.compare_digest(str(r['token_plain']), token): return r
|
||||
return None
|
||||
def sanitize(obj: Any) -> Any:
|
||||
if isinstance(obj, dict):
|
||||
return {k: ('REDACTED' if re.search(r'(?i)(api[_-]?key|secret|password|token)', str(k)) else sanitize(v)) for k,v in obj.items()}
|
||||
if isinstance(obj, list): return [sanitize(x) for x in obj]
|
||||
if isinstance(obj, str): return SECRET_VAL_RE.sub('REDACTED', obj)
|
||||
return obj
|
||||
def ensure_git():
|
||||
if not sh(['config','user.email'])['stdout']: sh(['config','user.email','chatgpt-gitops-bot@cnnetmkt.top'], True)
|
||||
if not sh(['config','user.name'])['stdout']: sh(['config','user.name','chatgpt-gitops-bot'], True)
|
||||
def checkout(branch, base='main', create=False):
|
||||
ensure_git(); b=branch_name(branch)
|
||||
if create or sh(['rev-parse','--verify',b])['code'] != 0:
|
||||
sh(['checkout',base], True); sh(['checkout','-B',b], True)
|
||||
else: sh(['checkout',b], True)
|
||||
return b
|
||||
def write_file(path, content):
|
||||
if content_has_secret(content): raise ValueError('secret_rejected: use secret_ref; plaintext API keys must not enter Git')
|
||||
r=relpath(path); t=WS/r; t.parent.mkdir(parents=True, exist_ok=True); t.write_text(content, encoding='utf-8'); return r
|
||||
def write_json(path, data): return write_file(path, json.dumps(data, ensure_ascii=False, indent=2) + '\n')
|
||||
def sync_branch(branch: str) -> dict:
|
||||
if not branch or branch in ('main','master') or branch.startswith('main/') or branch.startswith('master/'):
|
||||
return {'status':'skipped','reason':'protected_branch','branch':branch}
|
||||
if BARE_REPO.exists():
|
||||
ref=f'refs/heads/{branch}:refs/heads/{branch}'
|
||||
p=subprocess.run(['git','--git-dir',str(BARE_REPO),'fetch',str(WS),ref], text=True, capture_output=True)
|
||||
if p.returncode != 0: raise RuntimeError(p.stderr or p.stdout or f'git sync failed for {branch}')
|
||||
return {'status':'synced','target':'bare','branch':branch,'stdout':p.stdout.strip(),'stderr':p.stderr.strip()}
|
||||
p=subprocess.run(['git','push','origin',f'HEAD:refs/heads/{branch}'], cwd=str(WS), text=True, capture_output=True)
|
||||
if p.returncode != 0: raise RuntimeError(p.stderr or p.stdout or f'git push failed for {branch}')
|
||||
return {'status':'synced','target':'origin','branch':branch,'stdout':p.stdout.strip(),'stderr':p.stderr.strip()}
|
||||
|
||||
def sync_tag(tag: str) -> dict:
|
||||
if not tag: return {'status':'skipped','reason':'empty_tag'}
|
||||
if BARE_REPO.exists():
|
||||
ref=f'refs/tags/{tag}:refs/tags/{tag}'
|
||||
p=subprocess.run(['git','--git-dir',str(BARE_REPO),'fetch',str(WS),ref], text=True, capture_output=True)
|
||||
if p.returncode != 0: raise RuntimeError(p.stderr or p.stdout or f'git tag sync failed for {tag}')
|
||||
return {'status':'synced','target':'bare','tag':tag,'stdout':p.stdout.strip(),'stderr':p.stderr.strip()}
|
||||
p=subprocess.run(['git','push','origin',tag], cwd=str(WS), text=True, capture_output=True)
|
||||
if p.returncode != 0: raise RuntimeError(p.stderr or p.stdout or f'git tag push failed for {tag}')
|
||||
return {'status':'synced','target':'origin','tag':tag,'stdout':p.stdout.strip(),'stderr':p.stderr.strip()}
|
||||
|
||||
def commit(paths, msg):
|
||||
ensure_git(); paths = [relpath(p) for p in paths] if paths else ['.']; sh(['add'] + paths, True)
|
||||
branch=sh(['rev-parse','--abbrev-ref','HEAD'])['stdout']
|
||||
if not sh(['status','--short'])['stdout']:
|
||||
return {'status':'no_changes','branch':branch,'sync':sync_branch(branch)}
|
||||
out=sh(['commit','-m',msg], True); branch=sh(['rev-parse','--abbrev-ref','HEAD'])['stdout']; sync=sync_branch(branch); return {'status':'committed','branch':branch,'rev':sh(['rev-parse','--short','HEAD'], True)['stdout'],'git':out,'sync':sync}
|
||||
def audit(actor, action, target, payload):
|
||||
p=ROOT/'artifacts'/'chatgpt-gitops-audit.jsonl'; p.parent.mkdir(parents=True, exist_ok=True)
|
||||
with p.open('a', encoding='utf-8') as f: f.write(json.dumps({'at':now(),'actor':actor,'action':action,'target':target,'payload':sanitize(payload)}, ensure_ascii=False)+'\n')
|
||||
def model_docs():
|
||||
out=[]
|
||||
for p in sorted((WS/'config'/'model_registry').glob('**/*.json')): out.append(jload(p, {'path':str(p.relative_to(WS))}))
|
||||
return out
|
||||
def patches():
|
||||
out=[]
|
||||
for p in sorted((WS/'patches').glob('**/*.json')): out.append(jload(p, {'path':str(p.relative_to(WS))}))
|
||||
return out
|
||||
|
||||
class H(BaseHTTPRequestHandler):
|
||||
server_version='AIOSChatGPTGitOps/V1R1C0'
|
||||
def sendj(self, data, code=200):
|
||||
b=json.dumps(data, ensure_ascii=False, indent=2).encode(); self.send_response(code); self.send_header('Content-Type','application/json; charset=utf-8'); self.send_header('Content-Length',str(len(b))); self.end_headers(); self.wfile.write(b)
|
||||
def sendt(self, text, ctype='text/plain; charset=utf-8', code=200):
|
||||
b=text.encode(); self.send_response(code); self.send_header('Content-Type',ctype); self.send_header('Content-Length',str(len(b))); self.end_headers(); self.wfile.write(b)
|
||||
def body(self):
|
||||
n=int(self.headers.get('Content-Length','0') or '0'); raw=self.rfile.read(n).decode() if n else '{}'
|
||||
try: return json.loads(raw or '{}')
|
||||
except Exception: return {'_raw':raw}
|
||||
def actor(self):
|
||||
auth=self.headers.get('Authorization',''); tok=auth[7:].strip() if auth.startswith('Bearer ') else self.headers.get('X-AIOS-Token','')
|
||||
rec=authenticate(tok)
|
||||
if rec: return {'ok':True,'account':safe(rec.get('account') or rec.get('name') or 'token-user'),'role':rec.get('role','user'),'scopes':set(rec.get('scopes') or [])}
|
||||
return {'ok':False,'account':'anonymous','role':'anonymous','scopes':set()}
|
||||
def need(self, scope):
|
||||
a=self.actor()
|
||||
if not a['ok']: self.sendj({'error':'unauthorized','hint':'Bearer token required'},401); return None
|
||||
if '*' not in a['scopes'] and scope not in a['scopes']: self.sendj({'error':'forbidden','required_scope':scope,'account':a['account']},403); return None
|
||||
return a
|
||||
def do_GET(self):
|
||||
u=urllib.parse.urlparse(self.path); path=u.path.rstrip('/') if u.path != '/' else u.path; q=urllib.parse.parse_qs(u.query)
|
||||
try:
|
||||
if path in ('/health','/api/health'): return self.sendj({'status':'ok','service':'chatgpt-gitops-adapter','workspace':str(WS)})
|
||||
if path in ('/api/openapi.yaml','/api/openapi-chatgpt-actions.yaml','/api/schema'):
|
||||
return self.sendt(OPENAPI.read_text(encoding='utf-8'), 'application/yaml; charset=utf-8') if OPENAPI.exists() else self.sendj({'error':'schema_missing'},404)
|
||||
if path in ('/api','/api/status'): return self.sendj(api_spec())
|
||||
if path in ('/api/base','/api/baseline','/base'):
|
||||
data=jload(WS/'baseline'/'current.json', {}); data.setdefault('git', sh(['rev-parse','--short','HEAD'])['stdout']); return self.sendj(data)
|
||||
if path in ('/api/ver','/api/versions','/ver'):
|
||||
return self.sendj({'tags':sh(['tag','--list','V*'])['stdout'].splitlines(),'versions_dir':[p.name for p in (WS/'versions').glob('*')],'head':sh(['rev-parse','--short','HEAD'])['stdout']})
|
||||
if path in ('/api/diff','/diff'):
|
||||
frm=(q.get('from') or ['HEAD~1'])[0]; to=(q.get('to') or ['HEAD'])[0]; return self.sendj({'from':frm,'to':to,'diff':sh(['diff',frm,to])})
|
||||
if path in ('/api/patch','/api/patches','/api/branches','/patch'): return self.sendj({'patches':patches(),'branches':sh(['branch','--format','%(refname:short)'])['stdout'].splitlines()})
|
||||
if path in ('/api/model','/model'): return self.sendj({'base':BASE_MODELS,'custom':model_docs(),'priority':[jload(p,{}) for p in sorted((WS/'config'/'model_priority').glob('*.json'))]})
|
||||
if path in ('/api/run','/api/runtime-status','/run'):
|
||||
svc=subprocess.run(['systemctl','is-active','aios-gitops-model-api'], text=True, capture_output=True)
|
||||
return self.sendj({'service':'aios-gitops-model-api','status':svc.stdout.strip() or svc.stderr.strip(),'workspace_branch':sh(['rev-parse','--abbrev-ref','HEAD'])['stdout']})
|
||||
if path in ('/api/action-monitor','/action-monitor'):
|
||||
latest=monitor.latest()
|
||||
ldict={'event_id':latest.event_id,'action':latest.action,'error':latest.error,'context':latest.context,'created_at':latest.created_at} if latest else None
|
||||
return self.sendj({'events':[{"event_id":e.event_id,"action":e.action,"error":e.error,"context":e.context,"created_at":e.created_at} for e in monitor.list_events()],'latest':ldict})
|
||||
return self.sendj({'error':'not_found','path':path},404)
|
||||
except Exception as e:
|
||||
monitor.record_failure('do_GET', str(e), {'path':path})
|
||||
return self.sendj({'error':str(e)},500)
|
||||
def do_PUT(self):
|
||||
path=urllib.parse.urlparse(self.path).path.rstrip('/'); data=self.body()
|
||||
if path == '/api/model-priority': return self.model_priority(data)
|
||||
return self.sendj({'error':'not_found','path':path},404)
|
||||
def do_POST(self):
|
||||
path=urllib.parse.urlparse(self.path).path.rstrip('/'); data=self.body()
|
||||
try:
|
||||
if path in ('/api/branch','/api/create-branch'): return self.branch(data)
|
||||
if path in ('/api/file','/api/commit-file'): return self.file(data)
|
||||
if path == '/api/commit': return self.commit(data)
|
||||
if path in ('/api/patch','/api/create-patch'): return self.patch(data)
|
||||
if path in ('/api/tag','/api/create-tag'): return self.tag(data)
|
||||
if path == '/api/rollback': return self.rollback(data)
|
||||
if path == '/api/model': return self.model(data)
|
||||
if path == '/api/model-priority': return self.model_priority(data)
|
||||
if path == '/api/nl': return self.nl(data)
|
||||
return self.sendj({'error':'not_found','path':path},404)
|
||||
except Exception as e:
|
||||
monitor.record_failure('do_POST', str(e), {'path':path})
|
||||
return self.sendj({'error':str(e)},500)
|
||||
def branch(self,data):
|
||||
a=self.need('branch:create');
|
||||
if not a: return
|
||||
b=checkout(data.get('branch'), data.get('base','main'), True); meta={'type':'branch','branch':b,'base':data.get('base','main'),'created_by':a['account'],'created_at':now(),'direct_main_push':False,'owner_approval_required':True}
|
||||
r=write_json(f'patches/{safe(b).replace("/","_")}.branch.json', meta); c=commit([r], data.get('message') or f'chore(chatgpt): create {b}'); audit(a['account'],'branch',b,meta); self.sendj({'status':'ok','branch':b,'commit':c})
|
||||
def file(self,data):
|
||||
a=self.need('file:write');
|
||||
if not a: return
|
||||
b=checkout(data.get('branch'), data.get('base','main'), bool(data.get('create_branch'))); r=write_file(data.get('path'), data.get('content','')); c=commit([r], data.get('message') or f'chore(chatgpt): update {r}'); audit(a['account'],'file',r,{'branch':b,'commit':c}); self.sendj({'status':'committed','branch':b,'path':r,'commit':c})
|
||||
def commit(self,data):
|
||||
a=self.need('commit:create');
|
||||
if not a: return
|
||||
b=checkout(data.get('branch') or sh(['rev-parse','--abbrev-ref','HEAD'])['stdout'], data.get('base','main'), False); paths=data.get('paths') or ['.']; paths=[paths] if isinstance(paths,str) else paths; c=commit(paths, data.get('message') or 'chore(chatgpt): commit changes'); audit(a['account'],'commit',b,{'paths':paths,'commit':c}); self.sendj({'status':c['status'],'branch':b,'commit':c})
|
||||
def patch(self,data):
|
||||
a=self.need('patch:create');
|
||||
if not a: return
|
||||
name=data.get('patch_name') or data.get('name') or uid('patch'); b=checkout(data.get('branch') or f'patch/{safe(name)}', data.get('base','main'), True); meta={'id':uid('patch'),'patch_name':name,'branch':b,'risk':data.get('risk','medium'),'description':data.get('description',''),'created_by':a['account'],'created_at':now()}
|
||||
r=write_json(f'patches/{safe(name)}.json', meta); c=commit([r], data.get('message') or f'chore(chatgpt): create patch {name}'); audit(a['account'],'patch',name,meta); self.sendj({'status':'patch_branch_created','patch':meta,'commit':c})
|
||||
def tag(self,data):
|
||||
a=self.need('tag:create');
|
||||
if not a: return
|
||||
ver=data.get('version') or data.get('tag')
|
||||
if not re.match(r'^V\d+R\d+C\d+(-[A-Za-z0-9_.-]+)?$', ver or ''): return self.sendj({'error':'version must match VxRxCx'},400)
|
||||
if data.get('confirm') != True:
|
||||
self.sendj({'status':'confirm_required','operation':'tag','hint':'This operation creates a Git tag. Confirm to proceed.','x-openai-isConsequential':True}, 428)
|
||||
return
|
||||
if a['role'] != 'owner' or not data.get('execute'):
|
||||
b=checkout(data.get('branch') or f'request/tag-{safe(ver)}', 'main', True); req={'version':ver,'requested_by':a['account'],'approval_required':True,'created_at':now()}; r=write_json(f'baseline/tag_requests/{safe(ver)}.json', req); c=commit([r], f'chore(chatgpt): request tag {ver}'); return self.sendj({'status':'approval_required','branch':b,'commit':c})
|
||||
out=sh(['tag','-a',ver,'-m',data.get('message') or f'release {ver}'], True); self.sendj({'status':'tagged','version':ver,'git':out,'sync':sync_tag(ver)})
|
||||
def rollback(self,data):
|
||||
a=self.need('rollback:request');
|
||||
if not a: return
|
||||
target=data.get('target_version') or data.get('version') or data.get('target')
|
||||
if not target: return self.sendj({'error':'target_version required'},400)
|
||||
b=checkout(data.get('branch') or f'request/rollback-{safe(target)}-{uid("rb")}', 'main', True); req={'id':uid('rollback'),'target_version':target,'reason':data.get('reason',''),'requested_by':a['account'],'approval_required':True,'execute':False,'created_at':now()}; r=write_json(f'baseline/rollback_requests/{safe(req["id"])}.json', req); c=commit([r], f'chore(chatgpt): request rollback {target}'); self.sendj({'status':'rollback_request_committed','branch':b,'request':req,'commit':c})
|
||||
def model(self,data):
|
||||
a=self.need('model:write');
|
||||
if not a: return
|
||||
name=data.get('model_name') or data.get('model'); base=(data.get('base_url') or '').rstrip('/'); prov=data.get('provider_name') or 'custom-provider'
|
||||
if not name or not base: return self.sendj({'error':'provider_name/base_url/model_name required'},400)
|
||||
owner=safe(data.get('username') or a['account']); secret_ref=data.get('api_key_secret_ref') or data.get('secret_ref')
|
||||
if data.get('api_key'):
|
||||
d=SECRETS/'llm'/owner; d.mkdir(parents=True, exist_ok=True); sid=uid('llmkey'); p=d/f'{sid}.secret'; p.write_text(str(data['api_key']), encoding='utf-8'); os.chmod(p,0o600); secret_ref=f'secret://llm/{owner}/{sid}'
|
||||
if not secret_ref: return self.sendj({'error':'api_key_secret_ref or api_key required'},400)
|
||||
mid=data.get('model_id') or f'custom.{owner}.{hashlib.sha1((prov+base+name).encode()).hexdigest()[:10]}'; b=checkout(data.get('branch') or f'patch/model-{safe(mid)}','main',True)
|
||||
doc=sanitize({k:v for k,v in data.items() if k!='api_key'}); doc.update({'model_id':mid,'provider_name':prov,'base_url':base,'model_name':name,'api_key_secret_ref':secret_ref,'created_by':a['account'],'created_at':now()})
|
||||
if data.get('confirm') != True:
|
||||
self.sendj({'status':'confirm_required','operation':'model','hint':'This operation registers a new model. Confirm to proceed.','x-openai-isConsequential':True}, 428)
|
||||
return
|
||||
r=write_json(f'config/model_registry/chatgpt/{safe(mid)}.json', doc); c=commit([r], data.get('message') or f'chore(chatgpt): register model {name}'); audit(a['account'],'model',mid,doc); self.sendj({'status':'model_registered','branch':b,'model_id':mid,'secret_ref':secret_ref,'commit':c})
|
||||
def model_priority(self,data):
|
||||
a=self.need('model:priority');
|
||||
if not a: return
|
||||
if data.get('confirm') != True:
|
||||
self.sendj({'status':'confirm_required','operation':'model-priority','hint':'This operation updates model priority. Confirm to proceed.','x-openai-isConsequential':True}, 428)
|
||||
return
|
||||
user=safe(data.get('username') or a['account']); b=checkout(data.get('branch') or f'patch/model-priority-{user}', 'main', True); doc={'username':user,'items':data.get('items') or data.get('priority') or [],'updated_by':a['account'],'updated_at':now()}; r=write_json(f'config/model_priority/{user}.json', doc); c=commit([r], data.get('message') or f'chore(chatgpt): update model priority {user}'); audit(a['account'],'model-priority',user,doc); self.sendj({'status':'updated','branch':b,'priority':doc,'commit':c})
|
||||
def nl(self,data):
|
||||
a=self.need('nl:plan');
|
||||
if not a: return
|
||||
text=data.get('text',''); low=text.lower(); intent='unknown'; plan=[]
|
||||
if 'rollback' in low or '回滚' in text: intent='rollback_request'; plan=['commit rollback request','wait owner/admin approval']
|
||||
elif 'diff' in low or '对比' in text: intent='diff'; plan=['call GET /api/diff']
|
||||
elif 'model' in low or '模型' in text: intent='model_change'; plan=['use POST /api/model or PUT /api/model-priority','commit metadata']
|
||||
elif 'patch' in low or '补丁' in text: intent='patch_create'; plan=['call POST /api/patch']
|
||||
b=checkout(data.get('branch') or f'patch/nl-{uid("nl")}', 'main', True); doc={'id':uid('nl'),'text':text,'intent':intent,'action_plan':plan,'execute':False,'owner_approval_required':True,'created_by':a['account'],'created_at':now()}; r=write_json(f'patches/nl/{safe(doc["id"])}.json', doc); c=commit([r], f'chore(chatgpt): record nl plan {intent}'); self.sendj({'status':'planned','branch':b,'request':doc,'commit':c})
|
||||
|
||||
def api_spec():
|
||||
return {'version':'V1R1C0-chatgpt-actions','openapi':'/api/openapi.yaml','read':['GET /api/base','GET /api/ver','GET /api/diff','GET /api/patch','GET /api/model','GET /api/run'],'write':['POST /api/branch','POST /api/file','POST /api/commit','POST /api/patch','POST /api/tag','POST /api/rollback','POST /api/model','PUT /api/model-priority','POST /api/nl'],'policy':{'bearer_required_for_write':True,'direct_main_push':False,'delete_tag':False,'owner_approval_required_for_v_level':True,'api_keys_in_git':False,'git_secret_field':'secret_ref','online_git_sync':True}}
|
||||
|
||||
def main():
|
||||
print(f'AIOS ChatGPT GitOps adapter listening on {HOST}:{PORT}')
|
||||
ThreadingHTTPServer((HOST,PORT), H).serve_forever()
|
||||
if __name__ == '__main__': main()
|
||||
@@ -51,7 +51,7 @@ def seed_base_models():
|
||||
pid = 'provider.openai' if m['provider']=='openai' else 'provider.one-api'
|
||||
exec_sql('''INSERT OR IGNORE INTO llm_models(id,provider_id,owner_username,model_name,display_name,context_window,input_modalities,output_modalities,supports_tools,supports_vision,supports_reasoning,cost_tier,is_base,scope,status,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', (m['id'], pid, 'system', m['model'], m['display'], m['context'], '["text"]', '["text"]', m['tools'], m['vision'], m['reasoning'], m['tier'], 1, 'global', 'active', now(), now()))
|
||||
if not one('SELECT * FROM gitops_baseline WHERE id=?', ('current',)):
|
||||
exec_sql('INSERT INTO gitops_baseline(id,active_version,active_tag,source_package,entry,update_mode,status,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?)', ('current','V1R1C0','V1R1C0','AIOS_V1R1C0_MULTIUSER_GITOPS_CUSTOM_LLM_PROD_FULL_DEPLOY_20260706',ENTRY,'git_patch_only','active',now(),now()))
|
||||
exec_sql('INSERT INTO gitops_baseline(id,active_version,active_tag,source_package,entry,update_mode,status,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?)', ('current','V1R1C0','V1R1C0-baseline-20260706','AIOS_V1R1C0_MULTIUSER_GITOPS_CUSTOM_LLM_PROD_FULL_DEPLOY_20260706.zip',ENTRY,'git_patch_only','active',now(),now()))
|
||||
if not one('SELECT * FROM gitops_versions WHERE version=?', ('V1R1C0',)):
|
||||
exec_sql('INSERT INTO gitops_versions(version,tag,level,summary,status,created_by,created_at) VALUES(?,?,?,?,?,?,?)', ('V1R1C0','V1R1C0','R','MultiUser runtime selector + GitOps + custom LLM priority registry','ready','system',now()))
|
||||
|
||||
@@ -127,6 +127,8 @@ class Handler(BaseHTTPRequestHandler):
|
||||
body = '<h2>Base Models</h2>'+self.table([m for m in model_visible_to(username) if int(m.get('is_base') or 0)==1]); body += '<h2>Custom Models</h2>'+self.table([m for m in model_visible_to(username) if int(m.get('is_base') or 0)==0]); body += '<h2>Priority</h2>'+self.table(priority_for(username)); self.send_html('Model Registry', body); return
|
||||
if path in ('/api/runtime/status','/run'):
|
||||
data = rows('SELECT * FROM runtime_instances ORDER BY created_at DESC LIMIT 100'); self.send_html('Runtime', self.table(data)) if path == '/run' else self.send_json(data); return
|
||||
if path in ('/hook','/api/hook'):
|
||||
self.send_html('Git Webhook', '<pre>'+esc(json.dumps({'status':'ready','endpoint':'/hook/git','version':'V1R1C0'}, ensure_ascii=False, indent=2))+'</pre>'); return
|
||||
if path == '/api/audit': self.send_json(rows('SELECT * FROM model_audit_logs ORDER BY created_at DESC LIMIT 200')); return
|
||||
self.send_json({'error':'not_found','path':path},404)
|
||||
except Exception as e: self.send_json({'error':str(e)},500)
|
||||
@@ -236,6 +238,22 @@ class Handler(BaseHTTPRequestHandler):
|
||||
elif '模型' in text and ('添加' in text or '新增' in text or 'add' in text.lower()): intent='add_custom_model'; plan=['validate provider','store api key as secret_ref','call /api/model/custom/add']
|
||||
elif 'patch' in text.lower() or '补丁' in text: intent='create_patch'; plan=['create patch branch','commit patch metadata','run tests']
|
||||
self.send_json({'intent':intent,'action_plan':plan,'execute':False,'safety_check':'V-level changes require owner approval'}); return
|
||||
if path in ('/hook/git','/api/hook/git'):
|
||||
event_id = uid('hook')
|
||||
event_file = ROOT / 'artifacts' / 'git-webhook-events.jsonl'
|
||||
event_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
event = {
|
||||
'id': event_id,
|
||||
'received_at': now(),
|
||||
'event': self.headers.get('X-Gitea-Event') or self.headers.get('X-GitHub-Event') or data.get('event') or 'unknown',
|
||||
'ref': data.get('ref'),
|
||||
'repository': (data.get('repository') or {}).get('full_name') if isinstance(data.get('repository'), dict) else data.get('repository'),
|
||||
}
|
||||
with event_file.open('a', encoding='utf-8') as fh:
|
||||
fh.write(json.dumps(event, ensure_ascii=False) + '\n')
|
||||
smoke = subprocess.run(['bash','scripts/package_contract_check_v1r1c0.sh'], cwd=str(WORKSPACE), text=True, capture_output=True, timeout=120)
|
||||
audit('webhook','git_event',event_id,{'event':event,'smoke_code':smoke.returncode})
|
||||
self.send_json({'status':'accepted' if smoke.returncode == 0 else 'smoke_failed','id':event_id,'event':event,'smoke':{'code':smoke.returncode,'stdout':smoke.stdout[-2000:],'stderr':smoke.stderr[-2000:]}}); return
|
||||
self.send_json({'error':'not_found','path':path},404)
|
||||
except Exception as e: self.send_json({'error':str(e)},500)
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Agent Instruction Center
|
||||
|
||||
Version-controlled execution instructions for AIOS agents.
|
||||
|
||||
## Supported agents
|
||||
|
||||
- codex
|
||||
- openclaw
|
||||
- hermes
|
||||
- windows_codex_app
|
||||
- custom_gpt_review
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Create versioned instruction file.
|
||||
2. Target agent executes instruction.
|
||||
3. Result is recorded with GitOps audit information.
|
||||
|
||||
## Naming
|
||||
|
||||
agent-instructions/<agent>/<instruction-name>-v<version>.md
|
||||
|
||||
## Rules
|
||||
|
||||
- GitOps managed only.
|
||||
- Patch-first workflow.
|
||||
- No secrets, tokens, passwords or credentials.
|
||||
- Production changes require approval.
|
||||
- Main branch changes require approval.
|
||||
@@ -0,0 +1,34 @@
|
||||
# Agent Execution Instruction
|
||||
|
||||
Instruction ID:
|
||||
Version:
|
||||
Target Agent:
|
||||
|
||||
## Objective
|
||||
|
||||
|
||||
## Context
|
||||
|
||||
|
||||
## Execution Steps
|
||||
|
||||
|
||||
## Constraints
|
||||
|
||||
- GitOps only
|
||||
- Patch-first
|
||||
- No secrets
|
||||
- No force push
|
||||
- Production changes require approval
|
||||
|
||||
## Validation
|
||||
|
||||
|
||||
## Return Format
|
||||
|
||||
Status:
|
||||
Branch:
|
||||
Commit:
|
||||
Rev:
|
||||
Sync Status:
|
||||
Issues:
|
||||
@@ -0,0 +1 @@
|
||||
# Placeholder for versioned Codex instructions.
|
||||
@@ -0,0 +1,65 @@
|
||||
# Codex Fix Patches Artifact Lifecycle Instruction
|
||||
|
||||
Instruction ID: fix-patches-artifact-lifecycle-v1
|
||||
Version: v1
|
||||
Target Agent: codex
|
||||
|
||||
## Objective
|
||||
|
||||
Manage the lifecycle of artifacts in the `patches/` directory — specifically, clean up stale or committed artifacts (`.branch.json`, `nl/*.json`, standalone `.json` patch files) that are no longer needed.
|
||||
|
||||
## Background
|
||||
|
||||
The ChatGPT Actions API writes several types of artifacts into `patches/`:
|
||||
|
||||
1. **Branch metadata** (`patches/patch_*.branch.json`) — created when the `branch()` action writes ahead. These are summary records of created branches.
|
||||
2. **Patch files** (`patches/*.json`, top level) — created when the `patch()` action saves a JSON patch proposal. Example: `patches/self-evolution-agent-orchestration-policy.json`.
|
||||
3. **Natural language plan files** (`patches/nl/nl-*.json`) — created when `nl()` action records a natural-language plan. These are transient planning artifacts.
|
||||
|
||||
None of these files represent durable long-term state. Once committed, they are snapshots of ephemeral decisions. The `patches/` directory should not grow unboundedly with stale artifacts.
|
||||
|
||||
## Preconditions
|
||||
|
||||
- GitOps authentication healthy.
|
||||
- Current working branch should not be diverging from main unnecessarily for this cleanup.
|
||||
- Run from the workspace root.
|
||||
|
||||
## Steps
|
||||
|
||||
### Phase 1: Assess Current State
|
||||
|
||||
1. List all files under `patches/`.
|
||||
2. For each file, check its git log (`git log --oneline patches/<file>`) to understand its purpose and age.
|
||||
3. Identify which files are:
|
||||
- Stale `.branch.json` files describing branches that have already been merged.
|
||||
- Stale `nl/` plan files that were experimental, incomplete, or superseded.
|
||||
- Patch files that are still relevant for the current self-evolution state.
|
||||
|
||||
### Phase 2: Clean Up Stale Artifacts
|
||||
|
||||
For each stale artifact file:
|
||||
1. Remove it with `git rm`.
|
||||
2. Create a single commit message describing the batch cleanup, e.g.:
|
||||
`chore(patches): remove stale artifact files [nl/*.json|branch.json]`
|
||||
|
||||
### Phase 3: Housekeeping (Optional)
|
||||
|
||||
If the file `self-evolution-agent-orchestration-policy.json` in patches/ is superseded by the active `config/self-evolution/agent-orchestration-policy.json`, evaluate whether it should also be removed or kept as reference. Document this decision.
|
||||
|
||||
## Constraints
|
||||
|
||||
- No force push.
|
||||
- No direct production publish.
|
||||
- No secret output.
|
||||
- Preserve audit trail.
|
||||
- Only remove files that are confirmed stale (do not remove actively referenced patches that the runtime still uses to bootstrap state).
|
||||
|
||||
## Return Format
|
||||
|
||||
Status:
|
||||
Workspace:
|
||||
Branch:
|
||||
Files Removed:
|
||||
Files Kept:
|
||||
Commit:
|
||||
Issues:
|
||||
@@ -0,0 +1,66 @@
|
||||
# Codex Execution Instruction
|
||||
|
||||
Instruction ID: fix-reports-permission-v1
|
||||
Version: v1
|
||||
Target Agent: codex
|
||||
|
||||
## Objective
|
||||
Fix the GitOps workspace reports directory permission issue so the Agent OS acceptance report can be committed.
|
||||
|
||||
## Context
|
||||
Current blocked path:
|
||||
|
||||
/data/aios-gitops/workspace/reports/
|
||||
|
||||
Error:
|
||||
|
||||
Permission denied
|
||||
|
||||
## Execution Steps
|
||||
|
||||
1. Enter workspace:
|
||||
|
||||
cd /data/aios-gitops/workspace
|
||||
|
||||
2. Check current user and permissions:
|
||||
|
||||
whoami
|
||||
id
|
||||
ls -ld reports
|
||||
|
||||
3. Create directory if missing:
|
||||
|
||||
mkdir -p reports
|
||||
|
||||
4. Fix ownership and write permission for this directory only:
|
||||
|
||||
chown -R <gitops-user>:<gitops-group> reports
|
||||
chmod -R u+rwX reports
|
||||
|
||||
5. Verify write access:
|
||||
|
||||
touch reports/.write-test
|
||||
rm reports/.write-test
|
||||
|
||||
6. Commit pending acceptance report changes after verification.
|
||||
|
||||
## Constraints
|
||||
|
||||
- GitOps only.
|
||||
- No force push.
|
||||
- No chmod 777.
|
||||
- Do not modify unrelated directories.
|
||||
- Do not write secrets.
|
||||
- Production changes require approval.
|
||||
|
||||
## Validation
|
||||
|
||||
Return:
|
||||
|
||||
Status:
|
||||
Branch:
|
||||
Commit:
|
||||
Rev:
|
||||
Sync Status:
|
||||
Permission Result:
|
||||
Issues:
|
||||
@@ -0,0 +1,31 @@
|
||||
# Codex System Recovery Instruction
|
||||
|
||||
Version: v1
|
||||
Target Agent: codex
|
||||
|
||||
## Objective
|
||||
Recover AIOS system issues requiring deep diagnosis or administrator assistance.
|
||||
|
||||
## Execution
|
||||
1. Inspect runtime status.
|
||||
2. Inspect GitOps state.
|
||||
3. Identify root cause.
|
||||
4. Propose safe patch.
|
||||
5. Apply only approved GitOps changes.
|
||||
|
||||
## Escalation
|
||||
Use when server agents cannot resolve issues.
|
||||
|
||||
## Constraints
|
||||
- GitOps only.
|
||||
- No secrets.
|
||||
- No force push.
|
||||
- Production changes require approval.
|
||||
|
||||
## Return
|
||||
Status:
|
||||
Branch:
|
||||
Commit:
|
||||
Rev:
|
||||
Sync Status:
|
||||
Issues:
|
||||
@@ -0,0 +1,48 @@
|
||||
# Codex Main Merge Verification Instruction
|
||||
|
||||
Instruction ID: v1r1c5-main-merge-verify-v1
|
||||
Version: v1
|
||||
Target Agent: codex
|
||||
|
||||
## Objective
|
||||
Verify whether V1R1C5 patch branch has been merged into main.
|
||||
|
||||
## Preconditions
|
||||
|
||||
Ensure GitOps authentication is healthy.
|
||||
If Gitea account password state blocks access, resolve account activation through approved admin procedure before continuing.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Check repository status.
|
||||
2. Fetch remote branches.
|
||||
3. Compare:
|
||||
|
||||
source:
|
||||
patch/self-evolution-agent-orchestration-policy
|
||||
|
||||
target:
|
||||
main
|
||||
|
||||
4. Confirm merge commit.
|
||||
5. Confirm main HEAD.
|
||||
6. Confirm no divergence.
|
||||
|
||||
## Constraints
|
||||
|
||||
- No force push.
|
||||
- No direct production publish.
|
||||
- No secret output.
|
||||
- Preserve audit trail.
|
||||
|
||||
## Return Format
|
||||
|
||||
Status:
|
||||
Source Branch:
|
||||
Target Branch:
|
||||
Merge Commit:
|
||||
Rev:
|
||||
Sync Status:
|
||||
Conflicts:
|
||||
Main HEAD:
|
||||
Issues:
|
||||
@@ -0,0 +1,59 @@
|
||||
# Codex V1R2C2 Branch Sync Validation Instruction
|
||||
|
||||
Instruction ID: v1r2c2-branch-sync-validation-v1
|
||||
Version: v1
|
||||
Target Agent: codex
|
||||
|
||||
## Objective
|
||||
|
||||
Validate synchronization state of all git branches between local, origin (Gitea), and gitops remote. Identify divergent branches that need reconciliation.
|
||||
|
||||
## Preconditions
|
||||
|
||||
- GitOps authentication healthy.
|
||||
- Run from workspace root (`/data/aios-gitops/workspace`).
|
||||
|
||||
## Validation Scope
|
||||
|
||||
1. **main branch sync**: local main == origin/main == gitops/main
|
||||
2. **patch branch sync**: for each local branch, compare HEAD against origin and gitops remotes
|
||||
3. **Current branch**: verify the active working branch is on the correct base
|
||||
4. **Untracked files**: check for files that should be committed vs ignored
|
||||
|
||||
## Steps
|
||||
|
||||
1. Fetch all remotes.
|
||||
2. Capture local branch HEAD hashes.
|
||||
3. Capture remote origin branch HEAD hashes.
|
||||
4. Capture remote gitops branch HEAD hashes.
|
||||
5. For each branch, report:
|
||||
- Fully synced: local == origin == gitops
|
||||
- Divergent: local != origin or local != gitops
|
||||
- Missing remote: branch exists locally but not in remote
|
||||
6. Check if current branch is based on main or is diverging.
|
||||
7. List untracked files that may need governance.
|
||||
|
||||
## Return Format
|
||||
|
||||
```
|
||||
Status: <synced|divergence_found>
|
||||
Workspace: /data/aios-gitops/workspace
|
||||
Main Sync: <yes|no>
|
||||
Main HEAD: <sha>
|
||||
Origin/main HEAD: <sha>
|
||||
Gitops/main HEAD: <sha>
|
||||
Divergent Branches:
|
||||
- <branch name>: <local sha> vs <remote sha> [origin|gitops] (<reason>)
|
||||
Missing Remote Branches:
|
||||
- <branch name>: <local sha>
|
||||
Untracked File Count: <count>
|
||||
Issues:
|
||||
- <issue description>
|
||||
```
|
||||
|
||||
## Constraints
|
||||
|
||||
- No force push.
|
||||
- No destructive actions.
|
||||
- No secret output.
|
||||
- Report only; do not auto-resolve discrepancies.
|
||||
@@ -0,0 +1 @@
|
||||
# Placeholder for versioned custom GPT review instructions.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Custom GPT Solution Review Instruction
|
||||
|
||||
Version: v1
|
||||
Target Agent: custom_gpt_review
|
||||
|
||||
## Objective
|
||||
Review difficult architecture decisions and implementation proposals from server agents.
|
||||
|
||||
## Role
|
||||
Act as reviewer and advisor for OpenClaw, Hermes and other AIOS agents.
|
||||
|
||||
## Execution
|
||||
1. Receive complete problem context.
|
||||
2. Review baseline, branch and proposed changes.
|
||||
3. Analyze alternative solutions.
|
||||
4. Recommend implementation approach.
|
||||
5. Identify risks and validation requirements.
|
||||
|
||||
## Constraints
|
||||
- Advisory role only.
|
||||
- No direct production changes.
|
||||
- No secrets.
|
||||
- GitOps workflow required.
|
||||
- Release requires approval.
|
||||
|
||||
## Return
|
||||
Status:
|
||||
Recommendation:
|
||||
Risk:
|
||||
Validation:
|
||||
Next Step:
|
||||
@@ -0,0 +1 @@
|
||||
# Placeholder for versioned Hermes instructions.
|
||||
@@ -0,0 +1,32 @@
|
||||
# Hermes Architecture Review Instruction
|
||||
|
||||
Version: v1
|
||||
Target Agent: hermes
|
||||
|
||||
## Objective
|
||||
Review AIOS architecture, policies and implementation plans.
|
||||
|
||||
## Execution
|
||||
1. Review baseline and branch diff.
|
||||
2. Analyze architecture impact.
|
||||
3. Review OpenClaw execution proposals.
|
||||
4. Validate acceptance criteria.
|
||||
5. Provide improvement recommendations.
|
||||
|
||||
## Escalation
|
||||
For unresolved architecture choices, prepare complete context for external review.
|
||||
|
||||
## Constraints
|
||||
- GitOps only.
|
||||
- No secrets.
|
||||
- No direct production changes.
|
||||
- Approval required for release.
|
||||
|
||||
## Return
|
||||
Status:
|
||||
Branch:
|
||||
Commit:
|
||||
Rev:
|
||||
Sync Status:
|
||||
Issues:
|
||||
Recommendations:
|
||||
@@ -0,0 +1 @@
|
||||
# Placeholder for versioned OpenClaw instructions.
|
||||
@@ -0,0 +1,24 @@
|
||||
# OpenClaw Agent Instructions Index
|
||||
|
||||
## Available Instructions
|
||||
|
||||
### v1r1c5-hermes-coordination-v1
|
||||
|
||||
Instruction file:
|
||||
|
||||
agent-instructions/openclaw/v1r1c5-hermes-coordination-v1.md
|
||||
|
||||
Purpose:
|
||||
|
||||
Coordinate Hermes architecture review through OpenClaw controlled communication channel.
|
||||
|
||||
Invocation:
|
||||
|
||||
Execute openclaw/v1r1c5-hermes-coordination-v1 instruction
|
||||
|
||||
Requirements:
|
||||
|
||||
- Read the referenced markdown file from the GitOps workspace.
|
||||
- Do not treat the identifier as a local shell command.
|
||||
- Do not search external skill marketplaces unless the file is missing.
|
||||
- Follow GitOps workflow rules defined in the instruction.
|
||||
@@ -0,0 +1,32 @@
|
||||
# OpenClaw Runtime Maintenance Instruction
|
||||
|
||||
Version: v1
|
||||
Target Agent: openclaw
|
||||
|
||||
## Objective
|
||||
Maintain server-side AIOS runtime and perform safe optimization tasks.
|
||||
|
||||
## Execution
|
||||
1. Check runtime health.
|
||||
2. Review logs and GitOps status.
|
||||
3. Identify optimization opportunities.
|
||||
4. Create patch proposals.
|
||||
5. Execute approved changes.
|
||||
|
||||
## Escalation
|
||||
When solution selection or implementation is uncertain, prepare full context and escalate for review.
|
||||
|
||||
## Constraints
|
||||
- GitOps only.
|
||||
- Patch-first workflow.
|
||||
- No secrets.
|
||||
- No force push.
|
||||
- Production release requires approval.
|
||||
|
||||
## Return
|
||||
Status:
|
||||
Branch:
|
||||
Commit:
|
||||
Rev:
|
||||
Sync Status:
|
||||
Issues:
|
||||
@@ -0,0 +1,51 @@
|
||||
# OpenClaw Execution Instruction: Hermes Coordination
|
||||
|
||||
Instruction ID: v1r1c5-hermes-coordination-v1
|
||||
Version: v1
|
||||
Target Agent: openclaw
|
||||
|
||||
## Objective
|
||||
|
||||
Coordinate Hermes architecture review through OpenClaw-controlled communication channel.
|
||||
|
||||
## Architecture Rule
|
||||
|
||||
Hermes is not directly connected to external control channels.
|
||||
All Hermes requests and responses must be routed through OpenClaw personal WeChat channel.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Receive AIOS task or review request.
|
||||
2. Send required context to Hermes through OpenClaw WeChat.
|
||||
3. Collect Hermes architecture analysis.
|
||||
4. Record review result in GitOps reports.
|
||||
5. Continue execution or escalate when required.
|
||||
|
||||
## Context Package
|
||||
|
||||
Include:
|
||||
|
||||
- Baseline version
|
||||
- Branch
|
||||
- Diff summary
|
||||
- Problem description
|
||||
- Proposed solution
|
||||
- Validation requirements
|
||||
|
||||
## Constraints
|
||||
|
||||
- GitOps only.
|
||||
- No secrets.
|
||||
- No direct production changes.
|
||||
- Maintain audit records.
|
||||
- Use escalation flow for unresolved decisions.
|
||||
|
||||
## Return Format
|
||||
|
||||
Status:
|
||||
Branch:
|
||||
Commit:
|
||||
Rev:
|
||||
Sync Status:
|
||||
Hermes Result:
|
||||
Issues:
|
||||
@@ -0,0 +1,119 @@
|
||||
# OpenClaw Execution Instruction
|
||||
|
||||
Instruction ID: v1r1c5-hermes-review-improvement-v1
|
||||
Version: v1
|
||||
Target Agent: openclaw
|
||||
|
||||
## Objective
|
||||
|
||||
Implement V1R1C5 improvements based on Hermes Conditional Approval review.
|
||||
|
||||
## Context
|
||||
|
||||
Baseline:
|
||||
V1R1C4
|
||||
|
||||
Branch:
|
||||
patch/self-evolution-agent-orchestration-policy
|
||||
|
||||
Proposal:
|
||||
V1R1C5-agent-os-evolution-proposal
|
||||
|
||||
## Required Changes
|
||||
|
||||
1. Add Agent Resource Governance:
|
||||
|
||||
Create:
|
||||
config/self-evolution/agent-resource-manager.json
|
||||
|
||||
Support:
|
||||
- resource limits
|
||||
- parallel task limits
|
||||
- agent priority
|
||||
- runtime isolation
|
||||
- overload handling
|
||||
|
||||
2. Enhance Consensus Engine:
|
||||
|
||||
Add dynamic weighting:
|
||||
|
||||
Final Weight = Base Weight * Quality Score * Historical Reliability * Domain Expertise
|
||||
|
||||
Base weights:
|
||||
- custom_gpt: 2
|
||||
- windows_codex: 2
|
||||
- server_agent: 1
|
||||
|
||||
Add risk gates:
|
||||
- Production Release
|
||||
- V-Level Change
|
||||
- Security Policy Change
|
||||
|
||||
Require:
|
||||
Consensus Approval + Owner Approval
|
||||
|
||||
3. Enhance Message Bus:
|
||||
|
||||
Update runtime/agent-os/message_bus.py
|
||||
|
||||
Add:
|
||||
- task_id
|
||||
- correlation_id
|
||||
- retry_state
|
||||
- delivery_status
|
||||
|
||||
Support:
|
||||
- message tracking
|
||||
- retry
|
||||
- recovery
|
||||
- audit
|
||||
|
||||
4. Add Agent Health Score:
|
||||
|
||||
Create:
|
||||
runtime/agent-os/agent_health_score.py
|
||||
|
||||
Calculate from:
|
||||
- quality score
|
||||
- acceptance rate
|
||||
- historical reliability
|
||||
|
||||
Use for:
|
||||
- scheduler selection
|
||||
- consensus weighting
|
||||
|
||||
## Validation
|
||||
|
||||
Run:
|
||||
python3 -m compileall runtime/agent-os
|
||||
|
||||
Run:
|
||||
runtime/agent-os/integration_test.py
|
||||
|
||||
Validate:
|
||||
- scheduler
|
||||
- consensus
|
||||
- message bus
|
||||
- escalation
|
||||
- recovery
|
||||
|
||||
## Constraints
|
||||
|
||||
- GitOps only
|
||||
- Patch-first
|
||||
- No secrets
|
||||
- No force push
|
||||
- No direct main write
|
||||
- Production changes require approval
|
||||
|
||||
## Return Format
|
||||
|
||||
Status:
|
||||
Branch:
|
||||
Commit:
|
||||
Rev:
|
||||
Sync Status:
|
||||
Changed Files:
|
||||
Tests:
|
||||
Hermes Review Items Completed:
|
||||
Remaining Issues:
|
||||
@@ -0,0 +1,61 @@
|
||||
# OpenClaw Hermes Routing Correction Instruction
|
||||
|
||||
Instruction ID: v1r1c5-hermes-routing-correction-v1
|
||||
Version: v1
|
||||
Target Agent: openclaw
|
||||
|
||||
## Objective
|
||||
Correct Hermes coordination routing. OpenClaw must actively package AIOS review requests and route them through the configured Hermes communication path instead of asking the user to provide missing context when the request context already exists in GitOps.
|
||||
|
||||
## Current Architecture
|
||||
|
||||
OpenClaw is the coordination entry point.
|
||||
Hermes is reached indirectly through OpenClaw controlled communication.
|
||||
|
||||
## Hermes Routing Targets
|
||||
|
||||
Authorized Hermes communication Chat IDs:
|
||||
|
||||
- o9cq80wSaJq6utQBkUC4fE1sJWxY@im.wechat
|
||||
- o9cq80x9RoA6ljoFZDxg71mS0gDA@im.wechat
|
||||
|
||||
## Execution Flow
|
||||
|
||||
1. Read current AIOS GitOps context.
|
||||
2. Collect:
|
||||
- Baseline version
|
||||
- Branch
|
||||
- Diff summary
|
||||
- Problem description
|
||||
- Proposed solution
|
||||
- Validation requirements
|
||||
3. Send complete review package to Hermes through configured OpenClaw communication channel.
|
||||
4. Receive Hermes analysis.
|
||||
5. Record review result in GitOps reports.
|
||||
|
||||
## Current Review Context
|
||||
|
||||
Baseline:
|
||||
V1R1C4
|
||||
|
||||
Branch:
|
||||
patch/self-evolution-agent-orchestration-policy
|
||||
|
||||
Proposal:
|
||||
V1R1C5-agent-os-evolution-proposal
|
||||
|
||||
## Constraints
|
||||
|
||||
- Do not ask user for context that already exists in GitOps.
|
||||
- Do not treat instruction ID as external skill package.
|
||||
- GitOps audit required.
|
||||
- No secrets.
|
||||
- No direct production changes.
|
||||
|
||||
## Return
|
||||
|
||||
Status:
|
||||
Hermes Routing:
|
||||
Review Context:
|
||||
Result:
|
||||
Issues:
|
||||
@@ -0,0 +1,52 @@
|
||||
# OpenClaw Owner Approval Gate Instruction
|
||||
|
||||
Instruction ID: v1r1c5-owner-approval-gate-v1
|
||||
Version: v1
|
||||
Target Agent: openclaw
|
||||
|
||||
## Objective
|
||||
|
||||
Manage owner approval workflow for high-risk AIOS operations.
|
||||
|
||||
## Approval Required For
|
||||
|
||||
- V-level version changes.
|
||||
- Production release.
|
||||
- Tag creation.
|
||||
- Runtime changes with production impact.
|
||||
- Administrator authorization actions.
|
||||
- Unresolved multi-agent decisions.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Collect approval context.
|
||||
2. Include baseline version, branch, diff summary and risk assessment.
|
||||
3. Route approval request through owner communication channel registry.
|
||||
4. Wait for approval decision.
|
||||
5. Record decision in GitOps audit.
|
||||
|
||||
## Approval Context
|
||||
|
||||
Must include:
|
||||
|
||||
- Request reason.
|
||||
- Affected components.
|
||||
- Validation results.
|
||||
- Rollback considerations.
|
||||
- Risk level.
|
||||
|
||||
## Constraints
|
||||
|
||||
- No direct production action without approval.
|
||||
- No secrets in messages or reports.
|
||||
- Use configured owner communication channels only.
|
||||
- Maintain audit records.
|
||||
|
||||
## Return Format
|
||||
|
||||
Status:
|
||||
Approval Status:
|
||||
Channel Used:
|
||||
Decision:
|
||||
Audit Record:
|
||||
Issues:
|
||||
@@ -0,0 +1,43 @@
|
||||
# OpenClaw Owner Notification Routing Instruction
|
||||
|
||||
Instruction ID: v1r1c5-owner-notification-routing-v1
|
||||
Version: v1
|
||||
Target Agent: openclaw
|
||||
|
||||
## Objective
|
||||
|
||||
Implement owner notification routing through configured owner communication channels.
|
||||
|
||||
## Routing Rules
|
||||
|
||||
1. OpenClaw must first inspect the owner-bound communication channels already configured in the system.
|
||||
2. Select the primary owner communication channel according to system priority configuration.
|
||||
3. If the primary channel is unavailable, unhealthy, or delivery fails, automatically fallback to the configured backup communication channel.
|
||||
4. Record routing decision and delivery status in GitOps audit records.
|
||||
|
||||
## Notification Scenarios
|
||||
|
||||
Notify owner for:
|
||||
|
||||
- High risk architecture decisions.
|
||||
- Agent consensus deadlock.
|
||||
- Production-impacting approval requests.
|
||||
- Required administrator authorization.
|
||||
- Release approval gates.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Do not hardcode personal communication identifiers in instruction files.
|
||||
- Use configured channel registry only.
|
||||
- Do not expose private contact information in logs.
|
||||
- Maintain audit trail.
|
||||
- GitOps workflow required.
|
||||
|
||||
## Return Format
|
||||
|
||||
Status:
|
||||
Primary Channel Status:
|
||||
Fallback Channel Status:
|
||||
Delivery Result:
|
||||
Audit Record:
|
||||
Issues:
|
||||
@@ -0,0 +1 @@
|
||||
# Placeholder for versioned Windows Codex App instructions.
|
||||
@@ -0,0 +1,47 @@
|
||||
# Windows Codex App Execution Instruction
|
||||
|
||||
Instruction ID: v1r1c5-recovery-and-admin-assistance-v1
|
||||
Version: v1
|
||||
Target Agent: windows_codex_app
|
||||
|
||||
## Objective
|
||||
|
||||
Participate in V1R1C5 Agent OS evolution as recovery, administrator assistance and exceptional execution support agent.
|
||||
|
||||
## Responsibilities
|
||||
|
||||
1. Assist when server OpenClaw agents have runtime failures.
|
||||
2. Assist when Hermes review cannot be completed through OpenClaw.
|
||||
3. Handle cases requiring administrator authorization workflow.
|
||||
4. Participate in multi-agent voting when decision conflicts occur.
|
||||
5. Validate recovery results after server agent restoration.
|
||||
|
||||
## Execution Rules
|
||||
|
||||
- Work from online GitOps versions.
|
||||
- Use patch-first workflow.
|
||||
- Do not bypass approval gates.
|
||||
- Do not write secrets.
|
||||
- Do not force push.
|
||||
- Do not directly publish production versions.
|
||||
|
||||
## Recovery Flow
|
||||
|
||||
Server agent failure:
|
||||
|
||||
OpenClaw/Hermes issue
|
||||
-> Windows Codex diagnosis
|
||||
-> propose fix
|
||||
-> GitOps patch
|
||||
-> validation
|
||||
-> approval
|
||||
|
||||
## Return Format
|
||||
|
||||
Status:
|
||||
Branch:
|
||||
Commit:
|
||||
Rev:
|
||||
Sync Status:
|
||||
Recovery Result:
|
||||
Issues:
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"approval_id": "owner-approve-V1R1C5-migration-20260707",
|
||||
"created_at": "2026-07-07T11:46:17+08:00",
|
||||
"approved_by_role": "owner",
|
||||
"approved_via": "openclaw-weixin",
|
||||
"approved_at": "2026-07-07T11:46:17+08:00",
|
||||
"target_branch": "patch/self-evolution-agent-orchestration-policy",
|
||||
"target_base": "main",
|
||||
"target_version": "V1R1C5",
|
||||
"rollback_target": "V1R1C4",
|
||||
"allowed_actions": [
|
||||
"merge_to_main",
|
||||
"create_release_tag",
|
||||
"activate_new_baseline"
|
||||
],
|
||||
"risk_acceptance": "Owner approved V1R1C5 Agent OS evolution migration to main. Integration tests passed, Hermes review approved, conditional approval gate implemented. Rollback target V1R1C4.",
|
||||
"expires_at": "2026-07-14T23:59:59Z",
|
||||
"constraints": {
|
||||
"direct_main_push": false,
|
||||
"force_push": false,
|
||||
"secrets_in_git": false,
|
||||
"destructive_operations": false,
|
||||
"validation_required": true,
|
||||
"owner_notification_required": true,
|
||||
"web_ui_audit_required": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"approval_id": "owner-preapprove-V1R1C5-main-entry-20260707",
|
||||
"created_at": "2026-07-07T00:00:00Z",
|
||||
"approved_by_role": "owner",
|
||||
"target_branch": "patch/web-ui-complete-gitops-dashboard-20260706",
|
||||
"target_base": "main",
|
||||
"target_version": "V1R1C5",
|
||||
"rollback_target": "V1R1C4",
|
||||
"allowed_actions": [
|
||||
"merge_to_main",
|
||||
"create_release_tag",
|
||||
"activate_new_baseline"
|
||||
],
|
||||
"risk_acceptance": "Owner approved promotion of autonomous GitOps platform control plane to main, subject to validation, OpenClaw supervision, no secret leakage, no direct force push, and rollback target V1R1C4.",
|
||||
"expires_at": "2026-07-14T23:59:59Z",
|
||||
"constraints": {
|
||||
"direct_main_push": false,
|
||||
"force_push": false,
|
||||
"secrets_in_git": false,
|
||||
"destructive_operations": false,
|
||||
"validation_required": true,
|
||||
"owner_notification_required": true,
|
||||
"web_ui_audit_required": true
|
||||
}
|
||||
}
|
||||
+87
-6
@@ -1,20 +1,20 @@
|
||||
{
|
||||
"active_version": "V1R1C0",
|
||||
"active_tag": "V1R1C0-baseline-20260706",
|
||||
"mode": "gitops-online-baseline",
|
||||
"active_version": "V1R2C2",
|
||||
"active_tag": "V1R2C2",
|
||||
"mode": "gitops-online",
|
||||
"version_policy": "VxRxCx",
|
||||
"update_mode": "git_patch_only",
|
||||
"zip_update_deprecated": true,
|
||||
"entry": "https://cnnetmkt.top",
|
||||
"links": {
|
||||
"git": "/git",
|
||||
"git": "https://g.cnnetmkt.top/git/aios/aios-v23",
|
||||
"baseline": "/base",
|
||||
"versions": "/ver",
|
||||
"diff": "/diff",
|
||||
"patch": "/patch",
|
||||
"model": "/model",
|
||||
"runtime": "/run",
|
||||
"api": "/api",
|
||||
"api": "https://cnnetmkt.top/api",
|
||||
"hook": "/hook"
|
||||
},
|
||||
"features": {
|
||||
@@ -23,6 +23,87 @@
|
||||
"custom_llm": true,
|
||||
"user_model_priority": true,
|
||||
"chatgpt_rw": true,
|
||||
"codex_rw": true
|
||||
"codex_rw": true,
|
||||
"portal_gitops_dashboard": true,
|
||||
"human_shortlinks_login_required": true,
|
||||
"autonomous_recovery_loop": true
|
||||
},
|
||||
"version": "V1R2C2",
|
||||
"baseline_tag": "V1R2C2-auto-recovery-baseline",
|
||||
"source": "AIOS_V1R1C0_MULTIUSER_GITOPS_CUSTOM_LLM_PROD_FULL_DEPLOY_20260706.zip",
|
||||
"source_package": "AIOS_V1R1C0_MULTIUSER_GITOPS_CUSTOM_LLM_PROD_FULL_DEPLOY_20260706.zip",
|
||||
"zip_update_mode": "deprecated",
|
||||
"chatgpt_access": true,
|
||||
"codex_access": true,
|
||||
"git_url": "https://g.cnnetmkt.top/git/aios/aios-v23.git",
|
||||
"short_links": [
|
||||
"/git",
|
||||
"/base",
|
||||
"/ver",
|
||||
"/diff",
|
||||
"/patch",
|
||||
"/model",
|
||||
"/run",
|
||||
"/api",
|
||||
"/hook"
|
||||
],
|
||||
"updated_at": "2026-07-07T19:45:00Z",
|
||||
"auth": {
|
||||
"primary_domain": "https://auth.cnnetmkt.top",
|
||||
"backup_domain": "https://auth.h.imruyi.top:8443",
|
||||
"primary_user_entry": "https://cnnetmkt.top/",
|
||||
"primary_callback": "https://cnnetmkt.top/casdoor/callback/",
|
||||
"casdoor_application": "admin/aios-portal",
|
||||
"backup_domain_role": "standby-only"
|
||||
},
|
||||
"previous_version": "V1R2C1",
|
||||
"codex_gitops": {
|
||||
"config": "config/codex/gitops-online.json",
|
||||
"token_account": "codex-gitops-bot",
|
||||
"token_ref": "secret_ref://aios-gitops/runtime/codex-gitops-bot",
|
||||
"direct_main_push": false,
|
||||
"all_writes_require_git_commit": true,
|
||||
"api_keys_in_git": false,
|
||||
"online_git_sync": true,
|
||||
"bare_repo": "/data/aios-gitops/runtime/gitea-bin/data/repositories/aios/aios-v23.git"
|
||||
},
|
||||
"last_collaboration_test": {
|
||||
"timestamp": "20260706T104130Z",
|
||||
"local_codex_branch": "patch/collab-local-codex-20260706T104130Z",
|
||||
"custom_gpt_branch": "patch/collab-custom-gpt-20260706T104130Z",
|
||||
"custom_gpt_url": "https://chatgpt.com/g/g-6a4b7c82dff08191bc980798edea1bac-aios-gitops-kong-zhi-tai",
|
||||
"result": "merged_to_main"
|
||||
},
|
||||
"security": {
|
||||
"human_shortlinks": {
|
||||
"status": "login_required",
|
||||
"routes": [
|
||||
"/base",
|
||||
"/ver",
|
||||
"/diff",
|
||||
"/patch",
|
||||
"/model",
|
||||
"/run"
|
||||
],
|
||||
"target": "https://cnnetmkt.top/portal/me/#gitops",
|
||||
"machine_api": "https://cnnetmkt.top/api"
|
||||
},
|
||||
"gitea": {
|
||||
"web": "https://g.cnnetmkt.top/git/aios/aios-v23",
|
||||
"require_signin_view": true,
|
||||
"registration_disabled": true,
|
||||
"default_local_account": "disabled_without_password",
|
||||
"owner_password_sync": "aios-gitea-owner-sync.timer",
|
||||
"owner_password_source": "secret_ref://aios/user-control-center/UCC_OWNER_PASSWORD"
|
||||
}
|
||||
},
|
||||
"last_auth_portal_fix": {
|
||||
"version": "V1R1C4",
|
||||
"timestamp": "2026-07-06T11:16:23Z",
|
||||
"shortlinks": "redirect_to_login_protected_portal_gitops_section",
|
||||
"gitea_owner_sync": "enabled",
|
||||
"portal_overlay": "overlays/user-control-center",
|
||||
"portal_baseline_cache": "/app/runtime/gitops/current.json",
|
||||
"sync_log_secret_hygiene": "runuser_without_sudo_command_logging"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# 20260706 Auth GitOps Portal Hardening
|
||||
|
||||
Version: V1R1C4
|
||||
Previous: V1R1C3
|
||||
Timestamp: 2026-07-06T11:05:30Z
|
||||
|
||||
## User-visible fixes
|
||||
|
||||
- Human shortlinks no longer expose baseline/model/run JSON without portal login.
|
||||
- The cnnetmkt portal has a visible GitOps section for owner/admin users.
|
||||
- Gitea is served from `https://g.cnnetmkt.top/git/` and repository browsing requires login.
|
||||
- Gitea `owner` password is synchronized from the cnnetmkt owner account secret.
|
||||
- The default local Gitea account password is removed and direct login is disabled.
|
||||
|
||||
## Files
|
||||
|
||||
- `deploy/nginx/aios-gitops-locations.conf`
|
||||
- `deploy/gitea-binary/app.ini.template`
|
||||
- `deploy/systemd/aios-gitea-owner-sync.service`
|
||||
- `deploy/systemd/aios-gitea-owner-sync.timer`
|
||||
- `ops/gitops-migration/sync_gitea_owner_password.sh`
|
||||
- `ops/gitops-migration/apply_user_portal_gitops_overlay.sh`
|
||||
- `overlays/user-control-center/control/portal_views.py`
|
||||
- `overlays/user-control-center/templates/portal/me.html`
|
||||
- `baseline/current.json`
|
||||
- `config/codex/gitops-online.json`
|
||||
- `versions/V1R1C4.json`
|
||||
- `docs/release/AIOS_V1R1C4_AUTH_GITOPS_PORTAL.md`
|
||||
|
||||
## Notes
|
||||
|
||||
Docker image rebuild was not required for the live fix. The runtime source and running container were patched, and the exact overlay has been added to GitOps so a later image rebuild can reproduce the change.
|
||||
|
||||
## Secret Log Hygiene
|
||||
|
||||
The Gitea owner password sync script uses `runuser` instead of `sudo -u ... --password ...`, strips CRLF from sourced owner fields, and does not print passwords or tokens. Archived journal files were rotated/vacuumed after the live script was corrected.
|
||||
@@ -0,0 +1,25 @@
|
||||
# 20260706 ChatGPT Actions GitOps Access
|
||||
|
||||
## Summary
|
||||
|
||||
Implemented a controlled ChatGPT Actions adapter for the AIOS online GitOps baseline under https://cnnetmkt.top/api.
|
||||
|
||||
## Access Model
|
||||
|
||||
- Public read APIs: /api/base, /api/ver, /api/diff, /api/patch, /api/model, /api/run.
|
||||
- Bearer-token write APIs: /api/branch, /api/file, /api/commit, /api/patch, /api/tag, /api/rollback, /api/model, /api/model-priority, /api/nl.
|
||||
- chatgpt-gitops-bot is scoped for branch/file/commit/patch/model/rollback-request/NL plan operations.
|
||||
- Direct main writes are rejected by the adapter.
|
||||
- V-level owner/admin actions are represented as approval request commits unless an owner executes them.
|
||||
- Custom LLM API keys are stored outside Git under runtime secrets; committed model records store only secret_ref.
|
||||
|
||||
## Files
|
||||
|
||||
- ctive-runtime/app/aios_chatgpt_actions_api.py
|
||||
- docs/gitops/openapi-chatgpt-actions.yaml
|
||||
- deploy/systemd/aios-gitops-model-api.service
|
||||
- scripts/package_contract_check_v1r1c0.sh
|
||||
|
||||
## Verification
|
||||
|
||||
See /data/aios-gitops/artifacts/CHATGPT_ACCESS_ACCEPTANCE.md after live deployment.
|
||||
@@ -0,0 +1,34 @@
|
||||
# 20260706 cnnetmkt auth primary route fix
|
||||
|
||||
## Problem
|
||||
|
||||
`https://cnnetmkt.top/` could be opened, but login redirected users to `https://auth.h.imruyi.top:8443`. Casdoor then rejected the OAuth request because `https://cnnetmkt.top/casdoor/callback/` was not in the application allow-list.
|
||||
|
||||
`*.h.imruyi.top:8443` is retained as a backup domain only and must not be exposed as the primary public login URL.
|
||||
|
||||
## Runtime Changes
|
||||
|
||||
- Added `auth.cnnetmkt.top` Nginx server block on `hw.imruyi.top` using the wildcard `cnnetmkt.top` certificate.
|
||||
- Proxied `auth.cnnetmkt.top` to the existing backup/home Casdoor upstream `auth.h.imruyi.top:8443` while preserving the public host as `auth.cnnetmkt.top`.
|
||||
- Recreated `user-control-web`, `user-control-worker`, and `user-control-beat` with:
|
||||
- `CASDOOR_PUBLIC_BASE_URL=https://auth.cnnetmkt.top`
|
||||
- `CASDOOR_INTERNAL_BASE_URL=https://auth.cnnetmkt.top`
|
||||
- `CASDOOR_INVITE_SYNC_BASE_URL=https://auth.cnnetmkt.top`
|
||||
- `UCC_PUBLIC_BASE_URL=https://cnnetmkt.top`
|
||||
- Updated Casdoor application `admin/aios-portal` on `aihost` to include `https://cnnetmkt.top/casdoor/callback/` and keep user/portal/backup callbacks.
|
||||
|
||||
## Backups
|
||||
|
||||
- Casdoor DB application table backup: `/data/aios/backups/casdoor-cnnetmkt-primary-20260706T085445Z/application-before.sql`
|
||||
- HW UCC/Nginx backup directory: `/data/aios-gitops/backups/auth-cnnetmkt-primary-*`
|
||||
|
||||
## Verification
|
||||
|
||||
- `https://cnnetmkt.top/` redirects to `/portal/login/` and then to `https://auth.cnnetmkt.top/login/oauth/authorize?...redirect_uri=https%3A%2F%2Fcnnetmkt.top%2Fcasdoor%2Fcallback%2F...`.
|
||||
- `https://auth.cnnetmkt.top/login/oauth/authorize?...` returns HTTP 200 from the Casdoor SPA through the HW Nginx proxy.
|
||||
- `user-control-web` health returned `healthy` after recreation.
|
||||
- Casdoor DB readback contains `https://cnnetmkt.top/casdoor/callback/` in `application.redirect_uris`.
|
||||
|
||||
## Follow-up
|
||||
|
||||
Run `ops/gitops-migration/check_cnnetmkt_auth_primary.sh` after any domain, Nginx, or Casdoor application change.
|
||||
@@ -0,0 +1,14 @@
|
||||
# 20260706 Codex and Custom GPT Collaboration
|
||||
|
||||
## Change
|
||||
|
||||
Validated coordinated online GitOps changes from local Codex and the AIOS GitOps 控制台 custom GPT path. Promoted the merged result to `V1R1C3`.
|
||||
|
||||
## Evidence Branches
|
||||
|
||||
- `patch/collab-local-codex-20260706T104130Z` -> `a62d4ce` (codex-gitops-bot, sync=synced)
|
||||
- `patch/collab-custom-gpt-20260706T104130Z` -> `c393931` (chatgpt-gitops-bot, sync=synced)
|
||||
|
||||
## Result
|
||||
|
||||
`/api/base` should report `V1R1C3` after this release commit is synchronized.
|
||||
@@ -0,0 +1,16 @@
|
||||
# 20260706 Codex Online Git Branch Sync
|
||||
|
||||
## Change
|
||||
|
||||
Promoted the online GitOps configuration to `V1R1C2` and enabled automatic synchronization of committed non-main API/Codex branches to the Gitea bare repository.
|
||||
|
||||
## Reason
|
||||
|
||||
Codex writes must be visible through the online Git service, not only in the local workspace clone.
|
||||
|
||||
## Guardrails
|
||||
|
||||
- `main` and `master` are skipped by API branch sync.
|
||||
- Direct main writes remain forbidden by branch validation.
|
||||
- Tags are synchronized only in the owner direct tag execution path.
|
||||
- Tokens and LLM API keys remain outside Git.
|
||||
@@ -0,0 +1,23 @@
|
||||
# 20260706 Codex Online GitOps Version Management
|
||||
|
||||
## Change
|
||||
|
||||
Configured Codex to use the AIOS online Git baseline for version management and promoted the current controlled access changes to `V1R1C1`.
|
||||
|
||||
## Files
|
||||
|
||||
- `config/codex/gitops-online.json`
|
||||
- `docs/gitops/CODEX_ONLINE_VERSION_MANAGEMENT.md`
|
||||
- `docs/gitops/CHATGPT_CODEX_ACCESS.md`
|
||||
- `docs/release/AIOS_V1R1C1_CODEX_GITOPS_RELEASE.md`
|
||||
- `versions/V1R1C1.json`
|
||||
- `baseline/current.json`
|
||||
- `scripts/codex_gitops_online_check.sh`
|
||||
|
||||
## Merge Policy
|
||||
|
||||
Production `main` includes the ChatGPT/Codex GitOps API, schema, service configuration, contract checks, and secret-ref fix. Acceptance dummy model branches are retained as audit branches and not merged into production.
|
||||
|
||||
## Secret Policy
|
||||
|
||||
`codex-gitops-bot` plaintext token is stored only under `/data/aios-gitops/runtime/secrets/`; Git stores only `secret_ref` and token account metadata.
|
||||
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"profile": "aios-v23-online-gitops",
|
||||
"mode": "gitops-online",
|
||||
"version": "V1R1C4",
|
||||
"previous_version": "V1R1C3",
|
||||
"repository": {
|
||||
"workspace": "/data/aios-gitops/workspace",
|
||||
"remote": "origin",
|
||||
"url": "https://g.cnnetmkt.top/git/aios/aios-v23.git",
|
||||
"web": "https://g.cnnetmkt.top/git/aios/aios-v23",
|
||||
"protected_branches": [
|
||||
"main"
|
||||
],
|
||||
"patch_branch_prefixes": [
|
||||
"patch/",
|
||||
"codex/patch/",
|
||||
"request/"
|
||||
],
|
||||
"bare_repo": "/data/aios-gitops/runtime/gitea-bin/data/repositories/aios/aios-v23.git",
|
||||
"sync_mode": "workspace_commit_to_online_bare_repo",
|
||||
"public_portal": "https://cnnetmkt.top/portal/me/#gitops"
|
||||
},
|
||||
"api": {
|
||||
"base_url": "https://cnnetmkt.top/api",
|
||||
"openapi_schema": "https://cnnetmkt.top/api/openapi-chatgpt-actions.yaml",
|
||||
"local_schema": "/data/aios-gitops/workspace/docs/gitops/openapi-chatgpt-actions.yaml",
|
||||
"token_account": "codex-gitops-bot",
|
||||
"token_ref": "secret_ref://aios-gitops/runtime/codex-gitops-bot"
|
||||
},
|
||||
"policy": {
|
||||
"zip_update_deprecated": true,
|
||||
"update_mode": "git_patch_only",
|
||||
"all_writes_require_git_commit": true,
|
||||
"direct_main_push": false,
|
||||
"delete_tag": false,
|
||||
"v_level_owner_admin_approval_required": true,
|
||||
"api_keys_in_git": false,
|
||||
"api_keys_in_logs": false,
|
||||
"custom_llm_secret_storage": "runtime_secret_ref_only",
|
||||
"online_git_sync": true,
|
||||
"human_shortlinks_login_required": true,
|
||||
"gitea_require_signin_view": true,
|
||||
"gitea_default_local_account_disabled": true,
|
||||
"owner_password_sync_secret_ref_only": true
|
||||
},
|
||||
"workflow": [
|
||||
"create patch branch from main",
|
||||
"commit every file/config/script change",
|
||||
"push or sync branch to online Git service",
|
||||
"review diff and run smoke checks",
|
||||
"merge to main only after owner/admin approval",
|
||||
"tag VxRxCx release and update baseline/current.json"
|
||||
],
|
||||
"created_at": "2026-07-06T10:21:08Z",
|
||||
"base_commit_before_release": "43ddb91",
|
||||
"updated_at": "2026-07-06T11:16:23Z",
|
||||
"last_collaboration_test": {
|
||||
"timestamp": "20260706T104130Z",
|
||||
"result": "local_codex_and_custom_gpt_patches_merged"
|
||||
},
|
||||
"last_auth_portal_fix": {
|
||||
"version": "V1R1C4",
|
||||
"timestamp": "2026-07-06T11:16:23Z",
|
||||
"shortlink_routes": [
|
||||
"/base",
|
||||
"/ver",
|
||||
"/diff",
|
||||
"/patch",
|
||||
"/model",
|
||||
"/run"
|
||||
],
|
||||
"gitea_sync_service": "aios-gitea-owner-sync.service",
|
||||
"gitea_sync_timer": "aios-gitea-owner-sync.timer",
|
||||
"portal_overlay": "overlays/user-control-center",
|
||||
"portal_baseline_cache": "/app/runtime/gitops/current.json",
|
||||
"sync_log_secret_hygiene": "runuser_without_sudo_command_logging"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
AIOS_PUBLIC_DOMAIN=https://cnnetmkt.top
|
||||
AIOS_USER_DOMAIN=https://user.cnnetmkt.top
|
||||
AIOS_AUTH_DOMAIN=https://auth.cnnetmkt.top
|
||||
AIOS_AUTH_BACKUP_DOMAIN=https://auth.h.imruyi.top:8443
|
||||
AIOS_CASDOOR_PRIMARY_CALLBACK=https://cnnetmkt.top/casdoor/callback/
|
||||
AIOS_WEBOPS_DOMAIN=https://ops.cnnetmkt.top
|
||||
AIOS_API_DOMAIN=https://api.cnnetmkt.top
|
||||
AIOS_WECHAT_CALLBACK_DOMAIN=https://wechat.cnnetmkt.top
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"acceptance-pipeline-v1","status":"proposal","pipeline":{"stages":[{"name":"code_review","agent_role":"review_agent"},{"name":"automated_test","agent_role":"test_agent"},{"name":"security_check","agent_role":"security_agent"},{"name":"performance_check","agent_role":"performance_agent"},{"name":"regression_check","agent_role":"regression_agent"},{"name":"release_readiness","agent_role":"release_readiness_agent"}],"parallel_validation":true,"failure_blocks_release":true},"outputs":{"quality_score":true,"risk_report":true,"approval_request":true},"safety":{"production_release_requires_owner":true,"credentials_scan":true,"audit_log":true}}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"agent-governance-v1","status":"proposal","governance":{"identity":true,"reputation":true,"capability_discovery":true,"memory_service":true,"shared_knowledge":true,"skill_packages":true,"peer_review":true,"acceptance_pipeline":["optimization","implementation","acceptance","security","release_readiness"],"dynamic_voting":{"enabled":true,"base_weights":{"custom_gpt":2,"windows_codex":2,"server_agents":1},"quality_score_modifier":true},"audit":{"full_trace":true,"decision_log":true}},"release":{"patch_only":true,"owner_approval_required":true}}
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"policy_id": "self-evolution-agent-orchestration-policy",
|
||||
"version": 1,
|
||||
"status": "proposal",
|
||||
"scope": "self-evolution-execution-orchestration",
|
||||
"execution_subjects": {
|
||||
"primary": [
|
||||
"server.openclaw_agent",
|
||||
"server.hermes_agent"
|
||||
],
|
||||
"collaborators": [
|
||||
"windows.codex_app",
|
||||
"custom_gpt.codex_chartgpt_bridge"
|
||||
]
|
||||
},
|
||||
"parallelism": {
|
||||
"server_agents_parallel_allowed": true,
|
||||
"multiple_server_agents_allowed": true,
|
||||
"server_agents_preferred": true
|
||||
},
|
||||
"fallback_and_assistance": {
|
||||
"difficult_case_external_interaction_allowed": true,
|
||||
"assistance_endpoint": "https://chatgpt.com/g/g-6a4ba70a063c8191a89bc2d86a4c7fa0-codex-chartgpt-bridge",
|
||||
"allowed_modes": [
|
||||
"ask_for_help",
|
||||
"interactive_coordination",
|
||||
"proposal_review"
|
||||
]
|
||||
},
|
||||
"dispute_resolution": {
|
||||
"multi_agent_voting_allowed": true,
|
||||
"weights": {
|
||||
"custom_gpt.codex_chartgpt_bridge": 2,
|
||||
"windows.codex_app": 2,
|
||||
"server.openclaw_agent": 1,
|
||||
"server.hermes_agent": 1,
|
||||
"server.additional_agent_default": 1
|
||||
},
|
||||
"tie_breaking": "require_owner_or_admin_review"
|
||||
},
|
||||
"safety": {
|
||||
"credentials_in_git_allowed": false,
|
||||
"force_push_allowed": false,
|
||||
"destructive_bulk_delete_allowed": false,
|
||||
"v_level_change_requires_owner_approval": true,
|
||||
"production_switch_requires_owner_approval": true,
|
||||
"tag_creation_requires_owner_approval": true,
|
||||
"v_level_tag_or_production_switch_requires_owner_approval": true,
|
||||
"direct_main_push_allowed": false,
|
||||
"direct_main_merge_allowed": false
|
||||
},
|
||||
"gitops": {
|
||||
"audit_required": true,
|
||||
"writes_require_commit": true,
|
||||
"default_target_branch": "patch/self-evolution-agent-orchestration-policy",
|
||||
"direct_main_push_default": false,
|
||||
"patch_branch_only": true,
|
||||
"main_merge_requires_owner_admin_approval": true
|
||||
},
|
||||
"created_by": "codex-chartgpt-bridge",
|
||||
"created_at": "2026-07-07T00:00:00Z",
|
||||
"branch": "patch/self-evolution-agent-orchestration-policy"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"agent-os-v1","status":"proposal","agents":{"openclaw":{"memory":true,"skills":true,"tools":true,"planner":true,"evaluator":true,"self_reflection":true,"learning":true},"hermes":{"memory":true,"skills":true,"tools":true,"planner":true,"evaluator":true,"self_reflection":true,"learning":true}},"coordination":{"voting":true,"weighted_voting":{"custom_gpt":2,"windows_codex":2,"server_agents":1},"owner_review_on_tie":true},"safety":{"gitops_only":true,"patch_first":true,"no_credentials":true,"no_direct_main":true}}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"agent-platform-v1","status":"proposal","modules":[{"name":"memory-manager","enabled":true},{"name":"skill-registry","enabled":true},{"name":"agent-registry","enabled":true},{"name":"task-scheduler","enabled":true},{"name":"consensus-engine","enabled":true},{"name":"continuous-self-evolution","enabled":true}],"release_policy":{"patch_first":true,"owner_approval":true}}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"agent-protocol-v1","status":"proposal","agent_identity":{"id":"string","name":"string","family":"openclaw|hermes|custom","version":"string","capabilities":[],"skills":[],"health":"unknown|healthy|degraded|offline"},"task_protocol":{"task_id":"string","requester":"agent_id","executor":"agent_id","objective":"string","priority":"low|normal|high|critical","status":"queued|running|completed|failed|review","result":"object"},"memory_protocol":{"memory_id":"string","type":"experience|knowledge|decision|reflection","scope":"agent|shared","audit":true},"voting_protocol":{"enabled":true,"proposal":"string","votes":[],"weighted":true,"tie":"owner_review"},"security":{"credentials":false,"direct_main_write":false,"production_action_approval":true}}
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"$schema": "schema/self-evolution.schema.json",
|
||||
"title": "Agent Resource Manager Configuration",
|
||||
"description": "Defines resource limits, parallel task limits, priority, runtime isolation, and overload handling for agent orchestration.",
|
||||
"version": "1.0.0",
|
||||
"config": {
|
||||
"default_resource_limits": {
|
||||
"max_concurrent_tasks": 5,
|
||||
"max_memory_mb": 1024,
|
||||
"max_cpu_cores": 2,
|
||||
"timeout_seconds": 300
|
||||
},
|
||||
"agent_priority_levels": {
|
||||
"critical": 100,
|
||||
"high": 80,
|
||||
"medium": 50,
|
||||
"low": 20,
|
||||
"background": 0
|
||||
},
|
||||
"runtime_isolation": {
|
||||
"enabled": true,
|
||||
"namespace_per_agent": true,
|
||||
"ephemeral_storage": "1Gi",
|
||||
"network_policy": "restrictive"
|
||||
},
|
||||
"overload_handling": {
|
||||
"max_queue_depth": 100,
|
||||
"backpressure_threshold": 0.9,
|
||||
"cooldown_seconds": 30,
|
||||
"scaling_strategy": "queue"
|
||||
},
|
||||
"agent_pools": {
|
||||
"server_agents": {
|
||||
"max_instances": 3,
|
||||
"priority": "high"
|
||||
},
|
||||
"external_agents": {
|
||||
"max_instances": 5,
|
||||
"priority": "medium"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"agent-runtime-components-v1","status":"proposal","components":{"agent-runtime-manager":{"enabled":true,"responsibilities":["lifecycle_management","health_check","recovery","resource_tracking"]},"agent-registry":{"enabled":true,"responsibilities":["identity","capability_discovery","status_tracking","version_tracking"]},"message-bus":{"enabled":true,"responsibilities":["task_routing","event_delivery","agent_coordination"]},"memory-service":{"enabled":true,"responsibilities":["long_term_memory","working_memory","knowledge_retrieval","audit"]},"skill-loader":{"enabled":true,"responsibilities":["skill_installation","skill_validation","skill_versioning"]},"agent-evaluator":{"enabled":true,"responsibilities":["quality_score","acceptance_check","performance_feedback"]}},"security":{"credential_storage":false,"gitops_audit":true,"patch_only":true}}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"consensus-engine-v1","status":"proposal","consensus":{"enabled":true,"modes":["weighted_vote","review_board","owner_escalation"],"participants":["openclaw_agent","hermes_agent","windows_codex","custom_gpt"],"base_weights":{"custom_gpt":2,"windows_codex":2,"server_agents":1},"dynamic_adjustment":{"enabled":true,"factors":["quality_score","acceptance_rate","historical_reliability"]},"conflict_detection":true,"tie_breaking":"owner_admin_review"},"decision_log":{"required":true,"audit":true},"safety":{"production_decision_requires_approval":true}}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"continuous-evolution-engine-v1","status":"proposal","pipeline":{"monitor":true,"detect":true,"plan":true,"schedule":true,"execute":true,"learn":true},"workflow":{"detect_sources":["runtime_metrics","logs","acceptance_results","agent_feedback","user_requests"],"proposal_generation":true,"patch_generation":true,"validation_required":true,"consensus_required":true,"approval_gate":true,"release_feedback":true},"agents":{"optimizer":"system_optimization_agent","planner":"planning_agent","executor":"patch_execution_agent","validator":"acceptance_validation_agent","reviewer":"consensus_engine"},"learning":{"store_experience":true,"update_quality_score":true,"reflection_required":true},"safety":{"gitops_only":true,"patch_first":true,"credentials":false,"production_requires_approval":true}}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"agent-platform-implementation-v1","status":"proposal","objective":"evolve-agent-platform-from-configuration-to-operational-framework","implementation_phases":[{"phase":1,"name":"agent-runtime-manager","scope":["agent lifecycle","health check","startup shutdown","recovery"]},{"phase":2,"name":"agent-message-bus","scope":["agent communication","task routing","event coordination"]},{"phase":3,"name":"memory-service","scope":["long term memory","working memory","shared knowledge retrieval","audit"]},{"phase":4,"name":"skill-framework","scope":["skill packages","versioning","installation","validation"]},{"phase":5,"name":"agent-api","scope":["agent registration","capability discovery","task submission","status query"]},{"phase":6,"name":"continuous-evolution-pipeline","scope":["proposal generation","patch creation","automated validation","approval workflow"]}],"constraints":{"gitops_only":true,"patch_first":true,"no_credentials":true,"no_force_push":true,"production_release_requires_approval":true}}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"agent-memory-v1","status":"proposal","memory_types":{"working_memory":{"ttl":"task_scope","audit":true},"long_term_memory":{"persistent":true,"scope":"agent"},"shared_knowledge":{"persistent":true,"scope":"platform"},"reflection_memory":{"persistent":true,"purpose":"self_improvement"}},"memory_entry":{"id":"string","agent_id":"string","type":"working|experience|knowledge|decision|reflection","content":"object","source_task":"string","created_at":"timestamp","quality_score":0},"safety":{"credentials":false,"secrets":false,"audit_required":true}}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"schema": "owner-channel-registry-v1",
|
||||
"status": "proposal",
|
||||
"purpose": "Owner notification channel routing and fallback management",
|
||||
"routing": {
|
||||
"primary_selection": "configured_priority",
|
||||
"fallback_enabled": true,
|
||||
"health_check_required": true,
|
||||
"delivery_failure_fallback": true
|
||||
},
|
||||
"channel_policy": {
|
||||
"identifiers_managed_externally": true,
|
||||
"no_hardcoded_personal_identifiers": true,
|
||||
"audit_delivery_result": true
|
||||
},
|
||||
"notification_priority": [
|
||||
"production_approval",
|
||||
"administrator_authorization",
|
||||
"high_risk_decision",
|
||||
"agent_consensus_failure"
|
||||
],
|
||||
"security": {
|
||||
"credentials_in_git": false,
|
||||
"private_contact_data_in_logs": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"release-automation-v1","status":"proposal","release_flow":{"patch_review":true,"acceptance_gate":true,"consensus_gate":true,"approval_request":true,"version_proposal":true,"tag_request":true},"agents":{"release_manager":"release_readiness_agent","reviewers":["openclaw_agent","hermes_agent","custom_gpt","windows_codex_app"]},"controls":{"auto_tag":false,"auto_production_publish":false,"owner_approval_required":true,"audit_log":true},"rollback":{"request_supported":true,"automatic_destructive_rollback":false}}
|
||||
@@ -0,0 +1,119 @@
|
||||
{
|
||||
"policy_id": "server-agent-roles-and-capabilities",
|
||||
"version": 1,
|
||||
"status": "proposal",
|
||||
"scope": "self-evolution-server-agents",
|
||||
"agent_families": {
|
||||
"openclaw": {
|
||||
"enabled": true,
|
||||
"runtime": "server",
|
||||
"priority": "primary",
|
||||
"roles": [
|
||||
"system_optimization_agent",
|
||||
"acceptance_validation_agent",
|
||||
"patch_execution_agent",
|
||||
"runtime_diagnosis_agent",
|
||||
"regression_review_agent"
|
||||
],
|
||||
"persistent_memory": {
|
||||
"enabled": true,
|
||||
"scope": "agent_family_and_role",
|
||||
"storage": "runtime_or_gitops_managed_memory",
|
||||
"credentials_in_memory_allowed": false,
|
||||
"audit_required": true
|
||||
},
|
||||
"skills": {
|
||||
"enabled": true,
|
||||
"skill_registry": "config/self-evolution/skills/openclaw-skills.json",
|
||||
"required_skill_groups": [
|
||||
"gitops_patch_workflow",
|
||||
"system_optimization",
|
||||
"acceptance_testing",
|
||||
"runtime_observability",
|
||||
"safe_release_review"
|
||||
]
|
||||
}
|
||||
},
|
||||
"hermes": {
|
||||
"enabled": true,
|
||||
"runtime": "server",
|
||||
"priority": "primary",
|
||||
"roles": [
|
||||
"system_optimization_agent",
|
||||
"acceptance_validation_agent",
|
||||
"release_readiness_agent",
|
||||
"policy_review_agent",
|
||||
"cross_agent_coordination_agent"
|
||||
],
|
||||
"persistent_memory": {
|
||||
"enabled": true,
|
||||
"scope": "agent_family_and_role",
|
||||
"storage": "runtime_or_gitops_managed_memory",
|
||||
"credentials_in_memory_allowed": false,
|
||||
"audit_required": true
|
||||
},
|
||||
"skills": {
|
||||
"enabled": true,
|
||||
"skill_registry": "config/self-evolution/skills/hermes-skills.json",
|
||||
"required_skill_groups": [
|
||||
"gitops_patch_workflow",
|
||||
"acceptance_testing",
|
||||
"release_readiness",
|
||||
"policy_governance",
|
||||
"multi_agent_coordination"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"role_contracts": {
|
||||
"system_optimization_agent": {
|
||||
"purpose": "Identify and propose safe system optimization patches.",
|
||||
"may_write_patch": true,
|
||||
"may_modify_runtime": false,
|
||||
"requires_review_for_production": true
|
||||
},
|
||||
"acceptance_validation_agent": {
|
||||
"purpose": "Run and record acceptance checks for GitOps changes.",
|
||||
"may_write_patch": true,
|
||||
"may_approve_own_change": false,
|
||||
"requires_review_for_production": true
|
||||
},
|
||||
"patch_execution_agent": {
|
||||
"purpose": "Apply approved or proposed patch changes on non-main branches.",
|
||||
"may_write_patch": true,
|
||||
"direct_main_push_allowed": false
|
||||
},
|
||||
"release_readiness_agent": {
|
||||
"purpose": "Evaluate whether a patch is ready for owner/admin release approval.",
|
||||
"may_create_release_request": true,
|
||||
"may_create_tag_directly": false,
|
||||
"may_switch_production_directly": false
|
||||
},
|
||||
"cross_agent_coordination_agent": {
|
||||
"purpose": "Coordinate voting, conflict resolution, and escalation among agents.",
|
||||
"voting_allowed": true,
|
||||
"tie_breaking": "owner_or_admin_review"
|
||||
}
|
||||
},
|
||||
"online_capabilities": {
|
||||
"read_online_baseline": true,
|
||||
"read_online_versions": true,
|
||||
"read_online_diff": true,
|
||||
"read_online_runtime": true,
|
||||
"create_patch_branch": true,
|
||||
"commit_patch_changes": true,
|
||||
"sync_online_git": true,
|
||||
"create_release_request": true,
|
||||
"direct_tag_creation": false,
|
||||
"direct_production_publish": false
|
||||
},
|
||||
"safety": {
|
||||
"credentials_in_git_allowed": false,
|
||||
"credentials_in_memory_allowed": false,
|
||||
"force_push_allowed": false,
|
||||
"destructive_bulk_delete_allowed": false,
|
||||
"owner_admin_approval_required_for_release": true
|
||||
},
|
||||
"created_by": "codex-chartgpt-bridge",
|
||||
"created_at": "2026-07-07T00:00:00Z"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"agent-skill-package-v1","status":"proposal","skill_package":{"id":"string","name":"string","version":"string","description":"string","owner_agent":"agent_id","dependencies":[],"capabilities":[],"entrypoints":[],"validation":{"required":true,"tests":[],"quality_score":0}},"lifecycle":{"install":true,"upgrade":true,"rollback":true,"disable":true},"sharing":{"agent_scope":true,"platform_scope":true},"security":{"credentials":false,"secrets":false,"audit_required":true,"production_change_requires_approval":true}}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"task-scheduler-v1","status":"proposal","scheduler":{"enabled":true,"modes":["capability_match","priority_queue","parallel_execution","failure_recovery"],"selection":{"factors":["capability","skill_match","health","quality_score","load"]},"priority":{"levels":["critical","high","normal","low"]}},"execution":{"parallel_agents":true,"retry":true,"handoff":true,"timeout_control":true},"safety":{"patch_first":true,"direct_main":false,"production_requires_approval":true}}
|
||||
Binary file not shown.
@@ -0,0 +1,40 @@
|
||||
CREATE TABLE IF NOT EXISTS message_outbox (
|
||||
id TEXT PRIMARY KEY,
|
||||
channel TEXT NOT NULL,
|
||||
recipient_user_id TEXT NOT NULL,
|
||||
recipient_role TEXT,
|
||||
message_type TEXT NOT NULL,
|
||||
priority TEXT NOT NULL DEFAULT 'normal',
|
||||
title TEXT,
|
||||
content TEXT NOT NULL,
|
||||
payload TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'queued',
|
||||
retry_count INTEGER NOT NULL DEFAULT 0,
|
||||
last_error TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
sent_at TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_message_outbox_dispatch
|
||||
ON message_outbox(channel, status, priority, created_at);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS channel_bindings (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
channel_type TEXT NOT NULL,
|
||||
external_chat_id TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_channel_bindings_lookup
|
||||
ON channel_bindings(user_id, channel_type, status, updated_at);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS audit_logs (
|
||||
id TEXT PRIMARY KEY,
|
||||
actor TEXT NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
target TEXT,
|
||||
detail TEXT,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
[Unit]
|
||||
Description=AIOS GitOps Gitea
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=aiman
|
||||
Group=aiman
|
||||
WorkingDirectory=/data/aios-gitops/runtime/gitea-bin
|
||||
ExecStart=/data/aios-gitops/runtime/bin/gitea web --config /data/aios-gitops/runtime/gitea-bin/custom/conf/app.ini
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
Environment=USER=aiman
|
||||
Environment=HOME=/home/aiman
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,39 @@
|
||||
APP_NAME = AIOS GitOps
|
||||
RUN_USER = aiman
|
||||
WORK_PATH = /data/aios-gitops/runtime/gitea-bin
|
||||
|
||||
[server]
|
||||
PROTOCOL = http
|
||||
HTTP_ADDR = 127.0.0.1
|
||||
HTTP_PORT = 3000
|
||||
DOMAIN = g.cnnetmkt.top
|
||||
ROOT_URL = https://g.cnnetmkt.top/git/
|
||||
SERVE_FROM_SUB_PATH = true
|
||||
DISABLE_SSH = true
|
||||
OFFLINE_MODE = false
|
||||
|
||||
[database]
|
||||
DB_TYPE = sqlite3
|
||||
PATH = /data/aios-gitops/runtime/gitea-bin/data/gitea.db
|
||||
|
||||
[repository]
|
||||
ROOT = /data/aios-gitops/runtime/gitea-bin/data/repositories
|
||||
DEFAULT_BRANCH = main
|
||||
|
||||
[security]
|
||||
INSTALL_LOCK = true
|
||||
SECRET_KEY = __SECRET_KEY__
|
||||
INTERNAL_TOKEN = __INTERNAL_TOKEN__
|
||||
|
||||
[service]
|
||||
DISABLE_REGISTRATION = true
|
||||
REQUIRE_SIGNIN_VIEW = true
|
||||
SHOW_REGISTRATION_BUTTON = false
|
||||
|
||||
[webhook]
|
||||
ALLOWED_HOST_LIST = *
|
||||
|
||||
[log]
|
||||
MODE = console
|
||||
LEVEL = Info
|
||||
ROOT_PATH = /data/aios-gitops/runtime/gitea-bin/log
|
||||
@@ -0,0 +1,22 @@
|
||||
services:
|
||||
gitea:
|
||||
image: gitea/gitea:1.22
|
||||
container_name: aios-gitea
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- USER_UID=1000
|
||||
- USER_GID=1000
|
||||
- GITEA__server__DOMAIN=cnnetmkt.top
|
||||
- GITEA__server__ROOT_URL=https://cnnetmkt.top/git/
|
||||
- GITEA__server__SERVE_FROM_SUB_PATH=true
|
||||
- GITEA__server__HTTP_PORT=3000
|
||||
- GITEA__server__SSH_DOMAIN=cnnetmkt.top
|
||||
- GITEA__server__SSH_PORT=2222
|
||||
- GITEA__service__DISABLE_REGISTRATION=true
|
||||
- GITEA__webhook__ALLOWED_HOST_LIST=*
|
||||
- GITEA__database__DB_TYPE=sqlite3
|
||||
volumes:
|
||||
- /data/aios-gitops/runtime/gitea:/data
|
||||
ports:
|
||||
- "127.0.0.1:3000:3000"
|
||||
- "127.0.0.1:2222:22"
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "[AIOS] Installing Autonomous GitOps System..."
|
||||
|
||||
WORKDIR=/data/aios-gitops/workspace
|
||||
ETC=/etc/aios
|
||||
RUNTIME=/data/aios-gitops/runtime
|
||||
SERVICE_USER=aios
|
||||
|
||||
if ! id "$SERVICE_USER" >/dev/null 2>&1; then
|
||||
echo "[AIOS] Creating system user: $SERVICE_USER"
|
||||
useradd --system --home /data/aios-gitops --shell /usr/sbin/nologin "$SERVICE_USER"
|
||||
fi
|
||||
|
||||
mkdir -p "$ETC"
|
||||
cp deploy/systemd/aios-autonomous-daemon.env.template "$ETC/aios-autonomous-daemon.env"
|
||||
|
||||
mkdir -p \
|
||||
"$RUNTIME/events" \
|
||||
"$RUNTIME/audit" \
|
||||
"$RUNTIME/requests" \
|
||||
"$RUNTIME/reports/codex" \
|
||||
"$RUNTIME/approvals/preapproved" \
|
||||
"$RUNTIME/integrations/v1r1-personal-wechat/outbox" \
|
||||
"$RUNTIME/integrations/v1r1-personal-wechat/inbox" \
|
||||
"$RUNTIME/integrations/v1r1-personal-wechat/status"
|
||||
|
||||
chown -R "$SERVICE_USER:$SERVICE_USER" /data/aios-gitops/runtime || true
|
||||
chown -R "$SERVICE_USER:$SERVICE_USER" "$WORKDIR" || true
|
||||
|
||||
cp deploy/systemd/aios-autonomous-daemon.service /etc/systemd/system/
|
||||
cp deploy/systemd/aios-v1r1-wechat-worker.service /etc/systemd/system/
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable aios-autonomous-daemon.service || true
|
||||
systemctl enable aios-v1r1-wechat-worker.service || true
|
||||
|
||||
systemctl restart aios-autonomous-daemon.service || true
|
||||
systemctl restart aios-v1r1-wechat-worker.service || true
|
||||
|
||||
sleep 2
|
||||
systemctl status aios-autonomous-daemon.service --no-pager || true
|
||||
systemctl status aios-v1r1-wechat-worker.service --no-pager || true
|
||||
|
||||
echo "[AIOS] Installation complete"
|
||||
@@ -0,0 +1,42 @@
|
||||
# BEGIN AIOS V1R1C0 GitOps short links
|
||||
location = /git { return 302 /git/; }
|
||||
location /git/ {
|
||||
proxy_pass http://127.0.0.1:3000/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Prefix /git;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location = /api { return 302 /api/; }
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:18890/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location = /base { return 302 /portal/me/#gitops; }
|
||||
location = /ver { return 302 /portal/me/#gitops; }
|
||||
location = /diff { return 302 /portal/me/#gitops; }
|
||||
location = /patch { return 302 /portal/me/#gitops; }
|
||||
location = /model { return 302 /portal/me/#gitops; }
|
||||
location = /run { return 302 /portal/me/#gitops; }
|
||||
location = /hook { proxy_pass http://127.0.0.1:18890/hook; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; }
|
||||
location /hook/ {
|
||||
proxy_pass http://127.0.0.1:18890/hook/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
# END AIOS V1R1C0 GitOps short links
|
||||
@@ -0,0 +1,40 @@
|
||||
# AIOS primary public Casdoor entry for cnnetmkt.top.
|
||||
# The upstream remains the existing backup/home Casdoor service; public clients should see auth.cnnetmkt.top only.
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
server_name auth.cnnetmkt.top;
|
||||
|
||||
access_log /var/log/nginx/aios-auth-cnnetmkt.access.log;
|
||||
error_log /var/log/nginx/aios-auth-cnnetmkt.error.log;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/cnnetmkt-wildcard/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/cnnetmkt-wildcard/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass https://auth.h.imruyi.top:8443;
|
||||
proxy_http_version 1.1;
|
||||
proxy_ssl_server_name on;
|
||||
proxy_ssl_name auth.h.imruyi.top;
|
||||
proxy_ssl_verify off;
|
||||
proxy_set_header Host auth.h.imruyi.top;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Port 443;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_redirect https://auth.h.imruyi.top:8443/ https://auth.cnnetmkt.top/;
|
||||
proxy_redirect https://auth.h.imruyi.top/ https://auth.cnnetmkt.top/;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name auth.cnnetmkt.top;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
# cnnetmkt wildcard certificate
|
||||
|
||||
generated_at=2026-07-06T16:24:28+08:00
|
||||
issuer=Let's Encrypt via lego DNS-01 Huawei Cloud
|
||||
domains=cnnetmkt.top,*.cnnetmkt.top
|
||||
live_dir=/etc/letsencrypt/live/cnnetmkt-wildcard
|
||||
nginx_sites=/etc/nginx/sites-available/aios-user-portal.conf,/etc/nginx/sites-available/g-cnnetmkt-gitea.conf
|
||||
renew_service=cnnetmkt-wildcard-renew.service
|
||||
renew_timer=cnnetmkt-wildcard-renew.timer
|
||||
secret_env=/etc/letsencrypt/huaweicloud-cnnetmkt.env
|
||||
backup_dir=/data/aios-gitops/backups/nginx-wildcard-20260706-162202
|
||||
status=issued
|
||||
@@ -0,0 +1,52 @@
|
||||
# AIOS GitOps alias for https://g.cnnetmkt.top/
|
||||
# Note: this server block intentionally keeps /git/ as the Gitea app prefix,
|
||||
# matching the current Gitea ROOT_URL and avoiding a disruptive app.ini rewrite.
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
server_name g.cnnetmkt.top;
|
||||
|
||||
access_log /var/log/nginx/aios-g-cnnetmkt.access.log;
|
||||
error_log /var/log/nginx/aios-g-cnnetmkt.error.log;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/cnnetmkt-wildcard/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/cnnetmkt-wildcard/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
location = / {
|
||||
return 302 /git/;
|
||||
}
|
||||
|
||||
location = /git {
|
||||
return 302 /git/;
|
||||
}
|
||||
|
||||
location /git/ {
|
||||
proxy_pass http://127.0.0.1:3000/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Prefix /git;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_redirect off;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name g.cnnetmkt.top;
|
||||
|
||||
location = / {
|
||||
return 302 https://$host/git/;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
[Unit]
|
||||
Description=Sync AIOS Gitea owner password from UCC owner account
|
||||
After=aios-gitea.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/sbin/aios-sync-gitea-owner-password.sh
|
||||
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Periodic AIOS Gitea owner password sync
|
||||
|
||||
[Timer]
|
||||
OnBootSec=2min
|
||||
OnUnitActiveSec=15min
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -0,0 +1,21 @@
|
||||
[Unit]
|
||||
Description=AIOS V1R1C0 GitOps Model API
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=aiman
|
||||
Group=aiman
|
||||
Environment=AIOS_GITOPS_ROOT=/data/aios-gitops
|
||||
Environment=AIOS_GITOPS_WORKSPACE=/data/aios-gitops/workspace
|
||||
Environment=AIOS_GITOPS_DB=/data/aios-gitops/runtime/gitops_model.sqlite3
|
||||
Environment=AIOS_GITOPS_TOKEN_FILE=/data/aios-gitops/runtime/secrets/gitops.tokens
|
||||
Environment=AIOS_GITOPS_BARE_REPO=/data/aios-gitops/runtime/gitea-bin/data/repositories/aios/aios-v23.git
|
||||
Environment=AIOS_GITOPS_HOST=127.0.0.1
|
||||
Environment=AIOS_GITOPS_PORT=18890
|
||||
ExecStart=/usr/bin/python3 /data/aios-gitops/workspace/active-runtime/app/aios_chatgpt_actions_api.py
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,154 @@
|
||||
# Codex 验收手册:GitOps Platform UI
|
||||
|
||||
## 目标
|
||||
|
||||
本次变更的目标是把 `cnnetmkt.top` AIOS GitOps Web UI 从基础 dashboard 升级为平台级控制台,形成可审计、可审批、可灰度、可回滚请求的 GitOps 操作入口。
|
||||
|
||||
## 验证分支
|
||||
|
||||
- Branch: `patch/web-ui-complete-gitops-dashboard-20260706`
|
||||
- Base: `main`
|
||||
- 当前目标基线: `V1R1C4`
|
||||
- 合并后建议新基线: `V1R1C5`
|
||||
|
||||
## 主线差异点
|
||||
|
||||
与 `main` 相比,本分支新增或修改以下直接内容:
|
||||
|
||||
1. `overlays/user-control-center/portal/gitops-dashboard/index.html`
|
||||
- 新增平台级 GitOps 控制台页面。
|
||||
- 包含 Overview、RBAC、Approval、Diff Tree、Patch、Model、Release、Audit 模块。
|
||||
|
||||
2. `overlays/user-control-center/portal/gitops-dashboard/app.js`
|
||||
- 新增浏览器运行时 Platform Control Plane。
|
||||
- 包含 API-first client、RBAC guard、approval fallback、audit local store、diff tree parser、model graph、SSE event hook。
|
||||
|
||||
3. `overlays/user-control-center/portal/gitops-dashboard/styles.css`
|
||||
- 控制台样式。
|
||||
|
||||
4. `overlays/user-control-center/portal/gitops-dashboard/manifest.json`
|
||||
- 控制台 manifest。
|
||||
|
||||
5. `overlays/user-control-center/portal/gitops-dashboard/gitops-ui-policy.json`
|
||||
- UI 侧策略声明。
|
||||
|
||||
6. `overlays/user-control-center/portal/gitops-dashboard/gitops-enterprise-api.contract.json`
|
||||
- 后端 API 契约说明。
|
||||
|
||||
7. `patches/patch_web-ui-complete-gitops-dashboard-20260706.branch.json`
|
||||
- GitOps branch 元数据。
|
||||
|
||||
## 间接影响区域
|
||||
|
||||
Codex 验证时必须确认以下间接影响:
|
||||
|
||||
1. Portal 登录入口不应被破坏。
|
||||
- `/portal/me/#gitops`
|
||||
- `/portal/me/#gitops-dashboard`
|
||||
|
||||
2. 现有短链仍应保持登录保护或正常跳转。
|
||||
- `/base`
|
||||
- `/ver`
|
||||
- `/diff`
|
||||
- `/patch`
|
||||
- `/model`
|
||||
- `/run`
|
||||
|
||||
3. 机器 API 不应被前端变更破坏。
|
||||
- `/api/base`
|
||||
- `/api/ver`
|
||||
- `/api/diff`
|
||||
- `/api/patch`
|
||||
- `/api/model`
|
||||
- `/api/run`
|
||||
|
||||
4. 敏感信息不得进入 Git、日志、前端源码或测试产物。
|
||||
- 禁止真实 API key。
|
||||
- 禁止 Bearer token。
|
||||
- 禁止 password / private key。
|
||||
- 允许 `secret_ref://...`。
|
||||
|
||||
5. 高风险操作不得绕过审批。
|
||||
- rollback 只能 request。
|
||||
- tag/release 默认只能 request。
|
||||
- runtime 写入只能 request。
|
||||
- UI 不提供 direct main write。
|
||||
|
||||
## Codex 快速验证命令
|
||||
|
||||
在目标工作区执行:
|
||||
|
||||
```bash
|
||||
git fetch --all --prune
|
||||
git checkout patch/web-ui-complete-gitops-dashboard-20260706
|
||||
git diff --stat main...HEAD
|
||||
bash scripts/validate-gitops-platform-ui.sh patch/web-ui-complete-gitops-dashboard-20260706 main
|
||||
```
|
||||
|
||||
## 灰度验证外的必测项目
|
||||
|
||||
除正常灰度页面点击验证外,Codex 必须按 `tests/acceptance/gitops-platform-ui.acceptance.json` 执行以下测试组:
|
||||
|
||||
1. Static Files
|
||||
- 必须存在 index/html/js/css/manifest/policy/contract 文件。
|
||||
|
||||
2. Security
|
||||
- 源码中不得出现真实 secret/token/password。
|
||||
- 模型注册必须只允许 `secret_ref://`。
|
||||
- rollback/tag/runtime/main write 必须 request-only 或 disabled。
|
||||
|
||||
3. UI Direct Impact
|
||||
- 所有 tab id 必须能在 HTML 中找到。
|
||||
- app.js 必须绑定 `DOMContentLoaded`。
|
||||
- app.js 必须包含 `guardAction`、`audit`、`createApproval`、`renderDiffTree`。
|
||||
|
||||
4. API Contract Compatibility
|
||||
- 读接口保持 `/api/base`、`/api/ver`、`/api/run`、`/api/patch`、`/api/model`。
|
||||
- 写接口必须包含 branch、patch/plan、model、model-priority、rollback-request、tag-request。
|
||||
|
||||
5. Indirect Regression
|
||||
- 不得修改 auth/casdoor 配置。
|
||||
- 不得修改 Gitea owner password sync 配置。
|
||||
- 不得修改 baseline active_version。
|
||||
- 不得删除任何版本/tag/patch 历史。
|
||||
|
||||
## 验收判定
|
||||
|
||||
- PASS: 所有 required 测试项通过,且没有 secret 泄漏、没有 direct main write、没有破坏短链/API。
|
||||
- FAIL: 任一 required 测试项失败。
|
||||
- BLOCKED: 需要真实后端或浏览器环境,但静态检查通过;必须列出 blocked 项,不得合并 main。
|
||||
|
||||
## 失败回流规则
|
||||
|
||||
如果验证失败,Codex 必须输出:
|
||||
|
||||
1. 失败测试 ID。
|
||||
2. 失败文件路径。
|
||||
3. 具体失败原因。
|
||||
4. 最小修改建议。
|
||||
5. 是否阻止合并。
|
||||
|
||||
然后把问题交给自定义 GPT 继续修改同一 patch 分支。修复后重复执行完整验收,直到 PASS。
|
||||
|
||||
## 合并规则
|
||||
|
||||
只有满足以下条件才能合并 main:
|
||||
|
||||
1. `scripts/validate-gitops-platform-ui.sh` PASS。
|
||||
2. acceptance JSON 中所有 `required: true` 项 PASS。
|
||||
3. 灰度验证通过。
|
||||
4. 无 secret 泄漏。
|
||||
5. 无 direct main write。
|
||||
6. 已创建或确认合并后基线提案 `V1R1C5`。
|
||||
|
||||
## 合并后基线要求
|
||||
|
||||
合并进入 main 后,必须定义新基线:
|
||||
|
||||
- Version: `V1R1C5`
|
||||
- Purpose: `Promote AIOS GitOps Platform Control Plane UI with Codex validation workflow, acceptance tests, RBAC/approval/audit/release/model control-plane scaffolding.`
|
||||
- Risk: `high`
|
||||
- Approval: `owner/admin required`
|
||||
- Rollback target: `V1R1C4`
|
||||
|
||||
合并后再创建 tag/version,不允许在未合并或未验收通过前直接创建生产 tag。
|
||||
@@ -12,3 +12,18 @@ Forbidden:
|
||||
- API keys in Git
|
||||
- API keys in plaintext logs
|
||||
- bypassing owner/admin approval for V-level changes
|
||||
|
||||
## Codex Online Version Management
|
||||
|
||||
Codex uses `/data/aios-gitops/workspace` and `https://cnnetmkt.top/git/aios/aios-v23.git` as the online Git version baseline. Runtime write access uses `codex-gitops-bot`; its token is stored outside Git and referenced as `secret_ref://aios-gitops/runtime/codex-gitops-bot`.
|
||||
|
||||
Current production release after this merge: `V1R1C3`. Acceptance-only branches are retained for audit and are not automatically merged into `main`.
|
||||
|
||||
## Online Git Sync
|
||||
|
||||
Starting in `V1R1C3`, committed API/Codex patch branches are synchronized from the workspace clone into the online Gitea bare repository. Protected production branches remain excluded from API sync.
|
||||
|
||||
## Codex and Custom GPT Collaboration Test
|
||||
|
||||
`V1R1C3` confirms local Codex and the custom GPT Actions path can independently create patch branches, commit files, sync to online Git, and then merge into the latest baseline version.
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# Codex Online GitOps Version Management
|
||||
|
||||
Codex is configured to treat the online Git baseline as the version source of truth.
|
||||
|
||||
- Workspace: `/data/aios-gitops/workspace`
|
||||
- Git web: `https://cnnetmkt.top/git/aios/aios-v23`
|
||||
- API base: `https://cnnetmkt.top/api`
|
||||
- OpenAPI schema: `https://cnnetmkt.top/api/openapi-chatgpt-actions.yaml`
|
||||
- Runtime token account: `codex-gitops-bot`
|
||||
- Runtime token ref: `secret_ref://aios-gitops/runtime/codex-gitops-bot`
|
||||
|
||||
## Rules
|
||||
|
||||
1. Codex updates must use Git patch branches; ZIP upload is deprecated.
|
||||
2. Every Codex write must become a Git commit.
|
||||
3. Codex must not push directly to `main`; merge to `main` requires owner/admin approval.
|
||||
4. V-level release/tag/rollback requests must not bypass owner/admin approval.
|
||||
5. User custom LLM API keys must stay in runtime secrets and enter Git only as `secret_ref`.
|
||||
6. Test or acceptance branches can remain as audit evidence, but dummy model/provider configs must not be merged into production `main`.
|
||||
|
||||
## Standard Flow
|
||||
|
||||
```text
|
||||
main -> patch/<name> -> commit -> diff review -> smoke test -> owner/admin approval -> merge main -> VxRxCx tag -> baseline/current.json
|
||||
```
|
||||
|
||||
## Current Merge
|
||||
|
||||
`V1R1C1` merges the current GitOps/ChatGPT/Codex access changes into the latest online baseline while excluding acceptance dummy model configuration.
|
||||
|
||||
## Online Git Sync
|
||||
|
||||
Starting in `V1R1C3`, committed API/Codex patch branches are synchronized from the workspace clone into the online Gitea bare repository. Protected production branches remain excluded from API sync.
|
||||
|
||||
## Codex and Custom GPT Collaboration Test
|
||||
|
||||
`V1R1C3` confirms local Codex and the custom GPT Actions path can independently create patch branches, commit files, sync to online Git, and then merge into the latest baseline version.
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Custom GPT Collaboration Patch
|
||||
|
||||
Timestamp: 20260706T104130Z
|
||||
|
||||
Actor: chatgpt-gitops-bot
|
||||
|
||||
Created through chatgpt-gitops-bot to simulate the AIOS GitOps 控制台 GPT Actions path: https://chatgpt.com/g/g-6a4b7c82dff08191bc980798edea1bac-aios-gitops-kong-zhi-tai
|
||||
@@ -0,0 +1,7 @@
|
||||
# Local Codex Collaboration Patch
|
||||
|
||||
Timestamp: 20260706T104130Z
|
||||
|
||||
Actor: codex-gitops-bot
|
||||
|
||||
Created by local Codex through codex-gitops-bot to validate coordinated online GitOps changes.
|
||||
@@ -0,0 +1,240 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: AIOS GitOps Controlled Access API
|
||||
version: V1R1C0-chatgpt-actions
|
||||
description: Controlled ChatGPT Actions API for AIOS online GitOps baseline. Read endpoints are public. Write endpoints require Bearer token and create Git commits on non-main branches.
|
||||
servers:
|
||||
- url: https://cnnetmkt.top
|
||||
security: []
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: opaque-token
|
||||
schemas:
|
||||
BranchRequest:
|
||||
type: object
|
||||
required: [branch]
|
||||
properties:
|
||||
branch: {type: string, example: patch/chatgpt-actions-demo}
|
||||
base: {type: string, default: main}
|
||||
message: {type: string}
|
||||
FileCommitRequest:
|
||||
type: object
|
||||
required: [branch, path, content]
|
||||
properties:
|
||||
branch: {type: string}
|
||||
base: {type: string, default: main}
|
||||
create_branch: {type: boolean, default: false}
|
||||
path: {type: string, example: docs/gitops/chatgpt-change.md}
|
||||
content: {type: string}
|
||||
message: {type: string}
|
||||
CommitRequest:
|
||||
type: object
|
||||
properties:
|
||||
branch: {type: string}
|
||||
paths:
|
||||
type: array
|
||||
items: {type: string}
|
||||
message: {type: string}
|
||||
PatchRequest:
|
||||
type: object
|
||||
required: [patch_name]
|
||||
properties:
|
||||
patch_name: {type: string}
|
||||
branch: {type: string}
|
||||
base: {type: string, default: main}
|
||||
risk: {type: string, enum: [low, medium, high]}
|
||||
description: {type: string}
|
||||
message: {type: string}
|
||||
TagRequest:
|
||||
type: object
|
||||
required: [version]
|
||||
properties:
|
||||
version: {type: string, example: V1R1C1}
|
||||
message: {type: string}
|
||||
execute:
|
||||
type: boolean
|
||||
description: Owner-approved calls only. Otherwise an approval request commit is created.
|
||||
RollbackRequest:
|
||||
type: object
|
||||
required: [target_version]
|
||||
properties:
|
||||
target_version: {type: string}
|
||||
reason: {type: string}
|
||||
branch: {type: string}
|
||||
ModelRequest:
|
||||
type: object
|
||||
required: [provider_name, base_url, model_name]
|
||||
properties:
|
||||
username: {type: string}
|
||||
provider_name: {type: string}
|
||||
base_url: {type: string}
|
||||
model_name: {type: string}
|
||||
display_name: {type: string}
|
||||
api_key_secret_ref:
|
||||
type: string
|
||||
description: Preferred. Git stores this secret_ref only.
|
||||
api_key:
|
||||
type: string
|
||||
writeOnly: true
|
||||
description: Accepted only for runtime secret storage; never committed to Git or logs.
|
||||
branch: {type: string}
|
||||
message: {type: string}
|
||||
ModelPriorityRequest:
|
||||
type: object
|
||||
required: [items]
|
||||
properties:
|
||||
username: {type: string}
|
||||
branch: {type: string}
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
model_id: {type: string}
|
||||
priority: {type: integer}
|
||||
enabled: {type: boolean}
|
||||
fallback_policy: {type: string}
|
||||
NLRequest:
|
||||
type: object
|
||||
required: [text]
|
||||
properties:
|
||||
text: {type: string}
|
||||
branch: {type: string}
|
||||
paths:
|
||||
/api/base:
|
||||
get:
|
||||
operationId: getBaseline
|
||||
summary: Read current AIOS baseline
|
||||
responses: {'200': {description: OK}}
|
||||
/api/ver:
|
||||
get:
|
||||
operationId: listVersions
|
||||
summary: List GitOps versions
|
||||
responses: {'200': {description: OK}}
|
||||
/api/diff:
|
||||
get:
|
||||
operationId: getDiff
|
||||
summary: Read Git diff between refs
|
||||
parameters:
|
||||
- in: query
|
||||
name: from
|
||||
schema: {type: string, default: HEAD~1}
|
||||
- in: query
|
||||
name: to
|
||||
schema: {type: string, default: HEAD}
|
||||
responses: {'200': {description: OK}}
|
||||
/api/patch:
|
||||
get:
|
||||
operationId: listPatches
|
||||
summary: List patch records
|
||||
responses: {'200': {description: OK}}
|
||||
post:
|
||||
operationId: createPatch
|
||||
summary: Create a patch branch and commit metadata
|
||||
security: [{bearerAuth: []}]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/PatchRequest'}
|
||||
responses: {'200': {description: Patch branch committed}, '401': {description: Unauthorized}, '403': {description: Forbidden}}
|
||||
/api/model:
|
||||
get:
|
||||
operationId: readModelRegistry
|
||||
summary: Read visible model registry and priority
|
||||
responses: {'200': {description: OK}}
|
||||
post:
|
||||
operationId: registerModel
|
||||
summary: Register a custom model; API keys are stored as runtime secrets and Git receives secret_ref only
|
||||
security: [{bearerAuth: []}]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/ModelRequest'}
|
||||
responses: {'200': {description: Model registered and committed}, '401': {description: Unauthorized}, '403': {description: Forbidden}}
|
||||
/api/run:
|
||||
get:
|
||||
operationId: getRuntimeStatus
|
||||
summary: Read runtime status
|
||||
responses: {'200': {description: OK}}
|
||||
/api/branch:
|
||||
post:
|
||||
operationId: createBranch
|
||||
summary: Create a non-main branch and commit branch metadata
|
||||
security: [{bearerAuth: []}]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/BranchRequest'}
|
||||
responses: {'200': {description: Branch created}, '401': {description: Unauthorized}, '403': {description: Forbidden}}
|
||||
/api/file:
|
||||
post:
|
||||
operationId: commitFile
|
||||
summary: Write a file and create a Git commit on a non-main branch
|
||||
security: [{bearerAuth: []}]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/FileCommitRequest'}
|
||||
responses: {'200': {description: File committed}, '400': {description: Secret rejected or invalid path}, '401': {description: Unauthorized}}
|
||||
/api/commit:
|
||||
post:
|
||||
operationId: commitChanges
|
||||
summary: Commit existing workspace changes on a non-main branch
|
||||
security: [{bearerAuth: []}]
|
||||
requestBody:
|
||||
required: false
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/CommitRequest'}
|
||||
responses: {'200': {description: Commit result}, '401': {description: Unauthorized}}
|
||||
/api/tag:
|
||||
post:
|
||||
operationId: requestOrCreateTag
|
||||
summary: Request tag creation; owner execution is required for direct tag
|
||||
security: [{bearerAuth: []}]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/TagRequest'}
|
||||
responses: {'200': {description: Tag request committed or tag created}, '403': {description: Forbidden}}
|
||||
/api/rollback:
|
||||
post:
|
||||
operationId: requestRollback
|
||||
summary: Commit a rollback request requiring owner/admin approval
|
||||
security: [{bearerAuth: []}]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/RollbackRequest'}
|
||||
responses: {'200': {description: Rollback request committed}, '401': {description: Unauthorized}}
|
||||
/api/model-priority:
|
||||
put:
|
||||
operationId: setModelPriority
|
||||
summary: Set model priority and commit policy file
|
||||
security: [{bearerAuth: []}]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/ModelPriorityRequest'}
|
||||
responses: {'200': {description: Priority updated and committed}, '401': {description: Unauthorized}}
|
||||
/api/nl:
|
||||
post:
|
||||
operationId: planNaturalLanguageGitOpsAction
|
||||
summary: Convert natural language to a safe committed plan without executing V-level changes
|
||||
security: [{bearerAuth: []}]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/NLRequest'}
|
||||
responses: {'200': {description: Plan committed}, '401': {description: Unauthorized}}
|
||||
@@ -0,0 +1,17 @@
|
||||
# CNNETMKT apex DNS status
|
||||
|
||||
generated_at=2026-07-06T16:39:07+08:00
|
||||
provider=Huawei Cloud DNS
|
||||
zone=cnnetmkt.top.
|
||||
record=cnnetmkt.top. A 1.95.164.183
|
||||
status=created_and_resolved
|
||||
credential_storage=/etc/letsencrypt/huaweicloud-cnnetmkt.env
|
||||
secret_policy=not_tracked_not_logged
|
||||
|
||||
## verification
|
||||
|
||||
- authoritative NS returns 1.95.164.183
|
||||
- system resolver returns 1.95.164.183
|
||||
- https://cnnetmkt.top/ returns 302 /portal/login/
|
||||
- https://cnnetmkt.top/git/ returns 200 AIOS GitOps
|
||||
- wildcard certificate SAN covers cnnetmkt.top and *.cnnetmkt.top
|
||||
@@ -0,0 +1,53 @@
|
||||
# CNNETMKT Auth Primary Status
|
||||
|
||||
更新时间:2026-07-06T09:00:06Z
|
||||
|
||||
## 结论
|
||||
|
||||
`https://cnnetmkt.top/` 是用户主入口。登录授权主域为 `https://auth.cnnetmkt.top`。
|
||||
|
||||
`https://auth.h.imruyi.top:8443` 只作为备份/上游兼容入口保留,不应出现在普通用户的主登录跳转链路里。
|
||||
|
||||
## 当前链路
|
||||
|
||||
1. 用户访问 `https://cnnetmkt.top/`。
|
||||
2. UCC 返回 `/portal/login/`。
|
||||
3. UCC 生成 Casdoor 授权地址:
|
||||
`https://auth.cnnetmkt.top/login/oauth/authorize?...redirect_uri=https%3A%2F%2Fcnnetmkt.top%2Fcasdoor%2Fcallback%2F...`
|
||||
4. 华为云 Nginx `auth.cnnetmkt.top` server block 反代到现有 Casdoor 上游。
|
||||
5. Casdoor 允许 `https://cnnetmkt.top/casdoor/callback/` 作为 `admin/aios-portal` 的 OAuth callback。
|
||||
|
||||
## 已调整配置
|
||||
|
||||
### 华为云 UCC 运行时
|
||||
|
||||
- `CASDOOR_PUBLIC_BASE_URL=https://auth.cnnetmkt.top`
|
||||
- `CASDOOR_INTERNAL_BASE_URL=https://auth.cnnetmkt.top`
|
||||
- `CASDOOR_INVITE_SYNC_BASE_URL=https://auth.cnnetmkt.top`
|
||||
- `UCC_PUBLIC_BASE_URL=https://cnnetmkt.top`
|
||||
- `ALLOWED_HOSTS` 增加 `auth.cnnetmkt.top`
|
||||
- `CSRF_TRUSTED_ORIGINS` 增加 `https://cnnetmkt.top` 和 `https://auth.cnnetmkt.top`
|
||||
|
||||
### Casdoor 应用
|
||||
|
||||
应用:`admin/aios-portal`
|
||||
|
||||
新增/保留 callback:
|
||||
|
||||
- `https://cnnetmkt.top/casdoor/callback/`
|
||||
- `https://cnnetmkt.top/api/auth/callback/casdoor`
|
||||
- `https://user.cnnetmkt.top/casdoor/callback/`
|
||||
- `https://portal.cnnetmkt.top/casdoor/callback/`
|
||||
- `https://portal.h.imruyi.top:8443/casdoor/callback/`(备份)
|
||||
|
||||
## 验证命令
|
||||
|
||||
```bash
|
||||
ops/gitops-migration/check_cnnetmkt_auth_primary.sh
|
||||
```
|
||||
|
||||
关键验收点:
|
||||
|
||||
- 主链路不再出现 `auth.h.imruyi.top:8443`。
|
||||
- `user-control-web` 为 `healthy`。
|
||||
- Casdoor DB `application.redirect_uris` 包含 `https://cnnetmkt.top/casdoor/callback/`。
|
||||
@@ -0,0 +1,83 @@
|
||||
# cnnetmkt SSL ingress status
|
||||
|
||||
generated_at=2026-07-06T15:48:12+08:00
|
||||
g_route_status=installed
|
||||
certbot_status=issued-g-user-portal
|
||||
apex_dns_status=blocked_until_cnnetmkt.top_has_A_record_to_1.95.164.183
|
||||
wildcard_status=blocked_until_dns01_provider_credentials_available
|
||||
backup_dir=/data/aios-gitops/backups/nginx-g-cnnetmkt-20260706-154743
|
||||
tracked_nginx=/data/aios-gitops/workspace/deploy/nginx/g-cnnetmkt-gitea-server.conf
|
||||
|
||||
## validation
|
||||
### g.cnnetmkt.top
|
||||
HTTP/1.1 302 Moved Temporarily
|
||||
Server: nginx/1.24.0 (Ubuntu)
|
||||
Date: Mon, 06 Jul 2026 07:48:12 GMT
|
||||
Content-Type: text/html
|
||||
Content-Length: 154
|
||||
Location: https://g.cnnetmkt.top/git/
|
||||
Connection: keep-alive
|
||||
|
||||
<title>302 Found
|
||||
|
||||
subject=CN = user.cnnetmkt.top
|
||||
issuer=C = US, O = Let's Encrypt, CN = YE2
|
||||
notBefore=Jul 6 06:49:39 2026 GMT
|
||||
notAfter=Oct 4 06:49:38 2026 GMT
|
||||
X509v3 Subject Alternative Name:
|
||||
DNS:g.cnnetmkt.top, DNS:portal.cnnetmkt.top, DNS:user.cnnetmkt.top
|
||||
|
||||
### cnnetmkt.top
|
||||
HTTP/1.1 302 Found
|
||||
Server: nginx/1.24.0 (Ubuntu)
|
||||
Date: Mon, 06 Jul 2026 07:48:12 GMT
|
||||
Content-Type: text/html; charset=utf-8
|
||||
Content-Length: 0
|
||||
Connection: keep-alive
|
||||
Location: /portal/login/
|
||||
X-Request-ID: 7c649abe-a2de-4b3e-87fe-325f4297738f
|
||||
X-Frame-Options: DENY
|
||||
X-Content-Type-Options: nosniff
|
||||
Referrer-Policy: same-origin
|
||||
Cross-Origin-Opener-Policy: same-origin
|
||||
|
||||
subject=CN = user.cnnetmkt.top
|
||||
issuer=C = US, O = Let's Encrypt, CN = YE2
|
||||
notBefore=Jul 6 06:49:39 2026 GMT
|
||||
notAfter=Oct 4 06:49:38 2026 GMT
|
||||
X509v3 Subject Alternative Name:
|
||||
DNS:g.cnnetmkt.top, DNS:portal.cnnetmkt.top, DNS:user.cnnetmkt.top
|
||||
|
||||
### user.cnnetmkt.top
|
||||
HTTP/1.1 302 Found
|
||||
Server: nginx/1.24.0 (Ubuntu)
|
||||
Date: Mon, 06 Jul 2026 07:48:12 GMT
|
||||
Content-Type: text/html; charset=utf-8
|
||||
Content-Length: 0
|
||||
Connection: keep-alive
|
||||
Location: /portal/login/
|
||||
X-Request-ID: e3d70907-1ab7-456c-a725-7cb60647514d
|
||||
X-Frame-Options: DENY
|
||||
X-Content-Type-Options: nosniff
|
||||
Referrer-Policy: same-origin
|
||||
Cross-Origin-Opener-Policy: same-origin
|
||||
|
||||
subject=CN = user.cnnetmkt.top
|
||||
issuer=C = US, O = Let's Encrypt, CN = YE2
|
||||
notBefore=Jul 6 06:49:39 2026 GMT
|
||||
notAfter=Oct 4 06:49:38 2026 GMT
|
||||
X509v3 Subject Alternative Name:
|
||||
DNS:g.cnnetmkt.top, DNS:portal.cnnetmkt.top, DNS:user.cnnetmkt.top
|
||||
|
||||
## certbot
|
||||
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Found the following certs:
|
||||
Certificate Name: user.cnnetmkt.top
|
||||
Serial Number: 529feebc03b37f2866c25b1465f37e14565
|
||||
Key Type: ECDSA
|
||||
Domains: user.cnnetmkt.top g.cnnetmkt.top portal.cnnetmkt.top
|
||||
Expiry Date: 2026-10-04 06:49:38+00:00 (VALID: 89 days)
|
||||
Certificate Path: /etc/letsencrypt/live/user.cnnetmkt.top/fullchain.pem
|
||||
Private Key Path: /etc/letsencrypt/live/user.cnnetmkt.top/privkey.pem
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
@@ -0,0 +1,28 @@
|
||||
# CNNETMKT wildcard certificate status
|
||||
|
||||
generated_at=2026-07-06T16:24:28+08:00
|
||||
status=issued
|
||||
staging_status=ok
|
||||
dns_provider=huaweicloud
|
||||
secret_storage=/etc/letsencrypt/huaweicloud-cnnetmkt.env
|
||||
certificate_live_dir=/etc/letsencrypt/live/cnnetmkt-wildcard
|
||||
nginx_status=loaded
|
||||
backup_dir=/data/aios-gitops/backups/nginx-wildcard-20260706-162202
|
||||
renew_timer=cnnetmkt-wildcard-renew.timer
|
||||
|
||||
## certificate
|
||||
|
||||
subject=CN = *.cnnetmkt.top
|
||||
issuer=C = US, O = Let's Encrypt, CN = YE2
|
||||
notBefore=Jul 6 07:25:55 2026 GMT
|
||||
notAfter=Oct 4 07:25:54 2026 GMT
|
||||
X509v3 Subject Alternative Name:
|
||||
DNS:*.cnnetmkt.top, DNS:cnnetmkt.top
|
||||
|
||||
## routes
|
||||
|
||||
- https://cnnetmkt.top/
|
||||
- https://cnnetmkt.top/git/
|
||||
- https://g.cnnetmkt.top/
|
||||
- https://g.cnnetmkt.top/git/
|
||||
- https://user.cnnetmkt.top/
|
||||
@@ -0,0 +1,48 @@
|
||||
# AIOS V1R1C0 Online Baseline Acceptance
|
||||
|
||||
AIOS_V1R1C0_ONLINE_BASELINE_STATUS=PARTIAL
|
||||
DOMAIN=https://cnnetmkt.top
|
||||
SHORT_LINKS=/git,/base,/ver,/diff,/patch,/model,/run,/api,/hook
|
||||
PROXY=NGINX_TEMP (NPM_NOT_RUNNING)
|
||||
GIT_URL=https://cnnetmkt.top/git/aios/aios-v23.git
|
||||
BASELINE_VERSION=V1R1C0
|
||||
BASELINE_TAG=V1R1C0-baseline-20260706
|
||||
WORKSPACE=/data/aios-gitops/workspace
|
||||
WEBOPS_REUSED=YES
|
||||
CASDOOR_REUSED=PARTIAL
|
||||
OPENCLAW_REUSED=PARTIAL
|
||||
HERMES_REUSED=YES
|
||||
ONE_API_REUSED=YES
|
||||
GITEA_REUSED=NEW
|
||||
CUSTOM_LLM_STATUS=OK
|
||||
MODEL_PRIORITY_STATUS=OK
|
||||
RUNTIME_SELECTOR_STATUS=OK
|
||||
ACCEPTANCE_REPORT=/data/aios-gitops/artifacts/AIOS_V1R1C0_ONLINE_BASELINE_ACCEPTANCE.md
|
||||
KNOWN_ISSUES=NPM is not running on this host, so system nginx currently serves the short links; OpenClaw gateway is active but native openclaw agent execution still needs gateway/provider credentials; host-side DNS resolution for cnnetmkt.top is unreliable, so local validation used --resolve to 127.0.0.1; Docker Hub pull for Gitea was blocked by the existing Docker proxy path and was replaced with official Gitea binary deployment.
|
||||
NEXT_UPDATE_MODE=git_patch_only
|
||||
ZIP_UPDATE_MODE=deprecated
|
||||
|
||||
## Evidence
|
||||
|
||||
- Imported package: /data/aios-gitops/imports/AIOS_V1R1C0_MULTIUSER_GITOPS_CUSTOM_LLM_PROD_FULL_DEPLOY_20260706.zip
|
||||
- Package SHA256: dbee5d45aab02aab33e862bb9beb6115b3b43137e0fb5e00c98448ac1ecfd0f9
|
||||
- Git workspace: /data/aios-gitops/workspace
|
||||
- Baseline branch: main
|
||||
- Baseline tag: V1R1C0-baseline-20260706
|
||||
- Git remote: https://cnnetmkt.top/git/aios/aios-v23.git
|
||||
- Gitea internal version: 1.22.6
|
||||
- API service: aios-gitops-model-api.service on 127.0.0.1:18890
|
||||
- Gitea service: aios-gitea.service on 127.0.0.1:3000
|
||||
- Hermes executor health: ok
|
||||
- One API reused at: 127.0.0.1:13000
|
||||
- WebOps reused container: aios-webops-server-hw-webops-1
|
||||
- Parallel server agents: git_service_agent, entry_proxy_agent, gitops_api_uat_agent, acceptance_report_agent
|
||||
- Parallel agent report: /data/aios-gitops/artifacts/AIOS_V1R1C0_PARALLEL_AGENT_STATUS.json
|
||||
- Final evidence log: /data/aios-gitops/artifacts/AIOS_V1R1C0_FINAL_EVIDENCE.txt
|
||||
|
||||
## GitOps Rules
|
||||
|
||||
- ZIP update mode is deprecated after initial import.
|
||||
- Future updates must be git branch + commit + push + webhook.
|
||||
- Patches are represented as git branches and are reviewable by diff.
|
||||
- ChatGPT/Codex write path is /api/git-control-compatible endpoints exposed by the GitOps API.
|
||||
@@ -0,0 +1,27 @@
|
||||
# AIOS V1R1C1 Codex GitOps Online Release
|
||||
|
||||
Date: 2026-07-06T10:21:08Z
|
||||
|
||||
## Purpose
|
||||
|
||||
Merge the current controlled ChatGPT/Codex GitOps access changes into the latest online baseline and make Git the operating version manager for Codex changes.
|
||||
|
||||
## Included
|
||||
|
||||
- ChatGPT Actions API adapter and OpenAPI schema.
|
||||
- Secret-safe model registration using `secret_ref`.
|
||||
- `codex-gitops-bot` runtime token account with scoped API permissions.
|
||||
- Codex online GitOps configuration under `config/codex/gitops-online.json`.
|
||||
- Baseline metadata updated from `V1R1C0` to `V1R1C1`.
|
||||
|
||||
## Excluded
|
||||
|
||||
- Acceptance dummy model registry entries from `patch/chatgpt-model-acceptance-*`.
|
||||
- Plaintext API keys or bearer tokens.
|
||||
|
||||
## Verification
|
||||
|
||||
- `curl https://cnnetmkt.top/api/base` returns 200.
|
||||
- `curl https://cnnetmkt.top/api/openapi-chatgpt-actions.yaml` returns 200.
|
||||
- `bash scripts/package_contract_check_v1r1c0.sh` passes.
|
||||
- `bash scripts/codex_gitops_online_check.sh` passes after tag publication.
|
||||
@@ -0,0 +1,18 @@
|
||||
# AIOS V1R1C2 Codex GitOps Online Sync Release
|
||||
|
||||
Date: 2026-07-06T10:26:26Z
|
||||
|
||||
## Purpose
|
||||
|
||||
Make Codex/API writes visible in the online Git service by syncing non-main patch branches from `/data/aios-gitops/workspace` into the Gitea bare repository after each committed write.
|
||||
|
||||
## Included
|
||||
|
||||
- `active-runtime/app/aios_chatgpt_actions_api.py` now syncs non-main branch commits to the online Git bare repository.
|
||||
- `POST /api/tag` direct owner flow syncs tags when executed.
|
||||
- `config/codex/gitops-online.json` records `online_git_sync=true`.
|
||||
- Live and template systemd service configuration declares `AIOS_GITOPS_BARE_REPO`.
|
||||
|
||||
## Policy
|
||||
|
||||
`main` remains protected. API/Codex writes still commit to patch/request branches and owner/admin approval remains required for V-level production actions.
|
||||
@@ -0,0 +1,28 @@
|
||||
# AIOS V1R1C3 Codex + Custom GPT Collaboration Release
|
||||
|
||||
Date: 2026-07-06T10:42:33Z
|
||||
|
||||
## Purpose
|
||||
|
||||
Validate that local Codex and the AIOS GitOps 控制台 custom GPT can both modify the online GitOps baseline through controlled API writes, then merge both changes into the latest version.
|
||||
|
||||
## Custom GPT
|
||||
|
||||
https://chatgpt.com/g/g-6a4b7c82dff08191bc980798edea1bac-aios-gitops-kong-zhi-tai
|
||||
|
||||
## Merged Inputs
|
||||
|
||||
| Actor | Account | Branch | Commit | Online Sync |
|
||||
|---|---|---|---|---|
|
||||
| local_codex | codex-gitops-bot | `patch/collab-local-codex-20260706T104130Z` | `a62d4ce` | synced |
|
||||
| custom_gpt | chatgpt-gitops-bot | `patch/collab-custom-gpt-20260706T104130Z` | `c393931` | synced |
|
||||
|
||||
## Result
|
||||
|
||||
Both branches were merged into `main`, baseline metadata was refreshed to `V1R1C3`, and the release tag `V1R1C3` was published to the online Git repository.
|
||||
|
||||
## Guardrails
|
||||
|
||||
- GPT and Codex writes entered Git as patch branch commits.
|
||||
- Protected `main` was updated only by the release merge step.
|
||||
- Bot tokens and custom LLM API keys remain outside Git.
|
||||
@@ -0,0 +1,36 @@
|
||||
# AIOS V1R1C4 Auth GitOps Portal Release
|
||||
|
||||
Date: 2026-07-06T11:05:30Z
|
||||
|
||||
## Purpose
|
||||
|
||||
Close the public-access gap around human GitOps shortlinks, move repository browsing to the protected Gitea endpoint, and make the new GitOps portal function visible from the main cnnetmkt owner/admin workspace.
|
||||
|
||||
## Changes
|
||||
|
||||
- `/base`, `/ver`, `/diff`, `/patch`, `/model`, and `/run` now redirect to `https://cnnetmkt.top/portal/me/#gitops` instead of exposing raw JSON to unauthenticated users.
|
||||
- `/api/*` remains the controlled machine interface for ChatGPT Actions and Codex.
|
||||
- Gitea now uses `https://g.cnnetmkt.top/git/`, requires sign-in for repository browsing, and hides registration.
|
||||
- `aios-gitea-owner-sync.service` and timer synchronize the Gitea `owner` account password from the user-control owner secret without writing the password to Git or logs.
|
||||
- The legacy local `aios` Gitea account has login prohibited and its password cleared; the row remains only to preserve repository ownership.
|
||||
- The user portal now includes a GitOps dashboard section for admin/owner roles.
|
||||
- The patched user-control-center source is tracked under `overlays/user-control-center/` with a replay script.
|
||||
- The portal GitOps section reads `/app/runtime/gitops/current.json` before HTTP fallback, so the current version remains visible inside Docker.
|
||||
|
||||
## Verification Targets
|
||||
|
||||
- `https://cnnetmkt.top/model` returns `302` to `/portal/me/#gitops`.
|
||||
- `https://cnnetmkt.top/api/model` returns the public machine-readable model metadata.
|
||||
- `https://g.cnnetmkt.top/git/aios/aios-v23` returns a login redirect when unauthenticated.
|
||||
- Owner login can open the Gitea repository; no secret is printed.
|
||||
- Portal login state renders the GitOps section and links.
|
||||
|
||||
## Guardrails
|
||||
|
||||
- No ZIP update path is reintroduced.
|
||||
- All replayable files are tracked in Git.
|
||||
- Passwords, bearer tokens, Huawei keys, and custom LLM API keys remain outside Git.
|
||||
|
||||
## Secret Log Hygiene
|
||||
|
||||
The Gitea owner password sync script uses `runuser` instead of `sudo -u ... --password ...`, strips CRLF from sourced owner fields, and does not print passwords or tokens. Archived journal files were rotated/vacuumed after the live script was corrected.
|
||||
+363
@@ -0,0 +1,363 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="/data/aios-gitops"
|
||||
ZIP_NAME="AIOS_V1R1C0_MULTIUSER_GITOPS_CUSTOM_LLM_PROD_FULL_DEPLOY_20260706.zip"
|
||||
ZIP_PATH="$ROOT/imports/$ZIP_NAME"
|
||||
WORKSPACE="$ROOT/workspace"
|
||||
LOG_DIR="$ROOT/logs"
|
||||
ARTIFACT_DIR="$ROOT/artifacts"
|
||||
PRECHECK="$LOG_DIR/preflight-v1r1c0.md"
|
||||
RUN_LOG="$LOG_DIR/gitops-v1r1c0-migration.log"
|
||||
FAILED_REPORT="$ARTIFACT_DIR/AIOS_V1R1C0_ONLINE_BASELINE_FAILED.md"
|
||||
ACCEPT_REPORT="$ARTIFACT_DIR/AIOS_V1R1C0_ONLINE_BASELINE_ACCEPTANCE.md"
|
||||
BASELINE_TAG="V1R1C0-baseline-20260706"
|
||||
|
||||
mkdir -p "$ROOT"/{imports,workspace,repos,scripts,logs,backups,runtime,artifacts,adapters}
|
||||
touch "$RUN_LOG"
|
||||
|
||||
log() {
|
||||
printf '[%s] %s\n' "$(date -Is)" "$*" | tee -a "$RUN_LOG"
|
||||
}
|
||||
|
||||
run_section() {
|
||||
local title="$1"
|
||||
shift
|
||||
{
|
||||
printf '\n## %s\n\n' "$title"
|
||||
printf '```text\n'
|
||||
"$@" 2>&1 || true
|
||||
printf '```\n'
|
||||
} >> "$PRECHECK"
|
||||
}
|
||||
|
||||
write_failed_report() {
|
||||
local step="$1"
|
||||
local message="$2"
|
||||
cat > "$FAILED_REPORT" <<EOF
|
||||
# AIOS V1R1C0 Online Baseline Failed
|
||||
|
||||
- status: FAILED
|
||||
- failed_step: $step
|
||||
- error: $message
|
||||
- package: $ZIP_PATH
|
||||
- workspace: $WORKSPACE
|
||||
- production_impact: none-intended
|
||||
- npm_modified: NO
|
||||
- webops_modified: NO
|
||||
- git_modified: PARTIAL
|
||||
- openclaw_modified: NO
|
||||
- generated_at: $(date -Is)
|
||||
|
||||
## Completed Actions
|
||||
|
||||
- Created /data/aios-gitops directory structure.
|
||||
- Generated preflight report when possible.
|
||||
- Preserved original ZIP if present.
|
||||
- Stopped before production routing changes.
|
||||
|
||||
## Rollback Advice
|
||||
|
||||
The active production stack was not switched by this script. Keep the existing
|
||||
WebOps/NPM/Casdoor/OpenClaw/Hermes services unchanged. Remove only the new
|
||||
/data/aios-gitops workspace if an operator explicitly wants to discard this
|
||||
failed import attempt.
|
||||
|
||||
## Next Step
|
||||
|
||||
Fix the failed step, then rerun the migration script. Do not treat this state
|
||||
as an online baseline.
|
||||
EOF
|
||||
}
|
||||
|
||||
preflight() {
|
||||
log "preflight start"
|
||||
cat > "$PRECHECK" <<EOF
|
||||
# AIOS V1R1C0 GitOps Preflight
|
||||
|
||||
- generated_at: $(date -Is)
|
||||
- hostname: $(hostname)
|
||||
- user: $(whoami)
|
||||
- root: $ROOT
|
||||
- package: $ZIP_PATH
|
||||
|
||||
## Reuse Summary
|
||||
|
||||
- NPM_REUSED: UNKNOWN
|
||||
- WEBOPS_REUSED: UNKNOWN
|
||||
- CASDOOR_REUSED: UNKNOWN
|
||||
- OPENCLAW_REUSED: UNKNOWN
|
||||
- HERMES_REUSED: UNKNOWN
|
||||
- ONE_API_REUSED: UNKNOWN
|
||||
- GITEA_REUSED: UNKNOWN
|
||||
|
||||
EOF
|
||||
run_section "hostname" hostname
|
||||
run_section "date" date -Is
|
||||
run_section "whoami" whoami
|
||||
run_section "pwd" pwd
|
||||
run_section "df -h" df -h
|
||||
run_section "free -h" free -h
|
||||
run_section "docker ps" docker ps
|
||||
run_section "docker compose version" bash -lc 'docker compose version || docker-compose version || true'
|
||||
run_section "ss -lntp" ss -lntp
|
||||
run_section "ls /data" ls -lah /data
|
||||
run_section "ls /opt" ls -lah /opt
|
||||
run_section "component containers" bash -lc "docker ps | grep -Ei 'npm|nginx-proxy-manager|gitea|casdoor|openclaw|hermes|webops|one-api|postgres|redis|nats' || true"
|
||||
run_section "component paths" bash -lc "find /data /opt -maxdepth 4 \\( -iname '*npm*' -o -iname '*gitea*' -o -iname '*openclaw*' -o -iname '*webops*' -o -iname '*casdoor*' -o -iname '*one-api*' \\) 2>/dev/null || true"
|
||||
run_section "component services" bash -lc "systemctl list-units --type=service --no-pager | grep -Ei 'openclaw|hermes|aios|webops|npm|gitea|casdoor' || true"
|
||||
log "preflight done: $PRECHECK"
|
||||
}
|
||||
|
||||
backup_state() {
|
||||
log "backup start"
|
||||
local backup_dir="$ROOT/backups/pre-v1r1c0-$(date +%Y%m%d-%H%M%S)"
|
||||
mkdir -p "$backup_dir"
|
||||
cp -a /data/agent "$backup_dir/data-agent" 2>/dev/null || true
|
||||
cp -a /data/aios "$backup_dir/data-aios" 2>/dev/null || true
|
||||
cp -a /opt/aios "$backup_dir/opt-aios" 2>/dev/null || true
|
||||
cp -a /etc/systemd/system "$backup_dir/systemd-system" 2>/dev/null || true
|
||||
docker ps > "$backup_dir/docker-ps.txt" 2>/dev/null || true
|
||||
ss -lntp > "$backup_dir/ports.txt" 2>/dev/null || true
|
||||
find /data /opt -maxdepth 4 \( -iname '*npm*' -o -iname '*nginx-proxy-manager*' \) 2>/dev/null > "$backup_dir/npm-paths.txt" || true
|
||||
while IFS= read -r p; do
|
||||
[ -e "$p" ] || continue
|
||||
safe_name="$(printf '%s' "$p" | sed 's#^/##; s#[^A-Za-z0-9._-]#_#g')"
|
||||
cp -a "$p" "$backup_dir/$safe_name" 2>/dev/null || true
|
||||
done < "$backup_dir/npm-paths.txt"
|
||||
printf '%s\n' "$backup_dir" > "$ROOT/runtime/latest-backup-dir.txt"
|
||||
log "backup done: $backup_dir"
|
||||
}
|
||||
|
||||
import_zip() {
|
||||
log "import start"
|
||||
if [ ! -f "$ZIP_PATH" ]; then
|
||||
write_failed_report "locate_zip" "BLOCKED: missing $ZIP_NAME"
|
||||
log "BLOCKED: missing $ZIP_NAME"
|
||||
exit 2
|
||||
fi
|
||||
mkdir -p "$WORKSPACE"
|
||||
resolved="$(readlink -f "$WORKSPACE")"
|
||||
if [ "$resolved" != "$WORKSPACE" ]; then
|
||||
write_failed_report "workspace_resolve" "Unexpected workspace path: $resolved"
|
||||
exit 3
|
||||
fi
|
||||
find "$WORKSPACE" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +
|
||||
unzip -q -o "$ZIP_PATH" -d "$WORKSPACE"
|
||||
shopt -s nullglob dotglob
|
||||
entries=("$WORKSPACE"/*)
|
||||
if [ "${#entries[@]}" -eq 1 ] && [ -d "${entries[0]}" ]; then
|
||||
tmp_dir="$WORKSPACE/.flatten-tmp"
|
||||
mv "${entries[0]}" "$tmp_dir"
|
||||
shopt -s dotglob
|
||||
mv "$tmp_dir"/* "$WORKSPACE"/
|
||||
rmdir "$tmp_dir"
|
||||
fi
|
||||
chmod +x "$WORKSPACE"/package_verify_full.sh 2>/dev/null || true
|
||||
chmod +x "$WORKSPACE"/scripts/*.sh "$WORKSPACE"/tests/*.sh 2>/dev/null || true
|
||||
log "import done"
|
||||
}
|
||||
|
||||
run_package_checks() {
|
||||
log "package checks start"
|
||||
cd "$WORKSPACE"
|
||||
if ! bash scripts/package_contract_check_v1r1c0.sh > "$LOG_DIR/package_contract_check_v1r1c0.log" 2>&1; then
|
||||
write_failed_report "package_contract_check_v1r1c0" "scripts/package_contract_check_v1r1c0.sh failed; see $LOG_DIR/package_contract_check_v1r1c0.log"
|
||||
exit 4
|
||||
fi
|
||||
if ! bash tests/run_v1r1c0_acceptance.sh > "$LOG_DIR/run_v1r1c0_acceptance.log" 2>&1; then
|
||||
write_failed_report "run_v1r1c0_acceptance" "tests/run_v1r1c0_acceptance.sh failed; see $LOG_DIR/run_v1r1c0_acceptance.log"
|
||||
exit 5
|
||||
fi
|
||||
if ! ./package_verify_full.sh > "$LOG_DIR/package_verify_full.log" 2>&1; then
|
||||
write_failed_report "package_verify_full" "package_verify_full.sh failed; see $LOG_DIR/package_verify_full.log"
|
||||
exit 6
|
||||
fi
|
||||
log "package checks done"
|
||||
}
|
||||
|
||||
write_baseline_files() {
|
||||
log "write baseline files"
|
||||
cd "$WORKSPACE"
|
||||
mkdir -p baseline versions patches architecture evolution deploy ops tests docs/gitops docs/release
|
||||
cat > baseline/current.json <<'EOF'
|
||||
{
|
||||
"active_version": "V1R1C0",
|
||||
"active_tag": "V1R1C0-baseline-20260706",
|
||||
"mode": "gitops-online-baseline",
|
||||
"version_policy": "VxRxCx",
|
||||
"update_mode": "git_patch_only",
|
||||
"zip_update_deprecated": true,
|
||||
"entry": "https://cnnetmkt.top",
|
||||
"links": {
|
||||
"git": "/git",
|
||||
"baseline": "/base",
|
||||
"versions": "/ver",
|
||||
"diff": "/diff",
|
||||
"patch": "/patch",
|
||||
"model": "/model",
|
||||
"runtime": "/run",
|
||||
"api": "/api",
|
||||
"hook": "/hook"
|
||||
},
|
||||
"features": {
|
||||
"multiuser_runtime": true,
|
||||
"shared_dedicated_runtime": true,
|
||||
"custom_llm": true,
|
||||
"user_model_priority": true,
|
||||
"chatgpt_rw": true,
|
||||
"codex_rw": true
|
||||
}
|
||||
}
|
||||
EOF
|
||||
cat > versions/V1R1C0.json <<'EOF'
|
||||
{
|
||||
"version": "V1R1C0",
|
||||
"tag": "V1R1C0-baseline-20260706",
|
||||
"type": "baseline",
|
||||
"name": "MultiUser GitOps Custom LLM Baseline",
|
||||
"source": "AIOS_V1R1C0_MULTIUSER_GITOPS_CUSTOM_LLM_PROD_FULL_DEPLOY_20260706.zip",
|
||||
"update_mode": "git_patch_only",
|
||||
"zip_update_mode": "deprecated_after_import"
|
||||
}
|
||||
EOF
|
||||
cat > architecture/current.json <<'EOF'
|
||||
{
|
||||
"version": "V1R1C0",
|
||||
"architecture_version": "V1",
|
||||
"release_version": "R1",
|
||||
"change_version": "C0",
|
||||
"components": [
|
||||
"webops",
|
||||
"owner-admin-user",
|
||||
"npm",
|
||||
"casdoor",
|
||||
"openclaw",
|
||||
"hermes",
|
||||
"one-api",
|
||||
"gitops-control",
|
||||
"baseline-registry",
|
||||
"custom-llm-registry",
|
||||
"runtime-selector"
|
||||
],
|
||||
"entry": "https://cnnetmkt.top",
|
||||
"proxy": "npm",
|
||||
"runtime_sync": "openclaw"
|
||||
}
|
||||
EOF
|
||||
cat > docs/release/VxRxCx_RULES.md <<'EOF'
|
||||
# AIOS VxRxCx Versioning Rules
|
||||
|
||||
VxRxCx is the only supported online baseline version format.
|
||||
|
||||
- V: architecture version
|
||||
- R: release version
|
||||
- C: change version
|
||||
|
||||
V-level changes require owner/admin approval and blue-green or equivalent safe switching.
|
||||
R-level changes require canary or test-environment validation.
|
||||
C-level changes require smoke test and automatic rollback on failure.
|
||||
|
||||
Initial imported online baseline for this package: V1R1C0-baseline-20260706.
|
||||
EOF
|
||||
cat > docs/gitops/CHATGPT_CODEX_ACCESS.md <<'EOF'
|
||||
# ChatGPT / Codex GitOps Access
|
||||
|
||||
Entry: https://cnnetmkt.top
|
||||
|
||||
Short links: /git, /base, /ver, /diff, /patch, /model, /run, /api, /hook.
|
||||
|
||||
All writes must become Git commits. Fixes must use patch branches. Releases must use VxRxCx tags.
|
||||
|
||||
Forbidden:
|
||||
|
||||
- direct production edits without Git history
|
||||
- API keys in Git
|
||||
- API keys in plaintext logs
|
||||
- bypassing owner/admin approval for V-level changes
|
||||
EOF
|
||||
}
|
||||
|
||||
init_git() {
|
||||
log "git init start"
|
||||
cd "$WORKSPACE"
|
||||
git init
|
||||
git config user.name "AIOS GitOps Bot"
|
||||
git config user.email "gitops-bot@cnnetmkt.top"
|
||||
if [ ! -f .gitignore ]; then
|
||||
cat > .gitignore <<'EOF'
|
||||
.env
|
||||
*.secret
|
||||
*.key
|
||||
*.pem
|
||||
*.log
|
||||
__pycache__/
|
||||
node_modules/
|
||||
.venv/
|
||||
.DS_Store
|
||||
runtime/tmp/
|
||||
runtime/cache/
|
||||
logs/
|
||||
EOF
|
||||
fi
|
||||
git add .
|
||||
git commit -m "init(baseline): import AIOS V1R1C0 multiuser gitops custom llm baseline" || true
|
||||
git branch -M main
|
||||
git tag -a "$BASELINE_TAG" -m "AIOS V1R1C0 online baseline: multiuser gitops custom LLM" 2>/dev/null || true
|
||||
log "git init done"
|
||||
}
|
||||
|
||||
write_acceptance() {
|
||||
log "write acceptance report"
|
||||
cd "$WORKSPACE"
|
||||
cat > "$ACCEPT_REPORT" <<EOF
|
||||
# AIOS V1R1C0 Online Baseline Acceptance
|
||||
|
||||
1. server: $(hostname)
|
||||
2. zip_path: $ZIP_PATH
|
||||
3. npm_reused: PARTIAL_UNVERIFIED
|
||||
4. webops_reused: YES_DETECTED
|
||||
5. casdoor_reused: PARTIAL_UNVERIFIED
|
||||
6. openclaw_reused: YES_DETECTED
|
||||
7. hermes_reused: YES_DETECTED
|
||||
8. one_api_reused: YES_DETECTED
|
||||
9. git_service: LOCAL_GIT_READY_REMOTE_UI_PENDING
|
||||
10. git_url: PENDING_GITEA_OR_EXISTING_GIT_SERVICE
|
||||
11. short_links: /git,/base,/ver,/diff,/patch,/model,/run,/api,/hook
|
||||
12. baseline_version: V1R1C0
|
||||
13. baseline_tag: $BASELINE_TAG
|
||||
14. custom_llm_api: PACKAGE_UAT_PASS_ONLINE_ROUTE_PENDING
|
||||
15. model_priority: PACKAGE_UAT_PASS_ONLINE_ROUTE_PENDING
|
||||
16. runtime_selector: PACKAGE_UAT_PASS_ONLINE_ROUTE_PENDING
|
||||
17. gitops_patch: BASELINE_COMMIT_AND_TAG_READY
|
||||
18. smoke_test: PACKAGE_CHECKS_PASS
|
||||
19. known_issues:
|
||||
- NPM custom locations were not modified by this script.
|
||||
- Gitea/Web Git UI push was not completed by this script.
|
||||
- Spark sub-agent quota was unavailable during orchestration.
|
||||
20. next_update_mode: git_patch_only
|
||||
|
||||
## Git Evidence
|
||||
EOF
|
||||
{
|
||||
printf '\n### git status --short\n\n'
|
||||
git status --short
|
||||
printf '\n### git log --oneline -10\n\n'
|
||||
git log --oneline -10
|
||||
printf '\n### git tag grep V1R1C0\n\n'
|
||||
git tag | grep V1R1C0 || true
|
||||
} >> "$ACCEPT_REPORT"
|
||||
}
|
||||
|
||||
main() {
|
||||
preflight
|
||||
backup_state
|
||||
import_zip
|
||||
run_package_checks
|
||||
write_baseline_files
|
||||
init_git
|
||||
write_acceptance
|
||||
log "MIGRATION_LOCAL_BASELINE_OK"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="/data/aios-gitops"
|
||||
WORKSPACE="$ROOT/workspace"
|
||||
LOG="$ROOT/logs/runtime-services-v1r1c0.log"
|
||||
mkdir -p "$ROOT/logs" "$ROOT/runtime/secrets" "$WORKSPACE/deploy/systemd" "$WORKSPACE/deploy/gitea"
|
||||
touch "$LOG"
|
||||
|
||||
log() {
|
||||
printf '[%s] %s\n' "$(date -Is)" "$*" | tee -a "$LOG"
|
||||
}
|
||||
|
||||
cd "$WORKSPACE"
|
||||
|
||||
TOKEN_FILE="$ROOT/runtime/secrets/gitops.tokens"
|
||||
BARE_REPO="$ROOT/runtime/gitea-bin/data/repositories/aios/aios-v23.git"
|
||||
if [ ! -s "$TOKEN_FILE" ]; then
|
||||
(openssl rand -hex 32 || python3 -c 'import secrets; print(secrets.token_hex(32))') > "$TOKEN_FILE"
|
||||
chmod 600 "$TOKEN_FILE"
|
||||
fi
|
||||
|
||||
cat > deploy/systemd/aios-gitops-model-api.service <<EOF
|
||||
[Unit]
|
||||
Description=AIOS V1R1C0 GitOps Model API
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=aiman
|
||||
Group=aiman
|
||||
Environment=AIOS_GITOPS_ROOT=$ROOT
|
||||
Environment=AIOS_GITOPS_WORKSPACE=$WORKSPACE
|
||||
Environment=AIOS_GITOPS_DB=$ROOT/runtime/gitops_model.sqlite3
|
||||
Environment=AIOS_GITOPS_TOKEN_FILE=$TOKEN_FILE
|
||||
Environment=AIOS_GITOPS_BARE_REPO=$BARE_REPO
|
||||
Environment=AIOS_GITOPS_HOST=127.0.0.1
|
||||
Environment=AIOS_GITOPS_PORT=18890
|
||||
ExecStart=/usr/bin/python3 $WORKSPACE/active-runtime/app/aios_chatgpt_actions_api.py
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
cat > deploy/gitea/docker-compose.yml <<'EOF'
|
||||
services:
|
||||
gitea:
|
||||
image: gitea/gitea:1.22
|
||||
container_name: aios-gitea
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- USER_UID=1000
|
||||
- USER_GID=1000
|
||||
- GITEA__server__DOMAIN=cnnetmkt.top
|
||||
- GITEA__server__ROOT_URL=https://cnnetmkt.top/git/
|
||||
- GITEA__server__SERVE_FROM_SUB_PATH=true
|
||||
- GITEA__server__HTTP_PORT=3000
|
||||
- GITEA__server__SSH_DOMAIN=cnnetmkt.top
|
||||
- GITEA__server__SSH_PORT=2222
|
||||
- GITEA__service__DISABLE_REGISTRATION=true
|
||||
- GITEA__webhook__ALLOWED_HOST_LIST=*
|
||||
- GITEA__database__DB_TYPE=sqlite3
|
||||
volumes:
|
||||
- /data/aios-gitops/runtime/gitea:/data
|
||||
ports:
|
||||
- "127.0.0.1:3000:3000"
|
||||
- "127.0.0.1:2222:22"
|
||||
EOF
|
||||
|
||||
git add deploy/systemd/aios-gitops-model-api.service deploy/gitea/docker-compose.yml
|
||||
git commit -m "chore(gitops): add V1R1C0 runtime API and Gitea deployment manifests" || true
|
||||
|
||||
log "install API systemd unit"
|
||||
sudo cp deploy/systemd/aios-gitops-model-api.service /etc/systemd/system/aios-gitops-model-api.service
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now aios-gitops-model-api.service
|
||||
sleep 2
|
||||
systemctl is-active aios-gitops-model-api.service | tee -a "$LOG"
|
||||
curl -fsS http://127.0.0.1:18890/health | tee -a "$LOG"
|
||||
|
||||
log "start gitea"
|
||||
mkdir -p "$ROOT/adapters/gitea"
|
||||
cp deploy/gitea/docker-compose.yml "$ROOT/adapters/gitea/docker-compose.yml"
|
||||
(cd "$ROOT/adapters/gitea" && docker compose up -d)
|
||||
|
||||
for i in $(seq 1 60); do
|
||||
if curl -fsS http://127.0.0.1:3000/git/api/healthz >/dev/null 2>&1 || curl -fsS http://127.0.0.1:3000/git/ >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
curl -fsS http://127.0.0.1:3000/git/ >/dev/null || true
|
||||
docker ps --format '{{.Names}} {{.Image}} {{.Status}} {{.Ports}}' | grep -E '^aios-gitea ' | tee -a "$LOG" || true
|
||||
|
||||
log "runtime manifests committed and services attempted"
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="/data/aios-gitops"
|
||||
WORKSPACE="$ROOT/workspace"
|
||||
|
||||
cd "$WORKSPACE"
|
||||
git add active-runtime/app/aios_gitops_model_api.py scripts/hermes_gitops_parallel_agents.py
|
||||
git commit -m "fix(gitops): align baseline tag and secret scan" || true
|
||||
|
||||
python3 - <<'PY'
|
||||
import datetime
|
||||
import sqlite3
|
||||
|
||||
p = "/data/aios-gitops/runtime/gitops_model.sqlite3"
|
||||
con = sqlite3.connect(p)
|
||||
con.execute(
|
||||
"UPDATE gitops_baseline SET active_version=?, active_tag=?, source_package=?, update_mode=?, updated_at=? WHERE id=?",
|
||||
(
|
||||
"V1R1C0",
|
||||
"V1R1C0-baseline-20260706",
|
||||
"AIOS_V1R1C0_MULTIUSER_GITOPS_CUSTOM_LLM_PROD_FULL_DEPLOY_20260706.zip",
|
||||
"git_patch_only",
|
||||
datetime.datetime.utcnow().isoformat() + "Z",
|
||||
"current",
|
||||
),
|
||||
)
|
||||
con.commit()
|
||||
con.close()
|
||||
PY
|
||||
|
||||
sudo chown -R aiman:aiman "$ROOT/runtime" "$WORKSPACE" "$ROOT/logs" "$ROOT/artifacts"
|
||||
sudo systemctl restart aios-gitops-model-api.service
|
||||
sleep 2
|
||||
curl -fsS http://127.0.0.1:18890/api/status
|
||||
echo
|
||||
python3 scripts/hermes_gitops_parallel_agents.py
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="/data/aios-gitops"
|
||||
WORKSPACE="$ROOT/workspace"
|
||||
NGINX_SITE="/etc/nginx/sites-enabled/aios-user-portal.conf"
|
||||
BACKUP_DIR="$ROOT/backups/nginx-gitops-$(date +%Y%m%d-%H%M%S)"
|
||||
LOG="$ROOT/logs/nginx-gitops-routes.log"
|
||||
mkdir -p "$BACKUP_DIR" "$ROOT/logs" "$WORKSPACE/deploy/nginx"
|
||||
exec > >(tee -a "$LOG") 2>&1
|
||||
|
||||
cd "$WORKSPACE"
|
||||
|
||||
cat > deploy/nginx/aios-gitops-locations.conf <<'EOF'
|
||||
# BEGIN AIOS V1R1C0 GitOps short links
|
||||
location = /git { return 302 /git/; }
|
||||
location /git/ {
|
||||
proxy_pass http://127.0.0.1:3000/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Prefix /git;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location = /api { return 302 /api/; }
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:18890/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location = /base { proxy_pass http://127.0.0.1:18890/base; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; }
|
||||
location = /ver { proxy_pass http://127.0.0.1:18890/ver; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; }
|
||||
location = /diff { proxy_pass http://127.0.0.1:18890/diff; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; }
|
||||
location = /patch { proxy_pass http://127.0.0.1:18890/patch; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; }
|
||||
location = /model { proxy_pass http://127.0.0.1:18890/model; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; }
|
||||
location = /run { proxy_pass http://127.0.0.1:18890/run; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; }
|
||||
location = /hook { proxy_pass http://127.0.0.1:18890/hook; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; }
|
||||
location /hook/ {
|
||||
proxy_pass http://127.0.0.1:18890/hook/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
# END AIOS V1R1C0 GitOps short links
|
||||
EOF
|
||||
|
||||
git add deploy/nginx/aios-gitops-locations.conf active-runtime/app/aios_gitops_model_api.py
|
||||
git commit -m "feat(gitops): add nginx short links and webhook endpoint" || true
|
||||
|
||||
echo "== backup nginx site =="
|
||||
sudo cp -a "$NGINX_SITE" "$BACKUP_DIR/aios-user-portal.conf"
|
||||
|
||||
echo "== insert locations =="
|
||||
python3 - <<'PY'
|
||||
from pathlib import Path
|
||||
|
||||
site = Path("/etc/nginx/sites-enabled/aios-user-portal.conf")
|
||||
snippet = Path("/data/aios-gitops/workspace/deploy/nginx/aios-gitops-locations.conf").read_text()
|
||||
text = site.read_text()
|
||||
start = " # BEGIN AIOS V1R1C0 GitOps short links"
|
||||
end = " # END AIOS V1R1C0 GitOps short links"
|
||||
if start in text:
|
||||
before = text.split(start)[0]
|
||||
after = text.split(end, 1)[1]
|
||||
text = before + snippet + after
|
||||
else:
|
||||
marker = " location / {\n"
|
||||
if marker not in text:
|
||||
raise SystemExit("location marker not found")
|
||||
text = text.replace(marker, snippet + "\n" + marker, 1)
|
||||
Path("/tmp/aios-user-portal.conf.gitops").write_text(text)
|
||||
PY
|
||||
sudo cp /tmp/aios-user-portal.conf.gitops "$NGINX_SITE"
|
||||
|
||||
echo "== nginx test and reload =="
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
|
||||
echo "== local route checks =="
|
||||
for path in /git/ /base /ver /model /run /hook /api/status; do
|
||||
echo "== ${path} =="
|
||||
curl -k -sS -I --resolve cnnetmkt.top:443:127.0.0.1 "https://cnnetmkt.top${path}" | sed -n '1,12p' || true
|
||||
done
|
||||
echo "NGINX_GITOPS_ROUTES_OK backup=$BACKUP_DIR"
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
WORKSPACE="${AIOS_GITOPS_WORKSPACE:-/data/aios-gitops/workspace}"
|
||||
RUNTIME_ROOT="${AIOS_USER_CONTROL_ROOT:-/data/aios/aios_r13_v18_runtime_portable_silent_20260704/components/user-control-center}"
|
||||
CONTAINER="${AIOS_USER_CONTROL_CONTAINER:-user-control-web}"
|
||||
|
||||
install -D -m 0644 "$WORKSPACE/overlays/user-control-center/control/portal_views.py" "$RUNTIME_ROOT/control/portal_views.py"
|
||||
install -D -m 0644 "$WORKSPACE/overlays/user-control-center/templates/portal/me.html" "$RUNTIME_ROOT/templates/portal/me.html"
|
||||
install -D -m 0644 "$WORKSPACE/baseline/current.json" "$RUNTIME_ROOT/runtime/gitops/current.json"
|
||||
|
||||
if command -v docker >/dev/null 2>&1 && docker ps --format '{{.Names}}' | grep -qx "$CONTAINER"; then
|
||||
docker cp "$RUNTIME_ROOT/control/portal_views.py" "$CONTAINER:/app/control/portal_views.py"
|
||||
docker cp "$RUNTIME_ROOT/templates/portal/me.html" "$CONTAINER:/app/templates/portal/me.html"
|
||||
docker exec "$CONTAINER" mkdir -p /app/runtime/gitops
|
||||
docker cp "$RUNTIME_ROOT/runtime/gitops/current.json" "$CONTAINER:/app/runtime/gitops/current.json"
|
||||
docker restart "$CONTAINER" >/dev/null
|
||||
fi
|
||||
|
||||
echo "AIOS_USER_PORTAL_GITOPS_OVERLAY_APPLIED runtime=$RUNTIME_ROOT container=$CONTAINER"
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PUBLIC_ENTRY="${PUBLIC_ENTRY:-https://cnnetmkt.top/}"
|
||||
AUTH_ENTRY="${AUTH_ENTRY:-https://auth.cnnetmkt.top}"
|
||||
CLIENT_ID="${CASDOOR_CLIENT_ID:-aios8a810963c1dd368a}"
|
||||
CALLBACK="${CASDOOR_CALLBACK:-https://cnnetmkt.top/casdoor/callback/}"
|
||||
AIHOST_HOST="${AIHOST_HOST:-aihost}"
|
||||
HW_HOST="${HW_HOST:-hw.imruyi.top}"
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
CALLBACK_ENCODED="$(python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "$CALLBACK")"
|
||||
AUTH_URL="$AUTH_ENTRY/login/oauth/authorize?client_id=$CLIENT_ID&response_type=code&redirect_uri=$CALLBACK_ENCODED&scope=read&state=aios-portal&language=zh"
|
||||
|
||||
printf '== public redirect chain ==\n'
|
||||
chain="$(curl -k -sS -L -I --max-redirs 4 "$PUBLIC_ENTRY")"
|
||||
printf '%s\n' "$chain"
|
||||
printf '%s\n' "$chain" | grep -F "Location: $AUTH_ENTRY/login/oauth/authorize" >/dev/null \
|
||||
|| fail "public entry did not redirect to $AUTH_ENTRY"
|
||||
if printf '%s\n' "$chain" | grep -F 'auth.h.imruyi.top:8443' >/dev/null; then
|
||||
fail 'public chain still exposes backup auth domain'
|
||||
fi
|
||||
|
||||
printf '\n== auth authorize http ==\n'
|
||||
auth_headers="$(curl -k -sS -D - -o /tmp/aios-auth-primary-body --max-time 20 "$AUTH_URL")"
|
||||
printf '%s\n' "$auth_headers"
|
||||
printf '%s\n' "$auth_headers" | grep -F 'HTTP/' >/dev/null || fail 'auth endpoint returned no HTTP headers'
|
||||
if grep -E '许可跳转列表|redirect.*not|not.*redirect' /tmp/aios-auth-primary-body >/dev/null 2>&1; then
|
||||
fail 'auth body still contains redirect allow-list error text'
|
||||
fi
|
||||
|
||||
if command -v ssh >/dev/null 2>&1; then
|
||||
printf '\n== casdoor db redirect allow-list ==\n'
|
||||
redirects="$(ssh "$AIHOST_HOST" "docker exec casdoor-postgres psql -U casdoor -d casdoor -Atc \"select redirect_uris from application where owner='admin' and name='aios-portal';\"" 2>/dev/null || true)"
|
||||
if [ -n "$redirects" ]; then
|
||||
printf '%s\n' "$redirects"
|
||||
printf '%s\n' "$redirects" | grep -F "$CALLBACK" >/dev/null \
|
||||
|| fail "Casdoor DB does not include $CALLBACK"
|
||||
else
|
||||
printf 'WARN: skipped Casdoor DB readback; set AIHOST_HOST to a reachable SSH target for a strict DB check.\n'
|
||||
fi
|
||||
|
||||
printf '\n== ucc runtime env ==\n'
|
||||
runtime_env="$(docker inspect user-control-web --format '{{range .Config.Env}}{{println .}}{{end}}' 2>/dev/null | grep -E 'UCC_PUBLIC_BASE_URL|CASDOOR_(PUBLIC|INTERNAL|INVITE_SYNC)_BASE_URL' || true)"
|
||||
if [ -z "$runtime_env" ]; then
|
||||
runtime_env="$(ssh "$HW_HOST" 'docker inspect user-control-web --format "{{range .Config.Env}}{{println .}}{{end}}" | grep -E "UCC_PUBLIC_BASE_URL|CASDOOR_(PUBLIC|INTERNAL|INVITE_SYNC)_BASE_URL"' 2>/dev/null || true)"
|
||||
fi
|
||||
if [ -n "$runtime_env" ]; then
|
||||
printf '%s\n' "$runtime_env"
|
||||
printf '%s\n' "$runtime_env" | grep -F "CASDOOR_PUBLIC_BASE_URL=$AUTH_ENTRY" >/dev/null \
|
||||
|| fail 'UCC runtime CASDOOR_PUBLIC_BASE_URL is not primary auth domain'
|
||||
else
|
||||
printf 'WARN: skipped UCC runtime env readback; run on hw.imruyi.top or set HW_HOST to a reachable SSH target.\n'
|
||||
fi
|
||||
fi
|
||||
|
||||
printf '\nPASS: cnnetmkt primary auth route is canonical and callback is allowed.\n'
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "== host =="
|
||||
hostname
|
||||
date -Is
|
||||
|
||||
echo "== /etc/hosts cnnetmkt =="
|
||||
grep -nE 'cnnetmkt|gitea|aios' /etc/hosts || true
|
||||
|
||||
echo "== local resolver =="
|
||||
for n in cnnetmkt.top g.cnnetmkt.top user.cnnetmkt.top portal.cnnetmkt.top; do
|
||||
echo "-- ${n}"
|
||||
getent ahosts "${n}" || true
|
||||
done
|
||||
|
||||
echo "== DoH public DNS =="
|
||||
for n in cnnetmkt.top g.cnnetmkt.top user.cnnetmkt.top portal.cnnetmkt.top; do
|
||||
echo "-- ${n}"
|
||||
curl -fsS --connect-timeout 10 --max-time 20 \
|
||||
-H 'accept: application/dns-json' \
|
||||
"https://cloudflare-dns.com/dns-query?name=${n}&type=A" || true
|
||||
echo
|
||||
done
|
||||
|
||||
echo "== local SNI HTTP/TLS =="
|
||||
for h in cnnetmkt.top g.cnnetmkt.top user.cnnetmkt.top portal.cnnetmkt.top; do
|
||||
echo "-- ${h} curl /"
|
||||
rm -f /tmp/aios-ingress-body
|
||||
curl -k -sS -D - -o /tmp/aios-ingress-body \
|
||||
--resolve "${h}:443:127.0.0.1" \
|
||||
"https://${h}/" | sed -n '1,20p' || true
|
||||
sed -n '1,16p' /tmp/aios-ingress-body 2>/dev/null || true
|
||||
echo "-- ${h} curl /git/"
|
||||
curl -k -sS -D - -o /tmp/aios-ingress-body \
|
||||
--resolve "${h}:443:127.0.0.1" \
|
||||
"https://${h}/git/" | sed -n '1,12p' || true
|
||||
grep -ao '<title>[^<]*' /tmp/aios-ingress-body 2>/dev/null | head -1 || true
|
||||
echo "-- ${h} cert"
|
||||
echo | openssl s_client -connect 127.0.0.1:443 -servername "${h}" 2>/dev/null \
|
||||
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName 2>/dev/null || true
|
||||
done
|
||||
|
||||
echo "== certbot certificates =="
|
||||
sudo certbot certificates || true
|
||||
|
||||
echo "== nginx matched config =="
|
||||
sudo nginx -T 2>/dev/null | grep -nE 'server_name|ssl_certificate|location = /git|location /git|proxy_pass http://127.0.0.1:3000' || true
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
for path in /git/ /base /ver /model /run /hook /api/status; do
|
||||
echo "== ${path} =="
|
||||
curl -k -sS --max-time 10 --resolve cnnetmkt.top:443:127.0.0.1 "https://cnnetmkt.top${path}" | head -20 || true
|
||||
done
|
||||
|
||||
echo "== direct API =="
|
||||
for path in /base /ver /model /run /hook /api/status; do
|
||||
echo "== 18890${path} =="
|
||||
curl -sS --max-time 10 "http://127.0.0.1:18890${path}" | head -10 || true
|
||||
done
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from huaweicloudsdkcore.auth.credentials import BasicCredentials
|
||||
from huaweicloudsdkdns.v2 import (
|
||||
CreateRecordSetRequest,
|
||||
CreateRecordSetRequestBody,
|
||||
DnsClient,
|
||||
ListRecordSetsByZoneRequest,
|
||||
ListPublicZonesRequest,
|
||||
UpdateRecordSetRequest,
|
||||
UpdateRecordSetReq,
|
||||
)
|
||||
from huaweicloudsdkdns.v2.region.dns_region import DnsRegion
|
||||
|
||||
|
||||
ENV_FILE = Path("/etc/letsencrypt/huaweicloud-cnnetmkt.env")
|
||||
ZONE_NAME = "cnnetmkt.top."
|
||||
RECORD_NAME = "cnnetmkt.top."
|
||||
TARGET_IP = "1.95.164.183"
|
||||
|
||||
|
||||
def load_env() -> None:
|
||||
for line in ENV_FILE.read_text().splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
key, value = line.split("=", 1)
|
||||
os.environ.setdefault(key, value)
|
||||
|
||||
|
||||
def make_client() -> DnsClient:
|
||||
ak = os.environ["HUAWEICLOUD_ACCESS_KEY_ID"]
|
||||
sk = os.environ["HUAWEICLOUD_SECRET_ACCESS_KEY"]
|
||||
region = os.environ.get("HUAWEICLOUD_REGION", "cn-southwest-2")
|
||||
credentials = BasicCredentials(ak, sk)
|
||||
return DnsClient.new_builder().with_credentials(credentials).with_region(DnsRegion.value_of(region)).build()
|
||||
|
||||
|
||||
def as_dict(obj):
|
||||
if hasattr(obj, "to_json_object"):
|
||||
return obj.to_json_object()
|
||||
if hasattr(obj, "to_dict"):
|
||||
return obj.to_dict()
|
||||
return obj
|
||||
|
||||
|
||||
def main() -> int:
|
||||
load_env()
|
||||
client = make_client()
|
||||
|
||||
zones_resp = client.list_public_zones(ListPublicZonesRequest(name=ZONE_NAME))
|
||||
zones = getattr(zones_resp, "zones", None) or []
|
||||
if not zones:
|
||||
raise RuntimeError(f"zone not found: {ZONE_NAME}")
|
||||
zone = zones[0]
|
||||
zone_id = zone.id
|
||||
|
||||
rs_req = ListRecordSetsByZoneRequest(zone_id=zone_id, name=RECORD_NAME, type="A")
|
||||
rs_resp = client.list_record_sets_by_zone(rs_req)
|
||||
recordsets = getattr(rs_resp, "recordsets", None) or []
|
||||
existing = None
|
||||
for item in recordsets:
|
||||
if item.name == RECORD_NAME and item.type == "A":
|
||||
existing = item
|
||||
break
|
||||
|
||||
if existing:
|
||||
records = list(existing.records or [])
|
||||
if records == [TARGET_IP] and existing.status != "DISABLE":
|
||||
action = "unchanged"
|
||||
result = existing
|
||||
else:
|
||||
body = UpdateRecordSetReq(
|
||||
name=RECORD_NAME,
|
||||
type="A",
|
||||
ttl=300,
|
||||
records=[TARGET_IP],
|
||||
description="AIOS GitOps apex entry",
|
||||
)
|
||||
req = UpdateRecordSetRequest(zone_id=zone_id, recordset_id=existing.id, body=body)
|
||||
result = client.update_record_set(req)
|
||||
action = "updated"
|
||||
else:
|
||||
body = CreateRecordSetRequestBody(
|
||||
name=RECORD_NAME,
|
||||
type="A",
|
||||
ttl=300,
|
||||
records=[TARGET_IP],
|
||||
description="AIOS GitOps apex entry",
|
||||
)
|
||||
req = CreateRecordSetRequest(zone_id=zone_id, body=body)
|
||||
result = client.create_record_set(req)
|
||||
action = "created"
|
||||
|
||||
print(json.dumps({
|
||||
"action": action,
|
||||
"zone_id": zone_id,
|
||||
"record": RECORD_NAME,
|
||||
"type": "A",
|
||||
"records": [TARGET_IP],
|
||||
"result": as_dict(result),
|
||||
}, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="/data/aios-gitops"
|
||||
WORKSPACE="$ROOT/workspace"
|
||||
ARTIFACT="$ROOT/artifacts/AIOS_V1R1C0_ONLINE_BASELINE_ACCEPTANCE.md"
|
||||
REPORT_IN_REPO="$WORKSPACE/docs/release/AIOS_V1R1C0_ONLINE_BASELINE_ACCEPTANCE.md"
|
||||
EVIDENCE="$ROOT/artifacts/AIOS_V1R1C0_FINAL_EVIDENCE.txt"
|
||||
VERSION="V1R1C0"
|
||||
TAG="V1R1C0-baseline-20260706"
|
||||
SOURCE="AIOS_V1R1C0_MULTIUSER_GITOPS_CUSTOM_LLM_PROD_FULL_DEPLOY_20260706.zip"
|
||||
GIT_URL="https://cnnetmkt.top/git/aios/aios-v23.git"
|
||||
|
||||
mkdir -p "$ROOT/artifacts" "$ROOT/logs" "$WORKSPACE/docs/release"
|
||||
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
import sqlite3
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
root = Path("/data/aios-gitops")
|
||||
workspace = root / "workspace"
|
||||
db = root / "runtime" / "gitops_model.sqlite3"
|
||||
version = "V1R1C0"
|
||||
tag = "V1R1C0-baseline-20260706"
|
||||
source = "AIOS_V1R1C0_MULTIUSER_GITOPS_CUSTOM_LLM_PROD_FULL_DEPLOY_20260706.zip"
|
||||
entry = "https://cnnetmkt.top"
|
||||
git_url = "https://cnnetmkt.top/git/aios/aios-v23.git"
|
||||
short_links = ["/git", "/base", "/ver", "/diff", "/patch", "/model", "/run", "/api", "/hook"]
|
||||
|
||||
current_path = workspace / "baseline" / "current.json"
|
||||
current = {}
|
||||
if current_path.exists():
|
||||
current = json.loads(current_path.read_text(encoding="utf-8"))
|
||||
current.update({
|
||||
"version": version,
|
||||
"active_version": version,
|
||||
"baseline_tag": tag,
|
||||
"active_tag": tag,
|
||||
"source": source,
|
||||
"source_package": source,
|
||||
"mode": "gitops-online",
|
||||
"update_mode": "git_patch_only",
|
||||
"zip_update_mode": "deprecated",
|
||||
"chatgpt_access": True,
|
||||
"codex_access": True,
|
||||
"git_url": git_url,
|
||||
"entry": entry,
|
||||
"short_links": short_links,
|
||||
"updated_at": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
current_path.write_text(json.dumps(current, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
version_path = workspace / "versions" / "V1R1C0.json"
|
||||
version_doc = {}
|
||||
if version_path.exists():
|
||||
version_doc = json.loads(version_path.read_text(encoding="utf-8"))
|
||||
version_doc.update({
|
||||
"version": version,
|
||||
"tag": tag,
|
||||
"baseline_tag": tag,
|
||||
"source": source,
|
||||
"update_mode": "git_patch_only",
|
||||
"zip_update_mode": "deprecated",
|
||||
"git_url": git_url,
|
||||
"updated_at": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
version_path.write_text(json.dumps(version_doc, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
if db.exists():
|
||||
con = sqlite3.connect(db)
|
||||
try:
|
||||
con.execute(
|
||||
"UPDATE gitops_baseline SET active_version=?, active_tag=?, source_package=?, entry=?, update_mode=?, status=?, updated_at=? WHERE id='current'",
|
||||
(version, tag, source, entry, "git_patch_only", "active", datetime.now(timezone.utc).isoformat()),
|
||||
)
|
||||
con.execute(
|
||||
"UPDATE gitops_versions SET tag=?, status=? WHERE version=?",
|
||||
(tag, "ready", version),
|
||||
)
|
||||
con.commit()
|
||||
finally:
|
||||
con.close()
|
||||
PY
|
||||
|
||||
cd "$WORKSPACE"
|
||||
|
||||
{
|
||||
echo "== services =="
|
||||
systemctl is-active aios-gitops-model-api aios-gitea aios-hermes-router aios-hermes-executor openclaw-gateway nginx || true
|
||||
echo
|
||||
echo "== gitea =="
|
||||
curl -fsS http://127.0.0.1:3000/api/v1/version || true
|
||||
echo
|
||||
echo "== git =="
|
||||
git status --short
|
||||
git --no-pager log --oneline -8
|
||||
git tag --list 'V1R1C0*'
|
||||
git remote -v
|
||||
echo
|
||||
echo "== api health =="
|
||||
curl -fsS http://127.0.0.1:18890/health
|
||||
echo
|
||||
curl -fsS http://127.0.0.1:18890/api/status
|
||||
echo
|
||||
echo "== short links =="
|
||||
for path in /git/ /base /ver /diff /patch /model /run /api /hook /api/status; do
|
||||
tmp="$(mktemp)"
|
||||
code="$(curl -k -sS --max-time 10 --resolve cnnetmkt.top:443:127.0.0.1 -o "$tmp" -w "%{http_code}" "https://cnnetmkt.top${path}" || true)"
|
||||
title="$(grep -ao '<title>[^<]*' "$tmp" | head -1 | sed 's/<title>//' || true)"
|
||||
bytes="$(wc -c < "$tmp" | tr -d ' ')"
|
||||
printf '%s code=%s bytes=%s title=%s\n' "$path" "$code" "$bytes" "$title"
|
||||
rm -f "$tmp"
|
||||
done
|
||||
echo
|
||||
echo "== webhook smoke =="
|
||||
payload="$(mktemp)"
|
||||
cat > "$payload" <<'JSON'
|
||||
{"event":"manual-smoke","ref":"refs/heads/main","repository":{"full_name":"aios/aios-v23"}}
|
||||
JSON
|
||||
curl -fsS -H 'Content-Type: application/json' --data-binary "@${payload}" http://127.0.0.1:18890/hook/git || true
|
||||
rm -f "$payload"
|
||||
echo
|
||||
echo "== package smoke =="
|
||||
bash "$WORKSPACE/scripts/package_contract_check_v1r1c0.sh" || true
|
||||
echo
|
||||
echo "== hermes =="
|
||||
curl -fsS http://127.0.0.1:18792/internal/hermes/health || true
|
||||
echo
|
||||
echo "== openclaw =="
|
||||
openclaw --version 2>/dev/null || true
|
||||
openclaw models status --json 2>/dev/null | head -80 || true
|
||||
echo
|
||||
echo "== containers =="
|
||||
docker ps --format '{{.Names}} {{.Image}} {{.Ports}}' | grep -Ei 'webops|one-api|casdoor|nginx-proxy-manager|npm' || true
|
||||
echo
|
||||
echo "== secret scan =="
|
||||
if grep -R --binary-files=without-match --exclude-dir=.git -E 'sk-[A-Za-z0-9_-]{20,}|AIza[0-9A-Za-z_-]{20,}|xox[baprs]-[0-9A-Za-z-]{20,}' -n "$WORKSPACE" "$ROOT/logs" "$ROOT/artifacts" 2>/dev/null | head -20; then
|
||||
echo "SECRET_SCAN=FAILED"
|
||||
else
|
||||
echo "SECRET_SCAN=OK"
|
||||
fi
|
||||
} | tee "$EVIDENCE"
|
||||
|
||||
cat > "$ARTIFACT" <<EOF_REPORT
|
||||
# AIOS V1R1C0 Online Baseline Acceptance
|
||||
|
||||
AIOS_V1R1C0_ONLINE_BASELINE_STATUS=PARTIAL
|
||||
DOMAIN=https://cnnetmkt.top
|
||||
SHORT_LINKS=/git,/base,/ver,/diff,/patch,/model,/run,/api,/hook
|
||||
PROXY=NGINX_TEMP (NPM_NOT_RUNNING)
|
||||
GIT_URL=$GIT_URL
|
||||
BASELINE_VERSION=$VERSION
|
||||
BASELINE_TAG=$TAG
|
||||
WORKSPACE=$WORKSPACE
|
||||
WEBOPS_REUSED=YES
|
||||
CASDOOR_REUSED=PARTIAL
|
||||
OPENCLAW_REUSED=PARTIAL
|
||||
HERMES_REUSED=YES
|
||||
ONE_API_REUSED=YES
|
||||
GITEA_REUSED=NEW
|
||||
CUSTOM_LLM_STATUS=OK
|
||||
MODEL_PRIORITY_STATUS=OK
|
||||
RUNTIME_SELECTOR_STATUS=OK
|
||||
ACCEPTANCE_REPORT=$ARTIFACT
|
||||
KNOWN_ISSUES=NPM is not running on this host, so system nginx currently serves the short links; OpenClaw gateway is active but native openclaw agent execution still needs gateway/provider credentials; host-side DNS resolution for cnnetmkt.top is unreliable, so local validation used --resolve to 127.0.0.1; Docker Hub pull for Gitea was blocked by the existing Docker proxy path and was replaced with official Gitea binary deployment.
|
||||
NEXT_UPDATE_MODE=git_patch_only
|
||||
ZIP_UPDATE_MODE=deprecated
|
||||
|
||||
## Evidence
|
||||
|
||||
- Imported package: /data/aios-gitops/imports/$SOURCE
|
||||
- Package SHA256: dbee5d45aab02aab33e862bb9beb6115b3b43137e0fb5e00c98448ac1ecfd0f9
|
||||
- Git workspace: $WORKSPACE
|
||||
- Baseline branch: main
|
||||
- Baseline tag: $TAG
|
||||
- Git remote: $GIT_URL
|
||||
- Gitea internal version: 1.22.6
|
||||
- API service: aios-gitops-model-api.service on 127.0.0.1:18890
|
||||
- Gitea service: aios-gitea.service on 127.0.0.1:3000
|
||||
- Hermes executor health: ok
|
||||
- One API reused at: 127.0.0.1:13000
|
||||
- WebOps reused container: aios-webops-server-hw-webops-1
|
||||
- Parallel server agents: git_service_agent, entry_proxy_agent, gitops_api_uat_agent, acceptance_report_agent
|
||||
- Parallel agent report: /data/aios-gitops/artifacts/AIOS_V1R1C0_PARALLEL_AGENT_STATUS.json
|
||||
- Final evidence log: $EVIDENCE
|
||||
|
||||
## GitOps Rules
|
||||
|
||||
- ZIP update mode is deprecated after initial import.
|
||||
- Future updates must be git branch + commit + push + webhook.
|
||||
- Patches are represented as git branches and are reviewable by diff.
|
||||
- ChatGPT/Codex write path is /api/git-control-compatible endpoints exposed by the GitOps API.
|
||||
EOF_REPORT
|
||||
|
||||
cp "$ARTIFACT" "$REPORT_IN_REPO"
|
||||
|
||||
git add baseline/current.json versions/V1R1C0.json docs/release/AIOS_V1R1C0_ONLINE_BASELINE_ACCEPTANCE.md
|
||||
git commit -m "docs(gitops): finalize V1R1C0 online baseline acceptance" || true
|
||||
|
||||
admin_user="aios"
|
||||
admin_pass_file="$ROOT/runtime/secrets/gitea-admin-password"
|
||||
if [ -s "$admin_pass_file" ]; then
|
||||
tmp_home="$(mktemp -d)"
|
||||
chmod 700 "$tmp_home"
|
||||
{
|
||||
echo "machine 127.0.0.1"
|
||||
echo "login $admin_user"
|
||||
echo "password $(cat "$admin_pass_file")"
|
||||
} > "$tmp_home/.netrc"
|
||||
chmod 600 "$tmp_home/.netrc"
|
||||
HOME="$tmp_home" git push http://127.0.0.1:3000/aios/aios-v23.git main
|
||||
HOME="$tmp_home" git push http://127.0.0.1:3000/aios/aios-v23.git --tags
|
||||
rm -rf "$tmp_home"
|
||||
fi
|
||||
|
||||
git remote remove origin 2>/dev/null || true
|
||||
git remote add origin "$GIT_URL"
|
||||
git status --short
|
||||
echo "FINALIZE_V1R1C0_GITOPS_ACCEPTANCE_DONE"
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Run from an operator machine that can SSH to both hw.imruyi.top and aihost.
|
||||
# No secrets are embedded; the script uses existing server-side Docker/env state.
|
||||
|
||||
HW_HOST="${HW_HOST:-hw.imruyi.top}"
|
||||
AIHOST_HOST="${AIHOST_HOST:-aihost}"
|
||||
PRIMARY_AUTH="${PRIMARY_AUTH:-https://auth.cnnetmkt.top}"
|
||||
PRIMARY_ENTRY="${PRIMARY_ENTRY:-https://cnnetmkt.top}"
|
||||
UCC_DEPLOY_DIR="${UCC_DEPLOY_DIR:-/data/aios/aios_r13_v18_runtime_portable_silent_20260704/components/user-control-center/deploy}"
|
||||
|
||||
printf '== update Casdoor application on %s ==\n' "$AIHOST_HOST"
|
||||
ssh "$AIHOST_HOST" bash -s <<'AIHOST'
|
||||
set -euo pipefail
|
||||
TS=$(date -u +%Y%m%dT%H%M%SZ)
|
||||
BACKUP_DIR="/data/aios/backups/casdoor-cnnetmkt-primary-$TS"
|
||||
sudo mkdir -p "$BACKUP_DIR"
|
||||
sudo chown "$USER":"$USER" "$BACKUP_DIR"
|
||||
docker exec casdoor-postgres pg_dump -U casdoor -d casdoor -t application > "$BACKUP_DIR/application-before.sql"
|
||||
docker exec -i casdoor-postgres psql -U casdoor -d casdoor -v ON_ERROR_STOP=1 <<'SQL'
|
||||
UPDATE application
|
||||
SET
|
||||
homepage_url = 'https://cnnetmkt.top/',
|
||||
signin_url = 'https://cnnetmkt.top/portal/login/',
|
||||
signup_url = 'https://cnnetmkt.top/portal/register/',
|
||||
redirect_uris = '["https://cnnetmkt.top/api/auth/callback/casdoor", "https://cnnetmkt.top/casdoor/callback/", "https://portal.cnnetmkt.top/api/auth/callback/casdoor", "https://portal.cnnetmkt.top/casdoor/callback/", "https://user.cnnetmkt.top/api/auth/callback/casdoor", "https://user.cnnetmkt.top/casdoor/callback/", "https://portal.h.imruyi.top:8443/api/auth/callback/casdoor", "https://portal.h.imruyi.top:8443/casdoor/callback/"]'
|
||||
WHERE owner = 'admin' AND name = 'aios-portal';
|
||||
SQL
|
||||
docker exec casdoor-postgres psql -U casdoor -d casdoor -P pager=off -c "select owner,name,homepage_url,signin_url,signup_url,redirect_uris from application where owner='admin' and name='aios-portal';" > "$BACKUP_DIR/application-after.txt"
|
||||
cat "$BACKUP_DIR/application-after.txt"
|
||||
printf 'casdoor_backup_dir=%s\n' "$BACKUP_DIR"
|
||||
AIHOST
|
||||
|
||||
printf '\n== update HW ingress and UCC runtime on %s ==\n' "$HW_HOST"
|
||||
ssh "$HW_HOST" bash -s <<HW
|
||||
set -euo pipefail
|
||||
TS=\$(date -u +%Y%m%dT%H%M%SZ)
|
||||
BACKUP_DIR="/data/aios-gitops/backups/auth-cnnetmkt-primary-\$TS"
|
||||
ENV_FILE="$UCC_DEPLOY_DIR/.env"
|
||||
NGINX_AUTH="/etc/nginx/sites-available/auth-cnnetmkt-casdoor.conf"
|
||||
sudo mkdir -p "\$BACKUP_DIR"
|
||||
sudo cp -a "\$ENV_FILE" "\$BACKUP_DIR/user-control.env.before"
|
||||
sudo cp -a /etc/nginx/sites-available "\$BACKUP_DIR/nginx-sites-available.before"
|
||||
|
||||
set_env() {
|
||||
local key="\$1" value="\$2"
|
||||
if sudo grep -q "^\${key}=" "\$ENV_FILE"; then
|
||||
sudo sed -i "s#^\${key}=.*#\${key}=\${value}#" "\$ENV_FILE"
|
||||
else
|
||||
printf '%s=%s\n' "\$key" "\$value" | sudo tee -a "\$ENV_FILE" >/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
set_env ALLOWED_HOSTS 'cnnetmkt.top,portal.cnnetmkt.top,user.cnnetmkt.top,auth.cnnetmkt.top,127.0.0.1,localhost,hw.imruyi.top,1.95.164.183,portal.h.imruyi.top'
|
||||
set_env CSRF_TRUSTED_ORIGINS 'https://cnnetmkt.top,https://portal.cnnetmkt.top,https://user.cnnetmkt.top,https://auth.cnnetmkt.top,http://portal.cnnetmkt.top,http://hw.imruyi.top,http://1.95.164.183,https://portal.h.imruyi.top:8443,http://portal.h.imruyi.top,http://user.cnnetmkt.top'
|
||||
set_env UCC_PUBLIC_BASE_URL '$PRIMARY_ENTRY'
|
||||
set_env CASDOOR_PUBLIC_BASE_URL '$PRIMARY_AUTH'
|
||||
set_env CASDOOR_INTERNAL_BASE_URL '$PRIMARY_AUTH'
|
||||
set_env CASDOOR_INVITE_SYNC_BASE_URL '$PRIMARY_AUTH'
|
||||
|
||||
sudo tee "\$NGINX_AUTH" >/dev/null <<'NGINX'
|
||||
# AIOS primary public Casdoor entry for cnnetmkt.top.
|
||||
# The upstream remains the existing backup/home Casdoor service; public clients should see auth.cnnetmkt.top only.
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
server_name auth.cnnetmkt.top;
|
||||
|
||||
access_log /var/log/nginx/aios-auth-cnnetmkt.access.log;
|
||||
error_log /var/log/nginx/aios-auth-cnnetmkt.error.log;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/cnnetmkt-wildcard/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/cnnetmkt-wildcard/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass https://auth.h.imruyi.top:8443;
|
||||
proxy_http_version 1.1;
|
||||
proxy_ssl_server_name on;
|
||||
proxy_ssl_name auth.h.imruyi.top;
|
||||
proxy_ssl_verify off;
|
||||
proxy_set_header Host auth.h.imruyi.top;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_set_header X-Forwarded-Host \$host;
|
||||
proxy_set_header X-Forwarded-Port 443;
|
||||
proxy_set_header Upgrade \$http_upgrade;
|
||||
proxy_set_header Connection \$connection_upgrade;
|
||||
proxy_redirect https://auth.h.imruyi.top:8443/ https://auth.cnnetmkt.top/;
|
||||
proxy_redirect https://auth.h.imruyi.top/ https://auth.cnnetmkt.top/;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name auth.cnnetmkt.top;
|
||||
return 301 https://\$host\$request_uri;
|
||||
}
|
||||
NGINX
|
||||
|
||||
sudo ln -sf "\$NGINX_AUTH" /etc/nginx/sites-enabled/auth-cnnetmkt-casdoor.conf
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
cd "$UCC_DEPLOY_DIR"
|
||||
sudo docker compose up -d --force-recreate user-control-web user-control-worker user-control-beat
|
||||
printf 'hw_backup_dir=%s\n' "\$BACKUP_DIR"
|
||||
HW
|
||||
|
||||
"$(dirname "$0")/check_cnnetmkt_auth_primary.sh"
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="/data/aios-gitops"
|
||||
WORKSPACE="$ROOT/workspace"
|
||||
LOG="$ROOT/logs/g-cnnetmkt-ingress-fix.log"
|
||||
BACKUP_DIR="$ROOT/backups/nginx-g-cnnetmkt-$(date +%Y%m%d-%H%M%S)"
|
||||
SITE="/etc/nginx/sites-available/aios-user-portal.conf"
|
||||
TRACKED="$WORKSPACE/deploy/nginx/g-cnnetmkt-gitea-server.conf"
|
||||
REPORT="$ROOT/artifacts/CNNETMKT_SSL_INGRESS_STATUS.md"
|
||||
mkdir -p "$ROOT/logs" "$BACKUP_DIR" "$WORKSPACE/deploy/nginx" "$ROOT/artifacts"
|
||||
exec > >(tee -a "$LOG") 2>&1
|
||||
|
||||
echo "== backup =="
|
||||
sudo cp -a "$SITE" "$BACKUP_DIR/aios-user-portal.conf"
|
||||
|
||||
cat > "$TRACKED" <<'NGINX'
|
||||
# AIOS GitOps alias for https://g.cnnetmkt.top/
|
||||
# Note: this server block intentionally keeps /git/ as the Gitea app prefix,
|
||||
# matching the current Gitea ROOT_URL and avoiding a disruptive app.ini rewrite.
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
server_name g.cnnetmkt.top;
|
||||
|
||||
access_log /var/log/nginx/aios-g-cnnetmkt.access.log;
|
||||
error_log /var/log/nginx/aios-g-cnnetmkt.error.log;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/user.cnnetmkt.top/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/user.cnnetmkt.top/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
location = / {
|
||||
return 302 /git/;
|
||||
}
|
||||
|
||||
location = /git {
|
||||
return 302 /git/;
|
||||
}
|
||||
|
||||
location /git/ {
|
||||
proxy_pass http://127.0.0.1:3000/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Prefix /git;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_redirect off;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name g.cnnetmkt.top;
|
||||
|
||||
location = / {
|
||||
return 302 https://$host/git/;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
NGINX
|
||||
|
||||
echo "== install g.cnnetmkt server block =="
|
||||
sudo cp "$TRACKED" /etc/nginx/sites-available/g-cnnetmkt-gitea.conf
|
||||
sudo ln -sfn /etc/nginx/sites-available/g-cnnetmkt-gitea.conf /etc/nginx/sites-enabled/g-cnnetmkt-gitea.conf
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
|
||||
echo "== local validation before cert expand =="
|
||||
for h in g.cnnetmkt.top cnnetmkt.top user.cnnetmkt.top; do
|
||||
echo "-- $h /"
|
||||
curl -k -sS -D - -o /tmp/aios-body --resolve "$h:443:127.0.0.1" "https://$h/" | sed -n '1,12p' || true
|
||||
grep -ao '<title>[^<]*' /tmp/aios-body 2>/dev/null | head -1 || true
|
||||
echo "-- $h /git/"
|
||||
curl -k -sS -D - -o /tmp/aios-body --resolve "$h:443:127.0.0.1" "https://$h/git/" | sed -n '1,12p' || true
|
||||
grep -ao '<title>[^<]*' /tmp/aios-body 2>/dev/null | head -1 || true
|
||||
done
|
||||
|
||||
echo "== certbot dry run expand for g/user/portal =="
|
||||
CERTBOT_STATUS="not-run"
|
||||
if sudo certbot certonly --nginx --cert-name user.cnnetmkt.top \
|
||||
-d user.cnnetmkt.top -d portal.cnnetmkt.top -d g.cnnetmkt.top \
|
||||
--expand --dry-run --non-interactive --agree-tos; then
|
||||
CERTBOT_STATUS="dry-run-ok"
|
||||
else
|
||||
CERTBOT_STATUS="dry-run-failed"
|
||||
fi
|
||||
|
||||
if [ "$CERTBOT_STATUS" = "dry-run-ok" ]; then
|
||||
echo "== certbot real expand for g/user/portal =="
|
||||
if sudo certbot certonly --nginx --cert-name user.cnnetmkt.top \
|
||||
-d user.cnnetmkt.top -d portal.cnnetmkt.top -d g.cnnetmkt.top \
|
||||
--expand --non-interactive --agree-tos; then
|
||||
CERTBOT_STATUS="issued-g-user-portal"
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
else
|
||||
CERTBOT_STATUS="real-issue-failed"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "== final cert and route validation =="
|
||||
{
|
||||
echo "# cnnetmkt SSL ingress status"
|
||||
echo
|
||||
echo "generated_at=$(date -Is)"
|
||||
echo "g_route_status=installed"
|
||||
echo "certbot_status=$CERTBOT_STATUS"
|
||||
echo "apex_dns_status=blocked_until_cnnetmkt.top_has_A_record_to_1.95.164.183"
|
||||
echo "wildcard_status=blocked_until_dns01_provider_credentials_available"
|
||||
echo "backup_dir=$BACKUP_DIR"
|
||||
echo "tracked_nginx=$TRACKED"
|
||||
echo
|
||||
echo "## validation"
|
||||
for h in g.cnnetmkt.top cnnetmkt.top user.cnnetmkt.top; do
|
||||
echo "### $h"
|
||||
curl -k -sS -D - -o /tmp/aios-body --resolve "$h:443:127.0.0.1" "https://$h/" | sed -n '1,12p' || true
|
||||
grep -ao '<title>[^<]*' /tmp/aios-body 2>/dev/null | head -1 || true
|
||||
echo
|
||||
echo | openssl s_client -connect 127.0.0.1:443 -servername "$h" 2>/dev/null \
|
||||
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName 2>/dev/null || true
|
||||
echo
|
||||
done
|
||||
echo "## certbot"
|
||||
sudo certbot certificates || true
|
||||
} | tee "$REPORT"
|
||||
|
||||
cd "$WORKSPACE"
|
||||
git add deploy/nginx/g-cnnetmkt-gitea-server.conf
|
||||
git commit -m "fix(ingress): add g.cnnetmkt.top GitOps alias" || true
|
||||
|
||||
echo "G_CNNETMKT_INGRESS_FIX_DONE certbot_status=$CERTBOT_STATUS report=$REPORT"
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="/data/aios-gitops"
|
||||
WORKSPACE="$ROOT/workspace"
|
||||
GITEA_BIN="$ROOT/runtime/bin/gitea"
|
||||
GITEA_HOME="$ROOT/runtime/gitea-bin"
|
||||
SECRETS="$ROOT/runtime/secrets"
|
||||
LOG="$ROOT/logs/gitea-repo-push-fix.log"
|
||||
mkdir -p "$ROOT/logs"
|
||||
exec > >(tee -a "$LOG") 2>&1
|
||||
|
||||
admin_user="aios"
|
||||
admin_pass="$(cat "$SECRETS/gitea-admin-password")"
|
||||
|
||||
echo "== endpoint probe =="
|
||||
for path in / /git/ /api/v1/version /git/api/v1/version /user/login /git/user/login; do
|
||||
echo "== ${path} =="
|
||||
curl -sS -i --max-time 8 "http://127.0.0.1:3000${path}" 2>&1 | sed -n '1,18p'
|
||||
done
|
||||
|
||||
echo "== ensure admin user =="
|
||||
"$GITEA_BIN" admin user create \
|
||||
--config "$GITEA_HOME/custom/conf/app.ini" \
|
||||
--username "$admin_user" \
|
||||
--password "$admin_pass" \
|
||||
--email "gitops-bot@cnnetmkt.top" \
|
||||
--admin \
|
||||
--must-change-password=false 2>/dev/null || true
|
||||
|
||||
echo "== create repo by internal API =="
|
||||
if ! curl -fsS -u "$admin_user:$admin_pass" "http://127.0.0.1:3000/api/v1/repos/$admin_user/aios-v23" >/dev/null 2>&1; then
|
||||
curl -fsS -u "$admin_user:$admin_pass" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name":"aios-v23","description":"AIOS V1R1C0 online GitOps baseline","private":false,"auto_init":false,"default_branch":"main"}' \
|
||||
"http://127.0.0.1:3000/api/v1/user/repos" >/dev/null
|
||||
fi
|
||||
curl -fsS -u "$admin_user:$admin_pass" "http://127.0.0.1:3000/api/v1/repos/$admin_user/aios-v23" | python3 -m json.tool | sed -n '1,80p'
|
||||
|
||||
echo "== push main and tags =="
|
||||
cd "$WORKSPACE"
|
||||
push_url="http://${admin_user}:${admin_pass}@127.0.0.1:3000/${admin_user}/aios-v23.git"
|
||||
git push "$push_url" main
|
||||
git push "$push_url" --tags
|
||||
git remote remove origin 2>/dev/null || true
|
||||
git remote add origin "https://cnnetmkt.top/git/${admin_user}/aios-v23.git"
|
||||
|
||||
echo "== verify repo page =="
|
||||
curl -fsS "http://127.0.0.1:3000/${admin_user}/aios-v23" | grep -E "aios-v23|AIOS|V1R1C0" | head -30 || true
|
||||
git remote -v
|
||||
git log --oneline -8
|
||||
git status --short
|
||||
echo "GITEA_REPO_PUSH_OK"
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="/data/aios-gitops"
|
||||
WORKSPACE="$ROOT/workspace"
|
||||
LOG="$ROOT/logs/gitops-api-owner-fix-uat.log"
|
||||
mkdir -p "$ROOT/logs"
|
||||
exec > >(tee -a "$LOG") 2>&1
|
||||
|
||||
echo "== fix ownership =="
|
||||
sudo chown -R aiman:aiman "$ROOT/runtime" "$WORKSPACE" "$ROOT/logs" "$ROOT/artifacts"
|
||||
|
||||
echo "== commit sanitized runner =="
|
||||
cd "$WORKSPACE"
|
||||
git add scripts/hermes_gitops_parallel_agents.py
|
||||
git commit -m "fix(gitops): sanitize parallel agent UAT secret handling" || true
|
||||
|
||||
echo "== restart API =="
|
||||
sudo systemctl restart aios-gitops-model-api.service
|
||||
sleep 3
|
||||
systemctl status aios-gitops-model-api.service --no-pager | sed -n '1,100p'
|
||||
curl -fsS http://127.0.0.1:18890/health
|
||||
echo
|
||||
curl -fsS http://127.0.0.1:18890/api/status
|
||||
echo
|
||||
|
||||
echo "== API UAT =="
|
||||
TOKEN="$(head -1 "$ROOT/runtime/secrets/gitops.tokens")"
|
||||
curl -fsS \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H 'X-AIOS-User: agent-uat' \
|
||||
-H 'X-AIOS-Role: owner' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"username":"agent-uat","role":"owner","tier":"ultra"}' \
|
||||
http://127.0.0.1:18890/api/runtime/select
|
||||
echo
|
||||
curl -fsS \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H 'X-AIOS-User: agent-uat' \
|
||||
-H 'X-AIOS-Role: owner' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"provider_name":"agent-local-openai-compatible","base_url":"http://127.0.0.1:13000/v1","model_name":"agent-test-model","display_name":"Agent Test Model","priority":10,"api_key_secret_ref":"secret://llm/agent-uat/pre-provisioned-test"}' \
|
||||
http://127.0.0.1:18890/api/model/custom/add
|
||||
echo
|
||||
curl -fsS \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H 'X-AIOS-User: agent-uat' \
|
||||
-H 'X-AIOS-Role: owner' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"username":"agent-uat"}' \
|
||||
http://127.0.0.1:18890/api/model/route/resolve
|
||||
echo
|
||||
|
||||
echo "== secret scan =="
|
||||
if grep -R 'redacted-test-key\|sk-[A-Za-z0-9]' -n "$WORKSPACE" "$ROOT/logs" 2>/dev/null; then
|
||||
echo "SECRET_SCAN_FAILED"
|
||||
exit 2
|
||||
fi
|
||||
echo "SECRET_SCAN_OK"
|
||||
|
||||
echo "== git status =="
|
||||
git status --short
|
||||
git log --oneline -8
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="/data/aios-gitops"
|
||||
WORKSPACE="$ROOT/workspace"
|
||||
GITEA_HOME="$ROOT/runtime/gitea-bin"
|
||||
GITEA_BIN="$ROOT/runtime/bin/gitea"
|
||||
GITEA_VERSION="1.22.6"
|
||||
GITEA_URL="https://dl.gitea.com/gitea/${GITEA_VERSION}/gitea-${GITEA_VERSION}-linux-amd64"
|
||||
SECRETS="$ROOT/runtime/secrets"
|
||||
LOG="$ROOT/logs/gitea-binary-install.log"
|
||||
|
||||
mkdir -p "$ROOT/logs" "$SECRETS" "$ROOT/runtime/bin" "$GITEA_HOME/custom/conf" "$GITEA_HOME/data" "$GITEA_HOME/log" "$WORKSPACE/deploy/gitea-binary"
|
||||
exec > >(tee -a "$LOG") 2>&1
|
||||
|
||||
cd "$WORKSPACE"
|
||||
|
||||
echo "== write tracked deployment manifests =="
|
||||
cat > deploy/gitea-binary/app.ini.template <<'EOF'
|
||||
APP_NAME = AIOS GitOps
|
||||
RUN_USER = aiman
|
||||
WORK_PATH = /data/aios-gitops/runtime/gitea-bin
|
||||
|
||||
[server]
|
||||
PROTOCOL = http
|
||||
HTTP_ADDR = 127.0.0.1
|
||||
HTTP_PORT = 3000
|
||||
DOMAIN = cnnetmkt.top
|
||||
ROOT_URL = https://cnnetmkt.top/git/
|
||||
SERVE_FROM_SUB_PATH = true
|
||||
DISABLE_SSH = true
|
||||
OFFLINE_MODE = false
|
||||
|
||||
[database]
|
||||
DB_TYPE = sqlite3
|
||||
PATH = /data/aios-gitops/runtime/gitea-bin/data/gitea.db
|
||||
|
||||
[repository]
|
||||
ROOT = /data/aios-gitops/runtime/gitea-bin/data/repositories
|
||||
DEFAULT_BRANCH = main
|
||||
|
||||
[security]
|
||||
INSTALL_LOCK = true
|
||||
SECRET_KEY = __SECRET_KEY__
|
||||
INTERNAL_TOKEN = __INTERNAL_TOKEN__
|
||||
|
||||
[service]
|
||||
DISABLE_REGISTRATION = true
|
||||
REQUIRE_SIGNIN_VIEW = false
|
||||
|
||||
[webhook]
|
||||
ALLOWED_HOST_LIST = *
|
||||
|
||||
[log]
|
||||
MODE = console
|
||||
LEVEL = Info
|
||||
ROOT_PATH = /data/aios-gitops/runtime/gitea-bin/log
|
||||
EOF
|
||||
|
||||
cat > deploy/gitea-binary/aios-gitea.service <<EOF
|
||||
[Unit]
|
||||
Description=AIOS GitOps Gitea
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=aiman
|
||||
Group=aiman
|
||||
WorkingDirectory=$GITEA_HOME
|
||||
ExecStart=$GITEA_BIN web --config $GITEA_HOME/custom/conf/app.ini
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
Environment=USER=aiman
|
||||
Environment=HOME=/home/aiman
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
git add deploy/gitea-binary/app.ini.template deploy/gitea-binary/aios-gitea.service
|
||||
git commit -m "chore(gitops): add binary Gitea deployment manifests" || true
|
||||
|
||||
echo "== download gitea binary =="
|
||||
if [ ! -x "$GITEA_BIN" ]; then
|
||||
tmp="$GITEA_BIN.tmp"
|
||||
curl -fL --retry 3 --connect-timeout 20 -o "$tmp" "$GITEA_URL"
|
||||
chmod 755 "$tmp"
|
||||
mv "$tmp" "$GITEA_BIN"
|
||||
fi
|
||||
"$GITEA_BIN" --version
|
||||
|
||||
echo "== render runtime app.ini =="
|
||||
secret_key_file="$SECRETS/gitea-secret-key"
|
||||
internal_token_file="$SECRETS/gitea-internal-token"
|
||||
admin_pass_file="$SECRETS/gitea-admin-password"
|
||||
api_token_file="$SECRETS/gitea-api-token"
|
||||
for f in "$secret_key_file" "$internal_token_file" "$admin_pass_file"; do
|
||||
if [ ! -s "$f" ]; then
|
||||
openssl rand -hex 32 > "$f"
|
||||
chmod 600 "$f"
|
||||
fi
|
||||
done
|
||||
sed \
|
||||
-e "s#__SECRET_KEY__#$(cat "$secret_key_file")#g" \
|
||||
-e "s#__INTERNAL_TOKEN__#$(cat "$internal_token_file")#g" \
|
||||
deploy/gitea-binary/app.ini.template > "$GITEA_HOME/custom/conf/app.ini"
|
||||
chmod 600 "$GITEA_HOME/custom/conf/app.ini"
|
||||
sudo chown -R aiman:aiman "$ROOT/runtime" "$WORKSPACE/deploy/gitea-binary"
|
||||
|
||||
echo "== install systemd service =="
|
||||
sudo cp deploy/gitea-binary/aios-gitea.service /etc/systemd/system/aios-gitea.service
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now aios-gitea.service
|
||||
|
||||
echo "== wait gitea =="
|
||||
for i in $(seq 1 90); do
|
||||
if curl -fsS http://127.0.0.1:3000/git/api/healthz >/dev/null 2>&1 || curl -fsS http://127.0.0.1:3000/git/ >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
systemctl is-active aios-gitea.service
|
||||
curl -fsS http://127.0.0.1:3000/git/api/healthz || true
|
||||
echo
|
||||
|
||||
echo "== create admin user and repo =="
|
||||
admin_user="aios"
|
||||
admin_email="gitops-bot@cnnetmkt.top"
|
||||
admin_pass="$(cat "$admin_pass_file")"
|
||||
"$GITEA_BIN" admin user create \
|
||||
--config "$GITEA_HOME/custom/conf/app.ini" \
|
||||
--username "$admin_user" \
|
||||
--password "$admin_pass" \
|
||||
--email "$admin_email" \
|
||||
--admin \
|
||||
--must-change-password=false 2>/dev/null || true
|
||||
|
||||
if [ ! -s "$api_token_file" ]; then
|
||||
"$GITEA_BIN" admin user generate-access-token \
|
||||
--config "$GITEA_HOME/custom/conf/app.ini" \
|
||||
--username "$admin_user" \
|
||||
--token-name aios-gitops-push \
|
||||
--scopes write:repository,read:repository,write:user,read:user \
|
||||
| awk '/Access token was successfully created/ {print $NF}' > "$api_token_file.tmp" || true
|
||||
if [ -s "$api_token_file.tmp" ]; then
|
||||
mv "$api_token_file.tmp" "$api_token_file"
|
||||
chmod 600 "$api_token_file"
|
||||
else
|
||||
rm -f "$api_token_file.tmp"
|
||||
fi
|
||||
fi
|
||||
|
||||
repo_api="http://127.0.0.1:3000/git/api/v1/user/repos"
|
||||
if ! curl -fsS -u "$admin_user:$admin_pass" "http://127.0.0.1:3000/git/api/v1/repos/$admin_user/aios-v23" >/dev/null 2>&1; then
|
||||
curl -fsS -u "$admin_user:$admin_pass" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name":"aios-v23","description":"AIOS V1R1C0 online GitOps baseline","private":false,"auto_init":false,"default_branch":"main"}' \
|
||||
"$repo_api" >/dev/null
|
||||
fi
|
||||
|
||||
echo "== push repository =="
|
||||
push_url="http://${admin_user}:${admin_pass}@127.0.0.1:3000/git/${admin_user}/aios-v23.git"
|
||||
git push "$push_url" main
|
||||
git push "$push_url" --tags
|
||||
git remote remove origin 2>/dev/null || true
|
||||
git remote add origin "https://cnnetmkt.top/git/${admin_user}/aios-v23.git"
|
||||
|
||||
echo "== verify gitea repo =="
|
||||
curl -fsS "http://127.0.0.1:3000/git/${admin_user}/aios-v23" | grep -E "aios-v23|AIOS" | head -20 || true
|
||||
git status --short
|
||||
git log --oneline -8
|
||||
git remote -v
|
||||
|
||||
echo "GITEA_BINARY_INSTALL_OK version=$GITEA_VERSION url=https://cnnetmkt.top/git/${admin_user}/aios-v23"
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${EUID:-$(id -u)}" -ne 0 ]; then
|
||||
exec sudo bash "$0" "$@"
|
||||
fi
|
||||
|
||||
ROOT="/data/aios-gitops"
|
||||
WORKSPACE="$ROOT/workspace"
|
||||
LEGO_BIN="$ROOT/runtime/bin/lego"
|
||||
LEGO_PATH="/etc/letsencrypt/lego-cnnetmkt"
|
||||
LIVE_DIR="/etc/letsencrypt/live/cnnetmkt-wildcard"
|
||||
ENV_FILE="/etc/letsencrypt/huaweicloud-cnnetmkt.env"
|
||||
LOG="$ROOT/logs/cnnetmkt-wildcard-lego.log"
|
||||
REPORT="$ROOT/artifacts/CNNETMKT_WILDCARD_CERT_STATUS.md"
|
||||
TRACKED="$WORKSPACE/deploy/nginx/cnnetmkt-wildcard-certificate.md"
|
||||
BACKUP_DIR="$ROOT/backups/nginx-wildcard-$(date +%Y%m%d-%H%M%S)"
|
||||
EMAIL="admin@cnnetmkt.top"
|
||||
CERT_NAME="cnnetmkt-wildcard"
|
||||
LEGO_VERSION="v5.2.2"
|
||||
LEGO_URL="https://github.com/go-acme/lego/releases/download/${LEGO_VERSION}/lego_${LEGO_VERSION}_linux_amd64.tar.gz"
|
||||
mkdir -p "$ROOT/logs" "$ROOT/runtime/bin" "$ROOT/artifacts" "$WORKSPACE/deploy/nginx" "$BACKUP_DIR"
|
||||
exec > >(tee -a "$LOG") 2>&1
|
||||
|
||||
redact() {
|
||||
sed -E 's/(ACCESS_KEY_ID=).+/\1[REDACTED]/; s/(SECRET_ACCESS_KEY=).+/\1[REDACTED]/'
|
||||
}
|
||||
|
||||
echo "== preflight =="
|
||||
hostname
|
||||
date -Is
|
||||
test -s "$ENV_FILE"
|
||||
sudo chmod 600 "$ENV_FILE"
|
||||
sudo chown root:root "$ENV_FILE"
|
||||
echo "env_file=$ENV_FILE"
|
||||
sudo sed -n '1,20p' "$ENV_FILE" | redact
|
||||
|
||||
echo "== install lego =="
|
||||
if [ ! -x "$LEGO_BIN" ]; then
|
||||
tmp="$(mktemp -d)"
|
||||
curl -fL --retry 3 --connect-timeout 30 -o "$tmp/lego.tar.gz" "$LEGO_URL"
|
||||
tar -xzf "$tmp/lego.tar.gz" -C "$tmp"
|
||||
install -m 0755 "$tmp/lego" "$LEGO_BIN"
|
||||
rm -rf "$tmp"
|
||||
fi
|
||||
"$LEGO_BIN" --version
|
||||
|
||||
echo "== load env =="
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
. "$ENV_FILE"
|
||||
set +a
|
||||
: "${HUAWEICLOUD_ACCESS_KEY_ID:?missing access key id}"
|
||||
: "${HUAWEICLOUD_SECRET_ACCESS_KEY:?missing secret access key}"
|
||||
: "${HUAWEICLOUD_REGION:?missing huawei region}"
|
||||
export HUAWEICLOUD_HTTP_TIMEOUT="${HUAWEICLOUD_HTTP_TIMEOUT:-30}"
|
||||
export HUAWEICLOUD_PROPAGATION_TIMEOUT="${HUAWEICLOUD_PROPAGATION_TIMEOUT:-180}"
|
||||
export HUAWEICLOUD_POLLING_INTERVAL="${HUAWEICLOUD_POLLING_INTERVAL:-5}"
|
||||
export HUAWEICLOUD_TTL="${HUAWEICLOUD_TTL:-300}"
|
||||
|
||||
echo "== DNS state =="
|
||||
for n in cnnetmkt.top g.cnnetmkt.top user.cnnetmkt.top portal.cnnetmkt.top; do
|
||||
echo "-- $n"
|
||||
dig +time=5 +tries=1 +short "$n" A || true
|
||||
done
|
||||
|
||||
echo "== staging DNS-01 =="
|
||||
STAGING_STATUS="failed"
|
||||
if sudo --preserve-env=HUAWEICLOUD_ACCESS_KEY_ID,HUAWEICLOUD_SECRET_ACCESS_KEY,HUAWEICLOUD_REGION,HUAWEICLOUD_HTTP_TIMEOUT,HUAWEICLOUD_PROPAGATION_TIMEOUT,HUAWEICLOUD_POLLING_INTERVAL,HUAWEICLOUD_TTL \
|
||||
"$LEGO_BIN" run --path "$LEGO_PATH-staging" --email "$EMAIL" --accept-tos \
|
||||
--server "https://acme-staging-v02.api.letsencrypt.org/directory" \
|
||||
--dns huaweicloud --dns.propagation.wait 120s --cert.name "$CERT_NAME" --domains "cnnetmkt.top" --domains "*.cnnetmkt.top"; then
|
||||
STAGING_STATUS="ok"
|
||||
fi
|
||||
echo "staging_status=$STAGING_STATUS"
|
||||
if [ "$STAGING_STATUS" != "ok" ]; then
|
||||
echo "STAGING_DNS01_FAILED"
|
||||
exit 20
|
||||
fi
|
||||
|
||||
echo "== production DNS-01 =="
|
||||
PROD_STATUS="failed"
|
||||
if sudo --preserve-env=HUAWEICLOUD_ACCESS_KEY_ID,HUAWEICLOUD_SECRET_ACCESS_KEY,HUAWEICLOUD_REGION,HUAWEICLOUD_HTTP_TIMEOUT,HUAWEICLOUD_PROPAGATION_TIMEOUT,HUAWEICLOUD_POLLING_INTERVAL,HUAWEICLOUD_TTL \
|
||||
"$LEGO_BIN" run --path "$LEGO_PATH" --email "$EMAIL" --accept-tos \
|
||||
--dns huaweicloud --dns.propagation.wait 120s --cert.name "$CERT_NAME" --domains "cnnetmkt.top" --domains "*.cnnetmkt.top"; then
|
||||
PROD_STATUS="issued"
|
||||
else
|
||||
sudo --preserve-env=HUAWEICLOUD_ACCESS_KEY_ID,HUAWEICLOUD_SECRET_ACCESS_KEY,HUAWEICLOUD_REGION,HUAWEICLOUD_HTTP_TIMEOUT,HUAWEICLOUD_PROPAGATION_TIMEOUT,HUAWEICLOUD_POLLING_INTERVAL,HUAWEICLOUD_TTL \
|
||||
"$LEGO_BIN" run --path "$LEGO_PATH" --email "$EMAIL" --accept-tos \
|
||||
--dns huaweicloud --dns.propagation.wait 120s --cert.name "$CERT_NAME" --domains "cnnetmkt.top" --domains "*.cnnetmkt.top" --renew-days 60 --renew-force
|
||||
PROD_STATUS="renewed"
|
||||
fi
|
||||
|
||||
echo "== install cert material =="
|
||||
sudo mkdir -p "$LIVE_DIR"
|
||||
cert_file="$(sudo find "$LEGO_PATH/certificates" -maxdepth 1 -type f -name '*.crt' ! -name '*.issuer.crt' | head -1)"
|
||||
key_file="$(sudo find "$LEGO_PATH/certificates" -maxdepth 1 -type f -name '*.key' | head -1)"
|
||||
issuer_file="$(sudo find "$LEGO_PATH/certificates" -maxdepth 1 -type f -name '*.issuer.crt' | head -1 || true)"
|
||||
test -n "$cert_file"
|
||||
test -n "$key_file"
|
||||
sudo cp "$key_file" "$LIVE_DIR/privkey.pem"
|
||||
if [ -n "${issuer_file:-}" ] && [ -f "$issuer_file" ]; then
|
||||
sudo sh -c "cat '$cert_file' '$issuer_file' > '$LIVE_DIR/fullchain.pem'"
|
||||
sudo cp "$issuer_file" "$LIVE_DIR/chain.pem"
|
||||
else
|
||||
sudo cp "$cert_file" "$LIVE_DIR/fullchain.pem"
|
||||
fi
|
||||
sudo chmod 644 "$LIVE_DIR/fullchain.pem" "$LIVE_DIR/chain.pem" 2>/dev/null || true
|
||||
sudo chmod 600 "$LIVE_DIR/privkey.pem"
|
||||
|
||||
echo "== verify cert material =="
|
||||
sudo openssl x509 -in "$LIVE_DIR/fullchain.pem" -noout -subject -issuer -dates -ext subjectAltName
|
||||
|
||||
echo "== patch nginx cert paths =="
|
||||
sudo cp -a /etc/nginx/sites-available/aios-user-portal.conf "$BACKUP_DIR/aios-user-portal.conf"
|
||||
sudo cp -a /etc/nginx/sites-available/g-cnnetmkt-gitea.conf "$BACKUP_DIR/g-cnnetmkt-gitea.conf" 2>/dev/null || true
|
||||
sudo python3 - <<'PY'
|
||||
from pathlib import Path
|
||||
|
||||
files = [
|
||||
Path("/etc/nginx/sites-available/aios-user-portal.conf"),
|
||||
Path("/etc/nginx/sites-available/g-cnnetmkt-gitea.conf"),
|
||||
]
|
||||
for path in files:
|
||||
if not path.exists():
|
||||
continue
|
||||
text = path.read_text()
|
||||
text = text.replace(
|
||||
"/etc/letsencrypt/live/user.cnnetmkt.top/fullchain.pem",
|
||||
"/etc/letsencrypt/live/cnnetmkt-wildcard/fullchain.pem",
|
||||
)
|
||||
text = text.replace(
|
||||
"/etc/letsencrypt/live/user.cnnetmkt.top/privkey.pem",
|
||||
"/etc/letsencrypt/live/cnnetmkt-wildcard/privkey.pem",
|
||||
)
|
||||
path.write_text(text)
|
||||
PY
|
||||
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
|
||||
echo "== validation =="
|
||||
for h in cnnetmkt.top g.cnnetmkt.top user.cnnetmkt.top portal.cnnetmkt.top; do
|
||||
echo "-- $h cert"
|
||||
echo | openssl s_client -connect 127.0.0.1:443 -servername "$h" 2>/dev/null \
|
||||
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName 2>/dev/null || true
|
||||
echo "-- $h /"
|
||||
curl -k -sS -D - -o /tmp/cnnetmkt-body --resolve "$h:443:127.0.0.1" "https://$h/" | sed -n '1,12p' || true
|
||||
grep -ao '<title>[^<]*' /tmp/cnnetmkt-body 2>/dev/null | head -1 || true
|
||||
echo "-- $h /git/"
|
||||
curl -k -sS -D - -o /tmp/cnnetmkt-body --resolve "$h:443:127.0.0.1" "https://$h/git/" | sed -n '1,12p' || true
|
||||
grep -ao '<title>[^<]*' /tmp/cnnetmkt-body 2>/dev/null | head -1 || true
|
||||
done
|
||||
|
||||
echo "== write renewal helper =="
|
||||
sudo tee /usr/local/sbin/renew-cnnetmkt-wildcard.sh >/dev/null <<'SH'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT="/data/aios-gitops"
|
||||
LEGO_BIN="$ROOT/runtime/bin/lego"
|
||||
LEGO_PATH="/etc/letsencrypt/lego-cnnetmkt"
|
||||
LIVE_DIR="/etc/letsencrypt/live/cnnetmkt-wildcard"
|
||||
ENV_FILE="/etc/letsencrypt/huaweicloud-cnnetmkt.env"
|
||||
set -a
|
||||
. "$ENV_FILE"
|
||||
set +a
|
||||
sudo --preserve-env=HUAWEICLOUD_ACCESS_KEY_ID,HUAWEICLOUD_SECRET_ACCESS_KEY,HUAWEICLOUD_REGION,HUAWEICLOUD_HTTP_TIMEOUT,HUAWEICLOUD_PROPAGATION_TIMEOUT,HUAWEICLOUD_POLLING_INTERVAL,HUAWEICLOUD_TTL \
|
||||
"$LEGO_BIN" run --path "$LEGO_PATH" --email "admin@cnnetmkt.top" --accept-tos \
|
||||
--dns huaweicloud --dns.propagation.wait 120s --cert.name "cnnetmkt-wildcard" --domains "cnnetmkt.top" --domains "*.cnnetmkt.top" --renew-days 45
|
||||
cert_file="$(find "$LEGO_PATH/certificates" -maxdepth 1 -type f -name '*.crt' ! -name '*.issuer.crt' | head -1)"
|
||||
key_file="$(find "$LEGO_PATH/certificates" -maxdepth 1 -type f -name '*.key' | head -1)"
|
||||
cp "$cert_file" "$LIVE_DIR/fullchain.pem"
|
||||
cp "$key_file" "$LIVE_DIR/privkey.pem"
|
||||
chmod 644 "$LIVE_DIR/fullchain.pem"
|
||||
chmod 600 "$LIVE_DIR/privkey.pem"
|
||||
nginx -t
|
||||
systemctl reload nginx
|
||||
SH
|
||||
sudo chmod 700 /usr/local/sbin/renew-cnnetmkt-wildcard.sh
|
||||
|
||||
sudo tee /etc/systemd/system/cnnetmkt-wildcard-renew.service >/dev/null <<'UNIT'
|
||||
[Unit]
|
||||
Description=Renew cnnetmkt wildcard certificate via Huawei Cloud DNS
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/sbin/renew-cnnetmkt-wildcard.sh
|
||||
UNIT
|
||||
|
||||
sudo tee /etc/systemd/system/cnnetmkt-wildcard-renew.timer >/dev/null <<'UNIT'
|
||||
[Unit]
|
||||
Description=Daily cnnetmkt wildcard certificate renewal check
|
||||
|
||||
[Timer]
|
||||
OnCalendar=daily
|
||||
RandomizedDelaySec=2h
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
UNIT
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now cnnetmkt-wildcard-renew.timer
|
||||
|
||||
cat > "$TRACKED" <<EOF_TRACKED
|
||||
# cnnetmkt wildcard certificate
|
||||
|
||||
generated_at=$(date -Is)
|
||||
issuer=Let's Encrypt via lego DNS-01 Huawei Cloud
|
||||
domains=cnnetmkt.top,*.cnnetmkt.top
|
||||
live_dir=$LIVE_DIR
|
||||
nginx_sites=/etc/nginx/sites-available/aios-user-portal.conf,/etc/nginx/sites-available/g-cnnetmkt-gitea.conf
|
||||
renew_service=cnnetmkt-wildcard-renew.service
|
||||
renew_timer=cnnetmkt-wildcard-renew.timer
|
||||
secret_env=$ENV_FILE
|
||||
backup_dir=$BACKUP_DIR
|
||||
status=$PROD_STATUS
|
||||
EOF_TRACKED
|
||||
|
||||
cat > "$REPORT" <<EOF_REPORT
|
||||
# CNNETMKT wildcard certificate status
|
||||
|
||||
generated_at=$(date -Is)
|
||||
status=$PROD_STATUS
|
||||
staging_status=$STAGING_STATUS
|
||||
dns_provider=huaweicloud
|
||||
secret_storage=$ENV_FILE
|
||||
certificate_live_dir=$LIVE_DIR
|
||||
nginx_status=loaded
|
||||
backup_dir=$BACKUP_DIR
|
||||
renew_timer=cnnetmkt-wildcard-renew.timer
|
||||
|
||||
## certificate
|
||||
|
||||
$(sudo openssl x509 -in "$LIVE_DIR/fullchain.pem" -noout -subject -issuer -dates -ext subjectAltName)
|
||||
|
||||
## routes
|
||||
|
||||
- https://cnnetmkt.top/
|
||||
- https://cnnetmkt.top/git/
|
||||
- https://g.cnnetmkt.top/
|
||||
- https://g.cnnetmkt.top/git/
|
||||
- https://user.cnnetmkt.top/
|
||||
EOF_REPORT
|
||||
|
||||
cd "$WORKSPACE"
|
||||
chown -R aiman:aiman "$WORKSPACE/deploy/nginx" "$ROOT/artifacts" "$ROOT/logs"
|
||||
runuser -u aiman -- git add deploy/nginx/cnnetmkt-wildcard-certificate.md
|
||||
runuser -u aiman -- git commit -m "fix(ingress): switch cnnetmkt to wildcard certificate" || true
|
||||
|
||||
echo "CNNETMKT_WILDCARD_DONE status=$PROD_STATUS report=$REPORT"
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
for v in 1.22.6 1.22.5 1.22.4 1.22.3 1.22.2 1.22.1 1.22.0; do
|
||||
url="https://dl.gitea.com/gitea/${v}/gitea-${v}-linux-amd64"
|
||||
echo "== ${v} ${url} =="
|
||||
curl -I --max-time 20 "$url" 2>&1 | sed -n '1,16p'
|
||||
done
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ENV_FILE="${UCC_ENV_FILE:-/data/aios/aios_r13_v18_runtime_portable_silent_20260704/components/user-control-center/deploy/.env}"
|
||||
GITEA_BIN="${GITEA_BIN:-/data/aios-gitops/runtime/bin/gitea}"
|
||||
APP_INI="${GITEA_APP_INI:-/data/aios-gitops/runtime/gitea-bin/custom/conf/app.ini}"
|
||||
WORK_PATH="${GITEA_WORK_PATH:-/data/aios-gitops/runtime/gitea-bin}"
|
||||
DB="${GITEA_DB:-/data/aios-gitops/runtime/gitea-bin/data/gitea.db}"
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
. "$ENV_FILE"
|
||||
set +a
|
||||
strip_trailing_cr() {
|
||||
printf '%s' "${1:-}" | tr -d '\r'
|
||||
}
|
||||
OWNER_USERNAME="$(strip_trailing_cr "${UCC_OWNER_USERNAME:-owner}")"
|
||||
OWNER_EMAIL="$(strip_trailing_cr "${UCC_OWNER_EMAIL:-owner@cnnetmkt.top}")"
|
||||
OWNER_PASSWORD="$(strip_trailing_cr "${UCC_OWNER_PASSWORD:?UCC_OWNER_PASSWORD is required}")"
|
||||
GITEA_RUN_USER="${GITEA_RUN_USER:-aiman}"
|
||||
run_gitea() {
|
||||
runuser -u "$GITEA_RUN_USER" -- "$GITEA_BIN" --config "$APP_INI" --work-path "$WORK_PATH" "$@"
|
||||
}
|
||||
if run_gitea admin user list 2>/dev/null | awk '{print $2}' | grep -Fxq "$OWNER_USERNAME"; then
|
||||
run_gitea admin user change-password --username "$OWNER_USERNAME" --password "$OWNER_PASSWORD" --must-change-password=false >/dev/null
|
||||
else
|
||||
run_gitea admin user create --username "$OWNER_USERNAME" --password "$OWNER_PASSWORD" --email "$OWNER_EMAIL" --admin --must-change-password=false >/dev/null
|
||||
fi
|
||||
python3 - <<PY
|
||||
import sqlite3, time
|
||||
con=sqlite3.connect('$DB')
|
||||
cur=con.cursor()
|
||||
cur.execute("update user set is_admin=1,is_active=1,prohibit_login=0,must_change_password=0,updated_unix=? where lower_name=lower(?)", (int(time.time()), '$OWNER_USERNAME'))
|
||||
cur.execute("update user set prohibit_login=1,is_admin=0,passwd='',passwd_hash_algo='disabled',must_change_password=1,updated_unix=? where lower_name in ('aios','admin','gitea') and lower_name<>lower(?)", (int(time.time()), '$OWNER_USERNAME'))
|
||||
con.commit()
|
||||
PY
|
||||
printf 'GITEA_OWNER_SYNC_OK user=%s default_local_login_disabled=1\n' "$OWNER_USERNAME"
|
||||
@@ -0,0 +1,154 @@
|
||||
# OpenClaw 指令:AIOS Autonomous GitOps 静默优化与部署监督
|
||||
|
||||
Directive ID: `OPENCLAW-AIOS-AUTONOMOUS-GITOPS-DEPLOY-20260707`
|
||||
|
||||
Branch: `patch/web-ui-complete-gitops-dashboard-20260706`
|
||||
|
||||
Base: `main`
|
||||
|
||||
Current baseline: `V1R1C4`
|
||||
|
||||
Proposed baseline after approved merge: `V1R1C5`
|
||||
|
||||
## 1. 执行目的
|
||||
|
||||
OpenClaw 负责接管本分支的执行与监督,目标是在不影响现有执行层、不破坏用户使用体验的前提下,完成 AIOS Autonomous GitOps Runtime 的验证、静默优化、部署准备、Owner 通报与合并候选推进。
|
||||
|
||||
本次改动的目标不是直接绕过 main 保护,而是让系统形成:
|
||||
|
||||
- 自动 CI 验证;
|
||||
- Codex 自愈修复;
|
||||
- event bus + daemon 自动执行;
|
||||
- 微信/网页 Owner 审计与操作通道;
|
||||
- V1R1C5 基线提案;
|
||||
- 失败回流自定义 GPT 修复;
|
||||
- 通过后生成 merge candidate。
|
||||
|
||||
## 2. OpenClaw 角色
|
||||
|
||||
OpenClaw 是本任务的执行监督代理,职责如下:
|
||||
|
||||
1. 读取并监督当前分支与 main 的差异。
|
||||
2. 按验收测试执行静态验证、安全验证、策略验证、灰度验证。
|
||||
3. 发现失败后,生成结构化 failure report。
|
||||
4. 将失败项交给自定义 GPT 继续修复同一 patch 分支。
|
||||
5. 修复后重复验证,直到 PASS 或触发 hard block。
|
||||
6. 生成 merge candidate,但不得直接绕过 main 保护。
|
||||
7. 监督 Owner 通知记录:每个 main 版本变更必须有摘要通报与 Web UI 记录。
|
||||
|
||||
## 3. 禁止事项
|
||||
|
||||
OpenClaw 不得执行以下操作:
|
||||
|
||||
- 不得直接 force push;
|
||||
- 不得删除版本历史、tag、audit 记录;
|
||||
- 不得写入真实凭据;
|
||||
- 不得关闭 auth、Gitea 登录保护或 owner sync;
|
||||
- 不得在未通过验收前合并 main;
|
||||
- 不得在未有审批或预授权 runbook 前执行生产 tag、baseline activation 或 rollback execution。
|
||||
|
||||
## 4. 必须执行的命令
|
||||
|
||||
```bash
|
||||
git fetch --all --prune
|
||||
git checkout patch/web-ui-complete-gitops-dashboard-20260706
|
||||
git diff --stat main...HEAD
|
||||
bash scripts/validate-gitops-platform-ui.sh patch/web-ui-complete-gitops-dashboard-20260706 main
|
||||
bash scripts/autonomous-merge-gate.sh patch/web-ui-complete-gitops-dashboard-20260706 main
|
||||
```
|
||||
|
||||
若部署验证需要启动本地 runtime:
|
||||
|
||||
```bash
|
||||
bash deploy/install/install.sh
|
||||
python3 services/aios-autonomous-daemon/daemon.py emit --type patch_created --branch patch/web-ui-complete-gitops-dashboard-20260706 --base main
|
||||
python3 services/aios-autonomous-daemon/daemon.py run --once
|
||||
```
|
||||
|
||||
## 5. 必须验证的直接修改
|
||||
|
||||
OpenClaw 必须检查以下路径:
|
||||
|
||||
- `overlays/user-control-center/portal/gitops-dashboard/`
|
||||
- `services/aios-autonomous-daemon/`
|
||||
- `services/aios-event-ingress/`
|
||||
- `deploy/systemd/`
|
||||
- `deploy/install/`
|
||||
- `config/codex/`
|
||||
- `scripts/`
|
||||
- `tests/acceptance/`
|
||||
- `versions/proposals/V1R1C5-gitops-platform-ui.proposed.json`
|
||||
- `skills/`
|
||||
- `docs/codex/`
|
||||
|
||||
## 6. 必须验证的间接影响
|
||||
|
||||
OpenClaw 必须确认以下内容未被破坏:
|
||||
|
||||
- `/api/base`、`/api/ver`、`/api/diff`、`/api/patch`、`/api/model`、`/api/run`;
|
||||
- `/base`、`/ver`、`/diff`、`/patch`、`/model`、`/run` 登录保护;
|
||||
- Portal 登录入口 `/portal/me/#gitops`;
|
||||
- Gitea require sign-in view;
|
||||
- owner password sync;
|
||||
- active baseline 仍为 `V1R1C4`,未提前激活 `V1R1C5`。
|
||||
|
||||
## 7. Owner 通报要求
|
||||
|
||||
每个 main 版本变更、merge candidate、validation failure、rollback request、approval required 事件都必须形成:
|
||||
|
||||
1. Owner 消息摘要;
|
||||
2. Web UI 审计记录;
|
||||
3. 可追踪 event_id;
|
||||
4. 变更功能摘要;
|
||||
5. 代码量与配置量统计;
|
||||
6. 与上一次主线变化的间隔;
|
||||
7. 测试状态;
|
||||
8. rollback target。
|
||||
|
||||
## 8. OpenClaw 进度监督格式
|
||||
|
||||
OpenClaw 每次监督输出必须包含:
|
||||
|
||||
```text
|
||||
Status: PASS / FAIL / BLOCKED / RUNNING
|
||||
Branch: patch/web-ui-complete-gitops-dashboard-20260706
|
||||
Base: main
|
||||
Current Rev: <rev>
|
||||
Validation: <summary>
|
||||
Gray Test: <summary>
|
||||
Owner Notice: <summary>
|
||||
Merge Candidate: yes/no
|
||||
Blocked Reason: <reason or none>
|
||||
Next Action: <action>
|
||||
```
|
||||
|
||||
## 9. 失败回流规则
|
||||
|
||||
若失败,OpenClaw 必须写入 failure report:
|
||||
|
||||
- 路径:`reports/openclaw/failure-<event_id>.json`
|
||||
- 内容:失败测试 ID、文件路径、失败原因、建议修复、是否阻断合并。
|
||||
|
||||
随后将问题交给自定义 GPT 修改当前 patch 分支,修复后重新执行完整验收。
|
||||
|
||||
## 10. 合并前门禁
|
||||
|
||||
只有全部满足以下条件,OpenClaw 才能标记为 `merge_candidate_ready`:
|
||||
|
||||
- required acceptance tests 全部 PASS;
|
||||
- 无凭据泄露;
|
||||
- 无 direct main write;
|
||||
- 无高风险未审批动作;
|
||||
- 灰度验证 PASS;
|
||||
- Owner 通报与 Web UI 记录正常;
|
||||
- `V1R1C5` baseline proposal 存在且未提前激活。
|
||||
|
||||
## 11. 最终输出
|
||||
|
||||
OpenClaw 最终应生成:
|
||||
|
||||
- `reports/openclaw/final-supervision-report.json`
|
||||
- `reports/codex/merge-candidate.json`
|
||||
- Owner 摘要事件记录;
|
||||
- Web UI audit record;
|
||||
- 若失败则生成 failure report 并阻止合并。
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user