mirror of
git://f0xx.org/android_cast
synced 2026-07-29 01:38:41 +03:00
sync new docs mostly
This commit is contained in:
@@ -8,3 +8,6 @@ out/
|
||||
ota-publish/
|
||||
*.apk
|
||||
*.jar
|
||||
|
||||
.venv-pdf/
|
||||
docs/_pdf_build/mermaid_cache/
|
||||
|
||||
@@ -9,3 +9,6 @@ build/native
|
||||
*.iml
|
||||
.idea
|
||||
local.properties
|
||||
tmp/
|
||||
.venv-pdf/
|
||||
docs/_pdf_build/mermaid_cache/
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -65,6 +65,10 @@ ota-publish/
|
||||
# --- Scratch (not for commit) ---
|
||||
tmp/
|
||||
|
||||
# --- Docs PDF build (local; scripts/build-all-docs-pdf.sh, mermaid_render.py) ---
|
||||
.venv-pdf/
|
||||
docs/_pdf_build/mermaid_cache/
|
||||
|
||||
# --- Python ---
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
@@ -453,7 +453,7 @@ endobj
|
||||
endobj
|
||||
78 0 obj
|
||||
<<
|
||||
/Author (Android Cast project) /CreationDate (D:20260611185330+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260611185330+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Author (Android Cast project) /CreationDate (D:20260612190915+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612190915+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Android Cast \204 On-Demand Remote Access \(Reverse Tunnel\) Proposals) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
@@ -995,7 +995,7 @@ xref
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<bdb49c5d90f4a37cbafc93ebc8e3f036><bdb49c5d90f4a37cbafc93ebc8e3f036>]
|
||||
[<9a9de1b3349f63671f79c68f49ceb325><9a9de1b3349f63671f79c68f49ceb325>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 78 0 R
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -107,7 +107,7 @@ endobj
|
||||
endobj
|
||||
19 0 obj
|
||||
<<
|
||||
/Author (Android Cast project) /CreationDate (D:20260611185330+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260611185330+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Author (Android Cast project) /CreationDate (D:20260612190925+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612190925+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Stream dump \204 developer debug recording \(FR 0.1\)) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
@@ -220,7 +220,7 @@ xref
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<ba707e13386f6c1a64c9a32f17186d4f><ba707e13386f6c1a64c9a32f17186d4f>]
|
||||
[<3b9b84c2007c5695de8ad1fc8eea63e2><3b9b84c2007c5695de8ad1fc8eea63e2>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 19 0 R
|
||||
|
||||
@@ -148,7 +148,7 @@ endobj
|
||||
endobj
|
||||
30 0 obj
|
||||
<<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 42 0 R /Fit ] /Rect [ 45.68504 443.2874 191.069 454.2874 ] /Subtype /Link /Type /Annot
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 42 0 R /Fit ] /Rect [ 45.68504 443.2874 97.70104 454.2874 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
31 0 obj
|
||||
@@ -245,7 +245,7 @@ endobj
|
||||
endobj
|
||||
44 0 obj
|
||||
<<
|
||||
/Author (Android Cast project) /CreationDate (D:20260611185330+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260611185330+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Author (Android Cast project) /CreationDate (D:20260612190925+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612190925+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Android Cast \204 exposed services catalog) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
@@ -393,7 +393,7 @@ endobj
|
||||
endobj
|
||||
72 0 obj
|
||||
<<
|
||||
/Dest [ 42 0 R /Fit ] /Next 73 0 R /Parent 45 0 R /Prev 71 0 R /Title (URL shortener \(preview \204 not deployed\))
|
||||
/Dest [ 42 0 R /Fit ] /Next 73 0 R /Parent 45 0 R /Prev 71 0 R /Title (URL shortener)
|
||||
>>
|
||||
endobj
|
||||
73 0 obj
|
||||
@@ -413,10 +413,10 @@ endobj
|
||||
endobj
|
||||
76 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1071
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1045
|
||||
>>
|
||||
stream
|
||||
Gau0D_/A!e&A@6Wk#AjK7MR@LP'_8_c8O<_lR#5"!=E">A$0B@92\@QpH+NV-V]ej%Tj!u!Kkr[I.N)_&V1cP1>Jo^^ekDVNsQ@)jVK%RPX9X&Mt5f@"7g;@b+btFE=-u_7r^:YnUEM<`<At<!`:_tik0diB69RT0NSMS:9oMoU"ggp"i>Qk8p^ONdjUo0K&^DOPF8G1Q8QmsF`9od%cV9)Xf0&CF"JnNU#M(kqW]r&s$HMd*.Inl,lA7XVatGG@eZ[$9Vr\Ya1m[,-P7;3PFauG'DE#4@Bt-4f?!gJm*rb#g%#:7f=[5'G>bJ]N5d\9N&)`.[t^N:ApHe&Hm&C[KS8\=pkOI>]3*GW=A2?$C'<`R:hf$EEn5jc3b<g=jA+05d$+t&TC-j-MRRc2SHjV6KYi5rXe=XQQKmbe\Cm+$_OYIR2<qp,>N#Tf]HlE2!hWTLTlD_5Q/Qlq7^Ul/HhlBJ7(tklOog&n9G&5mBjbs.)X];IKW86.]o8:h@4>HgNTN*'9+Fs7)@lm#/:jS!ft^gr\&th,P82.^4<?o=30YX[=`&hYqT8`(Taa9);OADS'F"YES9Vi5hZi!8&$s[kC\uA\dX!-l:Kk'g1mA[Q1;$n>7"H*M6O#FMbJR4;jr4>Fio>U.0c%1,`nW[[GXWc+6:Kj,Cc.Z5='/l*.)GUI%NUKb&%;8.fc3F&(I+-07XOLF/D6X]BG"nn8qbmgDch.fWJ0lX-2Z_W?@'hID*\e=JGq12:YMYo@$Tr2EX#g-WUFp+VT&PE584![hs/L^nkJ_[\BAd?81Xir-b:%I4)MW0cbe5iJfD29;fLN1Z)R:<^`F9N5GF)ihdJ[o&9FspCO"BJ34`#uNrV:uV7ZEYO9Vc-Z#Kp5HjmGj_OqIB5paIofJtGKTGCY.hMU#[Rho%<cg^d$X2F[C\_Qki*p=kO2=dt/XGgtDG+t3H;+:o`D!RU#lU\`aGejW9jO"3`F5<6V]GoT%(otR#kDQZB-hG<&[&qC<TqR2e1fA0Agh="pWF5Pb1%ZV)`e%EiqeasYN>R[9@2*X1WsTkUUIT*SKDtti((.8~>endstream
|
||||
Gau0Cd8&FM'Rf-pmaG.4&Rm+_d_1pKChRMJ+Rk%@\V["ddl=\)1B)_].29dP\]@^F</OtRj)a[smi`8%6i)#3"?=J2s,[5SOC&'GK,F/hR,g=nGKB0qQj$($_AL]X%Dlcqj8X'X#W!C(#_H`WKP:WVb31N+TW`NX9sT=A+^ZDr"i<;+8im"cdNG8mK]@=aK2Goh,tf(rA)W$f'Z@1E),YttH6ir-T\u,;l=q.++-=6k]g#R`_Vaj.MKD/dG#+Q\A=*pQr;2k''X0H3dd]h9_X>;r$ms<i>-gCS3B$>61d/_MMH@+E<iUI/7RNX2KaAC,gVXDp=W5X<m7BN*+X3B^m\(c^-5Q5;(5&.'.KSfI$<:;->N7s[1M(q2kYBU.f9GpkkP%$*RQ"i]X93ngq830]cCj0>.I4$aj!I.Y$[8[eUJU]q=pbMmlnnfn')9P8()-FO:hXb/TLGW_2&Ek7,gu'OO;R26@LCc!S9cM0U>crH8^q=HUf68!M-gU&9r3(@/'4JuFlbl=q'C",<X[ObF&Ui(Yd+8^LW=s:*;[g6>S[_+Ujf[6VXFY\IOU5`5RMo@S/jW*PQ(jEjW@O"&]SpJ@A21;$CA&/_kF8-po&HoGDH=D[YOLF#V>XAiK,\qgkh(.'$"+=@YM480QDt:P51!PM-)6$$=7+miM$J0*nb`kimc`F%n:%CID,Hj-.0lo>\;CW%"*su'42rBVOq;Jb,Q00!LIV`l[.q@o6(7.ZiJodE0mkgLYb)>YPAIX##!k8qMhiKEF0@WR,=jnU%=7B^UVa9<_7*00*\EgRTlVZ<)<Gbhh#W*_cq).hP-ui,/>6Xid[`)(Z/U`cOalo[<$TA*B:($/RAI&&PIK2^`iF&PG<+#F?UW1QV5<<YNWlq62mVD]h`o;h)JRCFQ0F[G\I'`/K:%_-72TOOGl;;Najo$jEABj/:[<5nt2p_(ZKj]i'Zl2^7S.qcB#u/dpQ=]XZ`OT^P,;2$No!Q00n)BrG[R@S[]HhJ=U2!S$qR_(62OkCL.%F$]MV&*pjhgIKLXV!/C~>endstream
|
||||
endobj
|
||||
77 0 obj
|
||||
<<
|
||||
@@ -441,10 +441,10 @@ Gb!#^lYkfA'*"Q<@_NhD('u9a0.T;lE-ARK2V3(](W?CU%,#6/D.H57kWJ9Ps8*6O=dEX@VX/2I#SMpT
|
||||
endobj
|
||||
80 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 3145
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 3309
|
||||
>>
|
||||
stream
|
||||
Gb"/*lZ8Od'*%C7U<@\'EAeD\i?=oEes5Xqj*%bc@O\e\6$4X[,DcOX"@dRIWSHB[4WlFF+@*BEY3NIVf.]o`k0a.nT!8>PI['W*]`L/46haIZKtqpCMC*$kIL?tPUG#-[L/QJHUT_iZoQkpW<_X%\_esj!a`o6aN)u[nOtL)0?4uH0IWG>m(N;.h(10QsA7BXkUSGn_QSACQMiK5^i:6q-O&/aPW-^%=)7R1Gb"]L7Zr<YG';?H#UfeM(?,3+o"uCbs0%M1ghLN;HI%P^=G<MD#go,Y\1Cj/D6U\grl4i>U:,ErM/pN]NcuAOg-A;"6=O0)g>(RK0GAc(rJlcna6[*u4V#(POru*[3s,]%aA%'?^k/4t6!u9rjl!$+r+7N2i0tKb`XX>HmS3U"/K&Y!'@cdqfk=r>T'6d0uTGb-sRdJI+f;=au%5]8WWE1^6LcO/#<dpgMS^l[TT:uNj&B'caC&mk=qaS.Fr@6jnFh."4J@Rl$;5g^"L[R`C=E5HNK5ML5N24#^orH$JKkPUKV1d'8Cb+Mo+Cq+Cc+A.478"P-^Y<%kUFZSlTRf[TYOV*37?`0A]mg8Ck1mkFbEkXb1/3W1Z^K0dJAWHt@`:gHUSHEPdM:fLA(`b(LOaXk:/c]k^CTN.6E@nBo("Y7DDt\U7UOF,rTc/5Eu(o0cD`V,6L-$WXg#$Va[c[p%.WLcLdbraY<rg]d0Mf5Jp?!1fC7diq4.;o1QGJS.J)>*B6HL.b^TYW8ZLkY5f\Elhi>%*3Z`Z1(+((5NW[m?*5=[42TjNln.DQ1mj&F(^OInUHB&_=3dus^WR_!a5gO>h7&qZ]N/XF+"$'ZI,5W>;!b#kO\5XGtQetk>\flZaU4[>s3K]K[-`X>$Umd%nl\!^9M5_q5epVoTT4keb&VV(K_-dsTR/_[f`FhPIk[c#;bYEfMn'+KdWTC1L#Ok193kkB2+%[H]8[?/HZ4jTFSC;$0b'+(^[`4&Q)/kJ-[@if66<&Qc@3!/Yj3?<[g\e\#+&lI<CCR'gH"h17L>$r0g,#<3=jOOL[oWo3S^Ni&%:-IUVd[:(m<NWL*nCWqJb7\h(uMTY5WpY4^rl8Ihde=HQa:60c\1_k"^O\m)5dX>K`Q&gIQRbuLD\nA5`X'kp+j,DgA&fcXfoYTCb+^YA\$Y%Qo:>)#4/`i3t`t_.Ra9,Zd7/$Ysd[!q#0GJlB@g:.o?.lF*3/Pq_%R4Ws>PY/It<jLR`iHD`sC.k].C!CbS9<3j=lm?l6t-?n:V0kF_(U<n`,,>R,_S*U(Ud*S<tF#VhMT(V41;NG]i<E05#L8Q@XC(q6ggWZ##iD%-@F@T&tLYn]?LV0pCTb(ZkZL*ZM(o)R&bZEN+iZ7.5kdQ<!RE#f-6.QYB-;6TZ$'0*FLO<1m#"mWH+bY?OdZ8Yk,7^'4T8ciFgjMe%7Iltkb<:7Z;/gI,AG8^).=jbc1aMiL/,C<4ONFZurGLkDg=7:<4P@VQb.oElQ<(YsYZq_"h&@X)j7!@buR-PLX3;V0/"Wf:MGY-gS#ZuoKTKBJro^,C4GqJ^dds/-:`e.kpXfeUjH'R\Ac0:*#SkG.#J]$h?>[ilD^NIWZBbG_Q.lMH3bY_60c5[.e<OG?=W?Y%Q#M$uEehCf1$!QIZLkm3XMS\muM!uSo0gDIX8KgY>T,\bom%-74l5Q1Ca`4j4LXfSVV">fTNGCb=aG?et[n@M,2kjV;IQIVW^"la%8gBI5p2`jW7&'6OD(?6a-Y>5==*^gS`O@r8f'n*(=DX4[C^$25>e`R$CsR<=nR]C,DpS.uj"reV*4AXfY,UOMYp:9^7\$qSime3I.mi3s,&/h/Uh&N9?N1UEcN$lDK^-J@+Y55&nRrbNIc3k>[o).A)W6AtXESVT2I'+=S-R-^g4V7sL$2+g*9.JTDA5RXMt8=@$:!<.&IPXF:1(m+\&_c,^.X$lGTp8$A]!rmePi?.2]*/oC0)YD_guG_/BM9V7[Ue]bp;Li3'RO/<@1kmlP7^0[a8s\5UlFI=QhABhst0aZIKGU<[O>`d:l'ON_]W$]X8aCAh'#Ih+`)>cp^nVBuY$oq`rN[0a+D"D+gfe$7Nd0#_,<L+tKs8.*>M[Y![,r@5:6,#f_fE,%57NB:^o0']^HD-_p"Y__]gs1L6KA?'l?s(\Fangi.Et](g7]V.Jfsfa5^h?NfV:\2pcS/5V=tA=._l%+W_#^-P:IJP%\LQH8hAo/kQ6bX=@eHpuDdM_#1OhZmS>,0NJ#b>C%_cN,Xoj"nR8J<q?&bY)QeHKGkCbUn[d@H>%(>@@UJhB$0]QF(cW37'APQb.%26+Q*=K*QCJ$_%f7c^M\-3RnM1+i<"QXdd`%)BEK:8ik9!1Ns't;nLh&;OqP32/h\1,&maQBThH\9<2s?g6W>rEMVO[l+?).ZP*-`7I9X9VdXds4tN"C#+C-mZ[dp>KB#EZ6[S8pl*][SW?XL#e;';.L.p)dHj44"HR]G=/cI!61&1FZGRX&]VaV.V3FRJ@U=R%GT#3k]l3lblYXS@u+Q@WliC?jMT;@Rj/_<M5aB@HT&6SZJZ^`fNq^5d",[_pQ,3bu%=!Ai_pCW7LrFuCCA-=J-dm#G1FcpM8V>shYR1rfM8ZT[$MMNa)#=G3qfc&\68hK)oM]F[&s5-YE,4Ej2b$\Pg;Au'9[qGZl__4r*lWA(h*C-MY3_f2M.B5#EA_&^O[9Z*2[leLu;Nqpn/1r7>V&P8tLC#+LK#kD&bRaO+O<s=h[^/h9,F^<7GD#-@&:V9SI>u0]flo&DFs%@tJVO7/?@`["a-]=TJkCt.X^E#@0fa)]9-Yli[TpcQ+MR;HIZE&1:"lut\be?\OE4o!g>>$(3JG)AbGjYK/prr@^jPM9UCqocQJL2P!tpU"e+9#bBFk%T`M7,"=RcJ9^VK[Ijk"2'^AJpcc`KY-e^GR9ho*RBJ5YY:^U;rnc?Zn.5nL>6cD`[o*?o.3]jNLmrE8K@7mU8YC[qH;RuP5M"(=3Ybqg!:.,Ta'ZjX-7Xp80F8*)%ps1-lUWMPla=HQTT1Po"dH;,TOf\boVHKW/^jlX<-JC>6&.Sa4*=,:5Pc=q=t>5WG4I6"c4@Zq:#q1^LieJNEDa6/#mmH'AVkp;4OZ88DL=OBl;^Upo3keF?Uh!+~>endstream
|
||||
Gb"/*D0+Gi')q<+Z/_7a3)0/P^?DDWl,^"J\!In$;Qfa[EuKpf,UO5AOH:JAPQ(8[O<RCrUe&X"V_cqERM5n7Md6=pca09%;"7&6s)urnk@7>k1scMe(SDYO!TR7#3Bm7\U2<C=VJLBLeUUDm/$"!B_"Y&P?dpi6RL\@8BW>m+f9Iif>p3"'J&3M%NO[4l&J140P%mRWg4'Ii&$bDEj;D3._Y+RVMB=:VWku9J'M!&qSYVf+4e!#3g)ZW)_>,DL]4`ni([c;WdeSs^XDHdl*\:QWVBWLu54]YIHcdM+i@UT:ohWjAoS*F=mE]K+GmY3O3Y6+>Sf=fI%_93U*?N$Q\>>n2\/3ltV<`Vk"c\#QRHQBGjt?8a^OJM&\T7N^0$69R.:!ch-E>H*$WB7)d'EblQ.*[o&X';dB]g3"R\E)(C3)4Y:2aEn:7aA2&a@H/EeQo_ahb[])kHB`%\NP2B4(s1]I5`K@(pjBBJ*Jtb4thD>l+&B\N\o?:gpQa]H)u?`H`$f'a84faU@e5B:4HHd8Fq]KZouKG/U:Ea?EFU.o'bJ.iMj2X/o2PbJ>ZA@$95Xls?f-d]]FTDd^TIpMf<d<RHnE-:NR?9[()/mZ/Qs@j=+`d;oTXRW0h1Ne>Y$@%N.s<055j"58OW/r6#[I1DK'%5t`HeMDRPE>-b($eI<"FFI_Tp.9"&@:OKo:P`^eidVb2/Hu;i'7(ci,EVm,nZka<nDo2sSGHJ/a&Br'oA&D;.N]ks5Cpp%S`L`>1l!Gc:,>\:p:m+)'2SM'Ja4sbT:[G8Aa7S2&Z65-Op@ml^D8)CNu_0a5>='.lM+aqA@GNI<"C[aCd?/D1>(M3q^:;+@2iR,<:mrKmjf41&kPPK_6f/k8m1Noh:+aAH^<CMK%r(Pcd9V1qtM'n1!H4_1h)#haXEshp]3g@TC!35//oeRiX@77]7%dH[;trs&TVl-SQpPgBLnZ&8TMU'ZBqq@>gpW4jem_V7q4'B(E\STHV#`SL$/Z+_`C9Tqr+T-f_i?**`Q?P)@a&fH#9`%VV6>Pg'aJ`e]a&HG>JZ"S^pj>O=o@Fe$+;dm:G`[4fhSX"6m8E0YV%[5WpY4^rl8IhdeCJf<\rnc]fKT"[bjS*J6\W_Lsgr42(a\LR?p.5`X'kp,[Eag;Zaa2HHOWgFBd>gTJ!hV@=<>JU!s<N^SdnL!$#IqagZ%,q=D`s#uso?.(H$%>t/q?_1Zr^.Y7!qB77rbC`[IU#quJ4-.pIEOc\C@PC733j=lm?lI+/?n:W[i1K>N<n`,,>R17"4"ui\3j=lk&CO360'8HW[W?dsKo8W_aLZ7k`Oqc':u0:jCNJ^3OHUTRMk/YnRdq2_=)M!^Zt-S>p5Y6),5^ZCV=nHEcoO\<^=e/?Mi%P>H.HbZli`?NGLGMHZTt'>K"5Ta5Rqh"b^6H:<0@O)KGP/:IX+o6W'#h?6Uiu:4Clq`B?ep>Q](UD7eWFV>)@;gGLkDi=.aZlOB'Kh<MFT!kd%`>d$b`i.;i@oLn)g$OR!X+*;s]W!tVk-]HqM=">%K>TZh3WA,BM&?;'>X1u9dRnXpc!2)RNQYB%CRS?>uQ\anAnbaHq@92$hT2i1cKP11_Yg!',NUZ-Zs"V-dKeIa)d*EC*AW)n<"5gkl,BZZ)B&OWo<cLVS)YQml<dbf(>a&Q&9gA/NE@6tOX(Wp-<q:bN9?JYtq5M`pCmR:s8e8tSL&V?k<q;@tX%U01HIh1lUD,b<pl2You1R?lTF/Jr]qS#2[qj6V-aKNXg8\V4N%diAl@@Tf:,+lNF[WfNMbX7@8mpt%:QT/33<Gs`lc8h[Ho9WT\Jr,3Ud^\-_.JFPYGna??qnPC-LHFaF%R6S<-t7U'C-,$S?+gafM(\X$M2QVX,GQ/_R4$aYEDGn_L(G"94IDa!DFgghc8"\Zh66p9CEg0,T7%'309N_HI[,OQMk#@-DVOQ%oB,.`!SDI+T0hg)Cec;%QkpLG*?`cl^H@T!NhOri3l_DEWW@Q1o!%T2`0&=?6_tHUU(_[7'Kq[dHL8N>gl5D'3O$k9])Ic0@rH$q;67A0qDkM/aK6s@,2:$41?e^3.ENY-p^VE,20))2UpkU6@2;@`kW*n/:a!_=l&CM74f*N#mg;Q2--G@N:5S6C+F7+U%;W]Ynh?_(&;6kpOHu\O'Mp%h&cNe"7+G8ed^5u;h1XWA6gr)B''q0$>9?`mKO^foN)C2VFebGNo,HM)/YZ)Kcn>VD8F6PH_]V&Y?4L=u6O)_+dH-9!VGM/>b5LIEc)+qOEqCLQ@Q531?]M!F,$WlKJZL\>TAm_)RYqJNZoC"4W]MtA>]KEHY\W(AjfV$Co`._0#.,Y/DjW1`O-+s20=Rta=\=p76#t+_Lf-5@g*%O!S>V+gPd@#'\`K?Z7dabh:a7O*_^\-4hnKcIh[r#-YK!&!qrX(!7s#6I@Mup@)/9;*G;T;R8E&[M%N@+Ap/jMcjdhdc2b`%(Q-Hs-Q1:7=2Y#`-Cd21olkulZJj1$rms&V-)I6j_N'nu`HN<qkLDd\?SL-eYf$n5$D\;eA&16(:Yd3TMe\5dIJY%HpK/_$UHZNB]roY53HuW(Q`-k"j\@G+]kAK?7[.h:V]^^ua%!FY:'2kWkCGuZ)LnN1/Fc<7McfBL>MEZ:`DENZ_j$Ae09[6`_[]"W<DY&+k^Ac&2VY_*dG[I95oK+u41;:s)!Ol>V"!_H0lTek8Hd&(J`S]g^]Fm+EK]UmsE`i;W5i:6/b0fsl]+TN*"-IWM0Im_Id6!.PHJRieWgO<0$(:d0HgX#,,haHW>0PJR@V*eJ.FLUbWN4U-id>N`e'i%8QWLV2jSlof4A@t4lcNUA]Mf_r^sQK\A++elHJ?5]S"'I>S^D/o)i@%]e@J,LZsD^(*X:)HU9$d4A9,[kDBuC?-A7Hd,2WllFRau4972?IcGl6q/Uqtskp?s]]TIW%eEkh3+XeW^.Nn47J\X&QC(D01kj"n\R*Z=DV53bYcg>mlGo8,_\F*^QIGTi.m9>["niKR-F(7Skhn0@&E49jJNJhZ"dLVB\HS\N.ccgPNRrJSY3$2DT/4=jKT1hA)<fJJU"QUA$?_I=cfs`W,a0Xt&ZMEoKVYhN;ZqsDdo398kjR\,^@h=<p=`A*bBQ$^&i)t#"eG1N<_P<d*J2MV&Ls8Csp0h>hR`u3QYUJC_c*Rk`:Taq,Bh)arq_.\@?>=1#/FF/(kRjrnbE7f3pX0)E^XKaIr\>()]#IF_C$X)ein'G!9e*C,GG6#R#);hZ;sPkCku-#eEN\<1]E4QaX1_*&e_>2A[%L;Z;(3R$J$e.pIkNRZ;u~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 81
|
||||
@@ -479,60 +479,60 @@ xref
|
||||
0000003938 00000 n
|
||||
0000004086 00000 n
|
||||
0000004234 00000 n
|
||||
0000004382 00000 n
|
||||
0000004530 00000 n
|
||||
0000004678 00000 n
|
||||
0000004794 00000 n
|
||||
0000005215 00000 n
|
||||
0000005321 00000 n
|
||||
0000005441 00000 n
|
||||
0000005525 00000 n
|
||||
0000005603 00000 n
|
||||
0000005809 00000 n
|
||||
0000006015 00000 n
|
||||
0000006221 00000 n
|
||||
0000006427 00000 n
|
||||
0000006514 00000 n
|
||||
0000006831 00000 n
|
||||
0000006906 00000 n
|
||||
0000007007 00000 n
|
||||
0000007113 00000 n
|
||||
0000007220 00000 n
|
||||
0000007378 00000 n
|
||||
0000007477 00000 n
|
||||
0000007587 00000 n
|
||||
0000007692 00000 n
|
||||
0000007833 00000 n
|
||||
0000007932 00000 n
|
||||
0000008040 00000 n
|
||||
0000008156 00000 n
|
||||
0000008296 00000 n
|
||||
0000008395 00000 n
|
||||
0000008507 00000 n
|
||||
0000008654 00000 n
|
||||
0000008740 00000 n
|
||||
0000008895 00000 n
|
||||
0000008981 00000 n
|
||||
0000009126 00000 n
|
||||
0000009212 00000 n
|
||||
0000009320 00000 n
|
||||
0000009465 00000 n
|
||||
0000009553 00000 n
|
||||
0000009655 00000 n
|
||||
0000009770 00000 n
|
||||
0000009888 00000 n
|
||||
0000010026 00000 n
|
||||
0000010138 00000 n
|
||||
0000010238 00000 n
|
||||
0000010327 00000 n
|
||||
0000011490 00000 n
|
||||
0000015053 00000 n
|
||||
0000018045 00000 n
|
||||
0000020924 00000 n
|
||||
0000004383 00000 n
|
||||
0000004531 00000 n
|
||||
0000004679 00000 n
|
||||
0000004795 00000 n
|
||||
0000005216 00000 n
|
||||
0000005322 00000 n
|
||||
0000005442 00000 n
|
||||
0000005526 00000 n
|
||||
0000005604 00000 n
|
||||
0000005810 00000 n
|
||||
0000006016 00000 n
|
||||
0000006222 00000 n
|
||||
0000006428 00000 n
|
||||
0000006515 00000 n
|
||||
0000006832 00000 n
|
||||
0000006907 00000 n
|
||||
0000007008 00000 n
|
||||
0000007114 00000 n
|
||||
0000007221 00000 n
|
||||
0000007379 00000 n
|
||||
0000007478 00000 n
|
||||
0000007588 00000 n
|
||||
0000007693 00000 n
|
||||
0000007834 00000 n
|
||||
0000007933 00000 n
|
||||
0000008041 00000 n
|
||||
0000008157 00000 n
|
||||
0000008297 00000 n
|
||||
0000008396 00000 n
|
||||
0000008508 00000 n
|
||||
0000008655 00000 n
|
||||
0000008741 00000 n
|
||||
0000008896 00000 n
|
||||
0000008982 00000 n
|
||||
0000009127 00000 n
|
||||
0000009213 00000 n
|
||||
0000009321 00000 n
|
||||
0000009466 00000 n
|
||||
0000009554 00000 n
|
||||
0000009656 00000 n
|
||||
0000009771 00000 n
|
||||
0000009889 00000 n
|
||||
0000009997 00000 n
|
||||
0000010109 00000 n
|
||||
0000010209 00000 n
|
||||
0000010298 00000 n
|
||||
0000011435 00000 n
|
||||
0000014998 00000 n
|
||||
0000017990 00000 n
|
||||
0000020869 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<b8dfc902413467e181373d9a9bf51cbd><b8dfc902413467e181373d9a9bf51cbd>]
|
||||
[<c28c36aeaaa65554ca8ff15fe717eeb8><c28c36aeaaa65554ca8ff15fe717eeb8>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 44 0 R
|
||||
@@ -540,5 +540,5 @@ trailer
|
||||
/Size 81
|
||||
>>
|
||||
startxref
|
||||
24161
|
||||
24270
|
||||
%%EOF
|
||||
|
||||
@@ -153,7 +153,7 @@ endobj
|
||||
endobj
|
||||
27 0 obj
|
||||
<<
|
||||
/Author (Android Cast project) /CreationDate (D:20260611185330+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260611185330+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Author (Android Cast project) /CreationDate (D:20260612190925+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612190925+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Android Cast \204 alpha release & QA) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
@@ -320,7 +320,7 @@ xref
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<8e0b29f4527559737151344ff53c0eb9><8e0b29f4527559737151344ff53c0eb9>]
|
||||
[<ddd886f869ceb4f407d340fda95ecc82><ddd886f869ceb4f407d340fda95ecc82>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 27 0 R
|
||||
|
||||
@@ -92,7 +92,7 @@ endobj
|
||||
endobj
|
||||
17 0 obj
|
||||
<<
|
||||
/Author (Android Cast project) /CreationDate (D:20260611185330+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260611185330+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Author (Android Cast project) /CreationDate (D:20260612190926+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612190926+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Alpha soak checklist \(LAN demo\)) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
@@ -183,7 +183,7 @@ xref
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<512734ece359c2ed680f4edf5d59c938><512734ece359c2ed680f4edf5d59c938>]
|
||||
[<0031c88799808434ed1ff089b107fa6c><0031c88799808434ed1ff089b107fa6c>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 17 0 R
|
||||
|
||||
@@ -214,7 +214,7 @@ endobj
|
||||
endobj
|
||||
37 0 obj
|
||||
<<
|
||||
/Author (Android Cast project) /CreationDate (D:20260611185330+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260611185330+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Author (Android Cast project) /CreationDate (D:20260612190926+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612190926+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (AV quality enhancement \204 Q&A session notes) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
@@ -455,7 +455,7 @@ xref
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<13b148972764fd620c33507f36faea4e><13b148972764fd620c33507f36faea4e>]
|
||||
[<575181c260e696c2648cc1c1c1520a6e><575181c260e696c2648cc1c1c1520a6e>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 37 0 R
|
||||
|
||||
@@ -107,7 +107,7 @@ endobj
|
||||
endobj
|
||||
19 0 obj
|
||||
<<
|
||||
/Author (Android Cast project) /CreationDate (D:20260611185330+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260611185330+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Author (Android Cast project) /CreationDate (D:20260612190926+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612190926+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Android Cast build ecosystem) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
@@ -221,7 +221,7 @@ xref
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<8cf59d7b8ceb1fe7a630b86e8c53b8c3><8cf59d7b8ceb1fe7a630b86e8c53b8c3>]
|
||||
[<0d35921c8624899e1bb669ca58d2afef><0d35921c8624899e1bb669ca58d2afef>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 19 0 R
|
||||
|
||||
@@ -67,7 +67,7 @@ endobj
|
||||
endobj
|
||||
12 0 obj
|
||||
<<
|
||||
/Author (Android Cast project) /CreationDate (D:20260611185330+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260611185330+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Author (Android Cast project) /CreationDate (D:20260612190926+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612190926+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Commercial features \(developer-gated\)) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
@@ -135,7 +135,7 @@ xref
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<9dc9ad10d8d6171032f49fab4ea6ad8a><9dc9ad10d8d6171032f49fab4ea6ad8a>]
|
||||
[<7ebfeb118c0996a64ce6b35643381699><7ebfeb118c0996a64ce6b35643381699>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 12 0 R
|
||||
|
||||
@@ -72,7 +72,7 @@ endobj
|
||||
endobj
|
||||
13 0 obj
|
||||
<<
|
||||
/Author (Android Cast project) /CreationDate (D:20260611185330+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260611185330+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Author (Android Cast project) /CreationDate (D:20260612190926+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612190926+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Anonymous crash reporter) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
@@ -141,7 +141,7 @@ xref
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<4c86bec4fe3d879d5004ff9d9fa30ccc><4c86bec4fe3d879d5004ff9d9fa30ccc>]
|
||||
[<fb883105861ad656bbbce6ba3242676b><fb883105861ad656bbbce6ba3242676b>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 13 0 R
|
||||
|
||||
File diff suppressed because one or more lines are too long
633
docs/DRs/20100612_1_scaling.md
Normal file
633
docs/DRs/20100612_1_scaling.md
Normal file
@@ -0,0 +1,633 @@
|
||||
# Platform scaling and multi-environment architecture — design review (DR)
|
||||
|
||||
**Document type:** DR (Design Review)
|
||||
**Source draft:** [docs/drafts/20100612_1_scaling.txt](../drafts/20100612_1_scaling.txt)
|
||||
**Specification:** [docs/specs/20100612_1_scaling.md](../specs/20100612_1_scaling.md)
|
||||
**PDF:** [20100612_1_scaling.pdf](20100612_1_scaling.pdf) · Regenerate: `bash scripts/build-all-docs-pdf.sh` (Mermaid → PNG diagrams; requires `npx`)
|
||||
**Date:** 2026-06-12
|
||||
**Status:** Reviewed — planning / brainstorming (no implementation sprint yet)
|
||||
**Severity:** Strategic
|
||||
|
||||
**Documentation index:** [README.md](../README.md)
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Table of contents
|
||||
|
||||
<!-- toc -->
|
||||
- [1. Executive summary](#1-executive-summary)
|
||||
- [2. Problem statement](#2-problem-statement)
|
||||
- [3. Architecture options](#3-architecture-options)
|
||||
- [3.1 Option A — Dev cluster (draft default)](#31-option-a-dev-cluster-draft-default)
|
||||
- [3.2 Option B — Tighter cost (minimal split)](#32-option-b-tighter-cost-minimal-split)
|
||||
- [3.3 Option C — Cloud-first hybrid](#33-option-c-cloud-first-hybrid)
|
||||
- [3.4 Comparison matrix](#34-comparison-matrix)
|
||||
- [4. DR decision — recommended path](#4-dr-decision-recommended-path)
|
||||
- [5. Current vs target diagrams](#5-current-vs-target-diagrams)
|
||||
- [6. Service split rationale](#6-service-split-rationale)
|
||||
- [7. Environment promotion model](#7-environment-promotion-model)
|
||||
- [8. RSSH and connection capacity](#8-rssh-and-connection-capacity)
|
||||
- [8b. Network bandwidth (DR)](#8b-network-bandwidth-dr)
|
||||
- [8c. Global availability and GEO segments (DR)](#8c-global-availability-and-geo-segments-dr)
|
||||
- [9. Future capabilities — priority weighting](#9-future-capabilities-priority-weighting)
|
||||
- [10. Simulation lab design](#10-simulation-lab-design)
|
||||
- [11. Cloud provider notes (GCP / Azure)](#11-cloud-provider-notes-gcp-azure)
|
||||
- [12. RBAC evolution](#12-rbac-evolution)
|
||||
- [13. Risks and mitigations](#13-risks-and-mitigations)
|
||||
- [14. Estimates](#14-estimates)
|
||||
- [15. Task dependency graph](#15-task-dependency-graph)
|
||||
- [16. Resolved open questions](#16-resolved-open-questions)
|
||||
- [17. Remaining open items (PO)](#17-remaining-open-items-po)
|
||||
- [18. Suggestions (PO / dev discretion)](#18-suggestions-po-dev-discretion)
|
||||
- [19. Changelog](#19-changelog)
|
||||
<!-- /toc -->
|
||||
|
||||
**Documentation index:** [README.md](../README.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive summary
|
||||
|
||||
The project outgrew **one Alpine BE VM** that hosts consoles, APIs, MariaDB, Gitea, OTA, WireGuard, RSSH, URL shortener, and Docker builds. The draft asks for **clusters**, **dev/staging/prod**, **zero-downtime deploys**, optional **cloud**, and headroom for **RT cast**, **VPN commercialization**, **payments**, and **ffmpeg** pipelines.
|
||||
|
||||
**DR outcome:**
|
||||
|
||||
| Area | Decision |
|
||||
|------|----------|
|
||||
| **Timing** | **Post-alpha** strategic track; does not block alpha soak |
|
||||
| **Architecture** | **Option B → A** phased: minimal VM split first, then full dev cluster simulation |
|
||||
| **Edge** | Keep **Gentoo FE** as TLS/DNAT anchor; add LB upstream to N BE web nodes |
|
||||
| **Data** | **Dedicated MariaDB VM** before scaling anything else CPU-heavy |
|
||||
| **Build** | **First split candidate** — Docker off shared PHP VM |
|
||||
| **Cloud** | **Hybrid** — object store + staging dress rehearsal before prod migration |
|
||||
| **RSSH capacity** | Plan **100 concurrent SSH** / **5k heartbeats** per bastion pair before commercial VPN |
|
||||
| **Bandwidth** | Document **WAN / FE→BE / per-service** Mbps & GB-month budgets ([SPEC §12.3](../specs/20100612_1_scaling.md#123-network-bandwidth-and-egress)); CDN before horizontal web scale for OTA |
|
||||
| **Global + GEO** | **Worldwide reach** default; **deny segments** per content (e.g. no serve in `ZM`) via layered enforcement ([SPEC §14](../specs/20100612_1_scaling.md#14-global-availability-and-geo-policy)) |
|
||||
| **Future slots** | Weighted backlog in [§9](#9-future-capabilities-priority-weighting); SFU before ffmpeg farm; **F10 GEO** with F3/F5 |
|
||||
|
||||
---
|
||||
|
||||
## 2. Problem statement
|
||||
|
||||
| Pain | Evidence today |
|
||||
|------|----------------|
|
||||
| **Blast radius** | Submodule sync wiped gitignored configs; one BE restart affects all services |
|
||||
| **Resource contention** | Docker build spikes CPU/RAM during PHP console use |
|
||||
| **No promotion path** | Single prod-like env; staging OTA path still pending ([20260608_BE_SERVICES_and_infra.md](../20260608_BE_SERVICES_and_infra.md)) |
|
||||
| **Scaling ceiling** | RSSH + WG + future SFU on same VM as MariaDB |
|
||||
| **Bandwidth ceiling** | Single FE WAN + FE→BE path; OTA/APK egress competes with consoles ([SPEC §12.3](../specs/20100612_1_scaling.md#123-network-bandwidth-and-egress)) |
|
||||
| **GEO gap** | No per-content region policy yet; required before global recordings/commerce ([SPEC §14](../specs/20100612_1_scaling.md#14-global-availability-and-geo-policy)) |
|
||||
| **Operational risk** | Topology changes on FE/router are hard to roll back ([bottomline_reminder.txt](../../bottomline_reminder.txt)) |
|
||||
|
||||
**Success criteria:** PO can deploy unstable work to **dev**, beta-test on **staging**, and promote to **prod** with rollback per service — without rewriting mobile apps or public URLs each time.
|
||||
|
||||
---
|
||||
|
||||
## 3. Architecture options
|
||||
|
||||
### 3.1 Option A — Dev cluster (draft default)
|
||||
|
||||
Draft proposal: **N VMs per environment**, each role class (web+OTA, DB, object store, WG, url shortener+Gitea, build, +N for new features).
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Cluster["One environment cluster (e.g. dev)"]
|
||||
W["Web + OTA VM(s)"]
|
||||
D["Database VM(s)"]
|
||||
O["Object store VM(s)"]
|
||||
V["WireGuard VM(s)"]
|
||||
U["URL shortener + Gitea VM"]
|
||||
B["Build VM(s)"]
|
||||
X["Future service VM(s)"]
|
||||
end
|
||||
W --> D
|
||||
W --> O
|
||||
U --> D
|
||||
B --> O
|
||||
V --> W
|
||||
```
|
||||
|
||||
| Pros | Cons |
|
||||
|------|------|
|
||||
| Clear isolation; matches future SFU/ffmpeg | Highest VM count / cost |
|
||||
| Easy to simulate on FE XEN | Ops overhead for small team |
|
||||
| Aligns with cloud MIG later | Overkill before alpha exit |
|
||||
|
||||
### 3.2 Option B — Tighter cost (minimal split)
|
||||
|
||||
**Phase 1 prod shape** (recommended start):
|
||||
|
||||
| VM | Bundled services |
|
||||
|----|------------------|
|
||||
| **be-web** | nginx, PHP (hub, crashes, graphs, builder **UI**, OTA **metadata**) |
|
||||
| **be-data** | MariaDB only |
|
||||
| **be-ops** | Gitea, url-shortener |
|
||||
| **be-build** | Docker builder |
|
||||
| **be-vpn** | WireGuard + RSSH bastion |
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
FE["Gentoo FE"] --> WEB["be-web"]
|
||||
WEB --> DATA["be-data"]
|
||||
WEB --> VPN["be-vpn"]
|
||||
OPS["be-ops"] --> DATA
|
||||
BUILD["be-build"] --> DATA
|
||||
```
|
||||
|
||||
| Pros | Cons |
|
||||
|------|------|
|
||||
| ~5 VMs vs 7+; fits “money-wise” draft | Gitea + shortener still coupled |
|
||||
| Unblocks build/DB contention immediately | Second web node needed later for true HA |
|
||||
| Low risk to current URLs | WG still single point until pool |
|
||||
|
||||
### 3.3 Option C — Cloud-first hybrid
|
||||
|
||||
Move **staging** entirely to GCP/Azure; keep **prod** on FE→BE until hybrid LB proven.
|
||||
|
||||
| Pros | Cons |
|
||||
|------|------|
|
||||
| Managed LB, DB, object store day one | Egress cost; UDP/WG complexity |
|
||||
| Dress rehearsal for full cloud | PO learning curve; vendor lock-in mild |
|
||||
| ffmpeg/SFU elastic scale | **Violates** “safest topology” unless staged |
|
||||
|
||||
### 3.4 Comparison matrix
|
||||
|
||||
Scores 1 (poor) – 5 (excellent) for **current team size** and **alpha proximity**:
|
||||
|
||||
| Criterion | A — Full cluster | B — Minimal split | C — Cloud-first |
|
||||
|-----------|------------------|-------------------|-----------------|
|
||||
| Cost now | 2 | **4** | 3 |
|
||||
| Time to first value | 2 | **5** | 3 |
|
||||
| Zero-downtime readiness | 4 | 3 | **5** |
|
||||
| Topology safety | 4 | **5** | 2 |
|
||||
| SFU/ffmpeg fit | **5** | 3 | **5** |
|
||||
| Simulation fidelity | **5** | 4 | 2 |
|
||||
| **Global CDN / GEO** | 3 | 3 | **5** |
|
||||
| **Weighted total** | 25 | **27** | 25 |
|
||||
|
||||
**Weights used:** safety and cost now > cloud elasticity pre-alpha.
|
||||
|
||||
---
|
||||
|
||||
## 4. DR decision — recommended path
|
||||
|
||||
```text
|
||||
Now ──► Option B (minimal split) on-prem HVMs
|
||||
│
|
||||
├── Simulate Option A as DEV cluster (3–7 Alpine HVMs)
|
||||
│
|
||||
├── Add be-web-2 + FE upstream when metrics say so
|
||||
│
|
||||
└── Option C for staging only → then hybrid prod
|
||||
```
|
||||
|
||||
| Step | Action | Owner |
|
||||
|------|--------|-------|
|
||||
| 1 | Document service boundaries (this SPEC/DR) | Agent / PO review |
|
||||
| 2 | Move Docker builder to **be-build-1** | Sysop |
|
||||
| 3 | Move MariaDB to **be-data-1** (hardest cutover) | Sysop |
|
||||
| 4 | Stand up **dev cluster** simulation on intra network | Sysop |
|
||||
| 5 | Staging promotion runbook + DNS prefix | PO + DEV |
|
||||
| 6 | Cloud object store for OTA/APK | Sysop |
|
||||
| 7 | **CDN + bandwidth dashboards** (SPEC §12.3) | Sysop |
|
||||
| 8 | SFU SPEC + media workers (separate track) | PO spec |
|
||||
| 9 | **GEO policy schema + edge rules** (SPEC §14) | PO + DEV |
|
||||
|
||||
**Not chosen now:** full Option A in prod; full Option C prod cutover.
|
||||
|
||||
---
|
||||
|
||||
## 5. Current vs target diagrams
|
||||
|
||||
### Today (single BE)
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
R["Router 134.17.26.161"] --> FE["FE Gentoo 10.7.0.10"]
|
||||
FE --> BE["Single Alpine BE 10.7.16.128"]
|
||||
BE --> S1["PHP consoles + APIs"]
|
||||
BE --> S2["MariaDB"]
|
||||
BE --> S3["Gitea + url-shortener"]
|
||||
BE --> S4["Docker build"]
|
||||
BE --> S5["WG + RSSH"]
|
||||
FE -->|"UDP 45340 DNAT"| S5
|
||||
```
|
||||
|
||||
### Target phase B (minimal split + HA web)
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
R["Router"] --> FE["Gentoo FE"]
|
||||
FE --> LB["nginx upstream / cloud LB"]
|
||||
LB --> W1["be-web-1"]
|
||||
LB --> W2["be-web-2"]
|
||||
W1 --> DB["be-data MariaDB"]
|
||||
W2 --> DB
|
||||
OPS["be-ops Gitea+shortener"] --> DB
|
||||
BUILD["be-build Docker"] --> DB
|
||||
VPN1["be-vpn-1"] --> DB
|
||||
VPN2["be-vpn-2 optional"]
|
||||
FE -->|"UDP DNAT pool"| VPN1
|
||||
FE --> VPN2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Service split rationale
|
||||
|
||||
Draft asked to balance **cost vs CPU/RAM needs**. DR sizing rules:
|
||||
|
||||
| Workload | CPU | RAM | Disk | Pin to |
|
||||
|----------|-----|-----|------|--------|
|
||||
| PHP consoles | Medium | 2–4 GiB | Small | be-web* |
|
||||
| MariaDB | Medium | **8+ GiB** | **Fast SSD** | be-data |
|
||||
| Gitea mirrors | Low | 2 GiB | Medium | be-ops |
|
||||
| URL shortener | Low | 512 MiB | Tiny | be-ops |
|
||||
| Docker build | **Burst high** | **16 GiB** | Large ephemeral | be-build |
|
||||
| WireGuard | Low | 1 GiB | Tiny | be-vpn |
|
||||
| RSSH sshd | Low–med | 2 GiB | Tiny | be-vpn |
|
||||
| ffmpeg (future) | **Sustained high** | 8–32 GiB | Temp SSD | be-media* |
|
||||
| SFU (future) | **High** | 8+ GiB | Low | be-media* |
|
||||
| Recordings (future) | Low | — | **Large** | object store |
|
||||
|
||||
**Anti-pattern rejected:** MariaDB + Docker build + ffmpeg on one VM (draft implied bundle risk).
|
||||
|
||||
---
|
||||
|
||||
## 7. Environment promotion model
|
||||
|
||||
| Gate | dev → staging | staging → prod |
|
||||
|------|---------------|----------------|
|
||||
| Automated tests | `./gradlew :app:testDebugUnitTest` + BE smoke | + soak checklist |
|
||||
| Manual | PO feature acceptance | PO sign-off + tag |
|
||||
| Data | Synthetic | Anonymized copy optional |
|
||||
| Rollback | Re-deploy previous image | Git revert + DB restore runbook |
|
||||
|
||||
Align app releases with [GIT_FLOW.md](../GIT_FLOW.md); infra releases may cadence independently with **config version pins** per environment.
|
||||
|
||||
---
|
||||
|
||||
## 8. RSSH and connection capacity
|
||||
|
||||
Draft question: *how many simultaneous connections per instance?*
|
||||
|
||||
### Assumptions
|
||||
|
||||
- Heartbeat poll **1–7 min** ([REMOTE_ACCESS_IMPL](../REMOTE_ACCESS_IMPL.md))
|
||||
- Average SSH session **15 min** operator work
|
||||
- Alpine **be-vpn-1**: 2 vCPU, 2 GiB RAM
|
||||
|
||||
### Estimates
|
||||
|
||||
| Resource | Limiting factor | Conservative | With tuning |
|
||||
|----------|-----------------|--------------|-------------|
|
||||
| **Concurrent SSH** | `sshd` `MaxSessions` / CPU | 50 | **100** |
|
||||
| **Devices polling heartbeat** | PHP-FPM + MariaDB writes | 2 000 | **5 000** |
|
||||
| **New SSH/min** | `MaxStartups` | 10 | 30 |
|
||||
| **WG peers (lab)** | kernel + UDP | 200 | 500 / 2 nodes |
|
||||
|
||||
**Tuning knobs:** separate **heartbeat** PHP pool to **be-web** (off bastion); keep sshd on **be-vpn** only; connection pooling to MariaDB; read replicas for heartbeat read-heavy phases.
|
||||
|
||||
**DR recommendation:** Instrument **active SSH** and **heartbeat QPS** in graphs before selling commercial VPN (draft F4).
|
||||
|
||||
---
|
||||
|
||||
## 8b. Network bandwidth (DR)
|
||||
|
||||
Draft rev.2 adds **throughput** alongside connection counts.
|
||||
|
||||
### Baseline risk (today)
|
||||
|
||||
| Path | Risk |
|
||||
|------|------|
|
||||
| Single **WAN** uplink | OTA release or viral short link saturates all services |
|
||||
| **FE→BE** proxy | All HTTPS doubles through FE NIC |
|
||||
| No **CDN** | Every APK download hits origin BE |
|
||||
| **Media future** | SFU + ffmpeg on same uplink = hard fail without PoP/CDN |
|
||||
|
||||
### DR sizing decisions
|
||||
|
||||
| Decision | Rationale |
|
||||
|----------|-----------|
|
||||
| **CDN before be-web-N** for OTA/static | Fixes egress Mbps cheaper than more PHP VMs |
|
||||
| **Separate media NIC/subnet** when SFU lands | Isolate UDP/WebRTC from MariaDB LAN |
|
||||
| **Egress budget alerts** at 80% ISP/cloud quota | PO notified before hard cap |
|
||||
| **Simulate release day** on staging | e.g. 1000 concurrent OTA downloads |
|
||||
|
||||
### Planning numbers (single-site prod, until CDN)
|
||||
|
||||
| Traffic | Peak Mbps (conservative) | Mitigation |
|
||||
|---------|--------------------------|------------|
|
||||
| Steady HTTPS (API + consoles) | 5–20 | Normal |
|
||||
| OTA drop (1000 devices × 40 MB APK) | **200–400** burst 10 min | **CDN required** |
|
||||
| Recording download (F3) | 50–200 per hot file | Object store + CDN signed URLs |
|
||||
| SFU fan-out 100 viewers | 50–500+ | Regional PoP; not on FE WAN |
|
||||
|
||||
Full table: [SPEC §12.3](../specs/20100612_1_scaling.md#123-network-bandwidth-and-egress).
|
||||
|
||||
---
|
||||
|
||||
## 8c. Global availability and GEO segments (DR)
|
||||
|
||||
Draft rev.2: **global reach** + **segment isolation** (example: deny publish/serve in **Zambia `ZM`**).
|
||||
|
||||
### Policy model (DR decision)
|
||||
|
||||
| Rule | Choice |
|
||||
|------|--------|
|
||||
| **Default** | **Allow** worldwide for control plane and public APIs |
|
||||
| **Content objects** | Attach optional `geo_policy` with `deny_publish` / `deny_serve` / `allow_only` |
|
||||
| **Enforcement order** | CDN/WAF → BE API (trusted geo header) → object signed URL → app entitlement |
|
||||
| **Operator UI** | Hub/console editor for geo segments when F3/F10 ship — not alpha |
|
||||
| **Sanctions** | PO-maintained deny lists; not auto-synced from external API in v1 |
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
REQ["Client request"] --> EDGE["CDN / FE edge"]
|
||||
EDGE --> GEO{"Country in deny_serve?"}
|
||||
GEO -->|yes| DENY["451 / blocked manifest"]
|
||||
GEO -->|no| BE["BE + geo_policy DB"]
|
||||
BE --> OK["200 / stream / redirect"]
|
||||
```
|
||||
|
||||
### Phasing
|
||||
|
||||
| Phase | GEO capability |
|
||||
|-------|----------------|
|
||||
| **Now** | Implicit worldwide; no per-content rules |
|
||||
| **CDN phase** | Edge geo block for **coarse** country bans |
|
||||
| **F3 storage** | Per-recording `geo_policy` in MariaDB + object metadata |
|
||||
| **F5 payments** | Entitlement includes `geo_allowed` |
|
||||
| **Multi-PoP** | GeoDNS routes to nearest **allowed** PoP only |
|
||||
|
||||
### Example use cases
|
||||
|
||||
| Use case | Policy |
|
||||
|----------|--------|
|
||||
| OTA `stable` globally | `default: allow` |
|
||||
| Beta APK EU-only | `allow_only: ["DE","FR",…]` |
|
||||
| Recording licensed EU/US only | `deny_serve: ["ZM",…]` + `allow_only` optional |
|
||||
| Short link to blocked catalog | BE returns 451 at redirect time |
|
||||
|
||||
**Open for PO:** whether **VPN exit** in denied country circumvents policy (DR: log + ToS; technical block on **VPN product** separate from HTTP geo).
|
||||
|
||||
---
|
||||
|
||||
## 9. Future capabilities — priority weighting
|
||||
|
||||
Weighted for **platform dependency** and **revenue enablement** (PO may override):
|
||||
|
||||
| Rank | Slot | Weight | Rationale |
|
||||
|------|------|--------|-----------|
|
||||
| **1** | F1 Multipoint cast (SFU/MCU) | Critical path | Unblocks WAN cast; already on [OPEN_TASKS_GRAPH.md](../OPEN_TASKS_GRAPH.md) |
|
||||
| **2** | F2 ffmpeg transcode | High | Shared worker pool for F3, F9 |
|
||||
| **3** | F3 Cast storage | High | Needs F1+F2 + object store |
|
||||
| **4** | F5 Payments | Medium | Enables F3, F4, F7 |
|
||||
| **5** | F4 Commercial VPN | Medium | RSSH/WG scale must be proven first |
|
||||
| **6** | F9 3rd-party streaming | Medium | Uses F2 cast handlers |
|
||||
| **7** | F6 Mobile add-ons | Lower | Can ship on-device first |
|
||||
| **8** | F7 In-app transfers | Lower | Compliance heavy |
|
||||
| **9** | F8 DNS / subdomains | Exploratory | Largest scope creep |
|
||||
| **10** | F10 GEO enforcement | Medium | Required for F3/F5 global commerce; draft rev.2 |
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
SFU["F1 SFU / multipoint"] --> STORE["F3 Cast storage"]
|
||||
FF["F2 ffmpeg workers"] --> STORE
|
||||
FF --> EXT["F9 3rd-party streams"]
|
||||
SFU --> EXT
|
||||
PAY["F5 Payments"] --> STORE
|
||||
PAY --> VPN["F4 Commercial VPN"]
|
||||
PAY --> XFER["F7 In-app transfers"]
|
||||
PAY --> ADD["F6 Add-ons"]
|
||||
DNS["F8 DNS / site builder"] --> PAY
|
||||
GEO["F10 GEO policy"] --> STORE
|
||||
GEO --> PAY
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Simulation lab design
|
||||
|
||||
Sysop simulates **Option A** under dev without WAN changes:
|
||||
|
||||
| HVM | Alpine packages | Role |
|
||||
|-----|-----------------|------|
|
||||
| `dev-web-1` | nginx, php81-fpm | Web tier |
|
||||
| `dev-data-1` | mariadb | DB |
|
||||
| `dev-ops-1` | git, gitea (optional) | Gitea + shortener |
|
||||
| `dev-build-1` | docker | Builder |
|
||||
| `dev-vpn-1` | wireguard-tools, openssh | VPN lab |
|
||||
|
||||
**Network:** `10.7.0.0/8` only; FE routes `dev.apps.f0xx.org` to dev-web when ready.
|
||||
|
||||
**Deliverable suggestion:** `docs/SIMULATION_CLUSTER.md` + scripts in phase 1 — not blocking this DR.
|
||||
|
||||
---
|
||||
|
||||
## 11. Cloud provider notes (GCP / Azure)
|
||||
|
||||
| Concern | GCP | Azure |
|
||||
|---------|-----|-------|
|
||||
| **Managed LB** | ILB + NEG | App Gateway / Front Door |
|
||||
| **Object store** | GCS | Blob |
|
||||
| **Managed MariaDB** | Cloud SQL | Azure Database for MariaDB |
|
||||
| **UDP / WG** | harder | harder — keep on-prem FE DNAT longer |
|
||||
| **ffmpeg burst** | MIG (CPU) | VMSS |
|
||||
| **Egress cost** | Watch OTA/APK | Same |
|
||||
| **CDN + geo WAF** | Cloud CDN / Cloud Armor | Azure Front Door + rules |
|
||||
| **Multi-region** | GCS dual-region; Cloud CDN PoPs | Blob + AFD PoPs |
|
||||
|
||||
**DR:** Use cloud for **staging cluster first** (Option C partial) — validates IaC without prod risk. **CDN geo rules** prototype on staging before prod F3.
|
||||
|
||||
---
|
||||
|
||||
## 12. RBAC evolution
|
||||
|
||||
| Stage | Who | Access |
|
||||
|-------|-----|--------|
|
||||
| **Alpha (now)** | Developers = operators | Max privileges; shared session |
|
||||
| **Beta** | Split **developer** vs **support operator** | Tickets/RA without build admin |
|
||||
| **Commercial** | Customer admin + platform admin | Tenant isolation; audit export |
|
||||
|
||||
New roles MUST NOT require mobile app changes until F5 entitlements API exists.
|
||||
|
||||
---
|
||||
|
||||
## 13. Risks and mitigations
|
||||
|
||||
| Risk | Impact | Mitigation |
|
||||
|------|--------|------------|
|
||||
| MariaDB migration downtime | Prod outage | Replica + cutover window; practice on dev |
|
||||
| Config loss on sync | 503 (seen url-shortener) | `ensure_*` scripts; secrets outside tree |
|
||||
| FE upstream misconfig | Partial outage | `nginx -t`; weighted rollout |
|
||||
| WG UDP cloud move | VPN breaks | Keep DNAT path until PO signs UDP plan |
|
||||
| Over-scoping F5–F9 | Never ship SFU | [§9](#9-future-capabilities-priority-weighting) order |
|
||||
| WAN saturation on OTA | Global outage perception | CDN + [§8b](#8b-network-bandwidth-dr) budgets |
|
||||
| Geo policy bypass via VPN | Compliance / licensing | Separate VPN egress policy; audit logs |
|
||||
| Wrong deny list | Legitimate users blocked | Staging test + `X-Debug-Country`; PO approval workflow |
|
||||
| Simulation sprawl | Sysop time | Cap dev HVMs at 7; destroy scripts |
|
||||
|
||||
---
|
||||
|
||||
## 14. Estimates
|
||||
|
||||
Person-days (**single sysop + DEV support**), planning accuracy ±40%:
|
||||
|
||||
| Phase | Work | Days |
|
||||
|-------|------|------|
|
||||
| **P0** | SPEC/DR + simulation design (this doc) | 2 |
|
||||
| **P1** | Split Docker build VM | 2 |
|
||||
| **P2** | MariaDB dedicated VM + migration | 3 |
|
||||
| **P3** | be-ops split (Gitea + shortener) | 1 |
|
||||
| **P4** | Dev cluster HVM simulation (5 VMs) | 4 |
|
||||
| **P5** | Staging env + promotion runbook | 3 |
|
||||
| **P6** | be-web-2 + FE upstream HA | 2 |
|
||||
| **P7** | Cloud object store for OTA | 2 |
|
||||
| **P7b** | CDN + bandwidth metrics on graphs | 2 |
|
||||
| **P8** | GEO policy schema + staging CDN geo test | 3 |
|
||||
| **P9** | SFU/media (separate SPEC) | TBD |
|
||||
| **Total (through P7b)** | | **~21 days** |
|
||||
|
||||
**Severity:** Strategic — parallel to alpha; no alpha gate.
|
||||
|
||||
---
|
||||
|
||||
## 15. Task dependency graph
|
||||
|
||||
Legend: **1** = highest priority (PO: post-alpha infra). Assignee **Sysop** = PO today; **Agent** = documentation/automation; **DEV** = app/config changes.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
T1["1. SPEC+DR scaling docs<br/>Agent ✓"]
|
||||
T2["2. Split be-build Docker<br/>Sysop"]
|
||||
T3["3. Dedicated be-data MariaDB<br/>Sysop"]
|
||||
T4["4. ensure_* config hardening all services<br/>Agent"]
|
||||
T5["5. Dev cluster simulation HVMs<br/>Sysop"]
|
||||
T6["6. Staging env + DNS prefix<br/>Sysop + PO"]
|
||||
T7["7. Promotion runbook dev→stg→prod<br/>PO"]
|
||||
T8["8. be-web-2 + FE upstream<br/>Sysop"]
|
||||
T9["9. Cloud object store OTA<br/>Sysop"]
|
||||
T9b["9b. CDN + bandwidth metrics<br/>Sysop"]
|
||||
T10["10. GEO policy + edge rules<br/>PO+DEV"]
|
||||
T11["11. SFU owner SPEC<br/>PO"]
|
||||
T12["12. ffmpeg worker pool<br/>DEV post-SFU"]
|
||||
T13["13. Payments / entitlements SPEC<br/>PO"]
|
||||
|
||||
T1 --> T2
|
||||
T1 --> T4
|
||||
T2 --> T3
|
||||
T3 --> T5
|
||||
T4 --> T6
|
||||
T5 --> T6
|
||||
T6 --> T7
|
||||
T7 --> T8
|
||||
T3 --> T8
|
||||
T8 --> T9
|
||||
T9 --> T9b
|
||||
T9b --> T10
|
||||
T10 --> T11
|
||||
T11 --> T12
|
||||
T12 --> T13
|
||||
|
||||
style T1 fill:#86efac,color:#14532d
|
||||
style T11 fill:#e5e7eb,color:#374151
|
||||
style T12 fill:#e5e7eb,color:#374151
|
||||
style T13 fill:#e5e7eb,color:#374151
|
||||
```
|
||||
|
||||
| ID | Task | Assignee | Blocks |
|
||||
|----|------|----------|--------|
|
||||
| 1 | Scaling SPEC/DR | Agent | — |
|
||||
| 2 | Docker → be-build | Sysop | DB split |
|
||||
| 3 | MariaDB → be-data | Sysop | HA web |
|
||||
| 4 | Post-sync config scripts (all services) | Agent | staging |
|
||||
| 5 | Dev HVM simulation | Sysop | staging realism |
|
||||
| 6 | Staging cluster | Sysop | promotion |
|
||||
| 7 | Promotion runbook | PO | prod multi-node |
|
||||
| 8 | Second web node | Sysop | cloud OTA |
|
||||
| 9 | Object store OTA | Sysop | CDN |
|
||||
| 9b | CDN + bandwidth dashboards | Sysop | GEO test |
|
||||
| 10 | GEO policy + edge | PO+DEV | F3 commerce |
|
||||
| 11 | SFU SPEC | PO | media workers |
|
||||
| 12 | ffmpeg pool | DEV | commercial storage |
|
||||
| 13 | Payments SPEC | PO | F6–F8 |
|
||||
|
||||
**Parallel now (non-blocking):** alpha soak, url-shortener ops, Gitea mirrors, ticket **SVC-OTA**.
|
||||
|
||||
---
|
||||
|
||||
## 16. Resolved open questions
|
||||
|
||||
| # | Draft question | DR answer |
|
||||
|---|----------------|-----------|
|
||||
| Q1 | How many VMs in a cluster? | **Option B:** 5 prod VMs to start; **dev simulation:** 3–7 ([SPEC §6.3](../specs/20100612_1_scaling.md#63-reference-cluster-layout-prod)) |
|
||||
| Q2 | “Other” architecture approaches? | **Option C** hybrid for staging; not prod-first ([§3.3](#33-option-c-cloud-first-hybrid)) |
|
||||
| Q3 | Cost vs CPU/RAM balance? | Split by [§6](#6-service-split-rationale) profile table |
|
||||
| Q4 | Seamless scaling / LB? | FE upstream → N PHP nodes; cloud managed LB in phase 5 ([SPEC §9](../specs/20100612_1_scaling.md#9-zero-downtime-and-load-balancing)) |
|
||||
| Q5 | Simultaneous connections / RSSH? | [§8](#8-rssh-and-connection-capacity) — 100 SSH, 5k heartbeats per bastion pair (tuned) |
|
||||
| Q6 | Alpine simulation? | [§10](#10-simulation-lab-design) — intra `10.7.0.0/8` HVMs only |
|
||||
| Q7 | Future MCU/SFU/ffmpeg/storage/VPN/payments? | Platform **slots** F1–F9; priority [§9](#9-future-capabilities-priority-weighting) |
|
||||
| Q8 | RBAC model change? | [§12](#12-rbac-evolution) three-stage |
|
||||
| Q9 | Keep current topology safe? | **Yes** — [SPEC §18](../specs/20100612_1_scaling.md#18-safety-constraints-current-topology) |
|
||||
| Q10 | Network bandwidth metrics? | [§8b](#8b-network-bandwidth-dr) + [SPEC §12.3](../specs/20100612_1_scaling.md#123-network-bandwidth-and-egress) — CDN before web horizontal scale |
|
||||
| Q11 | Global availability + GEO deny (e.g. Zambia)? | [§8c](#8c-global-availability-and-geo-segments-dr) + [SPEC §14](../specs/20100612_1_scaling.md#14-global-availability-and-geo-policy) — default allow, per-content deny |
|
||||
|
||||
---
|
||||
|
||||
## 17. Remaining open items (PO)
|
||||
|
||||
| ID | Question | Needed for |
|
||||
|----|----------|------------|
|
||||
| O1 | Target **monthly infra budget** for phase B (5 VMs)? | Option A vs B timing |
|
||||
| O2 | **Staging URL** pattern: `staging.apps.f0xx.org` vs path prefix? | DNS + TLS |
|
||||
| O3 | **Cloud vendor** preference GCP vs Azure vs none for 12 months? | P7–P9 |
|
||||
| O4 | Accept **maintenance window** for MariaDB cutover (minutes of read-only)? | P2 schedule |
|
||||
| O5 | **Commercial VPN** — same WG infra or separate product stack? | F4 design |
|
||||
| O6 | **SFU** — Janus on `:8089` vs greenfield mediasoup? | F1 SPEC ([20260608_BE_SERVICES_and_infra.md](../20260608_BE_SERVICES_and_infra.md)) |
|
||||
| O7 | **Payments** — Stripe-only vs multi-provider? | F5 SPEC |
|
||||
| O8 | **ISP/cloud egress cap** (GB/month) on FE WAN? | [§8b](#8b-network-bandwidth-dr) budgets |
|
||||
| O9 | **CDN provider** (Cloudflare vs cloud-native vs none 12 mo)? | OTA + GEO WAF |
|
||||
| O10 | **GEO deny lists** — PO-owned static JSON vs admin UI first? | F10 rollout |
|
||||
| O11 | Recordings **451** vs silent 404 for denied regions? | UX + compliance |
|
||||
|
||||
*When blocked on O1–O7, append to `tmp/YYYYMMDD_implementation_summary.pdf` per bottomline_reminder.*
|
||||
|
||||
---
|
||||
|
||||
## 18. Suggestions (PO / dev discretion)
|
||||
|
||||
| # | Suggestion |
|
||||
|---|------------|
|
||||
| S1 | Add **infra smoke** to `validate_be_services.sh` per VM role after split |
|
||||
| S2 | Version **nginx upstream** fragments in `examples/crash_reporter/backend/deploy/` |
|
||||
| S3 | Use **Docker** for PHP-FPM pools only after MariaDB split — avoid double complexity |
|
||||
| S4 | Keep **Gitea** on be-ops until mirror count > 10 repos |
|
||||
| S5 | Document **connection pool** sizes when RSSH device count > 500 |
|
||||
| S6 | Re-read this DR when **SFU SPEC** lands — revise [§9](#9-future-capabilities-priority-weighting) |
|
||||
| S7 | Add **WAN Mbps** graph series on FE before next OTA wide release |
|
||||
| S8 | Staging test matrix: `X-Debug-Country: ZM` for geo deny paths |
|
||||
|
||||
---
|
||||
|
||||
## 19. Changelog
|
||||
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-06-12 | Initial DR from draft `20100612_1_scaling.txt`; options matrix; task graph |
|
||||
| 2026-06-12 | Rev. 2 — bandwidth (§8b), global/GEO (§8c), F10, task graph + open items |
|
||||
|
||||
---
|
||||
|
||||
## Related docs
|
||||
|
||||
- [SPEC — Platform scaling](../specs/20100612_1_scaling.md)
|
||||
- [INFRA.md](../INFRA.md)
|
||||
- [OPEN_TASKS_GRAPH.md](../OPEN_TASKS_GRAPH.md)
|
||||
- [20260608_ALPHA_PRIORITIES.md](../20260608_ALPHA_PRIORITIES.md)
|
||||
888
docs/DRs/20100612_1_scaling.pdf
Normal file
888
docs/DRs/20100612_1_scaling.pdf
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -2,7 +2,7 @@
|
||||
%“Œ‹ž ReportLab Generated PDF document (opensource)
|
||||
1 0 obj
|
||||
<<
|
||||
/F1 2 0 R /F2 3 0 R
|
||||
/F1 2 0 R /F2 3 0 R /F3 15 0 R /F4 17 0 R /F5 19 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
@@ -17,17 +17,83 @@ endobj
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/Contents 9 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 8 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 18 0 R /Fit ] /Rect [ 45.68504 716.2874 129.725 727.2874 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/Contents 10 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 8 0 R /Resources <<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 18 0 R /Fit ] /Rect [ 45.68504 705.2874 115.477 716.2874 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 18 0 R /Fit ] /Rect [ 45.68504 694.2874 112.821 705.2874 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 18 0 R /Fit ] /Rect [ 45.68504 683.2874 176.853 694.2874 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 18 0 R /Fit ] /Rect [ 59.68504 672.8499 100.53 682.8499 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
9 0 obj
|
||||
<<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 18 0 R /Fit ] /Rect [ 59.68504 662.8499 122.2125 672.8499 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
10 0 obj
|
||||
<<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 18 0 R /Fit ] /Rect [ 59.68504 652.8499 135.1275 662.8499 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
11 0 obj
|
||||
<<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 18 0 R /Fit ] /Rect [ 45.68504 642.2874 130.157 653.2874 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
12 0 obj
|
||||
<<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 18 0 R /Fit ] /Rect [ 45.68504 631.2874 189.741 642.2874 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
13 0 obj
|
||||
<<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 20 0 R /Fit ] /Rect [ 45.68504 620.2874 159.501 631.2874 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
14 0 obj
|
||||
<<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 20 0 R /Fit ] /Rect [ 45.68504 609.2874 98.59704 620.2874 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
15 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
16 0 obj
|
||||
<<
|
||||
/Annots [ 4 0 R 5 0 R 6 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R
|
||||
14 0 R ] /Contents 36 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 35 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0
|
||||
/Trans <<
|
||||
|
||||
>> /Type /Page
|
||||
>>
|
||||
endobj
|
||||
17 0 obj
|
||||
<<
|
||||
/BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
18 0 obj
|
||||
<<
|
||||
/Contents 37 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 35 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
@@ -35,59 +101,170 @@ endobj
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
19 0 obj
|
||||
<<
|
||||
/PageMode /UseNone /Pages 8 0 R /Type /Catalog
|
||||
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F5 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
20 0 obj
|
||||
<<
|
||||
/Author (Android Cast project) /CreationDate (D:20260611185339+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260611185339+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Grafana vs Others GraphVis Pivot) /Trapped /False
|
||||
/Contents 38 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 35 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
21 0 obj
|
||||
<<
|
||||
/Count 2 /Kids [ 4 0 R 5 0 R ] /Type /Pages
|
||||
/Outlines 23 0 R /PageMode /UseNone /Pages 35 0 R /Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
9 0 obj
|
||||
22 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2776
|
||||
/Author (Android Cast project) /CreationDate (D:20260612190936+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612190936+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Android Cast \204 Graph Visualization Pivot) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
23 0 obj
|
||||
<<
|
||||
/Count 12 /First 24 0 R /Last 34 0 R /Type /Outlines
|
||||
>>
|
||||
endobj
|
||||
24 0 obj
|
||||
<<
|
||||
/Dest [ 18 0 R /Fit ] /Next 25 0 R /Parent 23 0 R /Title (Context and constraints)
|
||||
>>
|
||||
endobj
|
||||
25 0 obj
|
||||
<<
|
||||
/Dest [ 18 0 R /Fit ] /Next 26 0 R /Parent 23 0 R /Prev 24 0 R /Title (Executive summary)
|
||||
>>
|
||||
endobj
|
||||
26 0 obj
|
||||
<<
|
||||
/Dest [ 18 0 R /Fit ] /Next 27 0 R /Parent 23 0 R /Prev 25 0 R /Title (Option comparison)
|
||||
>>
|
||||
endobj
|
||||
27 0 obj
|
||||
<<
|
||||
/Count 3 /Dest [ 18 0 R /Fit ] /First 28 0 R /Last 30 0 R /Next 31 0 R /Parent 23 0 R
|
||||
/Prev 26 0 R /Title (Role-based graph packs \(suggested\))
|
||||
>>
|
||||
endobj
|
||||
28 0 obj
|
||||
<<
|
||||
/Dest [ 18 0 R /Fit ] /Next 29 0 R /Parent 27 0 R /Title (User graphs)
|
||||
>>
|
||||
endobj
|
||||
29 0 obj
|
||||
<<
|
||||
/Dest [ 18 0 R /Fit ] /Next 30 0 R /Parent 27 0 R /Prev 28 0 R /Title (Slug admin graphs)
|
||||
>>
|
||||
endobj
|
||||
30 0 obj
|
||||
<<
|
||||
/Dest [ 18 0 R /Fit ] /Parent 27 0 R /Prev 29 0 R /Title (Platform admin graphs)
|
||||
>>
|
||||
endobj
|
||||
31 0 obj
|
||||
<<
|
||||
/Dest [ 18 0 R /Fit ] /Next 32 0 R /Parent 23 0 R /Prev 27 0 R /Title (Estimates \(engineering\))
|
||||
>>
|
||||
endobj
|
||||
32 0 obj
|
||||
<<
|
||||
/Dest [ 18 0 R /Fit ] /Next 33 0 R /Parent 23 0 R /Prev 31 0 R /Title (Recommendation under your constraints)
|
||||
>>
|
||||
endobj
|
||||
33 0 obj
|
||||
<<
|
||||
/Dest [ 20 0 R /Fit ] /Next 34 0 R /Parent 23 0 R /Prev 32 0 R /Title (Implementation slice \(proposed\))
|
||||
>>
|
||||
endobj
|
||||
34 0 obj
|
||||
<<
|
||||
/Dest [ 20 0 R /Fit ] /Parent 23 0 R /Prev 33 0 R /Title (Source linkage)
|
||||
>>
|
||||
endobj
|
||||
35 0 obj
|
||||
<<
|
||||
/Count 3 /Kids [ 16 0 R 18 0 R 20 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
36 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 737
|
||||
>>
|
||||
stream
|
||||
Gat=-gNV@2'7\>80opEDN%JCNSnO:1p\WYi:$A0sa>lE#(8-Yl?-=R's8!bXp]>6#b970(,L;>2qAWjnN\<cuoiD-cVD.j_J3dR(#K+t@R!sb$l,Bh6A%U3524^*E!S?iugU&P[%`t1-&^rX(1WPODHD%f,)-[<@M[ST[<+:sJ$:PL[4=Pjn.DRYpgN<rAl-fV:P:/t3*l&4")8$O?_O$K69DF^/S;-q`Rdi@h8%t"i%l!r$VSBst/dp2(420FU[]H@W[Vh$E"8:XH[C-IF%2]n)O\-.UrA\4-iToRZ,@!RBi6aX1W;k#7?jKU<!R3"qGnP/4coBtj$[K[FON&HURSgm&TFhVl)>AhcSu"r&e($"mo;(s%%?@Qag7A``0rkbs5)J?AGRhp%1s[ttE.u%UKDBH,<5,ef`cg)P:%>Pts*ad+lQ"u$]4^Uj]n:_&e%RC`]ZJ`_j?Kp:^j'+O:04(SLB/iV[pWUB0_ijp(j,W"+L\0%[W)f?5A?Lk4jM<ml[We5MPhO>d+AqT9='%l/LNQ21!ti!V;i*NAFEn@31950*+eJ"408#k]P3N7D6CXgCN6r$"TNQKP;'iFKpP)fm$EebPH/u7_Z.?tPgeA_0->KMBT[?G.'f*.k_M$d_>ZV&5K1ce2uPd8jU+Z7AlR>1>cj0N^%6'hF"1Xj/G:75P5L+LS\b/gq-;!M]4.i9UMLokXK\oEi9);B=7H"^,?AE]SAd;o7%^q`QAmWg)T6P+LcA3/,;SM*&OGcAd?gcQkK3\`k?@eBZR&8`c\:_HCG(&_Z70Oh[X.R1r;HLZo8%1Ukl(7R0A$]lX_J9F\rOpE#g\C(2[T?+a*M6*@nR%OCb-#&Y"AaRNs5Vd,lq4>K7L"cJ8LuG^f<tA<E&a=9/)<E@=G1\[4%=;hUQuo_piLS$LK_.C4ko6Wsr?0od]`HeaR3ZBQ3^kr&j"T"sREVB\K"sPs!6*b%lf!hQF^u"]/'H:7's4SRCqr\8IQO>elfW=i__G_@>upn-j[__69B*S$KCe^f0B.\D_!1Vt(.MW7(J:qc<rEMQhU+!,ju(!nJskBCPP-E`8Q@Yem-WUBQ7@!1of)7Y1r3XL",r=Afq=##\ZjL4@FV;?>ld!mQi\ej>,]Xq9-B@>SHf/6'O\,R92\+H52[$7Qdp9)9Ng\3YNneO5/=XjHtfQGt=bSl24I^ni<]YM#,(]L&QP&2'[0nft(Bb-fCCKB5RoCAV`0JO+j"B/pJ)C#V*=E#:&4XuUhBGfUV`L6H8-Yk$i%j2blbBMP".Kmpua/'[0a'=qR\#]V&RV'.l+26Ru>@UI0^ZWp&]$bB5"3s'A)(t+H4R%5sM=a%43[$W3Z4=G'3o.\*N*-G?C'&XDHk$$UbJEsY<f3Q1*2o2LgN^KOqDRDX8$ZSc,Q@i#`E/qb;]83_rBVu`gVWPFT:<qktR)[tLOr-.eNmU,TE0_UO7du)Afe($^FNlXbpFHWs._.RZRD07oNX+'gfdk]/gDY7'JhMbNOB#H<M\U"t)?"&teL'moS=BI'edG1G+XD'K`KF5g@'E[Q1mSLfZL+6oKR)2NW]^ZJ=Eoe>Fg"S,dr&+R\L@>"fc,,"3Le@T1qToKEgU@M6;T/tedBSA<d\9eDXnb0iC0[(9O(X&)iL_(l'hJXZFJVF"+/`\j4$A<Nisf2p6sFg>NrQZ89N3+Q=(Yi_64j=+m>I5gg8I<lYoc]^ro?Y)HRCRS)"HXg0Ju25QL0:W]QOf]b2od%X,$m(WRCCN#19QaOKtS!MhO<:b*T,O`;Hu(=@Hf)9dA^<"+>qiTReLVouPOQKD/,P#3n?fG`W+oG'($p+g6L8_6CK_2l@ROK9[(_@T*=)=,!KW+,*5pYrt(Yr>5F.-%@3DSQkk73WgpTPmfqF`=t2heO((^^kau69`FhYhg7qUHC)Q!cqM_'\nhU*5uj6\i-BYAf%Lg/`(2g!sNnO&N?s%9Hi2ja<e&O*)5VKl^fi4!Wa*$:Xfr8eOa/]2!S)?<(Jr!4WGj4B^lO1Bn[/Bd8L'aPe7%:DR=):OqS$fG@u5cbMrfFC1G;7fO,tU=d*CJPFt<N9p/hDQ&)c86R(1r4Eha(;I$W.`=[_:*gcM6;:A7o.XWE(eY8j`Nf4*b9%;RKSdEe;qJ"m@eDR=?6<;Q"NW9?)?3VT\TR4$Eel'BLZ-lE4I#"oL:@uGC=uR@HY_aEF@GH6$$N)Pe;fC]C!h.\ec.n0pV279NkRmJNJpA-p)qf7l\:qe&;)<<'SL*@bS]Z+TFK9g-a2;5e\,<Vi;Z..iFfdFS)2oZujVhSpS949cCt4?G7#<pa0VdB<`V*2U48D`G6H$NCi7O'\.EDn<>'tudE=J`_UO[7IAgMI$EBrO/Tu'B:QE!RgJc2:S#rr7K=K?kA-!mGL!ia*"5D/n[%$gYS0<O.FQAgROo!%N+rnG_!BK#B,\f<"1/W9QKTk'$UR*d=,j+4jQP))gMhZu\h0Z7QR1)bnTp!o<A,[B3[5@VMN4\R5uiWA6'_[u76O5&DmA53q7h@2>m67Pj5neGFTrp7O'`\@e2HTKY(6^5,Bemi?K\A=kE>Uc9s_K^fki"CZ"jE/DZ^2*\iCV<L/(dV%NCeOiS=t3]F7J;\9pP+d+kVJTCR].ue^AWPP0o29)"\rob!VXYkpu>D6\.H3p^FW,Vs$aLn29BKkNaZXb[XUef!iR9S=G5m.JspFa#H2>jP3m5o\JY"QpC>0E$0D^VZXi2ZgSg4pC/G?``$daH+AKBpl(*pTZS^8gh<e:fE:GT(>fAu~>endstream
|
||||
Gatn$?Z2Df'ZJu$.IL\,WLdu+mk*joC/VHN/_a4"a9@?D!LqahD>3:-TM;mDgIR$[5_<dsO2JNB"L'sYGKq[gpF3bG'-J";2%Ul*OC#h93r(KR:R-BZ_Do=p6rAWAQhYHhH]2o;]taa2,pA:X5Z'c[T7AO@m[[lA'1#e^#mqqk,q*U6#c=c]Xi?Ib0V-<j/!7^Q1DZ:@7W>2,]n(+=1oqBUR2&?26L9q"KCh7$DrCQ>=#f1mmPmZV&equT#FHb[J]$ds[>0@*S$X9qSAFTPCREDTWo3/S2P5DdX`e<Qm"T;^.U(]"0WVd=6rj61q%'\P68X]`Yi>m.fbY(kS)D!=oYp<6b3hk3C(6SoQ:kut!4k.[Y4"ON(3dKT:Apn%IB^Lth?"OpR2Eqo]s/D,+*c@=Z/]5\Wabhps6=#Za&;qBG8\\*<AQ4cOos4ofDPZDl8Pa(kBG/>*4hq4c*FI&jjZ+U_2[RPnn\;&F?1!'Jc=4n.-S:BU\\`8mWf!^+UlC+^FboV=C):o*$H.8@8Sel<k:=,_oJPFKX%7\QFB`LZ4N1?/S$Ra$oAhRS_UVA`F[opo8Ut"/fn#f:YRBd&s#KRZ)UIbLhojt8s+++;6<j#beRi,d9&67c3gIV$Fs^O+!OF@1P>UJZ31J"jJh&P1XS:OM*9b.+_.dLmY5AFYO*,D\6!ta0)ph?F4^eJmhUN20^JGs\lROrB9m>T8iJr>NRA"O=9+Ol^LS5"~>endstream
|
||||
endobj
|
||||
10 0 obj
|
||||
37 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1790
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2947
|
||||
>>
|
||||
stream
|
||||
Gatm<>Bed\&:Vs/R)cS5NFhpS53:40GIMpfVEF!0Ym""j$S0Drm'#</N/aN]_7PI)*&7"CUMiWM2\9rt7EfD-"5HX[nf%Vh_K6"#ZTX\)L(Dak^G!ZSVB=NP9*6!I+L6F\Hh\SL*FkE,$Uc*XBEZd(!F:B,J?Gs3/j1.;b\!\N^krhhZ;SIg-$#kB4C.Me/dfn#.-=<DhSAbF9],+H^>`oA7c<#H%O*!Hd-lZs4-<0a$9!kG-]GhNI5gkU%]n%IK]!$**CV4X$f@,bln)]3f6U%#m@.Q(3"L2If+3/s+>.e_;fJrs`=VK)Cj:/R@6K.f4[ug5'&<O!_t-j5jMsOjM&Tn\EARmqpHg:E1.Z[;Q<^R&3l,@ZJJ6Bb:-LD#ncI"m+?K1o9jMtm&uMMB"giE]j._U0pVYss@''lb-<m'+8iC/_K2bC5.(I#4:=0>#SeY^\QB8CP24U6M%X]cFG0jf6KS.o$rih.XI@Lap=')EGiSi5fJlaVO-'m7n(L7qKGk/!3N&6K45crt'1lQAbSX>/,aFru6rj7!;,)*uDX11Sl3n+Nj=sr]4),:T0Gj_j=M`>?[AqYq[TgW&A+`h5gi^rq`iJ'<-3P*c@_"[R24*!^7-a<LHq!l5piO-??o?Ak@UC=jSK@BkVg%sCTbj3O+%tf(Ac[_eAJG3^&J>:[\\"IF#R^f.b]JatdcBIS6:"H\^)g#[a_hN#FmKRIuf:CE5H7/4!^<l?ie/q6AcM&uG+)g>d#cc(;WiC,=*n<.;T)rr)@LdlB.G4SN<65No)8$=L!f=keBqe#dK<%S_WsYI"`L\kl]`VDL*4)dbYU'sNrq/Fk-!?f0$?GjRP\[L@9BBV8R<EBg/B5qlOd^fFk&0!>_4f\<o-BuI_]qt"4Qf.J_L]9h\V*sc92pR5V'H_.3kSt6(jqD:@d(?lIpSe=2k1gilf\o638sFbKH]-;:m;=6Rk3#LiTTV!,cqlN8/e5\:X0bQ(*"f_%O[Js:pO+Tf3QD!5jKQfp@U3m"b3WmEXh!Ia7&gJj1&,h]T@1\78fPq)-c'oMt.U*YBb)]`<]pbiZFaYKpaHUT"Gc]4AZgHl5`'=*%ci8Th<G!hX(*NX[RueHI"/J$T?&ZQjJVL)`9V!CF7F87kf[5l>#YmWiVe%q4Jt6c(pS-XV>I)FnWm.^1:BKja4%FfF4+o/JSqVq*_>`@H@j0[d$#['G!a@+:tl=(dc9([l8M5]aNk&i8Y\fVpqbCYnUY+5$5!p*-N"@X4P.A#?`\%$>D!b:FqhjUJ:D^==q-/E_j#._gVRfbmH0?I(ZM9r=RjX(_#F#HQ/d^>e7Ceg;5/V)l:A'-pF*3]gDagierGWnJp:,Icugn^2KG^IHhlffJ2?L7\^%b0YJ@[Q:0$o3b"JP&ZGGOL>@6$&u6^+U.DG+T6J>P3JcsXbDAE(-7mD1@87km6o)0oN`iCD%]NUDb0NM!LC"rOHBY5q_O4BLS>eB,f77.BG9%i.a;shSW'u&.RkomQnGq+;DiNql4FtWeiAn?-arSSdeet"+SaUYX_d&3e]ou].;VQ'G[Ac:[=-TjOX/WhqRPYY9Um"X4`>S5MSjMJ)oih>k7Cj`C!uPWi"KJWGL*VU'TXiG"$6"&,[3\pP'brr.+DaFWY!r@h6b:$85G@Af#5uuYJZUhVpi6Tc?6$I@o:BPVTs"AjQ;6lU[1;FY@=!/r?auN6^G5'WToSA?E7>e&X7Qf[C(:/CYNg%p9Y/bbdS5os[[Kn<2OW'!Vq,sBm9gSI%7rq%Z+#A:hR&2[nLpgIF2e~>endstream
|
||||
Gatm>>C4gY'>Eq=R*2c]Nob?)<IqH[+0Ypc?#N\H8ORtnP#Lr?^QBKtDOQ3Km*f6D7EWhfNphp$^r@I*rf&^a[o4,n+@$p6c@C/*#Lh"<42(E;6.RDHh[1Rbp)o;<bBG<TILin>h^4P;_"M\$^He6`A`HrX9J^5=6>1VrqLd+5q49'Z%?BfY,9-Z(?GZ7M9`NPb^Z!L6!IQE)]hf*@kC@&2ThQ":YV"W&^&82QcuNqa3cMm@g`SSl+sa*5ipT+i:>,fbTBh@GXX_B=qAFd%b^*lED:C(@^+46Cr[=Fb!ca>q,q.*)m%HG[a*Ta-)hMAXkQ"@f.0@mQ9APdcKHH)kO4TK`S7a\?;XkSfhgDte-a"5Hc[#KpY+T$aUKO#D?XKbio_i!pLN)I#3u,&`W=btlD5.ahg_\I-7jHVn\7ubIe!G=\#uq\&W_@\3'ef.J/^c.pa\n_:p$jp09=WU2LddGe*iNTUhHG_%n9b_"J<C8.VCVnM$Gkg%X$1a7]lOYPQ(]=p8D.[mR@ka1i([,A"e)U@)"JRpa[C2kDjuF?#L@>ADLkK^3R@n'Hcih@Pc@k0ZC4)j>m-kMdD_aY\Ga>)9(=20=_/HcWug'on6GG9Ei-STSY=Vc93?62OM:3*4p.r1VE+RWQrl5>!!sGm<p@cF0?[4,0a#C8R]E\O/3FVHT&=$(6.-WD=+.I?KtFa<o)7EFkh$d.ID!IjOc,!HMF7L\bQoKp1P"I%!NYU7j+W"aC,)#J8dmEc$sh<:('Bc@WqJ7e(F=f6T=_YM\[MC\iZcQJSm_'7Z4mJ%(GF4S@eCjgZ?;^%ni=Os;>m*20`M.'mS`*mU47Rf$lRM$P<,UM8jgi<H-H+U7/W(b:.t3e?:%oo^t[VH$d?,BWfI_bg"#438YCnrbV/1&7?2qOO];Vu?oVH.pU<*MclEjj$^9%^8B**RIBnqV\u0/6InRZf*=GL@QUSWT>7f=I]Or\>Z6RE_?*N*U@##=rU$kZ;".aK1g*B'8-^I,i!(OoG."^,Z85fJhO$m=;LeH;h/47^8mGI4_+ibU1H:A_>]K(S%XWn$$3bo_u]Kh?,7)=>A,PBdKCB>/Zm95j''hCl/7B!a1.,Tkj5;I7a`sV1k'T!ET8Jo?h/g'i:G;1Up2Ibe,oF9.KNkQ7//c&`s3cOd3,'819-!drR[,#)t&U+3>\6T1)&>M:L[Q&].;F+G.L0S+aNYbYCq&3+1cI.I)-"_#)P)<hSJYl*hX;Aooko[u+G\MM[/2t.F]gsAaL8@SW1$,DMBuX)q6Q-%R>fOG+\.Ji(GD5<ShI(eAmEr#^`KH'(f*/m=U-ueofPtgi6(=5jAXb/<Pq/H2)S[K3D'`rX4CD(2%aInKks0s'\d(4_1![Tsm^1DD>%9a<(%dm#5Xl4&GeG^8m7jirBi>@l`TU;._`r:i@eu?.CUHYiU%l-%%9L^hD?$6#`l>JbL*]KN&j_=WHe*1,(s__g!BGLA4Wn10#!I6j@.Jii#oUnKUVTRd@:FkjqBUrN(HRX*2#8r2.)AYqWT!IabUQVL9t8GGp4km%PcOc]5\nX'Ep1M92U8c@bZZ/J8`@1%LMQi]h0*:2aB6;6QN.)J`uU]L_>f!j=HO8C5(W+YhuCDUPkEPAN,2G[EJ;K_CtRRim:RNqRrb,6]?)2bk\T1Wfg$P*ft^UoJUjN%JZF*L&?g7#c)G!US/c0>>?5[OMg!kJ]NXQG]3VMI^[4CD?8/Y$^th3[o%^H40G$Esf0+LE=29Dd0!OueNtN[0<qA>hU3A#W2?FG6T)d9M22W:Y-Mi@t#XA!X^bG434S!=CE`u3c_>0]`a&Z;T6EGtUYT.+ZYqem]cLoORQX)VSpYjTuIR=i/RWMMJ>+`'iA*8Pk-E5S(s'_`m)a?(NfLLU6Eis;e#ae8QWGluWP=)1^53#=*r2=)3N!,mZm>L9G?7K6tQI9k:.5T9&7j:/Y>;'OoR.$"KM09O2:h^=1iU2"4mK6tZa(?7@2aUci5$.cn]!SGjL6j]IONPf<;N2Y-!RKpG-^aN>m2$\*p<`n$4)/\C"1uqT,t2af5IFHbR*$I:30X@f67KL_"3m#WSu@?_c9)tSbUi3i+kQm[N5GdiGb,UF]qgP2%ba]=#"G&5/1Ng$.lk4r]9Jaeh]joXoC)=k`/T>hIA@4r%cX\@]<OhTUU=3hUiT3ZLe=+K#!ug2'A%>_^*_HVVN5u9GC$s5HT!,:*ES7kc8F*p>]jZ7lfa3p+m$dm^?"X[O"/qk_u6.!f_sBd:pXKKeC]G&JMMkd@injDZbtS>!_Qisl23k0H$9ZUGp3U6q_r_kE&C!G1/VKsgh.,\fOm^hO_pnO&bZ2cXq4r)C7gLoluQ_9m"nAlfKuA'8"DI+7%Y_i->+]WBi.3^$g^OY;ZL=J2E>MgK,M;@H+cfs$,V*Zlf_gjlu"W?VR\J<L,1Wa2uk1m_'H:V%auYTaab?O9R>*h,=_s%Y()](\V!K2*]f%FC4A;$eda)*WIB6Cp9QpE>(_*JU]PoC6US2g(Hk&?"dZld-^/Fml=@2k9-1RbE$jEieEr)^h*%XT_s.&nkW&Ol.)Y:F7MsCj)'#-[*M2e<SctZJk1cl0aT)t_VsfXt!@AXY5O*$9_%Wd?bcP,X0c:R5$FO8%'^"Ar-5`4K3GtDH'<#;,liH5dcJY]AJlp*_b8hL(T0?'rp4s]Iq#5n&/;p(('s;ildb8cS#AG26=Aq:#\8!q+DK[pWFj_D7jg9Areo'ptUoh-#%ako"9sE=VHO"9;Mg<Qus5:#b)[(e]nJlh6]bgB(\S>KeLc(V@f2-M#R;ds3f(7u!*='0O4On]E%HI\'oN-SL4^P@O,3X7a>kLDEIb;?`Yj#OO<j0P@<Lt>3?"#rVm#<[Eg$!iWK&^cE&.AKX=LnC!KLA)!EGjT;(,NnaQ!1hFq^$1Rh)pEYdE>PeZIMT)$lApKE;-r$~>endstream
|
||||
endobj
|
||||
38 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 860
|
||||
>>
|
||||
stream
|
||||
GatUqh/;4+&;BTM'QZjT,2b]lh)jiXY#Mg9bp<uXZ'0F%`*(iGo(G.gJ6%JDdoMc"(ruVfc8cAc4q?R9irWU,61:6BnKCMT%)!fP[r<(Qr&l4XQ,CBVNJ%3A_XZ7d#3S@$%faNRaaPo#$QppLo^2CTgMq$gm(8rhkkH2mCQUD4L$0%"n='TPVh\J)\5=I$YS)>cHa,,]U9W,#Q.rE%O9h\Bk@UJ2QLP!r1!Xb)otSX0nDJLfjh0NJ3Fn+bD.E[R?Sc(hB1kf24&-*=<05[1dF*!ZJCZ%L=u%eE`$pM=;EQi`f=F[BU[V"<GH=:bgOnibljQ%E0EsIn-FBS1;o9?k1>dUQJ-h,7QW#kra6DXd$<gH(9L5<i1`$R%1KY$KakBYIcFaFhJQ0b!akEs0K(hr+LaDcn7L@uNIOiBD#ASoC60BHS0^ht5<`3us5AbDO:Yo44PFWp>>k1:IHuDh.@Z*S'#5_u&lPp;U%4IWcd'9Z'+mJi:OH#j([V:gdmi/7;H9k=]a>d13af08pm@5+\kZhgi_&$;+.cuM84bL@[[PO`F1L6[V1`DeF9Og)-I$`K;g==[@J%arrh(;K>FQjZX?Dh)O$;nMa%P!ofh3:K504^`$NpKYHfKhH0httgrr*.!P==_T0k.Q9L<:O5?V9!LI)IS==.kI?+7/Y7<jJ%%1[!m5g$/4nHl^\JI\?DEJoDMrK]>"=nGepjar[cGNI.*s2.,7VDN33a@B8`MhrWc?[/*u*Wk9RtgA9b-i<'<%J0%&OC=I_&_#L8!r9:Ojo$J#(-!m11t=8tn`TBJ>3K:"D3le]nHjEbi*=fh6^,+R)><7*1"$,*:U8fFmBaT3BOqu]DT!Ik~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 11
|
||||
0 39
|
||||
0000000000 65535 f
|
||||
0000000061 00000 n
|
||||
0000000102 00000 n
|
||||
0000000209 00000 n
|
||||
0000000321 00000 n
|
||||
0000000524 00000 n
|
||||
0000000728 00000 n
|
||||
0000000796 00000 n
|
||||
0000001102 00000 n
|
||||
0000001167 00000 n
|
||||
0000004034 00000 n
|
||||
0000000135 00000 n
|
||||
0000000242 00000 n
|
||||
0000000354 00000 n
|
||||
0000000501 00000 n
|
||||
0000000648 00000 n
|
||||
0000000795 00000 n
|
||||
0000000942 00000 n
|
||||
0000001088 00000 n
|
||||
0000001236 00000 n
|
||||
0000001385 00000 n
|
||||
0000001533 00000 n
|
||||
0000001681 00000 n
|
||||
0000001829 00000 n
|
||||
0000001978 00000 n
|
||||
0000002094 00000 n
|
||||
0000002386 00000 n
|
||||
0000002464 00000 n
|
||||
0000002670 00000 n
|
||||
0000002776 00000 n
|
||||
0000002982 00000 n
|
||||
0000003069 00000 n
|
||||
0000003387 00000 n
|
||||
0000003462 00000 n
|
||||
0000003567 00000 n
|
||||
0000003679 00000 n
|
||||
0000003791 00000 n
|
||||
0000003961 00000 n
|
||||
0000004054 00000 n
|
||||
0000004166 00000 n
|
||||
0000004269 00000 n
|
||||
0000004389 00000 n
|
||||
0000004521 00000 n
|
||||
0000004649 00000 n
|
||||
0000004745 00000 n
|
||||
0000004820 00000 n
|
||||
0000005648 00000 n
|
||||
0000008687 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<4acbacb74072b1ab8591e187494a02fa><4acbacb74072b1ab8591e187494a02fa>]
|
||||
[<cbce618176a4381f5b7db1e5aea50bfd><cbce618176a4381f5b7db1e5aea50bfd>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 7 0 R
|
||||
/Root 6 0 R
|
||||
/Size 11
|
||||
/Info 22 0 R
|
||||
/Root 21 0 R
|
||||
/Size 39
|
||||
>>
|
||||
startxref
|
||||
5916
|
||||
9638
|
||||
%%EOF
|
||||
|
||||
@@ -153,7 +153,7 @@ endobj
|
||||
endobj
|
||||
27 0 obj
|
||||
<<
|
||||
/Author (Android Cast project) /CreationDate (D:20260611185331+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260611185331+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Author (Android Cast project) /CreationDate (D:20260612190936+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612190936+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Android Cast \204 production infrastructure) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
@@ -319,7 +319,7 @@ xref
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<a00077b7d6dcf09115ddf11e77b9015b><a00077b7d6dcf09115ddf11e77b9015b>]
|
||||
[<5381a07cd3154e970925d8c0b9b894f6><5381a07cd3154e970925d8c0b9b894f6>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 27 0 R
|
||||
|
||||
@@ -112,7 +112,7 @@ endobj
|
||||
endobj
|
||||
20 0 obj
|
||||
<<
|
||||
/Author (Android Cast project) /CreationDate (D:20260611185331+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260611185331+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Author (Android Cast project) /CreationDate (D:20260612190936+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612190936+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Crash console \204 open ingest API \(design\)) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
@@ -232,7 +232,7 @@ xref
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<a8f81650784ccaabadb1f16a786b68af><a8f81650784ccaabadb1f16a786b68af>]
|
||||
[<3a7ba17afddd149c4cf82e5c80686f4b><3a7ba17afddd149c4cf82e5c80686f4b>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 20 0 R
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -133,7 +133,7 @@ endobj
|
||||
endobj
|
||||
24 0 obj
|
||||
<<
|
||||
/Author (Android Cast project) /CreationDate (D:20260611185331+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260611185331+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Author (Android Cast project) /CreationDate (D:20260612190938+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612190938+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Opus / Speex / stream protection \204 validation checklist \(task 3\)) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
@@ -276,7 +276,7 @@ xref
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<614bd83675b640eed7dfdacefc7557db><614bd83675b640eed7dfdacefc7557db>]
|
||||
[<0ddefe241699114217c6a77061fbf8ca><0ddefe241699114217c6a77061fbf8ca>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 24 0 R
|
||||
|
||||
@@ -112,7 +112,7 @@ endobj
|
||||
endobj
|
||||
20 0 obj
|
||||
<<
|
||||
/Author (Android Cast project) /CreationDate (D:20260611185331+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260611185331+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Author (Android Cast project) /CreationDate (D:20260612190938+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612190938+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Over-the-air \(OTA\) updates \204 deployment schema **v0**) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
@@ -233,7 +233,7 @@ xref
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<d6d86048f2e155d455a13032ad45a795><d6d86048f2e155d455a13032ad45a795>]
|
||||
[<9884c574c7519d156c7454ca12b46b44><9884c574c7519d156c7454ca12b46b44>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 20 0 R
|
||||
|
||||
@@ -97,7 +97,7 @@ endobj
|
||||
endobj
|
||||
17 0 obj
|
||||
<<
|
||||
/Author (Android Cast project) /CreationDate (D:20260611185331+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260611185331+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Author (Android Cast project) /CreationDate (D:20260612190938+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612190938+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Pro / OSS split \(cast-core + cast-pro AAR\)) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
@@ -202,7 +202,7 @@ xref
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<3fa21412f8ad7895eed1172469be6cd1><3fa21412f8ad7895eed1172469be6cd1>]
|
||||
[<dfaa46b9252e64daf7c85ea1894037ba><dfaa46b9252e64daf7c85ea1894037ba>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 17 0 R
|
||||
|
||||
@@ -59,6 +59,7 @@ Click a link to open a guide. Section links jump within the document (GitHub, Cu
|
||||
| Document | Summary |
|
||||
|----------|---------|
|
||||
| [20100611_3_url_shortener.md](specs/20100611_3_url_shortener.md) | URL shortener service — SPEC (`s.f0xx.org`, post-alpha). |
|
||||
| [20100612_1_scaling.md](specs/20100612_1_scaling.md) | Platform scaling — multi-VM clusters, dev/staging/prod, cloud path ([PDF](specs/20100612_1_scaling.pdf)). |
|
||||
|
||||
---
|
||||
|
||||
@@ -67,6 +68,7 @@ Click a link to open a guide. Section links jump within the document (GitHub, Cu
|
||||
| Document | Summary |
|
||||
|----------|---------|
|
||||
| [20100611_3_url_shortener.md](DRs/20100611_3_url_shortener.md) | URL shortener — design review (auth, slug algo, task graph). |
|
||||
| [20100612_1_scaling.md](DRs/20100612_1_scaling.md) | Platform scaling — architecture options, RSSH capacity, task graph ([PDF](DRs/20100612_1_scaling.pdf)). |
|
||||
|
||||
---
|
||||
|
||||
@@ -75,6 +77,8 @@ Click a link to open a guide. Section links jump within the document (GitHub, Cu
|
||||
### Infrastructure and deploy
|
||||
|
||||
- [INFRA.md](INFRA.md) — FE/BE topology, nginx, ports ([§1 Topology](INFRA.md#1-topology))
|
||||
- [specs/20100612_1_scaling.md](specs/20100612_1_scaling.md) — scaling SPEC ([§6 Target architecture](specs/20100612_1_scaling.md#6-target-architecture))
|
||||
- [DRs/20100612_1_scaling.md](DRs/20100612_1_scaling.md) — scaling DR ([§15 Task graph](DRs/20100612_1_scaling.md#15-task-dependency-graph))
|
||||
- [BUILD_DEPLOY.md](BUILD_DEPLOY.md) — Docker CI, builder ([§Docker CI](BUILD_DEPLOY.md#docker-ci))
|
||||
- [OTA.md](OTA.md) — OTA v0 layout ([§URL layout](OTA.md#url-layout))
|
||||
|
||||
@@ -127,3 +131,14 @@ Section links use GitHub/Cursor-style heading anchors. After adding or renaming
|
||||
python3 scripts/generate-docs-toc.py
|
||||
```
|
||||
|
||||
## PDF export (markdown → PDF)
|
||||
|
||||
```bash
|
||||
pip install -r docs/_pdf_build/requirements-pdf.txt # reportlab
|
||||
bash scripts/build-all-docs-pdf.sh
|
||||
```
|
||||
|
||||
- Renders every `docs/**/*.md` to a sibling `.pdf` (skips `drafts/` and `_pdf_build/`).
|
||||
- **` ```mermaid ` blocks** are rendered as PNG diagrams via `@mermaid-js/mermaid-cli` (needs `npx` + Chromium); cached under `docs/_pdf_build/mermaid_cache/` (gitignored — regenerated on build).
|
||||
- Dedicated diagram PDFs (pre-built PNGs): `build_git_flow_pdf.py`, `build_graphvis_pivot_pdf.py`, etc.
|
||||
|
||||
|
||||
182
docs/README.pdf
182
docs/README.pdf
@@ -2,7 +2,7 @@
|
||||
%“Œ‹ž ReportLab Generated PDF document (opensource)
|
||||
1 0 obj
|
||||
<<
|
||||
/F1 2 0 R /F2 3 0 R /F3 15 0 R /F4 17 0 R /F5 18 0 R
|
||||
/F1 2 0 R /F2 3 0 R /F3 15 0 R /F4 17 0 R /F5 19 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
@@ -17,68 +17,78 @@ endobj
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 19 0 R /Fit ] /Rect [ 45.68504 716.2874 95.92504 727.2874 ] /Subtype /Link /Type /Annot
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 20 0 R /Fit ] /Rect [ 45.68504 716.2874 95.92504 727.2874 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 19 0 R /Fit ] /Rect [ 45.68504 705.2874 95.03704 716.2874 ] /Subtype /Link /Type /Annot
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 20 0 R /Fit ] /Rect [ 45.68504 705.2874 95.03704 716.2874 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 19 0 R /Fit ] /Rect [ 45.68504 694.2874 99.91704 705.2874 ] /Subtype /Link /Type /Annot
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 20 0 R /Fit ] /Rect [ 45.68504 694.2874 99.91704 705.2874 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 19 0 R /Fit ] /Rect [ 45.68504 683.2874 74.14104 694.2874 ] /Subtype /Link /Type /Annot
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 20 0 R /Fit ] /Rect [ 45.68504 683.2874 74.14104 694.2874 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 19 0 R /Fit ] /Rect [ 59.68504 672.8499 142.6425 682.8499 ] /Subtype /Link /Type /Annot
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 20 0 R /Fit ] /Rect [ 59.68504 672.8499 142.6425 682.8499 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
9 0 obj
|
||||
<<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 19 0 R /Fit ] /Rect [ 59.68504 662.8499 132.21 672.8499 ] /Subtype /Link /Type /Annot
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 20 0 R /Fit ] /Rect [ 59.68504 662.8499 132.21 672.8499 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
10 0 obj
|
||||
<<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 19 0 R /Fit ] /Rect [ 59.68504 652.8499 106.38 662.8499 ] /Subtype /Link /Type /Annot
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 21 0 R /Fit ] /Rect [ 59.68504 652.8499 106.38 662.8499 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
11 0 obj
|
||||
<<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 19 0 R /Fit ] /Rect [ 59.68504 642.8499 107.2125 652.8499 ] /Subtype /Link /Type /Annot
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 21 0 R /Fit ] /Rect [ 59.68504 642.8499 107.2125 652.8499 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
12 0 obj
|
||||
<<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 20 0 R /Fit ] /Rect [ 59.68504 632.8499 125.97 642.8499 ] /Subtype /Link /Type /Annot
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 21 0 R /Fit ] /Rect [ 59.68504 632.8499 125.97 642.8499 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
13 0 obj
|
||||
<<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 20 0 R /Fit ] /Rect [ 45.68504 622.2874 102.165 633.2874 ] /Subtype /Link /Type /Annot
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 21 0 R /Fit ] /Rect [ 45.68504 622.2874 102.165 633.2874 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
14 0 obj
|
||||
<<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 20 0 R /Fit ] /Rect [ 45.68504 611.2874 109.701 622.2874 ] /Subtype /Link /Type /Annot
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 21 0 R /Fit ] /Rect [ 45.68504 611.2874 109.701 622.2874 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
15 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
|
||||
/BaseFont /Symbol /Name /F3 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
16 0 obj
|
||||
<<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 21 0 R /Fit ] /Rect [ 45.68504 600.2874 158.933 611.2874 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
17 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F4 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
18 0 obj
|
||||
<<
|
||||
/Annots [ 4 0 R 5 0 R 6 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R
|
||||
14 0 R ] /Contents 36 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 35 0 R /Resources <<
|
||||
14 0 R 16 0 R ] /Contents 38 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 37 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0
|
||||
/Trans <<
|
||||
@@ -86,29 +96,14 @@ endobj
|
||||
>> /Type /Page
|
||||
>>
|
||||
endobj
|
||||
17 0 obj
|
||||
<<
|
||||
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F4 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
18 0 obj
|
||||
<<
|
||||
/BaseFont /Symbol /Name /F5 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
19 0 obj
|
||||
<<
|
||||
/Contents 37 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 35 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F5 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
20 0 obj
|
||||
<<
|
||||
/Contents 38 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 35 0 R /Resources <<
|
||||
/Contents 39 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 37 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
@@ -118,104 +113,119 @@ endobj
|
||||
endobj
|
||||
21 0 obj
|
||||
<<
|
||||
/Outlines 23 0 R /PageMode /UseNone /Pages 35 0 R /Type /Catalog
|
||||
/Contents 40 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 37 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
22 0 obj
|
||||
<<
|
||||
/Author (Android Cast project) /CreationDate (D:20260611185331+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260611185331+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Android Cast \204 documentation index) /Trapped /False
|
||||
/Outlines 24 0 R /PageMode /UseNone /Pages 37 0 R /Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
23 0 obj
|
||||
<<
|
||||
/Count 12 /First 24 0 R /Last 34 0 R /Type /Outlines
|
||||
/Author (Android Cast project) /CreationDate (D:20260612190938+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612190938+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Android Cast \204 documentation index) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
24 0 obj
|
||||
<<
|
||||
/Dest [ 19 0 R /Fit ] /Next 25 0 R /Parent 23 0 R /Title (All documents)
|
||||
/Count 13 /First 25 0 R /Last 36 0 R /Type /Outlines
|
||||
>>
|
||||
endobj
|
||||
25 0 obj
|
||||
<<
|
||||
/Dest [ 19 0 R /Fit ] /Next 26 0 R /Parent 23 0 R /Prev 24 0 R /Title (Specifications)
|
||||
/Dest [ 20 0 R /Fit ] /Next 26 0 R /Parent 24 0 R /Title (All documents)
|
||||
>>
|
||||
endobj
|
||||
26 0 obj
|
||||
<<
|
||||
/Dest [ 19 0 R /Fit ] /Next 27 0 R /Parent 23 0 R /Prev 25 0 R /Title (Design reviews)
|
||||
/Dest [ 20 0 R /Fit ] /Next 27 0 R /Parent 24 0 R /Prev 25 0 R /Title (Specifications)
|
||||
>>
|
||||
endobj
|
||||
27 0 obj
|
||||
<<
|
||||
/Count 5 /Dest [ 19 0 R /Fit ] /First 28 0 R /Last 32 0 R /Next 33 0 R /Parent 23 0 R
|
||||
/Prev 26 0 R /Title (By topic)
|
||||
/Dest [ 20 0 R /Fit ] /Next 28 0 R /Parent 24 0 R /Prev 26 0 R /Title (Design reviews)
|
||||
>>
|
||||
endobj
|
||||
28 0 obj
|
||||
<<
|
||||
/Dest [ 19 0 R /Fit ] /Next 29 0 R /Parent 27 0 R /Title (Infrastructure and deploy)
|
||||
/Count 5 /Dest [ 20 0 R /Fit ] /First 29 0 R /Last 33 0 R /Next 34 0 R /Parent 24 0 R
|
||||
/Prev 27 0 R /Title (By topic)
|
||||
>>
|
||||
endobj
|
||||
29 0 obj
|
||||
<<
|
||||
/Dest [ 19 0 R /Fit ] /Next 30 0 R /Parent 27 0 R /Prev 28 0 R /Title (Development process)
|
||||
/Dest [ 20 0 R /Fit ] /Next 30 0 R /Parent 28 0 R /Title (Infrastructure and deploy)
|
||||
>>
|
||||
endobj
|
||||
30 0 obj
|
||||
<<
|
||||
/Dest [ 19 0 R /Fit ] /Next 31 0 R /Parent 27 0 R /Prev 29 0 R /Title (Alpha and QA)
|
||||
/Dest [ 20 0 R /Fit ] /Next 31 0 R /Parent 28 0 R /Prev 29 0 R /Title (Development process)
|
||||
>>
|
||||
endobj
|
||||
31 0 obj
|
||||
<<
|
||||
/Dest [ 19 0 R /Fit ] /Next 32 0 R /Parent 27 0 R /Prev 30 0 R /Title (Backend apps)
|
||||
/Dest [ 21 0 R /Fit ] /Next 32 0 R /Parent 28 0 R /Prev 30 0 R /Title (Alpha and QA)
|
||||
>>
|
||||
endobj
|
||||
32 0 obj
|
||||
<<
|
||||
/Dest [ 20 0 R /Fit ] /Parent 27 0 R /Prev 31 0 R /Title (Product and codecs)
|
||||
/Dest [ 21 0 R /Fit ] /Next 33 0 R /Parent 28 0 R /Prev 31 0 R /Title (Backend apps)
|
||||
>>
|
||||
endobj
|
||||
33 0 obj
|
||||
<<
|
||||
/Dest [ 20 0 R /Fit ] /Next 34 0 R /Parent 23 0 R /Prev 27 0 R /Title (Agent bootstrap)
|
||||
/Dest [ 21 0 R /Fit ] /Parent 28 0 R /Prev 32 0 R /Title (Product and codecs)
|
||||
>>
|
||||
endobj
|
||||
34 0 obj
|
||||
<<
|
||||
/Dest [ 20 0 R /Fit ] /Parent 23 0 R /Prev 33 0 R /Title (Maintaining TOCs)
|
||||
/Dest [ 21 0 R /Fit ] /Next 35 0 R /Parent 24 0 R /Prev 28 0 R /Title (Agent bootstrap)
|
||||
>>
|
||||
endobj
|
||||
35 0 obj
|
||||
<<
|
||||
/Count 3 /Kids [ 16 0 R 19 0 R 20 0 R ] /Type /Pages
|
||||
/Dest [ 21 0 R /Fit ] /Next 36 0 R /Parent 24 0 R /Prev 34 0 R /Title (Maintaining TOCs)
|
||||
>>
|
||||
endobj
|
||||
36 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 669
|
||||
/Dest [ 21 0 R /Fit ] /Parent 24 0 R /Prev 35 0 R /Title (\376\377\000P\000D\000F\000 \000e\000x\000p\000o\000r\000t\000 \000\(\000m\000a\000r\000k\000d\000o\000w\000n\000 !\222\000 \000P\000D\000F\000\))
|
||||
>>
|
||||
stream
|
||||
Gatn$?#SFN'Sc)P($C`SRcf_k:3HjGUe.]>Y.<=^l59!r[c`<erV(A"$QEQ?M]tfN'HcL`c\KgiJAWT0;f2W5"ajVf+;B\ZO9?k+>f42kr>Le7bClWIj#@&_"Mu]c_RZn1E(1^XJ68CPOT?)bjp;(')MN[("%GP\%1!;oG6gf&!$MCMJQ^VY.MtrTjfP]DWbbl66)fCWb2$*d[VW?/TM;83It6N+'Hc;_^P@Hm]<KTF9aTfL]c<4u-#i]Ee]SN].jghZ&/3iI\;'OjgF[fF:M)P,[&mZb]Rf5Id7j3-o*1-n%-$;=n0+Is(sm8"51Br+&u7C%#[A#d0VnUEI.eJLXH*kUl'&\BUb%7ZfhgaVVkdX6fb[6`3mHncka?2Xb(*QS>!9itZV0k;(-R)#eiGB-T]5oHOoYIH(*L?+#<hm`=o*)1rD'd_YFm]e<6Ebb3C6>!XH]-!<4-jSc%5IeW[9&!HrpuEg9&O^%!Rm@H87%">0;g3m*`E]h_/Y&Dp`L:X[4'#hFh'%)5DcB_4&ZLbP["#D5bM'7Bgbq'`4cW@5qtrbmK>H=##_jSsPV!r-'.MS0@H4E5B%n-faLKm^uM+NY?1m=;rR`O]lP,C#I,09pi)&4'Ou%`KY;6?'d-"46N\`_d9CV.I3(*\[K4<-]Z(-%kku`#l~>endstream
|
||||
endobj
|
||||
37 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 3307
|
||||
/Count 3 /Kids [ 18 0 R 20 0 R 21 0 R ] /Type /Pages
|
||||
>>
|
||||
stream
|
||||
Gb"/)>BALj&q9SYkbkQD/BAacad\6#LD'q1E3cCa<N<;@5d?Cb?*M#WIX<>57ZKd$D!f..Q7K4q*i<l)h;@i5!>0fPp:mp9\6-WD'`fMpEXbDK'NgC3>+Y[T,;1hf<R;.S<'WAN6'VZ^ILjP07ZKB>\,n)qPlqhf]51%`>>aH'N8_\$F.EQ/i48gh,QS#ko5V93h%^T2_48FQr/;Q3O)u2TURQ(*+-J4.<T'Z7jsP-BiS%!iVtW=kVGS=$4eVc]G!VY6KLQKQi6d<6@XFG)`ciMYO0JN_b<NKt2u*!I8uePBMU#3_1-lN]>pRH<W+%dGB@RcNE)T:?fDkb@b/S3FlinP*cRW"RO34I?Jg_9I'[\mJ,`%1#X"jtO5SJ8B"C78s$K;!RnS>FHTSN\9,*E.Y,"':Vr<KLIIu67LqVE28>>Tm[^Pd$h:8qH#)@S[.//_Xib.?A>APK75))jj:Q"?e@AVkM3=fh??)0GBk1$UnmZn<CFC:sk/d`$i,Y4j%^>s(ZrH4LLs3)HgQT@Uu0C;'B&:>sg?GON,GS<B-M$1oe;9>Asg))I;%Z?G;oj(3p1<]2R;o&J-i.*7XFM-?gr([5g-UZXRO++Bj6YnQtqe$ZEYKEj%S&X/>(9(q':B$D-X]i]Zj_^GeCR!Q"Xk7S*5<<Is0F.`=-(#/8I)6mJ/0VFHDiY_"HpZlmbjF$?@%*[DgmN[r_*A=096W&nJ><qXM=(WP.EP;?(\4=8]RO6egg'\skWSa;T`OWfGL`U9;Er?IF*UU(Nj!:?=I@R'#BF=$Y>7.u!:AS2+B;]^%N((qRBE5q.a0X*h4e%M0o?hSh/K1To1AmE`TE#mV'k^3RS?N8>l3<B?4@A=05@[Y"p&W\Z"`bt^g-BmLU\O*ASfta55fKH7@9.!(O=&^#F6I@OF'N*,=S$I+Ts_+EMqY>KNbC5<[-)aD6-Q(:lS-u=YQoO?\Z9N_5b-1H7@b5r[fr-eo5#WI>benXFk1uGS6P-o^:^#\lq?[(p:?[Km5\Lt!?I.\7Z?bC`IW1P1?YH>b+P516nBA49V'anqP#)F;240W#58e./%-q_)M:Bjl3/-B?*2!h]8,-<O68ECXcl4\`TMH5aF-qO'Cil],%(./PK6p3P@VMdbR>L-cbabJE9$MX*&7._eq4nT@X$l,KCue\/%2=SQ^l:^OsG6Lb?dle0?nTScXF'fU'/Ku=,6tT!7$lj%j'kLT#X^/?#\`BPKFKB[;t0kRp0qr>5i*jTT[l80q*Q.VJ!a3BU0u`ZaBN;R(oK3_m6'*9uc83hst.:-f!>Q$FBLk`q8UOGL&(V)_sN1Z]e-1K-n+,RN-69j%IT`Zgm[k7d>0TJ\^[S<>]1=@`$sjC-5dj4h+9nO6c$5IN0II=g?<]SkghL[)PDLl""8JLEglq9YL->0%lhA6-,f"L*i:QkUM=.(n!0NK$D<9ELGZ%g'm-k!m.u1S'IAO]U;gf\X7j(5OX1<PA<SH/YqSL]&K)+$kZ1h;tR'(3aF(hg);\OO-]LiGl#+A>&#'CqBbbd+q2(8j#Hf;(F_#IBU;#_[p\G?hG!Oo;t=PKh8^Q@WlJe3R<TYrC\J.!E([e[k^ClI8o$P14ZBRgYGT!om3((+B!W)RK\O0ZgS)4iP%LS,cZb1!3S6X;/EY(=3hgET;iFr$NHo6oTt2V:Q!8m.dFYFEZWc'knQ?R-d_$Nei!BZ$`:G@d`fqGGE+KcU<tE&d-`L;"rV>W,FW&Xa>Za,pZ&0^qn1iDN7Yp*lqK*&eX[1Q\Apq'!h_[,_qt5_IaCCrV=F?5A\sUBn*[Hk@2[hBuUOl^97Tc3ekHoPECQ+IV[knn.(,g((g#ls^G2U7Wdg;!U@U=D+dIj'nI"PpXR'6i,OYnS*cl1t=B=A7q0sds2f\[6Z>aij58>?9j0TFBp+_+<8Z>VC/`F/`Wr5hSNhNFsATQNk*UG\pc,2Yqp:+N*deG#@9*dnOH"SQ:8O*A=M`\!*qF)nTl@O.iFW7D`i:;[Co[+:UV2P8^#frY%Y?erP%e9:rspudb;N=H&$LBffb9sU7M)<qYIY(rZhF%S<t24g4?PNgN$bon.j(A;DsCPJA51]$N/loLuj$MFVX?0L5oXiGraCFE+]W+F>`B/b#4oEg94&]CZ<0N9N?H9k)I:ZcbWWO6VBo5\<c)YT@u='p&AG"0T7c4M89XG)i3M"$r!qC\[VB2042LjgFuki"?FCb*GE2#Lf7(Np`t4)f0OcmEaP)!SjoqMq;M8OAtbG&N.g%6q&p9NcZSe?oK)[u1XkoAsLn&'`)!R4hc_Ph^J%H#A$3c3rh\N>S68;(q"f:Da>#=Q-5$Es2*+Jq)dp+(p0Kl&&*kmI0lDR@qE,Gq6FP]gCXE%i-$S4=#`JO2Za.#?-X6`,@LD8N87m7&l>f=HN<;jT12K99PuK82r.l\m-E;YU!ot$2LeN.=-]$_ic8q'bV!LP!gcF*0qkT@TGZD8Gn53JAd3haf&<o%ni'dYm);9,dh(j4tK8t`>X,+P2qKXpol$+MDb15,`*7On6nU$</`PV[=qpAL&PfnX^VS'eIH?5%-jl.jGFQ1K,mhF&-iuL)PGGaOp62ZE[-g$@aj0RH^g[a)U=S3UJDC7;&47J5Q5mnek%lqjkBpVLQ8uiSLhh55>I<*6V]M"'E/(mNK$haa,@t2C]Efc@u#C7,)R9^[VVk(B?.Z=MHN)W;am.a:N#?*-s;ed.TtLHO#4a]].1(i(8%1O+POJBRdafB!o",T]RRk7?>PWkl,ch%c?m=SF:Ir1mi@C1=WRk6nMlgh'56\$9H]6c3MC#W?Y5(<G^2,G)sCtu"&Ak,o`'M.Dh1G0JY!&?l>ZXi7W=f4HOtPg#1!C@TU0e!o'XX"<'X*fKL);I7miU"[&]XCS1a+2]k'T=4S-RhldW[uP_a^?LQOg],Y#V'Vl'lg&\jltP:1RZ4%J.UaH,QpfSZCGC+&jY(`Q!&Okp[<)+=Wehh,"]q1Vg,CWdc7eoAY\H%uYE:%UcdqcGKo0a=h)kp52V)mikqK.F.bQ7J<),W$-/+8i#/nePI_'5s?,`M-_*Mo3Fps.GK:Wqn2@b>%VL99rk.L/GbD+uNba=S'43ql4k6DQ3Xda34MgpIVn1:>bmb9l>B<iZ/K;TEN6!;BlRlj>T6ug',Ukfsr/^PJChMr\ra,Af?LPlCArboA`F$`iS>[i+\">\[u(=G[?NuT>,otp_PS+.Y`bM+Cn2_@*!#5ItM:N$]/-$nhEKadq9SRcS(?[l:"SULEa.ub=-kI$coS=Uu<E1l^(uHV/m"a?6?qE"4$lL[-'K8~>endstream
|
||||
endobj
|
||||
38 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1189
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 717
|
||||
>>
|
||||
stream
|
||||
GatUrCN&2l'SaBs<ubq].qZ8D,ihG0\WF8IE(Q8?eOW53R^eG2`#7=&jkm846FPXBC,uW?abfP5Er;N\2F*T^U]/Iq!7qPLD@A3m8=mW$1^fI$prUL9$f`qhZ8K\/FUMI#e*R(LP5g`Tp%gRN?H="E6ffkrAj`l'kOe$gFVP`\B48;&bX-f:;&Eu9*#>F96;]?A(Ec)LkU]ENSQBTqjsmV1qA#1.+o+Lagrn:u,hLN;o8o,CpruTD*6liDK#379'\P8S*<(WG6BJPXMrUC5T%aP,TII`>Ni?+m`po4geS80)>ST=M)RqiPTanLRZe\\_3XKohBY]Z+#;T4:*So+;*;L+o'`0kMmr66Z7<uK,Qu=pQPjKDR7Q+3W8@+7oITY9ZlnmqDIEgOBrT+Ep>%Pl;\$EXj)=O!)U$I_Ii'-#'2<HF3"=XXm\ED6VROH+J5<Qg[ERJ$TIHOj4NhT=eNhWDrS?@&]#?m6&IV`SF't!&(a:7aJ:iu1:3q6NU@_tA6?L&kI1uDmHdP?T:k-Ehc-ZfQ*>KUnCRJ>/`5i\^qC!NCW!=E$2G\7sd(sOa$;3P;:!T^YQgq%1HS;qs)D9V$UHoIJsG_k@TiD:+h#'mO*LiS[p/C)p<JKF=Z*0#U342+3Ep>\-"9cGGO!2NkrAO\SR'Q:[X%A9Pc]&bEWdf]GY1Z47E%7BZTk#&qZqDCH?.\oR,"W,Tn>JM)Yju0bFXdoCi_P&>#Rl6P&;@;uUOH.G>\<L?@o.*G+/19ea"bZ6WpUhS$R%Zf"]nt*cH_0m$2-oXHPEZdHm(F?P$gMZ$.o5mDLY-%8%QmUc)P5>#cVcCLR#F1II_BQp#.HmMDoT,u)X9g>"gnE??'?%i@scM%ML@7.?\9WccX!rL'Mh/@"kW?R4Mi=-*7)>lU<=55M9GV)k9U\%Be:CC[=>lE&ZI04WH1>6r\TI3+Y-`\nus?EK!\-gC7_IFn$\p,l-4`S[c&Ks,k<N_L60W6D-#/.[/T<#+88#o1:99^\gX12CcPA5]t<ZADn'AKZ7Zsj23E#QEs8@m7N'h9d(XcGPs)oRR;UOC2bio*bM/'ArB0/=0o'jUqSBUVi<$IH2&5m<A41V&-5D-n@ER#BjC'%i+\h$//$HGU::1lg>5!M[XJc9j/_0)0'r7>JN->HMZtfV*U6@^"B'?,bU(=d/(M`UDLB~>endstream
|
||||
Gatn%>Ar4L'Z],,'^(TRRVP!)SEp[l8<[5ZQTXiu3X.>P4**_AhgS=n0G(9i'LbWipt3@]\_+q*Y^8q8Q6j=+L$KTW66TV7#uLum/POX0T)mVJL($L*_]a!E&IsuNc#^c.a#%0)THK(FNra5iPE:<#%c!L`O5Z1d%RGZM(-uXB+G:ubET"jc%#-!t=d*K;,O,KqP]Fj(gltDiU</CL>\E?h$I[gnIY7+0PF^80pRK\O&]>0*)(%pCi:MAr2aD2[R'b:I9Q9mh(5=V\8pHkd-?iBfAfkbO_)%*bbWG`JK7N:tr8UB<5+qNZcs\Dq$O(,Cdce[SY4`Fjdf"<;n.D5$M[o"8US?D=b_M.0^;s*8P5-;g?(lM9UP/>pSu=\WC9ZtL=0FgOpl^#s7???"0i]hhT`"+P&d&#E(Eg0B#9EW<?<F[0m7&uQX$GkW10#(Oh*XfaYEXli;2)"'RY/a$WG8%NY^T$"MJ=HK0Kb:Vk[D6uZ&ib_fV5BnI)"UTq2Bc@:$1`6o3RkZ8KSBUpNGaOgWVl$e$s]<Pm86UIBi6/m[@(WF1m8!f8YRG/;a7UhbRjWhC<#@ZQ,:JXu#[LTp'FY$][pDPXW38j#+V(1;ECB9i4;fo%u(>@Z&7Pi.<R#SoiX5Za!F,3&K@VI@sWH]*T1^?J"[]m+P_P=s'D#I$Lh-=c!@J:"=s:,tD)i$<N[:ASd?$*/249'2&ab~>endstream
|
||||
endobj
|
||||
39 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 3411
|
||||
>>
|
||||
stream
|
||||
Gb"/)>BALj&q801kbg#q/-q6<O>e%fLQ)TWiFG`LX&NR`J79Z6?*M#WIX<=c99'$EWSN?c;m+%[HC2FkFFVb&"@W/2l2T$Ei,Sk3.fr+j`<qDF.'U2^F7J5P#BH32W.NT%70W=4?Voj8K&8r$R$,A[#@)foMb8R.Mf$%95@%.I+5FZNcuYaJT]!B[,K'Tc46GRBS[_<>JAn&2%K)G/_2i-=K-rV7\3@hM#0XeR[`jfkro2Xk5:6s^(a`Z*KH6`b'"1(?_h<c*bM^Kt?-)^lH]%5U$6N^H!u?B*F`-a4?'SAT(DWLAFC]tKoQ?99T:h(\KV)7K&Ah-b8R2;PQ'@;1OP:3s%*pG'MiPr[o`\2>.O\c@`(1I:P,ueKa9,/dq]P+&1f+JPLQ>f+jZ\P]&0rH6cm:H#$s%l9Rf"P*,H:.V&^ggtGl'T&(@IE..nbL\Mj:-QG(+".DR6VqSmN?o6&V">jo!76BsY*ihLf@c`e1(:hLe43j$BE2pE3>eG`^#04\/*_%heEF#7#)cW1WgVf#E.'H1/kWNaE7Q^$E7=8t&?td*oKXrK9Z]N&^[UglZ0._[;#QD2\tpnS!03A4b-9XjuE3ZJEsujiuN@n+/Yb66C5/(Zq-i`)74&(ho^TccpCjgbN`iqJ>W;?l)5\MsMjU8D#P1.,:?ieF,$CQKQJRD1e2VFW;/cQrh%0pJ@%0b4QSgZgmua."jXQ7T"n&+#UKWhNb7&/dgWbh/2Y3I(M4l2r0`84OmBF3GXh"K2iIHd;\@HC\U!F@I6O(83-P1TgWQ$lKpJD[Rh0W2g!A@i<U<b<kkI3qR1B"qeXokq04qB0?0kh9C7>iYC$O7d*SgQ@mZrXL$2>l#i57ZFrp<T7YP4dW4"+9HV)Wi_D<+L:UUcBi=uDD(.JS9;Od#d]K'I3?#=G=90<XFr7cI0=uL7qg\"VkX_3j[>VAg-=L`Pq#$\\)^n,l?1>N*F`AC=dBC"h8#ErhmF\#lbkcRLZFgi--e!Sd_]?/8<R?r-Lp%3.InS/QtB[ebIF:1_O[%eX_:?nYag?D&E_P-YPC!<M+$6g'n=?PR$I`??GQFU3EE+hqY?k'WHFa.@+E&[4SfS6POP0g9)r&tk+.r&?6QFnd>Q/=BSX>Mk,HJ%POTuH$UnQ[g(\3)-oN;\ZWi7<G"?2NGc[HbPjX_?_5-_n3tafX'uXf:ieSo2e5=IUFliPtX";*Z5>feDU:_jJ^/3,5i18q&n%*"a,?pW^Fc1N(3j5ODZSd^6+GoP6TQF(Rq;1jL%:NGlW1%J:a-??L0b-5V0TIMnbs?i*KTiqj#!ZbtQ]O6H"liJj7rF=J0X_*uo4GW3>S@2t`l9`M2jD@7KkllK#X+eD76\8.[qV[K7%%uKUl4e9BL!X'`@[e#*;P7%"&j.P@Q1,7t0Z#N]<^#g;(CsTb%a2785`Yb!)14gR+N>][NHL78CRGJe7%-IfragjF$c55WUSlX+H(k1DXCkDkCCIk30,j62YjHsYj6SS0udAR!7FIFIiA&NIP?J&=<e*ltZ;U5B>?]NuSLrXDN3"A.1,5$JE[OH<E$^Q,.At%&;j#<1i5BcOhHAk<1M)R`5Eh>&l-e(4Cmuo(r)WR7O\T)2.2=,a%#Or!QRuMr0\(SBU]lWp6>gIN:q$mu`#?s`e76EP8LNGt5MW`Q1,G&)JW:!5gji/ETr:Of20:0+LHklq0,caDSGQU88na[!h0dHu)>a`.cjmlL@,uq"W5PlES04T6]9="^^b[QAjI#N0W&Y`sF3ZS8mA'd*(R9sgL2t[U.I/RK"j=sIe9YSRQ?-M7r#NF+,CBbWp;8E]76Hm`XF':`30ucf8>8Yp-bJqdjqclqA(4:@83i:;7`,cGb]"[d!D/bFE',KRRnk=iY1^5thfpu\Y1Xl9"r=&eJaB6q\)D-4$P,N$\(jj8aI5dVVQN=bga+lF_.@IA)l]e:F`g@*sHnE$8Xc,SFhe+!ToHd%;fS^c_hYHRH[k-slamEPg-S]m3[5O+I)G]Ln29%JZq:%<]HJ,e'l42sNXqMqnmm6e/3Xh)j?Jmq):-0UTe/L#G%mf1NP\OrC4>SoDdtMKT6bLWGp38?nLRaehqX0+)3hbLFp&s1<cGNA&*WmmOThJI[L52ol],T?tSmck"L+%%OL.Z#G0A'-.P;uoXC961>(eorlc$E:Jm#+V1mjQl>3CF";Q;Fk*&`,<LhrU*pcH3p7dI1!F5+]cI9nc].LL88ZAN*Jo"W%sUD&hZ(OUbWkmjP1Sa4/e<I6/FMq0hXC_NL+UXt^KuZS(XkUWM.0)V1XS0,nhob]bc+R@f$$!r>;PL,V`S$i,!V`k`p4Gj7d=_7Z](5OdQk%mjXO'pZShht-.EJdP:B=*"2l6Jt3AXHXur$"E"+Yr&^BjiBd$X'$e.YTOOW6Gd/+iZ62TM%9tl?`T9-=HIX86Gd.sdOGn#@6mf`$42Ik8QI;d*#9fV<(Ju"87KQ>I$g+BLTj2+_h-CA=G%+lP#egl*u6-0@LYLP84pi>HruKkMDb15+H_O=poklt'l3;I:)[)j0mL*[?8705C'h"O#jp.PC;gVHWtj]Q&`hB#W,@ES"f_dJop;I?G!<GKMF"sjDi_S+:dbb*9$5PZ#Dso;\u$R!]HHO9R*b>>!ocSf?+E]Kj3KqbONF06l-//jV8h8C2P!^Eg_n'sj7_P`Z9.khDb`nVHBU`22*u-4:.!9eGi9BY6!1I<hmDZh2nsoq8(!;fX7^]N)ZEp!M1h/hr%&!0qG")Ghl[P%VZ>dOnYnsh1,Xr"'0C5J$ES_='k`<jmH)0ZnL=gRE+"l4@[hVIg\j#2a%LY+.cRCAh`I&rH[#]rG*!IbbHs?t$[-N>0cOE3Di=Jf>,_X'V.R/::+,AU13,6PX/0?.c#kB9WH'>Gb![&qj"Z]FJu5NfUTDt3M?s`EkU8$E^u7T5fcVcd@YrQ@61k19Lc2ag<.\d%17%$-a(t9<]]h]B-#:/fXb0APgr]t-s'rm.L<dg#2([Sf%<a1Bl+l)[5(c4HeA7&d=o5scjZCjbl=7WW[H$4B;?^'aO4)-8cKPe5jlHk'\@^&^=1`]#3PqP`4[1:lSB6;2;!'OqY=bNK9X7s@X(GL[W<QR,'1D4U]bNoUdJ(u]"OV5G\B),EQ.`c+M^Cg=)b<$oG]*!?Ar%cpORGEb&5Z52mF"TlrY?dE]mG\oRIg#n5\OCl%2Ld*Zab&RC5FIA^M2$A]U#dsH#?CTDCPA2&5c>Se"Uc,l'YqUk',\Nc3,[([=gp2npC)RMXd*.]R7F0o?)>*%aQc\#Bf)FLaW`dS[:DDLPC*Np&S#BJ!O3sq7_U?H&q!dk./?_"^Y?/-7JPQ1HV,p!;Gf$JG]/33c7:ko(I)E)erR0L97!B=qWu'+^C'sET"/cW#0F[[D0cd+8u^ug7]5~>endstream
|
||||
endobj
|
||||
40 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1820
|
||||
>>
|
||||
stream
|
||||
GatU3CN&2l'`C51i_#tOd7t!nObE.h9nB`dBV?T?eOW6aO;Q%q`#7<OVYf]R3eaq85u!86+#ql_a60%@Cho^'7k9$Y.tBDt>3?f;3r!dC9Z7-a#^ua%cVN7AdVLG"doK2I*DKCjNf!As0i$6@G<&87r"Wn*SD?6XTd'g)`H)dH!ZGsF01-#O(iJ+Rn:,t</CWD4B3-S+<n_,bm9?1F7)8Y/;@250d&V1p#`YfqgY`R^Q`dWO@B/WObS_T(OWm"c(0QjHk:gaHFjkdB`Zpq9ktqn#D>P1W+%I)?7dek@hU&q?:;a[QVTU[>+!;*5Z5+_'pip)i_W["&/!uIN8>UG=X\78e-^TD9+-/P!n!+;L/EBH+k:$l#)E?6lTiDqhqq$B\*g?qnCdGQ2*84BN[K%NWl;YUb3/:Dl&%Z-FrZpu&DXY^gQcMRH2%gnC^dTG<=9FKA*(nP2kFT<;DK*o(.[GbX@)9^-H(XRK:[Y21_%jj-MSU4Pm,sAA'@7Lb$1(6b"YsB6/_*aAeBk\=2sCP\e5OY+:3i5tJ?N%TO?Jj'(g0^eSKC:Ro1`dZEj'uo2[/'XT9"KW>#i!@DL\7ALpqJ+;cQK>![CgR1uM$L"N^p"ESl(0-q@]$mWZ.uq\-`Q.,3=HU?m0o#2A`ASiKO68IT:8)>m`LIjHr3[?)@(DP>Q7[5"HH3YI/&7l3]LCF%C>[Zp4DlDj;q#O\fA-B'sl@,R&\!)O1aOKg_O>kc?>OU&\j.(M16d@BUL_7">F3=@Hd=+d13:Ab`[*'kM4@8da%-8i"e#9/Bo/\&?6LBIR'E.e%)Ikhn#Y"MIV2^$d/bTa>:RAbEt*It"rX/7HLmLEprAO&]#RO7RjqGtE&qhrA?6:.,<._%`jXT?F\'YV_uX)dM)_RX`=9cIQ>A:j^pka7lr'E!KP?]kG$0k$t;0^/5EotF]c9nC.R\bC2R'BnU.>s+dDA/'pfXn+I'Ea%MQ;4Z!ED"FY2bUJ_bcY*q]+W#8pVQJMnVbL7N@c.LS_pL[pl5;rT/V1$&;T!P/WqD=UGG[;5n3_ZVPhSbb:SLcI],_<,6*EKoNkQa4P9&hCPPVoS#nR[tD_Juo=<H_Q,<5p^3X>DRJ*g#WOZ?,k4RA_6OBUlX`NW?ZD]>.2<X-4C%Bi)dJj4K8"Fnq';,PfL'PDWq[sMLL,.Tj'RUgU^esshAhW9(7lE2#&XNAFK8s#AnQ@PdX.,tYNg,CWRYm2,f_Nk&6dPRR\=$*;P3oj;b-65T@=D'Kr^dKR(1@W>O&a[Y?-j5CI+Ue%c6efbY%G'Q??>T"gr';lJB&Md0-+Vs9KfiXoJi$sUnYVtAP!?TEZkJLsT\RfjqG>Eli19]]pWe?fcLKP>/qn.`(XlaEQ;_9,r\tpZ6Z$W17">"8n25@4b&Er&en):rO4bdKCZcm8;eRqJI("VhSd2TEP/](CI]a>bClH#`_hu6H!T5bK9<L/^B3Vm*RCotF$WP>fi_(@D,\Q]0>88n5>qlc:g05`<eeDa1nA1lMLK@`n_Z74pm%LHU@eId^X^cIFWr*%>NKGTpF@S\I\@u_U[))3oSA.\4_E%0cNL&bV9'!<.<2-9\2=/\'X*_D*N&jq',(7]3D[#$UT^%]M)E(6&FN7ajNtH(r83Pf164W&A$]'uXYDatApISImr84!Q2h1>Jp>78m?B^lk'#,VNbC<S<^Hd"bkUK`=VFQ,%'Lk^uZBg$L<*!Q!N;a9:TU8_27\*GEC8pM0b-jh",XTD,\#`0)OI+UiOetMq,c,aX3j!F"/0S+@,+,;#QX&(l/"bC3Z=hUDHiCaY3t;Mbl$krYqpt~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 39
|
||||
0 41
|
||||
0000000000 65535 f
|
||||
0000000061 00000 n
|
||||
0000000135 00000 n
|
||||
@@ -232,39 +242,41 @@ xref
|
||||
0000001683 00000 n
|
||||
0000001831 00000 n
|
||||
0000001979 00000 n
|
||||
0000002095 00000 n
|
||||
0000002387 00000 n
|
||||
0000002493 00000 n
|
||||
0000002571 00000 n
|
||||
0000002777 00000 n
|
||||
0000002983 00000 n
|
||||
0000003070 00000 n
|
||||
0000003382 00000 n
|
||||
0000003457 00000 n
|
||||
0000003552 00000 n
|
||||
0000003661 00000 n
|
||||
0000003770 00000 n
|
||||
0000003912 00000 n
|
||||
0000004019 00000 n
|
||||
0000004133 00000 n
|
||||
0000004240 00000 n
|
||||
0000004347 00000 n
|
||||
0000004447 00000 n
|
||||
0000004557 00000 n
|
||||
0000004655 00000 n
|
||||
0000004730 00000 n
|
||||
0000005490 00000 n
|
||||
0000008889 00000 n
|
||||
0000002057 00000 n
|
||||
0000002205 00000 n
|
||||
0000002321 00000 n
|
||||
0000002620 00000 n
|
||||
0000002726 00000 n
|
||||
0000002932 00000 n
|
||||
0000003138 00000 n
|
||||
0000003225 00000 n
|
||||
0000003537 00000 n
|
||||
0000003612 00000 n
|
||||
0000003707 00000 n
|
||||
0000003816 00000 n
|
||||
0000003925 00000 n
|
||||
0000004067 00000 n
|
||||
0000004174 00000 n
|
||||
0000004288 00000 n
|
||||
0000004395 00000 n
|
||||
0000004502 00000 n
|
||||
0000004602 00000 n
|
||||
0000004712 00000 n
|
||||
0000004823 00000 n
|
||||
0000005050 00000 n
|
||||
0000005125 00000 n
|
||||
0000005933 00000 n
|
||||
0000009436 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<5465d663d38226836f882a539aca7e31><5465d663d38226836f882a539aca7e31>]
|
||||
[<d1eb6aec62dda0de1b321db25aa5cc93><d1eb6aec62dda0de1b321db25aa5cc93>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 22 0 R
|
||||
/Root 21 0 R
|
||||
/Size 39
|
||||
/Info 23 0 R
|
||||
/Root 22 0 R
|
||||
/Size 41
|
||||
>>
|
||||
startxref
|
||||
10170
|
||||
11348
|
||||
%%EOF
|
||||
|
||||
@@ -133,7 +133,7 @@ endobj
|
||||
endobj
|
||||
24 0 obj
|
||||
<<
|
||||
/Author (Android Cast project) /CreationDate (D:20260611185331+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260611185331+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Author (Android Cast project) /CreationDate (D:20260612190938+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612190938+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Remote access \204 implementation notes \(v1 WireGuard\)) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
@@ -283,7 +283,7 @@ xref
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<a61d1e541febafda5803ca3349ad0aad><a61d1e541febafda5803ca3349ad0aad>]
|
||||
[<648e459d5f2c35d90d3f442a04b8bc8c><648e459d5f2c35d90d3f442a04b8bc8c>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 24 0 R
|
||||
|
||||
@@ -107,7 +107,7 @@ endobj
|
||||
endobj
|
||||
19 0 obj
|
||||
<<
|
||||
/Author (Android Cast project) /CreationDate (D:20260611185331+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260611185331+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Author (Android Cast project) /CreationDate (D:20260612190938+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612190938+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Remote access \204 validation \(Linux CLI + mobile parity\)) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
@@ -220,7 +220,7 @@ xref
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<46b18678d01cdbe7e73839fa89d85491><46b18678d01cdbe7e73839fa89d85491>]
|
||||
[<84e36839b1116312a237a780b9dc4734><84e36839b1116312a237a780b9dc4734>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 19 0 R
|
||||
|
||||
@@ -168,7 +168,7 @@ endobj
|
||||
endobj
|
||||
29 0 obj
|
||||
<<
|
||||
/Author (Android Cast project) /CreationDate (D:20260611185331+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260611185331+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Author (Android Cast project) /CreationDate (D:20260612190938+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612190938+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Android Cast \204 implementation roadmap) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
@@ -284,14 +284,14 @@ endobj
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 3099
|
||||
>>
|
||||
stream
|
||||
Gb"/)=d.i)&q7#kTr3b4"68.5qhYNWS!c<NNgYldNZa!,^sg$;QL!1YrU$*FKeXG!^dQDa##SrjjLg`"r%_ganaI.Ms+Z0QQT=?f#tPWTI#S8sPL9:LNTN0Lh$E7=<TeTu:lqPGn>.MMA+:uE"Ig>H-Aa48Po_:[n>.@#-m34%reBa<]V`lNHqb]7Gt/q8A$.L3'hjPI&eCe$&;V#n;LCgP[=]is*J#8Yrq<T]p^ICo&nG4,78Xl'Yjp1HYMZ<4n4qEVL$9*>_Qc3L*ZibRSp:UiT"n0^*]D\HMl@NCe?_P[sR_&K"8/JpSgE$.@5niS,IC.OC4-gM$tYZ31]EYKOQ*@*-nN0g_p"ZjFCd+.FCJk>TQFZ$h4PD@!MLd>)7,Jq04Y8H0$HJ)5.UW6'pIb1Gn0)br[PZ*3Z<8_hNZ1\`-f)tnW-P7R9^^[q9IE-$Cj\6iYEmZITJlm5gkHML;3h"QtgA;*s)KpQ5Cn=6?^)RIr`#qF8Xl%JVZK$%IgJS3TQYOqcn*P$E+'4eX%>uNJGEB>D0>4*`]I[?8U1K(\H6q,p],Tf?/)A)6X5O^'UQns7o/;BA<+`]#L&WEeR\j[c^9bJT8R]p]>@Qfq5,GJE%Z<PXtJI5KC9KPTi)CJ0*$mdCVGf`^4^s"']M35aO^1\hI>!;bdYf!+2."odf-fh[5TIe#1]`LEYf0r8n&1>G:$ZVUZp>e?u$-.\T?<e2)a1/^raRcg(FmTUSRTrKe(!`I&la:g\V(`(=m=Gb/du?/:GUc';8eGMe>)atN)J"KoKP)"J-nLKuOmMkoi?,K.CsTo'9EU&lBm:sZ68%oQWSdmTol83arecs%4/P'r4p+%s4H\7f?JEsj#eVkX(nTbl&.(uI2VImG/us8ZMF/VmHl@00O_("&(K1TKS]j^\k*\4g9r?"^/>K1]/]1VP\4h`3kQ.#:T%H!KEYV^=L2]Vq?L>^&Z-Ng$#7[$?[bRLn6p;l]rg3.H+9Wmrg^<d1e))a@Gb`nP_$ns:H:SkVCMXR7'!#[TYbfIAB@:%%eJ5g#\^P)UXE_O$FI&+$]fn0i!;d0n`"j4;O.G8!RA<n_4\NK5$-ps6b:ue$X8mM=SjB?h?7`\\,)RFtC#K*IprLKa8@JSX2d9l`(-(X)csH!ZIuGD\+]F!;PFbfPHmAurasDqL#8qmYncKdmi1C/2LjF6Q%=n!9JZi<115bXf*Mmlgg9\h.PRPA<%?b(6[e$EUdm\tOY[=[AoefKB.L@u?;N[&;WdaO0-\8#2+b%H:&85JTm]ljYR=t[Ob6@rN('`\L)Z]e<(kB57!)R06!:J.4f7^_e:$G6>T_>2(3^hB3!R_:>bF_aOH**;I=#31)C]<Zq8,_DaLi^cEKor,&3*h]tC5HIQT?sl#RKqISI4q6:qB`<>4N"M+SNj07A5t'tX=91:qMIO<cDWQ6Z+eZMSO'_V/U(VXU,L++HR2%n:^oZ\CZ6/],>.9X^)0TfjdBY=,F^SX(Y;D#i1=f'pL$lPB7$q,a\?\HnG?ZW!96COj:_;$O]R(\Q:"p2Yc$*Ai<0D8_KH%*d[]GAA2Tngrf1%GN(0j8,g?$\@fR'YJ"p;K*IApJ@eKt&6FY!3GndEIak/W;W\nIMm3.?*PRPkdbkd'cCVO-X<j]P&Y3F3(8j;]';CR])pY^S5K*.2N$3P76",PeTJEU:L':d3G#qTUeiY2A&Q"aK(GtsRAC?-OrH9S$6StBUP?oRE&0)ek?jX$$Bn>g>Uo(<I2hDl%as20VEKNeOBcYd=&VgBS-d-KC)#MklnOXE&9@g@1*!t%qa^)cocI)pUnGh]Vj(8j2b]>i[7q\Y=NfR?B:K!6-N(D-KH4%sWb#[aG:A:"NhWu8?uJZX5qXYJH1TKB3<07`QVYrU6Jh-f#G8ke<JfB?dmU_V\"T7#rXmmd9O@QI]L9h1t.O4pE)%,!M)l^hpsnQ(r#F5SOg[9Vd#f34@bh59mbo-s1<fM*!-5TK6\+>5u5<cD=U3d)JUKV0^XGCPCbOoV]lA'"]JpOLUf!Tm]'M9_9+#o\&3'VC+4`lR[DP,e*[c%qF5f_N*,?1TZeGg=HPM:k;Op-PT.:VfVki6iB0:(S"J7bI-^aXM2W)?X<OcMJ0'1_D@6jhT0]0(G\*TDP2[6R7$W._0!"/>$%l/h7LumBs3F]/E\9r;F[pekDW,i$o$o-`\Wk:Oj0?<U<^1>>^aX#q^^DjpA8gHnV99Cd\-#n!T#iI[hXu&?s4>[jlh4RT=m2@PPPdi>0R&iYLf7^#D"k9;s"^l?5O4c+gu5?EbkAo&F!gJj\0Z-FVTNSp["-2@lB2K7$.pa_62kT,;!<j9;,V"^ri3UVL3u6?%t\&VUEqY<=u).(9Tu6,`'*e4r38q\a6%$G6%M"=h`Z%>"=rbRefGBg\[cX>p`9r)X-(S-iF]$d1RYj;'qf[DC@XIF-WbA!`@o,ntq]TK0q'p^98dFd?0'>%)Es@a2!9T';mndPYD_C6rZt.3'd"Zh$'hT;4=%/Z>#9I#hA!0optI//H3=/WSn3m]sO1]'m7pK3'_6_-LU+1#O`IhhDYO=9G0b!D>.uLJ8$NBS^&WLBNH.r(uT+QWduUqpJ'*0sO+@JS.HoRnl5FgO`t2AeKrjel/U7G_$V+l%IW1I'QqQB/E^B5up#9(o*WnlG?qX:m&\s;H*cRU)7sj`j3CV?4UNjL@m1<7j`]Z4]!`VB#h+e"V]q&"3FL:@]--"*&hPP4hm$?bIdYL5B9IW!BGO9_#a9i%0,/S1mbl'ng&aC`:eRj/S)L32KSZ8n?.n"U/G+5o0Vj)h6`g?<cIQ?;>86>;:"uQDpoj&$A)M:oi\S>>drco!V&>F=(E15=[G5j`j$20QS8$u\00!aLiET%_kR0dTO6(?5(pq8gH"pjE%Xg[@OB33QEu6q&@>uqE&m..k.+;UOCV>:+4-:_j+#XK%%\L]7ao2`J%r=GaUZG5r<`C7h3J7`E/I(GS<k@r:Wn3:RL?skEQ@Pk4`lnVi4;aK:lF%\D5@G&:_bXlcI6=r\Em#240\i,DB[kRTE%>9Yj_i(h9!1C=fu_KM4WikY>*CcK&pDoY<;D&$.-rs!0B2:j8~>endstream
|
||||
Gb"/)=d.i)&q7#kTr3b4T#UQ6l$]:Vl=Q?^U,ch/2`cO%[K')BbAGFFm'#<208qo-6VYK]C_0Ud8Bsn?VD>n[=[F<[V"h^47HeWDg@l5CA2IL68JP2])kk+7k3%_gcI>QS<$VnjL&t^C.QeK-nkZL(nR.N],?=%8Z34N<P(Eae<Zc75N\26IMTXQC+ddK7+Yi*d;c:Gj=>'D7.KtAoYp@:9UjR=S`-+YD_k4\lm.UF8!mCpjSZTe+)_J>fK<A5sd-OObqoCdFK?oR9XN5uGl8rU54CZ`!UJ)MHgSZ)Yhf!#Qm<^E&^3p-='9HV?"X$)lRQc,rrPM2u']=.@!j+#i_t;.3IaD<gAX=7-jVqA.2"HD[a@LaA:_10Nf]S-RRju*I<[7s,I>%62nFu+*@*45(8Z7$-I(9hGoFh%YLpi/0\S)=7jF5!rcYrA_EW:5;ru#^7cNj1p%)?.(+Np<deXTrpP<S`CU`rePFAiiQfI3J%(m;]mFaScn$?A2$Cf"4hkYMof2=W2<hl6bh03s/L&#D1LP8Ui._O0Ac_sDS)q)osFqd%eFrC?E%1h?^(>N?$R&W8^QGf@<[:)tOFiYKAOP-PN(M.`"P@F'[VCD#`t"J67[&<G^HcN>Z68S?a+68JaE3MS!*bJm2$OsI1W=\b2,a7i"S\^'d!It\pBj@:@uVEQ0,-O%XZSCT.Y'1>c"1^+1X*4M#;BQ?VfA"Id)kj?>Xl$//f$B1]]8_GpQ+De!AFEQgL\uJn)=4%@R^hd(_Ln&@Q+lm\^U/m/5MpZ:\aL4PrBMNfp0T3d\km#7)C`NF_$c)HEW+0$kA.+=L`!V3[Q;bW'kdSSUhtfuDQLEJS`TA$<1?"Y6Ap0W?em:VZY[u4amm&-LA_:PV$b3jnK2`goJ`F)@]o3r_DL%U8&8Pi\g=4Hm\SAF(]UNLA]r>VR-&KYYd=ueZV3!(u'sq'INkHck<P>Qt@\hZ>L$+)Wk&DF1#I0C%8Ypbm4:?g-UkW)^'Jo^DYmDGhg'OXlma+><-s0o9^m8(l.tB3oXI`#TDH>B-VWpp??"3>SkNFHE+*r@0MB;IOMMO.<:EI-`iA4'_D<Y3gX\Wo)=tI]VKs:^;fm<q"qKd/6L.2S7&DP#a^@@>H_iS^8(p>l'NBD/\X.d)Rl2DN;nX<,W[a09RM;faG5c7k]#^_Z@(GC8,]a+If+<3-$B%m;&h]DR8]1#1lI:l&aS:gP-E>TuhcpNfRf9kslWW.'?i?R`V/5gVMPE\[;7BKN7,aGC%]_k?oBQ6SunGrbr(kK;8!)R99!:I,n^m9ZT\B>'V58FI]/fOf?=;ZS#Ms<1!WaO::MBM.(j^]C+RG\)0KTkOo._!&kb)<;L+iq!>ZmUKDs'[$dm2C\@Aon/S;>&BNWCB(F@;.P+YS[-m?m5@mY5"qah:'ZAeOHRA'f]5.7n.ViTPG?C)Oao:Blku#>Gme!Z5D!TA;S!_+ZE@0&CQs@Q1]sa324ar@n:l'=lIG5[Eum>-Ok%@VW#-GY*QCSfrF+F2C8-854!i@Te>mAqh3$@^h@@R4BAedf9kM6#L(#%%Eq_Bjr\+oRJ$Q:<pZ&>b<mQ!q(\oT&9!&n]i.#d5`5C0%PbgYa&%$q/f2X^-0V?`BTDa/b6F1>cR@$6L=C;%Xo^,n?62&LH;Go[G9!RraQRti5i9BY8.1+P\B=n@B+I7LhRO;ib'"D"W4F0d!$"b2EE*eY6]k.h%okOZ#n?M(,c^t#=grSRYKAE"aC9^-<=;d-DCBZ\Bdis&4rbj'$Li=\/$h7l/!5Hee_W:LhIMX/G4?M9Yj5IhO?2aqY^b\#f#,>YY5L(>f1F[RmbYR:;%&EOLZBulgAua(_11R%.9dG%QcU5XeACWfL<X+6UL$o0%/'\bc=uAEO?Pn[7s^.6N,-XnNTb,2?8b;W_;3N6CNjb_kg&=S_;]o!)#S$n,fOE;Y.e-cZGtI"=cU^W:H>T<Te5u.'rfZ!C(<1p!e5j9,iJu=9:lNb!IAa;pB@8.m?RkuX:oa2^I)F$!*Fn/jF@/E5j4j@F5I^h:6Tc+/"gboT8(XHBM33*0'>$Z&g%@`o<hG1o/>m]mF(N\?"^RiKh>XBJWnU"V%&<P+<nmeYhKMK;rFH/#/!o6oH=2P10d.Y$Tf;SKLXqXpH8P;8G;m[C<^p)54M%QX?`_jf>d+Bg/o`]n)"VaB$31@>SY?p<;fMj$*^.sbFR,'<]64j6:9\"Glhe2T8'G$?&2k<4Yp70#gtGf!(3bnX8":b7er%$q.,/q%Fg*M.hlUXM=\Xp@t+3`h%#.(q"cJe?sS*>qhHF$O2$]PG<FqpT>MNP2XMoCh3W+rrr[snSqS"MG6?KIj`=']m)>.GJ-:J=n]Pqnb]fW>cQLoi/On'oqKAmgb_#AFmGXU2%i_+F.Yf&iJO^PF;1NM>BBg:\XLp*j<m?4204I'`9s__VP_?C^C_c!a'or`88'h*@%E1ga20d<a[7_S"AUL)U`GrJHiqsOHlo7SI1ot208\\:2pQ=d]aMMhCm6;#q#!RqHSkB-Bm4J$/+2MP!$'NU[Oe"n7I][5Ee%8Z7Fbq-U&#^KpY;l?FD$lF8l:IcWE^r2sYY0_8:M=";-e>T&<Pr"'V@1Bu7eOZ)INjomV,Hpf0qoLi`QE5:V-X[B+9m1ZiXH^=r=PuoBCk^q'W1>cnWV"75[q2t<BmY5qWn&7S1X/!:OhFU>mKH[c%I[Ya'k?RQ`#,iQ'E]t-6t5:\[IUmB"?^Ua8Pm=b>Yd")(uB*O8"iU^:&WGh[eG&5T'0n9@l%1Y9[le<F7#0+LFdJ'bI"3T93X^J7+>^*+nQUOOS&/40JL%&Rk`k<abS"'(cS.eqMJ\p0-Wcr7qZ(Wn4g(KV/L0L5TA^3m36BK-Z(arZ-^Yi`pJ&Bp7E&_o#m1)g4_5NS+.cI+TIWJ)45*QG\[AhGnTT-VoY<GEN?Mj)N8:O<3,tGAph95+'5m_REF&+DAhtCZfCoG[(4ggPH['E!,WJ%@%]<JV2R-_mO5fFSTYd4jA.VRpn_fk2C6X4Gu(WNMW]+EAnu"qMY>\4F6kuc;uHN&to#JOAC6CY<b<VfM<(TI$_R@PtPIVntDQQ!>g,##6~>endstream
|
||||
endobj
|
||||
51 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 459
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 400
|
||||
>>
|
||||
stream
|
||||
GatVW?#.;2'F*KBJ&Sr:DRck:qR+L+lR%j1gF*=oLi(XZD,ZY`nD>mB>/p%uO?`m1,_mbN!P2H(ZR5oA+2cRS!5erP!lNG;^]F5?ZgAU-S!L0:Sul5THCe_5^KBclC?nSuX?116q5TlmC,Y"ERHZRRoj].qm_WpKkYF^bJ.2?I0V3&K$4G=)@DJ'[-=FC-o!AY-VX(<g_\f-iUo_`7!EM5\^'FQBpu,loe[jg_4\`]9pDTb#SOEHf*+;'HKo)IC"$:(fknO]K]eu^s#S#/d,'un+]jX6j[@l&^T1MplHX=1eVCWlj^*T[$Kr09Pd<9HQb1'-UGB?d&2=mTq-aAQ3'"m4ek*!ek;,D7GV7_1L=[C4F"=n`AJ0kSdnd;DYr/($>n-j*/k7=,^BL]if$:]886:>9IN^r,j<eb4jh9e?c&&?<s/JD0hZA,TWFmjbioFD;"+(+<\"o~>endstream
|
||||
GatVW?YdM4&-24oIYrIjDQkb<qJVGJW1+p@[[Af\6Ioc"@@EWa_/I(j[>a's+9r5.?&i:;"(]=8c9_Rp;g9.*i*i;9i;'MdF:8IuerFt@oE.%2"6;f`a):DAjP+meN4Q<$3-N0<eP<qu!*"+tj)--r-cRj-HVjo!)9Lc<QFp!LRUiQP@?,=1L$#7a9s[a1]ES-6oPSS<D4GZ8Z&>c+]Ok\F-A64F;f*hf@11>LC:c=(=`.6obIY'3de^Zlf-PP#$]JM9mB/.[+USgil^B,&T!7"QY0j7HL9D"A)_&>SohnfIQU:8S">-Ea"n85t``tb<U-Is+.m1Xol01Q*.[dR6K8`=mPGNro8Y2!?P?p^jYhnm"ZJC)1.pa#YMRnMKUCD)N(\n:>k8.2.WC[~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 52
|
||||
@@ -350,7 +350,7 @@ xref
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<9c7fc54d8829b4dd20be9a391b353f80><9c7fc54d8829b4dd20be9a391b353f80>]
|
||||
[<800c79e27ed019b09a790354c7c3deb4><800c79e27ed019b09a790354c7c3deb4>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 29 0 R
|
||||
@@ -358,5 +358,5 @@ trailer
|
||||
/Size 52
|
||||
>>
|
||||
startxref
|
||||
18616
|
||||
18557
|
||||
%%EOF
|
||||
|
||||
@@ -102,7 +102,7 @@ endobj
|
||||
endobj
|
||||
18 0 obj
|
||||
<<
|
||||
/Author (Android Cast project) /CreationDate (D:20260611185331+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260611185331+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Author (Android Cast project) /CreationDate (D:20260612190938+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612190938+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Tickets, Gitea, and alpha smoke delegation) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
@@ -209,7 +209,7 @@ xref
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<9a6afd45fa8ce10be0ffbc92fd23c7bc><9a6afd45fa8ce10be0ffbc92fd23c7bc>]
|
||||
[<4fad3f4eec3a3b3d3d5fc9ff821b245e><4fad3f4eec3a3b3d3d5fc9ff821b245e>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 18 0 R
|
||||
|
||||
@@ -82,7 +82,7 @@ endobj
|
||||
endobj
|
||||
15 0 obj
|
||||
<<
|
||||
/Author (Android Cast project) /CreationDate (D:20260611185331+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260611185331+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Author (Android Cast project) /CreationDate (D:20260612190938+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612190938+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (USB-C / HDMI and USB-tether cast \(roadmap D & E\)) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
@@ -159,7 +159,7 @@ xref
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<9a2835e2004e186f32352a8c6c16eef0><9a2835e2004e186f32352a8c6c16eef0>]
|
||||
[<d79a73d4072e12be58fa83a481c816e2><d79a73d4072e12be58fa83a481c816e2>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 15 0 R
|
||||
|
||||
394
docs/_pdf_build/20260612_MEDIASERVICE_AI_requirements.md
Normal file
394
docs/_pdf_build/20260612_MEDIASERVICE_AI_requirements.md
Normal file
@@ -0,0 +1,394 @@
|
||||
# Media service (SFU / MCU) — AI deployment prerequisites
|
||||
|
||||
**Document type:** Requirements / handoff (pre-SPEC)
|
||||
**Date:** 2026-06-12
|
||||
**Audience:** PO / sysop → AI agent for full deploy + tune on fresh Alpine HVM(s)
|
||||
**Related:** [docs/specs/20100612_1_scaling.md](../docs/specs/20100612_1_scaling.md), [docs/DRs/20100612_1_scaling.md](../docs/DRs/20100612_1_scaling.md), [examples/sfu/README.md](../examples/sfu/README.md), [docs/20260608_BE_SERVICES_and_infra.md](../20260608_BE_SERVICES_and_infra.md) §SFU
|
||||
**PDF:** `tmp/20260612-MEDIASERVICE-AI-requirements.pdf` — regenerate with `.venv-pdf/bin/python docs/_pdf_build/md_to_pdf.py` on the copy under `tmp/`
|
||||
**Code names (product):** **SPIDERWEB** = SFU-alike, **SPIDER** = MCU-alike ([draft glossary](../docs/drafts/20100611_1_net_topology_glossary.txt))
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Table of contents
|
||||
|
||||
<!-- toc -->
|
||||
- [1. Executive answer — HVM vs Docker vs monolith BE](#1-executive-answer-hvm-vs-docker-vs-monolith-be)
|
||||
- [2. What you give the AI on day zero](#2-what-you-give-the-ai-on-day-zero)
|
||||
- [3. Recommended instance layout (up to 3 HVMs)](#3-recommended-instance-layout-up-to-3-hvms)
|
||||
- [4. Network, DNS, and firewall prerequisites](#4-network-dns-and-firewall-prerequisites)
|
||||
- [5. Integration with existing BE (isolation model)](#5-integration-with-existing-be-isolation-model)
|
||||
- [6. SFU vs MCU stack choice](#6-sfu-vs-mcu-stack-choice)
|
||||
- [7. Packages and software (installed later by AI)](#7-packages-and-software-installed-later-by-ai)
|
||||
- [8. Sizing and bandwidth (200/200 Mbit context)](#8-sizing-and-bandwidth-200200-mbit-context)
|
||||
- [9. AI workflow after prerequisites are met](#9-ai-workflow-after-prerequisites-are-met)
|
||||
- [10. Out of scope for first media PoP](#10-out-of-scope-for-first-media-pop)
|
||||
- [11. PO checklist (copy before handoff)](#11-po-checklist-copy-before-handoff)
|
||||
- [12. Changelog](#12-changelog)
|
||||
<!-- /toc -->
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive answer — HVM vs Docker vs monolith BE
|
||||
|
||||
### Recommendation (DR-aligned)
|
||||
|
||||
| Approach | Verdict | Why |
|
||||
|----------|---------|-----|
|
||||
| **SFU/MCU on current androidcast BE monolith** | **No** | PHP/MariaDB/RSSH/Gitea share CPU/RAM/NIC; `pm.max_children=5` today; blast radius; [INFRA.md](../docs/INFRA.md) keeps `:8089` as **other** Janus stack |
|
||||
| **Fresh dedicated Alpine HVM(s)** | **Yes (primary)** | Isolated **media plane**; snapshots; UDP/WebRTC without touching hub/crashes |
|
||||
| **Docker on dedicated HVM** | **Yes (preferred delivery)** | Reproducible Janus/mediasoup/coturn/ffmpeg; easy rollback; same host still “empty” at handoff |
|
||||
| **Docker on existing BE** | **No** | Does not isolate bandwidth or failure domain |
|
||||
|
||||
**Practical model:** PO provides **1–3 empty Alpine HVMs** with SSH + DNS. AI installs **Docker (+ compose)** on them and runs **SPIDERWEB (SFU)** or **SPIDER (MCU)** stacks in containers. **Control plane** (signaling auth, room lifecycle, RBAC) talks to existing crashes BE via **HTTPS/OpenAPI-style APIs** — no co-location of MariaDB or PHP-FPM with media.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Today["Today — monolith BE (unchanged)"]
|
||||
FE["Gentoo FE apps.f0xx.org"]
|
||||
BE["Alpine BE :80<br/>PHP MariaDB Gitea RSSH"]
|
||||
end
|
||||
|
||||
subgraph New["New — media PoP (1–3 HVMs)"]
|
||||
MED["media-1 Alpine HVM"]
|
||||
DOCK["Docker: SFU + TURN + signaling sidecar"]
|
||||
end
|
||||
|
||||
MOB["Android app"] --> FE
|
||||
FE --> BE
|
||||
MOB -.->|"WebRTC UDP/TCP<br/>post-alpha"| MED
|
||||
DOCK --> MED
|
||||
BE -.->|"REST: token room RBAC<br/>OpenAPI"| DOCK
|
||||
|
||||
class New fill:#e8f5e9,stroke:#2e7d32
|
||||
```
|
||||
|
||||
**Not too heavy:** one HVM + Docker Compose is enough for **lab/stress**; three HVMs match **prod-shaped** split (SFU / TURN / transcode). Start with **1**; split when metrics or PO asks.
|
||||
|
||||
---
|
||||
|
||||
## 2. What you give the AI on day zero
|
||||
|
||||
Minimum handoff — **no media packages required yet** on the empty machine.
|
||||
|
||||
### 2.1 Access
|
||||
|
||||
| Item | Required | Example |
|
||||
|------|----------|---------|
|
||||
| **SSH target** | Yes | `ssh media@media-lab.example.org` or intra `10.7.x.x` |
|
||||
| **Sudo** | Yes | passwordless `sudo` for `apk`, `rc-service`, docker |
|
||||
| **SSH key** | Yes | laptop → HVM; optional: HVM → git read for deploy scripts |
|
||||
| **Jump host** | If no public SSH | `ProxyJump` via `f0xx-monstro` / `alpine-be` (document in `~/.ssh/config`) |
|
||||
|
||||
### 2.2 Machine identity
|
||||
|
||||
| Item | Required | Notes |
|
||||
|------|----------|-------|
|
||||
| **Hostname** | Yes | e.g. `media-1.intra.raptor.org` |
|
||||
| **OS** | Yes | Alpine 3.x x86_64 (match prod BE) |
|
||||
| **vCPU / RAM** | Yes | See [§8](#8-sizing-and-bandwidth-200200-mbit-context) |
|
||||
| **Root disk** | ≥ 40 GiB SSD | containers + logs |
|
||||
| **Intra IP** | Yes | `10.7.0.0/8` address stable across reboots |
|
||||
| **Public reach** | For external test | DNS name OR FE proxy only (see [§4](#4-network-dns-and-firewall-prerequisites)) |
|
||||
|
||||
### 2.3 PO-owned (snapshots, DNS, firewall)
|
||||
|
||||
| Item | Owner | AI needs |
|
||||
|------|-------|----------|
|
||||
| **VM snapshots** | PO | “Snapshot before AI session” + rollback name |
|
||||
| **External DNS** | PO | A/AAAA or CNAME for WebRTC/signaling test |
|
||||
| **Firewall / DNAT** | PO | UDP/TCP port list from [§4](#4-network-dns-and-firewall-prerequisites) opened on router/FE |
|
||||
| **TLS certs** | PO or AI via DNS-01 | Let’s Encrypt on FE or on media host |
|
||||
|
||||
### 2.4 Integration secrets (existing BE — read-only API)
|
||||
|
||||
| Item | Required | Purpose |
|
||||
|------|----------|---------|
|
||||
| **Crashes base URL** | Yes | `https://apps.f0xx.org/app/androidcast_project/crashes` |
|
||||
| **Service account or API token** | Yes | Mint rooms / validate session (new `sfu` API or extend OpenAPI) |
|
||||
| **RBAC model** | Reference | Same pattern as remote access / short links |
|
||||
| **Gitea / deploy** | Optional | Pull `android_cast` branch `feature/sfu-relay` or deploy tarball |
|
||||
|
||||
AI does **not** need MariaDB root on monolith BE if all integration is **HTTP APIs**.
|
||||
|
||||
### 2.5 Decisions PO should state (or defaults)
|
||||
|
||||
| Decision | Default if silent |
|
||||
|----------|-------------------|
|
||||
| **Topology code name** | **SPIDERWEB** (SFU) first; MCU later |
|
||||
| **Engine** | **mediasoup** (greenfield) — Janus on `:8089` is **other** stack, avoid unless PO picks Janus on **new** host |
|
||||
| **Instance count** | **1** for lab; **3** for prod-shaped |
|
||||
| **Public path** | Subdomain `media.apps.f0xx.org` → FE → media HVM (keeps TLS at FE) |
|
||||
| **TURN** | **coturn** on same HVM (lab) or instance 2 (prod) |
|
||||
|
||||
---
|
||||
|
||||
## 3. Recommended instance layout (up to 3 HVMs)
|
||||
|
||||
### 3.1 Lab — single HVM (minimum handoff)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph HVM1["media-1 (single Alpine HVM)"]
|
||||
NG["nginx :443 reverse proxy"]
|
||||
SIG["signaling sidecar<br/>Node or Go"]
|
||||
SFU["mediasoup / Janus SFU"]
|
||||
TURN["coturn STUN/TURN"]
|
||||
FF["ffmpeg worker optional"]
|
||||
end
|
||||
|
||||
Client["App / browser"] --> NG
|
||||
NG --> SIG
|
||||
Client --> TURN
|
||||
Client --> SFU
|
||||
SIG --> SFU
|
||||
BE["Existing BE PHP"] -.->|HTTPS| SIG
|
||||
```
|
||||
|
||||
All services in **one docker-compose** stack; fine for stress and first WAN cast tests.
|
||||
|
||||
### 3.2 Prod-shaped — three HVMs
|
||||
|
||||
| VM | Role | Containers / daemons |
|
||||
|----|------|----------------------|
|
||||
| **media-1** | **SPIDERWEB SFU** | mediasoup (or Janus), nginx edge, signaling sidecar |
|
||||
| **media-2** | **TURN + signaling** | coturn, optional dedicated signaling if scaled |
|
||||
| **media-3** | **Transcode / MCU path** | ffmpeg workers, optional MCU (SPIDER) if not SFU-only |
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
FE["FE TLS"] --> M1["media-1 SFU"]
|
||||
FE --> M2["media-2 TURN"]
|
||||
APP["Android"] -->|UDP WebRTC| M1
|
||||
APP -->|UDP TURN| M2
|
||||
BE["Monolith BE APIs"] -->|REST| M1
|
||||
M1 --> M3["media-3 ffmpeg optional"]
|
||||
```
|
||||
|
||||
PO can hand **one** machine first; AI documents split boundaries so instances 2–3 are **additive**, not rewrite.
|
||||
|
||||
---
|
||||
|
||||
## 4. Network, DNS, and firewall prerequisites
|
||||
|
||||
### 4.1 Traffic paths
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Internet["Internet clients"]
|
||||
Router["Router 200/200 Mbit"]
|
||||
FE["Gentoo FE"]
|
||||
M["Media HVM(s)"]
|
||||
BE["Androidcast BE :80"]
|
||||
|
||||
Internet --> Router --> FE
|
||||
FE -->|"HTTPS signaling<br/>wss / REST"| M
|
||||
FE --> BE
|
||||
Internet -->|"UDP WebRTC<br/>after DNAT or 1:1"| M
|
||||
```
|
||||
|
||||
**Do not** attach media to androidcast **`:8089`** vhost ([INFRA.md](../docs/INFRA.md) §2).
|
||||
|
||||
### 4.2 Ports to plan (PO opens on FE/router → media HVM)
|
||||
|
||||
| Port / proto | Service | Notes |
|
||||
|--------------|---------|-------|
|
||||
| **443/tcp** | HTTPS / WSS signaling | Via FE reverse proxy recommended |
|
||||
| **3478/udp,tcp** | STUN/TURN (coturn) | Required for many NATs |
|
||||
| **49152–65535/udp** | WebRTC media (ICE) | Range size depends on engine; document in SFU SPEC |
|
||||
| **8080/tcp** | SFU HTTP API (internal) | **Not** public; FE or intra only |
|
||||
| **22/tcp** | SSH | Intra or VPN only in prod |
|
||||
|
||||
Exact ICE port range: set in mediasoup/Janus config during deploy; PO must allow **UDP range** on path to media HVM.
|
||||
|
||||
### 4.3 DNS names (examples)
|
||||
|
||||
| Name | Points to | Use |
|
||||
|------|-----------|-----|
|
||||
| `media.apps.f0xx.org` | FE → proxy → media-1 | Public signaling + health |
|
||||
| `turn.apps.f0xx.org` | FE or media-2 | TURN URI in app |
|
||||
| `media-lab.intra.raptor.org` | 10.7.x.x | AI + sysop intra testing |
|
||||
|
||||
---
|
||||
|
||||
## 5. Integration with existing BE (isolation model)
|
||||
|
||||
Media HVM **must not** share MariaDB or PHP-FPM with crashes console. Integration is **API-only**:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App as Android app
|
||||
participant FE as FE nginx
|
||||
participant BE as Crashes BE PHP
|
||||
participant SIG as Media signaling
|
||||
participant SFU as SFU engine
|
||||
|
||||
App->>FE: HTTPS session / cast token
|
||||
FE->>BE: existing auth APIs
|
||||
App->>SIG: WSS create room (bearer)
|
||||
SIG->>BE: POST validate token / RBAC
|
||||
BE-->>SIG: room allowed + ttl
|
||||
SIG->>SFU: create room
|
||||
App->>SFU: WebRTC publish/subscribe UDP
|
||||
```
|
||||
|
||||
| Integration | Method | Existing artifact |
|
||||
|-------------|--------|-------------------|
|
||||
| Health gate | GET | `api/sfu_health.php` (stub today) |
|
||||
| Feature flag | config | `config.php` → `sfu.enabled`, URLs |
|
||||
| Room mint / revoke | **New REST** | OpenAPI under crashes or `media-api` submodule |
|
||||
| Auth | Bearer + session RBAC | Same as remote access / short links pattern |
|
||||
| Metrics | POST ingest | Optional → `graph_upload.php` or new series |
|
||||
| No direct DB | — | Media DB (if any) is **local** to HVM (Redis/SQLite) |
|
||||
|
||||
**OpenAPI:** extend [examples/crash_reporter/backend](../examples/crash_reporter/backend) or new `backend/media-signaling` repo later — AI needs **written** API contract before prod wire-up.
|
||||
|
||||
---
|
||||
|
||||
## 6. SFU vs MCU stack choice
|
||||
|
||||
| | **SPIDERWEB (SFU)** | **SPIDER (MCU)** |
|
||||
|---|---------------------|------------------|
|
||||
| **Use** | Multiparty cast; subscribers receive per-stream | Mix down; single output; higher CPU per room |
|
||||
| **Engine candidates** | **mediasoup** (preferred greenfield), Janus VideoRoom | Janus MCU plugin, ffmpeg bridge, custom |
|
||||
| **CPU** | Moderate per stream; scales with subscribers | High per room (encode) |
|
||||
| **Latency** | Lower | Higher |
|
||||
| **First milestone** | **Yes** | Phase 2 / media-3 |
|
||||
|
||||
**Janus on existing `:8089`:** treat as **foreign** — do not piggyback androidcast nginx. New Janus instance on **media HVM** only if PO rejects mediasoup.
|
||||
|
||||
**Docker vs bare metal on HVM:** use **Docker** for SFU/TURN/signaling; host only needs Alpine + docker + nginx + firewall tools.
|
||||
|
||||
---
|
||||
|
||||
## 7. Packages and software (installed later by AI)
|
||||
|
||||
PO delivers **almost empty** Alpine; AI installs when handoff complete.
|
||||
|
||||
### 7.1 Host (Alpine packages)
|
||||
|
||||
```text
|
||||
# Baseline (all media HVMs)
|
||||
openssh sudo bash curl wget git ca-certificates
|
||||
nginx openssl iptables ip6tables
|
||||
docker docker-cli-compose
|
||||
# Optional host tools
|
||||
tcpdump bind-tools jq python3
|
||||
|
||||
# If bare-metal fallback (no Docker) — not recommended first
|
||||
# janus-gateway mediasoup — prefer official containers
|
||||
```
|
||||
|
||||
### 7.2 Container images (typical compose)
|
||||
|
||||
| Service | Image / build | Role |
|
||||
|---------|---------------|------|
|
||||
| **mediasoup** or **janus** | Official or pinned Dockerfile in repo | SFU |
|
||||
| **coturn** | `coturn/coturn` | STUN/TURN |
|
||||
| **signaling** | Custom Node/Go from repo | Rooms, BE API client |
|
||||
| **nginx** | `nginx:alpine` or host nginx | TLS termination at edge or intra |
|
||||
| **ffmpeg** | `jrottenberg/ffmpeg` or alpine | Transcode sidecar (media-3) |
|
||||
| **redis** | `redis:alpine` | Room state optional |
|
||||
|
||||
### 7.3 Repo deliverables (AI produces after deploy)
|
||||
|
||||
| Artifact | Location (proposed) |
|
||||
|----------|---------------------|
|
||||
| `docker-compose.yml` | `examples/sfu/deploy/` or `backend/media-pop/` |
|
||||
| nginx FE fragment | `examples/sfu/deploy/nginx.fe-media.snippet` |
|
||||
| BE config keys | `config.example.php` → `sfu.*` (already stubbed) |
|
||||
| Smoke script | `examples/sfu/scripts/smoke_sfu.sh` |
|
||||
| OpenAPI | `examples/sfu/openapi.yaml` |
|
||||
|
||||
---
|
||||
|
||||
## 8. Sizing and bandwidth (200/200 Mbit context)
|
||||
|
||||
WAN **200/200 Mbit** sits behind FE; media UDP may be **DNAT** to media HVM.
|
||||
|
||||
| Profile | vCPU | RAM | Notes |
|
||||
|---------|------|-----|-------|
|
||||
| **Lab / stress (1 HVM)** | **4–8** | **8–16 GiB** | SFU + TURN + signaling; ~10–30 parallel WebRTC streams |
|
||||
| **Prod SFU node** | **8+** | **16 GiB** | Dedicated NIC budget; see [SPEC §12.3](../docs/specs/20100612_1_scaling.md#123-network-bandwidth-and-egress) |
|
||||
| **TURN only** | **2** | **4 GiB** | Mostly relay bandwidth |
|
||||
| **ffmpeg node** | **8+** | **16–32 GiB** | CPU-bound transcodes |
|
||||
|
||||
**Bandwidth rule of thumb (SFU):**
|
||||
|
||||
| Streams | Approx WAN need |
|
||||
|---------|-----------------|
|
||||
| 10 × 1 Mbps video | **~10–15 Mbps** + overhead |
|
||||
| 50 × 1 Mbps | **~50–80 Mbps** — approaches 200 Mbit shared with rest of estate |
|
||||
| 100+ viewers | **CDN/SFU PoP or second site** — not single 200 Mbit |
|
||||
|
||||
Stress test: measure on **intra** first, then **public DNS** through FE.
|
||||
|
||||
---
|
||||
|
||||
## 9. AI workflow after prerequisites are met
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["PO: empty HVM + SSH + DNS + firewall list"] --> B["AI: verify ssh sudo intra ping"]
|
||||
B --> C["AI: apk + docker + compose"]
|
||||
C --> D["AI: deploy SFU stack lab compose"]
|
||||
D --> E["AI: nginx FE fragment + BE sfu.enabled URLs"]
|
||||
E --> F["Smoke: sfu_health + WSS + ICE"]
|
||||
F --> G{"PO: load test OK?"}
|
||||
G -->|yes| H["Document + optional 2nd/3rd HVM split"]
|
||||
G -->|no| I["Tune UDP range CPU docker limits"]
|
||||
I --> F
|
||||
```
|
||||
|
||||
| Step | AI action | PO action |
|
||||
|------|-----------|-----------|
|
||||
| 0 | — | Snapshot empty VM |
|
||||
| 1 | Probe CPU/RAM/NIC, `apk update` | Confirm DNS/firewall |
|
||||
| 2 | Install docker, compose, pin versions | — |
|
||||
| 3 | Bring up mediasoup + coturn + signaling | — |
|
||||
| 4 | FE nginx `location /sfu/` or subdomain proxy | Reload FE nginx |
|
||||
| 5 | Enable `sfu.enabled` + URLs on monolith BE **only** | Sync config.php |
|
||||
| 6 | Run smoke + optional load/WebRTC client | Review metrics |
|
||||
| 7 | Snapshot “golden” media PoP | — |
|
||||
|
||||
---
|
||||
|
||||
## 10. Out of scope for first media PoP
|
||||
|
||||
- Replacing LAN UDP cast (alpha path unchanged)
|
||||
- Moving crashes/tickets/Gitea to media HVM
|
||||
- Full GEO policy ([SPEC §14](../docs/specs/20100612_1_scaling.md#14-global-availability-and-geo-policy)) — later with F3/F10
|
||||
- Production payments / recording storage
|
||||
- Merging with Janus stack on BE `:8089`
|
||||
|
||||
---
|
||||
|
||||
## 11. PO checklist (copy before handoff)
|
||||
|
||||
```text
|
||||
[ ] Alpine 3.x HVM provisioned (1–3); specs noted
|
||||
[ ] SSH + sudo for AI; key installed
|
||||
[ ] Intra IP 10.7.x.x stable
|
||||
[ ] External DNS name for test (or FE-only proxy agreed)
|
||||
[ ] Firewall: UDP 3478 + WebRTC range → media HVM
|
||||
[ ] Snapshot taken (empty baseline)
|
||||
[ ] Decision: SPIDERWEB (SFU) first — mediasoup default
|
||||
[ ] Crashes BE base URL + API token for room validation
|
||||
[ ] Confirm: do NOT use androidcast :8089 / monolith BE for SFU
|
||||
[ ] Optional: 2nd/3rd HVM reserved for TURN + ffmpeg
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. Changelog
|
||||
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-06-12 | Initial prerequisites — HVM vs Docker, 3-instance layout, BE API isolation, PO checklist |
|
||||
|
||||
---
|
||||
|
||||
**Documentation index:** [docs/README.md](../docs/README.md)
|
||||
@@ -28,6 +28,8 @@ SUBTITLE = {
|
||||
SUBTITLE_BY_REL = {
|
||||
"specs/20100611_3_url_shortener.md": "URL shortener service — SPEC (post-alpha, s.f0xx.org)",
|
||||
"DRs/20100611_3_url_shortener.md": "URL shortener service — design review",
|
||||
"specs/20100612_1_scaling.md": "Platform scaling — SPEC (clusters, bandwidth, GEO, cloud path)",
|
||||
"DRs/20100612_1_scaling.md": "Platform scaling — DR (architecture, bandwidth, GEO policy, task graph)",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -22,6 +22,11 @@ from reportlab.platypus import (
|
||||
)
|
||||
|
||||
from md_common import TOC_END, TOC_START, collect_headings, github_slug, strip_md_link_text
|
||||
from mermaid_render import (
|
||||
cached_mermaid_png,
|
||||
diagram_flowables,
|
||||
ensure_diagram_caption_style,
|
||||
)
|
||||
|
||||
|
||||
def _xml(text: str) -> str:
|
||||
@@ -187,7 +192,9 @@ def build_pdf_from_markdown(
|
||||
# Body — skip title, TOC block, index link
|
||||
skip_toc = False
|
||||
in_code = False
|
||||
code_lang = ""
|
||||
code_lines: list[str] = []
|
||||
ensure_diagram_caption_style(styles)
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
@@ -215,12 +222,29 @@ def build_pdf_from_markdown(
|
||||
|
||||
if line.strip().startswith("```"):
|
||||
if in_code:
|
||||
story.append(Preformatted("\n".join(code_lines), code_style))
|
||||
story.append(Spacer(1, 0.08 * cm))
|
||||
block = "\n".join(code_lines)
|
||||
if code_lang == "mermaid" and block.strip():
|
||||
try:
|
||||
png = cached_mermaid_png(block)
|
||||
story.extend(diagram_flowables(png, styles))
|
||||
except Exception as exc:
|
||||
story.append(
|
||||
Paragraph(
|
||||
f"<i>Diagram render failed ({_xml(str(exc))}) — source:</i>",
|
||||
small,
|
||||
)
|
||||
)
|
||||
story.append(Preformatted(block, code_style))
|
||||
story.append(Spacer(1, 0.08 * cm))
|
||||
else:
|
||||
story.append(Preformatted(block, code_style))
|
||||
story.append(Spacer(1, 0.08 * cm))
|
||||
code_lines = []
|
||||
code_lang = ""
|
||||
in_code = False
|
||||
else:
|
||||
in_code = True
|
||||
code_lang = line.strip()[3:].strip().lower()
|
||||
i += 1
|
||||
continue
|
||||
if in_code:
|
||||
|
||||
145
docs/_pdf_build/mermaid_render.py
Normal file
145
docs/_pdf_build/mermaid_render.py
Normal file
@@ -0,0 +1,145 @@
|
||||
"""Render Mermaid diagram sources to PNG for PDF embedding (mermaid-cli + Chromium)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from reportlab.lib.units import cm
|
||||
from reportlab.lib.utils import ImageReader
|
||||
from reportlab.platypus import Image, Paragraph, Spacer
|
||||
|
||||
BUILD_DIR = Path(__file__).resolve().parent
|
||||
CONFIG = BUILD_DIR / "mermaid-config.json"
|
||||
CACHE_DIR = BUILD_DIR / "mermaid_cache"
|
||||
MERMAID_CLI = ("npx", "--yes", "@mermaid-js/mermaid-cli@11.4.0")
|
||||
|
||||
MAX_DIAGRAM_WIDTH_CM = 15.8
|
||||
MAX_DIAGRAM_HEIGHT_CM = 17.0
|
||||
CAPTION_STYLE_NAME = "DiagramCaption"
|
||||
|
||||
|
||||
def _content_hash(content: str) -> str:
|
||||
return hashlib.sha256(content.strip().encode("utf-8")).hexdigest()[:16]
|
||||
|
||||
|
||||
def estimate_mermaid_pixels(content: str) -> tuple[int, int, int]:
|
||||
"""Return (width_px, height_px, scale) heuristics for flowchart layout."""
|
||||
lines = [ln for ln in content.splitlines() if ln.strip() and not ln.strip().startswith("%%")]
|
||||
n_edges = sum(1 for ln in lines if "-->" in ln or "---" in ln or "-.->" in ln)
|
||||
n_nodes = sum(
|
||||
1
|
||||
for ln in lines
|
||||
if "[" in ln or "(" in ln or "{" in ln or (ln.strip() and "-->" not in ln and "subgraph" not in ln.lower())
|
||||
)
|
||||
n_sub = sum(1 for ln in lines if "subgraph" in ln.lower())
|
||||
lr = any("flowchart LR" in ln or "graph LR" in ln for ln in lines)
|
||||
|
||||
scale = 2
|
||||
if lr:
|
||||
width = 2200 + min(n_nodes, 12) * 120
|
||||
height = 650 + n_sub * 180 + min(n_edges, 10) * 30
|
||||
else:
|
||||
width = 1700 + min(n_sub, 4) * 200
|
||||
height = 650 + n_edges * 70 + n_sub * 220 + min(n_nodes, 14) * 25
|
||||
|
||||
width = int(min(max(width, 1200), 3600))
|
||||
height = int(min(max(height, 550), 3400))
|
||||
return width, height, scale
|
||||
|
||||
|
||||
def render_mermaid_to_png(content: str, dest: Path, *, width: int, height: int, scale: int) -> None:
|
||||
if not CONFIG.exists():
|
||||
raise FileNotFoundError(f"mermaid config missing: {CONFIG}")
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
with tempfile.NamedTemporaryFile("w", suffix=".mmd", delete=False, encoding="utf-8") as tmp:
|
||||
tmp.write(content.strip() + "\n")
|
||||
mmd_path = Path(tmp.name)
|
||||
try:
|
||||
subprocess.run(
|
||||
[
|
||||
*MERMAID_CLI,
|
||||
"-i",
|
||||
str(mmd_path),
|
||||
"-o",
|
||||
str(dest),
|
||||
"-c",
|
||||
str(CONFIG),
|
||||
"-b",
|
||||
"white",
|
||||
"-w",
|
||||
str(width),
|
||||
"-H",
|
||||
str(height),
|
||||
"-s",
|
||||
str(scale),
|
||||
],
|
||||
cwd=str(BUILD_DIR.parents[1]),
|
||||
check=True,
|
||||
timeout=180,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
except subprocess.CalledProcessError as exc:
|
||||
msg = (exc.stderr or exc.stdout or str(exc)).strip()
|
||||
raise RuntimeError(f"mermaid-cli failed: {msg}") from exc
|
||||
finally:
|
||||
mmd_path.unlink(missing_ok=True)
|
||||
if not dest.is_file() or dest.stat().st_size == 0:
|
||||
raise RuntimeError(f"mermaid-cli produced no PNG: {dest}")
|
||||
|
||||
|
||||
def cached_mermaid_png(content: str) -> Path:
|
||||
"""Render or reuse cached PNG for Mermaid source."""
|
||||
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
key = _content_hash(content)
|
||||
png = CACHE_DIR / f"{key}.png"
|
||||
if png.is_file() and png.stat().st_size > 0:
|
||||
return png
|
||||
w, h, scale = estimate_mermaid_pixels(content)
|
||||
render_mermaid_to_png(content, png, width=w, height=h, scale=scale)
|
||||
return png
|
||||
|
||||
|
||||
def diagram_flowables(
|
||||
png_path: Path,
|
||||
styles,
|
||||
*,
|
||||
caption: str = "",
|
||||
max_width_cm: float = MAX_DIAGRAM_WIDTH_CM,
|
||||
max_height_cm: float = MAX_DIAGRAM_HEIGHT_CM,
|
||||
) -> list:
|
||||
"""ReportLab flowables: image (+ optional caption) sized to fit A4 body."""
|
||||
reader = ImageReader(str(png_path))
|
||||
w_px, h_px = reader.getSize()
|
||||
max_w = max_width_cm * cm
|
||||
max_h = max_height_cm * cm
|
||||
scale = min(max_w / w_px, max_h / h_px)
|
||||
img = Image(str(png_path), width=w_px * scale, height=h_px * scale)
|
||||
img.hAlign = "CENTER"
|
||||
out: list = [Spacer(1, 0.12 * cm), img, Spacer(1, 0.06 * cm)]
|
||||
if caption:
|
||||
cap = styles[CAPTION_STYLE_NAME] if CAPTION_STYLE_NAME in styles else styles["Normal"]
|
||||
out.insert(0, Spacer(1, 0.06 * cm))
|
||||
out.append(Paragraph(f"<i>{caption}</i>", cap))
|
||||
out.append(Spacer(1, 0.1 * cm))
|
||||
return out
|
||||
|
||||
|
||||
def ensure_diagram_caption_style(styles) -> None:
|
||||
from reportlab.lib.styles import ParagraphStyle
|
||||
from reportlab.lib import colors
|
||||
|
||||
if CAPTION_STYLE_NAME not in styles:
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
CAPTION_STYLE_NAME,
|
||||
parent=styles["Normal"],
|
||||
fontSize=7,
|
||||
textColor=colors.grey,
|
||||
alignment=1,
|
||||
spaceAfter=4,
|
||||
)
|
||||
)
|
||||
@@ -1,4 +1,5 @@
|
||||
reportlab>=4.0
|
||||
|
||||
# Diagrams: npx @mermaid-js/mermaid-cli (uses bundled Chromium).
|
||||
# Diagrams in markdown (```mermaid blocks): npx @mermaid-js/mermaid-cli (uses bundled Chromium).
|
||||
# Do NOT use rsvg-convert on Mermaid SVG — HTML labels are dropped.
|
||||
# Example: npx --yes @mermaid-js/mermaid-cli@11.4.0 --version
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Draft — source for SPEC/DR (rev. 2 — 2026-06-12)
|
||||
# SPEC: docs/specs/20100612_1_scaling.md
|
||||
# DR: docs/DRs/20100612_1_scaling.md
|
||||
#
|
||||
The problem:
|
||||
Now we have a small VM instance which serves not so much of things. A short list of services include:
|
||||
- own web service (nginx+php) for UI/UX and some control panels, serving issues, tickets, graphs, build control (orchestration)
|
||||
@@ -32,6 +36,17 @@ Ask AI to collect the information, make its decision based on all that we curren
|
||||
Also, the matter of seamless scaling is super important, better to have the load balancing ready to use just from cloud provider.
|
||||
Also, let AI think of how many simulatltaneous connections can we have per instance - the question is for RSSH implementation and future improvements
|
||||
|
||||
Network bandwidth (missing in rev.1 — add to SPEC/DR):
|
||||
- we scale not only by CPU/RAM/connections but by **network throughput**: ingress/egress on FE WAN, FE→BE backhaul, per-VM NIC, CDN/object-store egress, SFU/ffmpeg media pipes, OTA/APK download storms, WireGuard UDP
|
||||
- need **budget metrics** per service (Mbps sustained, peak, GB/month egress) and alerts before link saturation
|
||||
- single FE uplink + single BE path is a hidden bottleneck today; cloud/hybrid phase must model **egress cost** (OTA, recordings, transcodes)
|
||||
|
||||
Global availability and GEO (missing in rev.1 — add to SPEC/DR):
|
||||
- long-term: **the world can reach our services** (consoles, APIs, OTA, cast/signaling, short links, future commercial VPN) with acceptable latency — multi-region or CDN edge as we grow
|
||||
- simultaneously: **segment isolation by policy** — not all content or features in all countries; example: certain recordings, OTA builds, or published cast archives must **not** be offered in Zambia (ZM) or other denylisted regions unless PO overrides
|
||||
- enforcement should be layered: DNS/Geo routing, CDN/WAF geo rules, app/BE **geo_policy** on content objects, tenant RBAC — default allow globally, explicit **deny segments** per resource or catalog
|
||||
- dev/staging may stay single-region; prod gains GEO rules when F3 cast storage / F5 payments / F9 streaming go live
|
||||
|
||||
The future key role requirements:
|
||||
- multipoint casting and routing outside the intra network, usage of 3rdparty or own multipoint solutions on the backends(MCU, SFU...)
|
||||
- new RBAC control, new roles - now we're thinking as developers and deliver the max of services to developers, but we will migrate to the other model sooner or later
|
||||
|
||||
@@ -157,7 +157,7 @@ endobj
|
||||
endobj
|
||||
32 0 obj
|
||||
<<
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 45 0 R /Fit ] /Rect [ 45.68504 419.2874 129.269 430.2874 ] /Subtype /Link /Type /Annot
|
||||
/Border [ 0 0 0 ] /Contents () /Dest [ 46 0 R /Fit ] /Rect [ 45.68504 419.2874 129.269 430.2874 ] /Subtype /Link /Type /Annot
|
||||
>>
|
||||
endobj
|
||||
33 0 obj
|
||||
@@ -275,7 +275,7 @@ endobj
|
||||
endobj
|
||||
48 0 obj
|
||||
<<
|
||||
/Author (Android Cast project) /CreationDate (D:20260611185331+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260611185331+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Author (Android Cast project) /CreationDate (D:20260612190939+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260612190939+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (URL shortener service \204 specification) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
@@ -429,7 +429,7 @@ endobj
|
||||
endobj
|
||||
78 0 obj
|
||||
<<
|
||||
/Dest [ 45 0 R /Fit ] /Next 79 0 R /Parent 49 0 R /Prev 77 0 R /Title (18. Routing \(normative\))
|
||||
/Dest [ 46 0 R /Fit ] /Next 79 0 R /Parent 49 0 R /Prev 77 0 R /Title (18. Routing \(normative\))
|
||||
>>
|
||||
endobj
|
||||
79 0 obj
|
||||
@@ -456,10 +456,10 @@ Gatn'?#SIU'Sc)J/'_]eX;jIs\N=_LLB/:P7d=7ZK9d@4ZQuObjX1#A8%+ni6(61%+kQuM9$b[Jnsl%W
|
||||
endobj
|
||||
83 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 3526
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 3529
|
||||
>>
|
||||
stream
|
||||
Gau0F=``=W&q801ka/DL-8rj?E,$JbfTTjs)*=#D9iWWuKHqlY_#[+L]=%Ze)a#C'i[JM58JDM>GGEPNIpW9baS8W<J))-\LBUA$=$TVkYm*X`FBI>7mGcCJDj6uoDFgXAa?!G3/K("Pj*TF;0.M<OWarnS#Dt(Hmnf_c[Laj"+23<J%<Ku=%229=,'rnt(-aRf=g&.U+)X;T^FamJb.lo5aT0MMjDtctZ]r'94AOJLM11MrnMG$=VlHi>ZDf80[I5OT^;jHt',jb$--s[5)boW5:85#.$%^PqT0>+OGr+cjE9@Zad'-j8`.TCMHt(b2Mj$dH6sB`98P#He5Yk8ffh]f#mig[Sj*pP#47WICemBfoj<n1,E!n:TLl5!VJ<;#;lfS$BS^U(YbW$KU=6fVGDLcoT4PtDpkpl1.fV5]I$n.]E6h\/eOsn&5:1Iui5<hf"D[+jk)WuHZ7O&+75[5cSBqKZ$ir5aI2oY!mgdb`MS`ODB-fpgerbH5aA>pL0nI,5k\h]M%SgaOk-)&AImuf[+*c^n+\k/e;bI7R9V\lU>&h[=j=l>kGXO0U<=K85u%^?R'\W:EKrUAo:"N0+gj@_.:(CV19.]K^kM.&5bO<$p!A>Q&R=BpU;d6WsI=ggCQaHCD/m4(fYhKPpo>#BsR<OHqUGcl?rOl.DE&W:A<02,Ye<MLS\W,f1=3ICB]>+_U,%PN?e0(Z"HJ'ICj?m0c:LETfm\M`CYY]'[Ni>\6(&hLI*@`fd@9L=1:Ub7*hKBRCh#rsQiMS1j(n?(%N6,?.0G`40>3a3"6I)P-.D5JkC=[4W4#>JH^5)<M47nJ(/lSM0nKcOCn!EufUpok7\;nRL9EN*+Q1>KGa76<0S\1=?Q\Al229uKU$":+i=8cu`0Bp,q!Id,P*Q*khOIf4ci2quLUGNp<Wm:q/4<NSr<O1P12K2Hb#DVtn=ZCI$G"[4RT&&R/kiQ/^L2k9l<qODlq0C`BfUJ$KP/Y'f2ke1fd0fk'a$!44Z*2P#%cUjM(Gt\sMP8'c;9Q4K8BUgI%P7T8<93>&]GRb%trTSFUc=8Eq=\+/&j]%+f.*[<8ERmt"#E-RWo'MP/bKBVoV]Dt?7hYT'2Y_s'0eQFle*IYUitUN;'T:6_mM68UjpV;Ur-=F,.8JoZKjN`J5N_]i%^=f/Pk/N`Qf&VoMkq2%qg:U2R/SHCd`m8GEq>Y,Rmf;k0nqH<6c&h5#Q<_%I%56i],1`J>Hp]V9]hoe1nR7W&/:m^pGWM%hdPU6O5W9Hd8@#HR"?<NB\cW;3[EBZ2+%I;F%k8m6*^pLd1m7*+hm(EiuCWj<"gl=1PcM12\[])72L?NTo5.le"QbH]OVqoEXh[M\XLtt(L+o&`I;*51R>!%Of>9,'N"s:mTdZk:7hi3Z69&X@U?qI`)(e[3`)9FWKJ^"kGYmd'<rj-W2[oPl_JrZVDUWN;Upe4.J*mX""Lb.90_3M<QL:VQhXu?:km.b]Y3B^^U^"2&"g2`1W1Hs9tJUu)TR8p`"Z5Q6\NTd'kq(>@l;JJk6jO'$h'_g[4'$F\:=O?`[gtCHTg,WMB,!<66u+?K'!PS)!Ti_Va66.Q$3f!rqMH0?O?JKbm0%<K_YLu)R&QRo#./fQq6^u)*J076Y+.ADs,.ZW-2)\'#L1p"^*^Q9DOU(*/G2XGpl>E1"js#-<a/b^i0;[-AM_9C!,oGh[(g-]^>f!\Gmg..u?<>ZeXMpCK^EoH5ZK>?XOS1p*8OI:r"I;"p(ZDE-&Q%0P8C&`uFP1o`T_6$[-6GppflR#dq5jG-$F:1Ct6(*rc)%c)JLc3T.`t5%+*(lMI%!M>>E2d"*(A?m,TlQ<jG"r;5N6A?2#6-rBr8I<pMgjf-JK]0fW9VZ>j^K3\@\55JDGoW.Ut(5%IWdXY,)""*J/"$VtZR'J4Opf'.Z>-9^?;qZ[sPm7.b2IMQI1uM<hTW1aN$c;-+6XfVr2VaS>0su1.Y&!aF6Ot2T-u^;<:2ti$]'=aF(M(BuJg$+)rB@:REQU8\3r!JV9gs>'&7;5Tg"IZZc>-()R;gqT+f*VG/+>[@4_KBT']TK9-`(c<gN2,GW`urPipR#o+,!F4c&d%tBs1:qZF93U&t^?gRH<d3Uq4(n(sR*TFP-hGYY/Pfpu,>E!ad\ToqGk0BD/!p#7W1q,Bc2$Q\nXoMWg+Ppp(uT.uJb0A>e<q]+NilaL1(U@ufX8k:d"?T,FSBVs7SBXr'O3dP2ZT)3X;TMlj(:Z]V_kO3U9PA\_t9X`]o<+f5UB0<kA$h";_&^nU13CL8O4JQ#5e,#\AggU)iA#tMo`n7l"0'DR'70^gAPll$P5!+!rA*Q!Mj?(qgJ8L\gBL>:hf'&t4>a_E4]=7"$oh&"7+%;h]d]ITP$EAeT;%Ub$DO@RcoRF^gGV_NDh7>q'7WpU0Z-#DhV2RJBg[u>+pFXi2<XkS34T?f5Ed?,IZ?!l*s&<Iq'k2bip`Uq(GApPJSE`f5$mEkoXV@VVU9bj==Z;T1),EclH`goU7_%W`I*C>aY]n3&221DS\qF=a^D&o`^+)M&;lU0<\E=l':`H?h&IeblLD8hV.6]!EZmu/<Z3InIqm2AJfj(*Oal?P-Vi\X)ChQ*0((8^;?q,>A?fD_>%oMG@b(jo<VXSeXrfIm8&8;4^naf11P2(run\5"!XX'#>'/#<`:N5aeZk-HR/kUp>7n<X23e)q"#meci18Wr#(Eo#[Z$Cm5:&PcL->SP:90&.1;lM14;4b/'CQ7<78jakW4YN<f(*/4bt+c^[J<A;&6`onVkbSeX$7jO(^\>"Mn#E8UTUQ_8:dk/G5g,X[`3sQV.)JW_==fmTT(rQa@(^KlQ!.#5eU\::^S-7?jQL:dkP<9JI.k/@D!J6V:ag,Uj.g6nR2V/#1Jcr3T2Ao\PW[l+prD5flPbm*0*/Y$IOj20;PC97J53T83N3a"hd>sOU#9DMqTgCgT.b(4[KrT;s(J6WnQ5<NRQ5!83^D<KpqS'ib<*"_KQDHnEp03\_-Wd&Q,."`.J$buCF0sEBA7&+@RgJ7n:MDBOs1o)`9DoU4d*tN,a:)I1AeQW["K(cI&k=m'BT=&).Fl6;`Es1`M>C;E(>R4T?H<c6ML"3X!p'Fah2(l4-)Lbi)r[:fr,B<u)7!Vj.dm,[bfS+^?Nmn#"";rLWRn-4-^]e124dg0T9/-c$DtaaBcKp2@(lJ9J2A@`SiY0CgS4>ogL1H8r!rKdJW_)E!?QXjgN6GV`qc;.>7X>m+Bj5\?QRqifEXf0h!\B7j*QaQWP:OlE;_N+BU#p3Dr>n\=-\9sZ,h:m;sPZC-[u"E_uNB7HU(4im;\]2"**SjE^aGgTm+Asq#r<H^8U"gib$d6<U^8->np1<_na[6?SaU?LqtWF`?kB>SE'qlRE@#[bGaH__R<LXi[BuWB^PO:D+B`u7lK,*WE=oc,EDkI$*LhNob6VJ]64;%,`IAYIlq\](*#8!rq/N9BaJ^S]G%,oRDUJc4?(QhZ&&b!&Cn`ACph%OGC$@khY[n.8Q.h~>endstream
|
||||
Gau0F=``=W&q801ka/EW-T8s@E,$JbfTTjs)*=#D9iWg%KHqlY_#[+L]=%Ze)a#C'i[JM58JDM>GGEPNIpW9baS8W<J))]la#QhB=$TVk^&g`iFI:t%mJPetn!'QEDFgXAa?!G3/K("Pj*TF;0.K%dWarnm$]6LLmnf_c[Laj"+23K9)s4#%)CCQY7eGsck!(W7=g'WK4ktM/I[g?aP^g_LP6$t$aep%2@juUNGatju,Q/[)j%7f9:K1JZAQ)GI[E"L:O7@tpKSG^aa\+D)7al[9PA'<d6f@UYVpEUWhFp!^gj0&21orVq0d0+,+'J<QioH7*d>#Hr/>Op*TI21Dm&.hu#Dfn9\=^FNQFoPsnQ-#ZqoK%+hL\BEiaU@^?t>)h\p:l:BCq#f1QD\/NI$&b*M?[o:NtY_\dWs&2X%4e",^a@O?INF,`8cPP?;#10/!sulN+Gn`p'(_dYL1'&=88Cg1T9*\3F]k9ecDrGL9KA-Vg"4k+r*tQrs5W.Ds6]93][WWaQs_\ne=H74IGadN]D<HCXf&9B,"eip6nmP>7#Ok6%6Q(iNLs/m5;Yi#TA3d9dPhW2<RTYn2)%+49TSiTlU3L;/qnQX7cl(buM";J"Ys_eAH\4-F3t8k0,kc4%C]K-ja,Cj0u_-/192<LBYslVR2\U'Oi$C?]F%>kPHJ_*c?i(=R_>R(CkK7Q[,;j_2bVp>gS-5"XEI?2tF?k"uZgRbl$!6La1SA$S:>/&i`?i>N?Gf//=1Qu=CkPX"u2O8Khc?2TE]dIDMqE`fUZgM.G]>G3ErJs1lX(milt^)GoC/r+7MARWdaH;Q`ebf/duf]@RH`##XMPE]"uZDEU41u8dk=9\%O?CYM1WYU6rGB$1$cH?r?ctGYH0e[Y_0iC6P!C)@f!:O]lV!Yg%HF@g)m2W@b!5A6oc"2EpG*(C#2*d_4<[PFK?%'K(W4E0ln-)KgS<<:U^<GaN_VH<l^qITDSGo)US9jBPnR!3098^,/SAeOm]klCW3emD9=00smV+2KN;4+A/9GOFg'VcpII$Z*8cL^C"L_]e5/1F=VLP4Xc<"CtI!YJq!cU&6IYK'ND>%nIGMR/'&4daRa(?\')0.WqrS9DE>Sk.ddg#*O8`jeiN::!GIN)P]4:b4Yo\/R7YRkk?JnE36KVDsP[(4(\prg;OA3P]sY;,3`H?%Q9[/='J0mIU9i@.KpL8D!lY8^h0IC_ktH`Yci8%LE5r*V>Q1p2SAHls^eoDo<]J1RWT5d!Q`L=\t(.h;9p1?&+QS5DK'Z77Xah@1&TVU30D6k`B=ZeI2m4bqWeQ#0AW"7or*GL%q)]N,p7E;,+R=bTJg^hb^-D'OC4)L+Z@N:5A[inWIVYau.Z^jD%Gn>\k92(]/?sbm:)58#E"%Ysf+<\T84H4+/Hkab'"tLj9<i'1;.Yks/s_VFm_%Stb4.9uN@QTr';4XHW>YRaqK+8l[t$VU-QT%B.oS0E#k!<?YYb>iLOD5T)1%n`GuZ'^('24ab@lcObkj2sEA5CE3(_&lVm:$kXt-<MY2>O4>qtS]q2//A-`>e=me`-j0)PBtV;Y\C\&=Wn7r+-q#8_+_^:?L6:>`E#Q*>=a:N$mZ0$rrJbcggjFc76&=D_W:B.:+_CS.M2;A=R*fF50LV4cn91h5JSI.*+X,Vi;ik"s\Wh#haFP0?R"qs]*g81XB;3@J%N$eu_;p]n/57!Gi&b@9Pa=XC!r&;jAgL;eE0`T`E%Q-\9.pBslO/XqdXEH(&.81Xg`QT>%W3oN!lbWdl?FP&Vk1]mBYcS9&g(3S)PL3``/`C;I0tS#^r)e:ZT/bf2?u@3fa#aX=FK(I3\e8B(HO7&(+_l3fWqRXJU`0^PAJdt%0c%Se'IIoUcb!SH+VBpp_:LpCQPU3b6L8TqgOeOc!iB3<uUS-Cr#+;Bu$XP!E40<!M@L=9Zr\*Hn$*)WR?<Hap-(pPm2WY)cYA0B0dBJHROjPhM/Wt3MtA&bB;(dFm,O:%0[50bUG0+F]Slgq2.jj751O"V;Go35>E*:=9j=%W2Z`f$0$l-/mEZJb^"%s0GBJ9.>3q3E<VrS\EBr5JRUb#ihR9+VE8\-:j)(^:Ah5Eq1J19Xr3D*3pkH(?.O0>7cI@B,L`/'MGtDd)l$P2RkD=G%sFqr&O.7M;rAUl^0WXtilphuoUoX3CrD>=n5;!@:_EX\&7>,5D+1^*OR2V/Cc?t#*k@TlAB]U0r=ik:oeQ\Mm8VmL`YDuS<]WY/2a_WABX*g;EAI)co[+OjWXK`(VR<St2T/RGoECQ\J@jYO%9Si8F6tG'3H0ji6q"'4D4=R'/)c:d"^]GGFoq\>&E`qeqd&4j.6n^A;29+a6^O_R>Z-G"NZiUk^Voo'CU+jNbnW;q;"qrgXG/WoC>P6$^,RHt=9gOt6-,g8d8]H+NRO7VqkD(.GuaTlTG"^*;B+=Y]j&K6qDo?ZUt4o8gs9RLQfpu$L9<1E?jOjIH1UCWfD4Lflt18YM/l^r471DqGH)23nkBjlDR*0i/gHNqY/VJV=gb$(NmV%j[i2#k[&):'dAlle((e2DKXDX5&Z&W82[Jjgk;d6,L7B>XFRaWho^L'*UqLKo3*7(hj4dGWUIc)r.>%.tj]&T8<PQkP6C,=YmsLr.b%J]OK<f.OpIrA?$&Cntj:tEb?%f2&OgDrJgA"sml??M_Fa.H@kBAeU>sUfd&`\PZ(6Fru7$r'\e5o5)"TO_P%c:4CN3nF\b3J/a8=jh[Y'<jbf8&\n"E_"M;6V\aA0uRR5GmEo9TTU$X<XZqfpt#j(MTbl?nOSdBhZK_"Z/DNlt74Wm<"t=&MDVZ!Nb!8^l[)C'<B5i?!P5h:)M#2a:AL%M\mD@pN5LFTUn)a=3W_J,I+eB1;"/@BM"I?S*$qQ^qO<"/YFRad$e^5TiJ&&Elcf*,m6M:W_LXMLu99*m-HU_#E%l]*p^U$CHia95`*5,TcFgs[!eW74Xa:.C1h[_F@0hh6'Z:Y0pVHJ!K+#J3h/HH<L@W*)ROFt-(/mr2<l;>CKpa-48LBr48hkHmDQ5sE5)"0`\RQ9]PtO5?U5&oD=iD'!*3bkOhsk$3_L];FAmtW\5=>H*DB<-'g\=fJ(88LrN+t3/?pdp-XJ9!*[&ghR,:nQg-`.Xc5FD.a%MsIqh;-2Sfc2sh)tI4?u%ZN_Msn.SU,a:WE<Ec=U+[.8UddL6<oksnH90LlRrE4ZN9Elj8?PA9!>o[VXVguJH$92km8HiaX=;aQh>$^m0g/kTQCL7jnJT4U"taB7g-bYbUONl0tO:^me#Q4Vu38df$_9`kJ^_,rftMJZN3b%O/O)poreo/,g#HT/HiK%Fo!/tV5qccM$8Xj$8:QYYXJ;]L7a?m=d(e\,><2Z5[b*(8%X5pfVftuZXaf;kkfFa"'f@dDZD>`N^F0.>4IKF8@G;$hh/9V8N%RACqliVm5p7h#1+[+k3g5bW%7;i%60P*p:C7n_!+I1g][H)"6$%%]Tr%_PR_:8VsO!035<H[,4Iou(Or[Nmb6Rg[/tUI5?<J>P5~>endstream
|
||||
endobj
|
||||
84 0 obj
|
||||
<<
|
||||
@@ -484,17 +484,17 @@ Gb"/)CNH71(B'h3EBh$I3Z.C1VKT#g8NQ7l`(<&9c>JoJ;T-X7'+>"_\ij9phtM^3#0]FYajeg@=1T'_
|
||||
endobj
|
||||
87 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2854
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2847
|
||||
>>
|
||||
stream
|
||||
Gb!#^=``=W&q9SYkXT:*joO<p:JN*04<#Xh8XUr-V'$D6Qr'R_+FWU[hZcMIUb#iXGAGBe-k':-&<6\'DHI,QJ+WYWIfo1h%dLG33e@)>FAjo&rY['SZ[^9m6IN4TgU/jO*61o5__^h0\;XeE.p]^ro*l6T^T:b*r-=^6C;<niXqLt&Z>GggDciODKD?%Fs!#_cKg0o_[Yk?3$Z*__=].)C(mY2[iRs[[K&TnN1NcZE(<`m!(Ad5D6mjltl3;23b?n)Dr$c4([3IG!Y0_j>cgk;/f;mT2:JgBr]m:^$-A((bdVHG-HB5hB*kdQB=#j4Dc#\\kPG\t;O['-MB<d_57PqH,aQ0<WGZ)BR:!,ArGsVq6fU@5OikK2lYli8DDp-VV&IE&m^HoPNE0`(68WZ@L[PV8#LhVqWZYj5<rLYVNe;Qna@\*"Q`Jh901;ItO"u0\Y,X$Nl8";4i^JsFaC.$tS^,2q.oJ]cUC/7Vt_H/4H".k00($YHE:OXu^4VCcVgc=/^8)XQ^J(G:7CQ4>IH@d6Frb+sL_kU47lDkLi"cIg.]M2Dj]df6rro<N7'#XC<1J3F]NUm#NK#HhDdU&$98-`jIjJ;W;\bc*rW()V>UNIbYFe,6iU!i=/W']m-`U_$g'!K1IWBUealVbA_Yli6:%U3[c5ZtD9rE^os%_#Z.?BfW&Z3tj/aBW;ul)GEr$o"3VJ_tme]=bkim14WXl5ADcbt=-)W.6.!1Cfd-Eq^^8&?l.qC!)$u8@X=$T`I8Rc=rf+NZ\SWm"QmsP_\nkqu._[0lg/VOS[peSG?s/Thl`@,(n3]j1Z$QUuTj1'%99+TTF>g\"efVFiua=EG,2[)_qGg`17:o=Y*qoY&HNtiX6$%5KX%laYO[S`7u^qB6#ehGki`dq/N:A`%`ha%R6k@S1,49<jZ\LV[`2OJm#g`eJGU4IqppXRRi>\8aGY7pfK*kGH3CUA%@8WXgWgWjT<l9g&aI'$VnSABC@SgY1`q@PmbjpCG%P/XrJe,g[2V[<'?JNrcO;K'd^<u95tq75MF-]Lo$2"i:'k,<acd.Rf(sgG.2`T^*G.*]mm>LZ_5/]GDAga9JdZ)f2U=S#9&:^Sm*=b*cBHEo2H""mgJhp0`DX[9IO@uW&0F]0S2Uk63/W%/V2>r?>>$(`AH:UK]Qq0qUjZD:gh<@N/-Y=Ds(T@p>/JMj.^8$FPV(7T^.WkX4PouBJ5>DG)2[:%dDg::iC^>%\X8i-=Di[dN?9n7Ot'B08fYrk/+An6T"BpP"D>;TSamTDS-:/'ZiS7B38*;7aO\lY[E\OY)72KNK7amUq&e(HbA'H;8slDhI_ZooT;pn1!i[iGk=/DVUtd<RsJiRC1A[\O[/r"JrN#fq+C("RN0m\SN)KRIoQ08M7@dRWjPEa&_(?.4IMM'MsGPYjpas4(4(;Z`^p*daU(I]Ue#]=9_na5eK9nJ-Q%po04acdf'j6=P!DPN)*T[E+m#,RjEGe\\jFk9\"B:V'j=fN(_r!1rImBWNK95;/@-a>T&\^>:pK*R1:sbeXD)<TUDqJhK9"#d[9qFaS;sDe:DR:iW5*8\]"@bYBWqeQpiZb-OF3E%+W8G++T9qa^Q.8f9k(,:!uYu&fYVTCM@fgRPgnAb;R8Y\c"[9A]s`Ge'd[].@Um`>8V7P;4Wi77-LpNdWeK',>h*pj"cJ5b9$p>Q;!^NAFoQZ][Gm,kHc:"JkpMJKe*Y\@S@'O^JkTuC8U5X[0q#pB+V&miihu%bZIK:nn69?%^GHs*E=LB7pYKj>J[#X4<8d(/dU'Kj,G^_mirGfm'6_mLP&VCBTD*MU.&Ylc.^r0@PlJKR6Yl==pp5A6!sV>l'n9aeRRN"3LQb)HoVg`75/F_mB8B[&)DZOYDh2T!qkg#=>o$;G)XR'?'*X/:/$,=7\;Z.gc$8VX<4.ncWTthiYXS?DU%V\7o2QQZZ@tP"Z69,_5ha[0T[*]-c[DC+E29bXYi+_IHT2GgNDYTsE6Mj::RPs4XN&2u,6'/Y[<lLML@$B)iajVa)&>+5Ktq@a]k&W'ICW^Z_TD\!^K$43r$73L$S\3E<9rcT\3(!V]P`ks?U![m`tReDeh#"b`Oe\d'$h_/CLFo.WRafZ$plFpZ!_>r0P=\I8;XDNhr!9]+0t1F#hZYX*BJ7%%\(_dmFQsNOC#C&P4Z;[rX?GV3N>(/l&c$(*$nCl5_1I-;SL8X]?Nh6ro1NXkf'Wlb4L%+djGZ%SF?uI_LJnM*!\-)Nf#bP?12Ft2!Y.];a=s'_,fcZd&[1M>29pATaGMB]O`OI_/G!>NLBEH1`LmZ<-9-W(c/rSF6(!Gf^d'?Md^gDaBj)Fdb=A:K:q'1JpRGc`uAHXhMlYu!9MlSh&!>PU(a^N$jk<2J[(#>@saN<EpbCSA;`-crh+Jj`U@YYpohj&Z0]FJer%9;]&UTh0`D@a\Z+q)kqY^$kB#4Fes1[>ZYD4eAH,"DdU\Qn+p%[(&X]e\B/re&YQ&tHf^Z^3r[U*Ac`&06heiW8FNi>@HkWEZ\0o7:/]*%3&+We"j@nfF\Fg\"I%&CM@#=eN?@F'..7tL06S=DJfb]\6$Lt3@Jtj8k,qq5_*<i;kb-0shmop4iL7!is)*@)jjYIXiYCO`W$W<(AO?F)XXIHUO:U5?fA>YQkO1,d6'C=Ke5$]e)."GZ_E]_&oc$8VK\bU8/]rHIiSs1X3*=<Hq5?MhVaTUAhjCs.^SfqHP=$td/0[U/l@pSsqRnWjrq(S6(?XRk$MtDtNl1+`JG.5N-PK+gamh5FS?fg,9/+2ZP?0,#AB^G/[;iY#-mtB^mG&;G_+SPqIO.D3L0$3ZNI=Ij[\FeElVSUr8!,G9rir~>endstream
|
||||
Gb!#^gN)%.&q0LUoV43PP6?-\b`e73'@4[T7oJKk<@N`W,UO5)+\5u6laK0B'IiW'HagF_Nm+0L(SK1=1ggA'IRs19]*3Go!;2c\Ts.W"0ba^*T4,;jqr`q!P(or<Ni<n(gU<j@'*I.0@=43%\d3l+(+>G$X4K1T?=flM`+q=[<.U*X(IZSR*[^>`nH0QA82GbSk?uGQci>f:U6CH\7<>DFlCd>S9=#X8Cm.U&Jr7l@q3k`bNt[3[C;D4D+m-$*"a.s`+et5J)'r3p_!uX=(3(gB@<;sI-e8T]*PL'7>AH"MUXsI2p$1ZGrhB@oH,t#^Jt8_LI??cU:;=MMas&(qfgB?APStlH_>-gB\#jpBFfJQ4Fctb!=ci2!c.0;bDWln0C'+m-WkitlgdYTgE7hF+1E`P\-B+s]-$+S,cr8CQ\.kD\:3ak?<_I*mZc>Nij4btBT!npm-VeH&R-^k@meNP9XSq3=<F'<7:J)>)<>Kn#jEiKmanK'\rSVSA&6S2R3S^53r^5r(\L?V;ra!PY39Jj2\=V^3]RL+mbqBg?9n:A"<t0BuGj*/n]0qqSW'Vr2UcDn?6F`V17VU>V;8/ZS(V+N"[9)TJdQuT&M5ibD=FGRE<.i8'_'TeO\k18?:t]_6<2"KR@SXq_'!D3&WBUftF2.*`pIMWW@@<Q%:fdMbYZ?VerS<J](M,e]A=*5"QHRE#B'W2ce3P1i=mnQ1?!cFEp4kh<opfm(O@<"H9L+hYGRTrpEqLQ4_;h-UZ@mRO&UssM`&r9TEQl9fF9G0pZ(BaVfOl;`pBO9Z(e]!=P2d!3G;jom+Q%?f#+sqmI32Eoak0Fe+^5-QMJMC(CJ<6]4!(^2=2p5C4r7$2`s[^P:r(Xhj1$1b(Z:'dOEr==AL\CNGt@"`bJe;enk]2ZEMb_F2G=1mhm5Pm'GDN\Y-pK4$2GFR4<)_K"'Y*,[>J?\Ud^F,HIX0d/;K>6<JU(K(d]9F)8W4[!4c9E!2#,L]B4ZmE7q0-0b-+'HW::>W>Ilm<>iTMr;]8)S?9E1<'D#$rcOGO,u;"KV:[]J[W\tWYnWW7*,c3i.a0*2amVBZGNsP899^S!q^;&HmZk<Clu?`nM8k/'L<5nR^]NE:EXRO>qn(p65NMtEqiZFF4FZd*/ekQN<gnc"+$_FY^&Xbl(/JZ#09neF`R*?e_K8='5.704'ps&$NW%qK\",7XE@T6*FYfK;bMaK=":fAEG=`Q1U$'cM]mAol4VI#IOa;?aB*_eAFTr4)OmW3u2d5`&I8SdTG9"%K)PXb],FJZ]2l[_3j2RQR8<Ng+G_QY@)[).6"\[-M*im.bGN5GUUh?`r)oTA/X.A?`9G6Sq\dWs&q/dDT*T-b=B@m*R)l0ZMHGNG7,D#h#3?*O&`G<$D8T\3I$+1LBIT5L/M7@dRWjPD6&QE9mgmP%%M%cpco9La4$U$Rh@jM*CjFkh2q*qq/@a]V5WpYDU5J_kDGp0L9ZR5C..1(YQOlGEl)3QroP5O`l6<@i2FSkko>,/LuJFeE@Wk\$.V"$aXL/f,pomQbM$n@L3?=juhM@Jk[CimSj@F,c-XnI[!Q-k^>qT6Xu,nmd>=*HU<"7rsM#iBMB'p9)K+XBGN'r2pul.7XpX\MPID'6_J"^;g]71nO/Q'0i+8m5`Vp43l]2+h[*(bqb/Q'+lPPdJGcWE#YDF`uTc<HG?J\Z0)-$5O;gQ(f.^Tieijlh^'ACWRG%p57rsdSCXoVq\6_)/DZ'"I[oPPHsiUA%I2;8o=>__f*GaVs?4_i*0Xj^GIL934`P+H]Jh;#`N>^L&t_KP4l2X+!Af2(4$Pe>JHqF(5u2$[#3Z\b%W^S$.g#a8P``5s0'Yn*-0P*,B0::X:I9])QicZ?a2FI<]urm)P`?3DV5\NRsg$AL[-+]dGlfTc#DMRe>:>48CrKB*cE7c0Q2Zq1C9DaYbW/?*K-pu:d9(9V(q<J^A"T)QH4H=HG42=d#9N3rrnLUB]-..!VbbpS8iMDZ2%foj0A;s(q+0L/XAC*KM/jf%'HN`CF;"erEfS?mQrFqlDC>DLmP6"U`0RO\\Ml@^#_iP]5kSQM3I]&IOc9p\5=.k(LT?gWTMYC`,Zr1n@CueJ,)@f/rm.Tf^L^Z0&E3W,4QWS[Q=DBWRafZFJ>*'A&6_q@)9IdOTJZ7cJQH/+0mf]3nFqiBY7gB]8pf^c(c6H=+]Lp?FLE-m(&nj%(pFk<#iHMT`>Nlace>R+:X*jD`[m3ZgkJ)9k=+6]]kWL-]I%:$iGMGM[ThD@"F3E/D2#gk#pC=N2M3MI#G,R)kg@5?+%6sqEbqFCiRWr*!X.iT%2,^#G]uA>GJLY#lKbd2OPf9]CR=uh!bmKQ\PE\[k4^f?T/7RD@e)l(EIFIFefuB./l-03k?tZkR+#IFtf_FPQG>,_nu*u:#rj_E2+Gjh"_':\;N"WP)6#;V9Pn(`>[<b_"YdUiNjX*"GGPuG-PkK+T$4ihOW4&qi4[5q'>hZ;NXB%X;2+]2j(n/>s?F6Q1k'(Y`Bh)/*"C]:%k)J,4b,5J3hA+D88)=_Xi+Pgt0F;3;Sa8PmUdPr<nr56\eHq`>u%AhoNto,WC!?K9[O:Aifh+;6&!"fhQLBY*B&ue(%K,`e3Vm?N.XW!,mr4fqks;VCal.62^;`4E<^po&0,[!L>UL'';cORmCXE^%ut8<OUc@/`F"c.LJ<52(uH;Y"]<b-XH[^IkH/[Sm@UF>Yb`l0TUpeGrSp0&'4t/`R3-7LE/`Wjf9Au)T?_j[.!VNm-gDSe+mDRo/g*NB2"m<hk0B2CCG4T3$dO$rRK@Wqhf08F]\F(2&c?RkTn/!(uU&ofsR)`4#m*J:hSJH~>endstream
|
||||
endobj
|
||||
88 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2061
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2091
|
||||
>>
|
||||
stream
|
||||
Gau0DD0+E#&H;*)YsZtpUunI7]n#<k1,pG^1JU8eb]K_h*[)#T/L3b4*8Dh>]mGnB+X7)\<\g>smHrZqZb'iW.c*#XG3/c@"&RiPTF;#<TK:J/E"/ASak%2p;<e??Rtdq1UY-oIB'Hf8Vf<kco4#Xg_&TC'J$0NG:!$4+mUusX^?\QX%em:p`))*&/J94"8Q6F8U+VcXLo/in[^o(3PUq1/mTtJ3O!3Lm2r[R@@V/+MiY`!C\?PmT#jlj`5a%(3E>uE:ge)5a0;XZ#n_RiT^9R:]DT!ng1q2M4O1TRGT<(`?G(?Hd*frjZno5jr\;'9"N^M2lP_+mn5SZPh6W^grb)G(]4G3<lfjS^')D]38HZ!U'hY06_KTsXip0ne*1&%<dI`5\sIFf@Pf*.3V+Gc]fWBWpA%r48&IZ9T\_HT]`P0fgb8*]7YF?dL_F&)NsnN*,FVQro%%BC)5nmr4.[-JC5DZ07a4JV&bND3!?m#2UAYQesc38$l2>0Sq@Tn0#gP!F?\\_N5ffoDC`Fo<VTn[=('rF'R;lpYQ&VL."@GrG8X\Ll5_EF6$?-,C<c_=HP3["X$2;r(J#=n;.Ym;i.[?]o@Dmaq4u]Cm@UL0;UXd5,)'*2cHjAH;M?ge_T=b(KmB:Q$`+^'7cWB4\;&/X*-5AsL96Bb2L634]U5mBg9,n3$Ko]1R\*j^X)K\EFX-&r(=uT"m9$ZZLcb''i:#8kU!F:N.jL4S441&AB*<4<E(pr\A"BN3saf2Y.!p/1;E,%lY0Rj.`L2)NW!b`AA)r>*peF%MpA9Fd-a5js4?o.kXD66'4p.p*A@T\p][$(nmh'Q,ecbXpGG1Uf@2PZ.+CW)<Y9r/HTeE/V!+>TaaB/0Te"4&"FcP)r]r[ob]lpV=.1SCmFII:@9]]h]?4P,b2pb0uc\kF,/HtW'`_k6Te8p=eUfM4LgOM&M`%DR6e6gAqu[!WZ!1kM(_4q#;4a/cM8+_c\egQmWEj=&&BHia3d5He9W+-Bgom)]8'PXr,fnubU7'gO,2+ljW9m&`=)UCijo'E.r"uC4^G<`fHe(/T85[1?:`n\gT='SD>fO&^hp9s*+"3T0UI7SQZ2d^n8TYT%j"'o!]pf&$?kb?^2b_*s0T"<%&_u5.'(drS5rsl(?3eGA]1iJNRHFXLQ^8/N22oAW^!t0lX,,+%<a^8Z0@')g0!OX`5J,A$V[:&%V1,j'N;7B"(1Q*6E/M0ItcH#*%UL\V@a?4hLX.jLXjSaT_mb18.B;CCLn!Y#GB$,&=^D0gY;EBG(P"A,fM:mrkm&:_c6>p"+sLGX4/\S\=oq@ap="3fpaNGcS>E&Yej9n]ZIV[+?\o@lDY&f;D!%?>SA*FLmX'VBKX?6qf6h0-J1VM$.>WB\k5pB>>lAdb>f*SX<;GkisOR`/7<CB(ch<nfW`pVVX,GdOW@q\:CmqUgUF)./j!(Z^#q^OSNNGXc>g20kY^cgF'=^-06[CA^)n(AhnB?9NlJB=g@OA2Lu6:LLP[.n-cb2uK.:iFE';D__qH"'5n*J0K;u:#CNG:<&*_'(l^&D-'6dNQ#&$^]k?O`>R:^+t1s8i;9csmt=ZVhqkMn=MiN?:9;.`-8I?1;Qa\-.nmu.CMq[O7%Yg]<@Rk6t\I=1SP;^BJYT;@g?Z:?9k>YLbX;i7E1JX$57mDo]5l9M4$@]JRs3%s88!:u+[9U$L5RKnYDH#Xp2N*SjV49dX<=ZHq/Z/<-X\AqLK'P:MFZ[nP3gc1EJAiBe/FWKOWNs*3`n"TclfK.\@(DKH+,1R$fV+PNR!I^bbY$I1K&WhG'Sc*Fd?BE+t':Q7;q/mM$PQUUoGSD#;Y`B%YfCSo!Ha:oj4aq9E8<YoQK0o+;p[/1Un$c<*Qu>6J,7jXmKURCoDS9(0F?,67CbXE#-?->\^entNs&Kf#2f]Qk8gbI^;C>%-lTll^F'2YoRO2`N(+Utu>/MU:,<5P0o>,T^Y0XpUr^e<<-kt#N4-`,=3Kn*igL);25?iq6-#*h`&GY,"UQ_62&blAtjN9e<cNPpkd[b$A9b1D)-YFT=i8e6(BO=4~>endstream
|
||||
Gau0DHZRn1&HCX[@Y^gjS;*5LH@Y4(1-!+T1JU8eb]K_h*a'I*>=aWH34MPoHh[.4ECL-p/Js,IHi(DQmI!OJ(#N3khjjk>%7*#+5bg$'J7]>pL0)_B*dp3"E_aLt<m+GOBJu>JeSPE?+W2)2.p8;F_`:OC(+\Q)]q2$!(#g[l[2+=B\4F8?$DVnY`cENtmb-gb"GXB>Hmpn$`qMBn(+&*dX[j"<2-#$2V1,d0oaXflces6fncVirauP=n9Qa)Wl>fJ5Ga]<:eTFc/delS4G6q"uL7!t6R(3OmR1hAIAdj?#C&.3/3Q[54?l==h2Y;Ts]n`5%mH:[dQqc!O-#D@X@cs\/Beob#Sjbn(AVm#m@D`i0B1Wa+[\?;VU&fB:`JH_=?;kI[nWOil?F_0?VPk(aX]=F?..D]Tkp'_:[YX+Q0MYa>I.;o@;>jdE#[\B!IXj,,hP\1D<P?\LP1HhAnt#Z9kt_q#Y!#/<k<AN%^+2k%jFR4i=L,*XFUF,n0q5I[)uapt'=D9Kp"aqj=[+3#Cs<WYd&,A.H*W^OC=3j7^56B6'oDS&pK3M>nf;)Re-hT+`d&@<#G[LMe?eQIV0C<;75g3j[Abp[#[b13(!.30iUYqgE]Aht9AMEN?1RRUmIGi`>EWY8mC)dSHgO)8%',K9ib+#83C+J&b5q*^KqZY%e-RQnSlSptHj)>UAlZ(@ROV1>]2cbk[#(2,*8H=p]5<odHlp)p0@OJ)fGKb_cFS$t_Xs,i$%2?K8kWt:fQZ%Gftn;GS72;'OWQae&%j<"alnEEDF9^uYMsN*RqLr^A7uT?>YdJSES82D*n0FABGlo#7$9u/M]:PpPtdd71go:+Q__#SjqFj[c*jutmhI(*BU5c%k6_\s"iI$tP"hU%]+p:<-$3N_.2MJCm"673G;o<MD4YIr`*-m4?LVZ+n)Y\d(I@!7i69-qfS_H(DB/B\X\mH"7UCjPXCP;5N7g>+(A8>(gMNA+C9B,B[l%m"=j`_k1.$'?X>B(34u)O@Cu%k$gHU)l(NC[V(EBh@Oq:o_)2FbH<kZuB6KFM.d!lgpJ0i?$%O$]O##iM8K"KXOrDW*n&<ViAGH3IW"/rmE[V_%/[0kd1+'F6N>a#E*#,>%pr&-1cpe;SJ+__?,,1ZrGM@^+S;hopEbJW7?=tk]kmkrTpCY+S^0/=(.f;mU7__F$oDJ'PRD^PjslB\ILh"5H&nYnb.&o>P7DQ'Us%;&?dm('mH,S'!&(>)'\'_<^]3*R8bH-HS\k\VfS#!ZK6ZNqIT3<"8(^DIH-5H_lIU%[]G._q#TP[kiod$ILSS$Bk&Y\]T_Gg3pOfI9jgg9,U,ZqOr-RT0Z6UM19$B/g`=DtCP&a8B^?JYi#03oBN,n:[R.ML__8dSO!R#8SL'1SHUrj8OJ;gqscLXAku'71D)lY\]99[o*I)EX-m5>`[pB@fq)4a3;(A5Y!2=\R^I-Mk(":H\u3u4+T2!F"sZjGc]sMlbr$]3lfJ\2ih]<T$q5YJ_0u7pjU`"!p:V0-Q\3FlHI^%+T=kSI4PL-rc2;!1T1@f$dZk^0fjR`6(!1$6r4Wt@rIC3i#t>"o7H7^A$tg=XdPhdH7??lD,K-=J`sP=2HrG8-FF:WA7'l19T$Gk'<4n;)Amf`KhbsK8Fau.Ye+g^+fRF'jR7I.o'V[!GFr>9+#VEH'5R6fEs>mN4c-$E9=Y$a"]EAU'r!I6<2[Z4X_143,VfACMa^B?4$m.uW7/oS5'WI,MF_tcBZR2oQJ?ksGD3WK>3lZ0q=q:NRH%VcZ,:5+*7K%qjOoIp^Hqn$o0sdl*)R)S<^l.#J6?Tn%[bTJ#3X.h3?A4?kL%s'QGXFD9KurG4]Ga+&Pj;&>_,D-nK.S;LO>jJJ%[5!Eo5p?p5$(hdkEY@^\dJ[Yl9)%Wl9>p>.jE7O:p(6I<FSY]&kM_C_q`#VF85#Jq-oK!r`o;X;`Ge?=a73m4:dpe*8./oXUtRi<Vf>[S87kUi*2cjpVJ:G<S"SbR58tksH)D9gM*uQrKjsnk,GJDSmERo@\^[C/GqkLG83>*hDZ9M'*'i+BLBlEA$_tZ>MhkXCk*"+fCP_E:bf[^j;4~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 89
|
||||
@@ -582,15 +582,15 @@ xref
|
||||
0000011129 00000 n
|
||||
0000011232 00000 n
|
||||
0000012440 00000 n
|
||||
0000016058 00000 n
|
||||
0000019295 00000 n
|
||||
0000022787 00000 n
|
||||
0000025731 00000 n
|
||||
0000028677 00000 n
|
||||
0000016061 00000 n
|
||||
0000019298 00000 n
|
||||
0000022790 00000 n
|
||||
0000025734 00000 n
|
||||
0000028673 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<43846ba034f0f13d3c3b73c3a2995bc3><43846ba034f0f13d3c3b73c3a2995bc3>]
|
||||
[<551952d3ad7bea00713487e65aa30a36><551952d3ad7bea00713487e65aa30a36>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 48 0 R
|
||||
@@ -598,5 +598,5 @@ trailer
|
||||
/Size 89
|
||||
>>
|
||||
startxref
|
||||
30830
|
||||
30856
|
||||
%%EOF
|
||||
|
||||
718
docs/specs/20100612_1_scaling.md
Normal file
718
docs/specs/20100612_1_scaling.md
Normal file
@@ -0,0 +1,718 @@
|
||||
# Platform scaling and multi-environment architecture — specification
|
||||
|
||||
**Document type:** SPEC
|
||||
**Source draft:** [docs/drafts/20100612_1_scaling.txt](../drafts/20100612_1_scaling.txt)
|
||||
**Design review:** [docs/DRs/20100612_1_scaling.md](../DRs/20100612_1_scaling.md)
|
||||
**PDF:** [20100612_1_scaling.pdf](20100612_1_scaling.pdf) · Regenerate: `bash scripts/build-all-docs-pdf.sh` (Mermaid → PNG diagrams; requires `npx`)
|
||||
**Date:** 2026-06-12
|
||||
**Status:** Planning / brainstorming (pre-implementation)
|
||||
**Severity:** Strategic (post-alpha evolution)
|
||||
**Scope:** FE/BE topology, service decomposition, dev/staging/prod clusters, cloud migration path
|
||||
|
||||
**Documentation index:** [README.md](../README.md)
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Table of contents
|
||||
|
||||
<!-- toc -->
|
||||
- [1. Purpose](#1-purpose)
|
||||
- [2. Vocabulary](#2-vocabulary)
|
||||
- [3. Current state (baseline)](#3-current-state-baseline)
|
||||
- [4. Goals and non-goals](#4-goals-and-non-goals)
|
||||
- [5. Requirements](#5-requirements)
|
||||
- [5.1 Must (platform)](#51-must-platform)
|
||||
- [5.2 Should](#52-should)
|
||||
- [5.3 Future capability slots (normative intent)](#53-future-capability-slots-normative-intent)
|
||||
- [6. Target architecture](#6-target-architecture)
|
||||
- [6.1 Environment tiers](#61-environment-tiers)
|
||||
- [6.2 Service domains](#62-service-domains)
|
||||
- [6.3 Reference cluster layout (prod)](#63-reference-cluster-layout-prod)
|
||||
- [6.4 Traffic and control planes](#64-traffic-and-control-planes)
|
||||
- [7. Service inventory and scaling profile](#7-service-inventory-and-scaling-profile)
|
||||
- [8. Deployment and promotion pipeline](#8-deployment-and-promotion-pipeline)
|
||||
- [9. Zero-downtime and load balancing](#9-zero-downtime-and-load-balancing)
|
||||
- [10. Data and storage](#10-data-and-storage)
|
||||
- [11. Simulation lab (Alpine HVMs)](#11-simulation-lab-alpine-hvms)
|
||||
- [12. Capacity planning framework](#12-capacity-planning-framework)
|
||||
- [12.1 RSSH / remote access connections](#121-rssh-remote-access-connections)
|
||||
- [12.2 Cast and media paths (future)](#122-cast-and-media-paths-future)
|
||||
- [12.3 Network bandwidth and egress](#123-network-bandwidth-and-egress)
|
||||
- [13. Cloud migration (GCP / Azure / other)](#13-cloud-migration-gcp-azure-other)
|
||||
- [14. Global availability and GEO policy](#14-global-availability-and-geo-policy)
|
||||
- [14.1 Reach model](#141-reach-model)
|
||||
- [14.2 Segment isolation](#142-segment-isolation)
|
||||
- [14.3 Enforcement layers](#143-enforcement-layers)
|
||||
- [15. RBAC and operator model evolution](#15-rbac-and-operator-model-evolution)
|
||||
- [16. Commercial and paid services (platform hooks)](#16-commercial-and-paid-services-platform-hooks)
|
||||
- [17. Observability and SLOs](#17-observability-and-slos)
|
||||
- [18. Safety constraints (current topology)](#18-safety-constraints-current-topology)
|
||||
- [19. Phased delivery map](#19-phased-delivery-map)
|
||||
- [20. Out of scope (this SPEC revision)](#20-out-of-scope-this-spec-revision)
|
||||
- [21. Related docs](#21-related-docs)
|
||||
<!-- /toc -->
|
||||
|
||||
**Documentation index:** [README.md](../README.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
Define how Android Cast evolves from a **single Alpine BE HVM** hosting all backend services into a **multi-VM, multi-environment platform** that supports:
|
||||
|
||||
- Stable **dev → staging → prod** promotion
|
||||
- **Horizontal scaling** and **zero-downtime** deploys where services need it
|
||||
- Optional **cloud** footprint (GCP, Azure, or hybrid) without breaking the existing Gentoo FE → Alpine BE production path
|
||||
- **Network bandwidth** budgets and egress planning alongside CPU/RAM/connection limits
|
||||
- **Global availability** with **GEO segment isolation** (worldwide reach by default; denylisted regions per content/policy — e.g. no publish in Zambia)
|
||||
- Room for **future RT/AV**, **VPN**, **payments**, and **third-party streaming** integrations described in the draft
|
||||
|
||||
This SPEC is **normative for planning**; individual services retain their own SPEC/DR pairs (URL shortener, remote access, SFU, etc.).
|
||||
|
||||
---
|
||||
|
||||
## 2. Vocabulary
|
||||
|
||||
| Term | Meaning |
|
||||
|------|---------|
|
||||
| **FE** | Forward-End — Gentoo bare-metal / XEN host; TLS, public nginx, UDP DNAT |
|
||||
| **BE** | Back-End — Alpine HVM(s); app services today on `artc0.intra.raptor.org` |
|
||||
| **Cluster** | Named set of VMs + shared config for one **environment tier** (dev, staging, prod) |
|
||||
| **Control plane** | HTTPS APIs, consoles, heartbeats, RBAC, orchestration metadata |
|
||||
| **Media plane** | UDP/TCP cast, SFU/MCU relay, ffmpeg transcode pipelines (future) |
|
||||
| **Sysop** | System operator (PO today) — owns infra simulation and promotion decisions |
|
||||
| **PROD** | Production cluster serving end users |
|
||||
| **RSSH** | Reverse-SSH remote access track ([REMOTE_ACCESS_IMPL](../REMOTE_ACCESS_IMPL.md)) |
|
||||
| **GEO segment** | Country/region group (ISO 3166-1 alpha-2) used in allow/deny policy — e.g. `ZM` (Zambia) |
|
||||
| **geo_policy** | Per-resource or catalog rule: where content/API may be **served**, **published**, or **streamed** |
|
||||
| **Egress budget** | Planned GB/month or Mbps peak per service/link; triggers scale or CDN move |
|
||||
|
||||
---
|
||||
|
||||
## 3. Current state (baseline)
|
||||
|
||||
Today one **BE Alpine VM** co-locates (see [20260608_BE_SERVICES_and_infra.md](../20260608_BE_SERVICES_and_infra.md)):
|
||||
|
||||
| Domain | Services | Stack |
|
||||
|--------|----------|-------|
|
||||
| **Web / console** | Hub, crashes, tickets, graphs, builder UI | nginx + PHP-FPM |
|
||||
| **APIs** | Upload, heartbeat, remote access, graphs ingest, short links admin | PHP |
|
||||
| **Git** | Gitea mirrors (`AndroidCast/*`) | Gitea + MariaDB/SQLite |
|
||||
| **Database** | `androidcast_crashes`, `url_shortener`, Gitea DB | MariaDB |
|
||||
| **OTA** | Channel JSON + artifacts | nginx static / PHP |
|
||||
| **VPN** | WireGuard lab (`wg0`), RSSH bastion | kernel + sshd |
|
||||
| **URL shortener** | `s.f0xx.org` | nginx + PHP (submodule) |
|
||||
| **Build** | Docker builder orchestration | Docker on BE |
|
||||
|
||||
```text
|
||||
Internet → router (134.17.26.161) → FE (10.7.0.10) → BE (10.7.16.128) :80
|
||||
```
|
||||
|
||||
**Constraint:** This topology is **production truth** until a phased migration explicitly replaces it ([INFRA.md](../INFRA.md) §1, [bottomline_reminder.txt](../../bottomline_reminder.txt)).
|
||||
|
||||
---
|
||||
|
||||
## 4. Goals and non-goals
|
||||
|
||||
### Goals
|
||||
|
||||
| ID | Goal |
|
||||
|----|------|
|
||||
| G1 | Split services by **scaling profile** (CPU/RAM vs storage vs RT media) |
|
||||
| G2 | Three **environment tiers**: dev (unstable), staging (beta), prod (stable) |
|
||||
| G3 | **Seamless deploy/update** with rollback per service |
|
||||
| G4 | **Cost-aware** sizing — no oversized DB VMs; burst CPU for build/ffmpeg |
|
||||
| G5 | **Simulation lab** on Alpine HVMs before cloud spend |
|
||||
| G6 | Provider **L4/L7 load balancers** ready for stateless tiers |
|
||||
| G7 | Preserve **dual-remote** git flow and existing public URLs during transition |
|
||||
| G8 | **Bandwidth visibility** — per-link and per-service throughput metrics before saturation |
|
||||
| G9 | **Global reach** for public services; **segment isolation** when PO denies regions for specific content |
|
||||
|
||||
### Non-goals (this revision)
|
||||
|
||||
| ID | Non-goal |
|
||||
|----|----------|
|
||||
| NG1 | Alpha release blocker — scaling work is **post-alpha** unless PO reprioritizes |
|
||||
| NG2 | Immediate full cloud cutover |
|
||||
| NG3 | Replacing Gentoo FE in phase 1 |
|
||||
| NG4 | Implementing payments, SFU, or DNS resale in this SPEC — only **platform hooks** |
|
||||
| NG5 | Full **multi-region active-active** prod on day one — phased via CDN → hybrid → regional PoPs |
|
||||
| NG6 | Legal counsel for country block lists — platform provides **policy hooks**; PO owns compliance text |
|
||||
|
||||
---
|
||||
|
||||
## 5. Requirements
|
||||
|
||||
### 5.1 Must (platform)
|
||||
|
||||
| ID | Requirement |
|
||||
|----|-------------|
|
||||
| P1 | Every deployable service has an **owner**, **health endpoint**, and **config outside git** documented |
|
||||
| P2 | **Prod** changes require promotion from staging (except hotfix path per [GIT_FLOW.md](../GIT_FLOW.md)) |
|
||||
| P3 | **Stateless** HTTP tiers scale behind a load balancer; **stateful** tiers (DB, object store) have backup/restore runbooks |
|
||||
| P4 | **FE** remains TLS termination and edge routing; BE/cluster addresses are **internal** until cloud edge replaces FE |
|
||||
| P5 | **WireGuard UDP** and **RSSH TCP** paths remain explicit in firewall/DNAT docs when split across VMs |
|
||||
| P6 | Simulation cluster MUST be reproducible from repo scripts (Alpine + package sets) — see [§11](#11-simulation-lab-alpine-hvms) |
|
||||
| P7 | No single VM hosts **both** primary MariaDB and **unbounded** ffmpeg transcode workers |
|
||||
| P8 | Every **bandwidth-heavy** service (OTA, object store, SFU, ffmpeg, CDN origin) has documented **Mbps / GB-month budgets** and alert thresholds |
|
||||
| P9 | **geo_policy** model: default **global serve**; explicit **deny segments** (countries/regions) per content class or tenant — enforceable at edge and BE |
|
||||
|
||||
### 5.2 Should
|
||||
|
||||
| ID | Requirement |
|
||||
|----|-------------|
|
||||
| S1 | Use **managed** MariaDB / object storage in cloud phase when cost ≤ ops time |
|
||||
| S2 | **Blue/green** or rolling deploy for PHP-FPM pools and Gitea |
|
||||
| S3 | Central **secrets** store (SOPS, Vault, or cloud SM) — no secrets in git |
|
||||
| S4 | **Per-environment** DNS: `dev.apps…`, `staging.apps…`, prod unchanged |
|
||||
| S5 | Connection and CPU **dashboards** before scaling RSSH and SFU |
|
||||
| S6 | **IaC** fragments in repo (`deploy/`, nginx snippets) for each new VM role |
|
||||
| S7 | **CDN or edge cache** for OTA/APK and static consoles before scaling origin VM count for bandwidth |
|
||||
| S8 | **GeoDNS or latency routing** when a second region/PoP is added; single canonical URL preserved |
|
||||
|
||||
### 5.3 Future capability slots (normative intent)
|
||||
|
||||
Draft “future key roles” map to **service domains** (implementation deferred):
|
||||
|
||||
| Slot | Description | Depends on |
|
||||
|------|-------------|------------|
|
||||
| **F1 Multipoint cast** | MCU/SFU relay outside LAN; TURN/STUN | SFU SPEC, signaling |
|
||||
| **F2 Stream transcode** | ffmpeg any→any; RT and non-RT | Media worker pool, object store |
|
||||
| **F3 Cast storage** | Recordings on BE; paywalled retrieval | F2, billing |
|
||||
| **F4 Commercial VPN** | Anonymous RT VPN; capacity planning | WG pool, payments |
|
||||
| **F5 Payments** | Low fixed cost at scale; usage-based optional | F3, F4, add-ons |
|
||||
| **F6 Mobile add-ons** | Masks, background replace | On-device or cloud GPU |
|
||||
| **F7 In-app transfers** | P2P or ledger between users | F5, compliance |
|
||||
| **F8 DNS / subdomains** | User subdomains + site builder | FE DNS API, tenant isolation |
|
||||
| **F9 3rd-party streaming** | Ingest HLS/m3u8 or RTMP; re-emit as cast | F2, cast handlers |
|
||||
| **F10 GEO enforcement** | Region deny/allow on publish, stream, download | F3, F5, edge CDN/WAF |
|
||||
|
||||
---
|
||||
|
||||
## 6. Target architecture
|
||||
|
||||
### 6.1 Environment tiers
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Dev["DEV cluster — unstable features"]
|
||||
D_FE["Edge / FE stub or shared"]
|
||||
D_SVC["N × Alpine service VMs"]
|
||||
D_DB["MariaDB (disposable)"]
|
||||
end
|
||||
|
||||
subgraph Staging["STAGING cluster — beta"]
|
||||
ST_FE["FE mirror or path prefix"]
|
||||
ST_SVC["Prod-shaped VM count"]
|
||||
ST_DB["MariaDB + anonymized seed"]
|
||||
end
|
||||
|
||||
subgraph Prod["PROD cluster — current + expanded"]
|
||||
P_FE["Gentoo FE apps.f0xx.org"]
|
||||
P_SVC["BE VMs + future workers"]
|
||||
P_DB["MariaDB primary + replicas"]
|
||||
end
|
||||
|
||||
Dev -->|"promote on PO sign-off"| Staging
|
||||
Staging -->|"promote on stability gate"| Prod
|
||||
```
|
||||
|
||||
| Tier | Audience | Data | Deploy freedom |
|
||||
|------|----------|------|----------------|
|
||||
| **dev** | Developers, agents | Synthetic / disposable | Daily; may break |
|
||||
| **staging** | PO, QA, friendly beta | Anonymized prod subset | Weekly; feature flags |
|
||||
| **prod** | End users | Real | Hotfix + scheduled; zero-downtime target |
|
||||
|
||||
### 6.2 Service domains
|
||||
|
||||
Logical grouping for VM assignment (see DR §Architecture options):
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Edge["Edge tier"]
|
||||
FE["FE nginx TLS"]
|
||||
LB["L4/L7 LB optional"]
|
||||
end
|
||||
|
||||
subgraph App["Application tier"]
|
||||
WEB["Web + consoles + OTA metadata"]
|
||||
API["Control APIs PHP"]
|
||||
GIT["Gitea"]
|
||||
SHORT["URL shortener"]
|
||||
BUILD["Build / Docker"]
|
||||
end
|
||||
|
||||
subgraph Data["Data tier"]
|
||||
SQL["MariaDB"]
|
||||
OBJ["Object store / recordings"]
|
||||
end
|
||||
|
||||
subgraph Realtime["Realtime tier"]
|
||||
WG["WireGuard pool"]
|
||||
RSSH["RSSH bastion"]
|
||||
SFU["SFU / MCU future"]
|
||||
FF["ffmpeg workers future"]
|
||||
end
|
||||
|
||||
FE --> LB --> WEB
|
||||
LB --> API
|
||||
WEB --> SQL
|
||||
API --> SQL
|
||||
SHORT --> SQL
|
||||
GIT --> SQL
|
||||
BUILD --> OBJ
|
||||
SFU --> FF
|
||||
SFU --> OBJ
|
||||
WG --> RSSH
|
||||
```
|
||||
|
||||
### 6.3 Reference cluster layout (prod)
|
||||
|
||||
**Phase A (minimal split)** — cost-conscious; matches draft “tighter” intent:
|
||||
|
||||
| VM role | vCPU | RAM | Disk | Services |
|
||||
|---------|------|-----|------|----------|
|
||||
| **be-web-1** | 2 | 4 GiB | 40 GiB | nginx, PHP-FPM (hub, crashes, graphs, builder UI, OTA JSON) |
|
||||
| **be-data-1** | 2 | 8 GiB | 200 GiB+ | MariaDB primary; nightly backup to OBJ |
|
||||
| **be-ops-1** | 2 | 4 GiB | 80 GiB | Gitea, url-shortener, small object cache |
|
||||
| **be-build-1** | 4 | 16 GiB | 100 GiB | Docker builder; ephemeral |
|
||||
| **be-vpn-1** | 2 | 2 GiB | 20 GiB | WireGuard + RSSH bastion |
|
||||
| **fe-1** | (existing) | — | — | Gentoo FE — unchanged |
|
||||
|
||||
**Phase B (scale-out)** — when metrics justify:
|
||||
|
||||
| Add | When |
|
||||
|-----|------|
|
||||
| **be-web-2+** | PHP-FPM CPU > 70% sustained or p95 latency SLO miss |
|
||||
| **be-vpn-2+** | RSSH session count > [§12.1](#121-rssh-remote-access-connections) per node |
|
||||
| **be-media-N** | SFU/ffmpeg SPEC approved |
|
||||
| **CDN edge** | OTA/release origin egress > [§12.3](#123-network-bandwidth-and-egress) budget |
|
||||
| **Regional PoP** | p95 RTT > 300 ms for target markets + media revenue (F1/F3) |
|
||||
| **Managed SQL** | DBA ops > 4 h/month or replica lag requirements |
|
||||
|
||||
### 6.4 Traffic and control planes
|
||||
|
||||
| Plane | Protocols | Termination | Notes |
|
||||
|-------|-----------|-------------|-------|
|
||||
| **Control** | HTTPS :443 | FE → BE :80 | Heartbeats, upload, consoles |
|
||||
| **Git** | HTTPS / SSH | FE path or dedicated | Gitea behind same FE prefix |
|
||||
| **OTA** | HTTPS static | FE → BE or CDN | Large artifacts → object store |
|
||||
| **VPN lab** | UDP :45340 | Router DNAT → FE → BE | Unchanged until WG pool |
|
||||
| **RSSH** | TCP :443 | `ra.apps.f0xx.org` | Bastion may move to dedicated VM |
|
||||
| **Cast LAN** | UDP/TCP :41234–41235 | Device-local | No BE in alpha |
|
||||
| **Cast WAN** | UDP/TURN/WebRTC | SFU tier (future) | See [20260608_BE_SERVICES_and_infra.md](../20260608_BE_SERVICES_and_infra.md) §SFU |
|
||||
|
||||
---
|
||||
|
||||
## 7. Service inventory and scaling profile
|
||||
|
||||
| Service | Stateful? | Scale axis | Co-location rule |
|
||||
|---------|-----------|------------|------------------|
|
||||
| Hub / crashes / tickets | No (PHP) | Horizontal | With API pool |
|
||||
| Graphs ingest + UI | No | Horizontal | Same pool |
|
||||
| Builder UI | No | Horizontal | Same pool; triggers build VM |
|
||||
| Docker build | Ephemeral | Queue workers | **Dedicated** CPU VM |
|
||||
| MariaDB | Yes | Vertical → replica | **Dedicated**; no ffmpeg |
|
||||
| Gitea | Yes (git) | Vertical; mirror RO | Low traffic; ops VM OK |
|
||||
| URL shortener | No | Horizontal | Small; can share ops VM |
|
||||
| OTA artifacts | Yes (files) | CDN / object store | Split from PHP |
|
||||
| WireGuard | Yes (peers) | Pool by region | UDP-friendly VM |
|
||||
| RSSH bastion | Session | Horizontal + sticky | See §12.1 |
|
||||
| SFU / ffmpeg | Session | Horizontal | **Dedicated** media subnet |
|
||||
|
||||
---
|
||||
|
||||
## 8. Deployment and promotion pipeline
|
||||
|
||||
```text
|
||||
feature/* or next ──► dev cluster (auto or manual)
|
||||
│
|
||||
▼ QA + PO
|
||||
staging cluster
|
||||
│
|
||||
▼ soak + sign-off ([ALPHA.md](../ALPHA.md) pattern extended)
|
||||
prod cluster
|
||||
```
|
||||
|
||||
| Step | Actor | Gate |
|
||||
|------|-------|------|
|
||||
| Deploy to **dev** | DEV / CI | Unit tests green |
|
||||
| Promote to **staging** | PO or release delegate | Integration + e2e smoke |
|
||||
| Promote to **prod** | PO | Staging soak; [GIT_FLOW.md](../GIT_FLOW.md) tag on `master` for app releases |
|
||||
|
||||
**Config rule:** `config.php` and secrets are **environment-specific**; post-sync scripts (e.g. `ensure_url_shortener_prod_config.php`) must be idempotent per environment.
|
||||
|
||||
---
|
||||
|
||||
## 9. Zero-downtime and load balancing
|
||||
|
||||
| Tier | Strategy |
|
||||
|------|----------|
|
||||
| **PHP-FPM** | N≥2 VMs behind FE upstream block or cloud LB; `max_fpm_children` tuned per VM |
|
||||
| **MariaDB** | Primary + read replica; app read/write split optional later |
|
||||
| **Gitea** | Single primary; maintenance window for upgrades unless Gitea HA adopted |
|
||||
| **WireGuard** | New peers on least-loaded node; existing peers sticky until session end |
|
||||
| **Static OTA** | Blue bucket + CDN swap or versioned path (`v0/ota/…`) |
|
||||
|
||||
**FE nginx pattern (multi-BE):**
|
||||
|
||||
```nginx
|
||||
upstream androidcast_be {
|
||||
least_conn;
|
||||
server 10.7.16.128:80 max_fails=2 fail_timeout=10s;
|
||||
server 10.7.16.129:80 max_fails=2 fail_timeout=10s;
|
||||
}
|
||||
```
|
||||
|
||||
Cloud providers: prefer **managed LB** (GCP ILB, Azure Front Door/Application Gateway) when FE moves partial traffic to cloud.
|
||||
|
||||
---
|
||||
|
||||
## 10. Data and storage
|
||||
|
||||
| Data class | Store | Backup |
|
||||
|------------|-------|--------|
|
||||
| Relational (crashes, tickets, RA, short links) | MariaDB | Daily logical dump + binlog optional |
|
||||
| Git mirrors | Gitea disk | `gitea_collect_backup.sh`; off-VM copy |
|
||||
| OTA / APK / build artifacts | Object store or large volume | Version immutability |
|
||||
| Cast recordings (future) | Object store + lifecycle policy | Glacier-class after N days |
|
||||
| Audit / graphs time series | MariaDB or TSDB later | DR §open |
|
||||
|
||||
**Rule:** Object store VMs optimize for **disk IOPS and egress**, not CPU.
|
||||
|
||||
---
|
||||
|
||||
## 11. Simulation lab (Alpine HVMs)
|
||||
|
||||
Sysop MAY simulate a cluster on FE hypervisor:
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Base OS | Alpine 3.x (match prod BE) |
|
||||
| Pre-install | `nginx`, `php81`, `php81-fpm`, `mariadb`, `git`, `docker`, `wireguard-tools` |
|
||||
| Network | Private `10.7.0.0/8` — **no WAN topology change** |
|
||||
| Count | 3–7 HVMs typical for dev cluster |
|
||||
|
||||
```text
|
||||
FE XEN
|
||||
├── hvm-be-web-dev
|
||||
├── hvm-be-data-dev
|
||||
├── hvm-be-ops-dev
|
||||
└── hvm-be-build-dev
|
||||
```
|
||||
|
||||
**Deliverable (future script):** `orchestration/sim/cluster-up.sh` — out of scope until PO prioritizes; SPEC reserves name.
|
||||
|
||||
---
|
||||
|
||||
## 12. Capacity planning framework
|
||||
|
||||
### 12.1 RSSH / remote access connections
|
||||
|
||||
Planning numbers for **single bastion VM** (2 vCPU, 2 GiB RAM, Alpine):
|
||||
|
||||
| Metric | Conservative | Target design |
|
||||
|--------|--------------|---------------|
|
||||
| Concurrent **SSH** sessions | 50 | 100 with `MaxSessions` tuning |
|
||||
| Concurrent **heartbeat** devices (poll 1–7 min) | 2 000 | 5 000 (mostly idle HTTP) |
|
||||
| Peak **new sessions / min** | 10 | 30 with queue |
|
||||
| WireGuard **peers** (lab track) | 200 | 500 split across 2 WG nodes |
|
||||
|
||||
**Bottleneck order:** MariaDB connection pool → sshd `MaxStartups` → PHP-FPM for heartbeat API → CPU on bastion.
|
||||
|
||||
When **staging** exceeds conservative column for 24 h, add **be-vpn-2** before cloud migration.
|
||||
|
||||
### 12.2 Cast and media paths (future)
|
||||
|
||||
| Mode | Latency | Scale unit | ffmpeg role |
|
||||
|------|---------|------------|-------------|
|
||||
| LAN P2P | Lowest | N/A (alpha) | None |
|
||||
| SFU relay | Low | 1 SFU ≈ 50–200 viewers (codec dependent) | Optional simulcast |
|
||||
| HLS/m3u8 bridge | Medium–high | 1 worker ≈ 10–30 transcodes | Heavy CPU |
|
||||
|
||||
Detailed SFU sizing deferred to owner SFU SPEC ([OPEN_TASKS_GRAPH.md](../OPEN_TASKS_GRAPH.md)).
|
||||
|
||||
### 12.3 Network bandwidth and egress
|
||||
|
||||
Scale planning MUST include **throughput**, not only CPU/RAM/connections. Hidden bottleneck today: **single FE WAN uplink** → **FE→BE backhaul** → **single BE NIC**.
|
||||
|
||||
#### Link inventory (baseline)
|
||||
|
||||
| Link | Typical role | Metric |
|
||||
|------|--------------|--------|
|
||||
| **Router WAN** (`134.17.26.161`) | Public ingress/egress | Mbps sustained + peak; GB/month |
|
||||
| **FE ↔ BE** (`10.7.0.10` → `10.7.16.128`) | Proxied HTTP(S), internal sync | Mbps; must exceed sum of BE-facing services |
|
||||
| **BE origin** | PHP, MariaDB, Gitea, OTA origin | NIC util %; egress GB/month |
|
||||
| **UDP DNAT** | WireGuard lab | Mbps per active peer × peers |
|
||||
| **Future CDN origin pull** | OTA, recordings, HLS | Origin Mbps; **CDN egress** billed separately |
|
||||
|
||||
#### Per-service bandwidth profile (planning)
|
||||
|
||||
| Service | Direction | Order of magnitude (prod planning) | Scale trigger |
|
||||
|---------|-----------|-----------------------------------|---------------|
|
||||
| Control APIs (upload, heartbeat) | In ≪ out | 1–20 Mbps peak; low GB/month | p95 latency, not Mbps |
|
||||
| Consoles / hub | Out | < 5 Mbps sustained | CDN cache static assets |
|
||||
| OTA APK / `.otapkg` | Out | **Burst 50–500 Mbps** during release | CDN **required** before wide rollout |
|
||||
| Gitea clone/fetch | Out | 10–100 Mbps burst | Mirror geo cache |
|
||||
| URL shortener redirect | Out | Low | — |
|
||||
| Cast recordings (F3) | Out | **100 Mbps–1 Gbps** per hot object | Object store + CDN; geo replicas |
|
||||
| SFU relay (F1) | In+out symmetric | **5–50 Mbps × streams** | Dedicated media NIC / region |
|
||||
| ffmpeg transcode (F2) | In+out | **10–100 Mbps** per job | Worker pool + queue depth |
|
||||
| Commercial VPN (F4) | UDP bidirectional | **1–5 Mbps × active tunnels** | WG pool per region |
|
||||
|
||||
#### Budget template (PO fills per environment)
|
||||
|
||||
| Metric | Dev | Staging | Prod (initial) | Alert |
|
||||
|--------|-----|---------|----------------|-------|
|
||||
| WAN egress GB/month | — | — | _TBD_ | 80% of ISP/cloud quota |
|
||||
| Peak WAN Mbps | — | — | _TBD_ | 70% of measured cap |
|
||||
| FE→BE peak Mbps | — | — | _TBD_ | sustained > 60% 15 min |
|
||||
| OTA release peak | — | soak test | CDN absorbs > 90% | origin > 100 Mbps |
|
||||
| Media egress GB/month | — | — | _TBD_ (F3+) | cost budget |
|
||||
|
||||
**DR rule:** Do not add **be-web-2** for bandwidth until CDN/OTA offload is evaluated — horizontal PHP nodes do not fix a saturated WAN link.
|
||||
|
||||
---
|
||||
|
||||
## 13. Cloud migration (GCP / Azure / other)
|
||||
|
||||
**Hybrid recommended** for first cloud phase:
|
||||
|
||||
| Stay on-prem (FE + latency-sensitive) | Cloud candidate |
|
||||
|---------------------------------------|-----------------|
|
||||
| Gentoo FE TLS (or parallel cloud LB) | Object store (GCS/Azure Blob) |
|
||||
| WireGuard UDP if DNAT chain kept | Managed MariaDB |
|
||||
| LAN cast | ffmpeg worker MIG / VMSS |
|
||||
| | SFU pool (GPU optional) |
|
||||
|
||||
**Migration sequence (normative):**
|
||||
|
||||
1. Object store for OTA + build artifacts
|
||||
2. Read-only analytics replica or backup target
|
||||
3. Staging cluster entirely in cloud (dress rehearsal)
|
||||
4. Prod PHP tier behind hybrid LB
|
||||
5. Media plane last (highest risk)
|
||||
|
||||
**Egress rule:** Keep **one** canonical public URL per service; DNS/CNAME moves — no app hard-coded IP changes.
|
||||
|
||||
**Bandwidth rule:** Model **cloud egress pricing** in phase 1 object-store migration; prefer **CDN in front of origin** for any artifact > 1 MB served to > 100 users.
|
||||
|
||||
---
|
||||
|
||||
## 14. Global availability and GEO policy
|
||||
|
||||
Draft intent: **anyone in the world can use the platform** (latency permitting), while **specific content or features may be excluded by region** — e.g. a recording or OTA channel must **not** be published or downloadable in **Zambia (`ZM`)** when PO sets that policy.
|
||||
|
||||
### 14.1 Reach model
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Users["Global users"]
|
||||
U1["Americas"]
|
||||
U2["Europe / Africa"]
|
||||
U3["Asia-Pacific"]
|
||||
end
|
||||
|
||||
subgraph Edge["Edge layer"]
|
||||
DNS["GeoDNS / Anycast DNS"]
|
||||
CDN["CDN + WAF geo rules"]
|
||||
FE0["Primary FE TLS — on-prem"]
|
||||
end
|
||||
|
||||
subgraph Origin["Origin cluster(s)"]
|
||||
BE["BE VMs / cloud origin"]
|
||||
OBJ["Object store"]
|
||||
MEDIA["SFU / media PoP future"]
|
||||
end
|
||||
|
||||
U1 --> DNS
|
||||
U2 --> DNS
|
||||
U3 --> DNS
|
||||
DNS --> CDN
|
||||
CDN --> FE0
|
||||
CDN --> OBJ
|
||||
FE0 --> BE
|
||||
BE --> OBJ
|
||||
MEDIA --> OBJ
|
||||
```
|
||||
|
||||
| Phase | Reach | Latency target (control plane) |
|
||||
|-------|-------|--------------------------------|
|
||||
| **Now** | Single FE/BE (EU-centric) | Best-effort worldwide HTTPS |
|
||||
| **Phase 5–6** | CDN for static + OTA | p95 < 300 ms TTFB via edge |
|
||||
| **Future** | Optional regional **media PoP** + signaling | RT cast < 150 ms LAN-equivalent where PoP exists |
|
||||
|
||||
**Normative:** Public APIs and consoles remain **globally reachable** unless PO declares a **maintenance** or **sanctions** block at edge (separate from per-content deny).
|
||||
|
||||
### 14.2 Segment isolation
|
||||
|
||||
| Concept | Definition |
|
||||
|---------|------------|
|
||||
| **Segment** | ISO country code, optional region group (`EU`, `AFRICA`, custom list) |
|
||||
| **Default** | **Allow serve** worldwide for control plane; content inherits tenant default |
|
||||
| **deny_publish** | Content must not be **made available** in listed segments (upload/mirror/index) |
|
||||
| **deny_serve** | Existing object must not be **delivered** (HTTP 451 / empty manifest / VPN reject) |
|
||||
| **allow_only** | Exclusive list — only these segments (stricter catalogues) |
|
||||
|
||||
**Example (illustrative):**
|
||||
|
||||
```json
|
||||
{
|
||||
"resource": "cast_recording:abc123",
|
||||
"geo_policy": {
|
||||
"default": "allow",
|
||||
"deny_serve": ["ZM"],
|
||||
"deny_publish": ["ZM"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Same pattern applies to OTA channels, short-link targets (optional), and paid stream entitlements (F5).
|
||||
|
||||
| Content class | Typical policy |
|
||||
|---------------|----------------|
|
||||
| Crash/ticket consoles | Global (operator VPN/RBAC unchanged) |
|
||||
| OTA `stable` channel | Global unless PO restricts beta regions |
|
||||
| Cast recording (F3) | **Per-object** deny segments |
|
||||
| HLS rebroadcast (F9) | Per-channel geo + copyright rules |
|
||||
| Commercial VPN exit (F4) | **egress region** choice, not arbitrary bypass of deny_serve |
|
||||
|
||||
### 14.3 Enforcement layers
|
||||
|
||||
| Layer | Mechanism | When |
|
||||
|-------|-----------|------|
|
||||
| **L1 DNS / GeoDNS** | Route to nearest healthy edge | Multi-PoP phase |
|
||||
| **L2 CDN / WAF** | Geo block or cache vary-by-country | OTA, static, recording download |
|
||||
| **L3 FE nginx** | `geo` module or auth subrequest | Optional coarse blocks |
|
||||
| **L4 BE API** | Resolve client country (CDN header `CF-IPCountry`, GeoIP) → **geo_policy** check | Normative for dynamic content |
|
||||
| **L5 Object store** | Bucket policy / signed URL with segment claim | Large artifacts |
|
||||
| **L6 App / entitlements** | Mobile checks BE before playback | F3, F5, F9 |
|
||||
|
||||
**Headers (normative when behind CDN):** trust `X-Geo-Country` or provider equivalent only from **FE/CDN** — strip at edge from client.
|
||||
|
||||
**Audit:** deny_serve events logged to audit table (who, resource, country, time) for operator review.
|
||||
|
||||
**Dev/staging:** may use `X-Debug-Country: ZM` header to simulate segments without VPN.
|
||||
|
||||
---
|
||||
|
||||
## 15. RBAC and operator model evolution
|
||||
|
||||
| Phase | Model |
|
||||
|-------|-------|
|
||||
| **Now** | Developer-centric; shared `ac_crash_sess`; privilege sets for tickets, RA, short links |
|
||||
| **Staging scale** | Separate **operator** vs **developer** orgs in Gitea; audit log export |
|
||||
| **Prod commercial** | Tenant-scoped RBAC; customer admin roles; SSO optional |
|
||||
|
||||
Platform MUST support **per-environment** user tables or row-level `environment_id` before multi-tenant commercial features (F5–F9).
|
||||
|
||||
---
|
||||
|
||||
## 16. Commercial and paid services (platform hooks)
|
||||
|
||||
No implementation in this SPEC — only **interfaces**:
|
||||
|
||||
| Hook | Requirement |
|
||||
|------|-------------|
|
||||
| **Billing** | Webhook idempotency; `tenant_id` on all paid resources |
|
||||
| **Metering** | VPN minutes, storage GB, transcode minutes, **egress GB** — export to metrics |
|
||||
| **Entitlements** | Mobile app checks BE `/api/v1/entitlements` (future); includes **geo_allowed** flag |
|
||||
| **Add-ons** | Feature flags per user; GPU queue for masks/background |
|
||||
|
||||
Payment provider SHOULD have **no monthly minimum** at low volume (draft: “near 0 for developer”).
|
||||
|
||||
---
|
||||
|
||||
## 17. Observability and SLO
|
||||
|
||||
| Service | Health | SLO (prod, initial) |
|
||||
|---------|--------|---------------------|
|
||||
| Control APIs | `/api/…` + upload smoke | 99.5% monthly |
|
||||
| Consoles | HTTP 200 login page | 99.5% |
|
||||
| URL shortener | `/api/v1/health` | 99.9% |
|
||||
| RSSH heartbeat | device poll success rate | 99% |
|
||||
| Gitea | mirror sync age < 2× interval | Best effort |
|
||||
|
||||
**Bandwidth metrics (add to graphs / future central metrics):**
|
||||
|
||||
| Metric | Source | Alert |
|
||||
|--------|--------|-------|
|
||||
| `wan_egress_mbps` | router or FE interface | > 70% cap 15 min |
|
||||
| `fe_be_backhaul_mbps` | FE→BE link | > 60% cap |
|
||||
| `origin_egress_gbytes_month` | nginx / object store | > 80% budget |
|
||||
| `cdn_cache_hit_ratio` | CDN analytics | < 85% during OTA |
|
||||
| `geo_deny_serve_total` | BE audit | anomaly spike |
|
||||
| `sfu_media_mbps` | SFU (future) | per-PoP capacity plan |
|
||||
|
||||
Use existing graphs console + future centralized metrics ([GRAFANA_vs_others_graphvis_pivot.md](../GRAFANA_vs_others_graphvis_pivot.md)).
|
||||
|
||||
---
|
||||
|
||||
## 18. Safety constraints (current topology)
|
||||
|
||||
From [bottomline_reminder.txt](../../bottomline_reminder.txt) and [INFRA.md](../INFRA.md):
|
||||
|
||||
| Rule | Detail |
|
||||
|------|--------|
|
||||
| **No risky WAN changes** | Router `134.17.26.161` → FE `10.7.0.10` chain stays until DR-approved migration |
|
||||
| **Port 8089** | Janus/other — not androidcast vhost |
|
||||
| **BE listen 80** | Canonical for FE `proxy_pass` |
|
||||
| **ssh access** | `ssh alpine-be`, `ssh f0xx-monstro`, `ssh f0xx.org` — inspect `tmp/FE_gentoo`, `tmp/BE_alpine` before infra edits |
|
||||
| **Simulation only** | New HVMs on intra `10.7.0.0/8`; no public IP experiments without PO |
|
||||
|
||||
---
|
||||
|
||||
## 19. Phased delivery map
|
||||
|
||||
| Phase | Scope | Alpha relation |
|
||||
|-------|-------|----------------|
|
||||
| **0** | Document + simulation design (this SPEC/DR) | Parallel to alpha |
|
||||
| **1** | Split **build** Docker to dedicated VM; OTA on object disk | Post-alpha |
|
||||
| **2** | **be-web-2** + FE upstream; staging cluster on HVMs | Post-alpha |
|
||||
| **3** | Managed backup; MariaDB replica | Post-alpha |
|
||||
| **4** | SFU + ffmpeg workers (separate SPEC) | Post-alpha |
|
||||
| **5** | Hybrid cloud staging + **CDN for OTA/static** | Strategic |
|
||||
| **5b** | **Bandwidth dashboards** + egress budgets on graphs | Strategic |
|
||||
| **6** | Commercial hooks F5–F9 | Product-dependent |
|
||||
| **7** | **GEO policy** enforcement (F10) for recordings/OTA/catalog | After F3 or PO mandate |
|
||||
|
||||
---
|
||||
|
||||
## 20. Out of scope (this SPEC revision)
|
||||
|
||||
- Concrete Terraform/Pulumi modules
|
||||
- Pricing quotes for GCP/Azure
|
||||
- Legal/compliance for payments and VPN resale
|
||||
- Mobile app changes for commercial features
|
||||
- Replacing MariaDB with Postgres
|
||||
- Sanctions-law mapping per country — PO/legal owns lists; platform stores **deny segments** only
|
||||
|
||||
---
|
||||
|
||||
## 21. Related docs
|
||||
|
||||
| Doc | Link |
|
||||
|-----|------|
|
||||
| Infra topology | [INFRA.md](../INFRA.md) |
|
||||
| BE service map | [20260608_BE_SERVICES_and_infra.md](../20260608_BE_SERVICES_and_infra.md) |
|
||||
| Build / Docker | [BUILD_DEPLOY.md](../BUILD_DEPLOY.md) |
|
||||
| Remote access | [REMOTE_ACCESS_IMPL.md](../REMOTE_ACCESS_IMPL.md), [20260602_REVERSE_SSH_proposals_summary.md](../20260602_REVERSE_SSH_proposals_summary.md) |
|
||||
| Open tasks graph | [OPEN_TASKS_GRAPH.md](../OPEN_TASKS_GRAPH.md) |
|
||||
| Alpha priorities | [20260608_ALPHA_PRIORITIES.md](../20260608_ALPHA_PRIORITIES.md) |
|
||||
| URL shortener | [specs/20100611_3_url_shortener.md](20100611_3_url_shortener.md) |
|
||||
| Design review | [DRs/20100612_1_scaling.md](../DRs/20100612_1_scaling.md) |
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-06-12 | Initial SPEC from draft `20100612_1_scaling.txt` |
|
||||
| 2026-06-12 | Rev. 2 — network bandwidth (§12.3), global availability + GEO policy (§14), metrics |
|
||||
817
docs/specs/20100612_1_scaling.pdf
Normal file
817
docs/specs/20100612_1_scaling.pdf
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user