| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 | <!DOCTYPE html><html lang="en-us"><head>	<meta charset="utf-8">	<meta name="generator" content="Hugo 0.92.0" />	<meta name="viewport" content="width=device-width, initial-scale=1">	<link rel="stylesheet" href="/assets/css/theme.css">	<link rel="alternate" href="/rss.xml" type="application/rss+xml" title="Pleasant Programmer">	<script type="text/javascript" src="//use.typekit.net/iwm5axp.js"></script>	<script type="text/javascript">try{Typekit.load();}catch(e){}</script>	<title>Pleasant Programmer</title></head><body>	<header id="header" role="banner">		<div id="thomas">			<img src="/assets/img/thomas.gif" alt="DJ THOMAS IN DA HAUS">			<img src="/assets/img/thomas.png" alt="Pleasant Programmer">		</div>		<h1 class="site-title"><a href="/">Pleasant Programmer</a></h1>		<nav id="menu" role="navigation">			<ul>				<li><a href="/pages/projects.html">projects</a></li>				<li><a href="/posts.html">archives</a></li>				<li><a href="/tags.html">tags</a></li>				<li><a href="/rss.xml">rss</a></li>			</ul>		</nav>	</header>	<div id="container"><main id="content" role="main"><div class="postindex">		<article class="h-entry post-text" itemscope itemtype="http://schema.org/Blog">		<header>			<h1 class="p-name entry-title" itemprop="headline">				<a href="/posts/ssh-git-repo-docker.html" class="u-url">SSH Access to Git Repository in Docker</a>			</h1>		</header>		<div class="e-content entry-content">			<p>With the likes of <a href="https://gogs.io/">Gogs</a> and <a href="https://gitea.io">Gitea</a>,self-hosting a personal git service has become quite common. It’s also notunlikely that the software is run via docker but this brings a problem withregards to SSH access.</p><p>Ideally, the git service container just exposes port 22 but this would conflictwith the host’s own SSH service. One solution would be to just use differentports for the git SSH and host SSH and that’s perfectly fine. But we can alsojust have the host SSH service forward the request to the git service itselfusing the <code>command</code> option in <code>authorized_keys</code>. And as we’ll find out later,the git service itself is using this functionality.</p><h2 id="how-git-over-ssh-works">How git over SSH works</h2><p>When you do a <code>git push</code>, what actually happens is it runs <code>git-send-pack</code> whichruns <code>git-receive-pack <directory></code> on the remote using SSH. The actualcommunication then just simply happens via stdin/stdout. Conversely, doing a<code>git pull</code> just runs <code>git-fetch-pack</code> and the somewhat confusingly named<code>git-upload-pack</code> on the remote.</p><p>So far so good, but did you notice that when cloning via SSH, the remote istypically <code>git@github.com:org/repo</code>? If everyone SSHs in as the <code>git</code> user, howdoes the git service know which user is which? And how does it prevent usersfrom accessing each other’s repositories?</p><p>One typically thinks of <code>authorized_keys</code> as just a list of allowed SSH keys butit can do <a href="https://linux.die.net/man/8/sshd">much more than that</a>. Of particularinterest is the <code>command</code> directive which gets run instead of the user suppliedcommand. The original command is passed in as an environment variable<code>SSH_ORIGINAL_COMMAND</code> which can be used to check if we allow it to be run ornot.</p><p>So with an <code>authorized_keys</code> file like so:</p><div class="highlight"><pre tabindex="0" style="color:#e5e5e5;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">command="verify-user user-1" ssh-rsa ...command="verify-user user-1" ssh-ed25519 ...command="verify-user user-2" ssh-rsa ...command="verify-user user-3" ssh-rsa ...</code></pre></div><p>Each key is tied to a particular user by virtue of <code>command</code>. And <code>verify-user</code>can check the <code>SSH_ORIGINAL_COMMAND</code> if the particular user is allowed access tothe particular repository. You can also implement additional restrictions likepull-only or push-only permissions with this setup. This is how both Gogs andGitea work.</p><h2 id="forwarding-git-ssh-to-docker">Forwarding git SSH to Docker</h2><p>When running Gogs on the host, it’s typically run as the <code>git</code> user and when anSSH key is added or removed, it simply rewrites <code>~git/.ssh/authorized_keys</code>.Thus it just works with the host SSH service without problems. When runninginside docker, one thing we can do is bind mount the host’s <code>~git/.ssh</code> folderinto the docker container so that the host can authorize the SSH connections.</p><p>The problem lies with the <code>command</code> which only exists inside the dockercontainer itself. So any user trying to connect can authenticate successfullybut will get a <code>command not found</code> error. For the Gogs docker image, the commandlooks like <code>/app/gogs/gogs serv key-1</code>. So we can just make <code>/app/gogs/gogs</code>available on the host and forward the command to the docker container.</p><p><a href="https://docs.gitea.io/en-us/install-with-docker/#ssh-container-passthrough">Most</a><a href="http://www.ateijelo.com/blog/2016/07/09/share-port-22-between-docker-gogs-ssh-and-local-system">instructions</a>I’ve seen with regards to this involves using <code>ssh</code> to connect to the internaldocker SSH service but this just seems overly complicated to me. If youremember, at it’s core git really only communicates over stdin/stdout and SSH isjust a means to get that.</p><p>If all we need is for our shim <code>/app/gogs/gogs</code> to be able to run a commandinside the docker container with stdin/stdout attached, then we can actuallyjust do that with <code>docker exec</code>. So it can be something like this:</p><div class="highlight"><pre tabindex="0" style="color:#e5e5e5;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell"><span style="color:#0f0;font-weight:bold">#!/usr/bin/env bash</span><span style="color:#0f0;font-weight:bold"></span><span style="color:#007f7f"># Requires the following in sudoers</span><span style="color:#007f7f"># git ALL=(ALL) NOPASSWD: /app/gogs/gogs</span><span style="color:#007f7f"># Defaults:git env_keep=SSH_ORIGINAL_COMMAND</span>GOGS_CONTAINER=git-gogs-1<span style="color:#fff;font-weight:bold">if</span> [[ $EUID -ne <span style="color:#ff0;font-weight:bold">0</span> ]]; <span style="color:#fff;font-weight:bold">then</span>  <span style="color:#fff;font-weight:bold">exec</span> sudo <span style="color:#0ff;font-weight:bold">"</span>$0<span style="color:#0ff;font-weight:bold">"</span> <span style="color:#0ff;font-weight:bold">"</span>$@<span style="color:#0ff;font-weight:bold">"</span><span style="color:#fff;font-weight:bold">fi</span><span style="color:#fff;font-weight:bold">if</span> [ <span style="color:#0ff;font-weight:bold">"</span>$1<span style="color:#0ff;font-weight:bold">"</span> != <span style="color:#0ff;font-weight:bold">"serv"</span> ]; <span style="color:#fff;font-weight:bold">then</span>  <span style="color:#fff;font-weight:bold">exit</span> <span style="color:#ff0;font-weight:bold">1</span><span style="color:#fff;font-weight:bold">fi</span><span style="color:#fff;font-weight:bold">exec</span> docker <span style="color:#fff;font-weight:bold">exec</span> -i -u git -e <span style="color:#0ff;font-weight:bold">"SSH_ORIGINAL_COMMAND=</span>$SSH_ORIGINAL_COMMAND<span style="color:#0ff;font-weight:bold">"</span> <span style="color:#0ff;font-weight:bold">"</span>$GOGS_CONTAINER<span style="color:#0ff;font-weight:bold">"</span> /app/gogs/gogs <span style="color:#0ff;font-weight:bold">"</span>$@<span style="color:#0ff;font-weight:bold">"</span></code></pre></div><p>So for git SSH access to Gogs running in docker, the necessary steps here are:</p><ol><li>Have a <code>git</code> user on the host</li><li>Bind mount <code>~git/.ssh</code> to <code>/data/git/.ssh</code> in the Gogs container</li><li>Add the shim script to <code>/app/gogs/gogs</code> (make sure it’s owned by root andis chmod-ed <code>0755</code>)</li><li>Add the listed sudoers rules</li></ol>		</div>		<small class="dateline">Posted: <time class="published dt-published" itemprop="datePublished" datetime="2022-01-23">2022-01-23</time></small>		| <small class="commentline"><a href="/posts/ssh-git-repo-docker.html#isso-thread">Comments</a></small>	</article>	</article>		<article class="h-entry post-text" itemscope itemtype="http://schema.org/Blog">		<header>			<h1 class="p-name entry-title" itemprop="headline">				<a href="/posts/android-multisim.html" class="u-url">Android Multisim Pre-5.1</a>			</h1>		</header>		<div class="e-content entry-content">			<p><strong>NOTE</strong> if you’re just looking for a library to use, there’s <a href="https://github.com/UseHover/MultiSim">MultiSim</a>. I’ve never used this so I can’t guarantee anything about it. It also only supports SIM information and not SMS.</p><p>Phones that can take multiple SIM cards are quite popular in the Philippines. The two major telecoms would have unlimited SMS packages for messages within their networks. It was quite common to have a SIM for each telco and use the appropriate one depending on who you were sending to.</p><p>Android’s API only officially supported multiple SIM cards in 5.1 (API level 22) but Android phones with dual-SIM (and even triple-SIM) capabilities were already available at least as far back as 2.3 (API level 10) when I first needed to support it. Since there was no official API for this, the manufacturers just invented their own and of course each one implemented it in a different way.</p><h2 id="mediatek">Mediatek</h2><p>The first phone we started working on was a <a href="https://www.gsmarena.com/lenovo_a60-4711.php">Lenovo A60</a> which used a Mediatek SOC. We somehow got a library from the manufacturer that let us use the dual-SIM functionality, but it was quite a pain to get working as there was limited documentation and we were quite new to Android development at the time.</p><p>When we disassembled the library that they gave us, we noticed that the names they used for the additional functions were quite interesting. They were all the <code>TelephonyManager</code> and <code>SmsManager</code> methods with a <code>Gemini</code> suffix and they would take an additional <code>int</code> parameter in addition to the original.</p><p>It turned out that these were available on the standard <code>TelephonyManager</code> instance and could be accessed via reflection. The <code>SmsManager</code> was a bit trickier but we ended up figuring out that there was a <code>android.telephony.gemini.GeminiSmsManager</code> class that had the functionality.</p><p>In a different phone with a Mediatek SOC, this got renamed to <code>com.mediatek.telephony.gemini.SmsManager</code> for some reason and dropped the <code>Gemini</code> suffix only for the <code>SmsManager</code>.</p><h2 id="intel">Intel</h2><p>It was also around this time that Intel started making SOCs for smartphones. We had an <a href="https://www.gsmarena.com/asus_fonepad_7_%282014%29-6394.php">ASUS Fonepad 7</a>. Unlike with the Mediatek device, we didn’t have a library to use here and had to use reflection to find the hidden classes / methods.</p><p>What we found was that instead of having a single instance with every method taking a <code>sim</code> parameter, they instead had separate instances of <code>TelephonyManager</code> and <code>SmsManager</code> for each SIM. You would call <code>TelephonyManager.get2ndTm()</code> and <code>SmsManager.get2ndSmsManager()</code> to have access to the 2nd SIM.</p><h2 id="qualcomm">Qualcomm</h2><p>The last phone I looked at was a <a href="https://www.gsmarena.com/motorola_moto_g_dual_sim-5978.php">dual-SIM Moto G</a>. What’s interesting about this one is that the API completely changed in the upgrade from 4.4 to 5.0.</p><p>On Android 4.4, the API was pretty close to the Mediatek one. You had a single instance that could dispatch to other SIMs by having an extra parameter on all the methods. These were in <code>android.telephony.MSimTelephonyManager</code> and <code>android.telephony.MSimSmsManager</code>.</p><p>On Android 5.0, the API was a weird mix of all the above and also the introduction of <code>android.telephony.SubscriptionManager</code> which was quite close but not exactly the same as what ended up in the official API. Instead of <code>getActiveSubscriptionInfoList</code> there was <code>getActiveSubIdList</code> which only returned <code>long[]</code>.</p><p>For the information that would normally exist in <code>SubscriptionInfo</code>, you had to query the main <code>TelephonyManager</code> instance which had methods with an extra <code>long</code> parameter for the subscription id. The <code>SmsManager</code> was simpler with just <code>getSmsManagerForSubscriber</code>.</p><p>With Android 5.1, I assume they just switched to using the official API so this phone would have gone through 3 different multi-SIM APIs over the course of it’s life.</p><h2 id="epilogue">Epilogue</h2><p>Around the release of Android 5.1, we stopped work on the app so I never actually got to use the official API myself ironically. We also never really got a big deployment so while I saw quite the variety of multi-SIM implementations, that’s probably not all that’s been out in the wild.</p>		</div>		<small class="dateline">Posted: <time class="published dt-published" itemprop="datePublished" datetime="2021-09-12">2021-09-12</time></small>		| <small class="commentline"><a href="/posts/android-multisim.html#isso-thread">Comments</a></small>	</article>	</article>		<article class="h-entry post-text" itemscope itemtype="http://schema.org/Blog">		<header>			<h1 class="p-name entry-title" itemprop="headline">				<a href="/posts/isp-issues.html" class="u-url">ISP Issues</a>			</h1>		</header>		<div class="e-content entry-content">			<p>At the first office I worked at, we had 2 different ISPs. This was supposed to be for reliability, as one was fast but spotty, and the other was slow but reliable. Since they weren’t <em>too</em> expensive, we just went and got both.</p><p>We have monitoring setup to watch our office IPs from the outside so we could see how often the connection goes down. The interesting thing we found was that the fast and spotty connection had perfect uptime. Even when there was clearly no internet from the office, it was still “up” according to our monitoring.</p><p>So we tried pinging our office IP using the other connection and to our surprise it was indeed up. There was even a webserver running on it (we only have VPN exposed). Apparently, it was someone elses CCTV admin page. We could actually see a hallway with people walking by sometimes!</p><p>Apparently someone else had our IP address and nothing good comes from an IP conflict. This was completely baffling as our internet line was supposed to be a “business line” and that came with a static IP address. So the only scenarios where this could happen is, the ISP mistakenly gave the same IP to 2 different lines or the ISP allows some clients to freely set their own IP.</p><p>We complained to the ISP and eventually got it resolved. They just gave us an entirely new IP address, but they never explained what went wrong. We already had quite a negative opinion of that particular ISP though, and they somehow managed to outdo themselves.</p>		</div>		<small class="dateline">Posted: <time class="published dt-published" itemprop="datePublished" datetime="2018-08-16">2018-08-16</time></small>		| <small class="commentline"><a href="/posts/isp-issues.html#isso-thread">Comments</a></small>	</article>	</article>		<article class="h-entry post-text" itemscope itemtype="http://schema.org/Blog">		<header>			<h1 class="p-name entry-title" itemprop="headline">				<a href="/posts/audventure.html" class="u-url">Audventure</a>			</h1>		</header>		<div class="e-content entry-content">			<p>Sometime around 2013 I wrote a clone of the GBA game <a href="https://www.nintendo.co.jp/n08/bit_g/">bit GenerationsSoundVoyager</a> called<a href="https://audventure.pleasantprogrammer.com">audventure</a>. SoundVoyager isactually a collection of mini-games where sound is the main focus. You canactually play the game blind, and at some point, that’s pretty much whathappens.</p><h2 id="sound-catcher">sound catcher</h2><p>The signature mini-game in SoundVoyager is sound catcher. In the mini-game, youcan only move left and right at the bottom of the stage, while a “sound” fallsfrom the top. Your goal is to catch the sound which is signified by a green dot.When you catch it, the sound or beat becomes part of the BGM and a new dotappears with a different sound.</p><p>You can of course use your eyes and move accordingly, but if you put onearphones, you can actually hear where the dot is, either on your left or right,with it getting louder as it gets close to you. As you collect more sounds, thedot gets more and more transparent. Eventually (and this is where it gets fun),you won’t be able to see the sounds anymore and will have to rely mostly on yourears.</p><p>You can see what the original game looks like in <a href="https://www.youtube.com/watch?v=C12WRgfIOC8">thisvideo</a> or you can play it under<em>sound safari</em> in <a href="https://audventure.pleasantprogrammer.com">audventure</a>.</p><h2 id="webaudio-vs-flash">WebAudio vs Flash</h2><p>At the time I wrote audventure, only Chrome supported WebAudio. Also, the APIlooked (and still looks) quite complicated. Flash on the other hand, wasstarting to die, but still well-supported so I went with that. For the mostpart, it worked okay though Chrome actually had timing issues when playingsounds. Now, it doesn’t work in any browser. I tried to debug the issues butultimately ended up just rewriting it to use WebAudio instead.</p><p>For the game, I needed to simulate the source of the sound in 2D/3D space. Flashonly really gives you stereo panning and volume control. With some maths, we canactually get an acceptable solution. Less importantly, I needed to be able toget frequency data of the currently playing “sound” to pulse the background. Forthis, I actually had to implement the feature in the Flash library I was using.</p><p>With WebAudio, spatial audio is already built-in and you can simply give it thecoordinates of the sounds and the listener. There are some other options totweak, but for the most part, no complex math is needed. Getting frequency datafor a sound is also actually built-in and didn’t take too long to integrate.</p><p>Overall, I was impressed by how much you can do with WebAudio out-of-the-box. Ikind of understand why it’s complicated, but there’s some simple functionalitythat I wish was included. For example, there is no API to pause and then resumeplaying an audio buffer. You have to manually save the elapsed time and playfrom there.</p><h2 id="other-mini-games">Other mini-games</h2><p>So far I’ve only actually implemented the sound catcher mini-game. There arearound 4 different categories with slight variations in between.</p><h3 id="sound-catcher--sound-slalom">sound catcher / sound slalom</h3><p>I’ve explained sound catcher a while ago; sound slalom is a minor variation onthat. Instead of waiting for the “sound” to reach you, you now have to guideyourself in between 2 “poles” of sound, as in <a href="https://en.wikipedia.org/wiki/Slalom_skiing">slalomskiing</a>. But this time, you canalso accelerate forward. The goal is to finish the course before the time runsout.</p><h3 id="sound-drive--sound-chase">sound drive / sound chase</h3><p>In sound drive, you’re driving against the flow on a 5 lane road. You have toavoid oncoming cars, trucks and animals until you reach the end. You’re allowedto change lanes and accelerate, and the game tracks your best time. Sound chaseis pretty much the same, except you’re trying to catch up to a “sound”.</p><h3 id="sound-cannon">sound cannon</h3><p>In sound cannon, you’re immobile but can rotate within a 180 degree angle. Yourgoal is too shoot down “sounds” which are heading your way. If a sound reachesyou, it’s game over. You win when you kill all the sounds.</p><h3 id="sound-picker--sound-cock">sound picker / sound cock</h3><p>In sound picker, you can move in a giant square field where various sounds arescattered around. Your goal is to pick up all the sounds within the time limit.Sound cock is similar, except the sounds are chickens and you have to chase themaround.</p><h2 id="source-code">Source Code</h2><p>If you want to see the source code, you can check it out<a href="https://git.pleasantprogrammer.com/games/audventure">here</a>. The sound filesaren’t in the repo though, since I’m not quite sure about the licensing. If youwant to contribute music or sound effects, I’d gladly appreciate it.</p>		</div>		<small class="dateline">Posted: <time class="published dt-published" itemprop="datePublished" datetime="2017-11-19">2017-11-19</time></small>		| <small class="commentline"><a href="/posts/audventure.html#isso-thread">Comments</a></small>	</article>	</article>	</div><nav class="postindexpager">	<ul class="pager clearfix">						<li class="next">			<a href="/page/2.html">Older posts →</a>		</li>			</ul></nav></main>	<footer id="footer" role="contentinfo">		<p>		<a rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/deed.en_US">			<img alt="CC-BY-SA" style="border-width:0" src="https://licensebuttons.net/l/by-sa/3.0/80x15.png">		</a> © 2022 Thomas Dy - Powered by <a href="http://gohugo.io">Hugo</a></p>	</footer></div><script src="/assets/js/konami.js"></script><script>var easter_egg = new Konami();easter_egg.code = function() {	var el = document.getElementById('thomas');	if(el.className == "whoa") {		el.className = "";	}	else {		el.className = "whoa";	}	document.body.scrollTop = document.documentElement.scrollTop = 0;}easter_egg.load();</script><script	data-isso="https://isso.pleasantprogrammer.com/"	src="https://isso.pleasantprogrammer.com/js/count.min.js"></script></body></html>
 |