"""Deployment choices and explicit remote object-pool mounts; no replication implied.""" from pathlib import Path import math import drawsvg as d from .architecture import INK, GREEN, BLUE, PURPLE, MUTED, LINE, FONT def build_deployment(output: Path) -> None: svg = d.Drawing(1800, 1550) svg.append_title('Endor 部署与对象挂载:本地端、自部署与平台集群') svg.append(d.Rectangle(0, 0, 1800, 1550, fill='#fcfdfb')) def text(s, x, y, size=16, color=INK, weight='400', anchor='start'): svg.append(d.Text(s, size, x, y, fill=color, font_family=FONT, font_weight=weight, text_anchor=anchor)) def box(x, y, w, h, fill='white', dash=None): svg.append(d.Rectangle(x, y, w, h, rx=16, fill=fill, stroke=LINE, stroke_width=1.3, stroke_dasharray=dash)) def wire(points, color=GREEN, dash=None, arrow=False, width=1.6): p = d.Path(fill='none', stroke=color, stroke_width=width, stroke_dasharray=dash, stroke_linejoin='round', stroke_linecap='round') p.M(*points[0]) for point in points[1:]: p.L(*point) svg.append(p) if arrow: x, y = points[-1] px, py = points[-2] angle = math.atan2(y-py, x-px) for sign in [-1, 1]: svg.append(d.Line(x-7*math.cos(angle+sign*.48), y-7*math.sin(angle+sign*.48), x, y, stroke=color, stroke_width=width)) def dot(x, y, color=GREEN): svg.append(d.Circle(x, y, 3.5, fill='#fcfdfb', stroke=color, stroke_width=1.5)) def icon(kind, x, y, color=GREEN, scale=1): g = d.Group(transform=f'translate({x},{y}) scale({scale})', fill='none', stroke=color, stroke_width=1.8, stroke_linejoin='round', stroke_linecap='round') if kind == 'runtime': for yy in [-17, 0]: g.append(d.Rectangle(-26, yy, 52, 12, rx=3)) g.append(d.Circle(-18, yy+6, 1.5, fill=color)) g.append(d.Line(-10, yy+6, 17, yy+6)) g.append(d.Lines(-13,20,13,20)); g.append(d.Line(0,12,0,20)) elif kind == 'vm': g.append(d.Rectangle(-16,-12,32,24,rx=4)) for a in [-9,0,9]: g.append(d.Line(a,-17,a,-12)); g.append(d.Line(a,12,a,17)) for a in [-6,6]: g.append(d.Line(-21,a,-16,a)); g.append(d.Line(16,a,21,a)) g.append(d.Lines(-6,-5,0,0,-6,5)); g.append(d.Line(5,5,10,5)) elif kind == 'pool': for dx,dy in [(-12,-10),(12,-10),(0,13)]: g.append(d.Rectangle(dx-10,dy-10,20,20,rx=5)) g.append(d.Circle(dx,dy,2.5,fill=color)) elif kind == 'db': g.append(d.Ellipse(0,-14,22,8)) g.append(d.Path().M(-22,-14).L(-22,18).C(-22,30,22,30,22,18).L(22,-14)) g.append(d.Path().M(-22,1).C(-22,13,22,13,22,1)) elif kind == 's3': g.append(d.Ellipse(0,-16,23,7)) g.append(d.Lines(-23,-16,-18,23,18,23,23,-16)) g.append(d.Path().M(-17,6).Q(0,17,17,6)) elif kind == 'gateway': g.append(d.Lines(0,-24,24,0,0,24,-24,0,0,-24)) g.append(d.Line(-34,0,-12,0)) for a in [-13,0,13]: g.append(d.Lines(-8,0,8,a,34,a,29,a-4)) g.append(d.Line(34,a,29,a+4)) elif kind == 'laptop': g.append(d.Rectangle(-37,-27,74,46,rx=5)) g.append(d.Lines(-37,19,-46,30,46,30,37,19)) g.append(d.Line(-8,25,8,25)) elif kind == 'phone': g.append(d.Rectangle(-21,-35,42,72,rx=8)) g.append(d.Line(-7,-28,7,-28)); g.append(d.Line(-5,30,5,30)) svg.append(g) def runtime(x, y, label, color=GREEN, compact=False): icon('runtime', x, y, color, .85 if compact else 1) text(label,x,y+43,14,color=INK,weight='600',anchor='middle') wire([(x+26,y+5),(x+58,y+5),(x+58,y+68)],color,'2 4',arrow=True,width=1.1) icon('vm',x+58,y+83,color,.62) text('Aster VM',x+58,y+112,12,MUTED,anchor='middle') text('ENDOR / DEPLOYMENT ATLAS',40,43,14,GREEN,'600') text('本地、自部署与平台,连接到同一个对象世界',40,91,29,INK,'600') text('部署位置可以不同;Tessera 通过本地运行时组织对象,并按授权挂载远端对象池。',40,123,16,MUTED) # Two independent self-hosted examples, each with its own simple storage stack. for x, name, count in [(40,'自部署 A',1),(565,'自部署 B',2)]: cx=x+240 box(x,165,480,700) text(name,x+24,204,23,INK,'600') text('独立服务 / 独立数据',x+24,231,13,MUTED) icon('gateway',cx,295) text('Nginx / Traefik',cx,345,17,INK,'600','middle') text('可选网关 · 云上可用托管入口替代',cx,371,13,MUTED,anchor='middle') xs=[cx] if count==1 else [x+125,x+325] for i,nx in enumerate(xs): wire([(cx,385),(nx,419)],arrow=True) runtime(nx,445,f'Nexus Runtime {i+1:02}') wire([(nx-28,445),(nx-50,445),(nx-50,594),(nx,594),(nx,611)],width=1.2) for nx in xs: # Small deployments deliberately show a simple stack per Runtime. icon('pool',nx,642,scale=.8) text('Aerarium',nx,688,17,INK,'600','middle') text('Object data pool',nx,711,11,MUTED,anchor='middle') for target,kind,label in [(nx-65,'db','PostgreSQL'),(nx+65,'s3','S3')]: wire([(nx-25 if kind=='db' else nx+25,650),(target,748)],arrow=True) icon(kind,target,777,scale=.7) text(label,target,823,13,INK,'500','middle') # Every pool has an explicit route onto the platform mount lane. wire([(nx+24,628),(nx+50,628),(nx+50,590),(x+450,590), (x+450,925 if count==1 else 955), (1100 if count==1 else 1080,925 if count==1 else 955), (1100 if count==1 else 1080,610 if count==1 else 640), (1195,610 if count==1 else 640)],PURPLE,'6 6',arrow=True) wire([(cx,865),(x+16,865),(x+16,295),(cx-35,295)],BLUE,'2 5',arrow=True,width=1.1) dot(cx,865,BLUE) wire([(cx,865),(cx,901)],BLUE) text('… / etc · 更多独立自部署',565,897,14,MUTED) # Simplified platform: two gateways, a complete K2,3 mesh, grouped storage. box(1120,165,640,700) text('Endor 平台集群',1144,204,23,INK,'600') text('平台托管 · 多区域 · 按需扩展',1144,231,13,MUTED) for gx in [1300,1580]: icon('gateway',gx,295,BLUE,.8) text('区域入口',gx,337,14,INK,'500','middle') for nx in [1220,1430,1640]: wire([(gx,350),(nx,406)],BLUE,width=1.1) dot(gx,350,BLUE) for i,nx in enumerate([1220,1430,1640]): runtime(nx,432,f'Nexus {i+1:02}',BLUE,True) wire([(nx-25,432),(nx-48,432),(nx-48,575),(1440,575),(1440,600)],BLUE,width=1) text('… / etc',1708,426,13,BLUE,anchor='middle') box(1195,600,490,99,'#f5f8fb') icon('pool',1240,642,BLUE,.8) text('Aerarium 对象池',1282,637,20,INK,'600') text('平台分片群 · 远端挂载接入',1282,669,13,MUTED) for target,kind,label in [(1280,'db','PostgreSQL 分片群'),(1600,'s3','S3 分片群')]: wire([(1440,699),(target,744)],BLUE,arrow=True) icon(kind,target,773,BLUE,.85) text(label,target,824,15,INK,'500','middle') wire([(1440,865),(1740,865),(1740,295),(1608,295)],BLUE,'2 5',arrow=True,width=1.1) dot(1440,865,BLUE) wire([(1440,865),(1440,1015)],BLUE) text('统一备份 / 区域 HA:沿用系统架构图,此处省略展开',1440,851,12,MUTED,anchor='middle') # Dedicated labelled lanes keep federation separate from local remote-access mounts. box(500,969,780,31,'#fcfdfb') text('自部署 Aerarium → 平台:经授权挂载对象池,保留源端的数据归属',890,990,15,PURPLE,anchor='middle') wire([(280,901),(280,1015),(1640,1015)],BLUE) wire([(805,901),(805,1015)],BLUE) text('远端挂载 · 可选择自部署或 Endor 平台',40,990,15,BLUE,'500') for x in [280,805,1440]: dot(x,1015,BLUE) text('本地端',40,1071,23,INK,'600') for i,(name,kind) in enumerate([('Windows','laptop'),('Linux','laptop'),('macOS','laptop'),('iOS','phone'),('Android','phone')]): x=40+i*350 cx=x+160 box(x,1100,320,340,'#f9fbf8') wire([(cx,1100),(cx,1015)],BLUE,arrow=True) dot(cx,1015,BLUE) icon(kind,x+68,1160,GREEN,.7) text(name,x+118,1147,20,INK,'600') text('Tessera 前端',x+118,1178,16,GREEN,'500') wire([(x+145,1190),(x+145,1200),(x+65,1200),(x+65,1208)],arrow=True,width=1.1) text('本地对象',x+38,1226,14,INK,'500') wire([(x+105,1221),(x+155,1221),(x+155,1255)],arrow=True) text('挂载',x+166,1240,11,MUTED) runtime(x+155,1284,'Nexus Runtime',compact=True) wire([(x+181,1284),(x+293,1284),(x+293,1115),(cx,1115),(cx,1100)],BLUE,'2 5',arrow=True,width=1.2) text('本机运行 / 远端对象访问',cx,1420,12,MUTED,anchor='middle') wire([(40,1482),(100,1482)],GREEN,arrow=True) text('本地对象 / 存储依赖',114,1487,13,MUTED) wire([(435,1482),(495,1482)],BLUE,'2 5',arrow=True) text('本地 Nexus 挂载远端',509,1487,13,MUTED) wire([(850,1482),(910,1482)],PURPLE,'6 6',arrow=True) text('自部署 Aerarium 挂载到平台',924,1487,13,MUTED) text('目标架构示意 · 挂载不等于复制;鉴权、命名与写入策略需另行定义。',40,1526,13,MUTED) svg.save_svg(str(output))