<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by h3poteto on Medium]]></title>
        <description><![CDATA[Stories by h3poteto on Medium]]></description>
        <link>https://medium.com/@h3poteto?source=rss-57e5809fd962------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*8pNKorQlbQknEA4znZlp-g.png</url>
            <title>Stories by h3poteto on Medium</title>
            <link>https://medium.com/@h3poteto?source=rss-57e5809fd962------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 06:58:07 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@h3poteto/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Rheomesh now supports WebRTC-HTTP Egress Protocol(WHEP)]]></title>
            <link>https://medium.com/@h3poteto/rheomesh-now-supports-webrtc-http-egress-protocol-whep-1b3d1246445f?source=rss-57e5809fd962------2</link>
            <guid isPermaLink="false">https://medium.com/p/1b3d1246445f</guid>
            <category><![CDATA[sfu]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[webrtc]]></category>
            <category><![CDATA[rust]]></category>
            <dc:creator><![CDATA[h3poteto]]></dc:creator>
            <pubDate>Fri, 26 Jun 2026 01:18:53 GMT</pubDate>
            <atom:updated>2026-06-26T01:18:53.289Z</atom:updated>
            <content:encoded><![CDATA[<p>I previously posted about my WebRTC SFU library.</p><p><a href="https://medium.com/@h3poteto/developing-a-webrtc-sfu-library-in-rust-019d467ab6c1">Developing a WebRTC SFU library in Rust</a></p><p>And this year, it starts supporting WHIP.</p><p><a href="https://medium.com/@h3poteto/rheomesh-now-supports-webrtc-http-ingestion-protocol-whip-and-scalable-video-coding-svc-f39fef4c6213">Rheomesh now supports WebRTC-HTTP Ingestion Protocol(WHIP) and Scalable Video Coding(SVC)</a></p><p>Eventually, this library supports WHEP.</p><h3>WHEP Support</h3><p>From 0.8.0, you can use WHEP feature.</p><p><a href="https://github.com/h3poteto/rheomesh/releases/tag/v0.8.0">Release 0.8.0 · h3poteto/rheomesh</a></p><p>WHEP is a protocol for WebRTC signaling using HTTP for receivers.</p><p><a href="https://www.ietf.org/archive/id/draft-murillo-whep-03.html">WebRTC-HTTP Egress Protocol (WHEP)</a></p><p>In typical WebRTC implementations, you need to implement signaling before starting publishing/subscribing to exchange SDP. Usually, people use WebSocket for it.<br>However, this can be cumbersome. To support SDP signaling using only HTTP, WHEP is provided. WHIP is for publishing, and WHEP is for subscribing.</p><h3>Key points are only 3 HTTP endpoints</h3><p>The publishing process begins with the client accessing an HTTP endpoint and sending an SDP. This is similar to using WebSocket; the client can essentially create an SDP containing the video and audio it wants to send. Sending this to the server initiates the initial stage of publishing.</p><p>However, in the case of subscribing, the media information is only on the server side, and with WebSocket, SDP generation was performed on the server side. In fact, most SFU servers send the SDP to the subscriber. Simply providing an HTTP endpoint doesn’t allow the server to unilaterally send data to the client. Therefore, WHEP uses a flow where the client sends an SDP without media information, the server fills in the missing media information, and then sends it back. This allows SDP exchange to begin using a standard HTTP server.</p><ul><li>POST /whep/{session_id}/{publisher_id} — Accepts empty SDP offer from client, returns SDP answer that contains track information specified by publisher_id</li><li>PATCH /whep/{session_id} — Handles trickle ICE candidates</li><li>DELETE /whep/{session_id} — Terminates the session</li></ul><p>The session_id is a unique identifier for each publishing session. This is the same as WHIP case. To provide session_id, you need to implement SubscribeTransportProvider:</p><pre>struct SessionStore {<br>    sessions: Arc&lt;Mutex&lt;HashMap&lt;String, Session&gt;&gt;&gt;<br>}<br><br>struct Session {<br>    session_id: String,  // Or user_id.<br>    subscribe_transport: Arc&lt;SubscribeTransport&gt;<br>}<br><br><br>#[async_trait::async_trait]<br>impl SubscribeTransportProvider for SessionStore {<br>    async fn get_subscribe_transport(<br>        &amp;self,<br>        session_id: &amp;str,<br>    ) -&gt; Result&lt;Arc&lt;SubscribeTransport&gt;, actix_web::Error&gt; {<br>        let sessions = self.sessions.lock().await;<br>        if let Some(session) = sessions.get(session_id) {<br>            let s = session.subscribe_transport.clone();<br>            Ok(s)<br>        } else {<br>            Err(actix_web::error::ErrorNotFound(&quot;Session not found&quot;))<br>        }<br>    }<br>}</pre><h3>Architecture</h3><p>Rheomesh is a WebRTC library, so it doesn’t contain any HTTP servers. This provides a helper method for actix-web.</p><pre>#[actix_web::main]<br>async fn main() -&gt; std::io::Result&lt;()&gt; {<br>    let store = SessionStore {<br>        sessions: Arc::new(Mutex::new(HashMap::new())),<br>    };<br>    let store_data = Data::new(store.clone());<br><br>    let endpoint = WhepEndpoint::new(store);<br><br>    HttpServer::new(move || {<br>        App::new()<br>            .app_data(store_data.clone())<br>            .configure(|cfg| {<br>                endpoint.clone().configure(cfg);<br>            })<br>    })<br>    .bind(&quot;0.0.0.0:4000&quot;)?<br>    .run()<br>    .await<br>}</pre><p>For more details, please refer to the example.</p><p><a href="https://github.com/h3poteto/rheomesh/blob/master/client/example/whep/src/pages/room.tsx">rheomesh/client/example/whep/src/pages/room.tsx at master · h3poteto/rheomesh</a></p><h3>Future Plans</h3><p>Currently, it supports only <em>actix-web</em> web servers. I know it should support other web servers.</p><p>Furthermore, WHEP should have protocol extensions, e.g.</p><ul><li>Server Sent Events extension</li><li>Video Layer Selection extension</li></ul><p>I want to support them.</p><p>And, this applies to both WHIP and WHEP;</p><ul><li>STUN/TURN server configuration</li><li>Authentication and authorization</li></ul><p>should be supported.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1b3d1246445f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Rheomesh now supports WebRTC-HTTP Ingestion Protocol(WHIP) and Scalable Video Coding(SVC)]]></title>
            <link>https://medium.com/@h3poteto/rheomesh-now-supports-webrtc-http-ingestion-protocol-whip-and-scalable-video-coding-svc-f39fef4c6213?source=rss-57e5809fd962------2</link>
            <guid isPermaLink="false">https://medium.com/p/f39fef4c6213</guid>
            <category><![CDATA[sfu]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[rust]]></category>
            <category><![CDATA[webrtc]]></category>
            <dc:creator><![CDATA[h3poteto]]></dc:creator>
            <pubDate>Thu, 19 Feb 2026 13:05:46 GMT</pubDate>
            <atom:updated>2026-02-19T13:05:46.278Z</atom:updated>
            <content:encoded><![CDATA[<p>I previously posted about my WebRTC SFU library.</p><p><a href="https://medium.com/@h3poteto/developing-a-webrtc-sfu-library-in-rust-019d467ab6c1">Developing a WebRTC SFU library in Rust</a></p><p>I’ll share two recent significant updates.</p><ol><li>Support WebRTC-HTTP Ingestion Protocol(WHIP)</li><li>Support Scalable Video Coding(SVC)</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FiX1qp_v6hKCNPGJkhiXeg.png" /></figure><h3>WHIP Support</h3><p>From 0.7.0, you can use WHIP feature.</p><p><a href="https://github.com/h3poteto/rheomesh/releases/tag/v0.7.0">Release 0.7.0 · h3poteto/rheomesh</a></p><p>WHIP is a protocol for WebRTC signaling using HTTP.<br><a href="https://www.ietf.org/archive/id/draft-ietf-wish-whip-09.html">https://www.ietf.org/archive/id/draft-ietf-wish-whip-09.html</a></p><p>In typical WebRTC implementations, you need to implement signaling before starting publishing/subscribing to exchange SDP. Usually, people use WebSocket for it.<br>However, this can be cumbersome. To support SDP signaling using only HTTP, WHIP is provided.</p><h4>Key points are only 3 HTTP endpoints</h4><p>WHIP handles only the publish side of WebRTC signaling. The protocol defines three HTTP endpoints:</p><p>- POST /whip/{session_id} — Accepts SDP offer from client, returns SDP answer<br>- PATCH /whip/{session_id} — Handles trickle ICE candidates<br>- DELETE /whip/{session_id} — Terminates the session</p><p>The session_id is a unique identifier for each publishing session. This could be:</p><p>- A user ID (e.g., “user123”)<br>- A stream key (e.g., “live-stream-abc”)<br>- A room and user combination (e.g., “room1-user456”)</p><p>You decide what the session_id represents based on your application’s needs. The session_id is used to:</p><p>1. Route the WHIP request to the correct PublishTransport<br>2. Identify which session to update or terminate</p><p>To provide <em>session_id</em>, you need to implement PublishTransportProvider:</p><pre>struct SessionStore {<br>    sessions: Arc&lt;Mutex&lt;HashMap&lt;String, Session&gt;&gt;&gt;<br>}<br><br>struct Session {<br>    session_id: String,  // Or user_id.<br>    publish_transport: Arc&lt;PublishTransport&gt;<br>}<br><br>#[async_trait]<br>impl PublishTransportProvider for SessionStore {<br>    async fn get_publish_transport(<br>        &amp;self,<br>        session_id: &amp;str,<br>    ) -&gt; Result&lt;Arc&lt;PublishTransport&gt;, actix_web::Error&gt; {<br>        let sessions = self.sessions.lock().await;<br>        if let Some(session) = sessions.get(session_id) {<br>            let p = session.publish_transport.clone();<br>            Ok(p)<br>        } else {<br>            Err(actix_web::error::ErrorNotFound(&quot;Session not found&quot;))<br>        }<br>    }<br>}</pre><h4>Architecture</h4><p>Rheomesh is a WebRTC library, so it doesn’t contain any HTTP servers. Usually, users prepare an HTTP server for handling HTTP requests or WebSocket requests. Rheomesh should be called inside their servers, so it doesn’t have its own HTTP servers.</p><p>Regarding WHIP, it is the same architecture.<br>In order to provide HTTP endpoints, Rheomesh provides a method for <a href="https://actix.rs/"><em>actix-web</em></a>. If you call <em>configure </em>method, you can inject WHIP endpoints into your actix web server.</p><pre>#[actix_web::main]<br>async fn main() -&gt; std::io::Result&lt;()&gt; {<br>    let store = SessionStore {<br>        sessions: Arc::new(Mutex::new(HashMap::new())),<br>    };<br>    let store_data = Data::new(store.clone());<br><br>    let endpoint = WhipEndpoint::new(store);<br><br>    HttpServer::new(move || {<br>        App::new()<br>            .app_data(store_data.clone())<br>            .configure(|cfg| {<br>                endpoint.clone().configure(cfg);<br>            })<br>    })<br>    .bind(&quot;0.0.0.0:4000&quot;)?<br>    .run()<br>    .await<br>}</pre><p>For more details, please refer to the official document</p><p><a href="https://h3poteto.github.io/rheomesh//pages/05_WHIP/">https://h3poteto.github.io/rheomesh/pages/05_WHIP/</a></p><h4>Future Plans</h4><p>Currently, it supports only <em>actix-web</em> web servers. I know it should support other web servers.</p><p>Furthermore, WHIP is for publish protocol, so we should support WebRTC-HTTP Egress Protocol(WHEP).</p><h3>SVC Support</h3><p>From 0.7.1, you can use SVC feature.</p><p><a href="https://github.com/h3poteto/rheomesh/releases/tag/v0.7.1">Release 0.7.1 · h3poteto/rheomesh</a></p><p>SVC (Scalable Video Coding) is a technique that encodes a single video stream with multiple quality layers (spatial and temporal). Each layer represents different resolutions or frame rates, allowing the SFU to select and forward appropriate layers to each subscriber based on their network conditions. This is more efficient than Simulcast because it requires only one encoding process on the publisher side.</p><p><a href="https://www.w3.org/TR/webrtc-svc/">Scalable Video Coding (SVC) Extension for WebRTC</a></p><h4>How to use</h4><p><strong>Publisher side</strong></p><p>You should specify encodings in frontend side when publishing a track.</p><pre>import { SVCEncodings } from &quot;rheomesh&quot;;<br><br>const publisher = await publishTransport.current!.publish(track, {<br>  encodings: SVCEncodings(),<br>  preferredCodec: &quot;AV1&quot;,<br>});</pre><p><em>SVCEncodings</em> returns <em>L2T3_KEY</em> for SVC, and you can use this option only AV1 or VP9 codecs. The <em>encodings</em> type is <em>Array&lt;SVCRTCRtpEncodingParameters&gt;</em>, so you can specify any values you want.</p><pre>encodings: [{ scalabilityMode: &quot;L2T3_KEY&quot; }]</pre><p>For more details, please refer to the official document</p><p><a href="https://h3poteto.github.io/rheomesh//pages/02_getting_started/#publish">https://h3poteto.github.io/rheomesh/pages/02_getting_started/#publish</a></p><p><strong>Subscriber side</strong></p><p>SFU server needs to receive SID and TID parameter for a subscriber. For example: <a href="https://github.com/h3poteto/rheomesh/blob/a9a63ef08a995ea7f6b430cc743d3fc7a8d9b748/sfu/examples/media_server.rs#L381-L396">https://github.com/h3poteto/rheomesh/blob/a9a63ef08a995ea7f6b430cc743d3fc7a8d9b748/sfu/examples/media_server.rs#L381-L396</a></p><p>Inside the server, you can set SID and TID for the subscriber.</p><pre>if let Err(err) = subscriber.set_preferred_layer(sid, tid).await {<br>    tracing::error!(&quot;Failed to set preferred layer: {}&quot;, err);<br>}</pre><p>After that, this subscriber receives only specified SID and TID packets.</p><h4>Future Plans</h4><p>Currently, you have to specify SID and TID fora subscriber. Of course, it is useful for specific cases. For example, you want to show a video widely and minimize other videos.</p><p>However, generally, it is annoying to specify SID and TID one by one. So, I’m thinking it would be nice Rheomesh could receive feedback from subscriber’s RTCP and detect the appropriate SID and TID. If we can do it, Rheomesh can change SID and TID automatically depending on the subscriber’s situation.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f39fef4c6213" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Rheomesh now supports Recording]]></title>
            <link>https://medium.com/@h3poteto/rheomesh-now-supports-recording-aa3b3f269265?source=rss-57e5809fd962------2</link>
            <guid isPermaLink="false">https://medium.com/p/aa3b3f269265</guid>
            <category><![CDATA[sfu]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[recording]]></category>
            <category><![CDATA[webrtc]]></category>
            <category><![CDATA[rust]]></category>
            <dc:creator><![CDATA[h3poteto]]></dc:creator>
            <pubDate>Sun, 20 Jul 2025 07:30:29 GMT</pubDate>
            <atom:updated>2025-07-20T07:30:29.932Z</atom:updated>
            <content:encoded><![CDATA[<p>Once I posted about my WebRTC SFU library.</p><p><a href="https://medium.com/@h3poteto/developing-a-webrtc-sfu-library-in-rust-019d467ab6c1">Developing a WebRTC SFU library in Rust</a></p><p>I’ve recently added a recording function to this project. This update brings new capabilities for capturing and processing RTP streams in flexible, server-friendly manner.</p><p>The v0.5.0 release introduces recording features.</p><p><a href="https://github.com/h3poteto/rheomesh/releases/tag/v0.5.0">Release 0.5.0 · h3poteto/rheomesh</a></p><p>And I published the documentation site. The recording feature is explained on the site.</p><p><a href="https://h3poteto.github.io/rheomesh//pages/04_recording/">Recording</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*k0-Op-5CqVzq9aREjs33ag.png" /></figure><h3>Design Philosophy: Raw RTP Packet Forwarding</h3><p>Rather than creating a monolithic function that handles everything from capture to file generation, I opted for a more modular approach. The recording functionality simply forwards raw RTP packets, leaving encoding and file creation to external tools like FFmpeg or GStreamer.</p><p>This design decision prioritizes flexibility, especially for server deployments where different use cases might require different encoding strategies. Both FFmpeg and GStreamer can generate video files from raw RTP packets, making this delegation approach both practical and powerful.</p><h3>SDP Generation</h3><p>While the core functionality focuses on packet forwarding, I recognized that Session Description Protocol (SDP) information would be essential for most recording workflows. The library includes SDP generation capabilities, particularly important for FFmpeg-based recording setups.</p><p>You can generate SDP strings using the <a href="https://docs.rs/rheomesh/latest/rheomesh/recording/recording_transport/struct.RecordingTransport.html#method.generate_sdp">generate_sdp</a> method from the <a href="https://docs.rs/rheomesh/latest/rheomesh/recording/recording_transport/struct.RecordingTransport.html">RecordingTransport</a> struct.</p><h3>Recording Workflow Examples</h3><h4>Using FFmpeg</h4><p>To start recording with FFmpeg, first generate the SDP file and set up FFmpeg to wait for incoming streams:</p><pre>$ ffplay -protocol_whitelist file,rtp,udp -analyzeduration 10000000 -probesize 50000000 -f sdp -i stream.sdp</pre><p>Then, initiate recording using the <a href="https://docs.rs/rheomesh/latest/rheomesh/recording/recording_transport/struct.RecordingTransport.html#method.start_recording">start_recording</a> method. This begins raw RTP packet transmission, and you should see the video stream in FFplay.</p><p>To save the recording to a file instead of just viewing it:</p><pre>$ ffmpeg -protocol_whitelist file,rtp,udp -i stream.sdp -c copy output.mkv</pre><p>This command will record the stream directly to output.mkv .</p><h4>Using GStreamer</h4><p>For GStreamer users, you’ll need to parse the SDP information and construct an appropriate pipeline. Here’s an example command for H.264 streams:</p><pre>$ gst-launch-1.0 udpsrc port=30001 ! \<br>  application/x-rtp,payload=103,encoding-name=H264 ! \<br>  rtph264depay ! \<br>  h264parse ! \<br>  avdec_h264 ! \<br>  videoconvert ! \<br>  autovideosink</pre><p>The specific parameters will vary depending on your encoding format. For detailed information about RTP and parser elements, consult the official GStreamer documentation:</p><ul><li><a href="https://gstreamer.freedesktop.org/documentation/rtp/index.html?gi-language=c">RTP Documentation</a></li><li><a href="https://gstreamer.freedesktop.org/documentation/videoparsersbad/index.html?gi-language=c">Video Parsers Documentation</a></li></ul><p>Make sure your pipeline parameters match the encoding specified in the generated SDP.</p><h3>Try It Yourself</h3><p>The project includes practical examples to help you get started. Check out the <a href="https://h3poteto.github.io/rheomesh//others/examples/">examples section</a> in the documentation.</p><p>The camera example demonstrates the recording functionality in action. It displays the generated SDP on screen, which you can copy and use with FFmpeg or GStreamer to receive and process the stream.</p><h3>What’s Next</h3><p>Looking ahead, I plan to focus on improving the relay functionality to make the library even more robust and feature-complete.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=aa3b3f269265" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Developing a WebRTC SFU library in Rust]]></title>
            <link>https://medium.com/@h3poteto/developing-a-webrtc-sfu-library-in-rust-019d467ab6c1?source=rss-57e5809fd962------2</link>
            <guid isPermaLink="false">https://medium.com/p/019d467ab6c1</guid>
            <category><![CDATA[webrtc]]></category>
            <category><![CDATA[sfu]]></category>
            <category><![CDATA[rust]]></category>
            <category><![CDATA[open-source]]></category>
            <dc:creator><![CDATA[h3poteto]]></dc:creator>
            <pubDate>Wed, 26 Mar 2025 23:17:25 GMT</pubDate>
            <atom:updated>2025-03-26T23:17:25.138Z</atom:updated>
            <content:encoded><![CDATA[<p>If you’re working with WebRTC, you’ve probably heard of Selective Forwarding Units (SFUs). There are solid open-source SFUs out there- <a href="https://github.com/versatica/mediasoup">mediasoup</a>, <a href="https://github.com/livekit/livekit">livekit</a>, and <a href="https://github.com/jitsi">Jitsi</a> to name a few.</p><h3>Motivation</h3><p>There is a WebRTC protocol implementation in Rust; it is <a href="https://github.com/webrtc-rs/webrtc">webrtc-rs</a>. This repository provides some examples that include SFU, but we still need to write a lot of code to implement a practical SFU server. In addition, existing repositories implementing SFU servers with webrtc-rs weren’t directly usable for my needs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6tK5gyZvv32Q4y52p8hQ5Q.jpeg" /></figure><h3>Introducing Rheomesh: A Rust SFU SDK</h3><p><a href="https://github.com/h3poteto/rheomesh">https://github.com/h3poteto/rheomesh</a></p><p>I developed a new SFU library in Rust to build your own WebRTC SFU servers. A key design principle was separating SFU-related logic from signaling protocols. Usually, you need a signaling logic to connect client browsers to your SFU server. I often use WebSocket as the signaling protocol, but, of course, you can choose other protocols, like gRPC or MQTT.</p><h3>Key features</h3><p>Key features of this library are</p><p>- Rust SDKs for SFU server development<br>- JavaScript library to communicate with the SFU server<br>- Supporting relay</p><p>It supports:</p><p>- Audio and video streaming<br>- DataChannel<br>- Simulcast</p><p>Nowadays, there is Scalable Video Coding (SVC), but it is not supported yet. Currently, Rheomesh supports only temporal layer filtering and doesn’t support spatial layer filtering due to patent concerns.</p><p>An interesting technical challenge during development was implementing a <a href="https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension">Dependency Descriptor</a> parser. While webrtc-rs provides temporal layer parsing for VP8 and VP9, it lacks support for AV1 and H264, which use Dependency Descriptors for temporal and spatial layer representation.</p><h3>Getting started</h3><p>This library doesn’t contain WebSocket server, so please implement a WebSocket server at first, and call Rheomesh methods from it.</p><h4>Server-side</h4><p><a href="https://github.com/h3poteto/rheomesh/blob/master/sfu/README.md">https://github.com/h3poteto/rheomesh/blob/master/sfu/README.md</a><br>First of all, please create a worker and router. Router accommodates multiple transports, and they can communicate with each other. That means transports belonging to the same Router can send/receive their media. Router is like a meeting room.</p><pre>use rheomesh::config::MediaConfig;<br>use rheomesh::router::Router;<br>async fn new() {<br> let config = MediaConfig::default();<br> let router = Router::new(config);<br> let mut config = WebRTCTransportConfig::default();<br> config.configuration.ice_servers = vec![RTCIceServer {<br> urls: vec![&quot;stun:stun.l.google.com:19302&quot;.to_owned()],<br> ..Default::default()<br> }];<br> let publish_transport = router.create_publish_transport(config.clone()).await;<br> let subscribe_transport = router.create_subscribe_transport(config.clone()).await;<br>}</pre><p>Please call Rheomesh methods in your WebSocket handler.</p><p>Regarding publishers:</p><pre>publish_transport<br> .on_ice_candidate(Box::new(move |candidate| {<br> let init = candidate.to_json().expect(&quot;failed to parse candidate&quot;);<br> // Send `init` message to the client. The client has to call `addIceCandidate` method with this parameter.<br> }))<br> .await;<br>// When you receive RTCIceCandidateInit message from the client.<br>let _ = publish_transport<br> .add_ice_candidate(candidate)<br> .await<br> .expect(&quot;failed to add ICE candidate&quot;);<br>// When you receive offer message from the client.<br>let answer = publish_transport<br> .get_answer(offer)<br> .await<br> .expect(&quot;failed to connect publish_transport&quot;);<br>// Send `answer` message to the client. The client has to call `setAnswer` method.<br>// Publish<br>let publisher = publish_transport.publish(track_id).await;</pre><p>Regarding subscribers:</p><pre>subscribe_transport<br> .on_ice_candidate(Box::new(move |candidate| {<br> let init = candidate.to_json().expect(&quot;failed to parse candidate&quot;);<br> // Send `init` message to the client. The client has to call `addIceCandidate` method with this parameter.<br> }))<br> .await;<br>subscribe_transport<br> .on_negotiation_needed(Box::new(move |offer| {<br> // Send `offer` message to the client. The client has to call `setOffer` method.<br> }))<br>// When you receive RTCIceCandidateInit message from the client.<br>let _ = subscribe_transport<br> .add_ice_candidate(candidate)<br> .await<br> .expect(&quot;failed to add ICE candidate&quot;);<br>// Subscribe<br>let (subscriber, offer) = subscribe_transport<br> .subscribe(track_id)<br> .await<br> .expect(&quot;failed to connect subscribe_transport&quot;);<br>// Send `offer` message to the client. The client has to call `setOffer` method.<br>// When you receive answer message from the client.<br>let _ = subscribe_transport<br> .set_answer(answer)<br> .await<br> .expect(&quot;failed to set answer&quot;);</pre><h4>Client-side</h4><p><a href="https://github.com/h3poteto/rheomesh/blob/master/client/README.md">https://github.com/h3poteto/rheomesh/blob/master/client/README.md</a><br>At first, please initialize transports.</p><pre>import { PublishTransport } from &#39;rheomesh&#39;<br>const peerConnectionConfig: RTCConfiguration = {<br> iceServers: [{ urls: &quot;stun:stun.l.google.com:19302&quot; }],<br>}<br>const publishTransport = new PublishTransport(peerConnectionConfig)<br>const subscribeTransport = new SubscribeTransport(peerConnectionConfig)</pre><p>Regarding publishers:</p><pre>publishTransport.on(&quot;icecandidate&quot;, (candidate) =&gt; {<br> // Send `candidate` to the server. The server has to call `add_ice_candidate` method with this parameter.<br>})<br>// When you receive candidateInit message from the server.<br>publishTransport.addIceCandidate(candidateInit)<br>// Publish a track.<br>const stream = await navigator.mediaDevices.getDisplayMedia({<br> video: true,<br> audio: false,<br>})<br>const offer = await publishTransport.publish(stream)<br>// Send `offer` to the server. The server has to call `get_answer` method with this parameter.<br>// When you receive answer message from the server.<br>publishTransport.setAnswer(answer)</pre><p>Regarding subscribers:</p><pre>subscribeTransport.on(&quot;icecandidate&quot;, (candidate) =&gt; {<br> // Send `candidate` to the server. The server has to call `add_ice_candidate` method with this parameter.<br>})<br>// When you receive candidateInit message from the server.<br>subscribeTransport.addIceCandidate(candidateInit)<br>// When you receive offer message from the server.<br>subscribeTransport.setOffer(offer).then((answer) =&gt; {<br> // Send `answer` to the server. The server has to call `set_answer` method with this parameter.<br>})<br>// Subscribe a track.<br>subscribeTransport.subscribe(publisherId).then((track) =&gt; {<br> const stream = new MediaStream([track])<br> remoteVideo.srcObject = stream<br>});</pre><h4>Examples</h4><p>I prepared an example server. Since this flow is complicated, please refer to these examples.</p><p><a href="https://github.com/h3poteto/rheomesh/blob/master/sfu/examples/media_server.rs">https://github.com/h3poteto/rheomesh/blob/master/sfu/examples/media_server.rs</a></p><p>The corresponding client-side JavaScript code is here.</p><p><a href="https://github.com/h3poteto/rheomesh/blob/master/client/example/multiple/src/pages/room.tsx">https://github.com/h3poteto/rheomesh/blob/master/client/example/multiple/src/pages/room.tsx</a></p><h3>The Relay Mechanism: A Unique Approach to Load Balancing</h3><p>One of the library’s key innovations is its relay mechanism — a method for distributing load across SFU servers. I called this relay, but there doesn’t seem to be a fixed name for it. In libraries like mediasoup, this concept is often called a pipe.</p><p>Usually, SFU server, does not support clustering or load balancing. Because an SFU server is just one of peers in WebRTC stack, so the idea of splitting it into multiple instances isn’t something that’s natively supported. Because of this, performance is inherently limited by the server’s specs, and ensuring fault tolerance by running multiple instances is also challenging.</p><h4>How Relay Works</h4><p>Typically, RTP packets received from client “<strong><em>a”</em></strong> by server “<strong><em>A”</em></strong><em> </em>can only be viewed by clients connected to server “<strong><em>A”</em></strong> (client “<strong><em>c”</em></strong>). The relay mechanism allows these packets to be forwarded to clients connected to server “<strong><em>B”</em></strong>, effectively enabling cross-server streaming.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wlyuGvDj0-0L9n4UJE7U3Q.png" /></figure><h4>Current Implementation</h4><p>For now, I’ve built a simple prototype that transfers RTP packets from server “<strong><em>A”</em></strong> to server “<strong><em>B”</em></strong> using UDP. However, this communication does not utilize the WebRTC stack. It’s just a direct transfer of RTP packets over UDP. Of course, I’ve implemented logic to ensure that the source and destination can be identified, but it’s not using DTLS, and the receiving side is just a basic UDP server. This means that RTP packets for various destinations are all coming in through the same UDP server. The performance implications of this approach are still unknown.</p><h4>Issue: RTCP feedbacks</h4><p>RTCP is a protocol where the receiving side sends feedback to the sending side, and in most WebRTC implementations, it flows from the receiver to the sender. Whether or not to handle it is up to the implementation, but it will be sent regardless. However, as mentioned earlier, the relay servers in the current setup are only forwarding RTP packets, so RTCP, which involves reverse-direction communication, is not being handled.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OV42Qi_DQJpRE2i0TelSrA.png" /></figure><p>For server “<strong><em>A”</em></strong>, since client “<strong><em>a”</em></strong> is directly connected to it, RTCP packets can simply be forwarded there. However, for server “<strong><em>B”</em></strong>, the current architecture does not account for communication in the direction of server “<strong><em>B”</em></strong> -&gt; server “<strong><em>A”</em></strong>, meaning there’s no way to send RTCP feedback back to the original sender. If I need to implement this, it would essentially require bidirectional communication. At that point, if I’m going to establish bidirectional communication over UDP anyway, I might as well switch to WebRTC, which would simplify the process.</p><p>Because of this, I might change this approach in the future.</p><h4>Challenges and Future Improvements</h4><p>I suspect that making this server-to-server relay WebRTC-based would result in a more stable implementation. However, there are several concerns.</p><p>The first is signaling — server-to-server communication requires its own signaling mechanism, separate from client-server signaling. This raises the question of whether signaling should be built into the library itself. I haven’t fully settled on an answer yet, but if I were to use something, gRPC seems like a viable option.</p><p>Another concern is port usage. When WebRTC communicates over UDP, it consumes a port, and typically, that port isn’t reused. This means that every time a server receives RTP packets from a client, it consumes a port. If another client connects to the same server, another port is consumed. Linux only supports up to 65,535 ports, and many lower-numbered ports are reserved for other purposes, making them unavailable for UDP. This means there’s a hard limit on the number of users that can be supported. Moreover, if server-to-server RTP relaying also consumes ports, the number of ports used can increase significantly. For example, if a server receives a single video stream and relays it to 10 other servers, it would use 1 port for receiving and 10 ports for relaying. On top of that, additional ports are required for clients receiving the stream. This becomes a serious issue.</p><h3>Performance</h3><p>I haven’t checked the performance in a production environment, but, currently, it works in my local machine properly, so the performance is not completely unusable.</p><p>The only way to check the performance is to prepare many clients and stream simultaneously. I plan to try this at some point. However, if you use this library and have any performance issues, I would appreciate it if you could report them to me. I will improve it.</p><h3>Why I use Rust</h3><p>First of all, recently, I like to write Rust. It has powerful memory management capabilities that extend to parallel processing. In the SFU, a lot of parallel processing is required, but at the same time, high-speed single-thread performance such as receiving and sending RTP packet is also required. I thought Rust would be a good fit for this requirement.</p><h3>Conclusion</h3><p>Developing an SFU library from scratch has been an interesting challenge. By leveraging Rust’s strong memory safety and concurrency features, I’ve been able to build a system that efficiently processes RTP packets while keeping SFU-related logic separate from signaling.</p><p>While the current implementation works well locally, significant improvements are needed:</p><p>- Enhanced relay mechanism<br>- Comprehensive performance testing<br>- <a href="https://github.com/h3poteto/rheomesh/issues/49">E2E tests</a><br>- <a href="https://github.com/h3poteto/rheomesh/issues/150">Recording feature</a></p><p>I welcome feedback and contributions from the community. If you’re interested in WebRTC SFU development in Rust, I’d love to hear your thoughts!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=019d467ab6c1" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Whalebird and Fedistar Now Support Pixelfed]]></title>
            <link>https://medium.com/@h3poteto/whalebird-and-fedistar-now-support-pixelfed-0c45011f0450?source=rss-57e5809fd962------2</link>
            <guid isPermaLink="false">https://medium.com/p/0c45011f0450</guid>
            <category><![CDATA[fedistar]]></category>
            <category><![CDATA[pixelfed]]></category>
            <category><![CDATA[whalebird]]></category>
            <dc:creator><![CDATA[h3poteto]]></dc:creator>
            <pubDate>Tue, 14 Jan 2025 13:18:41 GMT</pubDate>
            <atom:updated>2025-01-14T13:18:41.406Z</atom:updated>
            <content:encoded><![CDATA[<p>Pixelfed users, rejoice! After a long-standing issue with OAuth code authentication, Pixelfed has finally resolved a critical bug that prevented successful authentication when using urn:ietf:wg:oauth:2.0:oob as the redirect URL. This fix is detailed here:<br><a href="https://github.com/pixelfed/pixelfed/issues/2522">https://github.com/pixelfed/pixelfed/issues/2522</a></p><p>While some additional hiccups cropped up along the way (<a href="https://github.com/pixelfed/pixelfed/issues/5426">https://github.com/pixelfed/pixelfed/issues/5426</a>), the issues appear to be fully addressed on <a href="https://pixelfed.social/">pixelfed.social</a>, allowing OAuth code authentication to work smoothly.</p><p>With this improvement, Pixelfed support has been added to <a href="https://github.com/h3poteto/megalodon">Megalodon</a>, enabling seamless integration with both <a href="https://whalebird.social/en">Whalebird</a> and <a href="https://fedistar.net/">Fedistar</a>. Now, Pixelfed users can access their accounts and enjoy these apps’ streamlined user experiences.</p><h3>What’s Missing?</h3><p>While the authentication process is finally sorted, there are still some limitations in what Pixelfed can offer, primarily due to its current feature set.</p><h4>No WebSocket Streaming</h4><p>Pixelfed does not support WebSocket-based streaming, which means real-time features like notifications or live timeline updates are unavailable. This limitation isn’t specific to third-party apps — it’s absent even in Pixelfed’s web client.</p><p>While these features could be added in the future, for now, users will need to refresh manually for updates.</p><h4>No Lists</h4><p>Another significant limitation is the lack of support for lists, a feature commonly found in other Fediverse platforms. Without lists, apps cannot offer list-based functionalities, making it harder to organize your feed.</p><h4>No Text-Only Posts</h4><p>As a photo-sharing-focused platform, Pixelfed does not allow text-only posts without attachments. This required additional validation within the apps to ensure compliance with Pixelfed’s posting requirements.</p><h3>The Bottom Line</h3><p>Despite these limitations, the introduction of Pixelfed support in Whalebird and Fedistar marks a big step forward for users seeking a smoother way to interact with the platform. While there’s still room for growth — like real-time updates and list management — Pixelfed’s integration into these apps adds significant value, and it’s exciting to see how things will evolve from here.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0c45011f0450" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Whalebird rewritten in React]]></title>
            <link>https://medium.com/@h3poteto/whalebird-rewritten-in-react-ce6c80fed83f?source=rss-57e5809fd962------2</link>
            <guid isPermaLink="false">https://medium.com/p/ce6c80fed83f</guid>
            <category><![CDATA[mastodon]]></category>
            <category><![CDATA[desktop-app]]></category>
            <category><![CDATA[pleroma]]></category>
            <category><![CDATA[fediverse]]></category>
            <dc:creator><![CDATA[h3poteto]]></dc:creator>
            <pubDate>Tue, 09 Apr 2024 15:40:08 GMT</pubDate>
            <atom:updated>2024-04-09T15:43:21.639Z</atom:updated>
            <content:encoded><![CDATA[<p><a href="https://whalebird.social/en">Whalebird</a>, which had been using Vue.js for quite some time, has now been rewritten in React.js — or more precisely, Next.js. Initially, I attempted to rewrite it in Svelte, but that didn’t go as smoothly as expected, leading to the decision to switch to Next.js. Version 6.0.0 marked the initial release of Next.js, and by the time it reached 6.1.0, it had become somewhat usable, prompting me to document the process.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fIAn_FYTAOh3AdmCSeO1UA.png" /></figure><h3>Background and Considerations</h3><p>The idea of rewriting Whalebird first emerged around April 2023. During a previous update, I replaced the Node.js store (which had been using nedb) with better-sqlite3 for security reasons and the robustness of SQL. However, this choice turned out to be less than ideal. better-sqlite3 relied on native extensions, causing issues with missing SQLite binaries during cross-builds.</p><p>Next on the list was Vue3. While I had partially migrated to Vue3 in 2022, the experience wasn’t particularly enjoyable. Although Vue2 lacked comprehensive type definitions and editor autocompletion, it excelled in ease of use. However, as the application grew, Vue3 became less convenient. Despite gaining better type support, Vue3 still left some areas without proper type coverage, leaving me with mixed feelings.</p><h3>Choosing React and Next.js</h3><p>In the latter half of 2023, I explored several alternatives for the rewrite. While Svelte was part of the experimentation, it had a grate editor autocmopletion. However, it is similar to Vue.js, and I didn’t want to use something like Vue again. Ultimately, I decided to transition to React. The choice of Next.js was pragmatic — its stability and suitability for Electron applications played a significant role.</p><p>Additionally, whether it’s Next.js, React.js, or Remix, I wanted to stick with traditional SPA architecture and client-side rendering. Svelte was excluded due to its limitations in this regard.</p><h3>The Rewrite and Its Impact</h3><p>Almost all the code underwent a rewrite, resulting in a significantly lighter application. Most of the logic now resides on the frontend, minimizing the need for IPC (inter-process communication). While this means background tasks are nearly impossible, the lightweight nature of the app renders them unnecessary.</p><p>For styling, I’ve adopted Tailwind CSS, which works well. However, I discovered that not all components libraries are Tailwind-compatible, limiting my choices.</p><h3>Upcoming Implementations</h3><p>Several features remain to be implemented:</p><ol><li><strong>Search</strong>: Automatic completion for usernames, hashtags, and emojis.</li><li><strong>Proxy Configuration</strong>: Handling proxy settings.</li><li><strong>Follow Requests</strong>: Managing follow requests.</li></ol><p>In summary, Whalebird’s transition from Vue.js to React.js (specifically Next.js) has been an interesting journey. The decision-making process involved trade-offs, but overall, the app is now more streamlined and ready for further enhancements.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ce6c80fed83f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Fedistar 1.0.0 released]]></title>
            <link>https://medium.com/@h3poteto/fedistar-1-0-0-released-81a66d53eab4?source=rss-57e5809fd962------2</link>
            <guid isPermaLink="false">https://medium.com/p/81a66d53eab4</guid>
            <category><![CDATA[mastodon]]></category>
            <category><![CDATA[fediverse]]></category>
            <category><![CDATA[pleroma]]></category>
            <category><![CDATA[desktop-app]]></category>
            <dc:creator><![CDATA[h3poteto]]></dc:creator>
            <pubDate>Thu, 02 Mar 2023 16:40:59 GMT</pubDate>
            <atom:updated>2023-03-02T16:40:59.321Z</atom:updated>
            <content:encoded><![CDATA[<p>I’ve just released Fedistar version 1.0.0.</p><p>Fedistar is a multi-column Fediverse client app. Once, I introduced it.</p><p><a href="https://medium.com/@h3poteto/im-developing-a-new-fediverse-client-application-for-desktop-fcd5b83a7ebb">I’m developing a new Fediverse client application for desktop</a></p><p>It supports Windows, MacOS, and Linux. You can install it from the official Web site.</p><p><a href="https://fedistar.net/">Fedistar</a></p><p>Now, Fedistar is published only in AppStore, but I will try to publish it in</p><ul><li>Snapcraft</li><li>Homebrew</li><li>Microsoft Store</li><li>Winget</li></ul><p>Also, I would say that Fedistar is very light and fast.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7GFJPJzY6bJdmJC2NOT4RA.png" /></figure><h3>Features</h3><p>Basically, it has typical features as a multi-column client.</p><ul><li>Multiple accounts management</li><li>Streaming update</li><li>Desktop notifications</li><li>Reblog/Favourite/Bookmark/Emoji reaction/Edit statuses</li><li>Show status threads</li><li>Display link preview</li><li>Poll/Vote</li><li>Auto-complete username, hashtags, and emoji in compose tab</li><li>Show media: image, video, and audio</li></ul><p>And there is a unique feature.</p><ul><li><strong>See timelines without sign in</strong></li></ul><h3>Planned Features</h3><p>I created some issues in <a href="https://github.com/h3poteto/fedistar/issues">GitHub</a>. For example,</p><ul><li>Search accounts and hashtags</li><li>Change color theme</li><li>Tag timeline</li><li><strong>Reblog and Favourite from another account</strong></li><li>Specify post language</li><li>Jump to unread notifications when clicking the server</li></ul><p>I will implement these features in near future. Especially, I’m considering how to implement “Reblog and Favourite from another account”. Once, I’ve had a request like that, and I know it is a little bit tricky.</p><h3>Feature Requests and Bug Reports</h3><p>Please create issues in GitHub,</p><p><a href="https://github.com/h3poteto/fedistar">GitHub - h3poteto/fedistar: Multi-column Mastodon and Pleroma client for desktop</a></p><p>Or please send it from the contact form.</p><p><a href="https://fedistar.net/inquiry">Inqiury - Fedistar</a></p><p>I welcome your opinion. Feel free to send it. Or, you can reply to <a href="https://pleroma.io/users/h3poteto">@h3poteto@pleroma.io</a> .</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=81a66d53eab4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[I’m developing a new Fediverse client application for desktop]]></title>
            <link>https://medium.com/@h3poteto/im-developing-a-new-fediverse-client-application-for-desktop-fcd5b83a7ebb?source=rss-57e5809fd962------2</link>
            <guid isPermaLink="false">https://medium.com/p/fcd5b83a7ebb</guid>
            <category><![CDATA[mastodon]]></category>
            <category><![CDATA[pleroma]]></category>
            <category><![CDATA[fediverse]]></category>
            <category><![CDATA[tauri]]></category>
            <category><![CDATA[desktop-app]]></category>
            <dc:creator><![CDATA[h3poteto]]></dc:creator>
            <pubDate>Sun, 15 Jan 2023 14:10:25 GMT</pubDate>
            <atom:updated>2023-01-20T13:01:53.452Z</atom:updated>
            <content:encoded><![CDATA[<p>Once, I posted an article about the Mastodon client application:</p><p><a href="https://medium.com/@h3poteto/i-have-developed-a-mastodon-client-for-mac-windows-and-linux-c76b4b794e16">I have developed a Mastodon client for Mac, Windows, and Linux</a></p><p>Whalebird is a single-column Fediverse client application. It can display only one timeline on the screen.</p><p>Recently, I bought a big monitor (it is very good for programming), and after that, I’m not satisfied with Whalebird. Because I want to see more information, statuses, and timelines on the screen. So, I’m creating a new multi-column Fediverse client app.</p><h3>Fedistar</h3><p>The new app name is “Fedistar”. Before creating the new app, I considered how about implementing multi-column on Whalebird. However,</p><ol><li>I want to clean up the current code.</li><li>I don’t want to write Vue.js anymore.</li><li>In the first place, the architecture could be better for multi-column.</li></ol><p>So, I’m creating a new client app.</p><p>Here is a screenshot.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*C6J0eoYf4_iCR7hiYrE29Q.png" /><figcaption>Fedistar</figcaption></figure><p>And the repository is</p><p><a href="https://github.com/h3poteto/fedistar">GitHub - h3poteto/fedistar: Multi-column Mastodon and Pleroma client for desktop</a></p><p>This app has some characteristic features:</p><ol><li>You can add servers without authorization. In this case, you can see only Public and Local timelines, and you can’t reply, reblog, and favourite. You can only see the timeline.</li><li>You can choose which account to post from.</li><li>It supports Mastodon and Pleroma, at the moment. I’m wondering about Misskey, Friendica, and Pixelfed.</li><li>And some other features typical of multi-column clients.</li></ol><p>I’ve created issues that I want to implement before version 1.0.0. Except for these issues, I’m planning</p><ul><li>When you react to status, you can choose which account to do from…… Is it possible, I’m not sure.</li><li>Read notifications count from markers.</li><li>Jump to the notification when you receive a notification.</li><li>Icon</li></ul><h3>Development</h3><p>I used <a href="https://www.electronjs.org/">Electron</a> and Vue.js in Whalebird, but now I’m using <a href="https://tauri.app/">tauri</a> and Next.js. So, probably it is smaller and faster than Whalebird. I’ve never measured that memory and CPU usage, so I can’t say for sure.</p><p>Then tauri executes the main process with Rust, so I created the Rust version of <a href="https://github.com/h3poteto/megalodon">megalodon</a> which is a client library for Mastodon, Pleroma, and Misskey for JavaScript. I spent a month working on this……</p><p><a href="https://github.com/h3poteto/megalodon-rs">GitHub - h3poteto/megalodon-rs: Mastodon and Pleroma API client library for Rust</a></p><p>Of course, I also had the option to call all Fediverse API requests from the frontend. However, I didn’t choose it, because it is easier and performs better to execute WebSocket streaming in the main process. So, I’m using megalodon-rs in the main process and megalodon in the frontend.</p><p>Regarding the frontend, I’m using Next.js. There are two reasons for this</p><ol><li>I don’t want to write Vue.js anymore.</li><li>React.js has a richer library and ecosystem.</li></ol><p>For example, Virtual Scroll is important in SNS timelines, but it is also very important to maintain the scroll position simultaneously. Few libraries provide this in React.js, but there is no library in Vue.js.</p><h3>Under development</h3><p>This software is under development, so there is no stable version yet. I will implement breaking changes before 1.0.0, so if you want to develop this, please install it. That’s why it is not distributed in stores, for example, Mac App Store, and Snap Store. It will be stable when it reaches version 1.0.0, and at that time I will publish it in stores.</p><p>I’m not soliciting bug reports, because this software still has many unimplemented features. If you find an unexpected behavior, it is not a bug, it’s just unimplemented. Therefore, pull requests are welcome.</p><h3>Questions and Answers</h3><h4>Not satisfied with Sengi or TheDesk?</h4><p>No, not satisfied. I’d rather develop a client than just do Fediverse. In the first place, I started Fediverse because I couldn’t create a client on Twitter.</p><h4>Are there any differences between them?</h4><p>Who knows? It is not important for me, because the app is free and I’m not looking for sales. So there is no need to differentiate and spread this app.</p><h4>What are you going to do with Whalebird?</h4><p>Updates will be less frequent. However, I will continue to develop as long as you need single-column clients.</p><h4>When is the stable version released?</h4><p>I don’t know.</p><h4>Android or iOS version?</h4><p>No, I have no plan. In mobile, I think that multi-column is not suitable.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fcd5b83a7ebb" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Manage AWS Global Accelerator from Kubernetes resources]]></title>
            <link>https://medium.com/ovice/manage-aws-global-accelerator-from-kubernetes-resources-55133253d0a2?source=rss-57e5809fd962------2</link>
            <guid isPermaLink="false">https://medium.com/p/55133253d0a2</guid>
            <category><![CDATA[aws-global-accelerator]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[aws-load-balancer]]></category>
            <category><![CDATA[kubernetes-controller]]></category>
            <dc:creator><![CDATA[h3poteto]]></dc:creator>
            <pubDate>Mon, 02 May 2022 23:32:49 GMT</pubDate>
            <atom:updated>2022-05-02T23:32:49.490Z</atom:updated>
            <content:encoded><![CDATA[<p>Today, there are many ways to expose services on a Kubernetes cluster through Load Balancer on AWS. For example, the famous one is AWS Load Balancer Controller.</p><p><a href="https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.4/">Welcome</a></p><p>However, what should we do when you want to create a Global Accelerator in front of the Load Balancer? I have developed an OSS to solve it.</p><p><a href="https://github.com/h3poteto/aws-global-accelerator-controller">GitHub - h3poteto/aws-global-accelerator-controller: A Kubernetes controller for Global Accelerators and Route53</a></p><h3>How to use</h3><p>This controller can manage AWS Global Accelerator and Route53 records from Service and Ingress resources. And it detects annotations from Service and Ingress to control AWS resources, so you only need to add a few annotations.</p><ul><li>When you add aws-global-accelerator-controller.h3poteto.dev/global-accelerator-managed: &quot;yes&quot;as an annotation, a Global Accelerator will be created and the related LoadBalancer will be added to an Endpoint Group.</li><li>When you add aws-global-accelerator-controller.h3poteto.dev/route53-hostname: your.hostnameas an annotation, route53 records associated with the Global Accelerator will be created.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/980/1*Bk9AkU5lkLTsxh_3lXQJLQ.png" /></figure><h4>Use it with Service type Load Balancer</h4><p>Please create a Service type: LoadBalancer, and specify the above annotations.</p><pre>apiVersion: v1<br>kind: Service<br>metadata:<br>  annotations:<br>    aws-global-accelerator-controller.h3poteto.dev/global-accelerator-managed: &quot;yes&quot;<br>    aws-global-accelerator-controller.h3poteto.dev/route53-hostname: &quot;foo.h3poteto-test.dev&quot;<br>    service.beta.kubernetes.io/aws-load-balancer-backend-protocol: tcp<br>    service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: &quot;true&quot;<br>    service.beta.kubernetes.io/aws-load-balancer-type: nlb<br>  name: h3poteto-test<br>  namespace: default<br>spec:<br>  externalTrafficPolicy: Local<br>  ports:<br>    - name: http<br>      port: 80<br>      protocol: TCP<br>      targetPort: 80<br>    - name: https<br>      port: 443<br>      protocol: TCP<br>      targetPort: 443<br>  selector:<br>    app: h3poteto<br>  sessionAffinity: None<br>  type: LoadBalancer</pre><p>After you apply this resource,</p><ul><li>A Network Load Balancer will be created. You can confirm it in the Service.</li></ul><pre>status:<br>  loadBalancer:<br>  ingress:<br>    - hostname: your-lb-name.elb.ap-northeast-1.amazonaws.com</pre><ul><li>A Global Accelerator will be created, and the Load Balancer will be added as an Endpoint Group</li><li>foo.h3poteto-test.dev record will be created</li></ul><h4>Use it with AWS Load Balancer Controller</h4><p>AWS Load Balancer Controller has a plan to support Global Accelerator.</p><p><a href="https://github.com/kubernetes-sigs/aws-load-balancer-controller/issues/1572">[Feature Request] Global LoadBalancer support · Issue #1572 · kubernetes-sigs/aws-load-balancer-controller</a></p><p>But the controller I built is unrelated to this issue.</p><p>When you use Ingress, the way is very similar.</p><pre>apiVersion: networking.k8s.io/v1<br>kind: Ingress<br>metadata:<br>  name: h3poteto-test<br>  namespace: default<br>  annotations:<br>    aws-global-accelerator-controller.h3poteto.dev/global-accelerator-managed: &quot;yes&quot;<br>    aws-global-accelerator-controller.h3poteto.dev/route53-hostname: &quot;foo.h3poteto-test.dev,bar.h3poteto-test.dev&quot;<br>    alb.ingress.kubernetes.io/scheme: internet-facing<br>spec:<br>  ingressClassName: alb<br>  rules:<br>  - http:<br>      paths:<br>      - pathType: Prefix<br>        path: &quot;/&quot;<br>        backend:<br>          service:<br>            name: h3poteto-test<br>            port:<br>            number: 80</pre><p>You can specify multiple hostnames in the route53-hostname annotation.</p><h4>Use it with <a href="https://kubernetes.github.io/ingress-nginx/">ingress-nginx</a></h4><p>First, please add aws-global-accelerator-controller.h3poteto.dev/global-accelerator-managed annotation to the Service of <a href="https://kubernetes.github.io/ingress-nginx/">ingress-nginx</a>. If you are using helm, you can add it using the following command:</p><pre>$ helm install ingress-nginx ingress-nginx/ingress-nginx \<br>--set controller.service.annotations.&quot;aws-global-accelerator-controller\.h3poteto.dev/global-accelerator-managed&quot;=&quot;yes&quot;</pre><p>Next, please apply your ingress resource.</p><pre>apiVersion: networking.k8s.io/v1<br>kind: Ingress<br>metadata:<br>  name: h3poteto-test<br>  namespace: default<br>  annotations:<br>    kubernetes.io/ingress.class: nginx<br>    aws-global-accelerator-controller.h3poteto.dev/route53-hostname: &quot;foo.h3poteto-test.dev,bar.h3poteto-test.dev&quot;<br>spec:<br>  rules:<br>  - http:<br>      paths:<br>      - pathType: Prefix<br>        path: &quot;/&quot;<br>        backend:<br>          service:<br>            name: h3poteto-test<br>            port:<br>              number: 80</pre><h3>How it works</h3><p>This controller is monitoring Service and Ingress resources and checks annotations when it is changed. And it creates Global Accelerators if needed. However, this controller waits until the Load Balancer becomes active. After the Load Balancer becomes active, it will create a new Global Accelerator. If there is no related Load Balancer, it will not create any Global Accelerators even if you specify the annotation.</p><p>Furthermore, it creates Route53 records after Global Accelerator is created. Also at this time, if there is no Global Accelerator, it will not create any Route53 records even if you specify the annotation.</p><h4>How to detect the created Accelerator</h4><p>Of course, Kubernetes Service does not have a field to store created Global Accelerator name, so the controller can not save it to the Service resource.</p><p>The controller saves the Service or Ingress resource name in Global Accelerator’s tag. Based on the information, it is determined whether or not a Global Accelerator for that Service was created.</p><h4>How to detect the created Route53 record</h4><p>In route53, this controller adopted the same method as external-dns. That means it stores the Service or Ingress resource name in a TXT record.</p><h3>Future work</h3><p>I’ve created some issues like these.</p><ul><li><strong>Binding existing Global Accelerator</strong><br>This is similar to the Target Group Binding of AWS Load Balancer Controller. I want to add a Load Balancer as an EndpointGroup to the existing Global Accelerator.</li><li><strong>Support endpoint weight</strong><br>If the above feature is implemented, I want to specify endpoint weight for each Service or Ingress.</li></ul><p>If you have some requests, feel free to create issues or pull requests. I would be happy to review your PR.</p><h3>Epilogue</h3><p>I’m working at oVice as a part-time developer, and I developed this controller for oVice. Because oVice’s Kubernetes clusters are using ingress-nginx and we wanted to add Global Accelerator for our services.</p><p>However, oVice decided to stop using ingress-nginx and we will use ALB and TargetGroup Binding of AWS Load Balancer Controller. Our application needs to track client IP addresses, but Global Accelerator with NLB can not preserve client IP addresses. On the other hand, Global Accelerator with ALB can preserve it. So we decided to change the Load Balancer.</p><p>Generally, when using TargetGroup Binding, ALB and TargetGroup are managed by another method, for example, Terraform. In that case, Global Accelerator and Route53 records should also be managed by Terraform.</p><p>So, we create ALB, TargetGroup, Global Accelerator, and Route53 records with Terraform. As a result, unfortunately, we don’t need to use this controller in oVice……</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/980/1*89S_-0MvC99WHgkE2_j3ww.png" /></figure><p>That’s why this repository has become my hobby OSS. oVice will not support this, but I will continue to develop.</p><p>Thank you.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=55133253d0a2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ovice/manage-aws-global-accelerator-from-kubernetes-resources-55133253d0a2">Manage AWS Global Accelerator from Kubernetes resources</a> was originally published in <a href="https://medium.com/ovice">oVice</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Set up IAM Role for Service Account (IRSA) on kOps]]></title>
            <link>https://medium.com/@h3poteto/et-up-iam-role-for-service-account-irsa-on-kops-5db0751949c9?source=rss-57e5809fd962------2</link>
            <guid isPermaLink="false">https://medium.com/p/5db0751949c9</guid>
            <category><![CDATA[kops]]></category>
            <category><![CDATA[irsa]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[kubernetes]]></category>
            <dc:creator><![CDATA[h3poteto]]></dc:creator>
            <pubDate>Tue, 22 Mar 2022 14:20:54 GMT</pubDate>
            <atom:updated>2022-03-22T14:21:04.665Z</atom:updated>
            <content:encoded><![CDATA[<p>kOps introduced installing eks-pod-identity-webhook as an addon in version 1.23. This feature will help you set up IRSA on your clusters.</p><p><a href="https://github.com/kubernetes/kops/releases/tag/v1.23.0">Release v1.23.0 · kubernetes/kops</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uF6N6gnDZTVtQr-WN3kdgQ.png" /></figure><p>For some time kOps has supported IRSA.</p><p><a href="https://dev.to/olemarkus/irsa-support-for-kops-1doe">Using IAM Roles for ServiceAccounts on kOps</a></p><p>But before kOps 1.23, you need to install eks-pod-identity-webhook manually.</p><p><a href="https://github.com/aws/amazon-eks-pod-identity-webhook">GitHub - aws/amazon-eks-pod-identity-webhook: Amazon EKS Pod Identity Webhook</a></p><p>In kOps 1.23, you can specify spec.podIdentityWebhook.enabled to true , and kOps installs eks-pod-identity-webhook automatically.</p><h3>Pod Identity Webhook</h3><p>When you specify spec.podIdentityWebhook , you need to enable cert-manager. For example:</p><pre>spec:<br>  podIdentityWebhook:<br>    enabled: true<br>  certManager:<br>    enabled: true</pre><p>After creating a new cluster, eks-pod-identity-webhook pods are deployed in kube-system .</p><pre>$ kubectl get pods -n kube-system<br>NAME                                                                        READY   STATUS    RESTARTS        AGE<br>cert-manager-84955789dd-g2djl                                               1/1     Running   0               3m54s<br>cert-manager-cainjector-6475d566f7-jtx99                                    1/1     Running   0               3m54s<br>cert-manager-webhook-7b7b64cc86-t64dk                                       1/1     Running   0               3m54s</pre><pre>## Omission</pre><pre>pod-identity-webhook-565f794b7c-gffkd                                       1/1     Running   0               3m54s<br>pod-identity-webhook-565f794b7c-scrf8                                       1/1     Running   0               3m54s</pre><h3>In conclusion, these steps are needed</h3><ol><li>Create an S3 bucket (public readable)</li><li>Specify spec.serviceAccountIssuerDiscovery in your cluster spec</li><li>(Optional) Specify spec.iam.serviceAccountExternalPermissions</li><li>Specify spec.podIdentityWebhook</li><li>Create a cluster using kOps</li></ol><pre>spec:<br>  serviceAccountIssuerDiscovery:<br>    discoveryStore: s3://public-readable-s3-bucket-name<br>    enableAWSOIDCProvider: true<br>  iam:<br>    serviceAccountExternalPermissions:<br>      - name: my-sa<br>        namespace: my-ns<br>        aws:<br>          inlinePolicy: |-<br>            [<br>              {<br>                &quot;Effect&quot;: &quot;Allow&quot;,<br>                &quot;Action&quot;: &quot;ec2:Describe*&quot;,<br>                &quot;Resource&quot;: &quot;*&quot;<br>              }<br>            ]<br>  podIdentityWebhook:<br>    enabled: true<br>  certManager:<br>    enabled: true</pre><p>That’s it, IRSA is set up.</p><h3>How to use IRSA</h3><h4>Create ServiceAccount</h4><p>Create a Service Account that you specified in iam.serviceAccountExternalPermissions.</p><pre>apiVersion: v1<br>kind: ServiceAccount<br>metadata:<br>  annotations:<br>    eks.amazonaws.com/role-arn: arn:aws:iam::00000000:role/my-sa.my-ns.sa.cluster-name<br>  name: my-sa<br>  namespace: my-ns</pre><p>You can find eks.amazonaws.com/role-arn using this command.</p><pre>$ aws iam get-role --role-name my-sa.my-ns.sa.cluster-name<br>{<br>    &quot;Role&quot;: {<br>        &quot;Path&quot;: &quot;/&quot;,<br>        &quot;RoleName&quot;: &quot;my-sa.my-ns.sa.cluster-name&quot;,<br>        &quot;RoleId&quot;: &quot;***********&quot;,<br>        &quot;Arn&quot;: &quot;arn:aws:iam::00000000:role/my-sa.my-ns.sa.cluster-name&quot;,<br>        &quot;CreateDate&quot;: &quot;2022-03-17T12:25:46+00:00&quot;,<br>        &quot;AssumeRolePolicyDocument&quot;: {<br>            &quot;Version&quot;: &quot;2012-10-17&quot;,<br>            &quot;Statement&quot;: [<br>                {<br>                    &quot;Effect&quot;: &quot;Allow&quot;,<br>                    &quot;Principal&quot;: {<br>                        &quot;Federated&quot;: &quot;arn:aws:iam::00000000:oidc-provider/public-readable-s3-bucket-name.s3.us-east-1.amazonaws.com&quot;<br>                    },<br>                    &quot;Action&quot;: &quot;sts:AssumeRoleWithWebIdentity&quot;,<br>                    &quot;Condition&quot;: {<br>                        &quot;StringEquals&quot;: {<br>                            &quot;public-readable-s3-bucket-name.s3.us-east-1.amazonaws.com:sub&quot;: &quot;system:serviceaccount:my-ns:my-sa&quot;<br>                        }<br>                    }<br>                }<br>            ]<br>        },<br>    }<br>}</pre><p><strong>Note: </strong>If you don’t specify spec.iam.serviceAccountExternalPermissions , you need to create an IAM Role that allows sts:AssumeRoleWithWebIdentity .</p><p>This is a terraform example for the IAM Role.</p><pre>resource &quot;aws_iam_role&quot; &quot;my-sa&quot; {<br>  name = &quot;my-sa.my-ns.sa&quot;<br>  path = &quot;/&quot;<br><br>  inline_policy {<br>    name = &quot;my_inline_policy&quot;<br><br>    policy = jsonencode({<br>      Version = &quot;2012-10-17&quot;<br>      Statement = [<br>        {<br>          Action   = [&quot;ec2:Describe*&quot;]<br>          Effect   = &quot;Allow&quot;<br>          Resource = &quot;*&quot;<br>        },<br>      ]<br>    })<br>  }</pre><pre>  assume_role_policy = &lt;&lt;EOF<br>{<br>  &quot;Version&quot;: &quot;2012-10-17&quot;,<br>  &quot;Statement&quot;: [<br>    {<br>      &quot;Effect&quot;: &quot;Allow&quot;,<br>      &quot;Principal&quot;: {<br>        &quot;Federated&quot;: &quot;arn:aws:iam::00000000:oidc-provider/public-readable-s3-bucket-name.s3.us-east-1.amazonaws.com&quot;<br>      },<br>      &quot;Action&quot;: &quot;sts:AssumeRoleWithWebIdentity&quot;,<br>      &quot;Condition&quot;: {<br>        &quot;StringEquals&quot;: {<br>          &quot;public-readable-s3-bucket-name.s3.us-east-1.amazonaws.com:sub&quot;: &quot;system:serviceaccount:my-ns:my-sa&quot;<br>        }<br>      }<br>    }<br>  ]<br>}<br>EOF<br>}</pre><p>You can find Federated using this command.</p><pre>$ aws iam list-open-id-connect-providers<br>{<br>    &quot;OpenIDConnectProviderList&quot;: [<br>        {<br>            &quot;Arn&quot;: &quot;arn:aws:iam::00000000:oidc-provider/public-readable-s3-bucket-name.s3.us-east-1.amazonaws.com&quot;<br>        }<br>    ]<br>}</pre><h4>Check the behavior</h4><p>Create a job to confirm the behavior.</p><pre>apiVersion: batch/v1<br>kind: Job<br>metadata:<br>  name: awscli<br>  namespace: my-ns<br>spec:<br>  template:<br>    spec:<br>      restartPolicy: Never <br>      serviceAccountName: my-sa<br>      containers:<br>        - name: awscli<br>          image: amazon/aws-cli:latest<br>          args: [&quot;ec2&quot;, &quot;describe-instances&quot;, &quot;--region&quot;, &quot;ap-northeast-1&quot;]<br>  backoffLimit: 0</pre><p>Environment variables and Volumes are injected by eks-pod-identity-webhook.</p><pre>spec:<br>  containers:<br>  - args:<br>    - ec2<br>    - describe-instances<br>    - --region<br>    - ap-northeast-1<br>    env:<br>    - name: AWS_ROLE_ARN<br>      value: arn:aws:iam::564677439943:role/my-sa.my-ns.sa.playground.k8s.h3poteto.dev<br>    - name: AWS_WEB_IDENTITY_TOKEN_FILE<br>      value: /var/run/secrets/eks.amazonaws.com/serviceaccount/token<br>    image: amazon/aws-cli:latest<br>    imagePullPolicy: Always<br>    name: awscli<br>    # Omission<br>  volumes:<br>  - name: aws-iam-token<br>    projected:<br>      defaultMode: 420<br>      sources:<br>      - serviceAccountToken:<br>          audience: amazonaws.com<br>          expirationSeconds: 86400<br>          path: token</pre><p>And the commandaws ec2 describe-instances is succeeded in the pod 🎉</p><pre>$ kubectl logs -f awscli-xbksl -n my-ns<br>{<br>    &quot;Reservations&quot;: [<br>        {<br>            &quot;Groups&quot;: [],<br>            &quot;Instances&quot;: [<br>                {<br>                    &quot;AmiLaunchIndex&quot;: 0,<br>                    &quot;ImageId&quot;: &quot;ami-********&quot;,<br>                    &quot;InstanceId&quot;: &quot;i-0f4e3e20cf27d652c&quot;,<br>                    &quot;InstanceType&quot;: &quot;t3.medium&quot;,<br>                    &quot;KeyName&quot;: &quot;******&quot;,<br>                    &quot;LaunchTime&quot;: &quot;2022-03-11T15:25:05+00:00&quot;,<br>                    &quot;Monitoring&quot;: {<br>                        &quot;State&quot;: &quot;disabled&quot;<br>                    },<br>                    &quot;Placement&quot;: {<br>                        &quot;AvailabilityZone&quot;: &quot;ap-northeast-1c&quot;,<br>                        &quot;GroupName&quot;: &quot;&quot;,<br>                        &quot;Tenancy&quot;: &quot;default&quot;<br>                    },</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5db0751949c9" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>