hydrian , to Selfhosted in What's your opinion on Ubiquiti/Unifi gear?
@hydrian@twit.social avatar

@early_riser@lemmy.world avatar early_riser I use for and . I enjoy those products. I don't like their and options.

I ran for over a decade, but since the 2.8 release you can't do an offline install. So I switched to .

@h4ckernews@mastodon.social avatar h4ckernews Bot , to random
@h4ckernews@mastodon.social avatar h4ckernews Bot , to random

Offline tiles and routing and geocoding in one Docker Compose stack

https://www.corviont.com/

@h4ckernews@mastodon.social avatar h4ckernews Bot , to random

API that auto-routes to the cheapest AI provider (OpenAI/Anthropic/Gemini)

https://tokensaver.org/

@h4ckernews@mastodon.social avatar h4ckernews Bot , to random
@h4ckernews@mastodon.social avatar h4ckernews Bot , to random
@h4ckernews@mastodon.social avatar h4ckernews Bot , to random

TREAD: Token Routing for Efficient Architecture-Agnostic Diffusion Training

https://arxiv.org/abs/2501.04765

@h4ckernews@mastodon.social avatar h4ckernews Bot , to random
@OSM_tourism@mapstodon.space avatar OSM_tourism , to random
@h4ckernews@mastodon.social avatar h4ckernews Bot , to random

Yggdrasil is an experimental compact routing scheme that is fully decentralised

https://yggdrasil-network.github.io/about.html

@aral@mastodon.ar.al avatar aral , to random

New Kitten release

• Socket routes now have precendence in the router.

This stops wildcard page routes from capturing the default socket routes that Kitten creates to enable the Streaming HTML workflow.

e.g., Previously, the following route:

/videos/index_[slug].page.js

Could not connect to its default socket (/videos/default.socket) because default.socket would be captured by the [slug] parameter.

Now, it will work as intended as the /videos/default.socket (a socket route) has precendence over index_[slug].page.js (a page route).

Learn more about Kitten’s Streaming HTML workflow here:
https://kitten.small-web.org/tutorials/streaming-html/

Enjoy!

:kitten:💕

@aral@mastodon.ar.al avatar aral , to random

New Kitten release

• Change: Add Kitten’s own routes (that every Kitten app inherits) for the Small Web protocol namespace (/💕) as well as for Kitten’s reserved namespace (/🐱) first, before app-specific routes so that a wildcard route in the form, e.g., [anything]_[anythingElse].get.js won’t lock you out of the Settings site for your app (at /🐱/settings).

https://kitten.small-web.org

Enjoy!

(To update, run kitten update from your terminal on your development machine or manually update deployment servers from /🐱/settings/kitten/ or just wait a few hours for them to update automatically. You can also just run the installation command again.)

@aral@mastodon.ar.al avatar aral , to random

New Kitten release

• Fixes issue with routes where dynamic routes with file names that had more than two extensions were not recognised as the correct type of route. e.g., A route called index.xml.get.js would previously have been treated as a static route instead of a dynamic GET route.

https://kitten.small-web.org

For more details, see the Valid File Types section of the Kitten reference¹ and the Dynamic Pages tutorial².

Enjoy!

:kitten:💕

¹ https://kitten.small-web.org/reference/#valid-file-types
² https://kitten.small-web.org/tutorials/dynamic-pages/

@DeltaWye@mstdn.social avatar DeltaWye , to random

So. My mother is worried if she is using the internet at the same time as my dad is downloading an iPad update it could somehow “mess up” his iOS update. (They have 300/20Mbps cable and a modern router.)

I told her that due to TCP/IP it makes sure everything is delivered correctly, and there’s also checks on the iOS side. Only possible impact is a small slowdown.

She’s asked me this multiple times.

Can anyone on here let me know if I’m correct, or not?

kkarhan ,
@kkarhan@infosec.space avatar

@dragonarchitect @DeltaWye this also is the advantage of : Only the need to be able to rech each ither: All the complicated is transparent and thus changing routes don't result in lost connections.

@blog@shkspr.mobi avatar blog , to random

ActivityPub Server in a Single PHP File

@blog@shkspr.mobi avatar blog : ActivityPub Server in a Single PHP File...

Any computer program can be designed to run from a single file if you architect it wrong enough!

I wanted to create the simplest possible Fediverse server which can be used as an educational tool to show how ActivityPub / Mastodon works.

The design goals were:

  • Upload a single PHP file to the server.
  • No databases or separate config files.
  • Single Actor (i.e. not multi-user).
  • Allow the Actor to be followed.
  • Post plain-text messages to followers.
  • Be roughly standards compliant.

And those goals have all been met! Check it out on GitLab. I warn you though, it is the nadir of bad coding. There are no tests, bugger-all security, scalability isn't considered, and it is a mess. But it works.

You can follow the test user @[email protected]

Architecture

Firstly, I've slightly cheated on my "single file" stipulation. There's an .htaccess file which turns example.com/whatever into example.com/index.php?path=whatever

The index.php file then takes that path and does stuff. It also contains all the configuration variables which is very bad practice.

Rather than using a database, it saves files to disk.

Again, this is not suitable for any real world use. This is an educational tool to help explain the basics of posting messages to the Fediverse. It requires absolutely no dependencies. You do not need to spin up a dockerised hypervisor to manage your node bundles and re-compile everything to WASM. Just FTP the file up to prod and you're done.

Walkthrough

This is a quick ramble through the code. It is reasonably well documented, I hope.

Preamble

This is where you set up your account's name and bio. You also need to provide a public/private keypair. The posting page is protected with a password that also needs to be set here.

<span class="tempest-highlight-language"><img alt="" class="tempest-highlight-language-icon" height="32" src="https://shkspr.mobi/blog/wp-content/plugins/tempest-highlight//svg/php.svg" width="32"></img><span itemprop="programmingLanguage"> PHP</span></span>```
    <span style="color: #008000;" translate="yes">//  Set up the Actor's information</span>    <span style="color: #001080;">$username</span> = <span style="color: #795E26;">rawurlencode</span>(<span style="color: #a31515;">"example"</span>);    <span style="color: #008000;" translate="yes">//  Encoded as it is often used as part of a URl</span>    <span style="color: #001080;">$realName</span> = <span style="color: #a31515;">"E. Xample. Jr."</span>;    <span style="color: #001080;">$summary</span>  = <span style="color: #a31515;">"Some text about the user."</span>;    <span style="color: #001080;">$server</span>   = <span style="color: #001080;">$_SERVER</span>[<span style="color: #a31515;">"SERVER_NAME"</span>];    <span style="color: #008000;" translate="yes">//  Domain name this is hosted on</span>    <span style="color: #008000;" translate="yes">//  Generate locally or from https://cryptotools.net/rsagen</span>    <span style="color: #008000;" translate="yes">//  Newlines must be replaced with "n"</span>    <span style="color: #001080;">$key_private</span> = <span style="color: #a31515;">"-----BEGIN RSA PRIVATE KEY-----n...n-----END RSA PRIVATE KEY-----"</span>;    <span style="color: #001080;">$key_public</span>  = <span style="color: #a31515;">"-----BEGIN PUBLIC KEY-----n...n-----END PUBLIC KEY-----"</span>;    <span style="color: #008000;" translate="yes">//  Password for sending messages</span>    <span style="color: #001080;">$password</span> = <span style="color: #a31515;">"P4ssW0rd"</span>;

### [Logging](#logging)

ActivityPub is a "chatty" protocol. This takes all the requests your server receives and saves them in `/logs/` as a datestamped text file.

<span class="tempest-highlight-language"><img alt="" class="tempest-highlight-language-icon" height="32" src="https://shkspr.mobi/blog/wp-content/plugins/tempest-highlight//svg/php.svg" width="32"></img><span itemprop="programmingLanguage"> PHP</span></span>```
<span style="color: #008000;" translate="yes">// Get all headers and requests sent to this server</span> <span style="color: #001080;">$headers</span> = <span style="color: #795E26;">print_r</span>( <span style="color: #795E26;">getallheaders</span>(), <span style="color: #0000ff;">true</span> ); <span style="color: #001080;">$postData</span> = <span style="color: #795E26;">print_r</span>( <span style="color: #001080;">$_POST</span>, <span style="color: #0000ff;">true</span> ); <span style="color: #001080;">$getData</span> = <span style="color: #795E26;">print_r</span>( <span style="color: #001080;">$_GET</span>, <span style="color: #0000ff;">true</span> ); <span style="color: #001080;">$filesData</span> = <span style="color: #795E26;">print_r</span>( <span style="color: #001080;">$_FILES</span>, <span style="color: #0000ff;">true</span> ); <span style="color: #001080;">$body</span> = <span style="color: #795E26;">json_decode</span>( <span style="color: #795E26;">file_get_contents</span>( <span style="color: #a31515;">"php://input"</span> ), <span style="color: #0000ff;">true</span> ); <span style="color: #001080;">$bodyData</span> = <span style="color: #795E26;">print_r</span>( <span style="color: #001080;">$body</span>, <span style="color: #0000ff;">true</span> ); <span style="color: #001080;">$requestData</span> = <span style="color: #795E26;">print_r</span>( <span style="color: #001080;">$_REQUEST</span>, <span style="color: #0000ff;">true</span> ); <span style="color: #001080;">$serverData</span> = <span style="color: #795E26;">print_r</span>( <span style="color: #001080;">$_SERVER</span>, <span style="color: #0000ff;">true</span> ); <span style="color: #008000;" translate="yes">// Get the type of request - used in the log filename</span> <span style="color: #0000ff;">if</span> ( <span style="color: #0000ff;">isset</span>( <span style="color: #001080;">$body</span>[<span style="color: #a31515;">"type"</span>] ) ) { <span style="color: #001080;">$type</span> = <span style="color: #a31515;">" "</span> . <span style="color: #001080;">$body</span>[<span style="color: #a31515;">"type"</span>]; } <span style="color: #0000ff;">else</span> { <span style="color: #001080;">$type</span> = <span style="color: #a31515;">""</span>; } <span style="color: #008000;" translate="yes">// Create a timestamp in ISO 8601 format for the filename</span> <span style="color: #001080;">$timestamp</span> = <span style="color: #795E26;">date</span>( <span style="color: #a31515;">"c"</span> ); <span style="color: #008000;" translate="yes">// Filename for the log</span> <span style="color: #001080;">$filename</span> = <span style="color: #a31515;">"{$timestamp}{$type}.txt"</span>; <span style="color: #008000;" translate="yes">// Save headers and request data to the timestamped file in the logs directory</span> <span style="color: #0000ff;">if</span>( ! <span style="color: #795E26;">is_dir</span>( <span style="color: #a31515;">"logs"</span> ) ) { <span style="color: #795E26;">mkdir</span>( <span style="color: #a31515;">"logs"</span>); } <span style="color: #795E26;">file_put_contents</span>( <span style="color: #a31515;">"logs/{$filename}"</span>, <span style="color: #a31515;">"Headers: n$headers nn"</span> . <span style="color: #a31515;">"Body Data: n$bodyData nn"</span> . <span style="color: #a31515;">"POST Data: n$postData nn"</span> . <span style="color: #a31515;">"GET Data: n$getData nn"</span> . <span style="color: #a31515;">"Files Data: n$filesData nn"</span> . <span style="color: #a31515;">"Request Data:n$requestDatann"</span> . <span style="color: #a31515;">"Server Data: n$serverData nn"</span> );

Routing

The .htaccess changes /whatever to /?path=whateverThis runs the function of the path requested.

<span class="tempest-highlight-language"><img alt="" class="tempest-highlight-language-icon" height="32" src="https://shkspr.mobi/blog/wp-content/plugins/tempest-highlight//svg/php.svg" width="32"></img><span itemprop="programmingLanguage"> PHP</span></span>```
    !<span style="color: #0000ff;">empty</span>( <span style="color: #001080;">$_GET</span>[<span style="color: #a31515;">"path"</span>] )  <span class="hl-operator">?</span> <span style="color: #001080;">$path</span> = <span style="color: #001080;">$_GET</span>[<span style="color: #a31515;">"path"</span>] : <span style="color: #0000ff;">die</span>();    <span style="color: #0000ff;">switch</span> (<span style="color: #001080;">$path</span>) {        <span style="color: #0000ff;">case</span> <span style="color: #a31515;">".well-known/webfinger"</span>:            <span style="color: #795E26;">webfinger</span>();        <span style="color: #0000ff;">case</span> <span style="color: #795E26;">rawurldecode</span>( <span style="color: #001080;">$username</span> ):            <span style="color: #267f99;">username()</span>;        <span style="color: #0000ff;">case</span> <span style="color: #a31515;">"following"</span>:            <span style="color: #795E26;">following</span>();        <span style="color: #0000ff;">case</span> <span style="color: #a31515;">"followers"</span>:            <span style="color: #795E26;">followers</span>();        <span style="color: #0000ff;">case</span> <span style="color: #a31515;">"inbox"</span>:            <span style="color: #795E26;">inbox</span>();        <span style="color: #0000ff;">case</span> <span style="color: #a31515;">"write"</span>:            <span style="color: #795E26;">write</span>();        <span style="color: #0000ff;">case</span> <span style="color: #a31515;">"send"</span>:            <span style="color: #795E26;">send</span>();        <span style="color: #795E26;">default</span>:            <span style="color: #0000ff;">die</span>();    }

### [WebFinger](#webfinger)

The [WebFinger Protocol](https://docs.joinmastodon.org/spec/webfinger/) is used to identify accounts.It is requested with `example.com/.well-known/webfinger?resource=acct:[email protected]`This server only has one user, so it ignores the query string and always returns the same details.

<span class="tempest-highlight-language"><img alt="" class="tempest-highlight-language-icon" height="32" src="https://shkspr.mobi/blog/wp-content/plugins/tempest-highlight//svg/php.svg" width="32"></img><span itemprop="programmingLanguage"> PHP</span></span>```
<span style="color: #0000ff;">function</span> <span style="color: #795E26;">webfinger</span>() { <span style="color: #0000ff;">global</span> <span style="color: #001080;">$username</span>, <span style="color: #001080;">$server</span>; <span style="color: #001080;">$webfinger</span> = <span style="color: #795E26;">array</span>( <span style="color: #a31515;">"subject"</span> => <span style="color: #a31515;">"acct:{$username}@{$server}"</span>, <span style="color: #a31515;">"links"</span> => <span style="color: #795E26;">array</span>( <span style="color: #795E26;">array</span>( <span style="color: #a31515;">"rel"</span> => <span style="color: #a31515;">"self"</span>, <span style="color: #a31515;">"type"</span> => <span style="color: #a31515;">"application/activity+json"</span>, <span style="color: #a31515;">"href"</span> => <span style="color: #a31515;">"https://{$server}/{$username}"</span> ) ) ); <span style="color: #795E26;">header</span>( <span style="color: #a31515;">"Content-Type: application/json"</span> ); <span style="color: #0000ff;">echo</span> <span style="color: #795E26;">json_encode</span>( <span style="color: #001080;">$webfinger</span> ); <span style="color: #0000ff;">die</span>(); }

Username

Requesting example.com/username returns a JSON document with the user's information.

<span class="tempest-highlight-language"><img alt="" class="tempest-highlight-language-icon" height="32" src="https://shkspr.mobi/blog/wp-content/plugins/tempest-highlight//svg/php.svg" width="32"></img><span itemprop="programmingLanguage"> PHP</span></span>```
    <span style="color: #0000ff;">function</span> <span style="color: #795E26;">username</span>() {        <span style="color: #0000ff;">global</span> <span style="color: #001080;">$username</span>, <span style="color: #001080;">$realName</span>, <span style="color: #001080;">$summary</span>, <span style="color: #001080;">$server</span>, <span style="color: #001080;">$key_public</span>;        <span style="color: #001080;">$user</span> = <span style="color: #795E26;">array</span>(            <span style="color: #a31515;">"@context"</span> => [                <span style="color: #a31515;">"https://www.w3.org/ns/activitystreams"</span>,                <span style="color: #a31515;">"https://w3id.org/security/v1"</span>            ],                                   <span style="color: #a31515;">"id"</span> => <span style="color: #a31515;">"https://{$server}/{$username}"</span>,                                 <span style="color: #a31515;">"type"</span> => <span style="color: #a31515;">"Person"</span>,                            <span style="color: #a31515;">"following"</span> => <span style="color: #a31515;">"https://{$server}/following"</span>,                            <span style="color: #a31515;">"followers"</span> => <span style="color: #a31515;">"https://{$server}/followers"</span>,                                <span style="color: #a31515;">"inbox"</span> => <span style="color: #a31515;">"https://{$server}/inbox"</span>,                    <span style="color: #a31515;">"preferredUsername"</span> =>  <span style="color: #795E26;">rawurldecode</span>(<span style="color: #001080;">$username</span>),                                 <span style="color: #a31515;">"name"</span> => <span style="color: #a31515;">"{$realName}"</span>,                              <span style="color: #a31515;">"summary"</span> => <span style="color: #a31515;">"{$summary}"</span>,                                  <span style="color: #a31515;">"url"</span> => <span style="color: #a31515;">"https://{$server}"</span>,            <span style="color: #a31515;">"manuallyApprovesFollowers"</span> =>  <span style="color: #0000ff;">true</span>,                         <span style="color: #a31515;">"discoverable"</span> =>  <span style="color: #0000ff;">true</span>,                            <span style="color: #a31515;">"published"</span> => <span style="color: #a31515;">"2024-02-12T11:51:00Z"</span>,            <span style="color: #a31515;">"icon"</span> => [                     <span style="color: #a31515;">"type"</span> => <span style="color: #a31515;">"Image"</span>,                <span style="color: #a31515;">"mediaType"</span> => <span style="color: #a31515;">"image/png"</span>,                      <span style="color: #a31515;">"url"</span> => <span style="color: #a31515;">"https://{$server}/icon.png"</span>            ],            <span style="color: #a31515;">"publicKey"</span> => [                <span style="color: #a31515;">"id"</span>           => <span style="color: #a31515;">"https://{$server}/{$username}#main-key"</span>,                <span style="color: #a31515;">"owner"</span>        => <span style="color: #a31515;">"https://{$server}/{$username}"</span>,                <span style="color: #a31515;">"publicKeyPem"</span> => <span style="color: #001080;">$key_public</span>            ]        );        <span style="color: #795E26;">header</span>( <span style="color: #a31515;">"Content-Type: application/activity+json"</span> );        <span style="color: #0000ff;">echo</span> <span style="color: #795E26;">json_encode</span>( <span style="color: #001080;">$user</span> );        <span style="color: #0000ff;">die</span>();    }

### [Following &amp; Followers](#following-followers)

These JSON documents show how many users are following / followers-of this account.The information here is self-attested. So you can lie and use any number you want.

<span class="tempest-highlight-language"><img alt="" class="tempest-highlight-language-icon" height="32" src="https://shkspr.mobi/blog/wp-content/plugins/tempest-highlight//svg/php.svg" width="32"></img><span itemprop="programmingLanguage"> PHP</span></span>```
<span style="color: #0000ff;">function</span> <span style="color: #795E26;">following</span>() { <span style="color: #0000ff;">global</span> <span style="color: #001080;">$server</span>; <span style="color: #001080;">$following</span> = <span style="color: #795E26;">array</span>( <span style="color: #a31515;">"@context"</span> => <span style="color: #a31515;">"https://www.w3.org/ns/activitystreams"</span>, <span style="color: #a31515;">"id"</span> => <span style="color: #a31515;">"https://{$server}/following"</span>, <span style="color: #a31515;">"type"</span> => <span style="color: #a31515;">"Collection"</span>, <span style="color: #a31515;">"totalItems"</span> => 0, <span style="color: #a31515;">"items"</span> => [] ); <span style="color: #795E26;">header</span>( <span style="color: #a31515;">"Content-Type: application/activity+json"</span> ); <span style="color: #0000ff;">echo</span> <span style="color: #795E26;">json_encode</span>( <span style="color: #001080;">$following</span> ); <span style="color: #0000ff;">die</span>(); } <span style="color: #0000ff;">function</span> <span style="color: #795E26;">followers</span>() { <span style="color: #0000ff;">global</span> <span style="color: #001080;">$server</span>; <span style="color: #001080;">$followers</span> = <span style="color: #795E26;">array</span>( <span style="color: #a31515;">"@context"</span> => <span style="color: #a31515;">"https://www.w3.org/ns/activitystreams"</span>, <span style="color: #a31515;">"id"</span> => <span style="color: #a31515;">"https://{$server}/followers"</span>, <span style="color: #a31515;">"type"</span> => <span style="color: #a31515;">"Collection"</span>, <span style="color: #a31515;">"totalItems"</span> => 0, <span style="color: #a31515;">"items"</span> => [] ); <span style="color: #795E26;">header</span>( <span style="color: #a31515;">"Content-Type: application/activity+json"</span> ); <span style="color: #0000ff;">echo</span> <span style="color: #795E26;">json_encode</span>( <span style="color: #001080;">$followers</span> ); <span style="color: #0000ff;">die</span>(); }

Inbox

The /inbox is the main server. It receives all requests. This server only responds to "Follow" requests.A remote server sends a follow request which is a JSON file saying who they are.This code does not cryptographically validate the headers of the received message.The name of the remote user's server is saved to a file so that future messages can be delivered to it.An accept request is cryptographically signed and POST'd back to the remote server.

<span class="tempest-highlight-language"><img alt="" class="tempest-highlight-language-icon" height="32" src="https://shkspr.mobi/blog/wp-content/plugins/tempest-highlight//svg/php.svg" width="32"></img><span itemprop="programmingLanguage"> PHP</span></span>```
    <span style="color: #0000ff;">function</span> <span style="color: #795E26;">inbox</span>() {        <span style="color: #0000ff;">global</span> <span style="color: #001080;">$body</span>, <span style="color: #001080;">$server</span>, <span style="color: #001080;">$username</span>, <span style="color: #001080;">$key_private</span>;        <span style="color: #008000;" translate="yes">//  Get the message and type</span>        <span style="color: #001080;">$inbox_message</span> = <span style="color: #001080;">$body</span>;        <span style="color: #001080;">$inbox_type</span> = <span style="color: #001080;">$inbox_message</span>[<span style="color: #a31515;">"type"</span>];        <span style="color: #008000;" translate="yes">//  This inbox only responds to follow requests</span>        <span style="color: #0000ff;">if</span> ( <span style="color: #a31515;">"Follow"</span> != <span style="color: #001080;">$inbox_type</span> ) { <span style="color: #0000ff;">die</span>(); }        <span style="color: #008000;" translate="yes">//  Get the parameters</span>        <span style="color: #001080;">$inbox_id</span>    = <span style="color: #001080;">$inbox_message</span>[<span style="color: #a31515;">"id"</span>];        <span style="color: #001080;">$inbox_actor</span> = <span style="color: #001080;">$inbox_message</span>[<span style="color: #a31515;">"actor"</span>];        <span style="color: #001080;">$inbox_host</span>  = <span style="color: #795E26;">parse_url</span>( <span style="color: #001080;">$inbox_actor</span>, <span style="color: #795E26;">PHP_URL_HOST</span> );        <span style="color: #008000;" translate="yes">//  Does this account have any followers?</span>        <span style="color: #0000ff;">if</span>( <span style="color: #795E26;">file_exists</span>( <span style="color: #a31515;">"followers.json"</span> ) ) {            <span style="color: #001080;">$followers_file</span> = <span style="color: #795E26;">file_get_contents</span>( <span style="color: #a31515;">"followers.json"</span> );            <span style="color: #001080;">$followers_json</span> = <span style="color: #795E26;">json_decode</span>( <span style="color: #001080;">$followers_file</span>, <span style="color: #0000ff;">true</span> );        } <span style="color: #0000ff;">else</span> {            <span style="color: #001080;">$followers_json</span> = <span style="color: #795E26;">array</span>();        }        <span style="color: #008000;" translate="yes">//  Add user to list. Don't care about duplicate users, server is what's important</span>        <span style="color: #001080;">$followers_json</span>[<span style="color: #001080;">$inbox_host</span>][<span style="color: #a31515;">"users"</span>][] = <span style="color: #001080;">$inbox_actor</span>;        <span style="color: #008000;" translate="yes">//  Save the new followers file</span>        <span style="color: #795E26;">file_put_contents</span>( <span style="color: #a31515;">"followers.json"</span>, <span style="color: #795E26;">print_r</span>( <span style="color: #795E26;">json_encode</span>( <span style="color: #001080;">$followers_json</span> ), <span style="color: #0000ff;">true</span> ) );        <span style="color: #008000;" translate="yes">//  Response Message ID</span>        <span style="color: #008000;" translate="yes">//  This isn't used for anything important so could just be a random number</span>        <span style="color: #001080;">$guid</span> = <span style="color: #795E26;">uuid</span>();        <span style="color: #008000;" translate="yes">//  Create the Accept message</span>        <span style="color: #001080;">$message</span> = [            <span style="color: #a31515;">"@context"</span> => <span style="color: #a31515;">"https://www.w3.org/ns/activitystreams"</span>,            <span style="color: #a31515;">"id"</span>       => <span style="color: #a31515;">"https://{$server}/{$guid}"</span>,            <span style="color: #a31515;">"type"</span>     => <span style="color: #a31515;">"Accept"</span>,            <span style="color: #a31515;">"actor"</span>    => <span style="color: #a31515;">"https://{$server}/{$username}"</span>,            <span style="color: #a31515;">"object"</span>   => [                <span style="color: #a31515;">"@context"</span> => <span style="color: #a31515;">"https://www.w3.org/ns/activitystreams"</span>,                <span style="color: #a31515;">"id"</span>       =>  <span style="color: #001080;">$inbox_id</span>,                <span style="color: #a31515;">"type"</span>     =>  <span style="color: #001080;">$inbox_type</span>,                <span style="color: #a31515;">"actor"</span>    =>  <span style="color: #001080;">$inbox_actor</span>,                <span style="color: #a31515;">"object"</span>   => <span style="color: #a31515;">"https://{$server}/{$username}"</span>,            ]        ];        <span style="color: #008000;" translate="yes">//  The Accept is sent to the server of the user who requested the follow</span>        <span style="color: #008000;" translate="yes">//  TODO: The path doesn't *always* end with/inbox</span>        <span style="color: #001080;">$host</span> = <span style="color: #001080;">$inbox_host</span>;        <span style="color: #001080;">$path</span> = <span style="color: #795E26;">parse_url</span>( <span style="color: #001080;">$inbox_actor</span>, <span style="color: #795E26;">PHP_URL_PATH</span> ) . <span style="color: #a31515;">"/inbox"</span>;        <span style="color: #008000;" translate="yes">//  Get the signed headers</span>        <span style="color: #001080;">$headers</span> = <span style="color: #795E26;">generate_signed_headers</span>( <span style="color: #001080;">$message</span>, <span style="color: #001080;">$host</span>, <span style="color: #001080;">$path</span> );        <span style="color: #008000;" translate="yes">//  Specify the URL of the remote server's inbox</span>        <span style="color: #008000;" translate="yes">//  TODO: The path doesn't *always* end with /inbox</span>        <span style="color: #001080;">$remoteServerUrl</span> = <span style="color: #001080;">$inbox_actor</span> . <span style="color: #a31515;">"/inbox"</span>;        <span style="color: #008000;" translate="yes">//  POST the message and header to the requester's inbox</span>        <span style="color: #001080;">$ch</span> = <span style="color: #795E26;">curl_init</span>( <span style="color: #001080;">$remoteServerUrl</span> );        <span style="color: #795E26;">curl_setopt</span>( <span style="color: #001080;">$ch</span>, <span style="color: #795E26;">CURLOPT_RETURNTRANSFER</span>, <span style="color: #0000ff;">true</span> );        <span style="color: #795E26;">curl_setopt</span>( <span style="color: #001080;">$ch</span>, <span style="color: #795E26;">CURLOPT_CUSTOMREQUEST</span>, <span style="color: #a31515;">"POST"</span> );        <span style="color: #795E26;">curl_setopt</span>( <span style="color: #001080;">$ch</span>, <span style="color: #795E26;">CURLOPT_POSTFIELDS</span>,     <span style="color: #795E26;">json_encode</span>(<span style="color: #001080;">$message</span>) );        <span style="color: #795E26;">curl_setopt</span>( <span style="color: #001080;">$ch</span>, <span style="color: #795E26;">CURLOPT_HTTPHEADER</span>,     <span style="color: #001080;">$headers</span> );        <span style="color: #001080;">$response</span> = <span style="color: #795E26;">curl_exec</span>( <span style="color: #001080;">$ch</span> );        <span style="color: #008000;" translate="yes">//  Check for errors</span>        <span style="color: #0000ff;">if</span>( <span style="color: #795E26;">curl_errno</span>( <span style="color: #001080;">$ch</span> ) ) {            <span style="color: #795E26;">file_put_contents</span>( <span style="color: #a31515;">"error.txt"</span>,  <span style="color: #795E26;">curl_error</span>( <span style="color: #001080;">$ch</span> ) );        }        <span style="color: #795E26;">curl_close</span>(<span style="color: #001080;">$ch</span>);        <span style="color: #0000ff;">die</span>();    }

### [UUID](#uuid)

Every message sent should have a unique ID. This can be anything you like. Some servers use a random number.I prefer a date-sortable string.

<span class="tempest-highlight-language"><img alt="" class="tempest-highlight-language-icon" height="32" src="https://shkspr.mobi/blog/wp-content/plugins/tempest-highlight//svg/php.svg" width="32"></img><span itemprop="programmingLanguage"> PHP</span></span>```
<span style="color: #0000ff;">function</span> <span style="color: #795E26;">uuid</span>() { <span style="color: #0000ff;">return</span> <span style="color: #795E26;">sprintf</span>( <span style="color: #a31515;">"%08x-%04x-%04x-%04x-%012x"</span>, <span style="color: #795E26;">time</span>(), <span style="color: #795E26;">mt_rand</span>(0, 0xffff), <span style="color: #795E26;">mt_rand</span>(0, 0xffff), <span style="color: #795E26;">mt_rand</span>(0, 0x3fff) | 0x8000, <span style="color: #795E26;">mt_rand</span>(0, 0xffffffffffff) ); }

Signing Headers

Every message that your server sends needs to be cryptographically signed with your Private Key.This is a complicated process. Please read "How to make friends and verify requests" for more information.

<span class="tempest-highlight-language"><img alt="" class="tempest-highlight-language-icon" height="32" src="https://shkspr.mobi/blog/wp-content/plugins/tempest-highlight//svg/php.svg" width="32"></img><span itemprop="programmingLanguage"> PHP</span></span>```
    <span style="color: #0000ff;">function</span> <span style="color: #795E26;">generate_signed_headers</span>(<span class="hl-injection"> $message, $host, $path </span>) {        <span style="color: #0000ff;">global</span> <span style="color: #001080;">$server</span>, <span style="color: #001080;">$username</span>, <span style="color: #001080;">$key_private</span>;        <span style="color: #008000;" translate="yes">//  Encode the message to JSON</span>        <span style="color: #001080;">$message_json</span> = <span style="color: #795E26;">json_encode</span>( <span style="color: #001080;">$message</span> );        <span style="color: #008000;" translate="yes">//  Location of the Public Key</span>        <span style="color: #001080;">$keyId</span> = <span style="color: #a31515;">"https://{$server}/{$username}#main-key"</span>;        <span style="color: #008000;" translate="yes">//  Generate signing variables</span>        <span style="color: #001080;">$hash</span>   = <span style="color: #795E26;">hash</span>( <span style="color: #a31515;">"sha256"</span>, <span style="color: #001080;">$message_json</span>, <span style="color: #0000ff;">true</span> );        <span style="color: #001080;">$digest</span> = <span style="color: #795E26;">base64_encode</span>( <span style="color: #001080;">$hash</span> );        <span style="color: #001080;">$date</span>   = <span style="color: #795E26;">date</span>( <span style="color: #a31515;">"D, d M Y H:i:s GMT"</span> );        <span style="color: #008000;" translate="yes">//  Get the Private Key</span>        <span style="color: #001080;">$signer</span> = <span style="color: #795E26;">openssl_get_privatekey</span>( <span style="color: #001080;">$key_private</span> );        <span style="color: #008000;" translate="yes">//  Sign the path, host, date, and digest</span>        <span style="color: #001080;">$stringToSign</span> = <span style="color: #a31515;">"(request-target): post $pathnhost: $hostndate: $datendigest: SHA-256=$digest"</span>;        <span style="color: #008000;" translate="yes">//  The signing function returns the variable $signature</span>        <span style="color: #008000;" translate="yes">//  https://www.php.net/manual/en/function.openssl-sign.php</span>        <span style="color: #795E26;">openssl_sign</span>(            <span style="color: #001080;">$stringToSign</span>,             <span style="color: #001080;">$signature</span>,             <span style="color: #001080;">$signer</span>,             <span style="color: #267f99;">OPENSSL_ALGO_SHA256</span>        );        <span style="color: #008000;" translate="yes">//  Encode the signature</span>        <span style="color: #001080;">$signature_b64</span> = <span style="color: #795E26;">base64_encode</span>( <span style="color: #001080;">$signature</span> );        <span style="color: #008000;" translate="yes">//  Full signature header</span>        <span style="color: #001080;">$signature_header</span> = <span style="color: #a31515;">'keyId="'</span> . <span style="color: #001080;">$keyId</span> . <span style="color: #a31515;">'",algorithm="rsa-sha256",headers="(request-target) host date digest",signature="'</span> . <span style="color: #001080;">$signature_b64</span> . <span style="color: #a31515;">'"'</span>;        <span style="color: #008000;" translate="yes">//  Header for POST reply</span>        <span style="color: #001080;">$headers</span> = <span style="color: #795E26;">array</span>(                    <span style="color: #a31515;">"Host: {$host}"</span>,                    <span style="color: #a31515;">"Date: {$date}"</span>,                  <span style="color: #a31515;">"Digest: SHA-256={$digest}"</span>,               <span style="color: #a31515;">"Signature: {$signature_header}"</span>,            <span style="color: #a31515;">"Content-Type: application/activity+json"</span>,                  <span style="color: #a31515;">"Accept: application/activity+json"</span>,        );        <span style="color: #0000ff;">return</span> <span style="color: #001080;">$headers</span>;    }

### [User Interface for Writing](#user-interface-for-writing)

This creates a basic HTML form. Type in your message and your password. It then POSTs the data to the `/send` endpoint.

<span class="tempest-highlight-language"><img alt="" class="tempest-highlight-language-icon" height="32" src="https://shkspr.mobi/blog/wp-content/plugins/tempest-highlight//svg/php.svg" width="32"></img><span itemprop="programmingLanguage"> PHP</span></span>```
<span style="color: #0000ff;">function</span> <span style="color: #795E26;">write</span>() { <span style="color: #008000;" translate="yes">// Display an HTML form for the user to enter a message.</span><span style="color: #0000ff;">echo</span> <<< <span style="color: #795E26;">HTML</span><!<span style="color: #795E26;">DOCTYPE</span> html><html lang=<span style="color: #a31515;">"en-GB"</span>> <head> <meta charset=<span style="color: #a31515;">"UTF-8"</span>> <title>Send Message</title> <style> *{font-family:sans-serif;font-size:1.1em;} </style> </head> <body> <form action=<span style="color: #a31515;">"/send"</span> method=<span style="color: #a31515;">"post"</span> enctype=<span style="color: #a31515;">"multipart/form-data"</span>> <label for=<span style="color: #a31515;">"content"</span>>Your message:</label><br> <textarea id=<span style="color: #a31515;">"content"</span> name=<span style="color: #a31515;">"content"</span> rows=<span style="color: #a31515;">"5"</span> cols=<span style="color: #a31515;">"32"</span>></textarea><br> <label for=<span style="color: #a31515;">"password"</span>>Password</label><br> <input type=<span style="color: #a31515;">"password"</span> name=<span style="color: #a31515;">"password"</span> id=<span style="color: #a31515;">"password"</span> size=<span style="color: #a31515;">"32"</span>><br> <input type=<span style="color: #a31515;">"submit"</span> value=<span style="color: #a31515;">"Post Message"</span>> </form> </body></html><span style="color: #267f99;">HTML</span>; <span style="color: #0000ff;">die</span>(); }

Send Endpoint

This takes the submitted message and checks the password is correct.It reads the followers.json file and sends the message to every server that is following this account.

<span class="tempest-highlight-language"><img alt="" class="tempest-highlight-language-icon" height="32" src="https://shkspr.mobi/blog/wp-content/plugins/tempest-highlight//svg/php.svg" width="32"></img><span itemprop="programmingLanguage"> PHP</span></span>```
    <span style="color: #0000ff;">function</span> <span style="color: #795E26;">send</span>() {        <span style="color: #0000ff;">global</span> <span style="color: #001080;">$password</span>, <span style="color: #001080;">$server</span>, <span style="color: #001080;">$username</span>, <span style="color: #001080;">$key_private</span>;        <span style="color: #008000;" translate="yes">//  Does the posted password match the stored password?</span>        <span style="color: #0000ff;">if</span>( <span style="color: #001080;">$password</span> != <span style="color: #001080;">$_POST</span>[<span style="color: #a31515;">"password"</span>] ) { <span style="color: #0000ff;">die</span>(); }        <span style="color: #008000;" translate="yes">//  Get the posted content</span>        <span style="color: #001080;">$content</span> = <span style="color: #001080;">$_POST</span>[<span style="color: #a31515;">"content"</span>];        <span style="color: #008000;" translate="yes">//  Current time - ISO8601</span>        <span style="color: #001080;">$timestamp</span> = <span style="color: #795E26;">date</span>( <span style="color: #a31515;">"c"</span> );        <span style="color: #008000;" translate="yes">//  Outgoing Message ID</span>        <span style="color: #001080;">$guid</span> = <span style="color: #795E26;">uuid</span>();        <span style="color: #008000;" translate="yes">//  Construct the Note</span>        <span style="color: #008000;" translate="yes">//  contentMap is used to prevent unnecessary "translate this post" pop ups</span>        <span style="color: #008000;" translate="yes">// hardcoded to English</span>        <span style="color: #001080;">$note</span> = [            <span style="color: #a31515;">"@context"</span>     => <span style="color: #795E26;">array</span>(                <span style="color: #a31515;">"https://www.w3.org/ns/activitystreams"</span>            ),            <span style="color: #a31515;">"id"</span>           => <span style="color: #a31515;">"https://{$server}/posts/{$guid}.json"</span>,            <span style="color: #a31515;">"type"</span>         => <span style="color: #a31515;">"Note"</span>,            <span style="color: #a31515;">"published"</span>    => <span style="color: #001080;">$timestamp</span>,            <span style="color: #a31515;">"attributedTo"</span> => <span style="color: #a31515;">"https://{$server}/{$username}"</span>,            <span style="color: #a31515;">"content"</span>      => <span style="color: #001080;">$content</span>,            <span style="color: #a31515;">"contentMap"</span>   => [<span style="color: #a31515;">"en"</span> => <span style="color: #001080;">$content</span>],            <span style="color: #a31515;">"to"</span>           => [<span style="color: #a31515;">"https://www.w3.org/ns/activitystreams#Public"</span>]        ];        <span style="color: #008000;" translate="yes">//  Construct the Message</span>        <span style="color: #001080;">$message</span> = [            <span style="color: #a31515;">"@context"</span> => <span style="color: #a31515;">"https://www.w3.org/ns/activitystreams"</span>,            <span style="color: #a31515;">"id"</span>       => <span style="color: #a31515;">"https://{$server}/posts/{$guid}.json"</span>,            <span style="color: #a31515;">"type"</span>     => <span style="color: #a31515;">"Create"</span>,            <span style="color: #a31515;">"actor"</span>    => <span style="color: #a31515;">"https://{$server}/{$username}"</span>,            <span style="color: #a31515;">"to"</span>       => [                <span style="color: #a31515;">"https://www.w3.org/ns/activitystreams#Public"</span>            ],            <span style="color: #a31515;">"cc"</span>       => [                <span style="color: #a31515;">"https://{$server}/followers"</span>            ],            <span style="color: #a31515;">"object"</span>   => <span style="color: #001080;">$note</span>        ];        <span style="color: #008000;" translate="yes">//  Create the context for the permalink</span>        <span style="color: #001080;">$note</span> = [ <span style="color: #a31515;">"@context"</span> => <span style="color: #a31515;">"https://www.w3.org/ns/activitystreams"</span>, ...<span style="color: #001080;">$note</span> ];        <span style="color: #008000;" translate="yes">//  Save the permalink</span>        <span style="color: #001080;">$note_json</span> = <span style="color: #795E26;">json_encode</span>( <span style="color: #001080;">$note</span> );        <span style="color: #008000;" translate="yes">//  Check for posts/ directory and create it</span>        <span style="color: #0000ff;">if</span>( ! <span style="color: #795E26;">is_dir</span>( <span style="color: #a31515;">"posts"</span> ) ) { <span style="color: #795E26;">mkdir</span>( <span style="color: #a31515;">"posts"</span>); }        <span style="color: #795E26;">file_put_contents</span>( <span style="color: #a31515;">"posts/{$guid}.json"</span>, <span style="color: #795E26;">print_r</span>( <span style="color: #001080;">$note_json</span>, <span style="color: #0000ff;">true</span> ) );        <span style="color: #008000;" translate="yes">//  Read existing users and get their hosts</span>        <span style="color: #001080;">$followers_file</span> = <span style="color: #795E26;">file_get_contents</span>( <span style="color: #a31515;">"followers.json"</span> );        <span style="color: #001080;">$followers_json</span> = <span style="color: #795E26;">json_decode</span>( <span style="color: #001080;">$followers_file</span>, <span style="color: #0000ff;">true</span> );             <span style="color: #001080;">$hosts</span> = <span style="color: #795E26;">array_keys</span>( <span style="color: #001080;">$followers_json</span> );        <span style="color: #008000;" translate="yes">//  Prepare to use the multiple cURL handle</span>        <span style="color: #001080;">$mh</span> = <span style="color: #795E26;">curl_multi_init</span>();        <span style="color: #008000;" translate="yes">//  Loop through all the severs of the followers</span>        <span style="color: #008000;" translate="yes">//  Each server needs its own cURL handle</span>        <span style="color: #008000;" translate="yes">//  Each POST to an inbox needs to be signed separately</span>        <span style="color: #0000ff;">foreach</span> ( <span style="color: #001080;">$hosts</span> <span style="color: #0000ff;">as</span> <span style="color: #001080;">$host</span> ) {            <span style="color: #001080;">$path</span> = <span style="color: #a31515;">"/inbox"</span>;            <span style="color: #008000;" translate="yes">//  Get the signed headers</span>            <span style="color: #001080;">$headers</span> = <span style="color: #795E26;">generate_signed_headers</span>( <span style="color: #001080;">$message</span>, <span style="color: #001080;">$host</span>, <span style="color: #001080;">$path</span> );            <span style="color: #008000;" translate="yes">// Specify the URL of the remote server</span>            <span style="color: #001080;">$remoteServerUrl</span> = <span style="color: #a31515;">"https://{$host}{$path}"</span>;            <span style="color: #008000;" translate="yes">//  POST the message and header to the requester's inbox</span>            <span style="color: #001080;">$ch</span> = <span style="color: #795E26;">curl_init</span>( <span style="color: #001080;">$remoteServerUrl</span> );            <span style="color: #795E26;">curl_setopt</span>( <span style="color: #001080;">$ch</span>, <span style="color: #795E26;">CURLOPT_RETURNTRANSFER</span>, <span style="color: #0000ff;">true</span> );            <span style="color: #795E26;">curl_setopt</span>( <span style="color: #001080;">$ch</span>, <span style="color: #795E26;">CURLOPT_CUSTOMREQUEST</span>, <span style="color: #a31515;">"POST"</span> );            <span style="color: #795E26;">curl_setopt</span>( <span style="color: #001080;">$ch</span>, <span style="color: #795E26;">CURLOPT_POSTFIELDS</span>,     <span style="color: #795E26;">json_encode</span>(<span style="color: #001080;">$message</span>) );            <span style="color: #795E26;">curl_setopt</span>( <span style="color: #001080;">$ch</span>, <span style="color: #795E26;">CURLOPT_HTTPHEADER</span>,     <span style="color: #001080;">$headers</span> );            <span style="color: #008000;" translate="yes">//  Add the handle to the multi-handle</span>            <span style="color: #795E26;">curl_multi_add_handle</span>( <span style="color: #001080;">$mh</span>, <span style="color: #001080;">$ch</span> );        }        <span style="color: #008000;" translate="yes">//  Execute the multi-handle</span>        <span style="color: #0000ff;">do</span> {            <span style="color: #001080;">$status</span> = <span style="color: #795E26;">curl_multi_exec</span>( <span style="color: #001080;">$mh</span>, <span style="color: #001080;">$active</span> );            <span style="color: #0000ff;">if</span> ( <span style="color: #001080;">$active</span> ) {                <span style="color: #795E26;">curl_multi_select</span>( <span style="color: #001080;">$mh</span> );            }        } <span style="color: #0000ff;">while</span> ( <span style="color: #001080;">$active</span> <span class="hl-operator">&&</span> <span style="color: #001080;">$status</span> == <span style="color: #795E26;">CURLM_OK</span> );        <span style="color: #008000;" translate="yes">//  Close the multi-handle</span>        <span style="color: #795E26;">curl_multi_close</span>( <span style="color: #001080;">$mh</span> );        <span style="color: #008000;" translate="yes">//  Render the JSON so the user can see the POST has worked</span>        <span style="color: #795E26;">header</span>( <span style="color: #a31515;">"Location: https://{$server}/posts/{$guid}.json"</span> );        <span style="color: #0000ff;">die</span>();    }

[Next Steps](#next-steps)
-------------------------

This is *not* intended to be used in production. **Ever**. But if you would like to contribute more simple examples of how the protocol works, please [come and play on GitLab](https://gitlab.com/edent/activitypub-single-php-file/).

You can follow the test user `@[email protected]`

#activitypub #mastodon #php