@fased/fased 0.1.67 → 0.1.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +20 -22
  3. package/config/sat-runtime.env +6 -2
  4. package/config/signer-protocol-v2.json +10 -0
  5. package/dist/agents/tools/wallet-action-tool.js +2 -0
  6. package/dist/agents/tools/wallet-tool.js +5 -3
  7. package/dist/brand.js +1 -1
  8. package/dist/build-info.json +3 -3
  9. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  10. package/dist/cli/program/register.wallet.js +119 -5
  11. package/dist/commands/wallet.js +1286 -74
  12. package/dist/config/sat-runtime-ids.js +6 -1
  13. package/dist/control-ui/assets/{app-De8gTtZU.js → app-BLFLoFPk.js} +1526 -1526
  14. package/dist/control-ui/assets/{federation-DgA9e-Pn.js → federation-SGMAnM_8.js} +2 -2
  15. package/dist/control-ui/assets/{index-DYYBRBOS.js → index-S88kp5ZQ.js} +3 -3
  16. package/dist/control-ui/assets/{mining-Dg-3ll0F.js → mining-ClI8RW4u.js} +2 -2
  17. package/dist/control-ui/assets/{wallet-DvStR_AT.js → wallet-CRIhDQbW.js} +325 -156
  18. package/dist/control-ui/index.html +1 -1
  19. package/dist/control-ui/version.json +1 -1
  20. package/dist/gateway/server-http.js +394 -55
  21. package/dist/mining/mainnet-sync.js +67 -5
  22. package/dist/operator/operator-readiness.js +1 -1
  23. package/dist/plugin-sdk/{actions-DRu135h0.js → actions-B-Vwj64K.js} +3 -3
  24. package/dist/plugin-sdk/{allow-list-DyAxs4yw.js → allow-list-Bf45HfoP.js} +1 -1
  25. package/dist/plugin-sdk/{audio-preflight-Dq_rJtqH.js → audio-preflight-CS7Q_7-R.js} +26 -25
  26. package/dist/plugin-sdk/{audit-C5d2z5U1.js → audit-CUULWG22.js} +1 -1
  27. package/dist/plugin-sdk/{channel-access-configure-DaI5wevh.js → channel-access-configure-BoUsC-Ys.js} +1 -1
  28. package/dist/plugin-sdk/{channel-adapters.discord-BS4S4Svh.js → channel-adapters.discord-DSiR4X2Y.js} +1 -1
  29. package/dist/plugin-sdk/channel-plugin-common.d.ts +2 -2
  30. package/dist/plugin-sdk/channel-plugin-common.js +1 -1
  31. package/dist/plugin-sdk/{chunk-CyoyqpbG.js → chunk-CBpl-AiQ.js} +1 -1
  32. package/dist/plugin-sdk/{chunk-items-Dunoq4Er.js → chunk-items-BLBZNORg.js} +1 -1
  33. package/dist/plugin-sdk/command-status.js +26 -25
  34. package/dist/plugin-sdk/{commands-registry-BfqdEWdF.js → commands-registry-G5KXSi_y.js} +1 -1
  35. package/dist/plugin-sdk/{config-schema--vl89M7d.js → config-schema-BiBOUW89.js} +431 -125
  36. package/dist/plugin-sdk/device-pair.d.ts +1 -1
  37. package/dist/plugin-sdk/{diagnostic-DLL_zekv.js → diagnostic-sYs6Agr-.js} +1 -1
  38. package/dist/plugin-sdk/{directory-live-CSuYxdwk.js → directory-live-BXMpuYAF.js} +1 -1
  39. package/dist/plugin-sdk/{directory-live-Dw7ZgC9_.js → directory-live-Rn1BzPof.js} +1 -1
  40. package/dist/plugin-sdk/discord.d.ts +4 -4
  41. package/dist/plugin-sdk/discord.js +20 -20
  42. package/dist/plugin-sdk/{draft-stream-controls-BqchW1vi.js → draft-stream-controls-CMNdKSx-.js} +1 -1
  43. package/dist/plugin-sdk/{guilds-CDhRIX57.js → guilds-Dkwl0yHv.js} +1 -1
  44. package/dist/plugin-sdk/{handle-action-hvwapMKg.js → handle-action-CRdM6B-8.js} +15 -15
  45. package/dist/plugin-sdk/index.d.ts +15 -15
  46. package/dist/plugin-sdk/index.js +28 -27
  47. package/dist/plugin-sdk/{login-Ca583Okc.js → login-D7WnK8-P.js} +1 -1
  48. package/dist/plugin-sdk/{login-qr-CGciBSNP.js → login-qr-CRueOKZK.js} +2 -114
  49. package/dist/plugin-sdk/{markdown-tables-DCOXEUfZ.js → markdown-tables-d7GlUs5R.js} +1 -1
  50. package/dist/plugin-sdk/{media-CAdQF5TG.js → media-BAoaxCrt.js} +1 -1
  51. package/dist/plugin-sdk/{monitor-DrTy_4CL.js → monitor-WXdn4mR-.js} +34 -33
  52. package/dist/plugin-sdk/{monitor-CV1sr0p2.js → monitor-xzVAPbg2.js} +44 -43
  53. package/dist/plugin-sdk/{openresponses-http-BAjgURZa.js → openresponses-http-DnI1LFgq.js} +26 -25
  54. package/dist/plugin-sdk/{outbound-attachment-QqTnIhZL.js → outbound-attachment-CyXM3cAv.js} +1 -1
  55. package/dist/plugin-sdk/{outbound-BgVDdHxx.js → outbound-zB6llMGW.js} +6 -6
  56. package/dist/plugin-sdk/{pairing-message-BjIJYVvp.d.ts → pairing-message-CiTcdD6m.d.ts} +1 -1
  57. package/dist/plugin-sdk/{pairing-store-BChahgI2.js → pairing-store-B6J2C0vP.js} +1 -1
  58. package/dist/plugin-sdk/{pi-model-discovery-runtime-DxTMz_XF.js → pi-model-discovery-runtime-6JzLw9Gk.js} +1 -1
  59. package/dist/plugin-sdk/{probe-CJb5uR8B.js → probe-BM7Oe40M.js} +2 -2
  60. package/dist/plugin-sdk/{probe-l_whvGLY.js → probe-CMPksimh.js} +1 -1
  61. package/dist/plugin-sdk/{probe-BFroeK3g.js → probe-oVp12D6L.js} +1 -1
  62. package/dist/plugin-sdk/provider-web-search-config-contract.d.ts +1 -1
  63. package/dist/plugin-sdk/{pw-ai-DlxqX6Es.js → pw-ai-DZYLDC-S.js} +1 -1
  64. package/dist/plugin-sdk/qr-image-B4MlMG4N.js +115 -0
  65. package/dist/plugin-sdk/{replies-ChYo5ZQM.js → replies-CY-V0Zob.js} +3 -3
  66. package/dist/plugin-sdk/{resolve-channels-87LZrFk_.js → resolve-channels-1HtDcMqh.js} +2 -2
  67. package/dist/plugin-sdk/{resolve-channels-_-nV-EK2.js → resolve-channels-CU72Qh2j.js} +1 -1
  68. package/dist/plugin-sdk/{resolve-outbound-target-D-KGgHJZ.js → resolve-outbound-target-nQSBVYz9.js} +2 -2
  69. package/dist/plugin-sdk/{resolve-route-C8tvQm8Z.js → resolve-route-Dufnu2cT.js} +1 -1
  70. package/dist/plugin-sdk/{resolve-users-DN9VQBwv.js → resolve-users-Bk_ss2vC.js} +1 -1
  71. package/dist/plugin-sdk/{resolve-users-V6dHh_2B.js → resolve-users-IevCOk4W.js} +2 -2
  72. package/dist/plugin-sdk/{runtime-2B9_WKoI.js → runtime-CXSQwMaR.js} +1 -1
  73. package/dist/plugin-sdk/{runtime-dependency-nup8niQL.js → runtime-dependency-DczkvPxn.js} +9 -9
  74. package/dist/plugin-sdk/sat-runtime.d.ts +105 -10
  75. package/dist/plugin-sdk/sat-runtime.js +26 -25
  76. package/dist/plugin-sdk/{send-DCO441U7.js → send-BXBS_mRK.js} +8 -8
  77. package/dist/plugin-sdk/{send-CFzGKJXo.js → send-D-HfKQZm.js} +4 -4
  78. package/dist/plugin-sdk/{send-B6KkGKZD.js → send-D1j12ZgX.js} +5 -5
  79. package/dist/plugin-sdk/{send-q9HXrSwK.js → send-DWKm6yay.js} +10 -10
  80. package/dist/plugin-sdk/{send-gDoGA-7e.js → send-Di1Jit3L.js} +7 -7
  81. package/dist/plugin-sdk/{skill-commands-FKEPLS7T.js → skill-commands-iJtvpqgb.js} +2 -2
  82. package/dist/plugin-sdk/{slack-DSC6T2KW.js → slack-CkIRVDUK.js} +45 -44
  83. package/dist/plugin-sdk/{slack-actions-DhbMcp8M.js → slack-actions-BzUEszJa.js} +10 -10
  84. package/dist/plugin-sdk/slack.d.ts +4 -4
  85. package/dist/plugin-sdk/slack.js +6 -6
  86. package/dist/plugin-sdk/{sticker-cache-C0l_5IKg.js → sticker-cache-Dpap5ce_.js} +2 -2
  87. package/dist/plugin-sdk/{tables-B3jr0Pvq.js → tables-_1W36GII.js} +2 -2
  88. package/dist/plugin-sdk/{targets-CtSbTxYX.js → targets-DDbNnLnB.js} +2 -2
  89. package/dist/plugin-sdk/{telegram-actions-BwGBIVDE.js → telegram-actions-Cewdga8H.js} +30 -29
  90. package/dist/plugin-sdk/telegram.d.ts +4 -4
  91. package/dist/plugin-sdk/telegram.js +3 -3
  92. package/dist/plugin-sdk/{thread-bindings.lifecycle-QbkNl-m6.js → thread-bindings.lifecycle-Y9T3CQGf.js} +3 -3
  93. package/dist/plugin-sdk/{thread-bindings.state-D9hg_M1c.js → thread-bindings.state-D2oOWDpv.js} +1 -1
  94. package/dist/plugin-sdk/{tmp-fased-dir-DXnGKKgi.d.ts → tmp-fased-dir-BSEHc8MQ.d.ts} +8 -2
  95. package/dist/plugin-sdk/{types-eG9aYqXF.d.ts → types-BakzIzGN.d.ts} +1 -1
  96. package/dist/plugin-sdk/{types-Cegc86EP.d.ts → types-CzF0YQqK.d.ts} +3 -3
  97. package/dist/plugin-sdk/{web-C_AH5PyB.js → web-DGWi7i3B.js} +33 -32
  98. package/dist/plugin-sdk/{whatsapp-actions-Bj_rOncr.js → whatsapp-actions-Blu9-eX-.js} +9 -9
  99. package/dist/plugin-sdk/{whatsapp-heartbeat-B1WHkJIg.js → whatsapp-heartbeat-DjmQM7hq.js} +2 -2
  100. package/dist/plugin-sdk/whatsapp.d.ts +9 -9
  101. package/dist/plugin-sdk/whatsapp.js +7 -7
  102. package/dist/plugin-sdk/{wsl-J-HscWxS.js → wsl-CTvIWvZz.js} +1 -1
  103. package/dist/plugin-sdk/{zod-schema.providers-core-FaYR6KdD.d.ts → zod-schema.providers-core-CIzCjXH9.d.ts} +61 -61
  104. package/dist/wallet/local-socket-signer-lifecycle.js +107 -3
  105. package/dist/wallet/local-socket-signer-protocol.js +97 -10
  106. package/dist/wallet/mining-wallet-retirement.js +256 -0
  107. package/dist/wallet/providers/local-socket-signer-adapter.js +10 -0
  108. package/dist/wallet/signer-network-admin.js +11 -118
  109. package/dist/wallet/signer-protocol-v2.generated.js +10 -0
  110. package/dist/wallet/solana-assets.js +9 -3
  111. package/dist/wallet/solana-rpc-read-fetch.js +65 -0
  112. package/dist/wallet/wallet-agent-selection.js +6 -10
  113. package/dist/wallet/wallet-provider-registry.js +70 -29
  114. package/dist/wallet/wallet-send-approvals.js +6 -2
  115. package/dist/wallet/wallet-status.js +63 -1
  116. package/dist/web/login-qr.js +1 -1
  117. package/dist/wizard/onboarding.js +86 -84
  118. package/dist/wizard/onboarding.wallet.js +10 -1
  119. package/extensions/acpx/package.json +1 -1
  120. package/extensions/bluebubbles/package.json +1 -1
  121. package/extensions/copilot-proxy/package.json +1 -1
  122. package/extensions/diagnostics-otel/package.json +1 -1
  123. package/extensions/fased-federation/package.json +1 -1
  124. package/extensions/google-gemini-cli-auth/package.json +1 -1
  125. package/extensions/imessage/package.json +1 -1
  126. package/extensions/irc/package.json +1 -1
  127. package/extensions/line/package.json +1 -1
  128. package/extensions/llm-task/package.json +1 -1
  129. package/extensions/lobster/package.json +1 -1
  130. package/extensions/matrix/CHANGELOG.md +12 -0
  131. package/extensions/matrix/package.json +1 -1
  132. package/extensions/mattermost/package.json +1 -1
  133. package/extensions/memory-core/package.json +2 -2
  134. package/extensions/memory-lancedb/package.json +1 -1
  135. package/extensions/minimax-portal-auth/package.json +1 -1
  136. package/extensions/msteams/CHANGELOG.md +12 -0
  137. package/extensions/msteams/package.json +1 -1
  138. package/extensions/nextcloud-talk/package.json +1 -1
  139. package/extensions/nostr/CHANGELOG.md +12 -0
  140. package/extensions/nostr/package.json +1 -1
  141. package/extensions/open-prose/package.json +1 -1
  142. package/extensions/sat-mining/package.json +1 -1
  143. package/extensions/signal/package.json +1 -1
  144. package/extensions/synology-chat/package.json +1 -1
  145. package/extensions/tlon/package.json +1 -1
  146. package/extensions/twitch/CHANGELOG.md +12 -0
  147. package/extensions/twitch/package.json +1 -1
  148. package/extensions/voice-call/CHANGELOG.md +12 -0
  149. package/extensions/voice-call/package.json +1 -1
  150. package/extensions/zalo/CHANGELOG.md +12 -0
  151. package/extensions/zalo/package.json +1 -1
  152. package/extensions/zalouser/CHANGELOG.md +12 -0
  153. package/extensions/zalouser/package.json +1 -1
  154. package/install.sh +286 -107
  155. package/package.json +4 -2
  156. package/scripts/fased-managed-updater.mjs +10 -2
  157. package/scripts/install-fased-signerd.sh +4 -8
  158. package/scripts/install-managed-runtime.mjs +10 -7
  159. package/scripts/install-runtime-profile.sh +27 -0
  160. package/scripts/start-managed.sh +13 -1
  161. /package/dist/plugin-sdk/{active-listener-B_NMtgPL.js → active-listener-p9Ooc1qj.js} +0 -0
  162. /package/dist/plugin-sdk/{blocks-input-Bg-8S2pl.js → blocks-input-DiZaqaN7.js} +0 -0
  163. /package/dist/plugin-sdk/{channel-access-CcFRdRPO.js → channel-access-B0aZBSd2.js} +0 -0
  164. /package/dist/plugin-sdk/{channel-activity-DBDMQ3yI.js → channel-activity-F07DjroV.js} +0 -0
  165. /package/dist/plugin-sdk/{channel-core-CFRs8LPy.js → channel-core-DoF4X04L.js} +0 -0
  166. /package/dist/plugin-sdk/{client-pI228AVT.js → client-DLY-kWF_.js} +0 -0
  167. /package/dist/plugin-sdk/{command-poll-backoff-yJU83wEq.js → command-poll-backoff-NAePk3eL.js} +0 -0
  168. /package/dist/plugin-sdk/{date-time-uUevHscU.js → date-time-ChY_tHoG.js} +0 -0
  169. /package/dist/plugin-sdk/{draft-stream-loop-CrOPfV3F.js → draft-stream-loop-DMBwjjd1.js} +0 -0
  170. /package/dist/plugin-sdk/{fetch-BtmoPIlf.js → fetch-B0v6OTUZ.js} +0 -0
  171. /package/dist/plugin-sdk/{filesystem-manager-DoI8IBNF.js → filesystem-manager-BzuIJ9K9.js} +0 -0
  172. /package/dist/plugin-sdk/{gateway-registry-DZDsqicG.js → gateway-registry-Bgl2GFgy.js} +0 -0
  173. /package/dist/plugin-sdk/{inbound-context-DrAcXE0y.js → inbound-context-CQnfFFQ9.js} +0 -0
  174. /package/dist/plugin-sdk/{pi-model-discovery-BX_JLcEJ.js → pi-model-discovery-BHNVyRny.js} +0 -0
  175. /package/dist/plugin-sdk/{polls-DKQXVfJp.js → polls-DXd67TD8.js} +0 -0
  176. /package/dist/plugin-sdk/{proxy-DWpYcsvR.js → proxy-CLRUdShp.js} +0 -0
  177. /package/dist/plugin-sdk/{qmd-manager-CgRaTxUq.js → qmd-manager-B8Vnu0ND.js} +0 -0
  178. /package/dist/plugin-sdk/{render-e7fENCYH.js → render-DrBfVs0u.js} +0 -0
  179. /package/dist/plugin-sdk/{reply-reference-Bqk-7HXE.js → reply-reference-BnAk5Wjb.js} +0 -0
  180. /package/dist/plugin-sdk/{retry-policy-C6SjKjQA.js → retry-policy-CXh1owkc.js} +0 -0
  181. /package/dist/plugin-sdk/{sandbox-paths-Dx2lUqCu.js → sandbox-paths-Bzh1CuZc.js} +0 -0
  182. /package/dist/plugin-sdk/{session-DHD4ZKyG.js → session-iuGR_rdi.js} +0 -0
  183. /package/dist/plugin-sdk/{shared-CBgp_GBw.js → shared-D1AD2Ejn.js} +0 -0
  184. /package/dist/plugin-sdk/{target-errors-Ckufe179.js → target-errors-DfS1sLR9.js} +0 -0
  185. /package/dist/plugin-sdk/{tool-loop-detection-Bp8bPJLs.js → tool-loop-detection-sDZl27w7.js} +0 -0
  186. /package/dist/plugin-sdk/{ui-Be8cLHIS.js → ui-GQDYm_jw.js} +0 -0
  187. /package/dist/plugin-sdk/{with-timeout-ckWB8rTo.js → with-timeout-CVV8tytk.js} +0 -0
package/install.sh CHANGED
@@ -36,16 +36,61 @@ if [[ "$install_entry_legacy_ts_authkey" -eq 1 ]]; then
36
36
  fi
37
37
 
38
38
  if [[ "$install_entry_is_stream" -eq 1 && "$install_entry_hosting" -eq 1 ]]; then
39
- echo "Refusing streamed VPS Hosting execution." >&2
40
- echo "Download and verify the exact tagged install.sh before execution:" >&2
41
- echo " https://docs.fased.ai/install/vps" >&2
42
- exit 1
39
+ if [[ "${#install_entry_args[@]}" -ne 1 || "${install_entry_args[0]:-}" != "--hosting" ]]; then
40
+ echo "Streamed VPS Hosting accepts only the exact fresh-install selector: --hosting" >&2
41
+ echo "Use an exact tagged, attested installer for repair, release overrides, or advanced selectors." >&2
42
+ exit 1
43
+ fi
44
+ install_entry_exported_env=()
45
+ mapfile -t install_entry_exported_env < <(compgen -e)
46
+ for install_entry_env_name in "${install_entry_exported_env[@]}"; do
47
+ if [[ "$install_entry_env_name" == FASED_* ]]; then
48
+ echo "Refusing Fased environment overrides during streamed VPS Hosting: ${install_entry_env_name}" >&2
49
+ exit 1
50
+ fi
51
+ done
52
+ install_entry_unsafe_env=(
53
+ HTTP_PROXY HTTPS_PROXY ALL_PROXY NO_PROXY http_proxy https_proxy all_proxy no_proxy
54
+ CURL_HOME CURL_CA_BUNDLE SSL_CERT_FILE SSL_CERT_DIR GIT_SSL_NO_VERIFY
55
+ GH_HOST GH_REPO GH_CONFIG_DIR TMPDIR LD_PRELOAD LD_LIBRARY_PATH BASH_ENV ENV CDPATH
56
+ )
57
+ for install_entry_env_name in "${install_entry_unsafe_env[@]}"; do
58
+ if [[ -n "${!install_entry_env_name+x}" ]]; then
59
+ echo "Refusing unsafe environment override during streamed VPS Hosting: ${install_entry_env_name}" >&2
60
+ exit 1
61
+ fi
62
+ done
63
+ install_entry_existing_hosting_paths=(
64
+ /etc/fased
65
+ /opt/fased
66
+ /var/lib/fased-installer
67
+ /var/lib/fased-gateway
68
+ /var/lib/fased-signerd
69
+ /etc/systemd/system/fased-gateway.service
70
+ /etc/systemd/system/fased-signerd.service
71
+ /usr/local/libexec/fased-gateway-launch
72
+ /usr/local/libexec/fased-host-updater
73
+ )
74
+ for install_entry_existing_path in "${install_entry_existing_hosting_paths[@]}"; do
75
+ if [[ -e "$install_entry_existing_path" || -L "$install_entry_existing_path" ]]; then
76
+ echo "Streamed VPS Hosting is only for a fresh host; existing Fased state was found." >&2
77
+ echo "Use the exact tagged repair or the installed updater instead of the mutable main bootstrap." >&2
78
+ exit 1
79
+ fi
80
+ done
81
+ PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
82
+ LANG="C.UTF-8"
83
+ LC_ALL="C.UTF-8"
84
+ export PATH LANG LC_ALL
85
+ unset -f curl gh jq tar sha256sum awk stat find grep flock install mv cp chown chmod sync 2>/dev/null || true
86
+ hash -r 2>/dev/null || true
43
87
  fi
44
88
 
45
- # A Hosting request always enters the attest-and-extract bootstrap unless it is
89
+ # A Hosting file request enters the attest-and-extract bootstrap unless it is
46
90
  # the exact inner invocation carrying the root-owned verified bundle marker.
47
- # Streamed Hosting was rejected above; standalone Hosting must be downloaded
48
- # and verified before execution.
91
+ # Streamed Hosting reaches this block only for the exact fresh --hosting
92
+ # selector validated above. Repair and advanced Hosting selectors remain
93
+ # exact-tag-only.
49
94
  if [[ "$install_entry_is_stream" -eq 1 || \
50
95
  ( "$install_entry_hosting" -eq 1 && -z "$install_entry_verified_bundle" ) ]]; then
51
96
  install_repo_url="${FASED_INSTALL_REPO:-https://github.com/fased-ai/fased.git}"
@@ -98,6 +143,10 @@ if [[ "$install_entry_is_stream" -eq 1 || \
98
143
  esac
99
144
  done
100
145
 
146
+ if [[ "$hosting_bootstrap" -eq 1 && "$hosting_repair_bootstrap" -eq 0 && -z "$hosting_release" ]]; then
147
+ hosting_release="latest"
148
+ fi
149
+
101
150
  bootstrap_as_root() {
102
151
  if [[ "$(id -u)" -eq 0 ]]; then
103
152
  "$@"
@@ -123,7 +172,7 @@ if [[ "$install_entry_is_stream" -eq 1 || \
123
172
  local source_tmp=""
124
173
  keyring_tmp="$(mktemp)"
125
174
  source_tmp="$(mktemp)"
126
- curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg -o "$keyring_tmp"
175
+ curl -q -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg -o "$keyring_tmp"
127
176
  printf 'deb [arch=%s signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main\n' "$(dpkg --print-architecture)" >"$source_tmp"
128
177
  bootstrap_as_root install -d -m 0755 /etc/apt/keyrings
129
178
  bootstrap_as_root install -m 0644 "$keyring_tmp" /etc/apt/keyrings/githubcli-archive-keyring.gpg
@@ -157,7 +206,7 @@ if [[ "$install_entry_is_stream" -eq 1 || \
157
206
  }
158
207
 
159
208
  resolve_public_latest_release_tag() {
160
- curl -fsSL --proto '=https' --tlsv1.2 \
209
+ curl -q -fsSL --proto '=https' --tlsv1.2 \
161
210
  -H 'Accept: application/vnd.github+json' \
162
211
  -H 'X-GitHub-Api-Version: 2022-11-28' \
163
212
  -H 'User-Agent: fased-installer' \
@@ -175,11 +224,10 @@ if [[ "$install_entry_is_stream" -eq 1 || \
175
224
  echo "Refusing a caller-supplied verified bundle marker." >&2
176
225
  exit 1
177
226
  fi
178
- if [[ -z "$hosting_release" ]]; then
179
- echo "VPS Hosting requires an explicit tagged release." >&2
180
- echo "Pass --release vX.Y.Z (recommended) or --release latest." >&2
227
+ [[ -n "$hosting_release" ]] || {
228
+ echo "VPS Hosting release resolution failed before verification." >&2
181
229
  exit 1
182
- fi
230
+ }
183
231
 
184
232
  install_hosting_bootstrap_tools() {
185
233
  [[ "$auto_install" -eq 1 ]] || return 0
@@ -225,22 +273,37 @@ if [[ "$install_entry_is_stream" -eq 1 || \
225
273
  fi
226
274
 
227
275
  local architecture=""
276
+ local signer_platform=""
228
277
  case "$(uname -m)" in
229
- x86_64|amd64) architecture="x64" ;;
230
- aarch64|arm64) architecture="arm64" ;;
278
+ x86_64|amd64)
279
+ architecture="x64"
280
+ signer_platform="linux-amd64"
281
+ ;;
282
+ aarch64|arm64)
283
+ architecture="arm64"
284
+ signer_platform="linux-arm64"
285
+ ;;
231
286
  *)
232
287
  echo "Unsupported Hosting architecture: $(uname -m)" >&2
233
288
  exit 1
234
289
  ;;
235
290
  esac
236
- local asset="fased-hosted-app-linux-${architecture}-v${release_version}.tar.gz"
291
+ local asset="fased-hosted-app-v2-linux-${architecture}-v${release_version}.tar.gz"
237
292
  local release_url="https://github.com/fased-ai/fased/releases/download/v${release_version}"
238
293
  local release_parent="/var/lib/fased-installer/releases/v${release_version}"
239
294
  local staging="${release_parent}/.staging.$$"
240
295
  local preflight=""
241
296
  preflight="$(mktemp -d "${TMPDIR:-/tmp}/fased-hosting-bootstrap.XXXXXX")"
242
297
  local archive="${preflight}/${asset}"
298
+ local dependency_archive=""
299
+ local dependency_asset=""
300
+ local dependency_expected=""
301
+ local dependency_hash=""
302
+ local signer_binary=""
303
+ local signer_asset=""
304
+ local signer_expected=""
243
305
  local release_manifest="${preflight}/fased-hosted-release-v2.json"
306
+ local release_manifest_bundle="${preflight}/fased-hosted-release-v2.json.attestation.json"
244
307
  local expected=""
245
308
  local actual=""
246
309
  local manifest_digest=""
@@ -248,16 +311,43 @@ if [[ "$install_entry_is_stream" -eq 1 || \
248
311
  local manifest_signer_commit=""
249
312
 
250
313
  umask 077
251
- trap 'rm -rf -- "${preflight:-}" "${staging:-}"' EXIT
252
- curl -fL --proto '=https' --tlsv1.2 "$release_url/fased-hosted-release-v2.json" -o "$release_manifest"
314
+ hosting_bootstrap_cleanup() {
315
+ local status="$?"
316
+ rm -rf -- "${preflight:-}" "${staging:-}"
317
+ if [[ "$status" -ne 0 ]]; then
318
+ if [[ -e /var/lib/fased-installer || -L /var/lib/fased-installer ]]; then
319
+ echo "Hosting bootstrap stopped without activating Fased services." >&2
320
+ echo "Persistent installer state exists; retry only with the exact tagged, attested repair procedure." >&2
321
+ else
322
+ echo "Hosting bootstrap stopped before persistent Fased state was created; fix the reported problem and rerun the exact --hosting command." >&2
323
+ fi
324
+ fi
325
+ return "$status"
326
+ }
327
+ trap hosting_bootstrap_cleanup EXIT
328
+ curl -q -fL --proto '=https' --tlsv1.2 "$release_url/fased-hosted-release-v2.json" -o "$release_manifest"
329
+ curl -q -fL --proto '=https' --tlsv1.2 "$release_url/fased-hosted-release-v2.json.attestation.json" -o "$release_manifest_bundle"
253
330
  GH_PROMPT_DISABLED=1 gh attestation verify "$release_manifest" \
254
331
  --repo fased-ai/fased \
332
+ --bundle "$release_manifest_bundle" \
255
333
  --signer-workflow fased-ai/fased/.github/workflows/hosted-runtime-release.yml \
256
334
  --source-ref "refs/tags/v${release_version}" \
257
335
  --deny-self-hosted-runners >/dev/null
258
336
  local manifest_selection=""
259
- manifest_selection="$(jq -er --arg version "$release_version" --arg architecture "$architecture" '
260
- if .schemaVersion == 2 and
337
+ manifest_selection="$(jq -er --arg version "$release_version" --arg architecture "$architecture" --arg signer_platform "$signer_platform" '
338
+ if (keys == ["application", "release", "schemaVersion", "signer"]) and
339
+ .schemaVersion == 2 and
340
+ (.release | keys == ["commit", "tag", "version"]) and
341
+ (.application | keys == ["linux"]) and
342
+ (.application.linux | keys == ["arm64", "x64"]) and
343
+ (.application.linux[$architecture] | keys == ["artifact", "dependencies"]) and
344
+ (.application.linux[$architecture].artifact | keys == ["asset", "sha256"]) and
345
+ (.application.linux[$architecture].dependencies | keys == ["asset", "dependencyHash", "sha256"]) and
346
+ (.signer | keys == ["capabilities", "capabilitiesDigest", "platforms", "release"]) and
347
+ (.signer.release | keys == ["buildInputDigest", "commit", "development", "version"]) and
348
+ (.signer.platforms | keys == ["darwin-amd64", "darwin-arm64", "linux-amd64", "linux-arm64"]) and
349
+ (.signer.platforms[$signer_platform] | keys == ["asset", "sha256"]) and
350
+ (.signer.capabilitiesDigest | test("^sha256:[a-f0-9]{64}$")) and
261
351
  (.release.version == $version) and (.release.tag == ("v" + $version)) and
262
352
  (.release.commit | test("^[a-f0-9]{40}$")) and
263
353
  (.signer.release.version == $version) and
@@ -265,38 +355,107 @@ if [[ "$install_entry_is_stream" -eq 1 || \
265
355
  (.signer.release.development == false) and
266
356
  (.signer.release.buildInputDigest | test("^sha256:[a-f0-9]{64}$")) and
267
357
  (.application.linux[$architecture].artifact.asset | test("^[A-Za-z0-9][A-Za-z0-9._-]+$")) and
268
- (.application.linux[$architecture].artifact.sha256 | test("^[a-f0-9]{64}$"))
358
+ (.application.linux[$architecture].artifact.sha256 | test("^[a-f0-9]{64}$")) and
359
+ (.application.linux[$architecture].dependencies.asset | test("^[A-Za-z0-9][A-Za-z0-9._-]+$")) and
360
+ (.application.linux[$architecture].dependencies.sha256 | test("^[a-f0-9]{64}$")) and
361
+ (.application.linux[$architecture].dependencies.dependencyHash | test("^[a-f0-9]{64}$")) and
362
+ (.signer.platforms[$signer_platform].asset | test("^[A-Za-z0-9][A-Za-z0-9._-]+$")) and
363
+ (.signer.platforms[$signer_platform].sha256 | test("^[a-f0-9]{64}$"))
269
364
  then [
270
365
  .release.commit,
271
366
  .signer.release.commit,
272
367
  .application.linux[$architecture].artifact.asset,
273
- .application.linux[$architecture].artifact.sha256
368
+ .application.linux[$architecture].artifact.sha256,
369
+ .application.linux[$architecture].dependencies.asset,
370
+ .application.linux[$architecture].dependencies.sha256,
371
+ .application.linux[$architecture].dependencies.dependencyHash,
372
+ .signer.platforms[$signer_platform].asset,
373
+ .signer.platforms[$signer_platform].sha256
274
374
  ] | @tsv
275
375
  else error("invalid hosted release manifest") end
276
376
  ' "$release_manifest")" || {
277
377
  echo "Hosted release manifest does not bind this exact app and signer release." >&2
278
378
  exit 1
279
379
  }
280
- IFS=$'\t' read -r manifest_commit manifest_signer_commit asset expected <<<"$manifest_selection"
281
- [[ "$manifest_commit" == "$manifest_signer_commit" && "$asset" == "fased-hosted-app-linux-${architecture}-v${release_version}.tar.gz" ]] || {
380
+ IFS=$'\t' read -r manifest_commit manifest_signer_commit asset expected dependency_asset dependency_expected dependency_hash signer_asset signer_expected <<<"$manifest_selection"
381
+ [[ "$manifest_commit" == "$manifest_signer_commit" && \
382
+ "$asset" == "fased-hosted-app-v2-linux-${architecture}-v${release_version}.tar.gz" && \
383
+ "$dependency_asset" == "fased-hosted-deps-linux-${architecture}-${dependency_hash}.tar.gz" && \
384
+ "$signer_asset" == "fased-signerd-${signer_platform}" ]] || {
282
385
  echo "Hosted release manifest selects a mixed commit or unexpected app artifact." >&2
283
386
  exit 1
284
387
  }
285
388
  archive="${preflight}/${asset}"
286
- curl -fL --proto '=https' --tlsv1.2 "$release_url/$asset" -o "$archive"
389
+ dependency_archive="${preflight}/${dependency_asset}"
390
+ signer_binary="${preflight}/${signer_asset}"
391
+ curl -q -fL --proto '=https' --tlsv1.2 "$release_url/$asset" -o "$archive"
392
+ curl -q -fL --proto '=https' --tlsv1.2 "$release_url/$dependency_asset" -o "$dependency_archive"
393
+ curl -q -fL --proto '=https' --tlsv1.2 "$release_url/$signer_asset" -o "$signer_binary"
287
394
  actual="$(sha256sum "$archive" | awk '{print tolower($1)}')"
395
+ local dependency_actual=""
396
+ local signer_actual=""
397
+ dependency_actual="$(sha256sum "$dependency_archive" | awk '{print tolower($1)}')"
398
+ signer_actual="$(sha256sum "$signer_binary" | awk '{print tolower($1)}')"
288
399
  manifest_digest="$(sha256sum "$release_manifest" | awk '{print tolower($1)}')"
289
- if [[ ! "$expected" =~ ^[a-f0-9]{64}$ || "$actual" != "$expected" ]]; then
290
- echo "Hosted release checksum verification failed." >&2
400
+ if [[ ! "$expected" =~ ^[a-f0-9]{64}$ || "$actual" != "$expected" || \
401
+ ! "$dependency_expected" =~ ^[a-f0-9]{64}$ || "$dependency_actual" != "$dependency_expected" || \
402
+ ! "$signer_expected" =~ ^[a-f0-9]{64}$ || "$signer_actual" != "$signer_expected" ]]; then
403
+ echo "Hosted app, dependency, or signer release checksum verification failed." >&2
291
404
  exit 1
292
405
  fi
293
- GH_PROMPT_DISABLED=1 gh attestation verify "$archive" \
294
- --repo fased-ai/fased \
295
- --signer-workflow fased-ai/fased/.github/workflows/hosted-runtime-release.yml \
296
- --source-ref "refs/tags/v${release_version}" \
297
- --deny-self-hosted-runners >/dev/null
298
- # Only after the exact unified manifest and selected app artifact are both
299
- # attested and digest-bound may the bootstrap create persistent root state.
406
+ local entry=""
407
+ while IFS= read -r entry; do
408
+ entry="${entry%/}"
409
+ if [[ -z "$entry" || "$entry" == /* || "$entry" == *\\* || \
410
+ ( "$entry" != "package" && "$entry" != package/* ) || \
411
+ "$entry" == *"/../"* || "$entry" == ../* || "$entry" == */.. || \
412
+ "$entry" == *"/./"* || "$entry" == ./* || "$entry" == */. ]]; then
413
+ echo "Hosted app archive contains an unsafe path: $entry" >&2
414
+ exit 1
415
+ fi
416
+ done < <(tar -tzf "$archive")
417
+ while IFS= read -r entry; do
418
+ entry="${entry%/}"
419
+ if [[ -z "$entry" || "$entry" == /* || "$entry" == *\\* || \
420
+ ( "$entry" != "node_modules" && "$entry" != node_modules/* ) || \
421
+ "$entry" == *"/../"* || "$entry" == ../* || "$entry" == */.. || \
422
+ "$entry" == *"/./"* || "$entry" == ./* || "$entry" == */. ]]; then
423
+ echo "Hosted dependency archive contains an unsafe path: $entry" >&2
424
+ exit 1
425
+ fi
426
+ done < <(tar -tzf "$dependency_archive")
427
+
428
+ local verified_extract="$preflight/verified-app"
429
+ install -d -m 0700 "$verified_extract"
430
+ tar -xzf "$archive" -C "$verified_extract" --no-same-owner --no-same-permissions
431
+ local verified_package_root="$verified_extract/package"
432
+ [[ -f "$verified_package_root/install.sh" && -f "$verified_package_root/package.json" && \
433
+ ! -L "$verified_package_root/install.sh" && -f "$verified_package_root/dist/build-info.json" && \
434
+ ! -L "$verified_package_root/dist/build-info.json" ]] || {
435
+ echo "Attested Hosting bundle is incomplete or has an invalid entrypoint." >&2
436
+ exit 1
437
+ }
438
+ local packaged_version=""
439
+ local packaged_commit=""
440
+ local build_info_version=""
441
+ packaged_version="$(awk -F'"' '/^[[:space:]]*"version"[[:space:]]*:/ { print $4; exit }' "$verified_package_root/package.json")"
442
+ packaged_commit="$(awk -F'"' '/^[[:space:]]*"commit"[[:space:]]*:/ { print $4; exit }' "$verified_package_root/dist/build-info.json")"
443
+ build_info_version="$(awk -F'"' '/^[[:space:]]*"version"[[:space:]]*:/ { print $4; exit }' "$verified_package_root/dist/build-info.json")"
444
+ [[ "$packaged_version" == "$release_version" && "$build_info_version" == "$release_version" && \
445
+ "$packaged_commit" =~ ^[a-f0-9]{40}$ && "$packaged_commit" == "$manifest_commit" ]] || {
446
+ echo "Attested Hosting application identity does not match the unified release manifest." >&2
447
+ exit 1
448
+ }
449
+ if find "$verified_package_root" -xdev ! -type f ! -type d -print -quit | grep -q . || \
450
+ find "$verified_package_root" -xdev -type f -links +1 -print -quit | grep -q . || \
451
+ find "$verified_package_root" -xdev \( ! -user root -o -perm /022 \) -print -quit | grep -q .; then
452
+ echo "Attested Hosting bundle violates the file, link, ownership, or writable-mode policy." >&2
453
+ exit 1
454
+ fi
455
+
456
+ # Only after the exact unified manifest, app/dependency layers, signer
457
+ # binary, archive layout, and build identity are verified may the
458
+ # bootstrap create persistent Fased root state.
300
459
  install -d -m 0700 -o root -g root /var/lib/fased-installer /var/lib/fased-installer/releases "$release_parent"
301
460
  exec 9>/var/lib/fased-installer/install.lock
302
461
  chmod 0600 /var/lib/fased-installer/install.lock
@@ -335,52 +494,9 @@ if [[ "$install_entry_is_stream" -eq 1 || \
335
494
  exit 1
336
495
  fi
337
496
 
338
- local entry=""
339
- while IFS= read -r entry; do
340
- entry="${entry%/}"
341
- if [[ -z "$entry" || "$entry" == /* || "$entry" == *\\* || \
342
- ( "$entry" != "package" && "$entry" != package/* ) || \
343
- "$entry" == *"/../"* || "$entry" == ../* || "$entry" == */.. || \
344
- "$entry" == *"/./"* || "$entry" == ./* || "$entry" == */. ]]; then
345
- echo "Hosted release archive contains an unsafe path: $entry" >&2
346
- exit 1
347
- fi
348
- done < <(tar -tzf "$archive")
349
-
350
497
  install -d -m 0700 -o root -g root "$staging/extract"
351
- tar -xzf "$archive" -C "$staging/extract" --no-same-owner --no-same-permissions
498
+ cp -a "$verified_package_root" "$staging/extract/package"
352
499
  local package_root="$staging/extract/package"
353
- [[ -f "$package_root/install.sh" && -f "$package_root/package.json" && ! -L "$package_root/install.sh" ]] || {
354
- echo "Attested Hosting bundle is incomplete." >&2
355
- exit 1
356
- }
357
- local packaged_version=""
358
- packaged_version="$(awk -F'"' '/^[[:space:]]*"version"[[:space:]]*:/ { print $4; exit }' "$package_root/package.json")"
359
- [[ "$packaged_version" == "$release_version" ]] || {
360
- echo "Attested Hosting bundle version does not match v${release_version}." >&2
361
- exit 1
362
- }
363
- [[ -f "$package_root/dist/build-info.json" && ! -L "$package_root/dist/build-info.json" ]] || {
364
- echo "Attested Hosting bundle is missing immutable build identity." >&2
365
- exit 1
366
- }
367
- local packaged_commit=""
368
- local build_info_version=""
369
- packaged_commit="$(awk -F'"' '/^[[:space:]]*"commit"[[:space:]]*:/ { print $4; exit }' "$package_root/dist/build-info.json")"
370
- build_info_version="$(awk -F'"' '/^[[:space:]]*"version"[[:space:]]*:/ { print $4; exit }' "$package_root/dist/build-info.json")"
371
- [[ "$packaged_commit" =~ ^[a-f0-9]{40}$ && "$build_info_version" == "$release_version" ]] || {
372
- echo "Attested Hosting bundle build identity is invalid or does not match v${release_version}." >&2
373
- exit 1
374
- }
375
- [[ "$packaged_commit" == "$manifest_commit" ]] || {
376
- echo "Attested Hosting application commit does not match the unified release manifest." >&2
377
- exit 1
378
- }
379
- if find "$package_root" -xdev ! -type f ! -type d -print -quit | grep -q . || \
380
- find "$package_root" -xdev -type f -links +1 -print -quit | grep -q .; then
381
- echo "Attested Hosting bundle contains a symlink, special file, or hardlinked regular file." >&2
382
- exit 1
383
- fi
384
500
  chown -R root:root "$staging"
385
501
  chmod -R go-w "$staging"
386
502
  if find "$staging" -xdev \( ! -user root -o -perm /022 \) -print -quit | grep -q .; then
@@ -3956,14 +4072,22 @@ EOF
3956
4072
 
3957
4073
  install_fixed_host_gateway_service() {
3958
4074
  local target_user="${FASED_INSTALL_USER:-app}"
4075
+ local gateway_user="${FASED_GATEWAY_USER:-fased-gateway}"
4076
+ local gateway_group="${FASED_GATEWAY_GROUP:-fased-gateway}"
4077
+ local config_group="${FASED_CONFIG_GROUP:-fased-config}"
3959
4078
  local target_home
3960
4079
  target_home="$(getent passwd "$target_user" | cut -d: -f6)"
3961
4080
  [[ -n "$target_home" ]] || target_home="/home/$target_user"
3962
4081
  local target_repo_dir="${1:-$target_home/fased}"
3963
- [[ "$target_user" != "root" && "$target_user" =~ ^[A-Za-z0-9_.@-]+$ ]] || {
3964
- echo "Invalid hosted Gateway app account." >&2
4082
+ [[ "$target_user" != "root" && "$target_user" != "$gateway_user" && "$target_user" =~ ^[A-Za-z0-9_.@-]+$ && "$gateway_user" =~ ^[A-Za-z0-9_.@-]+$ ]] || {
4083
+ echo "Invalid or overlapping hosted operator and Gateway accounts." >&2
3965
4084
  exit 1
3966
4085
  }
4086
+
4087
+ install -d -m 2770 -o "$target_user" -g "$config_group" "${target_home}/.fased"
4088
+ chgrp -R "$config_group" "${target_home}/.fased"
4089
+ chmod -R g+rwX,o-rwx "${target_home}/.fased"
4090
+ find "${target_home}/.fased" -type d -exec chmod g+s {} +
3967
4091
  [[ "$target_repo_dir" == "$target_home/"* && "$target_repo_dir" =~ ^/[A-Za-z0-9_./@-]+$ ]] || {
3968
4092
  echo "Hosted Gateway checkout must remain below the app account home." >&2
3969
4093
  exit 1
@@ -3981,8 +4105,9 @@ EOF
3981
4105
  chown root:root /usr/local/libexec/fased-gateway-launch
3982
4106
  chmod 0755 /usr/local/libexec/fased-gateway-launch
3983
4107
 
3984
- # The root-owned unit has fixed inputs. It may execute app-owned application code only as
3985
- # the app account; no app-supplied unit text or privileged lifecycle hook is accepted.
4108
+ # The root-owned unit has fixed inputs. The non-login Gateway identity can read the
4109
+ # operator-installed runtime and share application configuration through fased-config,
4110
+ # but it is not a member of the restricted signer-operator socket group.
3986
4111
  cat >/etc/systemd/system/fased-gateway.service <<EOF
3987
4112
  [Unit]
3988
4113
  Description=Fased Gateway (managed)
@@ -3991,19 +4116,21 @@ Wants=fased-signerd.service network-online.target
3991
4116
 
3992
4117
  [Service]
3993
4118
  Type=simple
3994
- User=${target_user}
3995
- Group=${target_user}
4119
+ User=${gateway_user}
4120
+ Group=${gateway_group}
4121
+ SupplementaryGroups=${config_group}
3996
4122
  WorkingDirectory=${target_repo_dir}
3997
4123
  Environment=HOME=${target_home}
3998
4124
  Environment=FASED_GATEWAY_MODE=managed
3999
4125
  Environment=FASED_MANAGED_INTERNAL=1
4126
+ Environment=FASED_GATEWAY_SERVICE=1
4000
4127
  Environment=FASED_GATEWAY_PORT=18789
4001
4128
  Environment=FASED_HOST_PROFILE=hosting
4002
4129
  Environment=FASED_WALLET_LOCAL_SIGNER_SOCKET=/run/fased-signerd/app.sock
4003
4130
  ExecStart=/usr/local/libexec/fased-gateway-launch
4004
4131
  Restart=always
4005
4132
  RestartSec=5
4006
- UMask=0077
4133
+ UMask=0007
4007
4134
  NoNewPrivileges=true
4008
4135
  PrivateTmp=true
4009
4136
  PrivateDevices=true
@@ -4214,8 +4341,15 @@ EOF
4214
4341
 
4215
4342
  ensure_host_boundary_accounts() {
4216
4343
  local target_user="${FASED_INSTALL_USER:-app}"
4344
+ local gateway_user="${FASED_GATEWAY_USER:-fased-gateway}"
4217
4345
  local signer_user="${FASED_SIGNER_USER:-fased-signer}"
4218
4346
  local gateway_group="${FASED_GATEWAY_GROUP:-fased-gateway}"
4347
+ local operator_group="${FASED_OPERATOR_GROUP:-fased-operator}"
4348
+ local config_group="${FASED_CONFIG_GROUP:-fased-config}"
4349
+ [[ "$target_user" != "$gateway_user" && "$target_user" != "$signer_user" && "$gateway_user" != "$signer_user" ]] || {
4350
+ echo "Hosting operator, Gateway, and signer accounts must be distinct." >&2
4351
+ exit 1
4352
+ }
4219
4353
  if ! id -u "$target_user" >/dev/null 2>&1; then
4220
4354
  if need_cmd useradd; then
4221
4355
  useradd -m -s /bin/bash "$target_user"
@@ -4224,6 +4358,15 @@ ensure_host_boundary_accounts() {
4224
4358
  fi
4225
4359
  fi
4226
4360
  getent group "$gateway_group" >/dev/null 2>&1 || groupadd --system "$gateway_group"
4361
+ getent group "$operator_group" >/dev/null 2>&1 || groupadd --system "$operator_group"
4362
+ getent group "$config_group" >/dev/null 2>&1 || groupadd --system "$config_group"
4363
+ if ! id -u "$gateway_user" >/dev/null 2>&1; then
4364
+ if need_cmd useradd; then
4365
+ useradd --system --gid "$gateway_group" --home-dir /var/lib/fased-gateway --shell /usr/sbin/nologin "$gateway_user"
4366
+ else
4367
+ adduser --system --ingroup "$gateway_group" --home /var/lib/fased-gateway --shell /usr/sbin/nologin "$gateway_user"
4368
+ fi
4369
+ fi
4227
4370
  if ! id -u "$signer_user" >/dev/null 2>&1; then
4228
4371
  if need_cmd useradd; then
4229
4372
  useradd --system --home-dir /var/lib/fased-signerd --shell /usr/sbin/nologin "$signer_user"
@@ -4231,8 +4374,13 @@ ensure_host_boundary_accounts() {
4231
4374
  adduser --system --home /var/lib/fased-signerd --shell /usr/sbin/nologin "$signer_user"
4232
4375
  fi
4233
4376
  fi
4377
+ usermod -g "$gateway_group" -s /usr/sbin/nologin "$gateway_user"
4378
+ passwd -l "$gateway_user" >/dev/null 2>&1 || true
4234
4379
  passwd -l "$signer_user" >/dev/null 2>&1 || true
4235
- usermod -aG "$gateway_group" "$target_user"
4380
+ usermod -aG "$config_group" "$gateway_user"
4381
+ usermod -aG "$operator_group,$config_group" "$target_user"
4382
+ gpasswd -d "$target_user" "$gateway_group" >/dev/null 2>&1 || true
4383
+ gpasswd -d "$gateway_user" "$operator_group" >/dev/null 2>&1 || true
4236
4384
  for admin_group in sudo wheel; do
4237
4385
  if getent group "$admin_group" >/dev/null 2>&1; then
4238
4386
  gpasswd -d "$target_user" "$admin_group" >/dev/null 2>&1 || true
@@ -4241,7 +4389,8 @@ ensure_host_boundary_accounts() {
4241
4389
  rm -f \
4242
4390
  "/etc/sudoers.d/fased-install-${target_user}" \
4243
4391
  "/etc/sudoers.d/fased-host-maintenance-${target_user}" \
4244
- "/etc/sudoers.d/fased-gateway-${target_user}-maintenance"
4392
+ "/etc/sudoers.d/fased-gateway-${target_user}-maintenance" \
4393
+ "/etc/sudoers.d/fased-signer-wallet-import-${target_user}"
4245
4394
  if need_cmd sudo && runuser -u "$target_user" -- sudo -n true >/dev/null 2>&1; then
4246
4395
  echo "Hosting security boundary cannot use an app account with passwordless sudo: $target_user" >&2
4247
4396
  echo "Remove custom sudoers access for this dedicated account, then rerun the exact tagged repair from the provider root console." >&2
@@ -4251,14 +4400,22 @@ ensure_host_boundary_accounts() {
4251
4400
 
4252
4401
  install_host_signer_and_updater_services() {
4253
4402
  local target_user="${FASED_INSTALL_USER:-app}"
4403
+ local gateway_user="${FASED_GATEWAY_USER:-fased-gateway}"
4254
4404
  local signer_user="${FASED_SIGNER_USER:-fased-signer}"
4255
4405
  local gateway_group="${FASED_GATEWAY_GROUP:-fased-gateway}"
4406
+ local operator_group="${FASED_OPERATOR_GROUP:-fased-operator}"
4256
4407
  local gateway_gid
4408
+ local gateway_uid
4409
+ local operator_uid
4410
+ local signer_uid
4257
4411
  local version
4258
4412
  gateway_gid="$(getent group "$gateway_group" | cut -d: -f3)"
4413
+ gateway_uid="$(id -u "$gateway_user")"
4414
+ operator_uid="$(id -u "$target_user")"
4415
+ signer_uid="$(id -u "$signer_user")"
4259
4416
  version="$(node -p "require(process.argv[1]).version" "$FASED_DIR/package.json" 2>/dev/null || true)"
4260
- [[ "$gateway_gid" =~ ^[0-9]+$ && "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]] || {
4261
- echo "Could not resolve hosted signer group or release version." >&2
4417
+ [[ "$gateway_gid" =~ ^[0-9]+$ && "$gateway_uid" =~ ^[0-9]+$ && "$operator_uid" =~ ^[0-9]+$ && "$signer_uid" =~ ^[0-9]+$ && "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]] || {
4418
+ echo "Could not resolve hosted signer principals, socket group, or release version." >&2
4262
4419
  exit 1
4263
4420
  }
4264
4421
  if [[ -n "${FASED_HOST_SIGNER_BINARY:-}" ]]; then
@@ -4266,6 +4423,19 @@ install_host_signer_and_updater_services() {
4266
4423
  echo "The root updater must fetch and attest the exact tagged signer artifact." >&2
4267
4424
  exit 1
4268
4425
  fi
4426
+ local sat_runtime_value=""
4427
+ if [[ -n "${FASED_SAT_PROGRAM_ID:-}${FASED_SAT_BOND_PROGRAM_ID:-}${FASED_SAT_MINT_ADDRESS:-}${FASED_SAT_MINT_PROGRAM_ID:-}" ]]; then
4428
+ for sat_runtime_value in \
4429
+ "${FASED_SAT_PROGRAM_ID:-}" \
4430
+ "${FASED_SAT_BOND_PROGRAM_ID:-}" \
4431
+ "${FASED_SAT_MINT_ADDRESS:-}" \
4432
+ "${FASED_SAT_MINT_PROGRAM_ID:-}"; do
4433
+ [[ "$sat_runtime_value" =~ ^[1-9A-HJ-NP-Za-km-z]{32,44}$ ]] || {
4434
+ echo "Hosted signer SAT runtime requires one complete valid signed-manifest ID tuple." >&2
4435
+ exit 1
4436
+ }
4437
+ done
4438
+ fi
4269
4439
  local existing_signer_dropins
4270
4440
  existing_signer_dropins="$(systemctl show fased-signerd.service --property=DropInPaths --value 2>/dev/null || true)"
4271
4441
  if [[ -n "$existing_signer_dropins" ]]; then
@@ -4282,6 +4452,7 @@ install_host_signer_and_updater_services() {
4282
4452
  install -m 0755 -o root -g root "$FASED_DIR/scripts/fased-host-updater.mjs" /usr/local/libexec/fased-host-updater.mjs
4283
4453
  install -m 0755 -o root -g root "$FASED_DIR/scripts/fased-host-updaterctl.mjs" /usr/local/libexec/fased-host-updaterctl.mjs
4284
4454
  rm -f /usr/local/libexec/fased-host-bootstrapd.mjs /usr/local/libexec/fased-host-bootstrapctl.mjs
4455
+ rm -f /usr/local/sbin/fased-signer-wallet-import
4285
4456
  rm -rf /run/fased-host-bootstrap
4286
4457
  rm -f /var/log/fased-host-bootstrap.log
4287
4458
  install -m 0755 -o root -g root "$FASED_DIR/scripts/migrate-hosted-signer-v2.mjs" /usr/local/libexec/migrate-hosted-signer-v2.mjs
@@ -4301,6 +4472,20 @@ install_host_signer_and_updater_services() {
4301
4472
  if [[ ! -f /etc/fased/signerd-webauthn.env ]]; then
4302
4473
  install -m 0644 -o root -g root /dev/null /etc/fased/signerd-webauthn.env
4303
4474
  fi
4475
+ install -m 0644 -o root -g root /dev/null /etc/fased/signerd-sat-runtime.env
4476
+ if [[ -n "${FASED_SAT_PROGRAM_ID:-}" ]]; then
4477
+ cat >/etc/fased/signerd-sat-runtime.env <<EOF
4478
+ FASED_SAT_PROGRAM_ID=${FASED_SAT_PROGRAM_ID}
4479
+ FASED_SAT_BOND_PROGRAM_ID=${FASED_SAT_BOND_PROGRAM_ID}
4480
+ FASED_SAT_MINT_ADDRESS=${FASED_SAT_MINT_ADDRESS}
4481
+ FASED_SAT_MINT_PROGRAM_ID=${FASED_SAT_MINT_PROGRAM_ID}
4482
+ FASED_SAT_RUNTIME_MANIFEST_PATH=${FASED_SAT_RUNTIME_MANIFEST_PATH:-}
4483
+ FASED_SAT_RUNTIME_MANIFEST_SHA256=${FASED_SAT_RUNTIME_MANIFEST_SHA256:-}
4484
+ FASED_SAT_RUNTIME_MANIFEST_SIGNATURE_PATH=${FASED_SAT_RUNTIME_MANIFEST_SIGNATURE_PATH:-}
4485
+ EOF
4486
+ chown root:root /etc/fased/signerd-sat-runtime.env
4487
+ chmod 0644 /etc/fased/signerd-sat-runtime.env
4488
+ fi
4304
4489
  install -d -m 0755 -o root -g root /etc/systemd/system/fased-gateway.service.d
4305
4490
  cat >/etc/systemd/system/fased-gateway.service.d/20-fased-update-gate.conf <<'EOF'
4306
4491
  [Unit]
@@ -4390,7 +4575,7 @@ Wants=network-online.target
4390
4575
  Type=simple
4391
4576
  User=${signer_user}
4392
4577
  Group=${signer_user}
4393
- SupplementaryGroups=${gateway_group}
4578
+ SupplementaryGroups=${gateway_group} ${operator_group}
4394
4579
  RuntimeDirectory=fased-signerd
4395
4580
  RuntimeDirectoryMode=0755
4396
4581
  StateDirectory=fased-signerd
@@ -4398,7 +4583,8 @@ StateDirectoryMode=0700
4398
4583
  UMask=0077
4399
4584
  Environment=HOME=/var/lib/fased-signerd
4400
4585
  EnvironmentFile=-/etc/fased/signerd-webauthn.env
4401
- ExecStart=/opt/fased/signer/fased-signerd -socket /run/fased-signerd/app.sock -control-socket /run/fased-signerd/control.sock -socket-mode 0660 -socket-group ${gateway_group} -state-db /var/lib/fased-signerd/state.db -master-key /var/lib/fased-signerd/master.key -update-gate /var/lib/fased-signer-update-gate/active -pid-file /run/fased-signerd/fased-signerd.pid -audit-log /var/lib/fased-signerd/audit.jsonl
4586
+ EnvironmentFile=-/etc/fased/signerd-sat-runtime.env
4587
+ ExecStart=/opt/fased/signer/fased-signerd -socket /run/fased-signerd/app.sock -operator-socket /run/fased-signerd/operator.sock -control-socket /run/fased-signerd/control.sock -socket-mode 0660 -socket-group ${gateway_group} -operator-socket-group ${operator_group} -application-uid ${gateway_uid} -operator-uid ${operator_uid} -control-uid ${signer_uid} -state-db /var/lib/fased-signerd/state.db -master-key /var/lib/fased-signerd/master.key -update-gate /var/lib/fased-signer-update-gate/active -pid-file /run/fased-signerd/fased-signerd.pid -audit-log /var/lib/fased-signerd/audit.jsonl
4402
4588
  Restart=always
4403
4589
  RestartSec=3
4404
4590
  NoNewPrivileges=true
@@ -4443,22 +4629,10 @@ EOF
4443
4629
  cat <<'EOF'
4444
4630
  Hosted signer owner handoff
4445
4631
  ---------------------------
4446
- Fresh signer-owned wallets remain deny-all. Enrollment and policy activation are
4447
- separate host-administrator actions; neither the Gateway nor the app account can run them.
4448
-
4449
- After onboarding creates a signer-owned wallet, use a root SSH/provider-console session:
4450
- 1. Copy /usr/local/share/fased/signer-policies/network.json.template to a
4451
- root-owned mode-0600 file, set the exact RPC endpoints, then run:
4452
- /usr/local/sbin/fased-signer-network --wallet-id <native-id> --network-file /root/fased-network.json
4453
- 2. /usr/local/sbin/fased-signer-enroll [authenticator-label]
4454
- 3. cp /usr/local/share/fased/signer-policies/<role>.json.template /root/fased-<role>-policy.json
4455
- 4. Set walletId to the canonical native signer ID (lowercase, separators become
4456
- underscores; do not use a different friendly registry ID), replace every
4457
- REPLACE_WITH_ value, review the exact destinations/caps, then run:
4458
- chmod 0600 /root/fased-<role>-policy.json
4459
- /usr/local/sbin/fased-signer-policy --initial-install --policy-file /root/fased-<role>-policy.json
4460
-
4461
- Copying a template or enrolling a passkey does not enable signing.
4632
+ Fresh wallets receive their versioned Agent, Mining, or Vault baseline from the
4633
+ native signer after the primary RPC is verified. Normal setup does not require a
4634
+ root policy or network helper. Optional approval-device enrollment and advanced
4635
+ custom policy changes remain separate owner operations.
4462
4636
  EOF
4463
4637
  }
4464
4638
 
@@ -4794,6 +4968,11 @@ if [[ -n "${FASED_SAT_PROGRAM_ID:-}" && -n "${FASED_SAT_BOND_PROGRAM_ID:-}" && -
4794
4968
  persist_managed_env_var "FASED_SAT_BOND_PROGRAM_ID" "$FASED_SAT_BOND_PROGRAM_ID"
4795
4969
  persist_managed_env_var "FASED_SAT_MINT_ADDRESS" "$FASED_SAT_MINT_ADDRESS"
4796
4970
  persist_managed_env_var "FASED_SAT_MINT_PROGRAM_ID" "$FASED_SAT_MINT_PROGRAM_ID"
4971
+ if [[ -n "${FASED_SAT_RUNTIME_MANIFEST_PATH:-}" && -n "${FASED_SAT_RUNTIME_MANIFEST_SHA256:-}" && -n "${FASED_SAT_RUNTIME_MANIFEST_SIGNATURE_PATH:-}" ]]; then
4972
+ persist_managed_env_var "FASED_SAT_RUNTIME_MANIFEST_PATH" "$FASED_SAT_RUNTIME_MANIFEST_PATH"
4973
+ persist_managed_env_var "FASED_SAT_RUNTIME_MANIFEST_SHA256" "$FASED_SAT_RUNTIME_MANIFEST_SHA256"
4974
+ persist_managed_env_var "FASED_SAT_RUNTIME_MANIFEST_SIGNATURE_PATH" "$FASED_SAT_RUNTIME_MANIFEST_SIGNATURE_PATH"
4975
+ fi
4797
4976
  else
4798
4977
  :
4799
4978
  fi