#!/usr/bin/env bash # Flightops pinned remote bootstrap (generated from bootstrap.sh.in). # # The deployment builder substitutes these placeholders and nothing else: # https://get.flightops.dev/releases/7412e2c51ff2fcc672ddc39278a04846a52eebc44eacdfdd150c62b5c833db15/flightops-20261022-0.2.0b1-jazzy-public3-redistributable.tar.gz immutable HTTPS URL of the versioned release archive # 7412e2c51ff2fcc672ddc39278a04846a52eebc44eacdfdd150c62b5c833db15 lower-case SHA-256 pin for those exact bytes # 46073102 exact archive size in bytes (decimal integer) # https://get.flightops.dev/engineer-guide-92fecbe69014624f fresh-system engineer guide URL # IyEvdXNyL2Jpbi9lbnYgYmFzaAojIEluc3RhbGwgYSBGbGlnaHRvcHMgcmVsZWFzZSBhcmNoaXZlIGZyb20gdGhpcyBjaGVja291dCB3aXRob3V0IHVucGFja2luZyBpdAojIGJ5IGhhbmQuIFRoZSBwdWJsaXNoZWQgSHVtYmxlIGFuZCBKYXp6eSBpbnN0YWxsZXJzIG9uCiMgaHR0cHM6Ly9nZXQuZmxpZ2h0b3BzLmRldiBlbWJlZCBhIHBpbm5lZCBjb3B5IG9mIHRoaXMgc2NyaXB0IGFuZCBjYWxsIGl0CiMgYXMgYGluc3RhbGwuc2ggLS1hcmNoaXZlIEZJTEUgLS1zaGEyNTYgRElHRVNUIFstLXB5dGhvbnwtLXByZWZpeHwtLWJpbi1kaXJdYDsKIyBrZWVwIHRoYXQgaW50ZXJmYWNlIHN0YWJsZS4gQmVmb3JlIGFueXRoaW5nIGVsc2UgaXQgYXNrcyB0aGUgdXNlciB0byBhY2NlcHQgdGhlCiMgYmV0YSB0ZXJtcyAoZG9jcy9URVJNUy5tZCwgZW1iZWRkZWQgYmVsb3cpOyB0aGUgcHVibGlzaGVkIGJvb3RzdHJhcHMgcnVuIGl0IHdpdGgKIyAtLXRlcm1zLW9ubHkgYmVmb3JlIHRoZWlyIGRvd25sb2FkLCB0aGVuIHBhc3MgLS1hY2NlcHQtdGVybXMuCnNldCAtZXVvIHBpcGVmYWlsClJFUE9fRElSPSQoQ0RQQVRIPSBjZCAtLSAiJChkaXJuYW1lIC0tICIke0JBU0hfU09VUkNFWzBdfSIpIiAmJiBwd2QgLVApClBZVEhPTj1weXRob24zClNPVVJDRT0KU0hBMjU2PQpET1dOTE9BRD0wCkRPV05MT0FEX0RJUj0KRVhUUkFDVEVEPQpTT1VSQ0VfS0lORD1sb2NhbApJTlNUQUxMX0FSR1M9KCkKQUNDRVBUX1RFUk1TPTAKVEVSTVNfT05MWT0wClRFUk1TX1ZFUlNJT049YmV0YS0yMDI2LTEwLTIyClRFUk1TX1VSTD1odHRwczovL2JldGEuZmxpZ2h0b3BzLmRldi90ZXJtcy8kVEVSTVNfVkVSU0lPTgpURVJNU19SRUNPUkQ9JHtYREdfQ09ORklHX0hPTUU6LSR7SE9NRTotL25vbmV4aXN0ZW50fS8uY29uZmlnfS9mbGlnaHRvcHMvdGVybXMtYWNjZXB0ZWQKY2xlYW51cCgpIHsKICBpZiBbWyAtbiAkRVhUUkFDVEVEIF1dOyB0aGVuIHJtIC1yZiAtLSAiJEVYVFJBQ1RFRCI7IGZpCiAgaWYgW1sgLW4gJERPV05MT0FEX0RJUiBdXTsgdGhlbiBybSAtcmYgLS0gIiRET1dOTE9BRF9ESVIiOyBmaQp9CnRyYXAgY2xlYW51cCBFWElUCnVzYWdlKCkgewogIGNhdCA8PCdIRUxQJwpVc2FnZTogYmFzaCBpbnN0YWxsLnNoIFtBUkNISVZFX09SX0VYVFJBQ1RFRF9ESVJdIFstLXNoYTI1NiBIRVhdIFstLXB5dGhvbiBQQVRIXSBbLS1wcmVmaXggRElSXSBbLS1iaW4tZGlyIERJUl0KICAgICAgIGJhc2ggaW5zdGFsbC5zaCAtLWFyY2hpdmUgQVJDSElWRSBbLS1zaGEyNTYgSEVYXSBbLS1weXRob24gUEFUSF0gWy0tcHJlZml4IERJUl0gWy0tYmluLWRpciBESVJdCiAgICAgICBiYXNoIGluc3RhbGwuc2ggLS1kb3dubG9hZCBbLS1zaGEyNTYgSEVYXSBbLS1weXRob24gUEFUSF0gWy0tcHJlZml4IERJUl0gWy0tYmluLWRpciBESVJdCiAgICAgICBiYXNoIGluc3RhbGwuc2ggLS10ZXJtcy1vbmx5CkV2ZXJ5IGZvcm0gYWxzbyB0YWtlcyAtLWFjY2VwdC10ZXJtcy4KCkluc3RhbGxpbmcgRmxpZ2h0T3BzIG1lYW5zIGFjY2VwdGluZyB0aGUgRmxpZ2h0T3BzIGJldGEgdGVybXMuIFRoZSBzY3JpcHQgc2hvd3MKdGhlbSBhbmQgYXNrcyB5b3UgdG8gdHlwZSB5ZXMsIHRoZW4gcmVjb3JkcyB0aGUgYWNjZXB0ZWQgdmVyc2lvbiBpbgp+Ly5jb25maWcvZmxpZ2h0b3BzL3Rlcm1zLWFjY2VwdGVkIHNvIGxhdGVyIGluc3RhbGxzIGRvIG5vdCBhc2sgYWdhaW4uIFdpdGhvdXQgYQp0ZXJtaW5hbCwgcmVhZCB0aGUgdGVybXMgYXQKaHR0cHM6Ly9iZXRhLmZsaWdodG9wcy5kZXYvdGVybXMvYmV0YS0yMDI2LTEwLTIyIGFuZCBwYXNzIC0tYWNjZXB0LXRlcm1zCm9yIHNldCBGTElHSFRPUFNfQUNDRVBUX1RFUk1TPTEuIC0tdGVybXMtb25seSBhc2tzIGZvciBhY2NlcHRhbmNlIGFuZCBpbnN0YWxscwpub3RoaW5nLgoKV2l0aCBubyBhcmNoaXZlLCBpbnN0YWxscyB0aGUgbmV3ZXN0IGNvbXBhdGlibGUgZXh0cmFjdGVkIHJlbGVhc2UgaW4Kb3V0cHV0L3ByaXZhdGUtcmVsZWFzZSwgb3IgZG93bmxvYWRzIHRoZSBwdWJsaXNoZWQgYnVpbGQgZm9yIHRoaXMgaG9zdCBpZiBub25lCmV4aXN0cy4gVXNlIC0tZG93bmxvYWQgdG8gZmV0Y2ggdGhhdCBidWlsZCBldmVuIHdoZW4gYSBsb2NhbCBidWlsZCBleGlzdHMuCgpQdWJsaXNoZWQgYnVpbGRzIChwdWJsaWMgSFRUUFMgZG93bmxvYWQsIG5vIEdpdEh1YiBhY2Nlc3MgbmVlZGVkKS4gVGhlIGFyY2hpdmUKc2l6ZSBhbmQgU0hBLTI1NiBhcmUgZW1iZWRkZWQgaW4gdGhpcyBzY3JpcHQgYW5kIGNoZWNrZWQgYmVmb3JlIGV4dHJhY3Rpb246CiAgVWJ1bnR1IDIyLjA0IHg4Nl82NCwgUHl0aG9uIDMuMTAsIFJPUyAyIEh1bWJsZQogIFVidW50dSAyNC4wNCB4ODZfNjQsIFB5dGhvbiAzLjEyLCBST1MgMiBKYXp6eQpPdGhlciBob3N0cyBtdXN0IHBhc3MgLS1hcmNoaXZlLiBUaGlzIHNjcmlwdCBkb2VzIG5vdCBidWlsZCBjdXJyZW50IHNvdXJjZTsgc2VlCmRvY3MvSU5TVEFMTEFUSU9OLm1kLgoKLS1zaGEyNTYgcGlucyB0aGUgZXhhY3QgcmVsZWFzZSBhcmNoaXZlIGJ5IGRpZ2VzdC4gSW5zdGFsbGF0aW9uIHJlZnVzZXMgYQptaXNtYXRjaCBiZWZvcmUgZXh0cmFjdGluZyBvciBpbnZva2luZyB0aGUgYnVuZGxlZCBpbnN0YWxsZXIuIFVzZSBpdCBmb3IgYW55CmFyY2hpdmUgeW91IGRpZCBub3QgYnVpbGQgeW91cnNlbGY7IHRoZSBkaWdlc3Qgb2YgYW4gLS1hcmNoaXZlIGZpbGUgaXMgbm90Cmtub3duIHRvIHRoaXMgc2NyaXB0LgpIRUxQCn0Kd2hpbGUgKCgkIykpOyBkbwogIGNhc2UgIiQxIiBpbgogICAgLS1hcmNoaXZlKQogICAgICAoKCQjID49IDIpKSB8fCB7IGVjaG8gJy0tYXJjaGl2ZSBuZWVkcyBhIHBhdGgnID4mMjsgZXhpdCAyOyB9CiAgICAgIFtbIC16ICRTT1VSQ0UgXV0gfHwgeyBlY2hvICdDaG9vc2Ugb25lIHJlbGVhc2Ugc291cmNlJyA+JjI7IGV4aXQgMjsgfQogICAgICBTT1VSQ0U9JDI7IHNoaWZ0IDIgOzsKICAgIC0tZG93bmxvYWQpIERPV05MT0FEPTE7IHNoaWZ0IDs7CiAgICAtLWFjY2VwdC10ZXJtcykgQUNDRVBUX1RFUk1TPTE7IHNoaWZ0IDs7CiAgICAtLXRlcm1zLW9ubHkpIFRFUk1TX09OTFk9MTsgc2hpZnQgOzsKICAgIC0tc2hhMjU2KQogICAgICAoKCQjID49IDIpKSB8fCB7IGVjaG8gJy0tc2hhMjU2IG5lZWRzIGEgaGV4IGRpZ2VzdCcgPiYyOyBleGl0IDI7IH0KICAgICAgU0hBMjU2PSQyOyBzaGlmdCAyIDs7CiAgICAtLXB5dGhvbnwtLXByZWZpeHwtLWJpbi1kaXIpCiAgICAgICgoJCMgPj0gMikpIHx8IHsgZWNobyAiJDEgbmVlZHMgYSB2YWx1ZSIgPiYyOyBleGl0IDI7IH0KICAgICAgW1sgLW4gJDIgXV0gfHwgeyBlY2hvICIkMSBuZWVkcyBhIHZhbHVlIiA+JjI7IGV4aXQgMjsgfQogICAgICBbWyAkMSAhPSAtLXB5dGhvbiBdXSB8fCBQWVRIT049JDIKICAgICAgSU5TVEFMTF9BUkdTKz0oIiQxIiAiJDIiKTsgc2hpZnQgMiA7OwogICAgLS1oZWxwfC1oKSB1c2FnZTsgZXhpdCAwIDs7CiAgICAtLSopIGVjaG8gIlVua25vd24gb3B0aW9uOiAkMSIgPiYyOyBleGl0IDIgOzsKICAgICopCiAgICAgIFtbIC16ICRTT1VSQ0UgXV0gfHwgeyBlY2hvICdDaG9vc2Ugb25lIHJlbGVhc2Ugc291cmNlJyA+JjI7IGV4aXQgMjsgfQogICAgICBTT1VSQ0U9JDE7IHNoaWZ0IDs7CiAgZXNhYwpkb25lCmlmIFtbICRET1dOTE9BRCA9PSAxICYmIC1uICRTT1VSQ0UgXV07IHRoZW4KICBlY2hvICdDaG9vc2UgLS1kb3dubG9hZCBvciBhbiBleHBsaWNpdCBhcmNoaXZlL2RpcmVjdG9yeScgPiYyCiAgZXhpdCAyCmZpCmlmIFtbIC1uICRTSEEyNTYgJiYgISAkU0hBMjU2ID1+IF5bMC05YS1mQS1GXXs2NH0kIF1dOyB0aGVuCiAgZWNobyAnLS1zaGEyNTYgbXVzdCBiZSBhIDY0LWNoYXJhY3RlciBoZXggZGlnZXN0JyA+JjIKICBleGl0IDIKZmkKdGVybXNfdGV4dCgpIHsKICBzZWQgJ3MvXiMjKiAvLycgPDwnVEVSTVMnCiMgRmxpZ2h0T3BzIGJldGEgdGVybXMgKHZlcnNpb24gYmV0YS0yMDI2LTEwLTIyKQoKQnkgaW5zdGFsbGluZyBvciB1c2luZyB0aGUgRmxpZ2h0T3BzIGJldGEsIHlvdSBhY2NlcHQgdGhlc2UgdGVybXMgZm9yIHlvdXJzZWxmCm9yIGZvciB0aGUgY29tcGFueSB5b3Ugd29yayBmb3IuICJXZSIgbWVhbnMgRmxpZ2h0T3BzLgoKIyMgMS4gQmV0YSBzb2Z0d2FyZQoKRmxpZ2h0T3BzIDAuMi4wYjEgaXMgcHJlLXJlbGVhc2Ugc29mdHdhcmUgZm9yIGV2YWx1YXRpb24uIEl0IG1heSBoYXZlIGVycm9ycywKY2hhbmdlIG9yIHN0b3Agd29ya2luZy4gRG8gbm90IHVzZSBpdCB0byBjb250cm9sIG9yIGFwcHJvdmUgcm9ib3RzIG9yIG1hY2hpbmVzCndoZXJlIGEgZmF1bHQgY291bGQgaW5qdXJlIHBlb3BsZSBvciBkYW1hZ2UgcHJvcGVydHkuIFlvdSBhcmUgcmVzcG9uc2libGUgZm9yCnlvdXIgb3duIHN5c3RlbXMuCgojIyAyLiBMaWNlbmNlcwoKVGhlIGxpY2VuY2VzIGluY2x1ZGVkIHdpdGggZWFjaCBidWlsZCAoTElDRU5TRSBhbmQgVEhJUkRfUEFSVFlfTk9USUNFUykgZ292ZXJuCnRoZSBzb2Z0d2FyZS4gVGhlc2UgdGVybXMgZ292ZXJuIHRoZSBiZXRhIHNlcnZpY2UgYW5kIHRoZSBsaWNlbnNlIGtleXMsIGFuZAp0YWtlIGF3YXkgbm8gcmlnaHQgdGhvc2UgbGljZW5jZXMgZ2l2ZSB5b3UuCgojIyAzLiBMaWNlbnNlIGtleXMKCllvdXIga2V5IGlzIGZvciB5b3Ugb3IgeW91ciB0ZWFtLiBEbyBub3Qgc2hhcmUgb3Igc2VsbCBpdCwgbWlzdXNlIHRoZSBzZXJ2aWNlCm9yIGdldCBhcm91bmQgaXRzIGxpbWl0cy4gV2UgbWF5IHJldm9rZSBhIGtleSB0aGF0IGJyZWFrcyB0aGVzZSB0ZXJtcy4gVGhlIGJldGEKYW5kIGl0cyBrZXlzIGVuZCBvbiAzMSBNYXJjaCAyMDI3LgoKIyMgNC4gVXNhZ2UgbWV0cmljcwoKRWFjaCBhY3RpdmF0ZWQgZGV2aWNlIHNlbmRzIHVzYWdlIG1ldHJpY3M7IHRoaXMgaXMgcmVxdWlyZWQgZm9yIHRoZSBiZXRhLiBUaGUKcHJpdmFjeSBub3RpY2UgYXQgaHR0cHM6Ly9iZXRhLmZsaWdodG9wcy5kZXYvcHJpdmFjeSBsaXN0cyB3aGF0IGlzIHNlbnQKYW5kIHdoYXQgbmV2ZXIgaXMuCgojIyA1LiBObyB3YXJyYW50eSwgbm8gbGlhYmlsaXR5CgpGbGlnaHRPcHMgaXMgcHJvdmlkZWQgImFzIGlzIiwgd2l0aG91dCBhbnkgd2FycmFudHkuIFRvIHRoZSBmdWxsZXN0IGV4dGVudCB0aGUKbGF3IGFsbG93cywgd2UgYXJlIG5vdCBsaWFibGUgZm9yIGFueSBsb3NzIG9yIGRhbWFnZSBmcm9tIHVzaW5nIGl0LgoKIyMgNi4gQ2hhbmdlcwoKQSBjaGFuZ2VkIHZlcnNpb24gb2YgdGhlc2UgdGVybXMgZ2V0cyBhIG5ldyB2ZXJzaW9uIG51bWJlciBhbmQgYXBwbGllcyB0byB5b3UKb25seSBhZnRlciB5b3UgYWNjZXB0IGl0LgoKVGhpcyB2ZXJzaW9uOiBodHRwczovL2JldGEuZmxpZ2h0b3BzLmRldi90ZXJtcy9iZXRhLTIwMjYtMTAtMjIKUXVlc3Rpb25zOiBiZXRhQGZsaWdodG9wcy5kZXYKVEVSTVMKfQojIFRoZSB0ZXJtcyBnYXRlIHJ1bnMgYmVmb3JlIGFueSBkb3dubG9hZCwgZXh0cmFjdGlvbiBvciBpbnN0YWxsYXRpb24uCmlmIFtbICRBQ0NFUFRfVEVSTVMgPT0gMSB8fCAke0ZMSUdIVE9QU19BQ0NFUFRfVEVSTVM6LX0gPT0gMSBdXTsgdGhlbgogIFRFUk1TX0FDQ0VQVEVEPW5ldwplbGlmIFtbIC1mICRURVJNU19SRUNPUkQgJiYgJChoZWFkIC1uIDEgLS0gIiRURVJNU19SRUNPUkQiIDI+L2Rldi9udWxsKSA9PSAiJFRFUk1TX1ZFUlNJT04gIiogXV07IHRoZW4KICBURVJNU19BQ0NFUFRFRD1yZWNvcmRlZAplbGlmICg6IDwvZGV2L3R0eSkgMj4vZGV2L251bGw7IHRoZW4KICAjIFJlYWQgZnJvbSB0aGUgdGVybWluYWw6IHRoZSBwdWJsaXNoZWQgYm9vdHN0cmFwcyBydW4gdGhpcyBzY3JpcHQgd2l0aCBzdGRpbgogICMgYWxyZWFkeSB1c2VkIGZvciB0aGVpciBvd24gY29kZS4KICBleGVjIDM8Pi9kZXYvdHR5CiAgeyB0ZXJtc190ZXh0OyBwcmludGYgJ1xuVHlwZSB5ZXMgdG8gYWNjZXB0IHRoZXNlIHRlcm1zOiAnOyB9ID4mMwogIEFOU1dFUj0KICByZWFkIC1yIEFOU1dFUiA8JjMgfHwgdHJ1ZQogIGV4ZWMgMz4mLQogIGlmIFtbICR7QU5TV0VSLCx9ICE9IHllcyBdXTsgdGhlbgogICAgZWNobyAnVGhlIEZsaWdodE9wcyBiZXRhIHRlcm1zIHdlcmUgbm90IGFjY2VwdGVkLiBOb3RoaW5nIHdhcyBpbnN0YWxsZWQuJyA+JjIKICAgIGV4aXQgMQogIGZpCiAgVEVSTVNfQUNDRVBURUQ9bmV3CmVsc2UKICBwcmludGYgJyVzXG4nICJJbnN0YWxsaW5nIEZsaWdodE9wcyByZXF1aXJlcyBhY2NlcHRpbmcgdGhlIEZsaWdodE9wcyBiZXRhIHRlcm1zICgkVEVSTVNfVkVSU0lPTikuIiBcCiAgICAiUmVhZCB0aGVtIGF0ICRURVJNU19VUkwiIFwKICAgICdhbmQgcnVuIHRoaXMgY29tbWFuZCBhZ2FpbiB3aXRoIC0tYWNjZXB0LXRlcm1zIChvciBGTElHSFRPUFNfQUNDRVBUX1RFUk1TPTEpLicgPiYyCiAgZXhpdCAyCmZpCmlmIFtbICRURVJNU19BQ0NFUFRFRCA9PSBuZXcgXV07IHRoZW4KICB7IG1rZGlyIC1wIC0tICIke1RFUk1TX1JFQ09SRCUvKn0iICYmCiAgICBwcmludGYgJyVzICVzXG4nICIkVEVSTVNfVkVSU0lPTiIgIiQoZGF0ZSAtdSArJVktJW0tJWRUJUg6JU06JVNaKSIgPiIkVEVSTVNfUkVDT1JEIjsgfSAyPi9kZXYvbnVsbCB8fAogICAgZWNobyAiV2FybmluZzogY291bGQgbm90IHJlY29yZCB0aGUgdGVybXMgYWNjZXB0YW5jZSBpbiAkVEVSTVNfUkVDT1JEIiA+JjIKZmkKaWYgW1sgJFRFUk1TX09OTFkgPT0gMSBdXTsgdGhlbgogIGVjaG8gIkZsaWdodE9wcyBiZXRhIHRlcm1zICRURVJNU19WRVJTSU9OIGFjY2VwdGVkLiIKICBleGl0IDAKZmkKaWYgW1sgLXogJFNPVVJDRSAmJiAkRE9XTkxPQUQgPT0gMCBdXTsgdGhlbgogIFNPVVJDRT0kKCIkUFlUSE9OIiAtSSAtICIkUkVQT19ESVIvb3V0cHV0L3ByaXZhdGUtcmVsZWFzZSIgPDwnUFknCmltcG9ydCBqc29uLCBwYXRobGliLCBwbGF0Zm9ybSwgc3lzCmJhc2UgPSBwYXRobGliLlBhdGgoc3lzLmFyZ3ZbMV0pCm9zX2luZm8gPSB7fQpmb3IgbGluZSBpbiBwYXRobGliLlBhdGgoJy9ldGMvb3MtcmVsZWFzZScpLnJlYWRfdGV4dCgpLnNwbGl0bGluZXMoKToKICAgIGlmICc9JyBpbiBsaW5lOgogICAgICAgIGtleSwgdmFsdWUgPSBsaW5lLnNwbGl0KCc9JywgMSkKICAgICAgICBvc19pbmZvW2tleV0gPSB2YWx1ZS5zdHJpcCgnIicpCmhvc3QgPSAob3NfaW5mby5nZXQoJ0lEJyksIG9zX2luZm8uZ2V0KCdWRVJTSU9OX0lEJyksIHBsYXRmb3JtLm1hY2hpbmUoKSwKICAgICAgICBmJ3tzeXMudmVyc2lvbl9pbmZvLm1ham9yfS57c3lzLnZlcnNpb25faW5mby5taW5vcn0nKQptYXRjaGVzID0gW10KZm9yIG1hbmlmZXN0IGluIGJhc2UuZ2xvYignKi9yZWxlYXNlLmpzb24nKToKICAgIHRyeToKICAgICAgICBkYXRhID0ganNvbi5sb2FkcyhtYW5pZmVzdC5yZWFkX3RleHQoKSkKICAgICAgICByZXF1aXJlZCA9IHR1cGxlKGRhdGFba2V5XSBmb3Iga2V5IGluICgnb3NfaWQnLCAnb3NfdmVyc2lvbicsICdhcmNoJywgJ3B5dGhvbl9taW5vcicpKQogICAgICAgIGlmIHJlcXVpcmVkID09IGhvc3QgYW5kIGRhdGEuZ2V0KCdyZWxlYXNlX2lkJykgPT0gbWFuaWZlc3QucGFyZW50Lm5hbWU6CiAgICAgICAgICAgIG1hdGNoZXMuYXBwZW5kKChtYW5pZmVzdC5zdGF0KCkuc3RfbXRpbWVfbnMsIGRhdGFbJ3JlbGVhc2VfaWQnXSwgbWFuaWZlc3QucGFyZW50KSkKICAgIGV4Y2VwdCAoT1NFcnJvciwgVmFsdWVFcnJvciwgS2V5RXJyb3IsIFR5cGVFcnJvcik6CiAgICAgICAgY29udGludWUKaWYgbWF0Y2hlczoKICAgIHByaW50KG1heChtYXRjaGVzKVsyXSkKUFkKICApCmZpCmlmIFtbIC16ICRTT1VSQ0UgXV07IHRoZW4KICAjIFNlbGVjdCB0aGUgcHVibGlzaGVkIGJ1aWxkIGZvciB0aGlzIGV4YWN0IGhvc3QgYmVmb3JlIGFueSBuZXR3b3JrIHVzZS4KICAjIFRoZSAwLjIuMGIxIGJldGEgYnVpbGRzLiBUaGVzZSByb3dzIG11c3QgbWF0Y2ggcmVsZWFzZXMvMC4yLjBiMS5qc29uIGluIHRoaXMgcmVwb3NpdG9yeQogICMgKHNjcmlwdHMvdGVzdHMvdGVzdF9pbnN0YWxsX2VudHJ5LnB5IGNoZWNrcyBpdCkgYW5kIHRoZSBob3N0aW5nIHByb2plY3QncwogICMgZGVwbG95bWVudC9jbG91ZGZsYXJlL3JlbGVhc2UuanNvbi4gVGhlIFVSTHMgc2VydmUgb25jZSB0aGUgb3duZXIgZGVwbG95cyB0aGVtLgogIFJFTEVBU0VfUk9XPSQoIiRQWVRIT04iIC1JIC0gPDwnUFknCmltcG9ydCBwYXRobGliLCBwbGF0Zm9ybSwgc3lzClBVQkxJU0hFRCA9ICgKICAgIHsndGFyZ2V0JzogKCd1YnVudHUnLCAnMjIuMDQnLCAneDg2XzY0JywgJzMuMTAnKSwgJ3Jvcyc6ICdIdW1ibGUnLAogICAgICdyZWxlYXNlX2lkJzogJ2ZsaWdodG9wcy0yMDI2MTAyMi0wLjIuMGIxLWh1bWJsZS1wdWJsaWMzJywKICAgICAnYXJjaGl2ZSc6ICdmbGlnaHRvcHMtMjAyNjEwMjItMC4yLjBiMS1odW1ibGUtcHVibGljMy1yZWRpc3RyaWJ1dGFibGUudGFyLmd6JywKICAgICAnc2l6ZSc6IDQ1ODU4MjMwLAogICAgICdzaGEyNTYnOiAnMzAxYjlmOTc3YjE0N2IxY2M2NzBhZmNmMzUzYjU3Y2E3YzQ2ZWM1OGMyMTJiMzJmMjZlNjNkNmExNDJjMzg3Zid9LAogICAgeyd0YXJnZXQnOiAoJ3VidW50dScsICcyNC4wNCcsICd4ODZfNjQnLCAnMy4xMicpLCAncm9zJzogJ0phenp5JywKICAgICAncmVsZWFzZV9pZCc6ICdmbGlnaHRvcHMtMjAyNjEwMjItMC4yLjBiMS1qYXp6eS1wdWJsaWMzJywKICAgICAnYXJjaGl2ZSc6ICdmbGlnaHRvcHMtMjAyNjEwMjItMC4yLjBiMS1qYXp6eS1wdWJsaWMzLXJlZGlzdHJpYnV0YWJsZS50YXIuZ3onLAogICAgICdzaXplJzogNDYwNzMxMDIsCiAgICAgJ3NoYTI1Nic6ICc3NDEyZTJjNTFmZjJmY2M2NzJkZGMzOTI3OGEwNDg0NmE1MmVlYmM0NGVhY2RmZGQxNTBjNjJiNWM4MzNkYjE1J30sCikKT1JJR0lOID0gJ2h0dHBzOi8vZ2V0LmZsaWdodG9wcy5kZXYnCm9zX2luZm8gPSB7fQpmb3IgbGluZSBpbiBwYXRobGliLlBhdGgoJy9ldGMvb3MtcmVsZWFzZScpLnJlYWRfdGV4dCgpLnNwbGl0bGluZXMoKToKICAgIGlmICc9JyBpbiBsaW5lOgogICAgICAgIGtleSwgdmFsdWUgPSBsaW5lLnNwbGl0KCc9JywgMSkKICAgICAgICBvc19pbmZvW2tleV0gPSB2YWx1ZS5zdHJpcCgnIicpCmFjdHVhbCA9IChvc19pbmZvLmdldCgnSUQnKSwgb3NfaW5mby5nZXQoJ1ZFUlNJT05fSUQnKSwgcGxhdGZvcm0ubWFjaGluZSgpLAogICAgICAgICAgZid7c3lzLnZlcnNpb25faW5mby5tYWpvcn0ue3N5cy52ZXJzaW9uX2luZm8ubWlub3J9JykKZm9yIGVudHJ5IGluIFBVQkxJU0hFRDoKICAgIGlmIGVudHJ5Wyd0YXJnZXQnXSA9PSBhY3R1YWw6CiAgICAgICAgdXJsID0gZiJ7T1JJR0lOfS9yZWxlYXNlcy97ZW50cnlbJ3NoYTI1NiddfS97ZW50cnlbJ2FyY2hpdmUnXX0iCiAgICAgICAgcHJpbnQoJ1x0Jy5qb2luKChlbnRyeVsncmVsZWFzZV9pZCddLCBlbnRyeVsnYXJjaGl2ZSddLCBzdHIoZW50cnlbJ3NpemUnXSksCiAgICAgICAgICAgICAgICAgICAgICAgICBlbnRyeVsnc2hhMjU2J10sIHVybCwgZW50cnlbJ3JvcyddKSkpCiAgICAgICAgcmFpc2UgU3lzdGVtRXhpdCgwKQpzdXBwb3J0ZWQgPSAnOyAnLmpvaW4oJ3t9IHt9IHt9IFB5dGhvbiB7fSAoUk9TIDIge30pJy5mb3JtYXQoKmVbJ3RhcmdldCddLCBlWydyb3MnXSkKICAgICAgICAgICAgICAgICAgICAgIGZvciBlIGluIFBVQkxJU0hFRCkKcmFpc2UgU3lzdGVtRXhpdCgnTm8gcHVibGlzaGVkIEZsaWdodG9wcyBidWlsZCBtYXRjaGVzIHRoaXMgaG9zdCAoJwogICAgICAgICAgICAgICAgICsgJyAnLmpvaW4oc3RyKHgpIGZvciB4IGluIGFjdHVhbCkgKyAnKS4gUHVibGlzaGVkIGJ1aWxkczogJwogICAgICAgICAgICAgICAgICsgc3VwcG9ydGVkICsgJy4gRm9yIGFub3RoZXIgaG9zdCBwYXNzIC0tYXJjaGl2ZS4nKQpQWQogICkgfHwgZXhpdCAyCiAgSUZTPSQnXHQnIHJlYWQgLXIgUkVMRUFTRV9JRCBBUkNISVZFX05BTUUgQVJDSElWRV9TSVpFIEFSQ0hJVkVfU0hBMjU2IEFSQ0hJVkVfVVJMIFJFTEVBU0VfUk9TIDw8PCIkUkVMRUFTRV9ST1ciCiAgY29tbWFuZCAtdiBjdXJsID4vZGV2L251bGwgMj4mMSB8fCB7CiAgICBlY2hvICdjdXJsIGlzIHJlcXVpcmVkIHRvIGRvd25sb2FkIHRoZSBGbGlnaHRvcHMgcmVsZWFzZSAoaW5zdGFsbCBpdCwgb3IgcGFzcyAtLWFyY2hpdmUpLicgPiYyCiAgICBleGl0IDEKICB9CiAgRE9XTkxPQURfRElSPSQobWt0ZW1wIC1kICIke1RNUERJUjotL3RtcH0vZmxpZ2h0b3BzLWRvd25sb2FkLlhYWFhYWFhYIikKICBwcmludGYgJ0Rvd25sb2FkaW5nIEZsaWdodG9wcyAlcyBmb3IgUk9TIDIgJXMgKCVzIGJ5dGVzKVxuICAlc1xuJyBcCiAgICAiJFJFTEVBU0VfSUQiICIkUkVMRUFTRV9ST1MiICIkQVJDSElWRV9TSVpFIiAiJEFSQ0hJVkVfVVJMIgogICMgSFRUUFMgb25seSwgaW5jbHVkaW5nIHJlZGlyZWN0cy4gRmFpbHVyZXMgc3RheSB2aXNpYmxlIG9uIHN0ZGVyci4KICBpZiAhIGN1cmwgLS1wcm90byAnPWh0dHBzJyAtLXRsc3YxLjIgLS1wcm90by1yZWRpciAnPWh0dHBzJyAtLW1heC1yZWRpcnMgNSBcCiAgICAgIC0tZmFpbCAtLXNpbGVudCAtLXNob3ctZXJyb3IgLS1sb2NhdGlvbiAtLXVzZXItYWdlbnQgJ0ZsaWdodG9wcy1JbnN0YWxsZXIvMScgXAogICAgICAtLWNvbm5lY3QtdGltZW91dCAzMCAtLXNwZWVkLWxpbWl0IDEwMDAgLS1zcGVlZC10aW1lIDYwIFwKICAgICAgLS1tYXgtZmlsZXNpemUgIiRBUkNISVZFX1NJWkUiIFwKICAgICAgLS1vdXRwdXQgIiRET1dOTE9BRF9ESVIvJEFSQ0hJVkVfTkFNRSIgIiRBUkNISVZFX1VSTCI7IHRoZW4KICAgIGVjaG8gIkNvdWxkIG5vdCBkb3dubG9hZCB0aGUgRmxpZ2h0b3BzICRSRUxFQVNFX0lEIGFyY2hpdmUgZnJvbSAkQVJDSElWRV9VUkwiID4mMgogICAgZWNobyAnQ2hlY2sgdGhlIG5ldHdvcmsgY29ubmVjdGlvbiwgb3IgZG93bmxvYWQgdGhlIGFyY2hpdmUgeW91cnNlbGYgYW5kIHBhc3MgLS1hcmNoaXZlIHdpdGggLS1zaGEyNTYuJyA+JjIKICAgIGV4aXQgMQogIGZpCiAgIyBUaGUgZGlnZXN0IGFuZCBzaXplIGFyZSBlbWJlZGRlZCBpbiB0aGlzIHNjcmlwdCwgbm90IGZldGNoZWQgZnJvbSB0aGUKICAjIGRvd25sb2FkIGhvc3QsIHNvIGEgcmVwbGFjZWQgYXJjaGl2ZSBpcyByZWZ1c2VkIHJhdGhlciB0aGFuIG1lcmVseSBub3RpY2VkLgogICIkUFlUSE9OIiAtSSAtICIkRE9XTkxPQURfRElSLyRBUkNISVZFX05BTUUiICIkQVJDSElWRV9TSVpFIiAiJEFSQ0hJVkVfU0hBMjU2IiA8PCdQWScgfHwgZXhpdCAxCmltcG9ydCBoYXNobGliLCBwYXRobGliLCBzeXMKYXJjaGl2ZSwgc2l6ZSwgZXhwZWN0ZWQgPSBwYXRobGliLlBhdGgoc3lzLmFyZ3ZbMV0pLCBpbnQoc3lzLmFyZ3ZbMl0pLCBzeXMuYXJndlszXQppZiBub3QgYXJjaGl2ZS5pc19maWxlKCkgb3IgYXJjaGl2ZS5zdGF0KCkuc3Rfc2l6ZSAhPSBzaXplOgogICAgcmFpc2UgU3lzdGVtRXhpdChmJ0Rvd25sb2FkZWQgcmVsZWFzZSBhcmNoaXZlIGlzIG5vdCB0aGUgcGlubmVkIHNpemUgKHtzaXplfSBieXRlcyknKQpkaWdlc3QgPSBoYXNobGliLnNoYTI1NigpCndpdGggYXJjaGl2ZS5vcGVuKCdyYicpIGFzIHN0cmVhbToKICAgIGZvciBjaHVuayBpbiBpdGVyKGxhbWJkYTogc3RyZWFtLnJlYWQoMTAyNCAqIDEwMjQpLCBiJycpOgogICAgICAgIGRpZ2VzdC51cGRhdGUoY2h1bmspCmlmIGRpZ2VzdC5oZXhkaWdlc3QoKSAhPSBleHBlY3RlZDoKICAgIHJhaXNlIFN5c3RlbUV4aXQoJ0Rvd25sb2FkZWQgcmVsZWFzZSBhcmNoaXZlIFNIQS0yNTYgbWlzbWF0Y2gnKQpQWQogIFNPVVJDRT0kRE9XTkxPQURfRElSLyRBUkNISVZFX05BTUUKICBTT1VSQ0VfS0lORD1kb3dubG9hZGVkCmZpCltbIC1lICRTT1VSQ0UgXV0gfHwgeyBlY2hvICJSZWxlYXNlIHNvdXJjZSBkb2VzIG5vdCBleGlzdDogJFNPVVJDRSIgPiYyOyBleGl0IDI7IH0KU09VUkNFPSQocmVhbHBhdGggLWUgLS0gIiRTT1VSQ0UiKQppZiBbWyAtbiAkU0hBMjU2IF1dOyB0aGVuCiAgW1sgLWYgJFNPVVJDRSBdXSB8fCB7IGVjaG8gJy0tc2hhMjU2IHBpbnMgYW4gYXJjaGl2ZSwgbm90IGFuIGV4dHJhY3RlZCBkaXJlY3RvcnknID4mMjsgZXhpdCAyOyB9CiAgIiRQWVRIT04iIC1JIC0gIiRTT1VSQ0UiICIkU0hBMjU2IiA8PCdQWScgfHwgZXhpdCAxCmltcG9ydCBoYXNobGliLCBwYXRobGliLCBzeXMKYXJjaGl2ZSwgZXhwZWN0ZWQgPSBwYXRobGliLlBhdGgoc3lzLmFyZ3ZbMV0pLCBzeXMuYXJndlsyXS5sb3dlcigpCmRpZ2VzdCA9IGhhc2hsaWIuc2hhMjU2KCkKd2l0aCBhcmNoaXZlLm9wZW4oJ3JiJykgYXMgc3RyZWFtOgogICAgZm9yIGNodW5rIGluIGl0ZXIobGFtYmRhOiBzdHJlYW0ucmVhZCgxMDI0ICogMTAyNCksIGInJyk6CiAgICAgICAgZGlnZXN0LnVwZGF0ZShjaHVuaykKaWYgZGlnZXN0LmhleGRpZ2VzdCgpICE9IGV4cGVjdGVkOgogICAgcmFpc2UgU3lzdGVtRXhpdCgnUmVsZWFzZSBhcmNoaXZlIGRvZXMgbm90IG1hdGNoIHRoZSAtLXNoYTI1NiBwaW4nKQpQWQpmaQppZiBbWyAtZiAkU09VUkNFIF1dOyB0aGVuCiAgRVhUUkFDVEVEPSQobWt0ZW1wIC1kICIke1RNUERJUjotL3RtcH0vZmxpZ2h0b3BzLWluc3RhbGwuWFhYWFhYWFgiKQogIFNPVVJDRT0kKCIkUFlUSE9OIiAtSSAtICIkU09VUkNFIiAiJEVYVFJBQ1RFRCIgPDwnUFknCmltcG9ydCBwYXRobGliLCBzeXMsIHRhcmZpbGUsIHpsaWIKYXJjaGl2ZSwgZGVzdGluYXRpb24gPSBwYXRobGliLlBhdGgoc3lzLmFyZ3ZbMV0pLCBwYXRobGliLlBhdGgoc3lzLmFyZ3ZbMl0pCnRyeToKICAgIHdpdGggdGFyZmlsZS5vcGVuKGFyY2hpdmUsICdyOmd6JykgYXMgYnVuZGxlOgogICAgICAgIG1lbWJlcnMgPSBidW5kbGUuZ2V0bWVtYmVycygpCiAgICAgICAgcm9vdHMsIHNlZW4gPSBzZXQoKSwgc2V0KCkKICAgICAgICB0b3RhbCA9IDAKICAgICAgICBmb3IgbWVtYmVyIGluIG1lbWJlcnM6CiAgICAgICAgICAgIHBhcnRzID0gbWVtYmVyLm5hbWUucnN0cmlwKCcvJykuc3BsaXQoJy8nKQogICAgICAgICAgICBpZiAobm90IHBhcnRzIG9yIG1lbWJlci5uYW1lLnN0YXJ0c3dpdGgoJy8nKSBvcgogICAgICAgICAgICAgICAgICAgIGFueShwYXJ0IGluICgnJywgJy4nLCAnLi4nKSBmb3IgcGFydCBpbiBwYXJ0cykgb3IKICAgICAgICAgICAgICAgICAgICBub3QgKG1lbWJlci5pc2ZpbGUoKSBvciBtZW1iZXIuaXNkaXIoKSkgb3IgbWVtYmVyLm5hbWUgaW4gc2Vlbik6CiAgICAgICAgICAgICAgICByYWlzZSBTeXN0ZW1FeGl0KGYnVW5zYWZlIHJlbGVhc2UgYXJjaGl2ZSBtZW1iZXI6IHttZW1iZXIubmFtZX0nKQogICAgICAgICAgICByb290cy5hZGQocGFydHNbMF0pCiAgICAgICAgICAgIHNlZW4uYWRkKG1lbWJlci5uYW1lKQogICAgICAgICAgICB0b3RhbCArPSBtZW1iZXIuc2l6ZQogICAgICAgICAgICBpZiB0b3RhbCA+IDEwMjQgKiAxMDI0ICogMTAyNDoKICAgICAgICAgICAgICAgIHJhaXNlIFN5c3RlbUV4aXQoJ1JlbGVhc2UgYXJjaGl2ZSBleGNlZWRzIDEgR2lCIHVucGFja2VkJykKICAgICAgICBpZiBsZW4ocm9vdHMpICE9IDE6CiAgICAgICAgICAgIHJhaXNlIFN5c3RlbUV4aXQoJ1JlbGVhc2UgYXJjaGl2ZSBtdXN0IGNvbnRhaW4gb25lIHRvcC1sZXZlbCBkaXJlY3RvcnknKQogICAgICAgIHJvb3QgPSByb290cy5wb3AoKQogICAgICAgIGlmIG5vdCBhbnkobWVtYmVyLm5hbWUgPT0gcm9vdCArICcvcmVsZWFzZS5qc29uJyBmb3IgbWVtYmVyIGluIG1lbWJlcnMpOgogICAgICAgICAgICByYWlzZSBTeXN0ZW1FeGl0KCdSZWxlYXNlIGFyY2hpdmUgaGFzIG5vIG1hbmlmZXN0JykKICAgICAgICAjIFRoZSBtZW1iZXIgY2hlY2tzIGFib3ZlIGFscmVhZHkgcmVmdXNlIGxpbmtzLCBkZXZpY2VzIGFuZCBwYXRoIGVzY2FwZXMuCiAgICAgICAgIyBXaGVyZSBhdmFpbGFibGUgKDMuMTIrLCBhbmQgc2VjdXJpdHkgYmFja3BvcnRzIHRvIDMuMTAvMy4xMSksIGFsc28gYXBwbHkKICAgICAgICAjIHRoZSBzdGRsaWIgImRhdGEiIGZpbHRlcjsgaXQgYXZvaWRzIHRoZSAzLjEyIGRlcHJlY2F0aW9uIHdhcm5pbmcuCiAgICAgICAgaWYgaGFzYXR0cih0YXJmaWxlLCAnZGF0YV9maWx0ZXInKToKICAgICAgICAgICAgYnVuZGxlLmV4dHJhY3RhbGwoZGVzdGluYXRpb24sIGZpbHRlcj0nZGF0YScpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgYnVuZGxlLmV4dHJhY3RhbGwoZGVzdGluYXRpb24pCmV4Y2VwdCAodGFyZmlsZS5UYXJFcnJvciwgRU9GRXJyb3IsIE9TRXJyb3IsIHpsaWIuZXJyb3IpIGFzIGVycm9yOgogICAgZGV0YWlsID0gJyAnLmpvaW4oc3RyKGVycm9yKS5zcGxpdCgpKSBvciB0eXBlKGVycm9yKS5fX25hbWVfXwogICAgcmFpc2UgU3lzdGVtRXhpdChmJ0Nhbm5vdCByZWFkIHRoZSByZWxlYXNlIGFyY2hpdmUgKGNvcnJ1cHQgb3IgdHJ1bmNhdGVkKToge2RldGFpbH0nKQpwcmludChkZXN0aW5hdGlvbiAvIHJvb3QpClBZCiAgKQplbGlmIFtbICEgLWQgJFNPVVJDRSBdXTsgdGhlbgogIGVjaG8gIlJlbGVhc2Ugc291cmNlIG11c3QgYmUgYW4gYXJjaGl2ZSBvciBkaXJlY3Rvcnk6ICRTT1VSQ0UiID4mMgogIGV4aXQgMgpmaQpwcmludGYgJ0luc3RhbGxpbmcgJXMgRmxpZ2h0b3BzIHJlbGVhc2UgZnJvbSAlc1xuJyAiJFNPVVJDRV9LSU5EIiAiJFNPVVJDRSIKYmFzaCAiJFNPVVJDRS9pbnN0YWxsLnNoIiAiJHtJTlNUQUxMX0FSR1NbQF19Igo= base64 (single line, no wrapping) of the repository # root install.sh, embedded so no mutable helper is # fetched beside the archive # # Only --prefix, --bin-dir, --python and --accept-terms are accepted. The embedded # install.sh asks for the FlightOps beta terms before the download (--terms-only) # and the install then runs with --accept-terms. The archive URL, digest # and size are pinned above and cannot be overridden. The bootstrap refuses # unsupported hosts before any download, streams over HTTPS while enforcing the # exact size and embedded SHA-256, then delegates to the pinned install.sh and # verifies the installed command. set -euo pipefail ARCHIVE_URL='https://get.flightops.dev/releases/7412e2c51ff2fcc672ddc39278a04846a52eebc44eacdfdd150c62b5c833db15/flightops-20261022-0.2.0b1-jazzy-public3-redistributable.tar.gz' ARCHIVE_SHA256='7412e2c51ff2fcc672ddc39278a04846a52eebc44eacdfdd150c62b5c833db15' ARCHIVE_SIZE='46073102' GUIDE_URL='https://get.flightops.dev/engineer-guide-92fecbe69014624f' INSTALL_SH_BASE64='IyEvdXNyL2Jpbi9lbnYgYmFzaAojIEluc3RhbGwgYSBGbGlnaHRvcHMgcmVsZWFzZSBhcmNoaXZlIGZyb20gdGhpcyBjaGVja291dCB3aXRob3V0IHVucGFja2luZyBpdAojIGJ5IGhhbmQuIFRoZSBwdWJsaXNoZWQgSHVtYmxlIGFuZCBKYXp6eSBpbnN0YWxsZXJzIG9uCiMgaHR0cHM6Ly9nZXQuZmxpZ2h0b3BzLmRldiBlbWJlZCBhIHBpbm5lZCBjb3B5IG9mIHRoaXMgc2NyaXB0IGFuZCBjYWxsIGl0CiMgYXMgYGluc3RhbGwuc2ggLS1hcmNoaXZlIEZJTEUgLS1zaGEyNTYgRElHRVNUIFstLXB5dGhvbnwtLXByZWZpeHwtLWJpbi1kaXJdYDsKIyBrZWVwIHRoYXQgaW50ZXJmYWNlIHN0YWJsZS4gQmVmb3JlIGFueXRoaW5nIGVsc2UgaXQgYXNrcyB0aGUgdXNlciB0byBhY2NlcHQgdGhlCiMgYmV0YSB0ZXJtcyAoZG9jcy9URVJNUy5tZCwgZW1iZWRkZWQgYmVsb3cpOyB0aGUgcHVibGlzaGVkIGJvb3RzdHJhcHMgcnVuIGl0IHdpdGgKIyAtLXRlcm1zLW9ubHkgYmVmb3JlIHRoZWlyIGRvd25sb2FkLCB0aGVuIHBhc3MgLS1hY2NlcHQtdGVybXMuCnNldCAtZXVvIHBpcGVmYWlsClJFUE9fRElSPSQoQ0RQQVRIPSBjZCAtLSAiJChkaXJuYW1lIC0tICIke0JBU0hfU09VUkNFWzBdfSIpIiAmJiBwd2QgLVApClBZVEhPTj1weXRob24zClNPVVJDRT0KU0hBMjU2PQpET1dOTE9BRD0wCkRPV05MT0FEX0RJUj0KRVhUUkFDVEVEPQpTT1VSQ0VfS0lORD1sb2NhbApJTlNUQUxMX0FSR1M9KCkKQUNDRVBUX1RFUk1TPTAKVEVSTVNfT05MWT0wClRFUk1TX1ZFUlNJT049YmV0YS0yMDI2LTEwLTIyClRFUk1TX1VSTD1odHRwczovL2JldGEuZmxpZ2h0b3BzLmRldi90ZXJtcy8kVEVSTVNfVkVSU0lPTgpURVJNU19SRUNPUkQ9JHtYREdfQ09ORklHX0hPTUU6LSR7SE9NRTotL25vbmV4aXN0ZW50fS8uY29uZmlnfS9mbGlnaHRvcHMvdGVybXMtYWNjZXB0ZWQKY2xlYW51cCgpIHsKICBpZiBbWyAtbiAkRVhUUkFDVEVEIF1dOyB0aGVuIHJtIC1yZiAtLSAiJEVYVFJBQ1RFRCI7IGZpCiAgaWYgW1sgLW4gJERPV05MT0FEX0RJUiBdXTsgdGhlbiBybSAtcmYgLS0gIiRET1dOTE9BRF9ESVIiOyBmaQp9CnRyYXAgY2xlYW51cCBFWElUCnVzYWdlKCkgewogIGNhdCA8PCdIRUxQJwpVc2FnZTogYmFzaCBpbnN0YWxsLnNoIFtBUkNISVZFX09SX0VYVFJBQ1RFRF9ESVJdIFstLXNoYTI1NiBIRVhdIFstLXB5dGhvbiBQQVRIXSBbLS1wcmVmaXggRElSXSBbLS1iaW4tZGlyIERJUl0KICAgICAgIGJhc2ggaW5zdGFsbC5zaCAtLWFyY2hpdmUgQVJDSElWRSBbLS1zaGEyNTYgSEVYXSBbLS1weXRob24gUEFUSF0gWy0tcHJlZml4IERJUl0gWy0tYmluLWRpciBESVJdCiAgICAgICBiYXNoIGluc3RhbGwuc2ggLS1kb3dubG9hZCBbLS1zaGEyNTYgSEVYXSBbLS1weXRob24gUEFUSF0gWy0tcHJlZml4IERJUl0gWy0tYmluLWRpciBESVJdCiAgICAgICBiYXNoIGluc3RhbGwuc2ggLS10ZXJtcy1vbmx5CkV2ZXJ5IGZvcm0gYWxzbyB0YWtlcyAtLWFjY2VwdC10ZXJtcy4KCkluc3RhbGxpbmcgRmxpZ2h0T3BzIG1lYW5zIGFjY2VwdGluZyB0aGUgRmxpZ2h0T3BzIGJldGEgdGVybXMuIFRoZSBzY3JpcHQgc2hvd3MKdGhlbSBhbmQgYXNrcyB5b3UgdG8gdHlwZSB5ZXMsIHRoZW4gcmVjb3JkcyB0aGUgYWNjZXB0ZWQgdmVyc2lvbiBpbgp+Ly5jb25maWcvZmxpZ2h0b3BzL3Rlcm1zLWFjY2VwdGVkIHNvIGxhdGVyIGluc3RhbGxzIGRvIG5vdCBhc2sgYWdhaW4uIFdpdGhvdXQgYQp0ZXJtaW5hbCwgcmVhZCB0aGUgdGVybXMgYXQKaHR0cHM6Ly9iZXRhLmZsaWdodG9wcy5kZXYvdGVybXMvYmV0YS0yMDI2LTEwLTIyIGFuZCBwYXNzIC0tYWNjZXB0LXRlcm1zCm9yIHNldCBGTElHSFRPUFNfQUNDRVBUX1RFUk1TPTEuIC0tdGVybXMtb25seSBhc2tzIGZvciBhY2NlcHRhbmNlIGFuZCBpbnN0YWxscwpub3RoaW5nLgoKV2l0aCBubyBhcmNoaXZlLCBpbnN0YWxscyB0aGUgbmV3ZXN0IGNvbXBhdGlibGUgZXh0cmFjdGVkIHJlbGVhc2UgaW4Kb3V0cHV0L3ByaXZhdGUtcmVsZWFzZSwgb3IgZG93bmxvYWRzIHRoZSBwdWJsaXNoZWQgYnVpbGQgZm9yIHRoaXMgaG9zdCBpZiBub25lCmV4aXN0cy4gVXNlIC0tZG93bmxvYWQgdG8gZmV0Y2ggdGhhdCBidWlsZCBldmVuIHdoZW4gYSBsb2NhbCBidWlsZCBleGlzdHMuCgpQdWJsaXNoZWQgYnVpbGRzIChwdWJsaWMgSFRUUFMgZG93bmxvYWQsIG5vIEdpdEh1YiBhY2Nlc3MgbmVlZGVkKS4gVGhlIGFyY2hpdmUKc2l6ZSBhbmQgU0hBLTI1NiBhcmUgZW1iZWRkZWQgaW4gdGhpcyBzY3JpcHQgYW5kIGNoZWNrZWQgYmVmb3JlIGV4dHJhY3Rpb246CiAgVWJ1bnR1IDIyLjA0IHg4Nl82NCwgUHl0aG9uIDMuMTAsIFJPUyAyIEh1bWJsZQogIFVidW50dSAyNC4wNCB4ODZfNjQsIFB5dGhvbiAzLjEyLCBST1MgMiBKYXp6eQpPdGhlciBob3N0cyBtdXN0IHBhc3MgLS1hcmNoaXZlLiBUaGlzIHNjcmlwdCBkb2VzIG5vdCBidWlsZCBjdXJyZW50IHNvdXJjZTsgc2VlCmRvY3MvSU5TVEFMTEFUSU9OLm1kLgoKLS1zaGEyNTYgcGlucyB0aGUgZXhhY3QgcmVsZWFzZSBhcmNoaXZlIGJ5IGRpZ2VzdC4gSW5zdGFsbGF0aW9uIHJlZnVzZXMgYQptaXNtYXRjaCBiZWZvcmUgZXh0cmFjdGluZyBvciBpbnZva2luZyB0aGUgYnVuZGxlZCBpbnN0YWxsZXIuIFVzZSBpdCBmb3IgYW55CmFyY2hpdmUgeW91IGRpZCBub3QgYnVpbGQgeW91cnNlbGY7IHRoZSBkaWdlc3Qgb2YgYW4gLS1hcmNoaXZlIGZpbGUgaXMgbm90Cmtub3duIHRvIHRoaXMgc2NyaXB0LgpIRUxQCn0Kd2hpbGUgKCgkIykpOyBkbwogIGNhc2UgIiQxIiBpbgogICAgLS1hcmNoaXZlKQogICAgICAoKCQjID49IDIpKSB8fCB7IGVjaG8gJy0tYXJjaGl2ZSBuZWVkcyBhIHBhdGgnID4mMjsgZXhpdCAyOyB9CiAgICAgIFtbIC16ICRTT1VSQ0UgXV0gfHwgeyBlY2hvICdDaG9vc2Ugb25lIHJlbGVhc2Ugc291cmNlJyA+JjI7IGV4aXQgMjsgfQogICAgICBTT1VSQ0U9JDI7IHNoaWZ0IDIgOzsKICAgIC0tZG93bmxvYWQpIERPV05MT0FEPTE7IHNoaWZ0IDs7CiAgICAtLWFjY2VwdC10ZXJtcykgQUNDRVBUX1RFUk1TPTE7IHNoaWZ0IDs7CiAgICAtLXRlcm1zLW9ubHkpIFRFUk1TX09OTFk9MTsgc2hpZnQgOzsKICAgIC0tc2hhMjU2KQogICAgICAoKCQjID49IDIpKSB8fCB7IGVjaG8gJy0tc2hhMjU2IG5lZWRzIGEgaGV4IGRpZ2VzdCcgPiYyOyBleGl0IDI7IH0KICAgICAgU0hBMjU2PSQyOyBzaGlmdCAyIDs7CiAgICAtLXB5dGhvbnwtLXByZWZpeHwtLWJpbi1kaXIpCiAgICAgICgoJCMgPj0gMikpIHx8IHsgZWNobyAiJDEgbmVlZHMgYSB2YWx1ZSIgPiYyOyBleGl0IDI7IH0KICAgICAgW1sgLW4gJDIgXV0gfHwgeyBlY2hvICIkMSBuZWVkcyBhIHZhbHVlIiA+JjI7IGV4aXQgMjsgfQogICAgICBbWyAkMSAhPSAtLXB5dGhvbiBdXSB8fCBQWVRIT049JDIKICAgICAgSU5TVEFMTF9BUkdTKz0oIiQxIiAiJDIiKTsgc2hpZnQgMiA7OwogICAgLS1oZWxwfC1oKSB1c2FnZTsgZXhpdCAwIDs7CiAgICAtLSopIGVjaG8gIlVua25vd24gb3B0aW9uOiAkMSIgPiYyOyBleGl0IDIgOzsKICAgICopCiAgICAgIFtbIC16ICRTT1VSQ0UgXV0gfHwgeyBlY2hvICdDaG9vc2Ugb25lIHJlbGVhc2Ugc291cmNlJyA+JjI7IGV4aXQgMjsgfQogICAgICBTT1VSQ0U9JDE7IHNoaWZ0IDs7CiAgZXNhYwpkb25lCmlmIFtbICRET1dOTE9BRCA9PSAxICYmIC1uICRTT1VSQ0UgXV07IHRoZW4KICBlY2hvICdDaG9vc2UgLS1kb3dubG9hZCBvciBhbiBleHBsaWNpdCBhcmNoaXZlL2RpcmVjdG9yeScgPiYyCiAgZXhpdCAyCmZpCmlmIFtbIC1uICRTSEEyNTYgJiYgISAkU0hBMjU2ID1+IF5bMC05YS1mQS1GXXs2NH0kIF1dOyB0aGVuCiAgZWNobyAnLS1zaGEyNTYgbXVzdCBiZSBhIDY0LWNoYXJhY3RlciBoZXggZGlnZXN0JyA+JjIKICBleGl0IDIKZmkKdGVybXNfdGV4dCgpIHsKICBzZWQgJ3MvXiMjKiAvLycgPDwnVEVSTVMnCiMgRmxpZ2h0T3BzIGJldGEgdGVybXMgKHZlcnNpb24gYmV0YS0yMDI2LTEwLTIyKQoKQnkgaW5zdGFsbGluZyBvciB1c2luZyB0aGUgRmxpZ2h0T3BzIGJldGEsIHlvdSBhY2NlcHQgdGhlc2UgdGVybXMgZm9yIHlvdXJzZWxmCm9yIGZvciB0aGUgY29tcGFueSB5b3Ugd29yayBmb3IuICJXZSIgbWVhbnMgRmxpZ2h0T3BzLgoKIyMgMS4gQmV0YSBzb2Z0d2FyZQoKRmxpZ2h0T3BzIDAuMi4wYjEgaXMgcHJlLXJlbGVhc2Ugc29mdHdhcmUgZm9yIGV2YWx1YXRpb24uIEl0IG1heSBoYXZlIGVycm9ycywKY2hhbmdlIG9yIHN0b3Agd29ya2luZy4gRG8gbm90IHVzZSBpdCB0byBjb250cm9sIG9yIGFwcHJvdmUgcm9ib3RzIG9yIG1hY2hpbmVzCndoZXJlIGEgZmF1bHQgY291bGQgaW5qdXJlIHBlb3BsZSBvciBkYW1hZ2UgcHJvcGVydHkuIFlvdSBhcmUgcmVzcG9uc2libGUgZm9yCnlvdXIgb3duIHN5c3RlbXMuCgojIyAyLiBMaWNlbmNlcwoKVGhlIGxpY2VuY2VzIGluY2x1ZGVkIHdpdGggZWFjaCBidWlsZCAoTElDRU5TRSBhbmQgVEhJUkRfUEFSVFlfTk9USUNFUykgZ292ZXJuCnRoZSBzb2Z0d2FyZS4gVGhlc2UgdGVybXMgZ292ZXJuIHRoZSBiZXRhIHNlcnZpY2UgYW5kIHRoZSBsaWNlbnNlIGtleXMsIGFuZAp0YWtlIGF3YXkgbm8gcmlnaHQgdGhvc2UgbGljZW5jZXMgZ2l2ZSB5b3UuCgojIyAzLiBMaWNlbnNlIGtleXMKCllvdXIga2V5IGlzIGZvciB5b3Ugb3IgeW91ciB0ZWFtLiBEbyBub3Qgc2hhcmUgb3Igc2VsbCBpdCwgbWlzdXNlIHRoZSBzZXJ2aWNlCm9yIGdldCBhcm91bmQgaXRzIGxpbWl0cy4gV2UgbWF5IHJldm9rZSBhIGtleSB0aGF0IGJyZWFrcyB0aGVzZSB0ZXJtcy4gVGhlIGJldGEKYW5kIGl0cyBrZXlzIGVuZCBvbiAzMSBNYXJjaCAyMDI3LgoKIyMgNC4gVXNhZ2UgbWV0cmljcwoKRWFjaCBhY3RpdmF0ZWQgZGV2aWNlIHNlbmRzIHVzYWdlIG1ldHJpY3M7IHRoaXMgaXMgcmVxdWlyZWQgZm9yIHRoZSBiZXRhLiBUaGUKcHJpdmFjeSBub3RpY2UgYXQgaHR0cHM6Ly9iZXRhLmZsaWdodG9wcy5kZXYvcHJpdmFjeSBsaXN0cyB3aGF0IGlzIHNlbnQKYW5kIHdoYXQgbmV2ZXIgaXMuCgojIyA1LiBObyB3YXJyYW50eSwgbm8gbGlhYmlsaXR5CgpGbGlnaHRPcHMgaXMgcHJvdmlkZWQgImFzIGlzIiwgd2l0aG91dCBhbnkgd2FycmFudHkuIFRvIHRoZSBmdWxsZXN0IGV4dGVudCB0aGUKbGF3IGFsbG93cywgd2UgYXJlIG5vdCBsaWFibGUgZm9yIGFueSBsb3NzIG9yIGRhbWFnZSBmcm9tIHVzaW5nIGl0LgoKIyMgNi4gQ2hhbmdlcwoKQSBjaGFuZ2VkIHZlcnNpb24gb2YgdGhlc2UgdGVybXMgZ2V0cyBhIG5ldyB2ZXJzaW9uIG51bWJlciBhbmQgYXBwbGllcyB0byB5b3UKb25seSBhZnRlciB5b3UgYWNjZXB0IGl0LgoKVGhpcyB2ZXJzaW9uOiBodHRwczovL2JldGEuZmxpZ2h0b3BzLmRldi90ZXJtcy9iZXRhLTIwMjYtMTAtMjIKUXVlc3Rpb25zOiBiZXRhQGZsaWdodG9wcy5kZXYKVEVSTVMKfQojIFRoZSB0ZXJtcyBnYXRlIHJ1bnMgYmVmb3JlIGFueSBkb3dubG9hZCwgZXh0cmFjdGlvbiBvciBpbnN0YWxsYXRpb24uCmlmIFtbICRBQ0NFUFRfVEVSTVMgPT0gMSB8fCAke0ZMSUdIVE9QU19BQ0NFUFRfVEVSTVM6LX0gPT0gMSBdXTsgdGhlbgogIFRFUk1TX0FDQ0VQVEVEPW5ldwplbGlmIFtbIC1mICRURVJNU19SRUNPUkQgJiYgJChoZWFkIC1uIDEgLS0gIiRURVJNU19SRUNPUkQiIDI+L2Rldi9udWxsKSA9PSAiJFRFUk1TX1ZFUlNJT04gIiogXV07IHRoZW4KICBURVJNU19BQ0NFUFRFRD1yZWNvcmRlZAplbGlmICg6IDwvZGV2L3R0eSkgMj4vZGV2L251bGw7IHRoZW4KICAjIFJlYWQgZnJvbSB0aGUgdGVybWluYWw6IHRoZSBwdWJsaXNoZWQgYm9vdHN0cmFwcyBydW4gdGhpcyBzY3JpcHQgd2l0aCBzdGRpbgogICMgYWxyZWFkeSB1c2VkIGZvciB0aGVpciBvd24gY29kZS4KICBleGVjIDM8Pi9kZXYvdHR5CiAgeyB0ZXJtc190ZXh0OyBwcmludGYgJ1xuVHlwZSB5ZXMgdG8gYWNjZXB0IHRoZXNlIHRlcm1zOiAnOyB9ID4mMwogIEFOU1dFUj0KICByZWFkIC1yIEFOU1dFUiA8JjMgfHwgdHJ1ZQogIGV4ZWMgMz4mLQogIGlmIFtbICR7QU5TV0VSLCx9ICE9IHllcyBdXTsgdGhlbgogICAgZWNobyAnVGhlIEZsaWdodE9wcyBiZXRhIHRlcm1zIHdlcmUgbm90IGFjY2VwdGVkLiBOb3RoaW5nIHdhcyBpbnN0YWxsZWQuJyA+JjIKICAgIGV4aXQgMQogIGZpCiAgVEVSTVNfQUNDRVBURUQ9bmV3CmVsc2UKICBwcmludGYgJyVzXG4nICJJbnN0YWxsaW5nIEZsaWdodE9wcyByZXF1aXJlcyBhY2NlcHRpbmcgdGhlIEZsaWdodE9wcyBiZXRhIHRlcm1zICgkVEVSTVNfVkVSU0lPTikuIiBcCiAgICAiUmVhZCB0aGVtIGF0ICRURVJNU19VUkwiIFwKICAgICdhbmQgcnVuIHRoaXMgY29tbWFuZCBhZ2FpbiB3aXRoIC0tYWNjZXB0LXRlcm1zIChvciBGTElHSFRPUFNfQUNDRVBUX1RFUk1TPTEpLicgPiYyCiAgZXhpdCAyCmZpCmlmIFtbICRURVJNU19BQ0NFUFRFRCA9PSBuZXcgXV07IHRoZW4KICB7IG1rZGlyIC1wIC0tICIke1RFUk1TX1JFQ09SRCUvKn0iICYmCiAgICBwcmludGYgJyVzICVzXG4nICIkVEVSTVNfVkVSU0lPTiIgIiQoZGF0ZSAtdSArJVktJW0tJWRUJUg6JU06JVNaKSIgPiIkVEVSTVNfUkVDT1JEIjsgfSAyPi9kZXYvbnVsbCB8fAogICAgZWNobyAiV2FybmluZzogY291bGQgbm90IHJlY29yZCB0aGUgdGVybXMgYWNjZXB0YW5jZSBpbiAkVEVSTVNfUkVDT1JEIiA+JjIKZmkKaWYgW1sgJFRFUk1TX09OTFkgPT0gMSBdXTsgdGhlbgogIGVjaG8gIkZsaWdodE9wcyBiZXRhIHRlcm1zICRURVJNU19WRVJTSU9OIGFjY2VwdGVkLiIKICBleGl0IDAKZmkKaWYgW1sgLXogJFNPVVJDRSAmJiAkRE9XTkxPQUQgPT0gMCBdXTsgdGhlbgogIFNPVVJDRT0kKCIkUFlUSE9OIiAtSSAtICIkUkVQT19ESVIvb3V0cHV0L3ByaXZhdGUtcmVsZWFzZSIgPDwnUFknCmltcG9ydCBqc29uLCBwYXRobGliLCBwbGF0Zm9ybSwgc3lzCmJhc2UgPSBwYXRobGliLlBhdGgoc3lzLmFyZ3ZbMV0pCm9zX2luZm8gPSB7fQpmb3IgbGluZSBpbiBwYXRobGliLlBhdGgoJy9ldGMvb3MtcmVsZWFzZScpLnJlYWRfdGV4dCgpLnNwbGl0bGluZXMoKToKICAgIGlmICc9JyBpbiBsaW5lOgogICAgICAgIGtleSwgdmFsdWUgPSBsaW5lLnNwbGl0KCc9JywgMSkKICAgICAgICBvc19pbmZvW2tleV0gPSB2YWx1ZS5zdHJpcCgnIicpCmhvc3QgPSAob3NfaW5mby5nZXQoJ0lEJyksIG9zX2luZm8uZ2V0KCdWRVJTSU9OX0lEJyksIHBsYXRmb3JtLm1hY2hpbmUoKSwKICAgICAgICBmJ3tzeXMudmVyc2lvbl9pbmZvLm1ham9yfS57c3lzLnZlcnNpb25faW5mby5taW5vcn0nKQptYXRjaGVzID0gW10KZm9yIG1hbmlmZXN0IGluIGJhc2UuZ2xvYignKi9yZWxlYXNlLmpzb24nKToKICAgIHRyeToKICAgICAgICBkYXRhID0ganNvbi5sb2FkcyhtYW5pZmVzdC5yZWFkX3RleHQoKSkKICAgICAgICByZXF1aXJlZCA9IHR1cGxlKGRhdGFba2V5XSBmb3Iga2V5IGluICgnb3NfaWQnLCAnb3NfdmVyc2lvbicsICdhcmNoJywgJ3B5dGhvbl9taW5vcicpKQogICAgICAgIGlmIHJlcXVpcmVkID09IGhvc3QgYW5kIGRhdGEuZ2V0KCdyZWxlYXNlX2lkJykgPT0gbWFuaWZlc3QucGFyZW50Lm5hbWU6CiAgICAgICAgICAgIG1hdGNoZXMuYXBwZW5kKChtYW5pZmVzdC5zdGF0KCkuc3RfbXRpbWVfbnMsIGRhdGFbJ3JlbGVhc2VfaWQnXSwgbWFuaWZlc3QucGFyZW50KSkKICAgIGV4Y2VwdCAoT1NFcnJvciwgVmFsdWVFcnJvciwgS2V5RXJyb3IsIFR5cGVFcnJvcik6CiAgICAgICAgY29udGludWUKaWYgbWF0Y2hlczoKICAgIHByaW50KG1heChtYXRjaGVzKVsyXSkKUFkKICApCmZpCmlmIFtbIC16ICRTT1VSQ0UgXV07IHRoZW4KICAjIFNlbGVjdCB0aGUgcHVibGlzaGVkIGJ1aWxkIGZvciB0aGlzIGV4YWN0IGhvc3QgYmVmb3JlIGFueSBuZXR3b3JrIHVzZS4KICAjIFRoZSAwLjIuMGIxIGJldGEgYnVpbGRzLiBUaGVzZSByb3dzIG11c3QgbWF0Y2ggcmVsZWFzZXMvMC4yLjBiMS5qc29uIGluIHRoaXMgcmVwb3NpdG9yeQogICMgKHNjcmlwdHMvdGVzdHMvdGVzdF9pbnN0YWxsX2VudHJ5LnB5IGNoZWNrcyBpdCkgYW5kIHRoZSBob3N0aW5nIHByb2plY3QncwogICMgZGVwbG95bWVudC9jbG91ZGZsYXJlL3JlbGVhc2UuanNvbi4gVGhlIFVSTHMgc2VydmUgb25jZSB0aGUgb3duZXIgZGVwbG95cyB0aGVtLgogIFJFTEVBU0VfUk9XPSQoIiRQWVRIT04iIC1JIC0gPDwnUFknCmltcG9ydCBwYXRobGliLCBwbGF0Zm9ybSwgc3lzClBVQkxJU0hFRCA9ICgKICAgIHsndGFyZ2V0JzogKCd1YnVudHUnLCAnMjIuMDQnLCAneDg2XzY0JywgJzMuMTAnKSwgJ3Jvcyc6ICdIdW1ibGUnLAogICAgICdyZWxlYXNlX2lkJzogJ2ZsaWdodG9wcy0yMDI2MTAyMi0wLjIuMGIxLWh1bWJsZS1wdWJsaWMzJywKICAgICAnYXJjaGl2ZSc6ICdmbGlnaHRvcHMtMjAyNjEwMjItMC4yLjBiMS1odW1ibGUtcHVibGljMy1yZWRpc3RyaWJ1dGFibGUudGFyLmd6JywKICAgICAnc2l6ZSc6IDQ1ODU4MjMwLAogICAgICdzaGEyNTYnOiAnMzAxYjlmOTc3YjE0N2IxY2M2NzBhZmNmMzUzYjU3Y2E3YzQ2ZWM1OGMyMTJiMzJmMjZlNjNkNmExNDJjMzg3Zid9LAogICAgeyd0YXJnZXQnOiAoJ3VidW50dScsICcyNC4wNCcsICd4ODZfNjQnLCAnMy4xMicpLCAncm9zJzogJ0phenp5JywKICAgICAncmVsZWFzZV9pZCc6ICdmbGlnaHRvcHMtMjAyNjEwMjItMC4yLjBiMS1qYXp6eS1wdWJsaWMzJywKICAgICAnYXJjaGl2ZSc6ICdmbGlnaHRvcHMtMjAyNjEwMjItMC4yLjBiMS1qYXp6eS1wdWJsaWMzLXJlZGlzdHJpYnV0YWJsZS50YXIuZ3onLAogICAgICdzaXplJzogNDYwNzMxMDIsCiAgICAgJ3NoYTI1Nic6ICc3NDEyZTJjNTFmZjJmY2M2NzJkZGMzOTI3OGEwNDg0NmE1MmVlYmM0NGVhY2RmZGQxNTBjNjJiNWM4MzNkYjE1J30sCikKT1JJR0lOID0gJ2h0dHBzOi8vZ2V0LmZsaWdodG9wcy5kZXYnCm9zX2luZm8gPSB7fQpmb3IgbGluZSBpbiBwYXRobGliLlBhdGgoJy9ldGMvb3MtcmVsZWFzZScpLnJlYWRfdGV4dCgpLnNwbGl0bGluZXMoKToKICAgIGlmICc9JyBpbiBsaW5lOgogICAgICAgIGtleSwgdmFsdWUgPSBsaW5lLnNwbGl0KCc9JywgMSkKICAgICAgICBvc19pbmZvW2tleV0gPSB2YWx1ZS5zdHJpcCgnIicpCmFjdHVhbCA9IChvc19pbmZvLmdldCgnSUQnKSwgb3NfaW5mby5nZXQoJ1ZFUlNJT05fSUQnKSwgcGxhdGZvcm0ubWFjaGluZSgpLAogICAgICAgICAgZid7c3lzLnZlcnNpb25faW5mby5tYWpvcn0ue3N5cy52ZXJzaW9uX2luZm8ubWlub3J9JykKZm9yIGVudHJ5IGluIFBVQkxJU0hFRDoKICAgIGlmIGVudHJ5Wyd0YXJnZXQnXSA9PSBhY3R1YWw6CiAgICAgICAgdXJsID0gZiJ7T1JJR0lOfS9yZWxlYXNlcy97ZW50cnlbJ3NoYTI1NiddfS97ZW50cnlbJ2FyY2hpdmUnXX0iCiAgICAgICAgcHJpbnQoJ1x0Jy5qb2luKChlbnRyeVsncmVsZWFzZV9pZCddLCBlbnRyeVsnYXJjaGl2ZSddLCBzdHIoZW50cnlbJ3NpemUnXSksCiAgICAgICAgICAgICAgICAgICAgICAgICBlbnRyeVsnc2hhMjU2J10sIHVybCwgZW50cnlbJ3JvcyddKSkpCiAgICAgICAgcmFpc2UgU3lzdGVtRXhpdCgwKQpzdXBwb3J0ZWQgPSAnOyAnLmpvaW4oJ3t9IHt9IHt9IFB5dGhvbiB7fSAoUk9TIDIge30pJy5mb3JtYXQoKmVbJ3RhcmdldCddLCBlWydyb3MnXSkKICAgICAgICAgICAgICAgICAgICAgIGZvciBlIGluIFBVQkxJU0hFRCkKcmFpc2UgU3lzdGVtRXhpdCgnTm8gcHVibGlzaGVkIEZsaWdodG9wcyBidWlsZCBtYXRjaGVzIHRoaXMgaG9zdCAoJwogICAgICAgICAgICAgICAgICsgJyAnLmpvaW4oc3RyKHgpIGZvciB4IGluIGFjdHVhbCkgKyAnKS4gUHVibGlzaGVkIGJ1aWxkczogJwogICAgICAgICAgICAgICAgICsgc3VwcG9ydGVkICsgJy4gRm9yIGFub3RoZXIgaG9zdCBwYXNzIC0tYXJjaGl2ZS4nKQpQWQogICkgfHwgZXhpdCAyCiAgSUZTPSQnXHQnIHJlYWQgLXIgUkVMRUFTRV9JRCBBUkNISVZFX05BTUUgQVJDSElWRV9TSVpFIEFSQ0hJVkVfU0hBMjU2IEFSQ0hJVkVfVVJMIFJFTEVBU0VfUk9TIDw8PCIkUkVMRUFTRV9ST1ciCiAgY29tbWFuZCAtdiBjdXJsID4vZGV2L251bGwgMj4mMSB8fCB7CiAgICBlY2hvICdjdXJsIGlzIHJlcXVpcmVkIHRvIGRvd25sb2FkIHRoZSBGbGlnaHRvcHMgcmVsZWFzZSAoaW5zdGFsbCBpdCwgb3IgcGFzcyAtLWFyY2hpdmUpLicgPiYyCiAgICBleGl0IDEKICB9CiAgRE9XTkxPQURfRElSPSQobWt0ZW1wIC1kICIke1RNUERJUjotL3RtcH0vZmxpZ2h0b3BzLWRvd25sb2FkLlhYWFhYWFhYIikKICBwcmludGYgJ0Rvd25sb2FkaW5nIEZsaWdodG9wcyAlcyBmb3IgUk9TIDIgJXMgKCVzIGJ5dGVzKVxuICAlc1xuJyBcCiAgICAiJFJFTEVBU0VfSUQiICIkUkVMRUFTRV9ST1MiICIkQVJDSElWRV9TSVpFIiAiJEFSQ0hJVkVfVVJMIgogICMgSFRUUFMgb25seSwgaW5jbHVkaW5nIHJlZGlyZWN0cy4gRmFpbHVyZXMgc3RheSB2aXNpYmxlIG9uIHN0ZGVyci4KICBpZiAhIGN1cmwgLS1wcm90byAnPWh0dHBzJyAtLXRsc3YxLjIgLS1wcm90by1yZWRpciAnPWh0dHBzJyAtLW1heC1yZWRpcnMgNSBcCiAgICAgIC0tZmFpbCAtLXNpbGVudCAtLXNob3ctZXJyb3IgLS1sb2NhdGlvbiAtLXVzZXItYWdlbnQgJ0ZsaWdodG9wcy1JbnN0YWxsZXIvMScgXAogICAgICAtLWNvbm5lY3QtdGltZW91dCAzMCAtLXNwZWVkLWxpbWl0IDEwMDAgLS1zcGVlZC10aW1lIDYwIFwKICAgICAgLS1tYXgtZmlsZXNpemUgIiRBUkNISVZFX1NJWkUiIFwKICAgICAgLS1vdXRwdXQgIiRET1dOTE9BRF9ESVIvJEFSQ0hJVkVfTkFNRSIgIiRBUkNISVZFX1VSTCI7IHRoZW4KICAgIGVjaG8gIkNvdWxkIG5vdCBkb3dubG9hZCB0aGUgRmxpZ2h0b3BzICRSRUxFQVNFX0lEIGFyY2hpdmUgZnJvbSAkQVJDSElWRV9VUkwiID4mMgogICAgZWNobyAnQ2hlY2sgdGhlIG5ldHdvcmsgY29ubmVjdGlvbiwgb3IgZG93bmxvYWQgdGhlIGFyY2hpdmUgeW91cnNlbGYgYW5kIHBhc3MgLS1hcmNoaXZlIHdpdGggLS1zaGEyNTYuJyA+JjIKICAgIGV4aXQgMQogIGZpCiAgIyBUaGUgZGlnZXN0IGFuZCBzaXplIGFyZSBlbWJlZGRlZCBpbiB0aGlzIHNjcmlwdCwgbm90IGZldGNoZWQgZnJvbSB0aGUKICAjIGRvd25sb2FkIGhvc3QsIHNvIGEgcmVwbGFjZWQgYXJjaGl2ZSBpcyByZWZ1c2VkIHJhdGhlciB0aGFuIG1lcmVseSBub3RpY2VkLgogICIkUFlUSE9OIiAtSSAtICIkRE9XTkxPQURfRElSLyRBUkNISVZFX05BTUUiICIkQVJDSElWRV9TSVpFIiAiJEFSQ0hJVkVfU0hBMjU2IiA8PCdQWScgfHwgZXhpdCAxCmltcG9ydCBoYXNobGliLCBwYXRobGliLCBzeXMKYXJjaGl2ZSwgc2l6ZSwgZXhwZWN0ZWQgPSBwYXRobGliLlBhdGgoc3lzLmFyZ3ZbMV0pLCBpbnQoc3lzLmFyZ3ZbMl0pLCBzeXMuYXJndlszXQppZiBub3QgYXJjaGl2ZS5pc19maWxlKCkgb3IgYXJjaGl2ZS5zdGF0KCkuc3Rfc2l6ZSAhPSBzaXplOgogICAgcmFpc2UgU3lzdGVtRXhpdChmJ0Rvd25sb2FkZWQgcmVsZWFzZSBhcmNoaXZlIGlzIG5vdCB0aGUgcGlubmVkIHNpemUgKHtzaXplfSBieXRlcyknKQpkaWdlc3QgPSBoYXNobGliLnNoYTI1NigpCndpdGggYXJjaGl2ZS5vcGVuKCdyYicpIGFzIHN0cmVhbToKICAgIGZvciBjaHVuayBpbiBpdGVyKGxhbWJkYTogc3RyZWFtLnJlYWQoMTAyNCAqIDEwMjQpLCBiJycpOgogICAgICAgIGRpZ2VzdC51cGRhdGUoY2h1bmspCmlmIGRpZ2VzdC5oZXhkaWdlc3QoKSAhPSBleHBlY3RlZDoKICAgIHJhaXNlIFN5c3RlbUV4aXQoJ0Rvd25sb2FkZWQgcmVsZWFzZSBhcmNoaXZlIFNIQS0yNTYgbWlzbWF0Y2gnKQpQWQogIFNPVVJDRT0kRE9XTkxPQURfRElSLyRBUkNISVZFX05BTUUKICBTT1VSQ0VfS0lORD1kb3dubG9hZGVkCmZpCltbIC1lICRTT1VSQ0UgXV0gfHwgeyBlY2hvICJSZWxlYXNlIHNvdXJjZSBkb2VzIG5vdCBleGlzdDogJFNPVVJDRSIgPiYyOyBleGl0IDI7IH0KU09VUkNFPSQocmVhbHBhdGggLWUgLS0gIiRTT1VSQ0UiKQppZiBbWyAtbiAkU0hBMjU2IF1dOyB0aGVuCiAgW1sgLWYgJFNPVVJDRSBdXSB8fCB7IGVjaG8gJy0tc2hhMjU2IHBpbnMgYW4gYXJjaGl2ZSwgbm90IGFuIGV4dHJhY3RlZCBkaXJlY3RvcnknID4mMjsgZXhpdCAyOyB9CiAgIiRQWVRIT04iIC1JIC0gIiRTT1VSQ0UiICIkU0hBMjU2IiA8PCdQWScgfHwgZXhpdCAxCmltcG9ydCBoYXNobGliLCBwYXRobGliLCBzeXMKYXJjaGl2ZSwgZXhwZWN0ZWQgPSBwYXRobGliLlBhdGgoc3lzLmFyZ3ZbMV0pLCBzeXMuYXJndlsyXS5sb3dlcigpCmRpZ2VzdCA9IGhhc2hsaWIuc2hhMjU2KCkKd2l0aCBhcmNoaXZlLm9wZW4oJ3JiJykgYXMgc3RyZWFtOgogICAgZm9yIGNodW5rIGluIGl0ZXIobGFtYmRhOiBzdHJlYW0ucmVhZCgxMDI0ICogMTAyNCksIGInJyk6CiAgICAgICAgZGlnZXN0LnVwZGF0ZShjaHVuaykKaWYgZGlnZXN0LmhleGRpZ2VzdCgpICE9IGV4cGVjdGVkOgogICAgcmFpc2UgU3lzdGVtRXhpdCgnUmVsZWFzZSBhcmNoaXZlIGRvZXMgbm90IG1hdGNoIHRoZSAtLXNoYTI1NiBwaW4nKQpQWQpmaQppZiBbWyAtZiAkU09VUkNFIF1dOyB0aGVuCiAgRVhUUkFDVEVEPSQobWt0ZW1wIC1kICIke1RNUERJUjotL3RtcH0vZmxpZ2h0b3BzLWluc3RhbGwuWFhYWFhYWFgiKQogIFNPVVJDRT0kKCIkUFlUSE9OIiAtSSAtICIkU09VUkNFIiAiJEVYVFJBQ1RFRCIgPDwnUFknCmltcG9ydCBwYXRobGliLCBzeXMsIHRhcmZpbGUsIHpsaWIKYXJjaGl2ZSwgZGVzdGluYXRpb24gPSBwYXRobGliLlBhdGgoc3lzLmFyZ3ZbMV0pLCBwYXRobGliLlBhdGgoc3lzLmFyZ3ZbMl0pCnRyeToKICAgIHdpdGggdGFyZmlsZS5vcGVuKGFyY2hpdmUsICdyOmd6JykgYXMgYnVuZGxlOgogICAgICAgIG1lbWJlcnMgPSBidW5kbGUuZ2V0bWVtYmVycygpCiAgICAgICAgcm9vdHMsIHNlZW4gPSBzZXQoKSwgc2V0KCkKICAgICAgICB0b3RhbCA9IDAKICAgICAgICBmb3IgbWVtYmVyIGluIG1lbWJlcnM6CiAgICAgICAgICAgIHBhcnRzID0gbWVtYmVyLm5hbWUucnN0cmlwKCcvJykuc3BsaXQoJy8nKQogICAgICAgICAgICBpZiAobm90IHBhcnRzIG9yIG1lbWJlci5uYW1lLnN0YXJ0c3dpdGgoJy8nKSBvcgogICAgICAgICAgICAgICAgICAgIGFueShwYXJ0IGluICgnJywgJy4nLCAnLi4nKSBmb3IgcGFydCBpbiBwYXJ0cykgb3IKICAgICAgICAgICAgICAgICAgICBub3QgKG1lbWJlci5pc2ZpbGUoKSBvciBtZW1iZXIuaXNkaXIoKSkgb3IgbWVtYmVyLm5hbWUgaW4gc2Vlbik6CiAgICAgICAgICAgICAgICByYWlzZSBTeXN0ZW1FeGl0KGYnVW5zYWZlIHJlbGVhc2UgYXJjaGl2ZSBtZW1iZXI6IHttZW1iZXIubmFtZX0nKQogICAgICAgICAgICByb290cy5hZGQocGFydHNbMF0pCiAgICAgICAgICAgIHNlZW4uYWRkKG1lbWJlci5uYW1lKQogICAgICAgICAgICB0b3RhbCArPSBtZW1iZXIuc2l6ZQogICAgICAgICAgICBpZiB0b3RhbCA+IDEwMjQgKiAxMDI0ICogMTAyNDoKICAgICAgICAgICAgICAgIHJhaXNlIFN5c3RlbUV4aXQoJ1JlbGVhc2UgYXJjaGl2ZSBleGNlZWRzIDEgR2lCIHVucGFja2VkJykKICAgICAgICBpZiBsZW4ocm9vdHMpICE9IDE6CiAgICAgICAgICAgIHJhaXNlIFN5c3RlbUV4aXQoJ1JlbGVhc2UgYXJjaGl2ZSBtdXN0IGNvbnRhaW4gb25lIHRvcC1sZXZlbCBkaXJlY3RvcnknKQogICAgICAgIHJvb3QgPSByb290cy5wb3AoKQogICAgICAgIGlmIG5vdCBhbnkobWVtYmVyLm5hbWUgPT0gcm9vdCArICcvcmVsZWFzZS5qc29uJyBmb3IgbWVtYmVyIGluIG1lbWJlcnMpOgogICAgICAgICAgICByYWlzZSBTeXN0ZW1FeGl0KCdSZWxlYXNlIGFyY2hpdmUgaGFzIG5vIG1hbmlmZXN0JykKICAgICAgICAjIFRoZSBtZW1iZXIgY2hlY2tzIGFib3ZlIGFscmVhZHkgcmVmdXNlIGxpbmtzLCBkZXZpY2VzIGFuZCBwYXRoIGVzY2FwZXMuCiAgICAgICAgIyBXaGVyZSBhdmFpbGFibGUgKDMuMTIrLCBhbmQgc2VjdXJpdHkgYmFja3BvcnRzIHRvIDMuMTAvMy4xMSksIGFsc28gYXBwbHkKICAgICAgICAjIHRoZSBzdGRsaWIgImRhdGEiIGZpbHRlcjsgaXQgYXZvaWRzIHRoZSAzLjEyIGRlcHJlY2F0aW9uIHdhcm5pbmcuCiAgICAgICAgaWYgaGFzYXR0cih0YXJmaWxlLCAnZGF0YV9maWx0ZXInKToKICAgICAgICAgICAgYnVuZGxlLmV4dHJhY3RhbGwoZGVzdGluYXRpb24sIGZpbHRlcj0nZGF0YScpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgYnVuZGxlLmV4dHJhY3RhbGwoZGVzdGluYXRpb24pCmV4Y2VwdCAodGFyZmlsZS5UYXJFcnJvciwgRU9GRXJyb3IsIE9TRXJyb3IsIHpsaWIuZXJyb3IpIGFzIGVycm9yOgogICAgZGV0YWlsID0gJyAnLmpvaW4oc3RyKGVycm9yKS5zcGxpdCgpKSBvciB0eXBlKGVycm9yKS5fX25hbWVfXwogICAgcmFpc2UgU3lzdGVtRXhpdChmJ0Nhbm5vdCByZWFkIHRoZSByZWxlYXNlIGFyY2hpdmUgKGNvcnJ1cHQgb3IgdHJ1bmNhdGVkKToge2RldGFpbH0nKQpwcmludChkZXN0aW5hdGlvbiAvIHJvb3QpClBZCiAgKQplbGlmIFtbICEgLWQgJFNPVVJDRSBdXTsgdGhlbgogIGVjaG8gIlJlbGVhc2Ugc291cmNlIG11c3QgYmUgYW4gYXJjaGl2ZSBvciBkaXJlY3Rvcnk6ICRTT1VSQ0UiID4mMgogIGV4aXQgMgpmaQpwcmludGYgJ0luc3RhbGxpbmcgJXMgRmxpZ2h0b3BzIHJlbGVhc2UgZnJvbSAlc1xuJyAiJFNPVVJDRV9LSU5EIiAiJFNPVVJDRSIKYmFzaCAiJFNPVVJDRS9pbnN0YWxsLnNoIiAiJHtJTlNUQUxMX0FSR1NbQF19Igo=' PYTHON=python3 FORWARD=() declare -A SEEN_OPTIONS=() usage() { cat <<'HELP' Usage: bash bootstrap.sh [--python PATH] [--prefix DIR] [--bin-dir DIR] [--accept-terms] Installs the pinned Flightops release on Ubuntu 24.04 x86_64 with Python 3.12 and ROS 2 Jazzy (source its setup file, normally /opt/ros/jazzy/setup.bash). The archive URL, size and SHA-256 are embedded; there is no --archive, --sha256, --download or --source override, and no sudo or GitHub access is used. It first shows the FlightOps beta terms and asks you to type yes. Without a terminal, read them at https://beta.flightops.dev/terms and pass --accept-terms (or set FLIGHTOPS_ACCEPT_TERMS=1). HELP } while (($#)); do case "$1" in --python|--prefix|--bin-dir) (($# >= 2)) || { echo "$1 needs a value" >&2; exit 2; } [[ -n $2 ]] || { echo "$1 needs a value" >&2; exit 2; } [[ -z ${SEEN_OPTIONS[$1]+present} ]] || { echo "$1 may be supplied only once" >&2; exit 2; } SEEN_OPTIONS["$1"]=present [[ $1 != --python ]] || PYTHON=$2 FORWARD+=("$1" "$2"); shift 2 ;; --accept-terms) export FLIGHTOPS_ACCEPT_TERMS=1; shift ;; --archive|--sha256|--download|--source) echo "$1 is not supported; this bootstrap installs only the pinned release" >&2 exit 2 ;; --help|-h) usage; exit 0 ;; --*) echo "Unknown option: $1" >&2; exit 2 ;; *) echo "Unexpected argument: $1" >&2; exit 2 ;; esac done command -v "$PYTHON" >/dev/null 2>&1 || { echo "Python interpreter not found: $PYTHON" >&2; exit 2; } exec "$PYTHON" - "$ARCHIVE_URL" "$ARCHIVE_SHA256" "$ARCHIVE_SIZE" "$GUIDE_URL" "$INSTALL_SH_BASE64" "${FORWARD[@]+"${FORWARD[@]}"}" <<'PY' # @@BOOTSTRAP-PYTHON-BEGIN@@ """Pinned Flightops bootstrap: gate the host, stream the archive over HTTPS, then delegate to the embedded repository install.sh.""" import base64 import hashlib import importlib import os import pathlib import platform import re import shlex import shutil import subprocess import sys import tempfile import urllib.error import urllib.parse import urllib.request from collections import namedtuple MAX_REDIRECTS = 5 TIMEOUT = 30.0 CHUNK = 1 << 20 SUPPORTED = ('ubuntu', '24.04', 'x86_64', '3.12') Host = namedtuple('Host', 'os_id os_version arch python_minor ros_distro ros_jazzy_setup') class BootstrapError(RuntimeError): pass def detect_host(): release = {} for line in pathlib.Path('/etc/os-release').read_text().splitlines(): if '=' in line: key, value = line.split('=', 1) release[key] = value.strip().strip('"') return Host(release.get('ID'), release.get('VERSION_ID'), platform.machine(), f'{sys.version_info.major}.{sys.version_info.minor}', os.environ.get('ROS_DISTRO'), pathlib.Path('/opt/ros/jazzy/setup.bash').is_file()) def validate_host(host): actual = (host.os_id, host.os_version, host.arch, host.python_minor) if actual != SUPPORTED: raise BootstrapError('Flightops requires Ubuntu 24.04 x86_64 with Python 3.12; ' 'this host is ' + ' '.join(str(value) for value in actual)) if host.ros_distro and host.ros_distro != 'jazzy': raise BootstrapError(f'ROS_DISTRO={host.ros_distro} is active; Flightops requires ROS 2 Jazzy. ' 'Unset the conflicting ROS environment and source the Jazzy setup file ' '(normally /opt/ros/jazzy/setup.bash) first') if host.ros_distro != 'jazzy' and not host.ros_jazzy_setup: raise BootstrapError('Flightops requires ROS 2 Jazzy: source its setup file ' '(normally /opt/ros/jazzy/setup.bash) first. Already-sourced custom ' 'Jazzy installations are also supported') class _NoRedirect(urllib.request.HTTPRedirectHandler): def redirect_request(self, *args, **kwargs): return None class Fetch: def __init__(self, status, headers, body, url): self.status, self.headers, self.body, self.url = status, headers, body, url def close(self): close = getattr(self.body, 'close', None) if close is not None: close() class HttpsTransport: """One HTTPS request, never following a redirect automatically.""" def __init__(self): self._opener = urllib.request.build_opener(_NoRedirect()) def get(self, url): try: request = urllib.request.Request(url, headers={'User-Agent': 'Flightops-Installer/1', 'Accept-Encoding': 'identity'}) response = self._opener.open(request, timeout=TIMEOUT) except urllib.error.HTTPError as error: return Fetch(error.code, error.headers, error, error.geturl()) except (urllib.error.URLError, TimeoutError, OSError) as error: reason = getattr(error, 'reason', error) raise BootstrapError(f'Network request failed for {url}: {reason}') from error return Fetch(response.status, response.headers, response, response.geturl()) def _validate_url(url): parts = urllib.parse.urlsplit(url) if parts.scheme != 'https': raise BootstrapError(f'Refusing non-HTTPS URL: {url}') if not parts.hostname or parts.username or parts.password: raise BootstrapError(f'Refusing URL without a plain HTTPS hostname: {url}') try: port = parts.port except ValueError as error: raise BootstrapError(f'Refusing URL with an invalid port: {url}') from error if port not in (None, 443): raise BootstrapError(f'Refusing HTTPS URL on port {port}; only port 443 is allowed: {url}') return parts.hostname def download(url, sha256, size, destination, transport): expected_host = _validate_url(url) current = url visited = set() for redirects in range(MAX_REDIRECTS + 1): if current in visited: raise BootstrapError(f'Redirect loop while downloading {url}') visited.add(current) try: result = transport.get(current) except BootstrapError: raise except (urllib.error.URLError, TimeoutError, OSError) as error: reason = getattr(error, 'reason', error) raise BootstrapError(f'Network request failed for {current}: {reason}') from error try: if result.status in (301, 302, 303, 307, 308): headers = result.headers or {} location = headers.get('Location') or headers.get('location') if not location: raise BootstrapError('Redirect response has no Location header') target = urllib.parse.urljoin(current, location) if _validate_url(target) != expected_host: raise BootstrapError(f'Refusing redirect off {expected_host}: {target}') if target in visited: raise BootstrapError(f'Redirect loop while downloading {url}') if redirects >= MAX_REDIRECTS: raise BootstrapError(f'More than {MAX_REDIRECTS} redirects while downloading {url}') current = target continue if not 200 <= result.status < 300: raise BootstrapError(f'Download failed with HTTP {result.status}: {current}') digest, written = hashlib.sha256(), 0 with open(destination, 'wb') as output: while True: try: chunk = result.body.read(CHUNK) except (urllib.error.URLError, TimeoutError, OSError) as error: reason = getattr(error, 'reason', error) raise BootstrapError(f'Network read failed for {current}: {reason}') from error if not chunk: break written += len(chunk) if written > size: raise BootstrapError('Download is larger than the pinned archive size') digest.update(chunk) output.write(chunk) if written != size: raise BootstrapError(f'Download is {written} bytes; expected {size}') if digest.hexdigest() != sha256.lower(): raise BootstrapError('Downloaded archive does not match the embedded SHA-256 pin') return finally: try: result.close() except Exception: pass def installer_defaults(): # These values match the pinned archive's install.sh defaults. Expanding # XDG_DATA_HOME as a shell expression would preserve literal ${...} text. home = pathlib.Path(os.environ.get('HOME') or pathlib.Path.home()) data_home = os.environ.get('XDG_DATA_HOME') or str(home / '.local/share') return str(home / '.local/bin'), str(pathlib.Path(data_home) / 'flightops'), 'flightops' def _forwarded_options(args): values = {} index = 0 while index < len(args): name = args[index] if name not in ('--python', '--prefix', '--bin-dir'): raise BootstrapError(f'Unsupported forwarded option: {name}') if name in values: raise BootstrapError(f'{name} may be supplied only once') if index + 1 >= len(args) or not args[index + 1]: raise BootstrapError(f'{name} needs a value') values[name] = args[index + 1] index += 2 return values def _absolute_target_paths(forwarded): default_bin_dir, default_prefix, command = installer_defaults() options = _forwarded_options(forwarded) return (pathlib.Path(options.get('--bin-dir', default_bin_dir)).resolve(), pathlib.Path(options.get('--prefix', default_prefix)).resolve(), command) def run(url, sha256, size, guide_url, install_sh_base64, forwarded, *, transport=None, host=None): host = host or detect_host() validate_host(host) try: importlib.import_module('rclpy') except (ImportError, OSError) as error: raise BootstrapError('ROS 2 Jazzy Python packages are unavailable to this interpreter; ' 'source /opt/ros/jazzy/setup.bash before installing') from error if not isinstance(size, int) or size <= 0: raise BootstrapError('Embedded archive size must be a positive integer') if not re.fullmatch(r'[0-9a-fA-F]{64}', sha256): raise BootstrapError('Embedded archive SHA-256 must be 64 hexadecimal characters') _validate_url(url) bin_dir, prefix, command = _absolute_target_paths(forwarded) try: install_script = base64.b64decode(install_sh_base64, validate=True) except ValueError: raise BootstrapError('Embedded install.sh is not valid base64') print(f'Supported platform: {host.os_id} {host.os_version} {host.arch} Python {host.python_minor}', flush=True) print(f'Archive SHA-256: {sha256.lower()}', flush=True) work = tempfile.mkdtemp(prefix='flightops-bootstrap.') try: installer = pathlib.Path(work, 'install.sh') installer.write_bytes(install_script) installer.chmod(0o700) # The beta terms are accepted, or refused, before the archive is downloaded. try: subprocess.run(['bash', str(installer), '--terms-only'], check=True) except subprocess.CalledProcessError as error: raise SystemExit(error.returncode) archive = pathlib.Path(work, 'flightops.tar.gz') download(url, sha256, size, archive, transport or HttpsTransport()) print(f'Install prefix: {prefix}') subprocess.run(['bash', str(installer), '--accept-terms', '--archive', str(archive), '--sha256', sha256, *forwarded], check=True) subprocess.run([str(bin_dir / command), '--verify'], check=True) path_entries = [pathlib.Path(item or '.').resolve() for item in os.environ.get('PATH', '').split(os.pathsep)] if bin_dir not in path_entries: print(f'For this shell, run: export PATH={shlex.quote(str(bin_dir))}:"$PATH"') print(f'Fresh-system engineer guide: {guide_url}') finally: shutil.rmtree(work, ignore_errors=True) def main(argv): if len(argv) < 5: raise BootstrapError('bootstrap is missing its embedded release metadata') url, sha256, size, guide_url, install_sh_base64 = argv[:5] try: archive_size = int(size) except ValueError as error: raise BootstrapError('Embedded archive size is not a decimal integer') from error run(url, sha256, archive_size, guide_url, install_sh_base64, list(argv[5:])) def cli(argv): try: main(argv) except (BootstrapError, subprocess.CalledProcessError) as error: print(f'flightops bootstrap failed: {error}', file=sys.stderr) sys.exit(error.returncode if isinstance(error, subprocess.CalledProcessError) else 1) if __name__ == '__main__': cli(sys.argv[1:]) # @@BOOTSTRAP-PYTHON-END@@ PY