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.
- package/LICENSE +21 -0
- package/README.md +123 -0
- package/architecture.svg +89 -0
- package/bin/fedipod.mjs +2303 -0
- package/cli.md +243 -0
- package/groups.md +85 -0
- package/gui.md +99 -0
- package/lib/account.mjs +87 -0
- package/lib/admin.mjs +1606 -0
- package/lib/atproto.mjs +310 -0
- package/lib/bskyfeed.mjs +166 -0
- package/lib/bskygroup.mjs +162 -0
- package/lib/c2s.mjs +305 -0
- package/lib/certs.mjs +265 -0
- package/lib/deliver.mjs +249 -0
- package/lib/directory.mjs +90 -0
- package/lib/embed.mjs +214 -0
- package/lib/export-collections.mjs +161 -0
- package/lib/front-core.mjs +322 -0
- package/lib/gateway-core.mjs +115 -0
- package/lib/guard.mjs +213 -0
- package/lib/home.mjs +161 -0
- package/lib/httpsig.mjs +109 -0
- package/lib/import.mjs +357 -0
- package/lib/intake.mjs +1710 -0
- package/lib/keys.mjs +139 -0
- package/lib/lease.mjs +190 -0
- package/lib/localapi.mjs +87 -0
- package/lib/mastoapi.mjs +1588 -0
- package/lib/migrate.mjs +127 -0
- package/lib/oidc-auth.mjs +53 -0
- package/lib/podrdf.mjs +132 -0
- package/lib/ports.mjs +27 -0
- package/lib/publisher.mjs +1003 -0
- package/lib/remote.mjs +303 -0
- package/lib/safefetch.mjs +235 -0
- package/lib/setup.mjs +262 -0
- package/lib/social.mjs +505 -0
- package/lib/storage.mjs +170 -0
- package/lib/store.mjs +470 -0
- package/lib/streaming.mjs +154 -0
- package/lib/tagfeed.mjs +158 -0
- package/lib/ua.mjs +29 -0
- package/lib/update.mjs +91 -0
- package/lib/webpush.mjs +98 -0
- package/lib/wire.mjs +762 -0
- package/package.json +60 -0
- package/phanpy/dist/404.html +40 -0
- package/phanpy/dist/_headers +2 -0
- package/phanpy/dist/apple-touch-icon.png +0 -0
- package/phanpy/dist/assets/ICONS-DIPJeU0C.js +2 -0
- package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +1 -0
- package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +183 -0
- package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +1 -0
- package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
- package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +2 -0
- package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +1 -0
- package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
- package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
- package/phanpy/dist/assets/chunk-aKtaBQYM.js +1 -0
- package/phanpy/dist/assets/compose-CFEx7wb4.js +2 -0
- package/phanpy/dist/assets/compose-CFEx7wb4.js.map +1 -0
- package/phanpy/dist/assets/compose-ZJN5xSb4.js +35 -0
- package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +1 -0
- package/phanpy/dist/assets/debug.module-YFQgwnIr.js +52 -0
- package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +1 -0
- package/phanpy/dist/assets/dom-p1YcJ5Rw.js +2 -0
- package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +1 -0
- package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
- package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
- package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
- package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
- package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +1 -0
- package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +1 -0
- package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +1 -0
- package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +1 -0
- package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +1 -0
- package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +1 -0
- package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +1 -0
- package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +1 -0
- package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +1 -0
- package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +1 -0
- package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +1 -0
- package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +1 -0
- package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +1 -0
- package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +1 -0
- package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +1 -0
- package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +1 -0
- package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +1 -0
- package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +1 -0
- package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +1 -0
- package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +1 -0
- package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +1 -0
- package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +1 -0
- package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +1 -0
- package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +1 -0
- package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +1 -0
- package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +1 -0
- package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +1 -0
- package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +1 -0
- package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +1 -0
- package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +1 -0
- package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +1 -0
- package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +1 -0
- package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +1 -0
- package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +1 -0
- package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +1 -0
- package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +1 -0
- package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +1 -0
- package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +1 -0
- package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +1 -0
- package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +1 -0
- package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +1 -0
- package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +1 -0
- package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +1 -0
- package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +1 -0
- package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +1 -0
- package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +1 -0
- package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +1 -0
- package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +1 -0
- package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +1 -0
- package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +1 -0
- package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +1 -0
- package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +1 -0
- package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +1 -0
- package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +1 -0
- package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +1 -0
- package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +1 -0
- package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +1 -0
- package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +1 -0
- package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +1 -0
- package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +1 -0
- package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +1 -0
- package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +1 -0
- package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +1 -0
- package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +1 -0
- package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +1 -0
- package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +1 -0
- package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +1 -0
- package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +1 -0
- package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +1 -0
- package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +1 -0
- package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +1 -0
- package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +1 -0
- package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +1 -0
- package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +1 -0
- package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +1 -0
- package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +1 -0
- package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +1 -0
- package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +1 -0
- package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +1 -0
- package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +1 -0
- package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +1 -0
- package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +1 -0
- package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +1 -0
- package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +1 -0
- package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +1 -0
- package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +1 -0
- package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +1 -0
- package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +1 -0
- package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +1 -0
- package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +1 -0
- package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +1 -0
- package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +1 -0
- package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +1 -0
- package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +1 -0
- package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +1 -0
- package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +1 -0
- package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +1 -0
- package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +1 -0
- package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +1 -0
- package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +1 -0
- package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +1 -0
- package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +1 -0
- package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +1 -0
- package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +1 -0
- package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +1 -0
- package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +1 -0
- package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +1 -0
- package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +1 -0
- package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +1 -0
- package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +1 -0
- package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +1 -0
- package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +1 -0
- package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +1 -0
- package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +1 -0
- package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +1 -0
- package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +1 -0
- package/phanpy/dist/assets/instances-BNPptnn-.json +333 -0
- package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +1 -0
- package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +1 -0
- package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +1 -0
- package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +1 -0
- package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +1 -0
- package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +1 -0
- package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +1 -0
- package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +1 -0
- package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +1 -0
- package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +1 -0
- package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +1 -0
- package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +1 -0
- package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +1 -0
- package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +1 -0
- package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +1 -0
- package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +1 -0
- package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +1 -0
- package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +1 -0
- package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +1 -0
- package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +1 -0
- package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +1 -0
- package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +1 -0
- package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +1 -0
- package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +1 -0
- package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +1 -0
- package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +1 -0
- package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +1 -0
- package/phanpy/dist/assets/locales/tok-CfheRbCu.js +1 -0
- package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +1 -0
- package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +1 -0
- package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +1 -0
- package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +1 -0
- package/phanpy/dist/assets/main-BdqNbG-a.js +26 -0
- package/phanpy/dist/assets/main-BdqNbG-a.js.map +1 -0
- package/phanpy/dist/assets/mock-home-DGhR__He.js +2 -0
- package/phanpy/dist/assets/mock-home-DGhR__He.js.map +1 -0
- package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
- package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
- package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
- package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +2 -0
- package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +1 -0
- package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +20 -0
- package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +1 -0
- package/phanpy/dist/assets/style-B1iLPTPR.css +2 -0
- package/phanpy/dist/assets/temml-DuyIQMei.js +97 -0
- package/phanpy/dist/assets/temml-DuyIQMei.js.map +1 -0
- package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +2 -0
- package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +1 -0
- package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
- package/phanpy/dist/assets/year-in-posts-DJv_star.js +3 -0
- package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +1 -0
- package/phanpy/dist/compose/index.html +20 -0
- package/phanpy/dist/favicon.ico +0 -0
- package/phanpy/dist/index.html +73 -0
- package/phanpy/dist/logo-192.png +0 -0
- package/phanpy/dist/logo-512.png +0 -0
- package/phanpy/dist/logo-badge-72.png +0 -0
- package/phanpy/dist/logo-maskable-512.png +0 -0
- package/phanpy/dist/logo-monochrome-512.png +0 -0
- package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
- package/phanpy/dist/manifest.webmanifest +1 -0
- package/phanpy/dist/og-image-2.jpg +0 -0
- package/phanpy/dist/og-image.png +0 -0
- package/phanpy/dist/robots.txt +2 -0
- package/phanpy/dist/sw.js +25 -0
- package/phanpy/dist/version.json +1 -0
- package/run-agent.mjs +805 -0
- package/scripts/build-dist.mjs +37 -0
- package/scripts/verify-ui.mjs +60 -0
- package/ui/README.md +23 -0
- package/vendor/gate.cjs +132 -0
- package/vendor/idp-grant.cjs +394 -0
- package/vendor/jfetch.cjs +16 -0
- package/web/admin/admin.js +958 -0
- package/web/admin/bar.css +64 -0
- package/web/admin/bar.js +61 -0
- package/web/admin/client/client.js +66 -0
- package/web/admin/client/index.html +42 -0
- package/web/admin/index.html +493 -0
- package/web/admin/setup/index.html +172 -0
- package/web/admin/setup/setup.js +268 -0
- package/web/admin/window.css +55 -0
- package/web/admin/window.js +136 -0
- package/web/front/install.sh +69 -0
- package/web/front/new-account.html +424 -0
- package/web/front/run.html +138 -0
- 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
|
+
|
package/lib/account.mjs
ADDED
|
@@ -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
|
+
}
|