Jekyll2020-11-12T13:40:57+00:00http://burakkarakan.com/blog/feed.xmlpsthis is my personal space and i usually have no idea wtf i'm talking aboutBurak KarakanPi-hole on Raspberry Pi using Docker and Docker Compose2020-10-12T00:00:00+00:002020-10-12T00:00:00+00:00http://burakkarakan.com/blog/pihole-on-raspberry-using-pi-docker-and-docker-compose<p>I need to start this article with some simple disclaimers: I love Raspberry Pi, I love Docker, I don’t love networking that much (spoiler alert: I suck at it).</p>
<ul>
<li>I love Raspberry Pi because it is a tiny, fully functioning computer that gives me goosebumps. It is one of those things that makes you feel like <a href="https://en.wikipedia.org/wiki/Mr._Robot">Mr. Robot</a>. It is relatively cheap, it is accessible, and there are tons of guides online to do pretty much anything you can imagine.</li>
<li>I love Docker because it is a simple way of running various pieces of software in a standardized way: you pull the Docker image for your platform, you run the image with a single command and that’s it! You can glue things together, you can add your own images, you can share your configuration, you can run the same setup on different machines, and you can destroy things easily once you don’t need them anymore. I am not saying it is the simplest software ever, but it is relatively easy to play around with.</li>
<li>I don’t love networking much, simply because I suck at it. I have a basic understanding of high-level concepts about many parts of it, but they don’t always translate to how things work in real life. I roughly know how computers communicate over a network, but I quickly get lost when I need to debug a bad connection for example. The good thing is that it means I’ll aim to keep this guide as simple as possible so that I can understand it as well.</li>
</ul>
<p>So, since we are done with the disclaimers, let’s touch on the basics a bit before we get on with the guide. If you know all the tools and technologies mentioned above, feel free to skip that part.</p>
<h1 id="basics">Basics</h1>
<p>Since we’ll need to get a bit technical in the article, there are a couple of things we need to clarify so that there will be less confusion moving forward. I’ll try to be brief here, and add some reading material in case you’d like to learn more.</p>
<h2 id="what-is-raspberry-pi">What is Raspberry Pi?</h2>
<p>Raspberry Pi is a simple, single-board computer that is originally developed for educational purposes; however, the board has become widely popular among makers and has been very popular for many use-cases including robotics, home automation, and IoT. The first one being launched in February 2012, the Raspberry Pi has 4 generations as of today, the latest one being the most advanced one including a Quad-core ARM processor and up to 8GB RAM. The latest version of it starts from $35 and goes up to $75; not super cheap, but a good price for a general-purpose computer.</p>
<p>Think of Raspberry Pi as a simple desktop computer without any screens or peripherals attached. You can connect screens to it, you can connect your keyboard, mouse, ethernet, and use it as a regular computer. There are tons of use-cases that don’t need these peripheral devices, therefore it is common to see Raspberry Pi devices being used inside handheld devices, or hidden in an office space as a network device, or whatever. It is a general-purpose computer, and your imagination is the limit here.</p>
<p>The device looks like this:</p>
<p><img src="/blog/assets/images/pihole-docker/raspberry-pi.png" alt="Raspberry Pi 4, [from the official Raspberry Pi website](https://www.raspberrypi.org/products/raspberry-pi-4-model-b/)" class="center-image" /></p>
<div style="text-align: center;">
<small><em>Raspberry Pi 4, <a href="https://www.raspberrypi.org/products/raspberry-pi-4-model-b/">from the official Raspberry Pi website</a></em></small>
</div>
<p>They also have an even-cheaper and smaller version of the same family, Raspberry Pi Zero W, which has fewer resources than the regular Pi, but it is even smaller than the regular ones, making it suitable for IoT applications and mobile use-cases. The current selling price for the Zero W is <a href="https://www.raspberrypi.org/products/raspberry-pi-zero-w/">$10</a>.</p>
<p><img src="/blog/assets/images/pihole-docker/raspberry-pi-zero.jpg" alt="Raspberry Pi Zero, [from raspberrypi-spy.co.uk](https://www.raspberrypi-spy.co.uk/2015/11/introducing-the-raspberry-pi-zero/)" class="center-image" /></p>
<div style="text-align: center;">
<small><em>Raspberry Pi Zero, <a href="https://www.raspberrypi-spy.co.uk/2015/11/introducing-the-raspberry-pi-zero/">from raspberrypi-spy.co.uk</a></em></small>
</div>
<p>All the Raspberry Pi devices are capable of running various operating systems (OS) depending on the specific model you have; however, the most common operating system for Raspberry Pi is <a href="https://www.raspberrypi.org/downloads/raspberry-pi-os/">Raspberry Pi OS</a>, Raspbian with its old name. It is based on Debian, has a bunch of simple installers, and it is a good starting point for tinkering with the Pi. I strongly recommend going with the Pi OS if you are just getting started with the ecosystem, it’ll definitely simplify your journey in the beginning in terms of finding documentation and help online.</p>
<p>For learning more about the Raspberry Pi, head over to the official <a href="https://www.raspberrypi.org/">Raspberry Pi website</a>.</p>
<h2 id="what-is-pi-hole">What is Pi-hole?</h2>
<p><a href="https://pi-hole.net/">Pi-hole</a> is a plug-and-play software that offers network-wide <a href="https://en.wikipedia.org/wiki/DNS_sinkhole">DNS sinkhole</a> for filtering out content for all the devices connected to the same network. In simple terms: when your browser tries to connect a server to show you some content on a website, Pi-hole will resolve the IP address for that host into a blackhole IP address if it is on a blocklist, meaning that your computer will not reach the ad server, and as a result, you won’t see ads. This has a bunch of benefits for the end-user:</p>
<ul>
<li>There is no need to install specific software to any of the devices connected to the network, and all of your devices can benefit from this, including your smart TV and mobile devices.</li>
<li>This allows blocking not only the traditional ads on websites but also the in-app ads that are embedded in other places, such as the operating system of your smart TV.</li>
<li>Since the request for the ad content will never leave your network, nothing will be downloaded, and your network performance will improve.</li>
<li>It also blocks some trackers, which means it automatically provides better privacy while you are surfing.</li>
</ul>
<p>All in all, Pi-hole is a neat piece of open-source software that gives you better visibility and control into the ad traffic that is happening in your network. For more details, go ahead and visit their <a href="https://pi-hole.net/">website</a>, as well as their <a href="https://github.com/pi-hole">GitHub organization</a> for checking the source code and learning more about the project.</p>
<h2 id="what-is-docker">What is Docker?</h2>
<p>The poster-child for the cloud-native era, Docker has been a very popular software in the last couple of years. It is essentially a nicely packaged system that simplifies managing containers on many different operating systems, and it is the de-facto standard engine for running containers. It allows you to package your application and its dependencies in a simple format and share them. You can head over to the following link to learn more about Docker (spoiler alert: I wrote the article):
<a href="https://medium.com/swlh/what-exactly-is-docker-1dd62e1fde38"><strong>What Exactly is Docker?</strong></a></p>
<h1 id="goals">Goals</h1>
<p>So, I wanted to set up Pi-hole on my home network, and I had a <a href="https://www.raspberrypi.org/products/raspberry-pi-3-model-b-plus/?resellerType=home">Raspberry Pi 3 Model B+</a> lying around. I had a couple of goals before I started the setup:</p>
<ul>
<li>I wanted to be able to manage the device remotely; meaning that all I need to change things there should be a working network connection to the device, and since I’ll start with my home network, that’ll be a given anyway. I don’t want to depend on keyboards, screens, or other peripherals to be able to play with it.</li>
<li>I wanted to utilize the same Pi for my other use-cases such as home automation; therefore, I wanted to keep the Pi installation as clean as possible, in case I’d need to rebuild the same setup using a different device, or if I need to do a clean install on another storage device.</li>
<li>I wanted to be able to keep my setup in a Git repo in order to be able to keep track of my changes and have a backup, because, why not?</li>
<li>I wanted the setup to be easy to reproduce in other devices and networks so that I can set it up for my family and friends as well.</li>
<li>I wanted to be able to extend my setup with other use-cases, hopefully with some sort of automation to deploy my changes to the Pi. I can always connect to the Pi and install whatever I need manually, but this would contradict my previous goal to make the setup easy to reproduce.</li>
</ul>
<p>For some of you, these goals might be irrelevant, and that’s totally fine. I just wanted to aim for these and learn to try to achieve them.</p>
<p>In the end, I decided to go for a simple Pi OS Lite setup with Docker & Docker Compose to manage Pi. The reason I picked the Lite OS is that I didn’t need a desktop environment and the other software that comes with the default Raspberry Pi OS, such as games or office software. The reason I decided on Docker is that I wanted to be able to run everything as containers on the device to not to depend on manual installation and the dependency hell, and Docker Compose is to be able to define all the things I’ll run in a simple YAML format that I can keep in the version control. In addition, relying on Docker from the beginning enables me for future adventures in case I want to go there, such as <a href="https://magpi.raspberrypi.org/articles/build-a-raspberry-pi-cluster-computer">building clusters</a> or <a href="https://ubuntu.com/tutorials/how-to-kubernetes-cluster-on-raspberry-pi#1-overview">running Kubernetes on Raspberry Pis.</a> Of course, these are not requirements, just potential ideas for my amusement.</p>
<p>As I have mentioned before, this doesn’t mean that you have to run this very same setup for your installation; it just happened to be the one I chose. The rest of the article will be about getting this configuration up and running, so, follow along if you are still interested.</p>
<h1 id="requirements">Requirements</h1>
<p>Our requirements for the project is relatively simple:</p>
<ul>
<li>A primary computer to manage the whole installation in your network.</li>
<li>A working internet connection.</li>
<li>A router with ethernet ports. You can also use the built-in Wi-Fi some models have, although it will perform better if you use a cable connection.</li>
<li>A Raspberry Pi, I’d imagine any model would do the job here.</li>
<li>A MicroSD card for installing the operating system. If you already have an installed one, that’s also fine, it shouldn’t matter much which OS you have.</li>
</ul>
<p>The rest of the article will assume that you meet these requirements on your part.</p>
<p>The steps we’ll take are:</p>
<ul>
<li>Setup the SD-card for booting the device</li>
<li>Connect the Pi to your router, and access the internet</li>
<li>Install Docker</li>
<li>Run Pi-hole using Docker & Docker Compose</li>
<li>Replace your router’s DHCP server with the Pi-hole DHCP server</li>
<li>That’s it!</li>
</ul>
<p>Let’s get started.</p>
<h2 id="before-you-go-on">Before you go on</h2>
<p>One thing to keep in mind is: Pi-hole cannot remove all the ads from all the websites. Blocking ads is simply a cat-mouse game, and Pi-hole is trying to disable them on the DNS level, meaning that you should still keep your blocker extensions on your browser for a good experience. Pi-hole will definitely contribute to your overall experience but do not get pissed off if it doesn’t remove all the ads, some ads are practically impossible to get rid of without significant effort.</p>
<p>If you are looking for a blocker extension, I recommend the open-source <a href="https://github.com/gorhill/uBlock">uBlock Origin</a>: here’s for <a href="https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm?hl=en">Google Chrome</a> and here’s for <a href="https://addons.mozilla.org/en-US/firefox/addon/ublock-origin/">Mozilla Firefox</a>.</p>
<h1 id="step-1-setup-the-sd-card">Step 1. Setup the SD-card</h1>
<p>You can skip this section if you already have a Raspberry Pi that has SSH enabled somehow. Stay here if you’d like to start by installing the Raspberry Pi OS on an SD-card from scratch.</p>
<p>Our Raspberry Pi needs an operating system to function, to begin with. If you don’t have much of an idea about which operating system to pick, or if you don’t have a preference, you are probably better off with the official operating system of the Raspberry Pi, which is <a href="https://www.raspberrypi.org/downloads/raspberry-pi-os/">Raspberry Pi OS</a>. This operating system is officially supported by the <a href="https://www.raspberrypi.org/about/">Raspberry Pi Foundation</a> and would be the simplest default to get started with in terms of online support.</p>
<p><img src="/blog/assets/images/pihole-docker/pi-os.jpg" alt="[from Tom’s Hardware](https://www.tomshardware.com/news/raspberry-pi-os-no-longer-raspbian)" class="center-image" /></p>
<div style="text-align: center;">
<small><em><a href="https://www.tomshardware.com/news/raspberry-pi-os-no-longer-raspbian">from Tom’s Hardware</a></em></small>
</div>
<p>For my specific use-case, I’ll be using the Raspberry Pi OS Lite version, which is the same operating system without the desktop graphical user interface (GUI). I don’t need a GUI because I will not connect my Pi to a monitor or anything, and I won’t be using it as my primary computer; therefore, I don’t need a user interface. This also has the additional benefits of less software included with the operating system and a much smaller OS size, 435MB for the Lite version vs 1133MB for the desktop version for the August 2020** **version.</p>
<h2 id="writing-the-base-image">Writing the base image</h2>
<p>The easiest way to write an OS image to an SD card is the <a href="https://www.raspberrypi.org/downloads/">Raspberry Pi Imager</a> tool, which is a simple tool that works on all major operating systems and allows creating an SD card with one of the included images.</p>
<p>Once you plug your SD card into your computer, you can simply click the “Choose OS” button and pick the Lite version in the popup window under the path “Raspberry Pi OS (other) > Raspberry Pi OS Lite (32-bit)”. After that, you can click the “Choose SD Card” button and pick your SD card from the list. Once you are done, simply click on the “Write” button and wait until the process completes.</p>
<p>Once you are done with this, do not unplug your SD card from your computer, because there is one last step before our OS setup is ready.</p>
<h2 id="enabling-ssh">Enabling SSH</h2>
<p>In order to be able to reach our Raspberry Pi from our primary computer, we need to have SSH access to the Pi. By default, Raspberry Pi OS disables the SSH access for security reasons; therefore, we need to enable it. <strong>Be careful: we are enabling SSH access because we are going to be using our Raspberry only in our internal network; if you are thinking of setting this up for your office space or a public network, you need to think about securing the SSH connection, especially getting rid of the password and using public-private key authentication.</strong></p>
<p>Enabling the SSH connection is very simple: open your SD card contents on your computer, and create a new file called <code class="language-plaintext highlighter-rouge">ssh</code> there. The file doesn’t need to have anything written into it, and make sure there is no extension in the file name, like <code class="language-plaintext highlighter-rouge">.txt</code> or anything: just name it as <code class="language-plaintext highlighter-rouge">ssh</code>. The first time the OS boots, it’ll check the existence of this file, and enable SSH access on your Raspberry.</p>
<p>Once this is done, your operating system is ready to be used.</p>
<h1 id="step-2-add-raspberry-pi-to-your-network">Step 2. Add Raspberry Pi to your network</h1>
<p>In order to be able to reach your Pi from your primary computer, the Pi needs to be in the same network as your computer: simply put, just plug an ethernet cable between your router and the Raspberry Pi. After this, you can simply plug your Raspberry Pi into your power adapter, and let it boot. Raspberry is usually quick to boot, but give it a few minutes just to make sure it booted.</p>
<p>At this point, you should be ready to connect to your Raspberry via SSH. Go to your primary computer’s terminal and type:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh pi@raspberrypi.local
</code></pre></div></div>
<p>In case you are prompted with a password for this connection, the default password is <code class="language-plaintext highlighter-rouge">raspberry</code>.</p>
<p>If you are on a Windows machine, it might not be that simple to run SSH through the default Windows terminal, so I suggest using <a href="https://www.putty.org/">PuTTY</a> for this use-case. The ways to connect your Raspberry Pi through SSH for different operating systems are also <a href="https://www.raspberrypi.org/documentation/remote-access/ssh/">covered in the official docs</a>, so make sure to go through them if you are unable to SSH into your Pi. This is also the point where you should figure out what terminal setup to use on your primary computer because the rest of the guide will assume that you are able to run commands like docker ps on your primary computer’s terminal. I am using macOS, but the process should be similar in any other common terminal.</p>
<p>Once you are successfully connected, you should see something like the following:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Linux raspberrypi 5.4.51-v7+ #1333 SMP Mon Aug 10 16:45:19 BST 2020 armv7l
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Mon Oct 26 00:59:18 2020 from 1.2.3.4
pi@raspberrypi:~ $
</code></pre></div></div>
<p>If you see the <code class="language-plaintext highlighter-rouge">pi@raspberrypi:~ $</code> prompt, it means you are connected already, which is great news.</p>
<h2 id="changing-the-default-ssh-password">Changing the default SSH password</h2>
<p>By default, SSH access is enabled for the user pi with the password raspberry, which is not a very safe default to keep on, therefore let’s go ahead and change the password.</p>
<p>To change the password, simply type <code class="language-plaintext highlighter-rouge">passwd</code> and hit the Enter key. This should show you a simple form to change your password. <strong>Make sure to set a strong password.</strong></p>
<h2 id="set-a-static-ip-on-your-raspberry-pi">Set a static IP on your Raspberry Pi</h2>
<p>IP addresses will constantly change in any network, and for the rest of our guide and the Pi-hole use-case, it is important to have a static IP set for our Raspberry Pi so that even if it disconnects, the IP address won’t change. TO do that, we need to run a couple of simple commands, gather the outputs, and write them to another file in the end.</p>
<p>Let’s start with the router IP:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ip r | grep default
</code></pre></div></div>
<p>The output will look like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>default via 192.168.0.1 dev eth0 src 192.168.0.2 metric 202
</code></pre></div></div>
<p>The first IP address, <code class="language-plaintext highlighter-rouge">192.168.0.1</code> in my case, is the IP address of our router. <strong>Take note of this value, you’ll need it later.</strong></p>
<p>Next, run the following command to get your current private IP:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ifconfig -a | grep -A 1 eth0 | grep inet
</code></pre></div></div>
<p>This will output something like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>inet 192.168.0.2 netmask 255.255.255.0 broadcast 192.168.0.255
</code></pre></div></div>
<p>The IP address right next to the <code class="language-plaintext highlighter-rouge">inet</code> one, <code class="language-plaintext highlighter-rouge">192.168.0.2</code> in my case, is your private IP. <strong>Take a note of this value too.</strong></p>
<p>Now the last step, run the following command:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo nano /etc/dhcpcd.conf
</code></pre></div></div>
<p>This will open up the editor for the configuration file, scroll to the bottom of the file and add the following lines:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>interface eth0
static ip_address=<YOUR_PRIVATE_IP_ADDRESS_HERE>
static routers=<YOUR_ROUTER_IP_HERE>
</code></pre></div></div>
<p>In my case, these lines look as follows:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>interface eth0
static ip_address=192.168.0.2
static routers=192.168.0.1
</code></pre></div></div>
<p>That’s it, you did it! At this point, you need to reboot your Raspberry Pi with a <code class="language-plaintext highlighter-rouge">sudo reboot</code> command for the changes to take effect; however, the following section will also require you to do that as well, so if you are gonna follow along with the guide, then you can keep going and restart later when Docker installation requires it.</p>
<h1 id="step-3-install-docker">Step 3. Install Docker</h1>
<p>Before we even install Docker on the Raspberry Pi, you need to make sure that you have Docker installed in your primary machine, so that we can run the Docker CLI commands from our terminal with the Raspberry as the remote Docker host, meaning that we don’t need to copy files over and stay connected to the Raspberry to modify our containers there. Docker is available for all the major operating systems, please <a href="https://docs.docker.com/get-docker/">install it</a> for your primary computer if you haven’t already.</p>
<p>In order to install Docker on your Raspberry Pi, start with connecting via SSH if you haven’t already:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh pi@raspberrypi.local
</code></pre></div></div>
<p>Once you are in, let’s start by updating our packages:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt-get update && apt-get upgrade
</code></pre></div></div>
<p>After your upgrade has finished, run the following command:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -sSL https://get.docker.com | sh
</code></pre></div></div>
<p>Well, in general, it is not a good practice to pipe <code class="language-plaintext highlighter-rouge">curl</code> output to <code class="language-plaintext highlighter-rouge">sh</code> directly, since this opens up basically a pool of endless remote code execution possibilities, and you should think about why this should be avoided. At the same time, I couldn’t find a safer way of installing Docker on Raspberry Pi OS which is also easy for beginners, so I decided to settle with this option. If you have a suggestion to improve this from the security perspective, please drop some comments and I’ll include them here.</p>
<p>After Docker is installed, you’ll need to add the <code class="language-plaintext highlighter-rouge">pi</code> user to the required group to be able to run <code class="language-plaintext highlighter-rouge">docker</code> commands:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo usermod -aG docker pi
</code></pre></div></div>
<p>At this point, you need to reboot your device for this to take effect:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo reboot
</code></pre></div></div>
<p>This will kill your current SSH connection since the device starts the reboot. Give it a few minutes to boot and connect it again:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh pi@raspberrypi.local
</code></pre></div></div>
<p>At this point, you should be able to validate your Docker installation with a simple command:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pi@raspberrypi:~ $ docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(arm32v7)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
[https://hub.docker.com/](https://hub.docker.com/)
For more examples and ideas, visit:
[https://docs.docker.com/get-started/](https://docs.docker.com/get-started/)
</code></pre></div></div>
<p>If you get this output, it means that your Docker setup is completed, yay!</p>
<h1 id="step-4-run-pi-hole">Step 4. Run Pi-hole</h1>
<p>Now is the time for the real stuff: running the Pi-hole. Docker allows us to control a remote Docker machine by running our regular Docker commands with a special env variable called <code class="language-plaintext highlighter-rouge">DOCKER_HOST</code>. So, let’s start by defining this environment variable first.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export DOCKER_HOST=ssh://pi@raspberrypi.local
</code></pre></div></div>
<p>From this point on, all the Docker commands we run will be against the Raspberry Pi’s Docker environment. Alternatively, you can prefix this environment variable to the commands you’ll run to run them against Pi explicitly, like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>DOCKER_HOST=ssh://pi@raspberrypi.local docker ps
</code></pre></div></div>
<p>This will list the running containers on your Raspberry Pi.</p>
<h2 id="docker-compose">Docker Compose</h2>
<p>We wanted to be able to define our containers declaratively and store them in version control, therefore we’ll use Docker Compose. Create a directory for your project, then create a file called <code class="language-plaintext highlighter-rouge">docker-compose.yml</code>. Put the following content into this file:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3.8"</span>
<span class="na">services</span><span class="pi">:</span>
<span class="na">pihole</span><span class="pi">:</span>
<span class="na">container_name</span><span class="pi">:</span> <span class="s">pihole</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">pihole/pihole:v5.1.2</span>
<span class="na">restart</span><span class="pi">:</span> <span class="s">always</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="na">TZ</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Europe/Berlin'</span> <span class="c1"># Put your own timezone here.</span>
<span class="na">WEBPASSWORD</span><span class="pi">:</span> <span class="s1">'</span><span class="s">replacethispasswordplease'</span> <span class="c1"># Put a strong password here.</span>
<span class="c1"># We'll use host networking simply because it is way easier to setup.</span>
<span class="na">network_mode</span><span class="pi">:</span> <span class="s">host</span>
<span class="c1"># Volumes store your data between container upgrades</span>
<span class="na">volumes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s1">'</span><span class="s">./etc-pihole/:/etc/pihole/'</span>
<span class="pi">-</span> <span class="s1">'</span><span class="s">./etc-dnsmasq.d/:/etc/dnsmasq.d/'</span>
<span class="c1"># Required for the DHCP server</span>
<span class="na">cap_add</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">NET_ADMIN</span>
</code></pre></div></div>
<p>That’s all actually. Once you have this file, all you have to do is:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>DOCKER_HOST=ssh://pi@raspberrypi.local docker-compose up -d
</code></pre></div></div>
<p>This will start Pi-hole on your Raspberry Pi. Give it a few minutes, and afterward, you can go to the local IP address of your Raspberry Pi, <code class="language-plaintext highlighter-rouge">192.168.0.2</code> in my case, on your browser and see the Pi-hole UI.</p>
<p><img src="/blog/assets/images/pihole-docker/pihole-dashboard-ss.png" alt="A sample screenshot from my own Pi-hole instance." class="center-image" /></p>
<div style="text-align: center;">
<small><em>A sample screenshot from my own Pi-hole instance.</em></small>
</div>
<h1 id="step-5-make-pi-hole-your-primary-dhcp-server">Step 5. Make Pi-hole your primary DHCP Server</h1>
<p>This step is optional if you can configure your router to use another device as the DNS server. In my case, my router did not allow me to change the DNS server, therefore I had to take the high road and use Pi-hole as the DHCP server in my network. Both options are documented <a href="https://blog.cryptoaustralia.org.au/instructions-for-setting-up-pi-hole/">here</a>, so I’ll focus on the DHCP part mainly.</p>
<h2 id="what-is-dhcp">What is DHCP?</h2>
<p>Dynamic Host Configuration Protocol (DHCP) is the communication protocol that allows every device in a network to have a dynamic local IP address so that the device can be addressed within that local network, and the software that manages this assignment process is called DHCP server. Simply, it is the thing that gives your computer the local <code class="language-plaintext highlighter-rouge">192.168.x.y</code> IP address.</p>
<p>One thing you need to know before you move forward, especially if you don’t know what DHCP is or how it works: <strong>there needs to be a single DHCP server in a simple home network, otherwise you’ll set for trouble.</strong></p>
<h2 id="before-you-go-on-1">Before you go on</h2>
<p>It is important to enable Pi-hole DHCP server and disable your router’s DHCP server subsequently to not to have two DHCP servers running in the same network, which would confuse the connected clients. Therefore, before you move on, make sure to find out how to disable your router’s DHCP server. This will highly depend on your router model, therefore you’ll need to google it.</p>
<h2 id="enabling-pi-hole-dhcp-server">Enabling Pi-hole DHCP server</h2>
<p>Pi-hole comes with a built-in DHCP server that can be used to replace your default DHCP server. To do that, go to <code class="language-plaintext highlighter-rouge">Settings > DHCP</code> and check the <code class="language-plaintext highlighter-rouge">DHCP Server Enabled</code> checkbox. Be careful about the range and IP address values there:</p>
<ul>
<li>
<p><strong>Range of IP addresses to hand out:</strong> This one is the IP range your devices will get locally. Give this a range between 50–250 as the last part of your IP address to have a safe range. In my case, I limited it to <code class="language-plaintext highlighter-rouge">192.168.0.201</code> to <code class="language-plaintext highlighter-rouge">192.168.0.251</code>, meaning that any new device that joins my network will get an IP within this range. In my case, I can only connect 50 clients safely, which is enough for my use-case, but you might want to change this for your own setup.</p>
</li>
<li>
<p><strong>Router (gateway) IP address:</strong> This is the IP address of your router which we have found previously.</p>
</li>
<li>
<p><strong>DHCP lease time:</strong> This is the time that a single local IP address will be allocated for a given client. It makes sense to give a low value to this limit during your setup so that you can test expired lease scenarios easily. Once you are done, you can increase this value to a week or so, which would be beneficial if you have stationary devices in your home network.</p>
</li>
<li>
<p><strong>Enable IPv6 support (SLAAC + RA):</strong> This one is for distributing IPv6 addresses in your home network. I want this to be taken care of by Pi-hole as well, so go ahead and check this too.</p>
</li>
<li>
<p><strong>Enable DHCP rapid commit (fast address assignment):</strong> This is an option to use <a href="https://tools.ietf.org/html/rfc4039">DHCP rapid commit</a>. It basically allows faster address assignment on trusted networks like your home. In my case, my router didn’t allow me to disable the IPv6 DHCP server, therefore I enabled this feature so that the clients canget an IP address from the Pi instead of my router, allowing using no-ads surfing on all my devices.</p>
</li>
</ul>
<p><img src="/blog/assets/images/pihole-docker/pihole-settings.png" alt="The settings I use with my Pi-hole." class="center-image" /></p>
<div style="text-align: center;">
<small><em>The settings I use with my Pi-hole.</em></small>
</div>
<p>Once you are done, hit the “Save” button at the bottom of the page and move onto the next step.</p>
<h2 id="disabling-routers-dhcp-server">Disabling router’s DHCP server</h2>
<p>This highly depends on your router configuration, but you should have figured out how to disable your router’s DHCP server at this point anyway. So, go ahead and disable it.</p>
<h1 id="thats-it">That’s it!</h1>
<p>At this point, you should be ready to surf the internet with Pi-hole enabled. To enjoy the benefits of Pi-hole quickly, you can reboot your devices and let them join the network again. Once they reconnect, Pi-hole should be the acting DHCP server and you should start seeing the number of queries increasing on your Pi-hole dashboard.</p>
<p>Keep in mind: there are many layers of DNS caching involved in network communication, therefore give your devices some time, a day or two maybe in some cases, until you start complaining that you are still seeing ads. Alternatively, you can clear the DNS cache of your devices, although it might still be the case that there’ll be some cache that shows you some ads. Give Pi-hole some trust and let your devices adapt as the time goes by renewing their DNS caches. I have been using this setup with no issues for ~2 weeks now. I have done some further research and added some more lists to my blocklists to improve my privacy, and Pi-hole is blocking somewhere between 10–15% of my network traffic on an average day.</p>
<p>One thing to note here is that: there might be some cases where Pi-hole blocks a domain you actually want to visit, therefore you want to put some exception rules in your blocking strategy. To do that, you can simply go to the <code class="language-plaintext highlighter-rouge">Query Log</code> tab on your Pi-hole dashboard and see the blocked domain:</p>
<p><img src="/blog/assets/images/pihole-docker/blocked-example.png" alt="I was blocking this due to an ad-list I have added." class="center-image" /></p>
<div style="text-align: center;">
<small><em>I was blocking this due to an ad-list I have added.</em></small>
</div>
<p>Once you get the domain, you can go to the <code class="language-plaintext highlighter-rouge">Whitelist</code> tab on the sidebar and add this domain to your whitelist so that Pi-hole doesn’t block this address.</p>
<p>I hope you enjoyed this piece and learned a thing or two. Feel free to suggest improvements or correct my mistakes in the comments below. I’d also like to thank my friend <a href="https://www.linkedin.com/in/buraksayin/">Burak Sayin</a> for his patience and help debugging my Pi-hole setup at home as well as for his review on the article.</p>
<p>Here’s a cute dog for a nice rest of the day for you:</p>
<p><img src="/blog/assets/images/pihole-docker/cute-dog.jpg" alt="Photo by Richard Brutyo" class="center-image" /></p>
<div style="text-align: center;">
<small><em>Photo by <a href="https://unsplash.com/@richardbrutyo?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Richard Brutyo</a> on <a href="https://unsplash.com/s/photos/happy-animal?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></em></small>
</div>Burak KarakanA practical guide for setting up Pi-hole for a privacy-focused home network.testing laravel subscription payments with cashier and stripe2020-07-22T00:00:00+00:002020-07-22T00:00:00+00:00http://burakkarakan.com/blog/laravel-testing-stripe<p>Laravel has been my go-to framework for all of my side projects thanks to its ease of use, but while integrating <a href="https://laravel.com/docs/7.x/billing">Cashier</a> into <a href="https://nana-landing.netlify.app/">Nana</a> recently, I have seen that there is no easy way of testing the subscription creation flows when using Cashier. The docs suggest that you do real calls against the Stripe’s test APIs:</p>
<blockquote>
<p>When testing an application that uses Cashier, you may mock the actual HTTP requests to the Stripe API; however, this requires you to partially re-implement Cashier’s own behavior. Therefore, we recommend allowing your tests to hit the actual Stripe API.</p>
</blockquote>
<p>This might be good advice for certain cases, and would definitely provide a good way to test the flow end-to-end, and it has some advantages:</p>
<ul>
<li>The flow can be tested end-to-end, along with the API contracts and the endpoints in the application.</li>
<li>The tests have overall better confidence due to being able to test multiple units within a single test case, such as auth, database interactions, Cashier, payment flows, and more.</li>
</ul>
<p>but it also has its problems:</p>
<ul>
<li>There needs to be test data set-up in the Stripe test dashboard, which makes it tricky to replicate in new projects.</li>
<li>The test data added to Stripe has to stay up-to-date and has to be separately updated along with the tests as needed.</li>
<li>If the test data doesn’t have to be on the Stripe dashboard, the tests would need to build the test data and clean-up after themselves after every test, which means quite a lot of boilerplate code even for simple tests.</li>
<li>You can hit the Stripe APIs rate limits if you have many tests, 25 requests per second on test mode <a href="https://stripe.com/docs/rate-limits">according to Stripe docs</a>.</li>
<li>The tests are going to be slower, making your overall feedback loop slower.</li>
</ul>
<p>Although some of these disadvantages can be solved by some clever tricks, it is still not the best approach. For my use-case, I would have liked to go with the flow that requires setting up Stripe test data for once and making the tests rely on them; however, Nana is supposed to be a dead-simple starter kit for Laravel, therefore making the users go and set things up on Stripe dashboard one-by-one was not an option. Another option was including a one-time script to set things up on Stripe, but that was also suboptimal since the file would be a dead code once it was executed, and I didn’t want to confuse users. I had to find an alternative.</p>
<h2 id="enter-prophecy">Enter Prophecy</h2>
<p>For those of you who don’t know <a href="https://github.com/phpspec/prophecy">Prophecy</a>, it is a dead-simple mocking library for PHP. Prophecy allows you to define behaviors and return values for your mock objects easily:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Create your mock</span>
<span class="nv">$mockUser</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="nf">prophesize</span><span class="p">(</span><span class="err">\</span><span class="nc">App\User</span><span class="o">::</span><span class="n">class</span><span class="p">);</span>
<span class="c1">// Define the behavior and return value</span>
<span class="nv">$mockUser</span><span class="o">-></span><span class="nf">doSomething</span><span class="p">()</span>
<span class="o">-></span><span class="nf">shouldBeCalled</span><span class="p">()</span>
<span class="o">-></span><span class="nf">willReturn</span><span class="p">(</span><span class="s1">'something done'</span><span class="p">);</span>
</code></pre></div></div>
<p>Prophecy is not very relevant for the trick I’ll show later in the article, it just sets the base for the following examples. In practice, any mocking library should work.</p>
<h2 id="handling-subscription-creation">Handling Subscription Creation</h2>
<p>Let’s say that you have a simple pricing structure, and the user picked one of the options and checked out. At this point, you have to create a subscription for the user with the given information.</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?php</span>
<span class="kn">namespace</span> <span class="nn">App\Http\Controllers</span><span class="p">;</span>
<span class="kn">use</span> <span class="nc">App\User</span><span class="p">;</span>
<span class="kn">use</span> <span class="nc">Illuminate\Http\Request</span><span class="p">;</span>
<span class="kd">class</span> <span class="nc">PlansController</span> <span class="kd">extends</span> <span class="nc">Controller</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">save</span><span class="p">(</span><span class="kt">Request</span> <span class="nv">$request</span><span class="p">,</span> <span class="kt">string</span> <span class="nv">$plan</span><span class="p">)</span>
<span class="p">{</span>
<span class="cd">/** @var User $user */</span>
<span class="nv">$user</span> <span class="o">=</span> <span class="nv">$request</span><span class="o">-></span><span class="nf">user</span><span class="p">();</span>
<span class="nv">$token</span> <span class="o">=</span> <span class="nv">$request</span><span class="o">-></span><span class="nf">get</span><span class="p">(</span><span class="s1">'paymentMethodId'</span><span class="p">);</span>
<span class="nv">$couponCode</span> <span class="o">=</span> <span class="nv">$request</span><span class="o">-></span><span class="nf">get</span><span class="p">(</span><span class="s1">'couponCode'</span><span class="p">);</span>
<span class="c1">// Build the subscription object.</span>
<span class="nv">$subscriptionBuilder</span> <span class="o">=</span> <span class="nv">$user</span><span class="o">-></span><span class="nf">newSubscription</span><span class="p">(</span><span class="s1">'default'</span><span class="p">,</span> <span class="nv">$plan</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$couponCode</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$subscriptionBuilder</span><span class="o">-></span><span class="nf">withCoupon</span><span class="p">(</span><span class="nv">$couponCode</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// Create the subscription.</span>
<span class="nv">$subscriptionBuilder</span><span class="o">-></span><span class="nf">create</span><span class="p">(</span><span class="nv">$token</span><span class="p">);</span>
<span class="k">return</span> <span class="nf">redirect</span><span class="p">(</span><span class="nf">route</span><span class="p">(</span><span class="s1">'home'</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>We have simply received the payment-related information from the request, attached the coupon if it exists, and created the subscription. For the sake of simplicity, the validations on the request are not shown here, <strong>please do not use this controller in production.</strong> We are not going to be dealing with the interface, as it is irrelevant at this point.</p>
<h2 id="testing-the-controller">Testing the Controller</h2>
<p>Now you have your controller ready, and we would like to test it. A naive approach for it would be to build a regular feature test with a user calling the endpoint and watch the subscription get created on Stripe, but as we have mentioned before, we don’t want to make internet calls because we want our tests to be simple and fast at the same time, not only simple or fast.</p>
<p>Here is the test function for our controller:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?php</span>
<span class="kn">namespace</span> <span class="nn">Tests\Feature</span><span class="p">;</span>
<span class="kn">use</span> <span class="nc">App\User</span><span class="p">;</span>
<span class="kn">use</span> <span class="nc">Laravel\Cashier\SubscriptionBuilder</span><span class="p">;</span>
<span class="kn">use</span> <span class="nc">Tests\TestCase</span><span class="p">;</span>
<span class="kn">use</span> <span class="k">function</span> <span class="n">route</span><span class="p">;</span>
<span class="kd">class</span> <span class="nc">PlansTest</span> <span class="kd">extends</span> <span class="nc">TestCase</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">testSubscriptionIsSuccessfullyCreatedWithCoupon</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// Disable the authentication middleware, we don't need it.</span>
<span class="nv">$this</span><span class="o">-></span><span class="nf">withoutMiddleware</span><span class="p">([</span><span class="nc">Authenticate</span><span class="o">::</span><span class="n">class</span><span class="p">]);</span>
<span class="c1">// Prepare request variables.</span>
<span class="nv">$paymentMethodId</span> <span class="o">=</span> <span class="s1">'payment-method'</span><span class="p">;</span>
<span class="nv">$couponCode</span> <span class="o">=</span> <span class="s1">'SOME_COUPON'</span><span class="p">;</span>
<span class="c1">// Prepare the subscription builder mock.</span>
<span class="nv">$subscriptionBuilder</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="nf">prophesize</span><span class="p">(</span><span class="nc">SubscriptionBuilder</span><span class="o">::</span><span class="n">class</span><span class="p">);</span>
<span class="nv">$subscriptionBuilder</span><span class="o">-></span><span class="nf">withCoupon</span><span class="p">(</span><span class="nv">$couponCode</span><span class="p">)</span>
<span class="o">-></span><span class="nf">shouldBeCalled</span><span class="p">();</span>
<span class="nv">$subscriptionBuilder</span><span class="o">-></span><span class="nf">create</span><span class="p">(</span><span class="nv">$paymentMethodId</span><span class="p">)</span>
<span class="o">-></span><span class="nf">shouldBeCalled</span><span class="p">();</span>
<span class="c1">// Prepare the user mock</span>
<span class="nv">$user</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="nf">prophesize</span><span class="p">(</span><span class="nc">User</span><span class="o">::</span><span class="n">class</span><span class="p">);</span>
<span class="nv">$user</span><span class="o">-></span><span class="nf">newSubscription</span><span class="p">(</span><span class="s1">'default'</span><span class="p">,</span> <span class="s1">'standard'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">shouldBeCalled</span><span class="p">()</span>
<span class="o">-></span><span class="nf">willReturn</span><span class="p">(</span><span class="nv">$subscriptionBuilder</span><span class="o">-></span><span class="nf">reveal</span><span class="p">());</span>
<span class="c1">// Perform the request as our user mock.</span>
<span class="nv">$testRoute</span> <span class="o">=</span> <span class="nf">route</span><span class="p">(</span><span class="s1">'plans.save'</span><span class="p">,</span> <span class="p">[</span><span class="s1">'plan'</span> <span class="o">=></span> <span class="s1">'standard'</span><span class="p">]);</span>
<span class="nv">$response</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="nf">actingAs</span><span class="p">(</span><span class="nv">$user</span><span class="o">-></span><span class="nf">reveal</span><span class="p">())</span>
<span class="o">-></span><span class="nf">post</span><span class="p">(</span><span class="nv">$testRoute</span><span class="p">,</span> <span class="p">[</span>
<span class="s1">'paymentMethodId'</span> <span class="o">=></span> <span class="nv">$paymentMethodId</span><span class="p">,</span>
<span class="s1">'couponCode'</span> <span class="o">=></span> <span class="nv">$couponCode</span><span class="p">,</span>
<span class="p">]);</span>
<span class="c1">// Assert that everything went smoothly.</span>
<span class="nv">$response</span><span class="o">-></span><span class="nf">assertRedirect</span><span class="p">(</span><span class="nf">route</span><span class="p">(</span><span class="s1">'home'</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>As you can see, the test starts with defining the common request variables and moves onto defining the behavior on the subscription mock. The SubscriptionBuilder class is the return value of <code class="language-plaintext highlighter-rouge">$user->subscription()</code> in our controller class; therefore, we first need to prepare it.</p>
<ul>
<li>The controller should attach the coupon, so define the <code class="language-plaintext highlighter-rouge">withCoupon</code> method behavior.</li>
<li>The controller should attempt to create the subscription, so define the create method with the argument <code class="language-plaintext highlighter-rouge">$paymentMethodId</code>.</li>
</ul>
<p>Once we have the subscription builder ready, we need to attach it to our user object.</p>
<ul>
<li>When the <code class="language-plaintext highlighter-rouge">newSubscription()</code> method of the <code class="language-plaintext highlighter-rouge">$user</code> object is called with those string arguments, the mock will return the subscription builder we have just prepared.</li>
</ul>
<p>At this point, we are done with our mocks, and we can perform the actual request. It is simply a POST request to the subscription creation endpoint, and it includes the request parameters we have defined at the beginning of our function.</p>
<p>The trick here is the method <code class="language-plaintext highlighter-rouge">actingAs</code> that is called right before performing the POST request. This value will be associated with the <code class="language-plaintext highlighter-rouge">$request->user()</code> method automatically, which is used by the controller, and allow us to define the behavior on the user exactly like we want, without ever needing to set up anything on the database, no need for factories, and absolutely no need for internet calls.</p>
<p>That’s it. Now you can use this method to mock your Cashier dependencies without doing any external calls and make sure your tests stay super fast.</p>
<h2 id="why-should-i-use-this-method">Why should I use this method?</h2>
<p>Just like any other tech decision, this approach also has its advantages and disadvantages.</p>
<p>Disadvantages of this approach are:</p>
<ul>
<li>It requires mocking the behavior of some application internals, such as the <code class="language-plaintext highlighter-rouge">subscription()</code> method on the User object. If you haven’t setup Cashier properly on the User model, these tests would still pass.</li>
<li>It doesn’t test the flow end-to-end, which means that if there are other issues in your pipelines, such as sending invalid tokens or coupons, these tests would still pass.</li>
<li>It requires you to properly define the return values of the methods used throughout the controller.</li>
</ul>
<p>Even though it has some disadvantages, this method still has a lot of value in it:</p>
<ul>
<li>There is no need to test internals of Cashier, just like suggested in <a href="https://laravel.com/docs/7.x/billing#testing">Cashier docs</a>.
<blockquote>
<p>When testing, remember that that Cashier itself already has a great test suite, so you should only focus on testing the subscription and payment flow of your own application and not every underlying Cashier behavior.</p>
</blockquote>
</li>
<li>The tests still cover the peripherals of the controller, such as the routing and request variable passing, which is better than just building a controller instance and calling its save method directly.</li>
<li>It doesn’t require any internet or database connection, and it is <strong>super fast.</strong></li>
<li>Since it doesn’t depend on Stripe’s API, there are no risks of rate-limiting or failed network calls.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>Overall, this approach seems to hit the sweet spot for my needs. It covers enough of the application and provides enough value for such a small effort. As next steps, it would be beneficial to have tests that call Stripe API as well, which might be grouped and executed only on master builds or only when needed, these two approaches can easily work together and would also allow you to isolate your problems in case a bug slips in. For the case of Nana, this approach will definitely allow me to cover all the payment/subscription related endpoints with tests so that users can build on top of those features without caring about setting anything up on Stripe.</p>Burak KarakanI should have a base test suite that can run without an internet connection, and it should be super fast. both of them, at the same time.who is home?2020-01-08T00:00:00+00:002020-01-08T00:00:00+00:00http://burakkarakan.com/blog/who-is-home<p>I used to live in a not-so-nice neighborhood of Istanbul since I was born, and there would be burglary incidents every once in a while. Also, we were living in a family building where all 3 apartments of the building were occupied by us and my uncles & their families. The thing is, our building had this old rusty external door which had a broken lock; therefore, we were not able to lock the door and we were only locking our apartment doors, but at the same time we were leaving our shoes in front of our apartments, which is pretty common here with family buildings, and this old rusty door caused quite a few pairs of shoes to be stolen throughout this time. After the last incident, we have decided to put a manual sliding lock to the door which would be locked only from the inside and the door wouldn’t open from outside even with a key when this lock was locked. The problem with this lock is before we could lock the door with this new manual lock, we had to ensure at least someone from each apartment was home in case someone else comes home so that they could go downstairs, open the lock, let the others in and lock it again, which meant we would leave the door unlocked and hope that whoever comes after 12 AM would lock the door as it was hard to check all the apartments to see if there is someone home just by looking at the shoes.</p>
<p>On a separate topic, I don’t know how it happened, but at some point I found myself supporting internet connection for 10+ people from my small dumb modem which was breaking quite often under the load. I wanted to know who is connected at which time, and I also wanted to be able to block some of my young cousins from time to time who were hijacking the bandwidth for all of us by watching Minecraft videos in full HD. I had a very simple Asus modem at the time which looked like a model spaceship. This modem had a very simple interface that was capable of doing very little, but it got the job done and allowed me to see all the connected clients, as well as blocking them.</p>
<p><img src="/blog/assets/images/who-is-home/modem.png" alt="[Source](https://www.asus.com/media/global/products/W8ra60WPu6xP3C6T/P_setting_fff_1_90_end_500.png)" class="center-image" /></p>
<div style="text-align: center;">
<small>The modem looked exactly like this one, but I don’t remember the exact model name.</small>
</div>
<h2 id="wtf-are-you-talking-about-dude">wtf are you talking about dude?</h2>
<p>At the time, just like most (read <em>all</em>) of my ideas, I had this idea that would never turn into reality: if I could get the list of all the connected clients of my modem, associate their MAC addresses with who they actually are, and display this list downstairs next to the door using an old phone taped to the wall, I might allow everyone coming home to check this screen to see who is home, and they would be able to decide whether to lock the door. Albeit this is a bit invasive of their privacy, I was more intrigued by the technical challenges it poses, that’s why I didn’t give a fuck and started building it.</p>
<p>The first challenge was to get the list of clients, and I thought I could scan the ports in the local network to see who is connected to the network using nmap, then I’d get additional information regarding the clients somehow.</p>
<h3 id="using-nmap">using nmap</h3>
<p>Python had nmap bindings through the <a href="https://xael.org/pages/python-nmap-en.html">python-nmap</a> package, which was dead easy to use:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">nm</span><span class="p">.</span><span class="n">scan</span><span class="p">(</span><span class="n">hosts</span><span class="o">=</span><span class="s">'192.168.2.0/24'</span><span class="p">,</span> <span class="n">arguments</span><span class="o">=</span><span class="s">'-sn'</span><span class="p">)</span>
<span class="n">hosts_list</span> <span class="o">=</span> <span class="p">[(</span><span class="n">x</span><span class="p">,</span> <span class="n">nm</span><span class="p">[</span><span class="n">x</span><span class="p">][</span><span class="s">'status'</span><span class="p">][</span><span class="s">'state'</span><span class="p">])</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">nm</span><span class="p">.</span><span class="n">all_hosts</span><span class="p">()]</span>
<span class="k">for</span> <span class="n">host</span><span class="p">,</span> <span class="n">status</span> <span class="ow">in</span> <span class="n">hosts_list</span><span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="s">'{0}: {1}'</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">host</span><span class="p">,</span> <span class="n">status</span><span class="p">))</span>
</code></pre></div></div>
<p>By using this, I was hoping I’d get a list of IPs, but guess what happened? every time I’d run the script, the modem would die and I had to restart it for it to come back to life. I tried a bunch of different options, but I haven’t properly used nmap before, and I had no idea what was wrong with the modem as well, that’s why I had to find another solution to this.</p>
<h3 id="using-the-admin-interface-of-the-modem">using the admin interface of the modem</h3>
<p>The second idea was to scrape the admin interface of the modem somehow since the list of clients was available on the parental control section of the interface. Before that, I checked the network requests, and found that it was using HTTP basic to communicate with the backend, which was returning a full HTML response with values embedded into the script section of the HTML. When I checked the HTML body, I found that it contained four main variables that had all the required values in a weird fashion: <code class="language-plaintext highlighter-rouge">leases</code>, <code class="language-plaintext highlighter-rouge">arps</code>, <code class="language-plaintext highlighter-rouge">wireless</code> and <code class="language-plaintext highlighter-rouge">ipmonitor</code>. Each of these values were holding arrays of arrays, and they were something like this when simplified:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">leases</span> <span class="o">=</span> <span class="p">[[</span><span class="dl">"</span><span class="s2">android-4c28aa974083dc66</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">40:D3:05:G7:BB:F9</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">192.168.2.228</span><span class="dl">"</span><span class="p">]];</span> <span class="c1">// [hostname, MAC, ip]</span>
<span class="kd">var</span> <span class="nx">arps</span> <span class="o">=</span> <span class="p">[[</span><span class="dl">"</span><span class="s2">192.168.2.22</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">40:0E:85:52:D4:43</span><span class="dl">"</span><span class="p">]];</span> <span class="c1">// [ip, MAC]</span>
<span class="kd">var</span> <span class="nx">wireless</span> <span class="o">=</span> <span class="p">[[</span><span class="dl">"</span><span class="s2">40:F3:08:F7:BB:F9</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Yes</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Yes</span><span class="dl">"</span><span class="p">]];</span> <span class="c1">// [MAC, associated, authorized]</span>
<span class="kd">var</span> <span class="nx">ipmonitor</span> <span class="o">=</span> <span class="p">[[</span><span class="dl">"</span><span class="s2">192.168.2.12</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">3C:BD:D8:D7:E8:17</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">3CBDD8D7E817</span><span class="dl">"</span><span class="p">]];</span> <span class="c1">// [IP, MAC, DeviceName]</span>
</code></pre></div></div>
<p>I am very ignorant when it comes to networking, therefore I don’t know what is <code class="language-plaintext highlighter-rouge">leases</code> or why would these things be stored in different variables like <code class="language-plaintext highlighter-rouge">leases</code> and <code class="language-plaintext highlighter-rouge">ipmonitor</code>, each having different properties and details; however, I had the hunch that these were the things I was looking for. While playing around with it, I noticed that the <code class="language-plaintext highlighter-rouge">ipmonitor</code> variable was always the biggest array which contained all the items that were scattered among other variables, and it seemed to be the source of truth regarding the connected devices. Being the worst python programmer in the world, I decided this was a good job for python.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">get_leases_arps_monitors</span><span class="p">(</span><span class="n">contents</span><span class="p">):</span>
<span class="n">contents</span> <span class="o">=</span> <span class="n">contents</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">leases</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">arps</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">ip_monitors</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">wireless_devices</span> <span class="o">=</span> <span class="p">[]</span>
<span class="c1"># Loop over the lines to find items.
</span> <span class="n">found_count</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">contents</span><span class="p">:</span>
<span class="k">if</span> <span class="s">'var leases'</span> <span class="ow">in</span> <span class="n">line</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">leases</span><span class="p">:</span>
<span class="n">leases</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">"= "</span><span class="p">,</span> <span class="mi">1</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">leases</span> <span class="o">=</span> <span class="n">leases</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">";"</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">leases</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">leases</span><span class="p">)</span>
<span class="n">found_count</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">elif</span> <span class="s">'var arps'</span> <span class="ow">in</span> <span class="n">line</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">arps</span><span class="p">:</span>
<span class="n">arps</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">"= "</span><span class="p">,</span> <span class="mi">1</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">arps</span> <span class="o">=</span> <span class="n">arps</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">";"</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">arps</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">arps</span><span class="p">)</span>
<span class="n">found_count</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">elif</span> <span class="s">'var ipmonitor'</span> <span class="ow">in</span> <span class="n">line</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">ip_monitors</span><span class="p">:</span>
<span class="n">ip_monitors</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">"= "</span><span class="p">,</span> <span class="mi">1</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">ip_monitors</span> <span class="o">=</span> <span class="n">ip_monitors</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">";"</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">ip_monitors</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">ip_monitors</span><span class="p">)</span>
<span class="n">found_count</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">elif</span> <span class="s">'var wireless'</span> <span class="ow">in</span> <span class="n">line</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">wireless_devices</span><span class="p">:</span>
<span class="n">wireless_devices</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">"= "</span><span class="p">,</span> <span class="mi">1</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">wireless_devices</span> <span class="o">=</span> <span class="n">wireless_devices</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">";"</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">wireless_devices</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">wireless_devices</span><span class="p">)</span>
<span class="n">found_count</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">if</span> <span class="n">found_count</span> <span class="o">==</span> <span class="mi">4</span><span class="p">:</span>
<span class="k">break</span>
<span class="k">return</span> <span class="n">leases</span><span class="p">,</span> <span class="n">arps</span><span class="p">,</span> <span class="n">ip_monitors</span><span class="p">,</span> <span class="n">wireless_devices</span>
</code></pre></div></div>
<p>Once I have the values of these variables, it was easy to build a simple array of dictionaries by looping over them. I built a simple class to do all these, including fetching the HTML response, parsing it and building a list of connected clients.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">base64</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">urllib2</span>
<span class="k">class</span> <span class="nc">Asus</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ip</span><span class="p">,</span> <span class="n">username</span><span class="p">,</span> <span class="n">password</span><span class="p">):</span>
<span class="bp">self</span><span class="p">.</span><span class="n">ip</span> <span class="o">=</span> <span class="n">ip</span>
<span class="bp">self</span><span class="p">.</span><span class="n">username</span> <span class="o">=</span> <span class="n">username</span>
<span class="bp">self</span><span class="p">.</span><span class="n">password</span> <span class="o">=</span> <span class="n">password</span>
<span class="bp">self</span><span class="p">.</span><span class="n">auth_token</span> <span class="o">=</span> <span class="n">base64</span><span class="p">.</span><span class="n">b64encode</span><span class="p">(</span><span class="s">"%s:%s"</span> <span class="o">%</span> <span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">username</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">password</span><span class="p">))</span>
<span class="bp">self</span><span class="p">.</span><span class="n">clients</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">def</span> <span class="nf">get_raw_html</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="c1"># Get the request data.
</span> <span class="n">request</span> <span class="o">=</span> <span class="n">urllib2</span><span class="p">.</span><span class="n">Request</span><span class="p">(</span><span class="s">"http://%s/ParentalControl.asp"</span> <span class="o">%</span> <span class="bp">self</span><span class="p">.</span><span class="n">ip</span><span class="p">,</span>
<span class="n">headers</span><span class="o">=</span><span class="p">{</span><span class="s">'Authorization'</span><span class="p">:</span> <span class="s">'Basic %s'</span> <span class="o">%</span> <span class="bp">self</span><span class="p">.</span><span class="n">auth_token</span><span class="p">})</span>
<span class="k">return</span> <span class="n">urllib2</span><span class="p">.</span><span class="n">urlopen</span><span class="p">(</span><span class="n">request</span><span class="p">).</span><span class="n">read</span><span class="p">()</span>
<span class="o">@</span><span class="nb">staticmethod</span>
<span class="k">def</span> <span class="nf">find_item_in_list</span><span class="p">(</span><span class="n">items</span><span class="p">,</span> <span class="n">index</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
<span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">items</span><span class="p">:</span>
<span class="k">if</span> <span class="n">item</span><span class="p">[</span><span class="n">index</span><span class="p">]</span> <span class="o">==</span> <span class="n">value</span><span class="p">:</span>
<span class="k">return</span> <span class="n">item</span>
<span class="k">return</span> <span class="p">[]</span>
<span class="o">@</span><span class="nb">staticmethod</span>
<span class="k">def</span> <span class="nf">get_leases_arps_monitors</span><span class="p">(</span><span class="n">contents</span><span class="p">):</span>
<span class="n">contents</span> <span class="o">=</span> <span class="n">contents</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">leases</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">arps</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">ip_monitors</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">wireless_devices</span> <span class="o">=</span> <span class="p">[]</span>
<span class="c1"># Loop over the lines to find items.
</span> <span class="n">found_count</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">contents</span><span class="p">:</span>
<span class="k">if</span> <span class="s">'var leases'</span> <span class="ow">in</span> <span class="n">line</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">leases</span><span class="p">:</span>
<span class="n">leases</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">"= "</span><span class="p">,</span> <span class="mi">1</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">leases</span> <span class="o">=</span> <span class="n">leases</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">";"</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">leases</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">leases</span><span class="p">)</span>
<span class="n">found_count</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">elif</span> <span class="s">'var arps'</span> <span class="ow">in</span> <span class="n">line</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">arps</span><span class="p">:</span>
<span class="n">arps</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">"= "</span><span class="p">,</span> <span class="mi">1</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">arps</span> <span class="o">=</span> <span class="n">arps</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">";"</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">arps</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">arps</span><span class="p">)</span>
<span class="n">found_count</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">elif</span> <span class="s">'var ipmonitor'</span> <span class="ow">in</span> <span class="n">line</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">ip_monitors</span><span class="p">:</span>
<span class="n">ip_monitors</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">"= "</span><span class="p">,</span> <span class="mi">1</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">ip_monitors</span> <span class="o">=</span> <span class="n">ip_monitors</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">";"</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">ip_monitors</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">ip_monitors</span><span class="p">)</span>
<span class="n">found_count</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">elif</span> <span class="s">'var wireless'</span> <span class="ow">in</span> <span class="n">line</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">wireless_devices</span><span class="p">:</span>
<span class="n">wireless_devices</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">"= "</span><span class="p">,</span> <span class="mi">1</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">wireless_devices</span> <span class="o">=</span> <span class="n">wireless_devices</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">";"</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">wireless_devices</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">wireless_devices</span><span class="p">)</span>
<span class="n">found_count</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">if</span> <span class="n">found_count</span> <span class="o">==</span> <span class="mi">4</span><span class="p">:</span>
<span class="k">break</span>
<span class="k">return</span> <span class="n">leases</span><span class="p">,</span> <span class="n">arps</span><span class="p">,</span> <span class="n">ip_monitors</span><span class="p">,</span> <span class="n">wireless_devices</span>
<span class="k">def</span> <span class="nf">get_clients</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="c1"># Get the contents of the result.
</span> <span class="n">contents</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">get_raw_html</span><span class="p">()</span>
<span class="c1"># Get the leases and arps from the html response.
</span> <span class="n">leases</span><span class="p">,</span> <span class="n">arps</span><span class="p">,</span> <span class="n">ip_monitors</span><span class="p">,</span> <span class="n">wireless_devices</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">get_leases_arps_monitors</span><span class="p">(</span><span class="n">contents</span><span class="p">)</span>
<span class="c1"># Loop over the array to construct a dictionary array.
</span> <span class="k">for</span> <span class="n">ip_monitor</span> <span class="ow">in</span> <span class="n">ip_monitors</span><span class="p">:</span>
<span class="n">ip</span> <span class="o">=</span> <span class="n">ip_monitor</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">mac</span> <span class="o">=</span> <span class="n">ip_monitor</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">device_name</span> <span class="o">=</span> <span class="n">ip_monitor</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
<span class="n">is_wireless</span> <span class="o">=</span> <span class="bp">False</span>
<span class="n">found</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">find_item_in_list</span><span class="p">(</span><span class="n">wireless_devices</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">mac</span><span class="p">)</span>
<span class="k">if</span> <span class="n">found</span><span class="p">:</span>
<span class="n">is_wireless</span> <span class="o">=</span> <span class="bp">True</span>
<span class="bp">self</span><span class="p">.</span><span class="n">clients</span><span class="p">.</span><span class="n">append</span><span class="p">({</span>
<span class="s">'ip'</span><span class="p">:</span> <span class="n">ip</span><span class="p">,</span>
<span class="s">'mac'</span><span class="p">:</span> <span class="n">mac</span><span class="p">,</span>
<span class="s">'name'</span><span class="p">:</span> <span class="n">device_name</span><span class="p">,</span>
<span class="s">'is_wireless'</span><span class="p">:</span> <span class="n">is_wireless</span>
<span class="p">})</span>
<span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">clients</span>
</code></pre></div></div>
<p>At the end, I was able to pull the list of clients with two lines of code:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">asus</span> <span class="o">=</span> <span class="n">Asus</span><span class="p">(</span><span class="s">'192.168.2.1'</span><span class="p">,</span> <span class="s">'admin_username'</span><span class="p">,</span> <span class="s">'admin_password'</span><span class="p">)</span>
<span class="n">clients</span> <span class="o">=</span> <span class="n">asus</span><span class="p">.</span><span class="n">get_clients</span><span class="p">()</span>
</code></pre></div></div>
<p>The list of items would be something like this, ready to be consumed by the clients:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"is_wireless"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
</span><span class="nl">"ip"</span><span class="p">:</span><span class="w"> </span><span class="s2">"192.168.2.12"</span><span class="p">,</span><span class="w">
</span><span class="nl">"mac"</span><span class="p">:</span><span class="w"> </span><span class="s2">"DB:45:D8:C3:38:14"</span><span class="p">,</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"burak's desktop computer"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"is_wireless"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="nl">"ip"</span><span class="p">:</span><span class="w"> </span><span class="s2">"192.168.2.228"</span><span class="p">,</span><span class="w">
</span><span class="nl">"mac"</span><span class="p">:</span><span class="w"> </span><span class="s2">"50:33:D8:37:CC:F5"</span><span class="p">,</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"burak's phone"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>
<p>Up until this point, all was perfect.</p>
<h2 id="then-what">then what?</h2>
<p>Since the code to pull the list of clients was ready, it was time to build a simple frontend to display these by polling the backend with <code class="language-plaintext highlighter-rouge">setInterval</code> continuously, every 5 seconds or so. At this point, all was ready to be built upon, but guess who couldn’t keep his calm to not to fuck this up?</p>
<p>At the back of my mind, I was still thinking about the nmap issue I had before; basically, the modem was not able to handle scans, which gave me the hunch that continuously polling its admin interface would consume its resources and saturate the connection for everyone, and I didn’t want that. Did I have any data to back this claim up? No. Did I know anything about how modems work? No. Did I do any proof of concept to test whether this was actually the case? Of course not.</p>
<p>Having a strong faith in my gut feeling and focusing on this non-existent, unrealistic issue, I decided to replace the default firmware of the modem with <a href="https://openwrt.org/">OpenWrt</a>, which would be more stable, would allow me to get more metrics with less code, and simplify the job here. Did I have any data to back this claim up? No. Did I know anything about how modems firmware work? No. Did I do any proof of concept to test whether this was actually the case? Of course not. Once again, all I had was a pure gut feeling.</p>
<p><strong>At the end, I was replacing the default firmware of my router with a custom open-source firmware to lock a god-damn old door.</strong></p>
<div style="text-align: center;">
-- INSERT A HUGE FACEPALM HERE --
</div>
<h3 id="attempting-to-upgrade-the-router-firmware">attempting to upgrade the router firmware</h3>
<p>I vaguely remember how it happened, but I remember reading the documentation of the router regarding updating the firmware, and the docs had a sentence written in big bold fonts, stating that <strong>if the process somehow fails or gets interrupted in the middle, the device will be dead</strong>. I have read this statement and immediately marked it as irrelevant in my mind as it should be quick and easy to upgrade this small simple device. Once again, did I have any data to back this claim up? No. Did I know anything about how does the upgrade process works? No. Did I do any proof of concept to test whether this was actually the case? <strong>OF FUCKING COURSE NOT.</strong></p>
<p>And guess what happened? <strong>SOMETHING HAPPENED, AND THE ROUTER DECIDED TO RESTART ITSELF IN THE MIDDLE OF THE UPGRADE.</strong> Essentially, I bricked my perfectly fine router for a project of locking a rusty old door that would never come to life even though the router worked; you can insert another huge facepalm here.</p>
<hr />
<h2 id="i-have-to-conclude">I have to conclude</h2>
<p>After all this work, all I had was a simple Python script that was supposed to work against this specific Asus router which was already dead. Eventually, after having no internet connection for around 3 hours, I bought another router and got everything working again. As a result of this, I had:</p>
<ul>
<li>a Python script for pulling the clients connected to my Asus router</li>
<li>a dead Asus router</li>
<li>a brand new router</li>
<li>a demotivated CS student (<em>hint: this is me</em>)</li>
</ul>
<p>Overall, this was a fun failed experiment that cost me a router, but it was fun, which was the main reason I started working on this. As an additional cost, my family made fun of me a little bit once I explained why there was no internet connection for a couple of hours, but that was fine, I deserved that.</p>
<p>If you think of building something similar, I’d suggest replacing the lock of the door somehow so that everyone could use good old keys instead of a complicated setup like I planned; it would probably be cheaper to replace the lock than to buy a new router.</p>Burak Karakanall my cousins connect to my internet, which gives me the right to scan the modem and see who is home right now, I guess.on seniority in software engineering2019-10-13T00:00:00+00:002019-10-13T00:00:00+00:00http://burakkarakan.com/blog/on-seniority-in-software-engineering<p>I’d expect a senior engineer to be a technically capable individual, understands/thinks about human psychology, has good self-motivation skills that would ignite the team motivation as well and is dependable. I wanted to pour some thoughts, think out loud about seniority and to reflect on some discussions I’ve had with some friends recently.</p>
<h2 id="what-doesnt-make-a-senior-engineer">what doesn’t make a senior engineer</h2>
<p>There may be multiple things to mention as pitfalls to understand if the subject individual is senior or not in a broad sense, but there is a common theme I see companies and teams fall into, and some of the characteristics of those mistakes seem important to discuss here.</p>
<p><strong>Strong technical skills are important, but not everything.</strong> I believe this is such a common misconception in growing companies that everyone that can develop some features starts calling themselves “senior engineers”, which damages the “senior engineer” image of the company in the employees’ eyes. An experienced engineer may be able to develop a lot of software, but that shouldn’t bring seniority automatically as that doesn’t imply that the individual understands about business, can motivate self and the ones around or can be relied upon. Overall, if I were to list traits of a senior engineer, technical skills wouldn’t be in the top 3 on that list.</p>
<p><strong>Working in a company for a long time doesn’t automatically bring seniority.</strong> An engineer working for a company for 5 years might be the most senior employee of the company when it comes to time spent in the company, but that doesn’t automatically make that person a <em>senior engineer</em> in the sense of being senior no matter what company or environment we are talking about. Being a long-time employee has its advantages for sure, but that doesn’t imply that this person has all the traits of a senior engineer, it just implies that the person is either happy in the company or very patient.</p>
<p><strong>Working in the industry for a long time doesn’t automatically bring seniority either.</strong> Just like the company version of the same case, it doesn’t imply that the subject individual is senior because he/she spent X years in the industry. One might spend 10 years just doing repetitive stuff or not thinking about the job in detail, which would disqualify them from the idea of seniority.</p>
<p>Even though these traits are usually seen in people correctly regarded as senior engineers, <a href="https://en.wikipedia.org/wiki/Correlation_does_not_imply_causation">correlation does not imply causation</a>; therefore, I believe these are some points to be careful about.</p>
<h2 id="what-makes-a-senior-engineer">what makes a senior engineer</h2>
<p><strong>A senior engineer should be able to communicate clearly.</strong> I think this is one of the most important traits that define a senior engineer; ideas matter only if one can communicate them clearly to other individuals. No matter how good of an engineer one is, if that person cannot explain what he/she is doing or cannot communicate various ideas with technical and non-technical people clearly, then all the technical skill becomes nothing. Technology is there to bring value, and bringing value is more often than not a team game, which means if you want to deliver value, you <em>have to</em> communicate well. There is always this myth of a geek that is not able to communicate with other people and creeps people out and regarded as a super-duper engineer; I believe that no one would like to work with someone in their team that cannot talk/stand to other people and explain his/her ideas. Good communication is key, a senior engineer needs to be able to communicate with engineers, managers, and customers clearly and efficiently so that he/she can keep building value.</p>
<p><strong>A senior engineer should understand the tech-business relationship.</strong> Software engineers are trying to build value for people in different areas, and it is very common for inexperienced engineers to fall into the trap of thinking only about the tech and not caring about the value implications of it at the end. The software can be a for-profit product, a hospital system, a non-profit application, a public transportation system, it can be anything; <em>business</em> here means the value produced at the end. Technology is important, that is correct, but the software should be seen as a tool to achieve other goals rather than being the goal itself, excluding education purposes. A senior engineer should be able to understand the business effects of the technology in use to aid the result rather than the intermediate tools.</p>
<p><strong>A senior engineer should be open to learning and criticism.</strong> One of the things that pulled me into computers was how huge the potential is and how many different directions one may take within the field itself. Since there are so many things that can be done, I believe this also brings the requirement of continuous learning. We should all be open to learning from academia, industry and our peers. We should keep reading, listening, watching, experimenting, testing and staying positive about all these while still delivering value. As these are the behaviors I would expect from every engineer, a senior engineer should be a role-model when it comes down to learning.</p>
<p><strong>A senior engineer should support their peers.</strong> It is hard to get started something and everybody was a beginner in their profession for quite some time. Helping other people to get something done, supporting them through technical and non-technical decisions and allowing them to grow has a huge impact on overall productivity and team energy. I would expect supportive behavior that shines a light to various bottlenecks for the individuals in the team from a senior engineer.</p>
<p><strong>A senior engineer should empower their peers through their career track.</strong> While this may sound a little bit controversial, I think it is hard for some types of people to promote themselves to other people, which makes them look like they are not achieving much even though they might have been doing incredible work. A senior engineer should be the one that gives credit to outstanding work, promotes their teammates to the open world and encourage them to do more if they are willing to. This can be a very small gesture, such as mentioning to an upper-level manager as “have you seen the outstanding work X has done over the topic of Y?” while both are in the environment; this kind of behavior can be a huge motivator for both of the parties as it would help the individuals’ career tracks and help them to achieve more.</p>
<p><strong>A senior engineer should be a motivator for the rest of the team.</strong> There may be cases where a team loses focus or motivation because of various reasons; a senior engineer should be able to manage the problems for the team and motivate them out of those depressive moments. These problems might be caused because of technical, managerial or personal issues; in either case, the senior engineer should be able to analyze the situation, understand the state-of-mind his/her peers are in and act accordingly to bring the motivation back. This may be bringing a technically challenging task on the table if the team is bored with what they are doing right now, pushing some ideas within the team to bring the spark back if they feel like they are dropped out of the game, or supporting them individually through tough times. A senior engineer should be the one that acts as a role model to the other members of the team and show the path to the end of the tunnel for those who feel kind of lost.</p>
<p><strong>A senior engineer should be dependable.</strong> Delivering things is important; however, what is even more important from my point of view is to deliver things that were <em>promised</em> to be delivered in various forms; these promises may be in the form of sprint plannings, RFC documents, product roadmaps or in-person communication. I believe a senior engineer should be dependable in the sense that the team should be able to internalize the notion of “if X said he/she will do something, he/she will do it.” confidently. This would allow better planning for future events, better allocation of resources and more stability against workload or environmental changes. Also, I believe this dependability would bring faith to the attention the engineer puts into their work; as in, the quality of their work would mostly not be doubted in the process.</p>
<p><strong>A senior engineer should be able to stand up for the good and point out what is broken.</strong> There may be cases where a broken thing is ignored to keep the status quo to stay comfortable; a senior engineer should be ready to point out problems and willing to take action to resolve those problems. The problems may be technical issues, behavioral problems or processes that don’t fit the team culture; the <em>senior</em> should be calling out the problems and making things explicit to improve the situation for good even though sometimes it requires extra work for various parties.</p>
<p><strong>A senior engineer should have a T-shaped knowledge.</strong> It is expected from a senior engineer to have broad knowledge about various topics and deep knowledge about <em>some</em> topics. The person should not be expected to know everything, but they should at least have a reasonable amount of knowledge about what the team is working on, from both technical and business perspectives. Applied technical knowledge might be required for some positions/teams, but I’d argue that it is not <em>that</em> important for a senior engineer to have extensive experience with a certain technology as it shouldn’t be too hard for someone that has a broad knowledge about various technologies to pick up something new and get productive with it.</p>
<p><strong>A senior engineer should stay away from bike-shedding and favor objectively better solutions.</strong> It is really easy to get lost in various firefighting, especially when it comes to various technologies, that it may harm the productivity of the team in very bad ways. A senior engineer should be able to analyze potential options at hand and favor a solution that is objectively better from another rather than being fanatic about one solution with no backing information. A senior engineer should also strive to stay away from analysis paralysis for similar reasons; engineering decisions should come from data rather than gut feeling and spending too much time on small differences is not productive.</p>
<p><strong>A senior engineer should strive for the best out of the team.</strong> Some might call this behavior as “being too picky” when it comes to certain areas, but I believe a senior engineer should push their peers to produce better work and to learn more. This behavior might be exemplified as asking for good test coverage for some software, pushing for more extendable architectures, pointing out mistakes and potential improvements and trying to bring out the best in everyone while not discouraging anyone. The team would ideally improve in terms of technical and soft skills.</p>
<h2 id="so-what">so what?</h2>
<p>As it is already obvious, I am at the beginning of my career as well and I am trying to grow and learn more as I go, but I believe putting some thoughts into the process and the expected outcome is important to be more precise about achieving our goals. I have worked in smaller teams where the progress and delivery was mainly a one-man show, and I have been working at Hellofresh for a while where delivery is a result of an effort of tens or hundreds of people, and the dynamics of these environments is quite apparent; however, the effect senior people can have in a team on delivering stuff and achieving more is one of the most important common points between these organizations, and I believe assessing these values and thinking about them should be a continuous process that keeps evolving.</p>
<hr />
<p>Since you have managed to read the post until here, here is a pic of a cute fox for you.</p>
<p><img src="/blog/assets/images/on-seniority-in-software-engineering/fox.jpg" alt="[Kaynak](https://juristr.com/blog/2013/04/git-explained/)" class="center-image" /></p>
<div style="text-align: center;">
<small>Photo by <a href="https://unsplash.com/@olga_kononenko">Olga Kononenko</a> on <a href="https://unsplash.com/photos/FdSD_9r8Uy8">Unsplash</a></small>
</div>Burak Karakanwell, a complicated topic. here are bunch of thoughts.wtf is docker?2019-02-09T00:00:00+00:002019-02-09T00:00:00+00:00http://burakkarakan.com/blog/wtf-is-docker<p>Alright, we get it, cloud is the future and we need to use containers with all the fancy tools it offers. We are going to <em>containerize</em> our app, use <em>container orchestration</em> tools for deployments, and we <em>have to install Docker</em>.</p>
<h2 id="wtf-is-a-container">wtf is a container?</h2>
<p>Remember the good old times where you used to ssh into the production server, go to the project directory, and run <code class="language-plaintext highlighter-rouge">git pull</code> to deploy your code? Before you deploy anything, in the very beginning of the life of your server, you’d install all the global dependencies for your app, <code class="language-plaintext highlighter-rouge">curl</code> most probably, then <code class="language-plaintext highlighter-rouge">git</code>, maybe the interpreter for the language you want to use, and some extensions for that as well, maybe <code class="language-plaintext highlighter-rouge">nginx</code> at some point. Once all the dependencies are installed, you’d bring your application to the server, run some commands and start it.</p>
<p>At this point, once you <code class="language-plaintext highlighter-rouge">pull</code>ed your code, you’d start the new version of your application, or you’d restart nginx for some changes to take effect, or whatever. This setup probably worked for a long time, until it didn’t. One of the developers in your team relied on a system dependency that has a different version installed in the production server, and now your service is down. You quickly rollback your changes, but you will need to update that dependency at some point. A worse example may be the bugs caused by these kind of dependency differences in a weird place of your app, which means you probably wouldn’t notice until it is too late, in other words, <em>already shipped</em>.</p>
<p>Consider another example, where you’d like to run multiple application on the same host, but you need them to be isolated for security reasons. You either need to move the applications into separate hosts, which is not cost efficient, or you’d run two different virtual machines in the host, which would give you the isolation but the resources will be consumed by the VMs mostly rather than your application, which is still not the best way.</p>
<p>These problems have existed for decades now; keeping the processes separate is a huge pain, and this caused a lot of security problems as well as inefficient setups.</p>
<h2 id="linux-containers-to-the-rescue">linux containers to the rescue</h2>
<p>In this context, a container is a set of isolated processes and resources. Linux achieves this by using namespaces, which allows processes to access only namespace resources, which allows to have a process tree that is completely independent of the rest of the systems. The actual way containers work is a complex topic that I will not get into here, but overall the concept is simple: give me independent resources in a physical machine that I can do whatever I want.</p>
<h2 id="enter-docker">enter docker</h2>
<p>Docker is one of the tools that used the isolated resources idea to create a set of tools that allows applications to be packaged with all the dependencies installed and ran wherever wanted.</p>
<p>I can hear the thoughts like “this is the same thing as virtual machines”, but the difference is, Docker containers share the same system resources, they don’t have separate, dedicated resources that allows them to behave like completely independent machines, they don’t need to have a full blown OS inside, and these advantages allow these containers to be pretty lightweight and efficient. A machine where you can run 2 VMs, you can run tens of Docker containers without any trouble, which means less resources = less cost = less maintenance = happy people.</p>
<p>Regarding the differences between the Docker containers and VMs, <a href="https://stackoverflow.com/a/16048358">here is a nice answer in StackOverflow</a>:</p>
<blockquote>
<p>Docker originally used LinuX Containers (LXC), but later switched to runC (formerly known as libcontainer), which runs in the same operating system as its host. This allows it to share a lot of the host operating system resources. Also, it uses a layered filesystem (AuFS) and manages networking.</p>
<p>AuFS is a layered file system, so you can have a read only part and a write part which are merged together. One could have the common parts of the operating system as read only (and shared amongst all of your containers) and then give each container its own mount for writing.</p>
<p>So, let’s say you have a 1 GB container image; if you wanted to use a full VM, you would need to have 1 GB times x number of VMs you want. With Docker and AuFS you can share the bulk of the 1 GB between all the containers and if you have 1000 containers you still might only have a little over 1 GB of space for the containers OS (assuming they are all running the same OS image).</p>
<p>A full virtualized system gets its own set of resources allocated to it, and does minimal sharing. You get more isolation, but it is much heavier (requires more resources). With Docker you get less isolation, but the containers are lightweight (require fewer resources). So you could easily run thousands of containers on a host, and it won’t even blink. Try doing that with Xen, and unless you have a really big host, I don’t think it is possible.</p>
</blockquote>
<p>Docker has two concept that is almost the same with its VM containers as the idea, an <em>image</em> and a <em>container</em>. An image is the definition of the what is going to be executed, it is just like an operating system image, and a container is the running instance of a given image.</p>
<h2 id="gimme-a-practical-example">gimme a practical example</h2>
<p>Docker images are defined within special text files called <code class="language-plaintext highlighter-rouge">Dockerfile</code>, and you need to define all the steps explicitly inside the Dockerfile. Here goes an example from one of my images that has Python 3.7, Chromium, Selenium + Chrome Driver and pytest in it, this is an actual image that I use for some of my acceptance test pipelines.</p>
<div class="language-dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> python:3.7-alpine3.9</span>
<span class="c"># update apk repo</span>
<span class="k">RUN </span><span class="nb">echo</span> <span class="s2">"http://dl-4.alpinelinux.org/alpine/v3.9/main"</span> <span class="o">>></span> /etc/apk/repositories <span class="o">&&</span> <span class="se">\
</span> <span class="nb">echo</span> <span class="s2">"http://dl-4.alpinelinux.org/alpine/v3.9/community"</span> <span class="o">>></span> /etc/apk/repositories
<span class="c"># install chromedriver</span>
<span class="k">RUN </span>apk update
<span class="k">RUN </span>apk add chromium chromium-chromedriver
<span class="c"># install selenium</span>
<span class="k">RUN </span>pip <span class="nb">install </span><span class="nv">selenium</span><span class="o">==</span>3.8.0 pytest pytest-xdist
</code></pre></div></div>
<p>It uses <code class="language-plaintext highlighter-rouge">python</code> base image with the tag <code class="language-plaintext highlighter-rouge">3.7-alpine3.9</code>, which is a specific version. It than puts the community repository definitions and updates the repository indexes; if you are thinking like “wtf is a community repository”, give <a href="https://wiki.alpinelinux.org/wiki/Enable_Community_Repository">this</a> piece a look. Then it basically uses <code class="language-plaintext highlighter-rouge">apk</code> to install <code class="language-plaintext highlighter-rouge">chromium</code> and <code class="language-plaintext highlighter-rouge">chromium-chromedriver</code>, and uses <code class="language-plaintext highlighter-rouge">pip</code> to install <code class="language-plaintext highlighter-rouge">selenium</code>, <code class="language-plaintext highlighter-rouge">pytest</code> and <code class="language-plaintext highlighter-rouge">pytest-xdist</code> which basically allows running pytest tests in parallel.</p>
<p>Once you have this file and named it as <code class="language-plaintext highlighter-rouge">Dockerfile</code>, you can just run this command to build your image:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker build <span class="nb">.</span> <span class="nt">-t</span> my-image-name:my-tag
</code></pre></div></div>
<p>It will build your image, and once the build is done, you can run your image with this simple command:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">-it</span> my-image-name:my-tag /bin/sh
</code></pre></div></div>
<p>This command will give you a shell inside the container, which you can use to do whatever you want. At this point, in order to understand the concept a little bit better, open another terminal session while keeping the one in the container running, and run <code class="language-plaintext highlighter-rouge">docker ps</code>, which will give you an output like this:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4a81c6a020c8 my-image-name:my-tag <span class="s2">"/bin/sh"</span> 44 seconds ago Up 44 seconds cocky_herschel
</code></pre></div></div>
<p>The one listed there is your currently running container, which you can see with the <code class="language-plaintext highlighter-rouge">IMAGE</code> column set to your new image. However, there is a small detail: if you exit the shell session inside the container by running <code class="language-plaintext highlighter-rouge">exit</code> or <code class="language-plaintext highlighter-rouge">CTRL+D</code>, your container will <em>die</em> and <code class="language-plaintext highlighter-rouge">docker ps</code> will give you an empty output.</p>
<p>There is a simple explanation behind this behavior; when you have executed the runner command above as <code class="language-plaintext highlighter-rouge">docker run -it my-image-name:my-tag /bin/sh</code>, you have basically told Docker to start this container with the <code class="language-plaintext highlighter-rouge">/bin/sh</code> process as the main process inside the container, which means once your process is dead, which is what happens when you exit the shell session, your container will die, simple as that.</p>
<h2 id="so-should-i-use-this-thing">so, should I use this thing?</h2>
<p>Overall, Docker allows applications to be packaged with all the dependencies inside, which simplifies the deployment process quite a bit and you get to have full reproducible environments. Docker by itself is not always enough and easy to use if you have multiple services that needs to be running together, then you can use <a href="https://docs.docker.com/compose/">Docker Compose</a> which comes with Docker and is great for managing multiple containers and allowing inter-container communication and it is damn easy to get started.</p>
<p>I have started using Docker for my side projects around a year ago, and my experience has been a real pleasure. I am able to run isolated pipelines for my CI/CD needs, I am able to coordinate with other developers without considering platform-specific dependencies, and deploying my applications is easier than ever. There are also a lot of new stuff going on around containers, and one of the biggest hypes is around <a href="https://kubernetes.io/">Kubernetes</a>, which is an open-source container orchestration tool that allows you to run your containers on a cluster with autoscaling, rollouts, rollbacks and self-healing deployments, it is pretty cool.</p>
<p>Give this thing a try, YMMV but for most of the cases it will allow you to move faster and simplify your workflow. If not, well, <code class="language-plaintext highlighter-rouge">¯\_(ツ)_/¯</code>.</p>Burak Karakanhow does this docker thingy work? why should I use it?git sunucu servisleri2017-07-20T00:00:00+00:002017-07-20T00:00:00+00:00http://burakkarakan.com/blog/git-sunucu-servisleri<p><img src="/blog/assets/images/git-servisler/github-gitlab-bitbucket.jpeg" alt="[Kaynak](http://www.amarinfotech.com/gitlab-vs-github-vs-bitbucket.html)" /></p>
<p><em><a href="http://www.amarinfotech.com/gitlab-vs-github-vs-bitbucket.html">Kaynak</a></em></p>
<p>Git’in ne olduğunu tartıştığımız <a href="/git-nedir/">git nedir?</a> adlı yazımızda bahsetmediğimiz, fakat bazı okuyucuların aklında canlanacak olan “lao biz git push yapıyoruz tamam da nereye yapıyoruz bunu?” sorusunu tartışmak istiyoruz bu yazıda.</p>
<p>Git’in takımlar arasında iş akışını hızlandırması ve tarafların sorunsuzca senkronize olabilmesini sağlamak için bir çeşit Git sunucusu gerekmekte. Bu sunucu, kullanıcıların yolladığı git repo’larını belirli bir düzen ve hiyerarşi içerisinde saklamalı, bu sayede de kodların kullanıcılar arasında paylaşılmasını sağlamalı. Bu noktada her şeyi çok basit hayal edebilirsiniz: uzak bir makinede boş bir git repo’su oluşturulacak, bir kullanıcıya bu repo’ya erişme izni verilecek, SSH kullanarak git push yapılacak, hiç bir problem kalmayacak. Bu şekilde bir setup yapmak için <a href="https://www.linux.com/learn/how-run-your-own-git-server">şuradan ulaşılabilecek</a> İngilizce bir kaynak var, böyle bir sunucunun nasıl hazırlanacağını adım adım anlatmış, gayet temiz. Böyle bir Git sunucusu kodun paylaşılmasına izin veriyor vermesine fakat hem her repo için makineye SSH çekip manuel olarak repo’yu oluşturma ihtiyacı ortaya çıkıyor, hem de olur olmaz insanlara bu makineye erişme izni vermeniz gerekiyor, bu sürecin tümü de hızlı çalışmayı amaç haline getirmiş geliştiriciler için işkence haline geliyor.</p>
<p>Bu noktada çeşitli Git sunucu servisleri doğmuş işte. Aslında Amerika’yı yeniden keşfetmemişler, böyle bir ihtiyacın olduğu Git öncesi versiyon kontrol sistemlerinde de belli olduğu için varolan çözümlerin Git’e adapte edilmesi gerekmiş sadece. Bu süreç içerisinde çeşitli servisler gelmiş gitmiş, biz bu yazıda en popüler olan 3 tanesinden bahsedeceğiz. Bu servislerin avantajlarından ve dezavantajlarından bahsedeceğiz, bununla beraber hangi bütçe ve senaryolarda hangi servisin kullanılabileceği yönünde fikir vermeye çalışacağız.</p>
<h2 id="github">GitHub</h2>
<p><img src="/blog/assets/images/git-servisler/github-logo.png" alt="GitHub Logo" /></p>
<p>Şüphesiz ki Git sunucu servislerinin en meşhurudur GitHub. <a href="https://github.com/about">Sitelerinde yazdığına göre</a> <strong>23 milyondan fazla kullanıcı ve 63 milyondan fazla proje varmış GitHub üzerinde.</strong> En popüler olmasının altında sanıyorum bu alandaki ilklerden olması yatıyor, fakat bugün en popüler açık kaynak projelerin çoğu GitHub üzerinde tutuluyor, en önemli geliştirmeler, tartışmalar ve iş birlikleri GitHub üzerinde yaşanıyor, bu da platformu herkes için daha cazip bir hale getiriyor.</p>
<p>GitHub’ın en temel özelliklerinden biri açık kaynak geliştirme işini bir sosyal medyaya çevirmesi aslında. Sevdiğiniz geliştiricileri ve organizasyonları takip edebiliyor, projelerinde yaptıkları güncellemeleri ve geliştirmeleri okuyabiliyorsunuz. Aynı zamanda kullandığınız açık kaynak bir paket için siz de geliştirmeye katkıda bulunabiliyor veya geliştirenlere hata bildirimlerinde bulunarak yardımcı olabiliyorsunuz. Bugün bu gibi özellikler tüm popüler Git sunucu servislerinde mevcut, fakat bunları popüler hale getiren GitHub oldu.</p>
<h3 id="maliyet">Maliyet</h3>
<p>GitHub tamamen ücretsiz kullanılabilecek bir servis aslında. Public repo’lar için hiç bir ücret talep etmiyor GitHub, dolayısıyla sınırsız sayıda repo’nuz olabiliyor. Ücretlendirme ise private repo’lar oluşturmak istediğinizde karşınıza çıkıyor. Fiyatlar şu şekilde görünüyor:</p>
<p><img src="/blog/assets/images/git-servisler/github-pricing.png" alt="GitHub Pricing" />
<em><a href="https://github.com/pricing">Kaynak</a></em></p>
<p>Bununla beraber GitHub bir <a href="https://education.github.com/pack">Student Pack</a> de sunuyor. Bu paket sayesinde eğer öğrenciyseniz çeşitli servislerin GitHub sayesinde size sunduğu özel servislerden faydalanabilirsiniz. Yine aynı paket ile yukarıdaki “Developer” pakedini de ücretsiz olarak kullanabiliyorsunuz.</p>
<p><strong>Guncelleme:</strong> 2019 itibariyle GitHub private repo’lari de maksimum 3 contributor ile kullanilabilecek sekilde serbest birakmis bulunmakta.</p>
<p><strong>GitHub en popüler Git sunucu servisi şu an</strong>, dolayısıyla güvenilirliği de yüksek. Eğer büyük projelerde çalışıyorsanız veya GitHub’ı sık sık kullanıyor ve “aaaaabii yeni bi sekmede başka servise mi geççeem şimdi yiaa” diyorsanız ücretli paketler işinizi görebilir. Ayriyetten “GitHub Enterprise” pakedi ile GitHub yazılımını satın alıp kendi sunucularınızda saklayabilirsiniz.</p>
<h3 id="popüler-projeler">Popüler Projeler</h3>
<p><a href="https://github.com/trending">GitHub Trending</a> sayesinde GitHub üzerinde günlük, haftalık ve aylık olarak trend olan repo’ları görüntüleyebilir ve bunları dillere göre filtreleyebilirsiniz. Bunun dışında <strong>GitHub en popüler açık kaynak projelere ev sahipliği yapıyor</strong> demiştik. Bugün itibariyle <a href="https://github.com/freeCodeCamp/freeCodeCamp">FreeCodeCamp</a>, <a href="https://github.com/twbs/bootstrap">Bootstrap</a>, <a href="https://github.com/facebook/react">React</a>, <a href="https://github.com/tensorflow/tensorflow">TensorFlow</a>, <a href="https://github.com/vuejs/vue">Vue.js</a>, <a href="https://github.com/angular/angular.js">Angular</a> gibi dev açık kaynak projeler GitHub üzerinde geliştiriliyorlar. Bunların dışında bahsetmemiz gereken belki de en önemli repository <a href="https://github.com/torvalds/linux">Linus Torvalds’ın Linux çekirdek repo’su</a> olabilirdi, fakat GitHub üzerinde bu kaynak kodun sadece okunabilir bir versiyonu saklanmakta, geliştirilmeleri mail grubu üzerinden yapılmakta.</p>
<h2 id="bitbucket">Bitbucket</h2>
<p><img src="/blog/assets/images/git-servisler/bitbucket-logo.png" alt="Bitbucket Logo" /></p>
<p>Piyasanın abisi <a href="https://www.atlassian.com/">Atlassian</a>’ı bilen bilir. Bu reisleri <strong>JIRA</strong> ile bilirsiniz belki, bunun dışında <strong>HipChat</strong>, <strong>Confluence</strong> gibi ürünleri var. Son olarak da geçtiğimiz sene <strong>Trello’yu satın aldı</strong> babalar, böyle böyle içten fethediyorlar piyasayı. İşte bu <strong>Bitbucket</strong> da Atlassian’ın sunduğu, <strong>Atlassian’ın diğer tüm ürünleri ile entegre çalışabilen bir Git sunucu servisi.</strong> 2008 yılında kurulan Bitbucket, 2010’da Atlassian tarafından satın alınmış; ilk odağı Mercurial projeleri olan bu servis zaman içerisinde yüzünü Git projelerine dönmüş.</p>
<p>GitHub’ın aksine Bitbucket ücretsiz planında <strong>sınırsız public ve private repo</strong> sunan servislerden biri. Repo sayısı sınırlanmamış fakat bu plan içerisindeki takım maksimum 5 kişiden oluşabiliyor. Bu plan aslında küçük takımlar için ideal, bilhassa JIRA ve amcaoğullarını kullanıyorsanız iş akışınıza katkısı olacağı kesin. <a href="https://bitbucket.org/product/comparison/bitbucket-vs-github">Şurada</a> görüldüğü kadarıyla Bitbucket kendini GitHub ile kıyaslamış, demiş ki “<em>GitHub’da code approval -takım arkadaşlarının kodlarının incelenmesi, bunun ardından beğenenlerin onaylaması- nanesi yok bizde var, bi de bizde continuous integration var</em>”. Code approval neyse de, continuous integration önemli mesele. Biliyorsunuz bu continuous integration mühim mesele. Temelde kod uzak sunucuya push’landığında testlerin koşturulması, eğer ilgili kod testleri başarıyla geçiyorsa da istenen branch’e uzak sunucu üzerinde merge’lenmesi pratiği olan continuous integration, <a href="https://bitbucket.org/product/features/pipelines">Bitbucket Pipelines</a> ile tamamen Bitbucket üzerinden halledilebiliyormuş, merak eden detaylarını okuyabilir.</p>
<h3 id="maliyet-1">Maliyet</h3>
<p>Fiyatlandırması GitHub’a göre daha uygun olan Bitbucket planları ise şu şeklide:</p>
<p><img src="/blog/assets/images/git-servisler/bitbucket-pricing.png" alt="Bitbucket Pricing" />
<em><a href="https://bitbucket.org/product/pricing?tab=host-in-the-cloud">Kaynak</a></em></p>
<p>Bitbucket aynı zamanda güzel paralara <strong>kendi sunucunuzda</strong> kullanabileceğiniz versiyonunu da satıyor, ilgilisi kaynak linkinden detayları inceleyebilir.</p>
<p>Bitbucket da bu şekil, bilhassa Atlassian ürünü kullanan geliştirici takımlarını hedefleyen bir ürün. Kişisel projelerinizi saklamak için gayet kullanışlı bir servis, arayüzü falan da gayet temiz, denemeye değer. Popüler projeler hakkında bir fikrimiz yok, GitHub gibi açık kaynak yazılım merkezi değil Bitbucket, daha çok kendi kendine takılan kullanıcıyı hedefliyor.</p>
<h2 id="gitlab">GitLab</h2>
<p><img src="/blog/assets/images/git-servisler/gitlab-logo.png" alt="GitLab Logo" /></p>
<p>Geldik gönlümün efendisine: <strong>GitLab</strong>. GitLab 2011 yılında ilk adımları atılmış, başından beri <strong>açık kaynak olarak geliştirilen</strong> bir Git sunucu servisi. Reisler başlarda açık kaynak olarak geliştirmişler, sonra kullanıcı sayıları artmış vesaire. Bu süreçte bazı büyük organizasyonlar bu abilerden yeni özellikler talep etmişler, e bu abiler de “<em>taş mı yiyek</em>” demiş, GitLab Enterprise’ı çıkarmışlar. 2011’den bugüne kadar <strong>proje halen açık kaynak olarak geliştiriliyor</strong> ve bu açık kaynak olan versiyona <strong>GitLab Community Edition</strong> adı veriliyor. Velhasılıkelam, GitLab size tamamen ücretsiz bir alternatif sunuyor, temel özelliklerin hiç biri için para ödemeniz gerekmiyor.</p>
<h3 id="maliyet-2">Maliyet</h3>
<p>Aslında Community Edition’u indirip <strong>kendi sunucunuzda</strong> saklayabiliyorsunuz, fakat bu benim çok da umrumda olmayan bir özellik açıkçası. Bunun yerine, direk <a href="https://gitlab.com">gitlab.com</a> üzerinden kayıt olduğunuzda <strong>Enterprise Edition’u tamamen ücretsiz olarak kullanabiliyorsunuz</strong>. Bu Enterprise Edition içerisinde de Free, Bronze, Silver ve Gold adında 4 farklı plan var, fakat şu an için Free ve Bronze planlar da Silver planın özelliklerini kullanabiliyormuş, gelecek dönemde kaldırılacak yazıyor sitelerinde. Fiyatlar ise şu şekilde:</p>
<p><img src="/blog/assets/images/git-servisler/gitlab-pricing.png" alt="GitLab Pricing" />
<em><a href="https://about.gitlab.com/gitlab-com/">Kaynak</a></em></p>
<p>GitLab de yine Bitbucket gibi continuous integration hizmeti sunuyor, direk sistem üzerinden continuous integration süreçlerini yönetebiliyorsunuz. Yine aynı şekilde sistem üzerinde kullanabildiğiniz Kanban board’da mevcut, tüm issue’larınızı çeşitli etiketlere göre board’lara ayırıp on numero iş takibi yapabiliyorsunuz. Bitbucket’taki approval falan alayı GitLab’de de var, üstüne işte bir kamyon fazlası var.</p>
<h3 id="popüler-projeler-1">Popüler Projeler</h3>
<p>Bir kere [<strong>GitLab Community Edition](https://gitlab.com/gitlab-org/gitlab-ce) tamamen Gitlab.com üzerinde saklanıyor</strong>, en önemlisi bu sanıyorum. Bunun dışında <a href="https://gitlab.com/gitlab-org/gitter/">Gitter</a>, <a href="https://gitlab.com/tortoisegit/tortoisegit/">TortoiseGit</a>, <a href="https://gitlab.com/inkscape/inkscape-web">Inkscape</a> gibi çeşitli büyük açık kaynak projeler de GitLab üzerinde saklanıyor. Tabii ki açık kaynak konusunda GitHub kadar popüler değil, fakat private repo’lar konusunda GitHub üzerine çok rahat tercih edilebilecek durumda.</p>
<h2 id="sonuç">Sonuç</h2>
<p>Yukarıda anlattık, dedik bu bunu yapar, şu şunu yapar. Her bir sistemin kendine göre artıları ve eksileri var, burada tercihi duruma göre yapmak gerekiyor. Eğer açık kaynak bir proje geliştiriyor ve bir komünite desteğine ihtiyaç duyuyorsanız <strong>GitHub</strong> en doğru çözüm olacaktır, eğer kısa zamanda güzel ilgi çekerseniz Trending sekmesi sayesinde çok iyi destek bulabilirsiniz projenize. Eğer bir takım olarak <strong>JIRA</strong>, <strong>HipChat</strong> gibi Atlassian ürünleri kullanıyorsanız ve projelerinizi özel repo’larda geliştirmek istiyorsanız <strong>Bitbucket</strong> çok temiz bir çözüm olacaktır sizin için, tüm platformlar ile tam entegrasyon içinde. Bunun dışında kalan irili ufaklı takımlar ve kişisel işlerin tümü için <strong>GitLab</strong> en doğru seçim gibi görünüyor. Ben bahsi geçen bu servislerin tümünü çeşitli amaçlar için kullanıyorum, kullanmaktan en keyif aldığım servis ise GitLab. Gerek proje yönetimi, gerek beleşe dünyaları sunmaları gibi detaylar bu hislerimde etken sanıyorum.</p>
<p>Eğer hatamız varsa affola, küçük araştırmalarla böyle bir derleme sunmak istedik. Yanlış gördüğünüz, düzeltilmesi gerektiğini düşündüğünüz bir nokta varsa yorumlardan veya <em>burak.karakan@gmail.com</em> adresinden bana ulaşabilirsiniz. Yazıyı inceleyip feedback verdiği için <a href="https://github.com/furkanhatipoglu">Furkan Hatipoğlu</a> kankimi de öpüyorum burdan. İyi akşamlar cümleten, her nerede yaşıyor ve yaşatılıyor iseniz…</p>
<p><img src="/blog/assets/images/git-servisler/reha.jpeg" alt="Reha Muhtar" class="center-image" /></p>Burak KarakanGit sunucusu olarak kullanabileceginiz servisler ve ozelliklerinin karsilastirilmasi.git nedir?2017-07-13T00:00:00+00:002017-07-13T00:00:00+00:00http://burakkarakan.com/blog/git-nedir<p><img src="/blog/assets/images/git-nedir/git-logo.png" alt="[Kaynak](https://juristr.com/blog/2013/04/git-explained/)" /><em><a href="https://juristr.com/blog/2013/04/git-explained/">Kaynak</a></em></p>
<p>Teknoloji dünyasında belki de en sık kullanılan, en sık kullanılması gereken programlardan biri <code class="language-plaintext highlighter-rouge">git</code> sanıyorum. Bu kadar hayati öneme sahip olmasına rağmen hem bu konuda pek kaynak olmadığını, hem de sık sık yanlış pratiklerin uygulandığını görüyoruz. Bu konu hakkında sizinle bir çay içmek isterdim fakat şu an için yazıyla yetinmek gerek sanıyorum. Yazı biraz uzun olabilir, arada goygoy falan vardır, affola. Direk uygulamaya geçmek isteyenler “Nasıl kullanıyoruz bu meredi?” adlı bölüme atlayabilir, direk uygulamalı olarak öğrenmeyi deneyebilirler; yazının ilk yarısında Git’in tarihinden ve nasıl çalıştığından bahsetmeye çalıştım.</p>
<h1 id="git-nedir"><code class="language-plaintext highlighter-rouge">git</code> nedir?</h1>
<p><code class="language-plaintext highlighter-rouge">git</code> aslında bir versiyon kontrol sistemidir, İngilizce kaynaklarda VCS — Version Control System olarak geçer. Temel amacı, yazılım geliştirme sürecini küçük parçalara bölmek, projenin hayatı boyunca çeşitli mihenk taşları yerleştirmek ve bu mihenk taşları arasında hızlıca hareket edebilmenizi sağlayabilmek aslında. Tamamen bir komut satırı programı olan *Git *sayesinde projede yaptığınız herhangi bir değişikliği o an kaydedebilir, dilediğiniz zaman bu değişikliği geri alabilir veya bir süre sonra projenin bu noktadaki haline geri dönebilirsiniz.</p>
<p>Örneğin projenizde yeni bir özellik geliştiriyorsunuz, fakat bu özelliği geliştirirken projenizi bozabileceğinizi öngörüyorsunuz. İşte tam bu noktada, bu riski almadan önce Git’e diyorsunuz ki “<em>projenin şuanki halini kaydet, sonra sorucam sana.</em>”, o da “<em>eyvallah reis</em>” diyip projenin o anki halini alıp saklıyor. Bunu yaparken de dosyaları olduğu gibi kopyalamıyor, tam o ana ait bir imaj saklıyor, bunun nasıl olduğunu daha sonra tartışacağız. Bu işlemin arkasından siz istediğiniz değişiklikleri yapıyorsunuz, artık olursa olur suyu, olmazsa bulgur suyu. Yaptığınız değişikliğe küfürler edip geri almak istediğiniz noktada Git kardeşimize söylüyorsunuz, lafınızı ikiletmeden eski halini getiriyor.</p>
<p>Yazılım geliştirme işi sık sık hata ayıklama, deneme-yanılma gibi işler gerektirdiği için böyle bir versiyon kontrol sisteminin çok önemli olduğu konusunda anlaştığımızı varsayıyorum. Fakat Git yalnızca bizim arkamızı toplamakla kalmıyor, aynı şekilde aynı proje üzerinde çalışan takımların da işini müthiş kolaylaştırıyor. Proje üzerindeki değişikliklerin paylaşılmasını sağlıyor, aynı satır üzerinde işlem yapıldığında tercih yapma zorunluluğu getiriyor, geliştiriciye özel dosyaların dağıtılmasını engelliyor, kısacası takımların işini muazzam ölçüde kolaylaştırıyor.</p>
<h1 id="tarihi">Tarihi</h1>
<p>İşin aslı, bu Git dediğimiz araç, Linux’un yaratıcısı abimiz Linus Torvalds tarafından yaratılmış. Sitelerine koydukları kısa tarihlerine göre, <a href="https://git-scm.com/book/en/v2/Getting-Started-A-Short-History-of-Git">buradan ulaşabilirsiniz</a>, 2002 yılında Linux projesi için versiyon kontrol sistemi olarak BitKeeper adlı ücretli aracı ücretsiz olarak kullanmaya başlamışlar. Fakat 2005 yılında bu şirket ile olan ilişkileri bozulmuş, şirket de kendilerine verilen beleş iznini kaldırmış. Ek olarak <a href="https://en.0wikipedia.org/index.php?q=aHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvR2l0I0hpc3Rvcnk">Wikipedi’de yazıldığına göre</a> BitKeeper adlı şirket, Linux çekirdek geliştirme projesine katkıda bulunan geliştiricilerden <a href="https://en.0wikipedia.org/index.php?q=aHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvQW5kcmV3X1RyaWRnZWxs">Andrew Tridgell</a> adlı abinin BitKeeper’ın kaynak koduna ters-mühendislik yapıp araklamaya çalıştığını iddia etmiş, bu da bu ilişkinin bitişine sebep olmuş. Bu olaya sinirlenen Linus reis sizi bitirecem demiş, sonuç olarak Git’i yaratmış.</p>
<p>Linus reis aslında Git’i yaratmadan önce problemi varolan uygulamalar ile çözmek istemiş. Bu sebeple BitKeeper ve benzeri uygulamaları kullanırken gördüğü eksikleri düzeltilmesi gereken problemler olarak not almış, araştırmalarını bu yönde yapmış. Fakat halihazırda piyasada bulunan araçlar sorunlarına çözüm olmayınca kendi araçlarını yapma ihtiyacı hissetmiş. Velhasılıkelam, Linus reis 3 Nisan 2005’te başlamış projeye. <a href="https://marc.info/?l=git&m=117254154130732">Bu mail</a> üzerinden anlattığı tarihlere göre, 6 Nisan’da duyurmuş projeyi, 7 Nisan’da da proje kendi versiyonlamasını yönetiyor hale gelmiş, self-hosting denilen durum. Linux çekirdeğinde yapılan ilk commit ise 16 Nisan tarihini göstermekte imiş. Linus başkan iki hafta içerisinde projeyi ana hatlarıyla ortaya koyduktan sonra da 26 Temmuz tarihinde Junio Hamano adlı abimize teslim etmiş projenin bakım ve sürdürülme işlemlerini, ki kendisi halen Git projesinin ana geliştiricisi imiş, <a href="https://marc.info/?l=git&m=112243466603239">şurada Linus Torvalds’ın kendisini mail grubuna tanıttığı mail görülebilir</a>.</p>
<p>Gerek olmadığı halde tarihe girdik, açıkçası ben de merak ediyodum, biraz da o yüzden gireyim dedim, neyse devam ediyorum şimdi.</p>
<h1 id="nasıl-çalışır">Nasıl çalışır?</h1>
<p>Git tamamen dağıtık bir versiyon kontrol sistemi. Bu şu anlama geliyor: projenin herhangi bir yerdeki herhangi bir kopyası tam bir depo (repository) halinde saklanıyor. Yani projenin uzak sunucudaki kopyasını bilgisayarınıza kopyaladığınızda proje olduğu gibi bilgisayarınıza geliyor, bilgisayarınızda o proje deposunun tamamı saklanıyor. Bu proje depolarına <strong>repository</strong> deniyor, bu kelimenin kısası olarak <em>repo</em> diyeceğim yazının bazı yerlerinde. Dolayısıyla ilk kopyalama operasyonunuzla tüm repo bilgisayarınıza gelmiş oluyor.</p>
<p>Git, bu repo ile beraber üç katmandan oluşmakta. Birincisi, halihazırda çalışmakta olduğunuz klasörünüz, görselde “<em>Working Repository</em>”. İkinci katman ise “<em>Staging</em>” katmanı. Staging aşaması projenizde bazı değişiklikler yaptığınız fakat bu değişiklikleri henüz kaydetmediğiniz alandır. Staging’den sonra ise değişikliklerinizi kaydettiğiniz commit operasyonu gelir, bu da katman olarak değişikliklerinizi repo’nuza yazdığınız alandır. Görselde bu katmanlar kabaca görselleştirilmiş.</p>
<p><img src="/blog/assets/images/git-nedir/how-it-works.png" alt="How Git Works" /><em><a href="https://git-scm.com/book/en/v2/Getting-Started-Git-Basics">Kaynak</a></em></p>
<p>Projenizi bir git repo’sundan çalışma klasörünüze kopyalarsınız, görseldeki “<em>Checkout the project</em>” operasyonu, artık lokalinizde de bir git repo’nuz olur ve tüm işlemler bu lokal repo’nuz üzerinden ilerler. Ardından değişikliklerinizi yaparsınız fakat henüz kaydetmemişsinizdir, “<em>Stage Fixes</em>” operasyonu. Bu değişiklikleri kaydetmek istediğinizde ise ilgili komutu çalıştırarak “<em>Commit</em>” operasyonunu yapmış olursunuz. “<em>Commit</em>” işleminiz ile de lokal git repo’nuza yaptığınız değişikliklerin bir kaydı düşülmüş olur. İlgili operasyonların komutlarına daha sonra gelicez.</p>
<p>Git, diğer versiyon kontrol sistemlerinden biraz daha farklı çalışıyor. Subversion gibi versiyon kontrol sistemleri değişiklik yapılan dosyaların değişimlerini saklıyor. Yani versiyon 2’de değişen A dosyası için “versiyon 1’in üzerine neler değişmiş” bilgisini saklayarak versiyonları saklıyor. Fakat Git’te bu işlemler “snapshot”, yani anlık alınan görüntüler olarak saklanıyor. Bu şu anlama geliyor, her kayıt operasyonunuzla beraber Git, projenizin o anki halinin bir fotoğrafını çekiyor ve bunu saklıyor. Bu işlemde yalnızca değişen dosyaların tümünün fotoğrafını saklarken aynı kalan dosyaların son hallerine birer bağlantı tutuyor. Eğer “lao ben niye hep aynı dosyaları saklıyorum, aradakilere gerek yok” diyorsanız bunun için de bazı Git komutları var, ki bunlar delta, yani değişimleri saklayarak repo boyutunuzu küçültebiliyor, fakat temelde Git’in snapshot’larla çalıştığını bilmeniz yeterli. Eğer bu delta mantığının detaylarını merak ediyorsanız <a href="https://gist.github.com/matthewmccullough/2695758">şu</a> bağlantıdaki İngilizce döküman faydalı olabilir.</p>
<p>Versiyon kontrol sistemlerinin bir güzel yanı da dallar, yani branch’ler. Branch’ler aynı projenin birden fazla versiyonu üzerinde aynı anda çalışmanızı, bu versiyonlar arasında hızlıca gezinmenizi sağlarlar. Branch’ler bir kökten çıkıp ayrılan ve ileride tekrar birleşen farklı dallar gibi düşünülebilir kabaca. Aşağıdaki görsel yardımcı olabilir:</p>
<p><img src="/blog/assets/images/git-nedir/branches.png" alt="Sample Branching Flow" />
<em><a href="http://jlord.us/git-it/challenges/branches_arent_just_for_birds.html">Kaynak</a></em></p>
<p>Görseldeki siyah renkli yol “master” adı verilen, ana branch’i temsil ediyor. Bir projede Git kullanmaya başladığınız an varsayılan branch olarak “master” branch’i gelir ve bu branch üzerinde çalışmaya başlarsınız. Bu branch’in dışında mavinin çeşitli tonlarıyla gösterilmiş branch’ler ise özellik branch’lerini temsil ediyorlar. Branch’ler ile çalışırken bunlara benzer çeşitli branch’ler açabilir, her bir özelliği ayrı branch’lerde geliştirebilirsiniz. Bu sayede özellikler izole ortamlarda geliştirilebilir, geliştiriciler birbirlerinin işlerini bozmaz, ek olarak bir özelliğin geliştirilmesi de çalışmanızı bloklayacak şekilde olmaz.</p>
<p>Branch’lerin en önemli güzelliklerinden biri aynı anda farklı şeyler üzerinde çalışmaya izin vermesi aslında. Örneğin, projenizin ilk versiyonunu yayına çıktınız, geliştirmeye devam ediyorsunuz. İkinci versiyonunuzu çıkarırken bir özellik üzerinde çalışıyorsunuz. Fakat bir aksilik oldu, özelliği beklediğiniz sürede çıkaramıyorsunuz ve vakit ihtiyacınız var. Bu esnada patronunuz yayındaki sürümde önemli bir hata olduğunu, sorunu acilen düzeltmeniz gerektiğini belirtti. Siz bu sıralar ikinci versiyon için olan özelliği geliştiriyordunuz, fakat bu esnada projenin canını çıkarmışsınız, hiçbir kısmı doğru düzgün çalışmıyor, eski haline getirmeniz bile çok uzun zaman alacak. İşte tam bu noktada patronun yedi ceddine küfretmeden önce sakince konsolunuzu açıyorsunuz, sırasıyla aşağıdaki komutları yazıyorsunuz:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git checkout master
git checkout -b v1-bugfix
</code></pre></div></div>
<p>Sonuç olarak v1-bugfix adındaki lokkum gibi branch’inize geçmiş oluyorsunuz. Geliştirdiğiniz özellik kendi branch’inde duruyor, siz de yeni branch’inizde hatayı düzeltip geri birleştiriyorsunuz, patron mutlu, siz dertsiz, dünya birkaç saniyeliğine güzelleşiyor.</p>
<p>Bu branch’ler aslında ayrılan dallar, ve bunların ana dallarına birleştirilmeleri gerekiyor ki proje tek koldan ilerleyebilsin, herkesin çalışmaları aynı temelde buluşsun, işte bu birleştirme işlemine “merge” deniyor. Merge operasyonu, branch’leri birbirleriyle birleştirmeye yarar, aynı noktada buluşmalarını sağlıyor. Bu sayede her branch kendi getirdiği özelliği ana branch’e birleştiriyor, ana branch de adeta bir takım gibi diğer branch’lerin getirdiği her şeyi birleştirip kullanmış oluyor.</p>
<h1 id="nasıl-kullanıyoruz-bu-meredi">Nasıl kullanıyoruz bu meredi?</h1>
<p>Buradaki ders için bir Git repository’si oluşturdum, <a href="https://github.com/karakanb/medium-git-demo">şu</a> linkten ulaşabilirsiniz. Tüm komutlar bu yazı için oluşturduğum boş bir projede çalıştırılacak, içinde de çok küçük <a href="https://en.0wikipedia.org/index.php?q=aHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvTWFya2Rvd24">Markdown</a> dosyaları saklayacağım. İlerleyen bölümlerde Git’i Windows’ta kullanıyor olacağım, dolayısıyla komutları Windows üzerinde Git Bash ile kullanacağım, fakat MacOS veya Linux işletim sistemlerinde de aynı komutları kullanabilirsiniz, hiçbir şey değişmeyecektir. Başlıyoruz.</p>
<h2 id="yükleme">Yükleme</h2>
<p>Yükleme operasyonları işletim sistemleri arasında farklılıklar göstermekte. Nasıl kurulacağına dair gerekli bilgilere <a href="https://git-scm.com/book/tr/v1/Ba%C5%9Flang%C4%B1%C3%A7-Git-in-Kurulumu">buradan ulaşabilirsiniz</a>.</p>
<h2 id="kullanım">Kullanım</h2>
<p>Öncelikle projemizin başlayacağı klasörümüzü oluşturuyoruz. Kullanım için herhangi bir klasörde çalışmamız gerek, örnek olması açısından projeye sıfırdan başlıyoruz. mkdir komutu bize medium-git-demo adlı bir klasör oluşturacak, cd komutu ise oluşturduğumuz klasöre gitmemizi sağlayacak.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir medium-git-demo
cd medium-git-demo
</code></pre></div></div>
<p>Aslında her şey Git repo’muzu yaratmamızla başlıyor. Bunun için init komutunu çalıştıracağız.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git init
</code></pre></div></div>
<p>Bu komutu çalıştırmamızla beraber şöyle bir mesaj göreceksiniz:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Initialized empty Git repository in C:/Users/burak/Code/medium-git-demo/.git/
</code></pre></div></div>
<p>Bu, repo’muzu başarıyla oluşturduğumuz anlamına geliyor, tebrikler. Sıradaki işlemimiz ise yeni bir dosya oluşturmak. Test etmek için bir test dosyası oluşturacağız ve içine “Hello World” yazacağız, bunu aşağıdaki komut ile yapabiliyoruz.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>echo "Hello World" >> README.md
</code></pre></div></div>
<p>Yukarıdaki echo komutu ile README.md adlı bir dosya oluşturduk ve içine “Hello World” yazdık. Şimdi Git repo’muzda bu değişikliğin nasıl göründüğünü görmek istiyoruz. Bu iş için çok sık kullanacağımız bir komut var, o da git status komutu. Bu komut ile repo’muzun şu anki durumunda sahnelenmemiş ve sahnelenmiş fakat kaydedilmemiş dosyaları görebiliyoruz. Şimdi projemizde bunu test etmek için aşağıdaki komutu çalıştırıyoruz.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git status
</code></pre></div></div>
<p>Bu komutun çıktısı olarak ise aşağıdaki metni görüyoruz.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>On branch master
Initial commit
Untracked files:
(use "git add <file>..." to include in what will be committed)
** README.md**
nothing added to commit but untracked files present (use "git add" to track)
</code></pre></div></div>
<p>Çıktı metninde gördüğümüz metni satır satır incelersek:</p>
<ul>
<li>
<p>On branch master : master branch’inde olduğumuzu söylüyor, default branch’imiz.</p>
</li>
<li>
<p>Initial commit : Bunun ilk commit’imiz olacağını ifade ediyor.</p>
</li>
<li>
<p>Untracked files: : Burası listenin başlangıcı, hemen altında (use “git add <file>... şeklinde devam eden kısımla birlikte altında gelecek olan dosyaların **yeni eklenmiş** olduğunu gösteriyor.</file></p>
</li>
<li>
<p>README.md : Eklenen dosyamızın adı.</p>
</li>
<li>
<p>nothing added to commit but untracked files present : Henüz herhangi bir dosyanın commit’lenmediğini, fakat bazı takip edilmeyen dosyalar olduğunu ifade ediyor.</p>
</li>
</ul>
<p>Bu çıktı, sahnelenmiş ve sahnelenmemiş dosyaları farklı renklerde gösteriyor aynı zamanda, en azından Windows üzerinde default Git Bash kurulumunda böyle gösteriyor. Yukarıdaki çıktıda README.md dosyamız kırmızı görünüyor örneğin.</p>
<p>Buraya kadar anlaştıysak, dosyamızı sahneleme kısmına geliyoruz. Yeni dosyamızı eklemek için git add komutunu kullanıyoruz. Bu komut parametre olarak eklenecek dosyaların yolunu alıyor, biz içinde bulunduğumuz klasördeki dosyaların tümünü eklemek için şu anki klasörümüzü işaret eden . , yani nokta işaretini kullanacağız.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git add .
</code></pre></div></div>
<p>Komutu çalıştırdığımızda herhangi bir çıktı almayı beklemiyoruz, yalnız Windows sistemlerde aşağıdaki uyarıyı alabilirsiniz, yalnızca satır sonlarındaki karakterlerin Windows platformuna uygun olması için dönüştürüldüğünü haber veriyor, dolayısıyla önemli bir şey değil, es geçebilirsiniz.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>warning: LF will be replaced by CRLF in README.md. The file will have its original line endings in your working directory.
</code></pre></div></div>
<p>Şimdi tekrar git status yaptığımızda sahnelenmiş dosyamızı görebileceğiz.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git status
</code></pre></div></div>
<p>Çıktı olarak ise aşağıdaki metni alacağız:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
**new file: README.md**
</code></pre></div></div>
<p>Bu çıktıda ilk iki satır bir önceki status ile aynı. Fakat bu aşamada artık değişikliğimiz sahnelendi, commit’lenmeye hazır. Değişikliğimizi commit’lerken ne değişiklikler yaptığımızı açıklayan bir commit mesajı ile birlikte commit’liyoruz. Bu işlem için aşağıdaki komutu kullanıyoruz.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git commit -m "İlk dosyamızı oluşturduk."
</code></pre></div></div>
<p>Bu commit ile birlikte artık dosyamızı Git repo’muza kaydetmiş oluyoruz. Artık tekrardan git status komutunu çalıştırırsak nothing to commit, working tree clean şeklinde bir mesaj alacağız, bu da henüz sahnelenecek bir değişikliğimiz olmadığını belirtiyor.</p>
<p>Yeni dosya eklerken veya varolan bir dosyayı değiştirirken aynı işlemleri uyguluyoruz aslında. Bir örnek olarak bu dosyayı tekrardan değiştirelim. Dosyamızı Notepad, Sublime Text gibi herhangi bir metin editörüyle açıp içindeki metni aşağıdaki ile değiştirelim:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Merhaba Dünya!
</code></pre></div></div>
<p>Dosyamızı kaydedip çıkalım. Şimdi tekrar git status yaptığımızda ise ilk eklediğimizdekine benzer bir çıktı ile karşılaşıyoruz:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
**modified: README.md**
no changes added to commit (use "git add" and/or "git commit -a")
</code></pre></div></div>
<p>Bu sefer şunu görüyoruz ki artık Initial commit metni status çıktısında görünmüyor, bunun sebebi ilk commit’imizi artık tamamlamış olmamız. Bunun dışında bir de use “git checkout şeklinde bir satır gelmiş, bu metin de halihazırda yapmış olduğumuz değişiklikleri iptal etmek istiyorsak kullanabileceğimiz komutu gösteriyor, bunu da es geçiyorum. Yine aynı şekilde aşağıdaki komutları sırasıyla çalıştırırsak ikinci commit’imizi tamamlamış oluyoruz.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git add .
git commit -m "README değişiklikleri kaydedildi."
</code></pre></div></div>
<p>Bu operasyonların ardından ikinci commit’imiz de kaydedilmiş oluyor. Şu ana kadar iki commit gerçekleştirdik ve bir dosya üzerinde çalıştık. Bu iki komutu listelemek için ise git log` komutunu kullanabiliriz. Komutu çalıştırdığımızda ise aşağıdaki gibi bir çıktı alacağız:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>commit 7b02cffb132b78f4d40e925af71a961e9ea65847
Author: Burak Karakan <burak.karakan@gmail.com>
Date: Tue Jun 27 03:31:28 2017 +0300
**README değişiklikleri kaydedildi.**
commit babe48326ab25f7356535ecfceb6147d35d1f3c7
Author: Burak Karakan <burak.karakan@gmail.com>
Date: Tue Jun 27 03:22:41 2017 +0300
**İlk dosyamızı oluşturduk.**
</code></pre></div></div>
<p>Log çıktımızda kaydettiğimiz commit’lerimizi görebiliyoruz. Bununla beraber commit’i kim yapmış, ne zaman yapmış gibi bilgileri de edinebiliyor, aynı zamanda ilgili commit’in 7b02cf şeklinde görülen hash değerine de erişebiliyoruz. Bu değeri kullanarak ileride dilersek geri dönebiliriz bu commit’e.</p>
<h1 id="faydalı-komutlar">Faydalı Komutlar</h1>
<p><code class="language-plaintext highlighter-rouge">git</code> basli basina dev bir program, dolayisiyla tum ozelliklerini ve kisayollarini ogrenmek epey zaman alacaktir, ayni zamanda cogu durum icin de gereksiz olur.</p>
<h2 id="git-branch--git-checkout"><code class="language-plaintext highlighter-rouge">git branch</code> & <code class="language-plaintext highlighter-rouge">git checkout</code></h2>
<p>Yeni bir branch oluşturmak için iki komut kullanabiliriz, bunlardan ilki <code class="language-plaintext highlighter-rouge">git branch <branch_adı></code> şeklindeki komut. Bu komut ile <code class="language-plaintext highlighter-rouge"><branch_adı></code> adında bir branch’imiz olur, fakat henüz o branch’e geçmemişizdir. Şu an bulunduğumuz branch’i bırakıp yeni branch’imize geçmek istiyorsak <code class="language-plaintext highlighter-rouge">git checkout <branch_adı></code> şeklindeki komutu kullanabiliriz. Fakat hem yeni bir branch oluşturup hem de o branch’e geçmek istiyorsak bunun tek komuta indirilmiş hali olarak ikinci yöntemimiz olan <code class="language-plaintext highlighter-rouge">git checkout -b <branch_adı></code> var. Bu komut sayesinde hem <code class="language-plaintext highlighter-rouge"><branch_adı></code> adında bir branch oluşturuyoruz, hem de o branch’e geçiyoruz, ki kendisi çok kullanışlı bir komut, sık sık kullanırsınız muhtemelen.</p>
<p>Varolan bir branch’i silmek için ise <code class="language-plaintext highlighter-rouge">git branch -d <branch_adı></code> komutu kullanılabilir.</p>
<h2 id="git-merge"><code class="language-plaintext highlighter-rouge">git merge</code></h2>
<p>Branch açma işleminin tersi olarak sık sık branch’leri birleştirmemiz de gerekebiliyor. Bu işlemin adı “<em>merge</em>”, dolayısıyla işlemi yapan komut da <code class="language-plaintext highlighter-rouge">git merge <branch_adı></code> şeklinde. Adını <code class="language-plaintext highlighter-rouge"><branch_adı></code> şeklinde verdiğimiz branch’i şu an üzerinde bulunduğumuz branch’e birleştirir. Örneğin <code class="language-plaintext highlighter-rouge">feature</code> adlı branch’teyiz ve işimizi bitirdik, bu branch’i <code class="language-plaintext highlighter-rouge">dev</code> isimli branch’e merge etmek istiyoruz. Bu işlem için sırasıyla aşağıdaki komutları kullanabiliriz:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git checkout dev
git merge feature
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">checkout</code> komutu ile birlikte <code class="language-plaintext highlighter-rouge">dev</code> adlı branch’imize geçtik, <code class="language-plaintext highlighter-rouge">merge</code> komutu ile de <code class="language-plaintext highlighter-rouge">feature</code> adlı branch’i <code class="language-plaintext highlighter-rouge">dev</code> branch’ine birlestirmis olduk.</p>
<h2 id="git-diff"><code class="language-plaintext highlighter-rouge">git diff</code></h2>
<p>Bir branch’i bir diğerine merge’lemeden evvel değişiklikleri görmek isteyebilirsiniz. Bunun için kullanışlı bir komut olan <code class="language-plaintext highlighter-rouge">git diff</code> yardımcı olacaktır. Örnek kullanım ise şu şekilde:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git diff <asıl_branch_adı> <mergelenecek_branch_adı>
</code></pre></div></div>
<h2 id="git-clone"><code class="language-plaintext highlighter-rouge">git clone</code></h2>
<p>Uzak sunucuda varolan bir repo’yu kendi bilgisayarımıza çekmek istediğimizde ise ihtiyacımız olan operasyona klonlama deniyor, ilgili komut ise <code class="language-plaintext highlighter-rouge">git clone</code> komutu. Bu komut ile uzak sunucuda bulunan bir repository’i olduğu gibi bilgisayarımızda bir klasöre kopyalayabiliriz. Örnek olarak bu proje için kullandığım GitHub repo’sunu kendinize kopyalamak isterseniz çalıştıracağınız komut şu olacaktır:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone git@github.com:karakanb/medium-git-demo.git
</code></pre></div></div>
<p>Bu komut ile Git bizim için ilgili linkteki repo’yu bulacak, komutu çalıştırdığımız klasörde repo ile aynı isimde bir klasör oluşturacak ve dosyaları buraya indirecek. Örneğin yukarıdaki komutu çalıştırdığınızda bilgisayarınızda <code class="language-plaintext highlighter-rouge">medium-git-demo</code> adlı bir klasör oluşacak, onun içerisinde ise proje dosyalarına erişebileceksiniz.</p>
<p>Ek olarak bu operasyonun ardından Git, komuta verdiğiniz URL’i de projeye <code class="language-plaintext highlighter-rouge">origin</code> olarak ekleyecek. Bu <code class="language-plaintext highlighter-rouge">origin</code> ismi ise URL için bir kısaltma vazifesi görüyor. Bu kısayolu projenize ekledikten sonra URL yazmanız gereken komutlarda sadece <code class="language-plaintext highlighter-rouge">origin</code> yazarak istediğiniz sonuca ulaşabileceksiniz. Bu kısayol değişkenlerine uzak sunucu adreslerini temsil ettiklerinden <code class="language-plaintext highlighter-rouge">remote</code> adı veriliyor.</p>
<h2 id="git-push"><code class="language-plaintext highlighter-rouge">git push</code></h2>
<p>Lokalde istediğiniz geliştirmeleri yaptınız, bunları uzaktaki sunucuya ulaştırmak istiyorsunuz. Bu noktada ise <code class="language-plaintext highlighter-rouge">git push</code> komutu yardımınıza koşuyor. Bu komut sayesinde lokaldeki sunucunuzu uzak sunucunuzla eşleyebiliyorsunuz, repo’nuzun ayrı bir yedeğini de orda saklayıp başkalarıyla da paylaşabiliyorsunuz. Projenin içerisinde daha önceden tanımlanmış bir <code class="language-plaintext highlighter-rouge">origin</code> remote’unun olduğunu varsayıyorum, bu durumda repo’nuzu origin adresindeki uzak sunucuyla eşlemek için <code class="language-plaintext highlighter-rouge">git push origin master</code> demeniz yeterli olacaktır. Buradaki <code class="language-plaintext highlighter-rouge">origin</code> remote adını belirtirken <code class="language-plaintext highlighter-rouge">master</code> ise göndermek istediğiniz branch adını belirtir. Eğer bir clone operasyonu yaptıysanız ve düzenli olarak push yapıyorsanız, ilk push komutunuza <code class="language-plaintext highlighter-rouge">-u</code> şeklinde bir bayrak ekleyerek Git’in push operasyonu için default olarak bu adresi kullanmasını salık vermiş oluyorsunuz; bu default uzak sunucu adresine ise upstream branch deniyor. Bu sayede bu branch icin tekrar eden push operasyonlarınızda yalnızca <code class="language-plaintext highlighter-rouge">git push</code> yazmanız yeterli oluyor. Örnek bir kullanımı buraya bırakıyorum.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Git'e default olarak origin adresini kullanmasını söylüyoruz.</span>
git push <span class="nt">-u</span> origin master
<span class="c"># Artık sadece push yazmamız yeterli oluyor.</span>
git push
</code></pre></div></div>
<h2 id="git-pull"><code class="language-plaintext highlighter-rouge">git pull</code></h2>
<p>Diyelim ki bir takımla çalışıyorsunuz ve herkes aynı uzak sunucuya push yapıyor. Bu esnada zaman zaman sizin repo’nuz, uzak sunucudaki repo’dan geri kalacaktır doğal olarak. Repo’nuzu en güncel haline getirmek için ise git pull komutunu kullanabilirsiniz. git push komutu ile hemen hemen aynıdır kullanımı, yine eğer bir upstream branch’iniz var ise yalnızca git pull yazarak repo’nuzu güncelleyebilirsiniz. Aksi takdirde ise komutun tam halini yazarak çekmek istediğiniz adresi ve branch’i belirttiğinizde gidip o branch’i size getirecektir.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git pull origin
</code></pre></div></div>
<h1 id="çeşitli-kaynaklar">Çeşitli Kaynaklar</h1>
<p>Öncelikle eğer İngilizce bilgisi yeterli düzeyde ise kesinlikle <a href="https://git-scm.com/book/en/v1/Getting-Started">Git’in “Getting Started” dökümanını</a> öneriyorum. Yine İngilizce olarak <a href="https://www.atlassian.com/git/tutorials">Atlassian’ın Git hakkında hazırladığı kapsamlı dökümanlar</a> var, baya lokum gibi anlatmışlar şekil şukul resimler falan da koyarak, o da çok faydalı olacaktır. Yine İngilizce olarak Git komutlarını interaktif olarak öğrenebileceğiniz, çalıştırıp deneyebileceğiniz bir kaynak olarak <a href="https://try.github.io/levels/1/challenges/1">şu adresi</a> kullanabilirsiniz, epey kullanışlı. Bunların dışında Türkçe olarak “git — basit rehber” adında, komutları ve kullanım örneklerini anlatan bir kaynak var, <a href="http://rogerdudler.github.io/git-guide/index.tr.html">şuradan ulaşabilirsiniz</a>. Bununla beraber yazıyı oluştururken karşılaştığım Ali Özgür beyefendi tarafından oluşturulmuş çok kapsamlı bir online kitap mevcut, <a href="https://aliozgur.gitbooks.io/git101/">şuradan ulaşabilirsiniz</a>, kitap detaya girmesi açısından çok başarılı bir kaynak.</p>
<p>Tüm bu kaynaklar elinizin altında olsa da, Git’i öğrenmek size kalıyor. Benim tavsiyem, bir iki örnek ile basitçe başlamaya çalışmanız yönünde. Zaten zaman içerisinde karşılaştığınız problemler sizi Git’in farklı özellikleri ile tanıştıracak, bu sayede daha detaylı olarak öğrenmiş olacaksınız uygulamayı.</p>
<h1 id="sonuç">Sonuç?</h1>
<p>Bu yazı ile beraber Git ve versiyon kontrol dünyasına bir adım atmış olmanızı umuyoruz. Aslında yazıyı tek başına bir kaynak haline getirmekten ziyade Git öğrenmeye çalışan kullanıcıya yardımcı bir kaynak haline getirmek istedik, o yüzden Git’in tüm detaylarını anlatmaktan ziyade eksik kalabilecek noktalara dokunup temellerini vermeyi amaçladık, umuyoruz ki faydalı olmuştur. Başka bir yazıda ücretli ve ücretsiz Git sunucu alternatiflerini tartışmayı planlıyorum, bu adımdan sonra orası faydalı olabilir.</p>
<p>Yazı içerisinde hatalar yapmış olabilirim, genel kullanım örnekleri üzerinden giderek kabaca anlatmaya çalıştım. Eksikleri buradan veya burak.karakan@gmail.com adresi üzerinden iletirseniz hızlıca düzeltebilirim. Yazıyı okuyup önceden feedback veren <a href="https://github.com/coaltunbey">Cem Ozan Altunbey</a>, <a href="https://github.com/sayinbu/">Burak Sayın</a> ve <a href="https://github.com/furkanhatipoglu">Furkan Hatipoğlu</a> kankilerime teşekkürlerimi iletiyorum.</p>
<p>Sevgiler.</p>Burak KarakanBir versiyon kontrol sistemi olan `git` nedir? Nasil calisir?