fedipod 0.8.0

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 (276) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +123 -0
  3. package/architecture.svg +89 -0
  4. package/bin/fedipod.mjs +2303 -0
  5. package/cli.md +243 -0
  6. package/groups.md +85 -0
  7. package/gui.md +99 -0
  8. package/lib/account.mjs +87 -0
  9. package/lib/admin.mjs +1606 -0
  10. package/lib/atproto.mjs +310 -0
  11. package/lib/bskyfeed.mjs +166 -0
  12. package/lib/bskygroup.mjs +162 -0
  13. package/lib/c2s.mjs +305 -0
  14. package/lib/certs.mjs +265 -0
  15. package/lib/deliver.mjs +249 -0
  16. package/lib/directory.mjs +90 -0
  17. package/lib/embed.mjs +214 -0
  18. package/lib/export-collections.mjs +161 -0
  19. package/lib/front-core.mjs +322 -0
  20. package/lib/gateway-core.mjs +115 -0
  21. package/lib/guard.mjs +213 -0
  22. package/lib/home.mjs +161 -0
  23. package/lib/httpsig.mjs +109 -0
  24. package/lib/import.mjs +357 -0
  25. package/lib/intake.mjs +1710 -0
  26. package/lib/keys.mjs +139 -0
  27. package/lib/lease.mjs +190 -0
  28. package/lib/localapi.mjs +87 -0
  29. package/lib/mastoapi.mjs +1588 -0
  30. package/lib/migrate.mjs +127 -0
  31. package/lib/oidc-auth.mjs +53 -0
  32. package/lib/podrdf.mjs +132 -0
  33. package/lib/ports.mjs +27 -0
  34. package/lib/publisher.mjs +1003 -0
  35. package/lib/remote.mjs +303 -0
  36. package/lib/safefetch.mjs +235 -0
  37. package/lib/setup.mjs +262 -0
  38. package/lib/social.mjs +505 -0
  39. package/lib/storage.mjs +170 -0
  40. package/lib/store.mjs +470 -0
  41. package/lib/streaming.mjs +154 -0
  42. package/lib/tagfeed.mjs +158 -0
  43. package/lib/ua.mjs +29 -0
  44. package/lib/update.mjs +91 -0
  45. package/lib/webpush.mjs +98 -0
  46. package/lib/wire.mjs +762 -0
  47. package/package.json +60 -0
  48. package/phanpy/dist/404.html +40 -0
  49. package/phanpy/dist/_headers +2 -0
  50. package/phanpy/dist/apple-touch-icon.png +0 -0
  51. package/phanpy/dist/assets/ICONS-DIPJeU0C.js +2 -0
  52. package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +1 -0
  53. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +183 -0
  54. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +1 -0
  55. package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
  56. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +2 -0
  57. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +1 -0
  58. package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  59. package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
  60. package/phanpy/dist/assets/chunk-aKtaBQYM.js +1 -0
  61. package/phanpy/dist/assets/compose-CFEx7wb4.js +2 -0
  62. package/phanpy/dist/assets/compose-CFEx7wb4.js.map +1 -0
  63. package/phanpy/dist/assets/compose-ZJN5xSb4.js +35 -0
  64. package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +1 -0
  65. package/phanpy/dist/assets/debug.module-YFQgwnIr.js +52 -0
  66. package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +1 -0
  67. package/phanpy/dist/assets/dom-p1YcJ5Rw.js +2 -0
  68. package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +1 -0
  69. package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  70. package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  71. package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  72. package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  73. package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +1 -0
  74. package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +1 -0
  75. package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +1 -0
  76. package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +1 -0
  77. package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +1 -0
  78. package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +1 -0
  79. package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +1 -0
  80. package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +1 -0
  81. package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +1 -0
  82. package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +1 -0
  83. package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +1 -0
  84. package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +1 -0
  85. package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +1 -0
  86. package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +1 -0
  87. package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +1 -0
  88. package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +1 -0
  89. package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +1 -0
  90. package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +1 -0
  91. package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +1 -0
  92. package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +1 -0
  93. package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +1 -0
  94. package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +1 -0
  95. package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +1 -0
  96. package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +1 -0
  97. package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +1 -0
  98. package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +1 -0
  99. package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +1 -0
  100. package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +1 -0
  101. package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +1 -0
  102. package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +1 -0
  103. package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +1 -0
  104. package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +1 -0
  105. package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +1 -0
  106. package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +1 -0
  107. package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +1 -0
  108. package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +1 -0
  109. package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +1 -0
  110. package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +1 -0
  111. package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +1 -0
  112. package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +1 -0
  113. package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +1 -0
  114. package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +1 -0
  115. package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +1 -0
  116. package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +1 -0
  117. package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +1 -0
  118. package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +1 -0
  119. package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +1 -0
  120. package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +1 -0
  121. package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +1 -0
  122. package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +1 -0
  123. package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +1 -0
  124. package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +1 -0
  125. package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +1 -0
  126. package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +1 -0
  127. package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +1 -0
  128. package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +1 -0
  129. package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +1 -0
  130. package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +1 -0
  131. package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +1 -0
  132. package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +1 -0
  133. package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +1 -0
  134. package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +1 -0
  135. package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +1 -0
  136. package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +1 -0
  137. package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +1 -0
  138. package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +1 -0
  139. package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +1 -0
  140. package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +1 -0
  141. package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +1 -0
  142. package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +1 -0
  143. package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +1 -0
  144. package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +1 -0
  145. package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +1 -0
  146. package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +1 -0
  147. package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +1 -0
  148. package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +1 -0
  149. package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +1 -0
  150. package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +1 -0
  151. package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +1 -0
  152. package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +1 -0
  153. package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +1 -0
  154. package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +1 -0
  155. package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +1 -0
  156. package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +1 -0
  157. package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +1 -0
  158. package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +1 -0
  159. package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +1 -0
  160. package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +1 -0
  161. package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +1 -0
  162. package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +1 -0
  163. package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +1 -0
  164. package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +1 -0
  165. package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +1 -0
  166. package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +1 -0
  167. package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +1 -0
  168. package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +1 -0
  169. package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +1 -0
  170. package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +1 -0
  171. package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +1 -0
  172. package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +1 -0
  173. package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +1 -0
  174. package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +1 -0
  175. package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +1 -0
  176. package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +1 -0
  177. package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +1 -0
  178. package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +1 -0
  179. package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +1 -0
  180. package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +1 -0
  181. package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +1 -0
  182. package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +1 -0
  183. package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +1 -0
  184. package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +1 -0
  185. package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +1 -0
  186. package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +1 -0
  187. package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +1 -0
  188. package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +1 -0
  189. package/phanpy/dist/assets/instances-BNPptnn-.json +333 -0
  190. package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +1 -0
  191. package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +1 -0
  192. package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +1 -0
  193. package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +1 -0
  194. package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +1 -0
  195. package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +1 -0
  196. package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +1 -0
  197. package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +1 -0
  198. package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +1 -0
  199. package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +1 -0
  200. package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +1 -0
  201. package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +1 -0
  202. package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +1 -0
  203. package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +1 -0
  204. package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +1 -0
  205. package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +1 -0
  206. package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +1 -0
  207. package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +1 -0
  208. package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +1 -0
  209. package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +1 -0
  210. package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +1 -0
  211. package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +1 -0
  212. package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +1 -0
  213. package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +1 -0
  214. package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +1 -0
  215. package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +1 -0
  216. package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +1 -0
  217. package/phanpy/dist/assets/locales/tok-CfheRbCu.js +1 -0
  218. package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +1 -0
  219. package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +1 -0
  220. package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +1 -0
  221. package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +1 -0
  222. package/phanpy/dist/assets/main-BdqNbG-a.js +26 -0
  223. package/phanpy/dist/assets/main-BdqNbG-a.js.map +1 -0
  224. package/phanpy/dist/assets/mock-home-DGhR__He.js +2 -0
  225. package/phanpy/dist/assets/mock-home-DGhR__He.js.map +1 -0
  226. package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
  227. package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  228. package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  229. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +2 -0
  230. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +1 -0
  231. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +20 -0
  232. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +1 -0
  233. package/phanpy/dist/assets/style-B1iLPTPR.css +2 -0
  234. package/phanpy/dist/assets/temml-DuyIQMei.js +97 -0
  235. package/phanpy/dist/assets/temml-DuyIQMei.js.map +1 -0
  236. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +2 -0
  237. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +1 -0
  238. package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
  239. package/phanpy/dist/assets/year-in-posts-DJv_star.js +3 -0
  240. package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +1 -0
  241. package/phanpy/dist/compose/index.html +20 -0
  242. package/phanpy/dist/favicon.ico +0 -0
  243. package/phanpy/dist/index.html +73 -0
  244. package/phanpy/dist/logo-192.png +0 -0
  245. package/phanpy/dist/logo-512.png +0 -0
  246. package/phanpy/dist/logo-badge-72.png +0 -0
  247. package/phanpy/dist/logo-maskable-512.png +0 -0
  248. package/phanpy/dist/logo-monochrome-512.png +0 -0
  249. package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
  250. package/phanpy/dist/manifest.webmanifest +1 -0
  251. package/phanpy/dist/og-image-2.jpg +0 -0
  252. package/phanpy/dist/og-image.png +0 -0
  253. package/phanpy/dist/robots.txt +2 -0
  254. package/phanpy/dist/sw.js +25 -0
  255. package/phanpy/dist/version.json +1 -0
  256. package/run-agent.mjs +805 -0
  257. package/scripts/build-dist.mjs +37 -0
  258. package/scripts/verify-ui.mjs +60 -0
  259. package/ui/README.md +23 -0
  260. package/vendor/gate.cjs +132 -0
  261. package/vendor/idp-grant.cjs +394 -0
  262. package/vendor/jfetch.cjs +16 -0
  263. package/web/admin/admin.js +958 -0
  264. package/web/admin/bar.css +64 -0
  265. package/web/admin/bar.js +61 -0
  266. package/web/admin/client/client.js +66 -0
  267. package/web/admin/client/index.html +42 -0
  268. package/web/admin/index.html +493 -0
  269. package/web/admin/setup/index.html +172 -0
  270. package/web/admin/setup/setup.js +268 -0
  271. package/web/admin/window.css +55 -0
  272. package/web/admin/window.js +136 -0
  273. package/web/front/install.sh +69 -0
  274. package/web/front/new-account.html +424 -0
  275. package/web/front/run.html +138 -0
  276. package/web/front/solid-client-authn.bundle.js +2 -0
package/cli.md ADDED
@@ -0,0 +1,243 @@
1
+ # CLI admin
2
+
3
+ Everything here runs from the install folder. Most day-to-day management is
4
+ in the [GUI admin](gui.md); the terminal is for starting and stopping, and
5
+ for the few things a page cannot do. Replace NAME, DIR and EMAIL with your
6
+ own values.
7
+
8
+ ## Starting and stopping
9
+
10
+ ```
11
+ fedipod start # start your default identity (the one you last used)
12
+ fedipod start --port 8081 # same, on a port of your choice — it is remembered
13
+ fedipod up --profile NAME # start a specific identity
14
+ fedipod stop # stop it, saving state first
15
+ fedipod status # is it running, and as whom
16
+ fedipod profiles # every identity on this machine, running or not
17
+ ```
18
+
19
+ Installing the boot services (see the README) makes starting by hand unnecessary.
20
+
21
+ ## Creating an identity
22
+
23
+ ```
24
+ fedipod setup # opens the browser setup
25
+ fedipod setup --cli # the same questions in the terminal
26
+ ```
27
+ `--group` makes a group actor instead of a person, `--new-account` creates the
28
+ pod account as part of it, `--keys pod` puts the signing key in pod state for
29
+ multi-device use, and `AP_PASSWORD` supplies the pod password without a
30
+ prompt. `--profile NAME` names the new identity when you have more than one.
31
+
32
+ ## Which identity a command acts on
33
+
34
+ `--profile NAME` works on every command, not just `up`; `AP_PROFILE` is the
35
+ same thing as an environment variable, and `AP_HOME` points at an install
36
+ folder directly. Without either, commands act on the default identity — with
37
+ several identities, say which you mean. `profiles` does not list identities
38
+ under a custom `AP_HOME`.
39
+
40
+ Commands that manage a live identity — `alias`, `admit`, `import`, `archive`,
41
+ `bsky`, `describe`, `status`, `rebuild`, `gateway --detach` and the group
42
+ commands — talk to the running agent and say so when it is not there. `keys`,
43
+ `state`, `home` and attaching a gateway want the agent stopped.
44
+
45
+ ## Recovery
46
+
47
+ ```
48
+ fedipod rotate-key --force
49
+ ```
50
+ When an identity's `keys.json` is lost or damaged the agent refuses to start;
51
+ this mints a replacement and tells other servers.
52
+ It asks before rotating; `--yes` answers for you.
53
+ Any other device still
54
+ using the old key stops working as this identity.
55
+
56
+ ```
57
+ fedipod rebuild --from-notes
58
+ ```
59
+ A deeper version of the GUI's **Recover posts**: reads every note the pod
60
+ still holds, and can bring back a post whose deletion half-failed. Adds only;
61
+ nothing local is overwritten. Agent must be running.
62
+
63
+ ## Security
64
+
65
+ ```
66
+ fedipod passwd # require a password when a client logs in
67
+ fedipod tokens # list client logins; --revoke <prefix> or --revoke-all
68
+ fedipod revoke-credential --email EMAIL # cut this machine off from the pod account
69
+ ```
70
+ A new password takes effect when a running agent restarts.
71
+
72
+ ## Bluesky
73
+
74
+ ```
75
+ fedipod bsky connect HANDLE APP-PASSWORD # drive an existing Bluesky account
76
+ fedipod bsky status # which account, and any last error
77
+ fedipod bsky crosspost off # your public posts stay off Bluesky (on: they mirror)
78
+ fedipod bsky disconnect # forget the session and the stored credential
79
+ ```
80
+ HANDLE and APP-PASSWORD come from the Bluesky account: make the app password
81
+ under its Settings → Privacy and security → App Passwords. Add
82
+ `--service URL` when the account lives on a PDS other than bsky.social.
83
+ Disconnecting does not remove anything already posted to Bluesky.
84
+
85
+ ## Inbox history
86
+
87
+ ```
88
+ fedipod archive off # stop keeping drained mail (on: keep it, the default)
89
+ ```
90
+ The archive keeps each incoming activity's original bytes in the private
91
+ half's `inbox-archive/` after it is verified and applied (JSON-LD, so the
92
+ receipts read as RDF too) — receipts you can re-verify, and the inbound half
93
+ of an account move. It lives only in the private half; the pod cannot
94
+ rebuild it.
95
+
96
+ ## Moving the install
97
+
98
+ ```
99
+ fedipod home # where your identities live
100
+ fedipod home --to DIR # move them all somewhere else
101
+ ```
102
+ Stop the agents first. If you use the boot services, run
103
+ `fedipod install-service` again afterwards.
104
+
105
+ ## Going quiet, and leaving
106
+
107
+ ```
108
+ fedipod park # stop the mail and unfollow, keeping the handle
109
+ fedipod revive # come back: re-follow everyone from the parking snapshot
110
+ fedipod retire # permanent: followers' servers are told to drop the account
111
+ fedipod retire --move-to @you@new.host # tell followers to migrate, then stand down
112
+ fedipod retire --keep-handle # stand down, but the handle keeps resolving
113
+ ```
114
+ Each asks before acting; `--yes` answers for you. These are the terminal form
115
+ of the record page's status control and lifecycle buttons.
116
+
117
+ ## Where the private half lives
118
+
119
+ ```
120
+ fedipod state # show where the private half and public face live
121
+ fedipod state --to pod # move the private half into pod state (multi-device)
122
+ fedipod state --to DIR # or into another directory
123
+ fedipod upgrade # which identities are on an older layout
124
+ ```
125
+ `upgrade` reports and names the `state` commands to run; it moves nothing
126
+ itself. Stop the agent before moving state.
127
+
128
+ ## Taking your data out
129
+
130
+ ```
131
+ fedipod export --format as-collections --to DIR
132
+ ```
133
+ Writes the outbox, followers and the archived inbox as paged AS2 collections;
134
+ `--to` also takes a pod container URL.
135
+
136
+ ## Running a group
137
+
138
+ ```
139
+ fedipod members # who is in
140
+ fedipod requests # who is waiting to join
141
+ fedipod admit ACTOR-URL # let one in (or: admit --all)
142
+ fedipod refuse ACTOR-URL
143
+ fedipod joins open|approve # whether joining waits for review
144
+ fedipod mute ACTOR-URL # stop carrying them; unmute undoes it
145
+ fedipod eject ACTOR-URL
146
+ fedipod review on|off # whether posts wait for approval
147
+ fedipod pending # posts waiting for approval
148
+ fedipod approve NOTE-URL
149
+ fedipod decline NOTE-URL
150
+ fedipod announced # what the group has carried
151
+ fedipod retract NOTE-URL # un-carry one
152
+ ```
153
+ The agent must be running. What these mean: [Groups](groups.md).
154
+
155
+ Also: `describe --summary "…" --icon URL` sets the bio and avatar and
156
+ republishes the actor; `install-service` / `uninstall-service` are the
157
+ service installers the README names, as fedipod subcommands; and
158
+ `home --restructure` is the one-time move of an old top-level identity into
159
+ `profiles/` — commands that need it say so.
160
+
161
+ ## Moving here from another server
162
+
163
+ ```
164
+ fedipod alias --add @you@old.server # list the old account on your actor
165
+ fedipod alias # show the aliases
166
+ fedipod alias --remove URL --yes # take one off
167
+ ```
168
+ The alias is what the old account's server checks before it will send the
169
+ move. Add it, then trigger the move on the old server. Leave the alias in
170
+ place until the migration has settled — servers keep checking it while they
171
+ retry.
172
+
173
+ ```
174
+ fedipod admit --all # accept every waiting follow request
175
+ ```
176
+ Turning on automatic acceptance first (the admin page's **new followers**
177
+ control) means the incoming wave never queues at all.
178
+
179
+ ```
180
+ fedipod import following_accounts.csv blocked_accounts.csv
181
+ ```
182
+ Takes the CSV files from the old server's export — follows, blocks, mutes,
183
+ lists and domain blocks; the file names say which is which, or pass
184
+ `--kind follow|block|mute|list|domain` with a single file. Rows are applied
185
+ slowly on purpose and the command shows progress; `import` with no files
186
+ shows where a run stands, and `--clear` drops the finished record. Bookmarks
187
+ are not imported.
188
+
189
+ ## The gateway
190
+
191
+ ```
192
+ fedipod gateway <door-inbox-url> --secret <hmac> --inbox-only
193
+ fedipod gateway --detach
194
+ ```
195
+ Attaching points your actor's advertised inbox at a gateway's door, so
196
+ deliveries are verified and de-junked before they reach your pod; your name,
197
+ key and data stay on your pod. The URL and secret come from the gateway's
198
+ signup page. Attaching takes effect when the agent next starts; detaching
199
+ needs it running, and republishes your actor with the pod's own inbox.
200
+ (`front` still works as an alias, and the `<…/ap/actor>` form without
201
+ `--inbox-only` takes a fronted identity instead.)
202
+
203
+ What a gateway is, easing into one, and running your own: [gateway.md](gateway.md).
204
+
205
+ ## Keys
206
+
207
+ ```
208
+ fedipod keys
209
+ fedipod keys --to pod
210
+ fedipod keys --to local
211
+ ```
212
+ Where the signing key lives. On the pod, every device that reaches your
213
+ state store can sign as the actor — that is what multi-device needs; the
214
+ pod's operator can also read a pod-held key. The pod copy is shared between
215
+ devices only when the state store is on the pod too — `state --to pod`
216
+ first. Local keeps it on this machine
217
+ alone; the next start adopts the pod copy and removes it from the pod. Stop
218
+ the agent before moving keys.
219
+
220
+ ## update
221
+
222
+ ```
223
+ fedipod update
224
+ ```
225
+ Pulls the latest published FediPod into this install and restarts the
226
+ agents — those installed as services restart themselves; any started by hand
227
+ need their own restart. The record page offers the same thing when a newer version exists —
228
+ every agent checks once a day. A checkout with local changes is refused
229
+ rather than overwritten. `AP_UPDATE_CHECK=0` turns the daily check off.
230
+
231
+ ## https
232
+
233
+ ```
234
+ fedipod https
235
+ fedipod https --trust
236
+ ```
237
+ Every agent's address is https, on the port you gave it.
238
+ The first agent start makes the certificate and signs it with a local
239
+ certificate authority that can only ever vouch for localhost names. On Linux
240
+ the authority lands in Chrome and Chromium's store by itself; Firefox, macOS
241
+ and Windows run `https --trust` once, which prints any remaining step.
242
+ `https` shows what is minted and when it expires.
243
+
package/groups.md ADDED
@@ -0,0 +1,85 @@
1
+ # Groups
2
+
3
+ A group is an actor other people follow, rather than one that follows people.
4
+ When members post to the group, it re-announces to everyone following it, so members see each
5
+ other without having to follow each other. If they do already follow each other , the posts are not duplicated. Joining is following, leaving is
6
+ unfollowing, and remote Mastodon users can join exactly as pod owners do — they
7
+ need no pod of their own.
8
+
9
+ A group needs a pod of its own, and runs like any other identity on its own
10
+ port — the same status, parking and lifecycle controls on its record page.
11
+
12
+ A group is made the same way as any other account: choose "add a new account"
13
+ and pick group rather than person, or take the group path on a signup page.
14
+ You can turn on join review as you create it. The group's pod must be the root
15
+ of its own host, so its handle resolves.
16
+
17
+ Being in a group also connects you to the people in it: posts from fellow
18
+ members reach your timeline even when you do not follow them individually,
19
+ so a reply in the room is something you see rather than something filed
20
+ among strangers' mail. Who counts is the group's own membership, so its
21
+ moderation settings decide who reaches you.
22
+
23
+ As with persons, a group display name and bio should be handled on the client.
24
+
25
+ ## Managing a group
26
+
27
+ The group passes along posts from its own members only. This stops spam from outside the group.
28
+ A carried post must also be public: the group never widens an audience, so a
29
+ followers-only post or a DM to the group is not carried.
30
+ Internal moderation is also possible :
31
+
32
+ * owner may choose to moderate join requests - no one becomes a member without review
33
+ * owner may choose to moderate posts - nothing gets posted without review
34
+ * owner may mute users (refuse to rebroadcast their posts)
35
+
36
+ The controls live on the group's admin page: members, join requests, held
37
+ posts and carried posts. Ejecting also mutes: a re-follow is accepted again,
38
+ but nothing of theirs is carried until unmuted. Refusing a join request is not
39
+ sticky — they may ask again.
40
+
41
+ A carried post names the group as its
42
+ `audience`, and when you ban or eject someone (or change the moderator roster),
43
+ the group announces that moderation to its members so their servers can mirror
44
+ it. A moderator list can be set on the group's config; those actors' moderation
45
+ requests arriving over federation are held in a review queue rather than acted
46
+ on automatically, since a delivery alone does not prove who sent it. That
47
+ queue is its own list, separate from post review, and appears on the group's
48
+ admin page as **Moderation requests** when something is waiting: carry it out,
49
+ or turn it down.
50
+ A followed group's own announced deletion of a post it carried to you is honoured.
51
+
52
+ ## Inviting people
53
+
54
+ A group has a page anyone can open, at `ap/profile.html` under its pod's
55
+ app container — `<pod>/activitypods-js/ap/profile.html`. It
56
+ carries the group's address and a Follow box that sends a visitor to their
57
+ own server's follow screen, so it is the link to put where people will find
58
+ it. Posts the group carries appear in members' timelines as the group
59
+ boosting the author.
60
+
61
+ ## Transferring group ownership
62
+
63
+ A group can be handed on rather than abandoned using the `Transfer this account away` button. This tells every follower to migrate, so the membership survives a change of host.
64
+
65
+
66
+
67
+ ## Bluesky members
68
+
69
+ A group with a connected Bluesky account is joinable from Bluesky: following
70
+ the group's Bluesky account is a join, and mentioning its handle submits a
71
+ post. Both pass through the same moderation settings as fediverse members —
72
+ join review, post review, and muting all apply, and an approved post is
73
+ carried to the group's Bluesky followers as a repost.
74
+ A follow from an already-bridged Bluesky account is ignored on the Bluesky
75
+ side — its join arrives over ActivityPub from the bridge instead. Carrying
76
+ fediverse posts to the group's Bluesky followers happens only while
77
+ crossposting is on.
78
+
79
+ A Bluesky member's posts reach the fediverse side only if their account is
80
+ bridged (it follows @ap.brid.gy on Bluesky). Unbridged members are welcomed
81
+ with a single post mentioning them explaining that, and everything else about their
82
+ membership still works, and bridging later upgrades them in place: the
83
+ bridge's follow supersedes their Bluesky-only membership rather than adding a
84
+ second one. Ejecting a Bluesky member blocks their account, since their server cannot be told any
85
+ other way.
package/gui.md ADDED
@@ -0,0 +1,99 @@
1
+ # The Admin interface
2
+
3
+ Open `https://localhost:8030/` while any agent is running — it forwards you to the agent — then choose `manage account` and select the actor you want from the local actors dropdown.
4
+ Picking an actor marked "(stopped)" starts its agent, then opens its page.
5
+
6
+ `Update`, shown on the software row when a newer FediPod exists, pulls the latest version and restarts every agent on the machine — agents installed as services; any started by hand need their own restart. The same row flags an older data layout after an update; the `fedipod upgrade` terminal command lists those moves and the commands that make them.
7
+
8
+ * **Parking** (the status control) stops the mail and unfollows people you follow, but keeps your handle
9
+ alive. Setting it back to active re-follows everyone — as requests, so a
10
+ few may not come back. If your local machine is going to be off for more than a couple of days, you should park the account because, if you don't, mail will accumulate on the pod possibly causing load issues.
11
+ * **Transfer this account away** hands your followers to the account you name; your
12
+ old handle keeps working as a redirect, and the identity is parked
13
+ afterwards.
14
+ * **Retire identity** is permanent — every follower's server is told to drop
15
+ the account, and the identity does not come back.
16
+ * In the **Upkeep** group: **Drain the inbox** fetches and handles everything
17
+ waiting now, and **Show dead letters** lists deliveries that failed for
18
+ good and will not be retried.
19
+ * **Rotate signing key** — any other device still using the old key can no
20
+ longer act as this identity.
21
+ * **Recover posts** re-reads your own posts from the pod after a restore. It
22
+ only ever adds; nothing local is overwritten.
23
+ * Discarding a backlog drops old content only — its follows, unfollows and
24
+ deletions are still applied, whatever their size.
25
+
26
+ The handle, the pod and person-vs-group are permanent. The display name, bio
27
+ and pictures are edited in the client, not here.
28
+
29
+ Most of these open a small window that floats over the page. You can drag it by
30
+ its title bar, resize it from its corner, and close it with its ✕ or by pressing
31
+ Escape. Parking is a control on the status row, and discarding a backlog is its
32
+ own section further down the page.
33
+
34
+ **+ add a new account…** at the bottom of the actors dropdown creates another
35
+ person or group on this machine: it needs a pod of its own (create one there,
36
+ or point it at one you have), and the new identity runs as its own agent on
37
+ its own port.
38
+
39
+ ## Sharing an account
40
+
41
+ Each identity has a page anyone can open, at `ap/profile.html` under its pod
42
+ — for example `https://your-pod.example/activitypods-js/ap/profile.html`. It
43
+ shows the name, bio and address, and offers a Follow box: a visitor types
44
+ their own server and lands on that server's follow screen. Hand out that
45
+ link, or the `@name@host` address itself, which works in the search box of
46
+ any fediverse app.
47
+
48
+ ## Bluesky
49
+
50
+ Connecting a Bluesky account makes this identity also post as that account:
51
+ your **public** posts mirror to Bluesky (never unlisted, followers-only or direct
52
+ ones), deleting a post deletes its Bluesky copy, and the account's timeline
53
+ and notifications appear in your home feed. Replying to, favouriting and
54
+ boosting a Bluesky post act as the connected account. A reply lives on
55
+ Bluesky only — it is always public and fits Bluesky's 300-character limit —
56
+ because your fediverse followers cannot see the post it answers. The
57
+ **crosspost** control stops the
58
+ mirroring without disconnecting. Disconnecting forgets the login but removes
59
+ nothing already posted. For a group, the connected account is the group's
60
+ presence on Bluesky — joining and posting through it follow the group's own
61
+ moderation settings; see [Groups](groups.md).
62
+
63
+ ## Handling follow requests
64
+
65
+ New followers appear under **Follow requests** with **Accept** and **Refuse**
66
+ beside them; nothing is accepted without you. Groups are different — joining
67
+ follows the group's own moderation settings; see [Groups](groups.md).
68
+
69
+ **Accept all** answers the whole queue at once, and the identity pane's
70
+ **new followers** control switches between waiting for your approval and
71
+ accepting automatically.
72
+
73
+ ## Moving here from another server
74
+
75
+ Open **Transfer an account here** on the action rail and add your old account
76
+ as an alias, set
77
+ **new followers** to *accepted automatically*, then trigger the move on the
78
+ old server; your followers arrive by themselves. Removing an alias asks
79
+ twice — servers still processing the move check it while they retry. The CSV
80
+ files from the old server's export are imported with the CLI; see
81
+ [CLI admin](cli.md).
82
+
83
+ ## Protecting & recovering your data
84
+
85
+ Your posts, timeline, contacts, blocklist and notifications are kept locally,
86
+ in `~/.fedipod`. This is the only copy, which means you should back
87
+ it up regularly. It contains credentials, so back it up securely.
88
+ Each identity's copy is under `~/.fedipod/profiles/<name>/`, and
89
+ `fedipod state --to` can move it elsewhere — if you have moved it, back up
90
+ where it lives now.
91
+
92
+ With a backup, restoring is copying the folder back and starting the agent —
93
+ everything returns, timeline included. Without one, nothing is fatal but some
94
+ things are gone: run setup again with your pod account (at `/admin/setup/` on the agent), rotate the signing
95
+ key, and your followers return by themselves the next time you post, while
96
+ **Recover posts** reads your own posts back from the pod. What nothing can
97
+ bring back is the timeline you received, your notifications, your blocklist,
98
+ and your client logins.
99
+
@@ -0,0 +1,87 @@
1
+ // account.mjs — create a brand-new account + pod on a CSS server (v7 account
2
+ // API), the `setup --new-account` path. Public-server sibling of
3
+ // data-kitchen's seed-account.cjs, but simpler: the pod.create control makes
4
+ // the pod AND its WebID in one step — no ownership challenge, because the
5
+ // server authors the WebID itself.
6
+ //
7
+ // Flow: POST .account/account/ (or login when the email already exists) →
8
+ // password.create → account.pod {name} → { pod, webId }.
9
+
10
+ async function jfetch(url, { method = 'GET', body, cookie } = {}) {
11
+ const headers = { accept: 'application/json' };
12
+ if (body !== undefined) headers['content-type'] = 'application/json';
13
+ if (cookie) headers.cookie = cookie;
14
+ const res = await fetch(url, { method, headers, body: body === undefined ? undefined : JSON.stringify(body) });
15
+ let json = null;
16
+ try { json = await res.json(); } catch { /* non-JSON */ }
17
+ return { status: res.status, json };
18
+ }
19
+
20
+ export async function createAccountWithPod({ issuer, email, password, podName }) {
21
+ const origin = issuer.replace(/\/+$/, '');
22
+ const accountRoot = `${origin}/.account/`;
23
+
24
+ // Authenticate: log in when the email is already registered (idempotent
25
+ // re-runs), otherwise create the account and set its password.
26
+ let cookie;
27
+ const login = await jfetch(`${accountRoot}login/password/`, { method: 'POST', body: { email, password } });
28
+ if (login.status < 400 && login.json?.authorization) {
29
+ cookie = `css-account=${login.json.authorization}`;
30
+ } else {
31
+ const create = await jfetch(`${accountRoot}account/`, { method: 'POST' });
32
+ if (!create.json?.authorization) {
33
+ throw new Error(`account create failed (HTTP ${create.status}): ${create.json?.message || 'is this a CSS server with signup enabled?'}`);
34
+ }
35
+ cookie = `css-account=${create.json.authorization}`;
36
+ const pwCreate = (await jfetch(accountRoot, { cookie })).json?.controls?.password?.create;
37
+ if (!pwCreate) throw new Error('password.create control missing — not a CSS v7 account API');
38
+ const pw = await jfetch(pwCreate, { method: 'POST', cookie, body: { email, password } });
39
+ if (pw.status >= 400) throw new Error(`password create failed (HTTP ${pw.status}): ${pw.json?.message || ''}`);
40
+ }
41
+
42
+ // Create the pod (server picks root-path or subdomain layout). When the
43
+ // name is already taken BY THIS ACCOUNT — a setup re-run, or a pod made
44
+ // earlier by hand — reuse it; taken by someone else stays a hard error.
45
+ const controls = (await jfetch(accountRoot, { cookie })).json?.controls;
46
+ const podCreate = controls?.account?.pod;
47
+ if (!podCreate) throw new Error('pod create control missing — not a CSS v7 account API');
48
+ // Scheme-agnostic on purpose: matching `https://name.` missed every pod on an
49
+ // http server, so a re-run after a crashed setup always tried to create again
50
+ // and died on "already registered to this account".
51
+ const findOwn = async () => {
52
+ const pods = (await jfetch(podCreate, { cookie })).json?.pods || {};
53
+ return Object.entries(pods).find(([url]) => {
54
+ try {
55
+ const u = new URL(url);
56
+ return u.hostname === podName || u.hostname.startsWith(`${podName}.`)
57
+ || u.pathname.split('/').filter(Boolean).includes(podName);
58
+ } catch { return false; }
59
+ }) || null;
60
+ };
61
+ // One pod per account, refused BEFORE anything is created. A second pod gives
62
+ // the account a second WebID, and a credential can only be bound to one of
63
+ // them — so the second actor authenticates and then 403s every write to its
64
+ // own pod. Reusing a pod of the same name is still fine: that is a re-run of
65
+ // a setup, not a second identity.
66
+ const owned = Object.keys((await jfetch(podCreate, { cookie })).json?.pods || {});
67
+ if (owned.length && !await findOwn()) {
68
+ throw new Error(`${email} already has a pod on ${new URL(origin).host}: ${owned[0]} — `
69
+ + 'one account, one pod. A second actor needs its own account and email. '
70
+ + 'Nothing was created.');
71
+ }
72
+ const made = await jfetch(podCreate, { method: 'POST', cookie, body: { name: podName } });
73
+ let pod = made.json?.pod || made.json?.podBaseUrl || null;
74
+ let webId = made.json?.webId || null;
75
+ if (made.status >= 400 || !pod) {
76
+ const own = await findOwn();
77
+ if (own) {
78
+ pod = own[0];
79
+ webId = webId || own[1]?.webId || null;
80
+ } else if (made.status >= 400) {
81
+ throw new Error(`pod create failed (HTTP ${made.status}): ${made.json?.message || `is the name "${podName}" taken by another account?`}`);
82
+ }
83
+ }
84
+ if (!pod) throw new Error('pod created but its URL was not reported — check the account dashboard');
85
+ pod = pod.endsWith('/') ? pod : pod + '/';
86
+ return { pod, webId: webId || new URL('profile/card#me', pod).href };
87
+ }