{"api_version":"aci/1","workload_keyset_digest":"sha256:338c049d02ba75455b770174735d81cfdca26b045509a3563eeffae302405b98","attestation":{"tee_type":"tdx","workload_keyset":{"subject":null,"not_after":1796530946,"receipt_signing_keys":[{"key_id":"dstack-kms-receipt-ed25519-v1","algo":"ed25519","public_key":"c9bd1a9c235cfffbd07d71fb5d9ea5f51d50fe1edb937f1189ab0e771722f34e"}],"e2ee_public_keys":[{"key_id":"dstack-kms-e2ee-v1","algo":"secp256k1-aes-256-gcm-hkdf-sha256","public_key":"04c4201704005a835ae62f8bffea2100cdb059a5a5bd0e79e5306bd3fd1358b7ab854994006c40b075724b3681b5ddd3ac455a2e7ec5d8e57a28b90f97a2f99c1f"},{"key_id":"dstack-kms-e2ee-x25519-v1","algo":"x25519-aes-256-gcm-hkdf-sha256","public_key":"fcc7896f383cdc46bfaef49ba492b565c43697a8b8b381c912a9ab7637ae5e2f"}],"tls_public_keys":[{"spki_sha256":"dd7451eed78f36e8e230047e0e1e91955810f802fea052996fa83add370a00ce","domain":"tee.redpill.ai"},{"spki_sha256":"506c4c558fb9e1c9555c773777838196f04ccdaab012709306eddeee85e17775","domain":"inference.phala.com"},{"spki_sha256":"08ef73dce9d18eeec450dc0e5dabe1a3da2b1d8f06be83e9f8329574fe507e59","domain":"api.redpill.ai"}]},"report_data":"8a5a7298c73a2d9b30e103cea439fc4e207f0b20ec8276c54a63f770f142dab4","source_provenance":{"repo_url":"https://github.com/Dstack-TEE/private-ai-gateway.git","repo_commit":"e9b6ae8a22ff9d8623b280d53bb4164cfe9d0521","image_digest":null,"image_provenance":null},"evidence":{"quote":"040002008100000000000000939a7233f79c4ca9940a0db3957f06074d79685452a1c5725d6ff7f9d09c91b4000000000b0105000000000000000000000000007bf063280e94fb051f5dd7b1fc59ce9aac42bb961df8d44b709c9b0ff87a7b4df648657ba6d1189589feab1d5a3c9a9d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000007020000000000007fe60787222bd394cb516abca2435f22f035ab2cc9c0a4b4b3b148e46297f3d931a237b72f359052c6e657d8c14091730166e91ac6c2325537851bc95fc0e4489df119b7b0204d27f2bae91ec1324ecf1f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d6118f0eeb30e9d9178d2b9106dddd002d979b6fa79bdec415051afae2021384c29a32d2f6454fa369617598378ffb5e4ae609807c765916a31d724786ed74bcb97e168a9d937b04936aeef2015c907eba5ac352c6bcd749c246fd62ffb659ec764489f277a8540189761e72b4238ba36d4d647e11bd958d8ab5522af2a4867b0ff63e06b0c4967f1857236244040a5db0fec3d3aed9d0d9f6dd95a776bb18034524a6d0378158057f7d819797dc7d4f34c6d9f8ff15b34dd32067b7229a71478a5a7298c73a2d9b30e103cea439fc4e207f0b20ec8276c54a63f770f142dab40000000000000000000000000000000000000000000000000000000000000000cc1000004c3343ee7537d4b0a6643ce790d9e92bfcc726614b516573eb476a1bb34193d60c717d748806f5cd812b5d4a7fc4ba5b90a5af6c3d081bc0b3ec229729e1948df0bfa28939a048d1d67a664ae6595ec4514829a10a01f7fc10230dd75c44697bf80c2e1e89a20ca66c1f76795d1cf5553a394c311bcc4601b9231b111d70d6b506004610000005050a0a05ff00020000000000000000000000000000000000000000000000000000000000000000000000000000000015000000000000000700000000000000e5a3a7b5d830c2953b98534c6c59a3a34fdc34e933f7f5898f0a85cf08846bca0000000000000000000000000000000000000000000000000000000000000000dc9e2a7c6f948f17474e34a7fc43ed030f7c1563f1babddf6340c82e0e54a8c5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009c1dc25ab6fd4ae8afb815c1c46f6d3f9bbe7ab053e699d259bf9136751037b20000000000000000000000000000000000000000000000000000000000000000de172a2d47bb1ad994a81e18a280ee6e25d8859b8a3228d7889fe6c1be5e78b3d4c1fd5525db088292d88ded5b4254217437749c32dc696c578c94b4066218be2000000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f05005e0e00002d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d49494538544343424a656741774942416749555359617753595541735374766a483236625541434d506e6f6a797777436759494b6f5a497a6a3045417749770a634445694d434147413155454177775a535735305a577767553064594946424453794251624746305a6d397962534244515445614d42674741315545436777520a535735305a577767513239796347397959585270623234784644415342674e564241634d43314e68626e526849454e7359584a684d51737743515944565151490a44414a445154454c4d416b474131554542684d4356564d774868634e4d6a59774d7a49784d4463304e5455795768634e4d7a4d774d7a49784d4463304e5455790a576a42774d534977494159445651514444426c4a626e526c624342545231676755454e4c49454e6c636e52705a6d6c6a5958526c4d526f77474159445651514b0a4442464a626e526c6243424462334a7762334a6864476c76626a45554d424947413155454277774c553246756447456751327868636d4578437a414a42674e560a4241674d416b4e424d517377435159445651514745774a56557a425a4d424d4742797147534d34394167454743437147534d34394177454841304941424747690a41664533764c54435150696e5470685047695854516c645948654f6f657257516d4b66745141787939446c63353469446b4b537852786e7a38474f7a466f65690a6b584e532b44374575464864666b476a754e796a67674d4e4d4949444354416642674e5648534d4547444157674253566231334e765276683655424a796454300a4d383442567776655644427242674e56485238455a4442694d47436758714263686c706f64485277637a6f764c32467761533530636e567a6447566b633256790a646d6c6a5a584d75615735305a577775593239744c334e6e6543396a5a584a3061575a7059324630615739754c3359304c33426a61324e796244396a595431770a624746305a6d397962535a6c626d4e765a476c755a7a316b5a584977485159445652304f42425945464c6d495a7555304e42506c79336e757041556f41396f590a73625a514d41344741315564447745422f775145417749477744414d42674e5648524d4241663845416a41414d4949434f67594a4b6f5a496876684e415130420a424949434b7a4343416963774867594b4b6f5a496876684e415130424151515162536e396a546e656c37306e456e776e523951666554434341575147436971470a534962345451454e41514977676746554d42414743797147534962345451454e41514942416745464d42414743797147534962345451454e41514943416745460a4d42414743797147534962345451454e41514944416745434d42414743797147534962345451454e41514945416745434d42414743797147534962345451454e0a41514946416745464d42454743797147534962345451454e41514947416749412f7a415142677371686b69472b453042445145434277494241444151426773710a686b69472b4530424451454343414942416a415142677371686b69472b45304244514543435149424144415142677371686b69472b45304244514543436749420a4144415142677371686b69472b45304244514543437749424144415142677371686b69472b45304244514543444149424144415142677371686b69472b4530420a44514543445149424144415142677371686b69472b45304244514543446749424144415142677371686b69472b453042445145434477494241444151426773710a686b69472b45304244514543454149424144415142677371686b69472b45304244514543455149424454416642677371686b69472b45304244514543456751510a425155434167582f4141494141414141414141414144415142676f71686b69472b45304244514544424149414144415542676f71686b69472b453042445145450a424159676f473841414141774477594b4b6f5a496876684e4151304242516f424154416542676f71686b69472b453042445145474242414e78727a30753075620a48784e354e3964353631576d4d45514743697147534962345451454e415163774e6a415142677371686b69472b45304244514548415145422f7a4151426773710a686b69472b45304244514548416745422f7a415142677371686b69472b45304244514548417745422f7a414b42676771686b6a4f5051514441674e49414442460a4169454131305850553751764338564a743949654f724a366c4430744b58462b54746a4e422f6f7a62635235517477434944576e57773946484e7351745359420a70394c5463792f5670376f517367417076763332732f4236576b73760a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949436c6a4343416a32674177494241674956414a567658633239472b487051456e4a3150517a7a674658433935554d416f4743437147534d343942414d430a4d476778476a415942674e5642414d4d45556c756447567349464e48574342536232393049454e424d526f77474159445651514b4442464a626e526c624342440a62334a7762334a6864476c76626a45554d424947413155454277774c553246756447456751327868636d4578437a414a42674e564241674d416b4e424d5173770a435159445651514745774a56557a4165467730784f4441314d6a45784d4455774d5442614677307a4d7a41314d6a45784d4455774d5442614d484178496a41670a42674e5642414d4d47556c756447567349464e4857434251513073675547786864475a76636d306751304578476a415942674e5642416f4d45556c75644756730a49454e76636e4276636d4630615739754d5251774567594456515148444174545957353059534244624746795954454c4d416b474131554543417743513045780a437a414a42674e5642415954416c56544d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741454e53422f377432316c58534f0a3243757a7078773734654a423732457944476757357258437478327456544c7136684b6b367a2b5569525a436e71523770734f766771466553786c6d546c4a6c0a65546d693257597a33714f42757a43427544416642674e5648534d4547444157674251695a517a575770303069664f44744a5653763141624f536347724442530a42674e5648523845537a424a4d45656752614244686b466f64485277637a6f764c324e6c636e52705a6d6c6a5958526c63793530636e567a6447566b633256790a646d6c6a5a584d75615735305a577775593239744c306c756447567355306459556d397664454e424c6d526c636a416442674e5648513445466751556c5739640a7a62306234656c4153636e553944504f4156634c336c517744675944565230504151482f42415144416745474d42494741315564457745422f7751494d4159420a4166384341514177436759494b6f5a497a6a30454177494452774177524149675873566b6930772b6936565947573355462f32327561586530594a446a3155650a6e412b546a44316169356343494359623153416d4435786b66545670766f34556f79695359787244574c6d5552344349394e4b7966504e2b0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949436a7a4343416a53674177494241674955496d554d316c71644e496e7a6737535655723951477a6b6e42717777436759494b6f5a497a6a3045417749770a614445614d4267474131554541777752535735305a5777675530645949464a766233516751304578476a415942674e5642416f4d45556c756447567349454e760a636e4276636d4630615739754d5251774567594456515148444174545957353059534244624746795954454c4d416b47413155454341774351304578437a414a0a42674e5642415954416c56544d423458445445344d4455794d5445774e4455784d466f58445451354d54497a4d54497a4e546b314f566f77614445614d4267470a4131554541777752535735305a5777675530645949464a766233516751304578476a415942674e5642416f4d45556c756447567349454e76636e4276636d46300a615739754d5251774567594456515148444174545957353059534244624746795954454c4d416b47413155454341774351304578437a414a42674e56424159540a416c56544d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a3044415163445167414543366e45774d4449595a4f6a2f69505773437a61454b69370a314f694f534c52466857476a626e42564a66566e6b59347533496a6b4459594c304d784f346d717379596a6c42616c54565978465032734a424b357a6c4b4f420a757a43427544416642674e5648534d4547444157674251695a517a575770303069664f44744a5653763141624f5363477244425342674e5648523845537a424a0a4d45656752614244686b466f64485277637a6f764c324e6c636e52705a6d6c6a5958526c63793530636e567a6447566b63325679646d6c6a5a584d75615735300a5a577775593239744c306c756447567355306459556d397664454e424c6d526c636a416442674e564851344546675155496d554d316c71644e496e7a673753560a55723951477a6b6e4271777744675944565230504151482f42415144416745474d42494741315564457745422f7751494d4159424166384341514577436759490a4b6f5a497a6a3045417749445351417752674968414f572f35516b522b533943695344634e6f6f774c7550524c735747662f59693747535839344267775477670a41694541344a306c72486f4d732b586f356f2f7358364f39515778485241765a55474f6452513763767152586171493d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","quote_report_data":"8a5a7298c73a2d9b30e103cea439fc4e207f0b20ec8276c54a63f770f142dab40000000000000000000000000000000000000000000000000000000000000000","event_log":"[{\"imr\":0,\"event_type\":2147483659,\"digest\":\"5fe4f9ee465a361d077a6892f6bc4d19a82b272fea2e3c2c1ef9c33b4d9050115fca0c6dc7a7f68f91c623de38c48226\",\"event\":\"\",\"event_payload\":\"095464785461626c65000100000000000000af96bb93f2b9b84e9462e0ba745642360090800000000000\"},{\"imr\":0,\"event_type\":2147483658,\"digest\":\"344bc51c980ba621aaa00da3ed7436f7d6e549197dfe699515dfa2c6583d95e6412af21c097d473155875ffd561d6790\",\"event\":\"\",\"event_payload\":\"2946762858585858585858582d585858582d585858582d585858582d58585858585858585858585829000000c0ff000000000040080000000000\"},{\"imr\":0,\"event_type\":2147483649,\"digest\":\"9dc3a1f80bcec915391dcda5ffbb15e7419f77eab462bbf72b42166fb70d50325e37b36f93537a863769bcf9bedae6fb\",\"event\":\"\",\"event_payload\":\"61dfe48bca93d211aa0d00e098032b8c0a00000000000000000000000000000053006500630075007200650042006f006f007400\"},{\"imr\":0,\"event_type\":2147483649,\"digest\":\"6f2e3cbc14f9def86980f5f66fd85e99d63e69a73014ed8a5633ce56eca5b64b692108c56110e22acadcef58c3250f1b\",\"event\":\"\",\"event_payload\":\"61dfe48bca93d211aa0d00e098032b8c0200000000000000000000000000000050004b00\"},{\"imr\":0,\"event_type\":2147483649,\"digest\":\"d607c0efb41c0d757d69bca0615c3a9ac0b1db06c557d992e906c6b7dee40e0e031640c7bfd7bcd35844ef9edeadc6f9\",\"event\":\"\",\"event_payload\":\"61dfe48bca93d211aa0d00e098032b8c030000000000000000000000000000004b0045004b00\"},{\"imr\":0,\"event_type\":2147483649,\"digest\":\"08a74f8963b337acb6c93682f934496373679dd26af1089cb4eaf0c30cf260a12e814856385ab8843e56a9acea19e127\",\"event\":\"\",\"event_payload\":\"cbb219d73a3d9645a3bcdad00e67656f0200000000000000000000000000000064006200\"},{\"imr\":0,\"event_type\":2147483649,\"digest\":\"18cc6e01f0c6ea99aa23f8a280423e94ad81d96d0aeb5180504fc0f7a40cb3619dd39bd6a95ec1680a86ed6ab0f9828d\",\"event\":\"\",\"event_payload\":\"cbb219d73a3d9645a3bcdad00e67656f03000000000000000000000000000000640062007800\"},{\"imr\":0,\"event_type\":4,\"digest\":\"394341b7182cd227c5c6b07ef8000cdfd86136c4292b8e576573ad7ed9ae41019f5818b4b971c9effc60e1ad9f1289f0\",\"event\":\"\",\"event_payload\":\"00000000\"},{\"imr\":0,\"event_type\":10,\"digest\":\"2058afc033c14a2558b6b3ce664343eeb2b7941427251330e66ff3f12eb5c5706a9c651eee49d656c38ba3090b29ff43\",\"event\":\"\",\"event_payload\":\"414350492044415441\"},{\"imr\":0,\"event_type\":10,\"digest\":\"2012337df782407ca9495fe820c6744e398a60731d13a2d11d3cdec1c9541e0946b928b929ff28715cc40bcfd71d92b9\",\"event\":\"\",\"event_payload\":\"414350492044415441\"},{\"imr\":0,\"event_type\":10,\"digest\":\"98731e9fce9603662dba3c4f225a117489960615cb2d893e913c903daeaf09528096875ea94610fdb7eba69a5c20e9e3\",\"event\":\"\",\"event_payload\":\"414350492044415441\"},{\"imr\":1,\"event_type\":2147483651,\"digest\":\"48368ab87e5b8fecfb216bc26bbccfe13081bf8b67d4d2ee7de708e91819eeb56227db2349fc3b04ad5f5719ffd11809\",\"event\":\"\",\"event_payload\":\"18e0fd7a00000000008485000000000000000000000000002a000000000000000403140072f728144ab61e44b8c39ebdd7f893c7040412006b00650072006e0065006c0000007fff0400\"},{\"imr\":0,\"event_type\":2147483650,\"digest\":\"1dd6f7b457ad880d840d41c961283bab688e94e4b59359ea45686581e90feccea3c624b1226113f824f315eb60ae0a7c\",\"event\":\"\",\"event_payload\":\"61dfe48bca93d211aa0d00e098032b8c0900000000000000020000000000000042006f006f0074004f0072006400650072000000\"},{\"imr\":0,\"event_type\":2147483650,\"digest\":\"23ada07f5261f12f34a0bd8e46760962d6b4d576a416f1fea1c64bc656b1d28eacf7047ae6e967c58fd2a98bfa74c298\",\"event\":\"\",\"event_payload\":\"61dfe48bca93d211aa0d00e098032b8c08000000000000003e0000000000000042006f006f0074003000300030003000090100002c0055006900410070007000000004071400c9bdb87cebf8344faaea3ee4af6516a10406140021aa2c4614760345836e8ab6f46623317fff0400\"},{\"imr\":1,\"event_type\":2147483655,\"digest\":\"77a0dab2312b4e1e57a84d865a21e5b2ee8d677a21012ada819d0a98988078d3d740f6346bfe0abaa938ca20439a8d71\",\"event\":\"\",\"event_payload\":\"43616c6c696e6720454649204170706c69636174696f6e2066726f6d20426f6f74204f7074696f6e\"},{\"imr\":1,\"event_type\":4,\"digest\":\"394341b7182cd227c5c6b07ef8000cdfd86136c4292b8e576573ad7ed9ae41019f5818b4b971c9effc60e1ad9f1289f0\",\"event\":\"\",\"event_payload\":\"00000000\"},{\"imr\":2,\"event_type\":6,\"digest\":\"549e21b8d8898430a0be6e53191717f1d30d66588bdafe0055b20d197e415fae963b06ec925f14b027267812d3734fea\",\"event\":\"\",\"event_payload\":\"ed223b8f1a0000004c4f414445445f494d4147453a3a4c6f61644f7074696f6e7300\"},{\"imr\":2,\"event_type\":6,\"digest\":\"fd01285d396a483667123c1a1a64d83b2dd5c6cef2b8803c97522be00c5255ff27feb79f6ea9b46c6856bbe2769bad66\",\"event\":\"\",\"event_payload\":\"ec223b8f0d0000004c696e757820696e6974726400\"},{\"imr\":1,\"event_type\":2147483655,\"digest\":\"214b0bef1379756011344877743fdc2a5382bac6e70362d624ccf3f654407c1b4badf7d8f9295dd3dabdef65b27677e0\",\"event\":\"\",\"event_payload\":\"4578697420426f6f7420536572766963657320496e766f636174696f6e\"},{\"imr\":1,\"event_type\":2147483655,\"digest\":\"0a2e01c85deae718a530ad8c6d20a84009babe6c8989269e950d8cf440c6e997695e64d455c4174a652cd080f6230b74\",\"event\":\"\",\"event_payload\":\"4578697420426f6f742053657276696365732052657475726e656420776974682053756363657373\"},{\"imr\":3,\"event_type\":134217729,\"digest\":\"f9974020ef507068183313d0ca808e0d1ca9b2d1ad0c61f5784e7157c362c06536f5ddacdad4451693f48fcc72fff624\",\"event\":\"system-preparing\",\"event_payload\":\"\"},{\"imr\":3,\"event_type\":134217729,\"digest\":\"ba1c15ff02c641d27669a16a8922fa0cb0644f037482b62270ac2f7052a45142782d0ffaa488e316b8eb73597b971a9c\",\"event\":\"app-id\",\"event_payload\":\"89c51ab856bee41efe78c40309a4deb277a02a0f\"},{\"imr\":3,\"event_type\":134217729,\"digest\":\"738cec8b85ba4b7aa08ac11f8cdf0c2c44d6fb208be43d801a4284bf8f040fddb1cd71a083cecb70547227bea4e071e3\",\"event\":\"compose-hash\",\"event_payload\":\"66e91ac6c2325537851bc95fc0e4489df119b7b0204d27f2bae91ec1324ecf1f\"},{\"imr\":3,\"event_type\":134217729,\"digest\":\"0f2180b75c3e3b32a1caffce116fed3e3e74f5c192459d669df7fa2e69bb18ed2c0e9c215930033388a70a8ac5164a08\",\"event\":\"instance-id\",\"event_payload\":\"2c9b6450ff2310d8a217416147514964116986b3\"},{\"imr\":3,\"event_type\":134217729,\"digest\":\"98bd7e6bd3952720b65027fd494834045d06b4a714bf737a06b874638b3ea00ff402f7f583e3e3b05e921c8570433ac6\",\"event\":\"boot-mr-done\",\"event_payload\":\"\"},{\"imr\":3,\"event_type\":134217729,\"digest\":\"3acc78a7cb3c95fc2bb69949c0a5e68030b985155086e032436239eb2288d09c10a103aac81fd39b35ef337679414afc\",\"event\":\"mr-kms\",\"event_payload\":\"2cccee72c105d74627366b2088957857b1460ce2fad4dc39d2ebdaf59526a677\"},{\"imr\":3,\"event_type\":134217729,\"digest\":\"9381eb17a317a6ebabc892bd4601743106b3f7415f2a82fe73c24b1b12d9fb982a26e7c49632fe2fae01ffbae6bf5ea2\",\"event\":\"os-image-hash\",\"event_payload\":\"8db40cb23008d6213668b5816702d100b37b859a8a5bfd18d938ae3a1b3715bd\"},{\"imr\":3,\"event_type\":134217729,\"digest\":\"f3a83287b84a2356a91901582ff1654035f2de7a628d3a230332b8294b157143686db4fbf2b7d002c082a025e2adf595\",\"event\":\"key-provider\",\"event_payload\":\"7b226e616d65223a226b6d73222c226964223a223330353933303133303630373261383634386365336430323031303630383261383634386365336430333031303730333432303030343862663834663532393131303536343734633230376362353062346538303130643938346638393735656135376238663063386231373530323138316332383365393361363861393836346164313966363239353038636336393966313235626533383731656332653236616230353937653562636134653239623734323566227d\"},{\"imr\":3,\"event_type\":134217729,\"digest\":\"ba51104636900268b0e059fa3d266419d079d1e94aea26fb9fcbb8d764bf4c89a67ac271b8a0d1a3989945132a111fc7\",\"event\":\"storage-fs\",\"event_payload\":\"7a6673\"},{\"imr\":3,\"event_type\":134217729,\"digest\":\"1a76b2a80a0be71eae59f80945d876351a7a3fb8e9fd1ff1cede5734aa84ea11fd72b4edfbb6f04e5a85edd114c751bd\",\"event\":\"system-ready\",\"event_payload\":\"\"}]","vm_config":"{\"os_image_hash\":\"8db40cb23008d6213668b5816702d100b37b859a8a5bfd18d938ae3a1b3715bd\",\"cpu_count\":16,\"memory_size\":34359738368,\"qemu_version\":\"8.2.2\",\"pci_hole64_size\":0,\"hugepages\":false,\"num_gpus\":0,\"num_nvswitches\":0,\"hotplug_off\":false,\"image\":\"dstack-0.5.12\",\"host_share_mode\":\"9p\",\"ovmf_variant\":\"pre202505\",\"spec_version\":1}","app_compose":"{\"allowed_envs\":[\"PRIVATE_AI_GATEWAY_ADMIN_TOKEN\",\"PRIVATE_AI_GATEWAY_CONTROL_URL\",\"PRIVATE_AI_GATEWAY_CONTROL_TOKEN\",\"CLOUDFLARE_API_TOKEN\",\"CERTBOT_EMAIL\",\"NODE_EXPORTER_USER\",\"NODE_EXPORTER_PASSWORD_HASH\",\"PRIVATE_AI_GATEWAY_PULL_TOKEN\",\"PRIVATEMODE_API_KEY\"],\"docker_compose_file\":\"# dstack / Phala deployment manifest for the private-ai-gateway stack via the launcher.\\n#\\n# The launcher image is the measured trust-root: pin it by @sha256 digest (the\\n# digest + this file are hashed into the TDX quote). The launcher clones+builds\\n# the gateway from source at boot; the attestation verifier is\\n# the gateway's vendored confidential_verifier package, so it needs no separate pin.\\n#\\n# The gateway's source pin + static config are inlined as compose `configs:`\\n# (measured into the attestation): the launcher clones the pin and the gateway\\n# reads the config from mounted files. Secret values are supplied as dstack\\n# ENCRYPTED SECRETS and referenced as ${VAR} so dstack injects them — only the\\n# variable names appear in this public file, never the values.\\n\\nservices:\\n  # TLS-terminating ingress: requests Let's Encrypt certs via Cloudflare DNS-01 for a\\n  # KMS-derived TLS key, registers under the dstack gateway domain, and reverse-proxies\\n  # inbound 443 to the launcher's gateway on :8086. Pin by @sha256 digest like the\\n  # launcher. DETERMINISTIC_TLS_KEY survives renewals from 2.7 on\\n  # (Dstack-TEE/dstack-examples#132); published on GHCR only.\\n  dstack-ingress:\\n    image: ghcr.io/dstack-tee/dstack-ingress:2.8@sha256:60e682449534715a2f2e5b72326ca4229209c8e26c931c6f34d27e02200ac086\\n    restart: unless-stopped\\n    # The ingress is an L4 TCP proxy: every client connection maps 1:1 to a\\n    # backend connection to the gateway, so it holds 2 fds per live connection.\\n    # haproxy has no `ulimit-n` in its config; it derives an fd need from MAXCONN\\n    # and raises its OWN soft nofile from the container hard limit. Pin the hard\\n    # limit here (like `launcher`) so MAXCONN is never silently capped by whatever\\n    # the docker daemon default happens to be. 65536 comfortably covers\\n    # MAXCONN 16384 (~33k fds) with headroom.\\n    ulimits:\\n      nofile:\\n        soft: 65536\\n        hard: 65536\\n    ports:\\n      - \\\"443:443\\\"\\n    environment:\\n      # --- public config: literals, measured into the attestation ---\\n      DNS_PROVIDER: cloudflare\\n      GATEWAY_DOMAIN: gateway.${DSTACK_GATEWAY_DOMAIN}\\n      # One TLS key per domain, derived from the dstack KMS (path tls/<domain>), so every\\n      # replica serves the same key and renewals never change it. The gateway reads the\\n      # served certificate's SPKI only at startup, so a key that changed on renewal made\\n      # the attested SPKI stale. The key changes only with a new app ID or KMS.\\n      DETERMINISTIC_TLS_KEY: \\\"true\\\"\\n      # No CAA record. With one ACME account per replica, each replica replaced the\\n      # others' accounturi CAA and renewals failed. Clients pin the attested SPKI, so\\n      # CAA is defense in depth. The existing CAA records must be deleted once by hand:\\n      # this flag only stops writing them.\\n      SET_CAA: \\\"false\\\"\\n      # Concurrent-connection ceiling. The image default is 4096; under real\\n      # streaming load the ingress sat pinned at that ceiling ~84% of the time\\n      # (avg ~3720 concurrent), so once a burst pushed past 4096 haproxy stopped\\n      # accepting and clients saw a hang. Streaming LLM traffic keeps thousands of\\n      # long-lived connections open at once, so give a 4x headroom. Must stay well\\n      # under the nofile hard limit above (2 fds/conn in TCP mode).\\n      MAXCONN: \\\"16384\\\"\\n      # Idle (no-data) timeout, reset on any byte in either direction. The image\\n      # default is 86400s (24h): a client that stops reading, or an upstream that\\n      # goes silent, keeps its slot for a full day, which is how slots leaked\\n      # toward the ceiling. 1800s reaps a genuinely idle/stalled connection while\\n      # never touching a stream that is still flowing (even a slow one). NOTE:\\n      # this is the one value to confirm against the longest legitimate silent\\n      # gap a request can have — e.g. a reasoning model that thinks >30 min before\\n      # its first token would be cut. Raise if such requests are expected.\\n      TIMEOUT_CLIENT: 1800s\\n      TIMEOUT_SERVER: 1800s\\n      # Multi-domain TLS. The ingress is an L4 (TCP) proxy: it terminates TLS for\\n      # each hostname and forwards the DECRYPTED stream untouched, so the client's\\n      # original HTTP Host survives. The gateway uses that Host to pick the\\n      # matching downstream_tls_binding. All public hostnames route by SNI to the\\n      # same gateway listener (compose service name, not localhost). tee.redpill.ai\\n      # and inference.phala.com are the TEE-only domains (see `tee_only_domains`\\n      # below).\\n      DOMAINS: |\\n        tee.redpill.ai\\n        inference.phala.com\\n        api.redpill.ai\\n      ROUTING_MAP: |\\n        tee.redpill.ai=launcher:8086\\n        inference.phala.com=launcher:8086\\n        api.redpill.ai=launcher:8086\\n      # --- secrets: values come from dstack Encrypted Secrets (never commit values) ---\\n      # NOTE: this single Cloudflare token must have DNS-edit (and CAA) rights on\\n      # BOTH the redpill.ai and phala.com zones, or cert issuance for one fails.\\n      CLOUDFLARE_API_TOKEN: ${CLOUDFLARE_API_TOKEN}\\n      CERTBOT_EMAIL: ${CERTBOT_EMAIL}\\n    volumes:\\n      - /var/run/dstack.sock:/var/run/dstack.sock\\n      - /var/run/tappd.sock:/var/run/tappd.sock\\n      - cert-data:/etc/letsencrypt\\n      # Per-domain leaf certs (cert-<domain>.pem) the gateway reads to bind SPKI.\\n      - evidences:/evidences\\n\\n  launcher:\\n    image: ghcr.io/redpill-ai/private-ai-launcher@sha256:c083ff9e6a5ddf10f6c9e9bb1f74cc618deebecfea5208b563c574399db4637c\\n    restart: always\\n    # The gateway opens many fds (inbound conns, per-upstream pools, control-plane\\n    # + verifier calls). Docker's default nofile (often 1024) is far too low: under\\n    # load it hit EMFILE (\\\"Too many open files\\\"), which killed the gateway's accept\\n    # loop. Raise the limit well above peak concurrency.\\n    ulimits:\\n      nofile:\\n        soft: 65536\\n        hard: 65536\\n    # supervisord streams the gateway logs to the container stdout; cap the\\n    # runtime log on disk so it can't grow unbounded (~500MB total, rotated).\\n    # The gateway writes no per-request lines — startup, verification, and its\\n    # own errors only — so the volume is low.\\n    logging:\\n      driver: json-file\\n      options:\\n        max-size: \\\"100m\\\"\\n        max-file: \\\"5\\\"\\n    # no host port: traffic enters via dstack-ingress (443) and reaches the\\n    # gateway over the compose network as http://launcher:8086.\\n    expose:\\n      - \\\"8086\\\"\\n    volumes:\\n      - /var/run/dstack.sock:/var/run/dstack.sock   # only the gateway uses this\\n      - pal-cache:/var/lib/pag/cache                 # cargo/uv build cache (speeds reboots)\\n      - pal-state:/var/lib/pag/state                 # mutable upstreams.json + sessions.jsonl\\n      - evidences:/evidences:ro                      # ingress-minted per-domain leaf certs (cert-<domain>.pem)\\n      - privatemode-manifests:/run/privatemode-manifests:ro\\n    # Static gateway config + upstream seed are mounted from the `configs:` block\\n    # at the bottom (measured into the attestation). The source pin is passed via\\n    # `environment:` instead (see below).\\n    # `docker compose up` does NOT recreate the container when only a `configs:`\\n    # content changes, so the old file stays mounted. Bump this label whenever\\n    # gateway-config / gateway-upstreams change to force a recreate.\\n    labels:\\n      private-ai-gateway.config-rev: \\\"5\\\"\\n    configs:\\n      - source: gateway-config\\n        target: /etc/private-ai-gateway/gateway.config.json\\n      - source: gateway-upstreams\\n        target: /etc/private-ai-gateway/upstreams.seed.json\\n    secrets:\\n      - source: privatemode-api-key\\n        target: privatemode-api-key\\n    depends_on:\\n      - privatemode-proxy\\n    environment:\\n      # The gateway reads its whole policy from this mounted static config file\\n      # (see `configs:` above); CONFIG_PATH is the only gateway-facing env var.\\n      - PRIVATE_AI_GATEWAY_CONFIG_PATH=/etc/private-ai-gateway/gateway.config.json\\n      # Tracing filter. The gateway does not log requests; failures are\\n      # accounted for in the control plane's usage pipeline.\\n      - RUST_LOG=info\\n      # Gateway source pin. These literals are measured into the attestation, and\\n      # because they live in `environment:` (part of Compose's config-hash) a\\n      # COMMIT_SHA bump forces a container recreate on `phala deploy` — so the new\\n      # pin actually takes effect. (A `configs:`-file pin would not: Compose ignores\\n      # config content when deciding to recreate, so the change would never apply.)\\n      # entrypoint.sh writes /etc/git-launcher/gateway.conf from these, and the\\n      # gateway reads that file as its attested source provenance.\\n      - PRIVATE_AI_GATEWAY_REPO_URL=https://github.com/Dstack-TEE/private-ai-gateway.git\\n      - PRIVATE_AI_GATEWAY_REPO_COMMIT=e9b6ae8a22ff9d8623b280d53bb4164cfe9d0521\\n      # NEAR AI verification posts TDX quotes to the dstack-verifier sidecar below;\\n      # reach it by compose service name (localhost would be the launcher itself).\\n      - DSTACK_VERIFIER_URL=http://dstack-verifier:8080\\n      # --- secrets: values come from dstack Encrypted Secrets (never commit values) ---\\n      # Control-plane credentials. The gateway's in-process middleware reads these\\n      # from the `middleware` config section below (rendered from the same secrets).\\n      - PRIVATE_AI_GATEWAY_CONTROL_URL=${PRIVATE_AI_GATEWAY_CONTROL_URL}\\n      - PRIVATE_AI_GATEWAY_CONTROL_TOKEN=${PRIVATE_AI_GATEWAY_CONTROL_TOKEN}\\n\\n  # Official Privatemode proxy, co-deployed on the private Compose network.\\n  privatemode-proxy:\\n    image: ghcr.io/edgelesssys/privatemode/privatemode-proxy@sha256:b2d564aa5f51dfc3cee60424108ca3de1ed0b5349d67e7eac7efe17e62d68d5b\\n    command:\\n      - --workspace\\n      - /var/lib/privatemode\\n      - --port\\n      - \\\"8080\\\"\\n      - --api-key\\n      - \\\"@/run/secrets/privatemode-api-key\\\"\\n      - --api-key-fallback=always\\n      - --disable-prompt-cache=false\\n      - --nvidia-ocsp-allow-unknown=false\\n      - --nvidia-ocsp-revoked-grace-period=0\\n    tmpfs:\\n      - /var/lib/privatemode:mode=0700\\n    volumes:\\n      - privatemode-manifests:/var/lib/privatemode/manifests\\n    secrets:\\n      - source: privatemode-api-key\\n        target: privatemode-api-key\\n    restart: unless-stopped\\n    logging:\\n      driver: json-file\\n      options:\\n        max-size: \\\"50m\\\"\\n        max-file: \\\"5\\\"\\n\\n  # TDX quote verifier for the NEAR AI path (reproduces ACPI/boot measurements via\\n  # QEMU). The gateway reaches it at http://dstack-verifier:8080 (DSTACK_VERIFIER_URL).\\n  dstack-verifier:\\n    image: ghcr.io/dstack-tee/dstack-verifier:0.6.3@sha256:22aeb6808e38a56115f843d4d66f4852407e72f6080d5f8781fd82d9f819db41\\n    restart: unless-stopped\\n    logging:\\n      driver: json-file\\n      options:\\n        max-size: \\\"50m\\\"\\n        max-file: \\\"5\\\"\\n\\n  # Host-metrics exporter for observing this CVM (CPU/mem/disk/net/fd). Inside a\\n  # CVM the container's /, /proc, /sys ARE the guest's, so node-exporter reports\\n  # the CVM's own state. Pinned by @sha256 like the other images (measured into\\n  # the attestation). Exposed on 9100 (reached via the dstack gateway) and guarded\\n  # by HTTP basic auth — both username and the bcrypt password HASH are injected as\\n  # dstack Encrypted Secrets, so neither appears in this measured file.\\n  node-exporter:\\n    image: prom/node-exporter:v1.8.2@sha256:4032c6d5bfd752342c3e631c2f1de93ba6b86c41db6b167b9a35372c139e7706\\n    restart: unless-stopped\\n    pid: host\\n    command:\\n      - '--path.rootfs=/host'\\n      - '--path.procfs=/host/proc'\\n      - '--path.sysfs=/host/sys'\\n      - '--web.config.file=/etc/node-exporter/web-config.yml'\\n    volumes:\\n      - /:/host:ro,rslave\\n    configs:\\n      - source: node-exporter-web-config\\n        target: /etc/node-exporter/web-config.yml\\n    ports:\\n      - \\\"9100:9100\\\"\\n    logging:\\n      driver: json-file\\n      options:\\n        max-size: \\\"10m\\\"\\n        max-file: \\\"3\\\"\\n\\nvolumes:\\n  pal-cache:\\n  pal-state:\\n  cert-data:   # Let's Encrypt certs/keys persisted for dstack-ingress\\n  evidences:   # ingress attestation evidence + per-domain leaf certs, shared read-only with the gateway\\n  privatemode-manifests:\\n\\n# Inlined, measured into the attestation. Public pins use LITERAL values so the\\n# code version is bound into compose_hash; only secrets use ${VAR} (injected by\\n# dstack, never measured).\\nconfigs:\\n  # Static gateway config. `admin_token`, the dedicated upstream pull token, and\\n  # the control-plane credentials are secrets — ${VAR} keeps their values out of\\n  # the measured compose while the presence and destination stay measured. The\\n  # `middleware` section runs the in-process middleware, which consults the control\\n  # plane at `control_url`.\\n  gateway-config:\\n    content: |\\n      {\\n        \\\"bind\\\": \\\"0.0.0.0:8086\\\",\\n        \\\"state_dir\\\": \\\"/var/lib/pag/state\\\",\\n        \\\"upstream_config_seed_path\\\": \\\"/etc/private-ai-gateway/upstreams.seed.json\\\",\\n        \\\"upstream_pull\\\": {\\n          \\\"url\\\": \\\"https://service.redpill.ai/api/admin/gateway-upstreams/config\\\",\\n          \\\"token\\\": \\\"${PRIVATE_AI_GATEWAY_PULL_TOKEN:?set PRIVATE_AI_GATEWAY_PULL_TOKEN}\\\",\\n          \\\"refresh_seconds\\\": 300,\\n          \\\"request_timeout_seconds\\\": 90\\n        },\\n        \\\"admin_token\\\": \\\"${PRIVATE_AI_GATEWAY_ADMIN_TOKEN:?set PRIVATE_AI_GATEWAY_ADMIN_TOKEN}\\\",\\n        \\\"tls\\\": {\\n          \\\"domain_certificates\\\": [\\n            { \\\"domain\\\": \\\"tee.redpill.ai\\\", \\\"certificate_path\\\": \\\"/evidences/cert-tee.redpill.ai.pem\\\" },\\n            { \\\"domain\\\": \\\"inference.phala.com\\\", \\\"certificate_path\\\": \\\"/evidences/cert-inference.phala.com.pem\\\" },\\n            { \\\"domain\\\": \\\"api.redpill.ai\\\", \\\"certificate_path\\\": \\\"/evidences/cert-api.redpill.ai.pem\\\" }\\n          ]\\n        },\\n        \\\"middleware\\\": {\\n          \\\"control_url\\\": \\\"${PRIVATE_AI_GATEWAY_CONTROL_URL:?set PRIVATE_AI_GATEWAY_CONTROL_URL}\\\",\\n          \\\"control_token\\\": \\\"${PRIVATE_AI_GATEWAY_CONTROL_TOKEN:?set PRIVATE_AI_GATEWAY_CONTROL_TOKEN}\\\",\\n          \\\"tee_only_domains\\\": [\\\"tee.redpill.ai\\\", \\\"inference.phala.com\\\"]\\n        },\\n        \\\"privatemode_proxy\\\": {\\n          \\\"base_url\\\": \\\"http://privatemode-proxy:8080\\\",\\n          \\\"manifest_log_path\\\": \\\"/run/privatemode-manifests/log.txt\\\",\\n          \\\"credential_path\\\": \\\"/run/secrets/privatemode-api-key\\\",\\n          \\\"credential_sha256\\\": \\\"70809859565c16e6c7bde0b4ed896d99f68fe53432bcbcf348a642c385d1febe\\\",\\n          \\\"proxy_image_digest\\\": \\\"sha256:b2d564aa5f51dfc3cee60424108ca3de1ed0b5349d67e7eac7efe17e62d68d5b\\\"\\n        }\\n      }\\n  # Initial upstream routes: seeded into <state_dir>/upstreams.json only when empty,\\n  # then managed at runtime via PUT /v1/admin/upstreams (does not touch this file).\\n  gateway-upstreams:\\n    content: |\\n      []\\n  # node-exporter basic-auth credentials. exporter-toolkit's basic_auth_users maps\\n  # username -> bcrypt hash; both are ${VAR} so dstack substitutes the injected\\n  # secret values when rendering this file into the container — only the literals\\n  # are measured. NODE_EXPORTER_PASSWORD_HASH must be a bcrypt hash\\n  # (`htpasswd -nBC 10 \\\"\\\" | tr -d ':\\\\n'`), NOT the plaintext password; the plaintext\\n  # goes to the external Prometheus scrape config.\\n  node-exporter-web-config:\\n    content: |\\n      basic_auth_users:\\n        ${NODE_EXPORTER_USER:?set NODE_EXPORTER_USER}: ${NODE_EXPORTER_PASSWORD_HASH:?set NODE_EXPORTER_PASSWORD_HASH}\\n\\nsecrets:\\n  privatemode-api-key:\\n    environment: PRIVATEMODE_API_KEY\\n\",\"features\":[\"kms\",\"tproxy-net\"],\"gateway_enabled\":true,\"kms_enabled\":true,\"local_key_provider_enabled\":false,\"manifest_version\":2,\"name\":\"\",\"no_instance_id\":false,\"pre_launch_script\":\"#!/bin/bash\\necho \\\"----------------------------------------------\\\"\\necho \\\"Running Phala Cloud Pre-Launch Script v0.0.20\\\"\\necho \\\"----------------------------------------------\\\"\\nset -e\\n\\n# Function: notify host\\n\\nnotify_host() {\\n    if command -v dstack-util >/dev/null 2>&1; then\\n        dstack-util notify-host -e \\\"$1\\\" -d \\\"$2\\\"\\n    else\\n        tdxctl notify-host -e \\\"$1\\\" -d \\\"$2\\\"\\n    fi\\n}\\n\\nnotify_host_hoot_info() {\\n    notify_host \\\"boot.progress\\\" \\\"$1\\\"\\n}\\n\\nnotify_host_hoot_error() {\\n    notify_host \\\"boot.error\\\" \\\"$1\\\"\\n}\\n\\n# Function: Perform Docker cleanup\\nperform_cleanup() {\\n    echo \\\"Pruning unused images\\\"\\n    docker image prune -af\\n    echo \\\"Pruning unused volumes\\\"\\n    docker volume prune -f\\n    notify_host_hoot_info \\\"docker cleanup completed\\\"\\n}\\n\\n# Function: Check Docker login status without exposing credentials\\ncheck_docker_login() {\\n    local registry=\\\"${1-}\\\"\\n\\n    # When registry is specified, check auth entry for that registry in Docker config\\n    if [[ -n \\\"$registry\\\" ]]; then\\n        local docker_config_path=\\\"${DOCKER_CONFIG:-${HOME:-}/.docker}/config.json\\\"\\n        if [[ -f \\\"$docker_config_path\\\" ]] && grep -q \\\"$registry\\\" \\\"$docker_config_path\\\"; then\\n            return 0\\n        else\\n            return 1\\n        fi\\n    fi\\n\\n    # Fallback check when no explicit registry is provided\\n    if docker info 2>/dev/null | grep -q \\\"Username\\\"; then\\n        return 0\\n    else\\n        return 1\\n    fi\\n}\\n\\n# Function: print the SHA256 fingerprint of every authorized key\\n#\\n# Output matches `ssh-keygen -lf` and the fingerprints GitHub and Phala Cloud\\n# display, so an operator can match the boot log against the account's key list.\\n# The guest image ships neither ssh-keygen nor a base64 applet (BusyBox has\\n# base32 only), so openssl does both the decode and the digest.\\nprint_authorized_key_fingerprints() {\\n    local file=\\\"$1\\\"\\n    if ! command -v openssl >/dev/null 2>&1; then\\n        echo \\\"openssl not available; skipping SSH key fingerprints\\\"\\n        return 0\\n    fi\\n\\n    # Locals start unset under bash; initialize so set -u (inherited from\\n    # dstack app-compose.sh source) does not trip the read loop guard.\\n    local key_type=\\\"\\\" blob=\\\"\\\" comment=\\\"\\\" decoded_bytes=\\\"\\\" fingerprint=\\\"\\\"\\n    # `|| [[ -n \\\"$key_type\\\" ]]` keeps the final line when the file does not end\\n    # with a newline; read returns non-zero there but still fills the variables.\\n    while read -r key_type blob comment || [[ -n \\\"$key_type\\\" ]]; do\\n        if [[ -z \\\"$blob\\\" ]]; then\\n            continue\\n        fi\\n        # openssl exits 0 on undecodable input and emits nothing, which would\\n        # otherwise print the SHA256 of the empty string as a real fingerprint.\\n        decoded_bytes=$(printf '%s' \\\"$blob\\\" | openssl base64 -d -A 2>/dev/null | wc -c || true)\\n        if [[ \\\"$decoded_bytes\\\" -eq 0 ]]; then\\n            echo \\\"  $key_type <unreadable key>\\\"\\n            continue\\n        fi\\n        fingerprint=$(printf '%s' \\\"$blob\\\" | openssl base64 -d -A 2>/dev/null | openssl dgst -sha256 -binary 2>/dev/null | openssl base64 -A 2>/dev/null | tr -d '=' || true)\\n        echo \\\"  $key_type SHA256:$fingerprint\\\"\\n    done < \\\"$file\\\"\\n}\\n\\n# Main logic starts here\\necho \\\"Starting login process...\\\"\\n\\n# Optional env vars below use ${VAR-} / ${VAR:-} so this script stays safe when\\n# app-compose.sh sources it under `set -euo pipefail` without registry/SSH/root\\n# credentials set. Bare \\\"$VAR\\\" throws \\\"unbound variable\\\" under nounset.\\nDOCKER_REGISTRY_TARGET=\\\"${DSTACK_DOCKER_REGISTRY:-docker.io}\\\"\\n\\n# Check if Docker credentials exist\\nif [[ -n \\\"${DSTACK_DOCKER_USERNAME-}\\\" && -n \\\"${DSTACK_DOCKER_PASSWORD-}\\\" ]]; then\\n    echo \\\"Docker credentials found\\\"\\n    echo \\\"Target Docker registry: $DOCKER_REGISTRY_TARGET\\\"\\n\\n    # Check if already logged in\\n    if check_docker_login \\\"${DSTACK_DOCKER_REGISTRY-}\\\"; then\\n        echo \\\"Already logged in to Docker registry: $DOCKER_REGISTRY_TARGET\\\"\\n    else\\n        echo \\\"Logging in to Docker registry: $DOCKER_REGISTRY_TARGET\\\"\\n        # Login without exposing password in process list\\n        if [[ -n \\\"${DSTACK_DOCKER_REGISTRY-}\\\" ]]; then\\n            if ! echo \\\"$DSTACK_DOCKER_PASSWORD\\\" | docker login -u \\\"$DSTACK_DOCKER_USERNAME\\\" --password-stdin \\\"$DSTACK_DOCKER_REGISTRY\\\"; then\\n                echo \\\"Docker login failed: $DOCKER_REGISTRY_TARGET\\\"\\n                notify_host_hoot_error \\\"docker login failed\\\"\\n                exit 1\\n            fi\\n        elif ! echo \\\"$DSTACK_DOCKER_PASSWORD\\\" | docker login -u \\\"$DSTACK_DOCKER_USERNAME\\\" --password-stdin; then\\n            echo \\\"Docker login failed: $DOCKER_REGISTRY_TARGET\\\"\\n            notify_host_hoot_error \\\"docker login failed\\\"\\n            exit 1\\n        fi\\n        echo \\\"Docker login successful: $DOCKER_REGISTRY_TARGET\\\"\\n    fi\\n# Check if AWS ECR credentials exist\\nelif [[ -n \\\"${DSTACK_AWS_ACCESS_KEY_ID-}\\\" && -n \\\"${DSTACK_AWS_SECRET_ACCESS_KEY-}\\\" && -n \\\"${DSTACK_AWS_REGION-}\\\" && -n \\\"${DSTACK_AWS_ECR_REGISTRY-}\\\" ]]; then\\n    echo \\\"AWS ECR credentials found\\\"\\n\\n    # Check if AWS CLI is installed\\n    if [ ! -f \\\"./aws/dist/aws\\\" ]; then\\n        notify_host_hoot_info \\\"awscli not installed, installing...\\\"\\n        echo \\\"AWS CLI not installed, installing...\\\"\\n        if ! curl \\\"https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.24.14.zip\\\" -o \\\"awscliv2.zip\\\"; then\\n            echo \\\"AWS CLI download failed\\\"\\n            notify_host_hoot_error \\\"awscli install failed\\\"\\n            exit 1\\n        fi\\n        if ! echo \\\"6ff031a26df7daebbfa3ccddc9af1450 awscliv2.zip\\\" | md5sum -c; then\\n            echo \\\"MD5 checksum failed\\\"\\n            notify_host_hoot_error \\\"awscli install failed\\\"\\n            exit 1\\n        fi\\n        unzip awscliv2.zip &> /dev/null\\n    else\\n        echo \\\"AWS CLI is already installed: ./aws/dist/aws\\\"\\n    fi\\n\\n    # Set AWS credentials as environment variables\\n    export AWS_ACCESS_KEY_ID=\\\"$DSTACK_AWS_ACCESS_KEY_ID\\\"\\n    export AWS_SECRET_ACCESS_KEY=\\\"$DSTACK_AWS_SECRET_ACCESS_KEY\\\"\\n    export AWS_DEFAULT_REGION=\\\"$DSTACK_AWS_REGION\\\"\\n\\n    # Set session token if provided (for temporary credentials)\\n    if [[ -n \\\"${DSTACK_AWS_SESSION_TOKEN-}\\\" ]]; then\\n        echo \\\"AWS session token found, using temporary credentials\\\"\\n        export AWS_SESSION_TOKEN=\\\"$DSTACK_AWS_SESSION_TOKEN\\\"\\n    fi\\n\\n    # Test AWS credentials before attempting ECR login\\n    echo \\\"Testing AWS credentials...\\\"\\n    if ! ./aws/dist/aws sts get-caller-identity &> /dev/null; then\\n        echo \\\"AWS credentials test failed\\\"\\n        # For session token credentials, this might be expected if they're expired\\n        # Log warning but don't fail startup\\n        if [[ -n \\\"${DSTACK_AWS_SESSION_TOKEN-}\\\" ]]; then\\n            echo \\\"Warning: AWS temporary credentials may have expired, continuing startup\\\"\\n            notify_host_hoot_info \\\"AWS temporary credentials may have expired\\\"\\n        else\\n            echo \\\"AWS credentials test failed\\\"\\n            notify_host_hoot_error \\\"Invalid AWS credentials\\\"\\n            exit 1\\n        fi\\n    else\\n        echo \\\"Logging in to AWS ECR...\\\"\\n        if ./aws/dist/aws ecr get-login-password --region \\\"$DSTACK_AWS_REGION\\\" | docker login --username AWS --password-stdin \\\"$DSTACK_AWS_ECR_REGISTRY\\\"; then\\n            echo \\\"AWS ECR login successful\\\"\\n            notify_host_hoot_info \\\"AWS ECR login successful\\\"\\n        else\\n            echo \\\"AWS ECR login failed\\\"\\n            # For session token credentials, don't fail startup if login fails\\n            if [[ -n \\\"${DSTACK_AWS_SESSION_TOKEN-}\\\" ]]; then\\n                echo \\\"Warning: AWS ECR login failed with temporary credentials, continuing startup\\\"\\n                notify_host_hoot_info \\\"AWS ECR login failed with temporary credentials\\\"\\n            else\\n                notify_host_hoot_error \\\"AWS ECR login failed\\\"\\n                exit 1\\n            fi\\n        fi\\n    fi\\nfi\\n\\nperform_cleanup\\n\\n#\\n# GHCR image pull access verification (pure HTTP, no docker daemon)\\n#\\nif [[ \\\"$DOCKER_REGISTRY_TARGET\\\" == \\\"ghcr.io\\\" && -n \\\"${DSTACK_DOCKER_USERNAME-}\\\" && -n \\\"${DSTACK_DOCKER_PASSWORD-}\\\" ]]; then\\n    # Strip surrounding quotes from compose image refs. Two tr calls keep the\\n    # intent obvious inside this Python string (no quote-concatenation idiom).\\n    COMPOSE_IMAGES=$(grep 'image:' /dstack/docker-compose.yaml 2>/dev/null | awk '{print $2}' | tr -d '\\\"' | tr -d \\\"'\\\" || true)\\n    HEADER=\\\"Accept: application/vnd.oci.image.index.v1+json,application/vnd.oci.image.manifest.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.docker.distribution.manifest.v2+json\\\"\\n    for img in $COMPOSE_IMAGES; do\\n        [[ \\\"$img\\\" != ghcr.io/* ]] && continue\\n        ref=\\\"${img#ghcr.io/}\\\"\\n        # Digest-pinned refs (repo@sha256:...) use the digest as the manifest\\n        # reference; tag refs (repo:tag) use the tag, defaulting to latest.\\n        if [[ \\\"$ref\\\" == *@* ]]; then\\n            repo=\\\"${ref%%@*}\\\"\\n            reference=\\\"${ref#*@}\\\"\\n        else\\n            repo=\\\"${ref%%:*}\\\"\\n            reference=\\\"${ref##*:}\\\"\\n            [[ \\\"$reference\\\" == \\\"$ref\\\" ]] && reference=\\\"latest\\\"\\n        fi\\n        echo \\\"Verifying GHCR pull access: $img\\\"\\n        token=$(curl -sf -u \\\"$DSTACK_DOCKER_USERNAME:$DSTACK_DOCKER_PASSWORD\\\" \\\"https://ghcr.io/token?service=ghcr.io&scope=repository:${repo}:pull\\\" | jq -r '.token // empty' || true)\\n        if [[ -z \\\"${token-}\\\" ]]; then\\n            echo \\\"ERROR: GHCR token exchange failed for $img\\\"\\n            notify_host_hoot_error \\\"GHCR token exchange failed: $img\\\"\\n            exit 1\\n        fi\\n        http_code=$(curl -s -o /dev/null -w \\\"%{http_code}\\\" -H \\\"Authorization: Bearer $token\\\" -H \\\"$HEADER\\\" \\\"https://ghcr.io/v2/${repo}/manifests/${reference}\\\")\\n        if [[ \\\"$http_code\\\" != \\\"200\\\" ]]; then\\n            echo \\\"ERROR: GHCR pull access denied for $img (HTTP $http_code)\\\"\\n            notify_host_hoot_error \\\"GHCR pull access denied: $img (HTTP $http_code)\\\"\\n            exit 1\\n        fi\\n        echo \\\"GHCR pull access OK: $img\\\"\\n    done\\nfi\\n\\n#\\n# Pull latest images from docker-compose.yaml so existing CVMs pick up new tags.\\n# Pull is fail-soft: short-lived tokens may expire between login and pull, but\\n# falling back to already-cached images is preferable to blocking boot.\\n#\\necho \\\"Images before pull:\\\"\\ndocker images --format '{{.Repository}}:{{.Tag}} {{.ID}} ({{.CreatedSince}})'\\n\\necho \\\"Pulling latest images from /dstack/docker-compose.yaml...\\\"\\nif docker compose -f /dstack/docker-compose.yaml pull; then\\n    echo \\\"docker compose pull completed\\\"\\n    notify_host_hoot_info \\\"docker compose pull completed\\\"\\nelse\\n    echo \\\"WARNING: docker compose pull failed; continuing with existing images\\\"\\n    notify_host_hoot_info \\\"docker compose pull failed; using existing images\\\"\\nfi\\n\\necho \\\"Images after pull:\\\"\\ndocker images --format '{{.Repository}}:{{.Tag}} {{.ID}} ({{.CreatedSince}})'\\n\\n#\\n# Set root password.\\n#\\necho \\\"Setting root password..\\\"\\n\\n# Check if password files are writable\\nPASSWD_WRITABLE=true\\nif [ ! -w /etc/passwd ]; then\\n    echo \\\"Warning: /etc/passwd is read-only\\\"\\n    PASSWD_WRITABLE=false\\nfi\\nif [ ! -w /etc/shadow ]; then\\n    echo \\\"Warning: /etc/shadow is read-only\\\"\\n    PASSWD_WRITABLE=false\\nfi\\n\\nif [ \\\"$PASSWD_WRITABLE\\\" = \\\"false\\\" ]; then\\n    echo \\\"Skipping password setup due to read-only file system\\\"\\nelse\\n    # Check if chpasswd is available\\n    if command -v chpasswd >/dev/null 2>&1; then\\n        echo \\\"Using chpasswd method\\\"\\n\\n        if [ -n \\\"${DSTACK_ROOT_PASSWORD-}\\\" ]; then\\n            echo \\\"Setting root password from user..\\\"\\n            echo \\\"root:$DSTACK_ROOT_PASSWORD\\\" | chpasswd\\n            unset DSTACK_ROOT_PASSWORD\\n            echo \\\"Root password set/updated from DSTACK_ROOT_PASSWORD\\\"\\n        elif [ -z \\\"$(grep '^root:' /etc/shadow 2>/dev/null | cut -d: -f2)\\\" ]; then\\n            echo \\\"Setting random root password..\\\"\\n            # dstack guest busybox lacks head -c (FANCY_HEAD off); dd is the\\n            # last pipeline command, so its 0 status masks tr's SIGPIPE exit.\\n            DSTACK_ROOT_PASSWORD=$(LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | dd bs=1 count=32 2>/dev/null)\\n            echo \\\"root:$DSTACK_ROOT_PASSWORD\\\" | chpasswd\\n            unset DSTACK_ROOT_PASSWORD\\n            echo \\\"Root password set (random auto-init)\\\"\\n        else\\n            echo \\\"Root password already set; no changes.\\\"\\n        fi\\n    else\\n        # No chpasswd: do not pipe into interactive passwd(1). It often reads\\n        # /dev/tty and fails with \\\"password for root is unchanged\\\" during boot.\\n        echo \\\"Using passwd method\\\"\\n\\n        if [ -n \\\"${DSTACK_ROOT_PASSWORD-}\\\" ]; then\\n            echo \\\"Setting root password from user..\\\"\\n            if command -v passwd >/dev/null 2>&1 && passwd --help 2>&1 | grep -q -- '--stdin'; then\\n                echo \\\"$DSTACK_ROOT_PASSWORD\\\" | passwd --stdin root\\n            elif command -v openssl >/dev/null 2>&1; then\\n                # busybox passwd has no --stdin; write the SHA-512 crypt hash\\n                # into /etc/shadow directly instead of driving interactive passwd.\\n                HASHED_ROOT_PASSWORD=$(openssl passwd -6 \\\"$DSTACK_ROOT_PASSWORD\\\")\\n                sed -i \\\"s|^root:[^:]*:|root:${HASHED_ROOT_PASSWORD}:|\\\" /etc/shadow\\n                unset HASHED_ROOT_PASSWORD\\n            else\\n                echo \\\"Error: cannot set DSTACK_ROOT_PASSWORD non-interactively\\\"\\n                echo \\\"Need chpasswd, passwd --stdin, or openssl\\\"\\n                exit 1\\n            fi\\n            unset DSTACK_ROOT_PASSWORD\\n            echo \\\"Root password set/updated from DSTACK_ROOT_PASSWORD\\\"\\n        elif [ -z \\\"$(grep '^root:' /etc/shadow 2>/dev/null | cut -d: -f2)\\\" ]; then\\n            # Never lock the account here: sshd built without PAM (dstack dev\\n            # images) treats a locked account as \\\"deny all authentication\\\",\\n            # which breaks public-key SSH too. Set a discarded random password\\n            # by writing its crypt hash into /etc/shadow directly.\\n            if command -v openssl >/dev/null 2>&1; then\\n                echo \\\"Setting random root password..\\\"\\n                DSTACK_ROOT_PASSWORD=$(LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | dd bs=1 count=32 2>/dev/null)\\n                HASHED_ROOT_PASSWORD=$(openssl passwd -6 \\\"$DSTACK_ROOT_PASSWORD\\\")\\n                sed -i \\\"s|^root:[^:]*:|root:${HASHED_ROOT_PASSWORD}:|\\\" /etc/shadow\\n                unset DSTACK_ROOT_PASSWORD HASHED_ROOT_PASSWORD\\n                echo \\\"Root password set (random auto-init)\\\"\\n            else\\n                echo \\\"Warning: no openssl to hash a random password; leaving root password empty\\\"\\n            fi\\n        else\\n            echo \\\"Root password already set; no changes.\\\"\\n        fi\\n    fi\\nfi\\n\\n#\\n# Set SSH authorized keys\\n#\\nif mkdir -p /home/root/.ssh 2>/dev/null; then\\n    if [[ -n \\\"${DSTACK_ROOT_PUBLIC_KEY-}\\\" ]]; then\\n        echo \\\"$DSTACK_ROOT_PUBLIC_KEY\\\" > /home/root/.ssh/authorized_keys\\n        unset DSTACK_ROOT_PUBLIC_KEY\\n        echo \\\"Root public key set\\\"\\n    fi\\n    if [[ -n \\\"${DSTACK_AUTHORIZED_KEYS-}\\\" ]]; then\\n        echo \\\"$DSTACK_AUTHORIZED_KEYS\\\" > /home/root/.ssh/authorized_keys\\n        unset DSTACK_AUTHORIZED_KEYS\\n        echo \\\"Root authorized_keys set\\\"\\n    fi\\n\\n    if [[ -f /dstack/user_config ]] && jq empty /dstack/user_config 2>/dev/null; then\\n        if [[ $(jq 'has(\\\"ssh_authorized_keys\\\")' /dstack/user_config 2>/dev/null) == \\\"true\\\" ]]; then\\n            # jq -r terminates the value with a newline; jq -j does not, which\\n            # both concatenated the next appended key onto the same line and\\n            # made line-based counting report one key fewer than the file holds.\\n            jq -r '.ssh_authorized_keys' /dstack/user_config >> /home/root/.ssh/authorized_keys\\n            # Drop duplicates and the blank line an empty key list produces.\\n            sort -u /home/root/.ssh/authorized_keys > /home/root/.ssh/authorized_keys.tmp\\n            mv /home/root/.ssh/authorized_keys.tmp /home/root/.ssh/authorized_keys\\n            KEY_COUNT=$(grep -c '^[^[:space:]]' /home/root/.ssh/authorized_keys || true)\\n            echo \\\"Set root authorized_keys from user preferences, total $KEY_COUNT keys\\\"\\n            print_authorized_key_fingerprints /home/root/.ssh/authorized_keys\\n        fi\\n    fi\\nelse\\n    echo \\\"Warning: Cannot create /home/root/.ssh directory (read-only file system?)\\\"\\n    echo \\\"Skipping SSH key setup\\\"\\nfi\\n\\nif [[ -S /var/run/dstack.sock ]]; then\\n    export DSTACK_APP_ID=$(curl -s --unix-socket /var/run/dstack.sock http://dstack/Info | jq -j .app_id)\\nelif [[ -S /var/run/tappd.sock ]]; then\\n    export DSTACK_APP_ID=$(curl -s --unix-socket /var/run/tappd.sock http://dstack/prpc/Tappd.Info | jq -j .app_id)\\nfi\\n# Check if DSTACK_GATEWAY_DOMAIN is not set, try to get it from user_config or app-compose.json\\n# Priority: user_config > app-compose.json\\nif [[ -z \\\"${DSTACK_GATEWAY_DOMAIN-}\\\" ]]; then\\n    # First try to get from /dstack/user_config if it exists and is valid JSON\\n    if [[ -f /dstack/user_config ]] && jq empty /dstack/user_config 2>/dev/null; then\\n        if [[ $(jq 'has(\\\"default_gateway_domain\\\")' /dstack/user_config 2>/dev/null) == \\\"true\\\" ]]; then\\n            export DSTACK_GATEWAY_DOMAIN=$(jq -j '.default_gateway_domain' /dstack/user_config)\\n        fi\\n    fi\\n\\n    # If still not set, try to get from app-compose.json\\n    if [[ -z \\\"${DSTACK_GATEWAY_DOMAIN-}\\\" ]] && [[ -f app-compose.json ]] && [[ $(jq 'has(\\\"default_gateway_domain\\\")' app-compose.json) == \\\"true\\\" ]]; then\\n        export DSTACK_GATEWAY_DOMAIN=$(jq -j '.default_gateway_domain' app-compose.json)\\n    fi\\nfi\\nif [[ -n \\\"${DSTACK_GATEWAY_DOMAIN-}\\\" && -n \\\"${DSTACK_APP_ID-}\\\" ]]; then\\n    export DSTACK_APP_DOMAIN=$DSTACK_APP_ID\\\".\\\"$DSTACK_GATEWAY_DOMAIN\\nfi\\n\\necho \\\"----------------------------------------------\\\"\\necho \\\"Script execution completed\\\"\\necho \\\"----------------------------------------------\\\"\\n\",\"public_logs\":true,\"public_sysinfo\":true,\"public_tcbinfo\":true,\"runner\":\"docker-compose\",\"secure_time\":false,\"storage_fs\":\"zfs\",\"tproxy_enabled\":true}","key_custody":{"provider":"dstack-kms","keys":[{"role":"receipt","path":"aci/receipt-ed25519/v1","purpose":"aci.receipt.ed25519.v1","algo":"ed25519","public_key":"c9bd1a9c235cfffbd07d71fb5d9ea5f51d50fe1edb937f1189ab0e771722f34e","kms_public_key":"03c537c8aeedad40ed997f0bfb5e52bcddb53eea4f00619430762a3edb8e69ac7c","signature_chain":["f8c83a3577a522472192ff089390ce97f9f8a188c9c89aa5c085abacc7f8ecf1116dbd67dde5f924dba995281cf6f21d34ea3fdce8cf69dfe90350770c6faa2f01","275d56da64b8bdb2d52a8525e987a6d496ecadd79b6eae24aaccf5e0bfc049e8429eaa03cc1e30f0f1bda0ef3a9827d813cda441cfecc9a0cb1896d39321366000"]},{"role":"e2ee-secp256k1","path":"aci/e2ee/v1","purpose":"aci.e2ee.v1","algo":"secp256k1-aes-256-gcm-hkdf-sha256","public_key":"04c4201704005a835ae62f8bffea2100cdb059a5a5bd0e79e5306bd3fd1358b7ab854994006c40b075724b3681b5ddd3ac455a2e7ec5d8e57a28b90f97a2f99c1f","kms_public_key":"03c4201704005a835ae62f8bffea2100cdb059a5a5bd0e79e5306bd3fd1358b7ab","signature_chain":["deccfc58bc5c43401b9b7f3633ce69d7cfb1a6a340b29cd2d5cd7a4d54627cc56bce9b82203fb67d1a36c352027b89a8aaaffde74ae1ea8b3694bcaf03c0a69001","275d56da64b8bdb2d52a8525e987a6d496ecadd79b6eae24aaccf5e0bfc049e8429eaa03cc1e30f0f1bda0ef3a9827d813cda441cfecc9a0cb1896d39321366000"]},{"role":"e2ee-x25519","path":"aci/e2ee-x25519/v1","purpose":"aci.e2ee.x25519.v1","algo":"x25519-aes-256-gcm-hkdf-sha256","public_key":"fcc7896f383cdc46bfaef49ba492b565c43697a8b8b381c912a9ab7637ae5e2f","kms_public_key":"02d144ebba3e142736d91ad32a8348407ec35bf5349f49bcf21ef9a580a7d7efe1","signature_chain":["0b688a1319c03f4a1e588b6a0e65cc6dcfe546d650b570c9fc56e2ed77ffa7a44aab19a440abb79f652838d4f9ffde0489da37588ecd7e4b1824c60a4f4023f101","275d56da64b8bdb2d52a8525e987a6d496ecadd79b6eae24aaccf5e0bfc049e8429eaa03cc1e30f0f1bda0ef3a9827d813cda441cfecc9a0cb1896d39321366000"]}]},"downstream_tls_binding":{"domain":"tee.redpill.ai","spki_sha256":"dd7451eed78f36e8e230047e0e1e91955810f802fea052996fa83add370a00ce"}}},"service_capabilities":{"supported_e2ee_versions":["2"],"supported_multipart_constraint_endpoints":["/v1/audio/transcriptions"],"serving":"aggregator"}}