Jekyll2020-11-26T09:24:24+00:00https://nickdurante.github.io/feed.xmlNick DuranteNick Durante's blog.Nick DuranteSet up a RPi seedbox and media server2020-11-24T00:00:00+00:002020-11-24T00:00:00+00:00https://nickdurante.github.io/development/Set%20up%20a%20RPi%20seedbox<p>Raspberry Pis are cheap and extremely useful for computing small tasks.
You can set up one of those with an HDD as a seedbox to download <strong>legal</strong> torrents such as Linux distributions and videos from the Internet Archive (<em>wink wink</em>).</p>
<p>A VPN for downloading torrents is always recommended, in my case I use NordVPN.</p>
<p>I thought it might be useful to write this down.
I based my configuration on the <a href="https://wiki.archlinux.org/index.php/Transmission">Transmission Arch wiki</a>, for more configurations head there.</p>
<h1 id="versions-of-software-used">Versions of software used</h1>
<table>
<thead>
<tr>
<th style="text-align: left">Software</th>
<th style="text-align: right">Version</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">OS</code></td>
<td style="text-align: right"><code class="language-plaintext highlighter-rouge">Raspbian GNU/Linux 10 (buster)</code></td>
</tr>
<tr>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">nordvpn</code></td>
<td style="text-align: right"><code class="language-plaintext highlighter-rouge">3.8.6</code></td>
</tr>
<tr>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">transmission-daemon</code></td>
<td style="text-align: right"><code class="language-plaintext highlighter-rouge">2.94-2+deb10u1</code></td>
</tr>
<tr>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">minidlna</code></td>
<td style="text-align: right"><code class="language-plaintext highlighter-rouge">1.2.1+dfsg-1+b1</code></td>
</tr>
</tbody>
</table>
<h1 id="install-software">Install software</h1>
<p>We need to install the missing software:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget <span class="nt">-O</span> /tmp/nordvpn.deb https://repo.nordvpn.com/deb/nordvpn/debian/pool/main/nordvpn-release_1.0.0_all.deb
apt <span class="nb">install</span> /tmp/nordvpn.deb
apt update
apt <span class="nb">install</span> <span class="nt">-y</span> nordvpn transmission-cli transmission-daemon
</code></pre></div></div>
<h1 id="configure-software">Configure software</h1>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>as root<span class="o">)</span>
systemctl <span class="nb">enable</span> <span class="nt">--now</span> nordvpnd
<span class="o">(</span>as pi<span class="o">)</span>
nordvpn login
nordvpn whitelist add subnet 192.168.1.0/24
nordvpn <span class="nb">set </span>killswitch on
nordvpn <span class="nb">set </span>autoconnect on <span class="o">[</span>countrycode] p2p
nordvpn connect <span class="nt">--group</span> p2p
</code></pre></div></div>
<p>It is extremely important to set the whitelist on your local subnet, in my case <code class="language-plaintext highlighter-rouge">192.168.1.0/24</code>, yours can be different.
If you don’t do so, you’ll lose connectivity and it will become quite difficult to login with SSH on your Raspberry.</p>
<p>Moreover you won’t be able to stream content on your local network.</p>
<p>If you are not using NordVPN you can configure your iptables not to use the VPN on the local network.
If you use an OpenVPN text file for config, it’s as easy as adding to it:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>push "route <network> <subnet>"
</code></pre></div></div>
<p>In my case it would be:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>push "route 192.168.1.0 255.255.255.0"
</code></pre></div></div>
<p>Edit <code class="language-plaintext highlighter-rouge">.config/transmission-daemon/settings.json</code> to your liking, it is important to enable RCP and add your IP to the RCP whitelist.
To allow access only from your subnet set RCP whitelist to something like:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s2">"rpc-whitelist"</span>: <span class="s2">"127.0.0.1,192.168.1.*"</span>
</code></pre></div></div>
<h1 id="manage-privileges">Manage privileges</h1>
<p>Separating privileges and groups is an important security feature.
Create a new group to access your HDD download location and a user for transmission:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>groupadd torrents
useradd <span class="nt">--create-home</span> <span class="nt">--groups</span> torrents transmission
</code></pre></div></div>
<p>Change the transmission service to run with dedicated user:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># cat /lib/systemd/system/transmission-daemon.service</span>
<span class="o">[</span>Unit]
<span class="nv">Description</span><span class="o">=</span>Transmission BitTorrent Daemon
<span class="nv">After</span><span class="o">=</span>network.target
<span class="o">[</span>Service]
<span class="nv">User</span><span class="o">=</span>transmission
<span class="c">#User=debian-transmission</span>
<span class="nv">Type</span><span class="o">=</span>notify
<span class="nv">ExecStart</span><span class="o">=</span>/usr/bin/transmission-daemon <span class="nt">-f</span> <span class="nt">--log-error</span>
<span class="nv">ExecStop</span><span class="o">=</span>/bin/kill <span class="nt">-s</span> STOP <span class="nv">$MAINPID</span>
<span class="nv">ExecReload</span><span class="o">=</span>/bin/kill <span class="nt">-s</span> HUP <span class="nv">$MAINPID</span>
<span class="o">[</span>Install]
<span class="nv">WantedBy</span><span class="o">=</span>multi-user.target
</code></pre></div></div>
<p>Then restart the Transmission daemon.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl restart transmission-daemon.service
</code></pre></div></div>
<p>You now can reach the RCP interface on your local network with a browser (at http://RPi_IP:9091/transmission/web/) or an app such as <a href="https://f-droid.org/en/packages/org.equeim.tremotesf/">Tremotesf</a> with the same IP.</p>
<p>On the interface or on the <code class="language-plaintext highlighter-rouge">.config/transmission-daemon/settings.json</code> set the download location of your torrents on your HDD, mine is mounted on <code class="language-plaintext highlighter-rouge">/media/hdd/</code>.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s2">"download-dir"</span>: <span class="s2">"/media/hdd/torrents/complete"</span>,
<span class="s2">"incomplete-dir"</span>: <span class="s2">"/media/hdd/torrents/incomplete"</span>,
</code></pre></div></div>
<h1 id="stream-torrents-on-your-local-network">Stream torrents on your local network</h1>
<p>The UNIX tool <code class="language-plaintext highlighter-rouge">minidlna</code> (<a href="https://wiki.archlinux.org/index.php/ReadyMedia">Arch wiki</a>) allows you to broadcast on your local network (i.e. smart TV, tablets (VLC) and other PCs) the media content you downloaded.
Install minidlna and assign right permissions to the HDD folder (in my case mounted on <code class="language-plaintext highlighter-rouge">/media/hdd/</code>) and start it.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt <span class="nb">install </span>minidlna
usermod <span class="nt">-aG</span> torrents minidlna
<span class="nb">cd</span> /media/
<span class="nb">chown</span> <span class="nt">-R</span> pi:torrents hdd/
<span class="nb">chmod</span> <span class="nt">-R</span> g+rwx hdd/
</code></pre></div></div>
<p>Configure minidlna modifying <code class="language-plaintext highlighter-rouge">/etc/minidlna.conf</code>.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">user</span><span class="o">=</span>minidlna
<span class="nv">media_dir</span><span class="o">=</span>/media/hdd/torrents/complete
<span class="nv">merge_media_dirs</span><span class="o">=</span><span class="nb">yes
</span><span class="nv">inotify</span><span class="o">=</span><span class="nb">yes
</span><span class="nv">network_interface</span><span class="o">=</span>eth0
<span class="nv">friendly_name</span><span class="o">=</span>your preferred name
</code></pre></div></div>
<p>Then start <code class="language-plaintext highlighter-rouge">minidlna</code> and you are ready to go:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl <span class="nb">enable</span> <span class="nt">--now</span> minidlna
</code></pre></div></div>
<h1 id="nice-to-have-tools">Nice to have tools</h1>
<ul>
<li>Using the tTorrent search (<a href="https://f-droid.org/en/packages/hu.tagsoft.ttorrent.search/">F-Droid</a>) app you can search and add torrents to the seedbox through Tremotesf (<a href="https://f-droid.org/en/packages/org.equeim.tremotesf/">F-Droid</a>).</li>
<li>Port forwarding (not covered here) allows you to access the seedbox controls remotely (<strong>set a strong authentication method!</strong>)</li>
</ul>Nick DuranteRaspberry Pis are cheap and extremely useful for computing small tasks. You can set up one of those with an HDD as a seedbox to download legal torrents such as Linux distributions and videos from the Internet Archive (wink wink).Dichiarazione di Indipendenza del Cyberspazio2020-10-29T00:00:00+00:002020-10-29T00:00:00+00:00https://nickdurante.github.io/privacy/Dichiarazione%20di%20indipendenza%20del%20cyberspazio<p><a href="https://www.eff.org/cyberspace-independence">A Declaration of the Independence of Cyberspace</a> scritta da John Perry Barlow è un testo in cui mi identifico molto, ho deciso di tradurlo in italiano per diletto, sperando di avvicinare qualcuno a questi temi a me cari.</p>
<blockquote>
<p>Governi del mondo industriale, fiacchi giganti di carne e acciaio, vengo dal Cyberspazio, la nuova casa della Mente. A nome del futuro, chiedo a voi, entità del passato, di non importunarci. Non siete benvenuti tra di noi. Non avete nessuna autorità nei luoghi dove ci riuniamo.</p>
<p>Non abbiamo un governo eletto, e probabilmente non lo avremo, per questo mi rivolgo a voi con un’autorità più grande di quella che la libertà stessa proclama. Rivendico lo spazio globale e sociale che stiamo costruendo perchè sia intrisicamente indipendente dalle tirannie che cercate di imporci. Non avete nessun diritto morale per governarci e non abbiamo nessuna vera ragione per temere i vostri metodi di amministrazione.</p>
<p>I governi ottengono i loro giusti poteri dal consenso dei governati. Voi non avete chiesto nè ottenuto il nostro. Non vi abbiamo invitato. Non ci conoscete e tantomeno conoscete il nostro mondo. Il Cyberspazio non è limitato dai vostri confini. Non credete di poterlo costruire, come se fosse un progetto di costruzione pubblica. Non potete. È una manifestazione della natura e cresce da solo tramite le nostre azioni collettive.</p>
<p>Voi non avete partecipato nelle nostre conversazioni, grandi e coinvolgenti, e nemmeno avete creato il benessere dei nostri mercati. Non conoscete la nostra cultura, la nostra etica, o il nostro codice non scritto che già dà alla nostra società più ordine di quello che potrebbe essere ottenuto dalle vostre imposizioni.</p>
<p>Voi pretendete che ci siano problemi tra di noi che voi dovete risolvere. Usate questo pretesto come scusante per invedere i nostri confini. Molti di questi problemi non esistono. Dove ci sono conflitti reali, dove ci sono ingiustizie, noi li identificheremo e li risolveremo con i nostri mezzi. Stiamo creando il nostro Contratto Sociale. Questo governo prenderà forma seguendo le condizioni del nostro mondo, non del vostro. Il nostro mondo è diverso.</p>
<p>Il Cyberspazio consiste nelle transazioni, nelle relazioni e nello stesso pensiero, allineati come una onda stazionaria nella rete delle nostre comunicazioni. Il nostro è un mondo che è ovunque e in nessun luogo, è dove i nostri corpi vivono.</p>
<p>Noi stiamo creando un mondo dove chiunque può entrare senza privilegi o pregiudizi dovuti dalla razza, dalla potenza economica, dalla forza militare o dal luogo di nascita.</p>
<p>Noi stiamo creando un mondo dove chiunque, ovunque può esprimere ciò in cui crede, senza che sia discriminato nella sua unicità, senza paura di essere costretto al silenzio o alla conformità.</p>
<p>I vostri concetti legali di proprietà, espressione, identità, movimento e contesto non possono essere applicati a noi. Questi sono concetti materiali, e qui la materia non esiste.</p>
<blockquote>
</blockquote>
<p>Le nostre identità non hanno corpo, e quindi, al contrario di voi, noi non possiamo ottenere l’ordine tramite la violenza fisica. Crediamo che dall’etica, dall’illuminato interesse personale e dal bene comune, il nostro atto di governo emergerà. Le nostre identità possono essere distribuite su molte delle vostre giurisdizioni. L’unica legge che le culture dei nostri membri costituenti generalmente riconoscono è la Regola Aurea. Noi auspichiamo di essere in grado di costruire le nostre soluzioni specifiche su quella base. Non possiamo accettare le soluzioni che tentate di imporci.</p>
<p>Negli Stati Uniti, oggi avete creato una legge, il Telecommunications Reform Act, che ripudia la stessa costituzione americana e insulta i sogni di Jefferson, Washington, Mill, Madison, DeToqueville e Brandeis. Questi sogni nascono nuovamente con noi.</p>
<p>Voi siete terrorizzati dai vostri figli, perchè sono nativi di un mondo in cui voi sarete solo immigranti. Le vostre paure vi spingono ad affidare alle burocrazie le responsabilità patriarcali che codardamente non affrontate voi stessi. Nel nostro mondo, tutti i sentimenti e espressioni di umanità, dalle più degradanti alle più angeliche, sono parte delle stesso tessuto, la conversazione globale dei bits. Non possiamo separare l’aria che ci soffoca dall’aria in cui libriamo le ali.</p>
<p>In Cina, Germania, Francia, Russia, Singapore, Italia e gli Stati Uniti, state cercando di scacciare il virus della libertà erigendo posti di guardia ai confini del Cyberspazio. Questi potrebbero allontanere il virus per qualche tempo, ma non funzioneranno in un mondo che sarà presto ricoperto e supportato dai bit.</p>
<p>Le vostre industrie dell’informazione sempre più obsolete, si rinnoverebbero proponendo leggi, in America e in altri luoghi, che rivendicano lo stesso diritto di parola in tutto il mondo. Queste leggi dichiarerebbero che le idee sono un altro prodotto industriale, non più nobile del piede di porco. Nel nostro mondo, ogni cosa creata dalla mente umana può essere riprodotta all’infinito senza costi. Il trasporto globale del pensiero non ha più bisogno delle vostre fabbriche.</p>
<p>Queste misure coloniali sempre più ostili ci pongono nella stessa posizione dei precendenti amanti della libertà e autodeterminazione che hanno dovuto rifiutare la autorità di poteri distanti e disinformati. Noi dobbiamo dichiarare il nostro essere virtuale come immune alla vostra sovranità, anche se continuiamo ad ammettere le vostre leggi sui nostri corpi. Ci spargeremo su tutto il Pianeta, in maniera tale che nessuno possa arrestare i nostri pensieri.</p>
<p>Noi creeremo una civiltà della Mente del Cyberspazio. Che sia più umana e giusta del mondo che i vostri governi hanno creato.</p>
<p>Davos, Svizzera
8 Febbraio 1996</p>
</blockquote>Nick DuranteA Declaration of the Independence of Cyberspace scritta da John Perry Barlow è un testo in cui mi identifico molto, ho deciso di tradurlo in italiano per diletto, sperando di avvicinare qualcuno a questi temi a me cari.Writing a Cinnamon Applet2020-07-07T00:00:00+00:002020-07-07T00:00:00+00:00https://nickdurante.github.io/development/Writing%20a%20Cinnamon%20Applet<p>Applets in the Cinnamon Desktop environment allow you to show information on your panel bar and execute actions from there.</p>
<p>They are extremely useful but at the same time there are few of them, so if you’d like to write one yourself you’ve come to the right place.</p>
<h2 id="setting-up-a-basic-indicator">Setting up a basic indicator</h2>
<p>First of all head over to:</p>
<p><a href="https://projects.linuxmint.com/reference/git/cinnamon-tutorials/write-applet.html">https://projects.linuxmint.com/reference/git/cinnamon-tutorials/write-applet.html</a></p>
<p>to get a basic understanding of the Applet structure.</p>
<p>Once you’ve obtained an <code class="language-plaintext highlighter-rouge">applet.js</code> and a <code class="language-plaintext highlighter-rouge">metadata.json</code> it’s time to get our hands dirty.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">Applet</span> <span class="o">=</span> <span class="nx">imports</span><span class="p">.</span><span class="nx">ui</span><span class="p">.</span><span class="nx">applet</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">Util</span> <span class="o">=</span> <span class="nx">imports</span><span class="p">.</span><span class="nx">misc</span><span class="p">.</span><span class="nx">util</span><span class="p">;</span>
<span class="kd">function</span> <span class="nx">MyApplet</span><span class="p">(</span><span class="nx">orientation</span><span class="p">,</span> <span class="nx">panel_height</span><span class="p">,</span> <span class="nx">instance_id</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">_init</span><span class="p">(</span><span class="nx">orientation</span><span class="p">,</span> <span class="nx">panel_height</span><span class="p">,</span> <span class="nx">instance_id</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">MyApplet</span><span class="p">.</span><span class="nx">prototype</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">__proto__</span><span class="p">:</span> <span class="nx">Applet</span><span class="p">.</span><span class="nx">IconApplet</span><span class="p">.</span><span class="nx">prototype</span><span class="p">,</span>
<span class="na">_init</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">orientation</span><span class="p">,</span> <span class="nx">panel_height</span><span class="p">,</span> <span class="nx">instance_id</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">Applet</span><span class="p">.</span><span class="nx">IconApplet</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">_init</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">orientation</span><span class="p">,</span> <span class="nx">panel_height</span><span class="p">,</span> <span class="nx">instance_id</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">set_applet_icon_name</span><span class="p">(</span><span class="dl">"</span><span class="s2">force-exit</span><span class="dl">"</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">set_applet_tooltip</span><span class="p">(</span><span class="nx">_</span><span class="p">(</span><span class="dl">"</span><span class="s2">Click here to kill a window</span><span class="dl">"</span><span class="p">));</span>
<span class="p">},</span>
<span class="na">on_applet_clicked</span><span class="p">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">Util</span><span class="p">.</span><span class="nx">spawn</span><span class="p">(</span><span class="dl">'</span><span class="s1">xkill</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="kd">function</span> <span class="nx">main</span><span class="p">(</span><span class="nx">metadata</span><span class="p">,</span> <span class="nx">orientation</span><span class="p">,</span> <span class="nx">panel_height</span><span class="p">,</span> <span class="nx">instance_id</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nx">MyApplet</span><span class="p">(</span><span class="nx">orientation</span><span class="p">,</span> <span class="nx">panel_height</span><span class="p">,</span> <span class="nx">instance_id</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="editing-the-basic-example">Editing the basic example</h2>
<p>There are different types of Applets:</p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr>
<td>Text only</td>
<td>Applet.TextApplet</td>
</tr>
<tr>
<td>Icon only</td>
<td>Applet.IconApplet</td>
</tr>
<tr>
<td>Icon + text</td>
<td>Applet.TextIconApplet</td>
</tr>
</tbody>
</table>
<p>Depending on your preferences update the <code class="language-plaintext highlighter-rouge">__proto__</code> and the <code class="language-plaintext highlighter-rouge">__init__</code> functions accordingly.</p>
<p>I’ll be using a <code class="language-plaintext highlighter-rouge">TextIconApplet</code> for completeness.</p>
<p>Adjust the displayed icon and label in the <code class="language-plaintext highlighter-rouge">__init__</code> function:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// icon</span>
<span class="k">this</span><span class="p">.</span><span class="nx">set_applet_icon_name</span><span class="p">(</span><span class="dl">"</span><span class="s2">network-vpn</span><span class="dl">"</span><span class="p">);</span>
<span class="c1">// tooltip on hover</span>
<span class="k">this</span><span class="p">.</span><span class="nx">set_applet_tooltip</span><span class="p">(</span><span class="nx">_</span><span class="p">(</span><span class="dl">"</span><span class="s2">Manage your VPN connection</span><span class="dl">"</span><span class="p">));</span>
<span class="c1">// label</span>
<span class="k">this</span><span class="p">.</span><span class="nx">set_applet_label</span><span class="p">(</span><span class="dl">"</span><span class="s2">Hello</span><span class="dl">"</span><span class="p">);</span>
</code></pre></div></div>
<p>Remove the click function, as we don’t need it.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">on_applet_clicked</span><span class="p">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">Util</span><span class="p">.</span><span class="nx">spawn</span><span class="p">(</span><span class="dl">'</span><span class="s1">xkill</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="update-the-label-on-a-loop">Update the label on a loop</h2>
<p>Now every good indicator updates its displayed value, to do so add these methods to the <code class="language-plaintext highlighter-rouge">applet.js</code>, before closing the <code class="language-plaintext highlighter-rouge">MyApplet.prototype</code> bracket.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">on_applet_removed_from_panel</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="c1">// stop the loop when the applet is removed</span>
<span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_updateLoopID</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">Mainloop</span><span class="p">.</span><span class="nx">source_remove</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_updateLoopID</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="nx">_run_cmd</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">command</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// run a command and return the output</span>
<span class="k">try</span> <span class="p">{</span>
<span class="kd">let</span> <span class="p">[</span><span class="nx">result</span><span class="p">,</span> <span class="nx">stdout</span><span class="p">,</span> <span class="nx">stderr</span><span class="p">]</span> <span class="o">=</span> <span class="nx">GLib</span><span class="p">.</span><span class="nx">spawn_command_line_sync</span><span class="p">(</span><span class="nx">command</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">stdout</span> <span class="o">!=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">stdout</span><span class="p">.</span><span class="nx">toString</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
<span class="nb">global</span><span class="p">.</span><span class="nx">logError</span><span class="p">(</span><span class="nx">e</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="dl">""</span><span class="p">;</span>
<span class="p">},</span>
<span class="nx">_get_status</span><span class="p">:</span> <span class="kd">function</span><span class="p">(){</span>
<span class="kd">let</span> <span class="nx">status</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">_run_cmd</span><span class="p">(</span><span class="dl">"</span><span class="s2">your command to run</span><span class="dl">"</span><span class="p">);</span>
<span class="c1">// update the label with the output of your command</span>
<span class="k">this</span><span class="p">.</span><span class="nx">set_applet_label</span><span class="p">(</span><span class="nx">status</span><span class="p">);</span>
<span class="p">},</span>
<span class="nx">_update_loop</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">_get_status</span><span class="p">();</span>
<span class="c1">// run the loop every 5000 ms</span>
<span class="k">this</span><span class="p">.</span><span class="nx">_updateLoopID</span> <span class="o">=</span> <span class="nx">Mainloop</span><span class="p">.</span><span class="nx">timeout_add</span><span class="p">(</span><span class="mi">5000</span><span class="p">,</span> <span class="nx">Lang</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">_update_loop</span><span class="p">));</span>
<span class="p">},</span>
</code></pre></div></div>
<p>Then import the requred libraries:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">GLib</span> <span class="o">=</span> <span class="nx">imports</span><span class="p">.</span><span class="nx">gi</span><span class="p">.</span><span class="nx">GLib</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">Mainloop</span> <span class="o">=</span> <span class="nx">imports</span><span class="p">.</span><span class="nx">mainloop</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">Lang</span> <span class="o">=</span> <span class="nx">imports</span><span class="p">.</span><span class="nx">lang</span><span class="p">;</span>
</code></pre></div></div>
<h2 id="dropdown-options">Dropdown options</h2>
<p>A good applet allows you to do more than display a value, so now we’ll add dropdown options with commands.</p>
<p>Import:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">PopupMenu</span> <span class="o">=</span> <span class="nx">imports</span><span class="p">.</span><span class="nx">ui</span><span class="p">.</span><span class="nx">popupMenu</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">St</span> <span class="o">=</span> <span class="nx">imports</span><span class="p">.</span><span class="nx">gi</span><span class="p">.</span><span class="nx">St</span><span class="p">;</span>
</code></pre></div></div>
<p>In your <code class="language-plaintext highlighter-rouge">__init__</code> function add:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c1">// Create the popup menu</span>
<span class="k">this</span><span class="p">.</span><span class="nx">menuManager</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">PopupMenu</span><span class="p">.</span><span class="nx">PopupMenuManager</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">menu</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Applet</span><span class="p">.</span><span class="nx">AppletPopupMenu</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">orientation</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">menuManager</span><span class="p">.</span><span class="nx">addMenu</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">menu</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">_contentSection</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">PopupMenu</span><span class="p">.</span><span class="nx">PopupMenuSection</span><span class="p">();</span>
<span class="k">this</span><span class="p">.</span><span class="nx">menu</span><span class="p">.</span><span class="nx">addMenuItem</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_contentSection</span><span class="p">);</span>
<span class="c1">// First item: Turn on</span>
<span class="kd">let</span> <span class="nx">item</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">PopupMenu</span><span class="p">.</span><span class="nx">PopupIconMenuItem</span><span class="p">(</span><span class="dl">"</span><span class="s2">Label 1</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">icon 1</span><span class="dl">"</span><span class="p">,</span> <span class="nx">St</span><span class="p">.</span><span class="nx">IconType</span><span class="p">.</span><span class="nx">FULLCOLOR</span><span class="p">);</span>
<span class="nx">item</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="dl">'</span><span class="s1">activate</span><span class="dl">'</span><span class="p">,</span> <span class="nx">Lang</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">Util</span><span class="p">.</span><span class="nx">spawnCommandLine</span><span class="p">(</span><span class="dl">"</span><span class="s2">command item 1</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}));</span>
<span class="k">this</span><span class="p">.</span><span class="nx">menu</span><span class="p">.</span><span class="nx">addMenuItem</span><span class="p">(</span><span class="nx">item</span><span class="p">);</span>
<span class="nx">item</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">PopupMenu</span><span class="p">.</span><span class="nx">PopupIconMenuItem</span><span class="p">(</span><span class="dl">"</span><span class="s2">Label 2</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">icon 2</span><span class="dl">"</span><span class="p">,</span> <span class="nx">St</span><span class="p">.</span><span class="nx">IconType</span><span class="p">.</span><span class="nx">FULLCOLOR</span><span class="p">);</span>
<span class="nx">item</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="dl">'</span><span class="s1">activate</span><span class="dl">'</span><span class="p">,</span> <span class="nx">Lang</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">Util</span><span class="p">.</span><span class="nx">spawnCommandLine</span><span class="p">(</span><span class="dl">"</span><span class="s2">command item 2</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}));</span>
<span class="k">this</span><span class="p">.</span><span class="nx">menu</span><span class="p">.</span><span class="nx">addMenuItem</span><span class="p">(</span><span class="nx">item</span><span class="p">);</span>
<span class="c1">// Second item: Turn off</span>
<span class="nx">item</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">PopupMenu</span><span class="p">.</span><span class="nx">PopupIconMenuItem</span><span class="p">(</span><span class="dl">"</span><span class="s2">Label 3</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">icon 3</span><span class="dl">"</span><span class="p">,</span> <span class="nx">St</span><span class="p">.</span><span class="nx">IconType</span><span class="p">.</span><span class="nx">FULLCOLOR</span><span class="p">);</span>
<span class="nx">item</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="dl">'</span><span class="s1">activate</span><span class="dl">'</span><span class="p">,</span> <span class="nx">Lang</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">Util</span><span class="p">.</span><span class="nx">spawnCommandLine</span><span class="p">(</span><span class="dl">"</span><span class="s2">command item 3</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}));</span>
<span class="k">this</span><span class="p">.</span><span class="nx">menu</span><span class="p">.</span><span class="nx">addMenuItem</span><span class="p">(</span><span class="nx">item</span><span class="p">);</span>
</code></pre></div></div>
<p>And then change the labels, icons and commands accordingly.</p>
<h2 id="settings-page">Settings page</h2>
<p>A nice feature to have is a settings page to tweak a couple of parameters, in this example we’ll update the update frequency of the applet.</p>
<p>Create a new file: <code class="language-plaintext highlighter-rouge">settings-schema.json</code></p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"update-interval"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"spinbutton"</span><span class="p">,</span><span class="w">
</span><span class="nl">"default"</span><span class="p">:</span><span class="w"> </span><span class="mi">5000</span><span class="p">,</span><span class="w">
</span><span class="nl">"min"</span><span class="p">:</span><span class="w"> </span><span class="mi">2000</span><span class="p">,</span><span class="w">
</span><span class="nl">"max"</span><span class="p">:</span><span class="w"> </span><span class="mi">30000</span><span class="p">,</span><span class="w">
</span><span class="nl">"step"</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="p">,</span><span class="w">
</span><span class="nl">"units"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ms"</span><span class="p">,</span><span class="w">
</span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Update interval"</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>Now on the <code class="language-plaintext highlighter-rouge">applet.js</code> import and set the UUID:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">Settings</span> <span class="o">=</span> <span class="nx">imports</span><span class="p">.</span><span class="nx">ui</span><span class="p">.</span><span class="nx">settings</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">UUID</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">yourUUID</span><span class="dl">"</span><span class="p">;</span>
</code></pre></div></div>
<p>In the <code class="language-plaintext highlighter-rouge">__init__</code> method add:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">this</span><span class="p">.</span><span class="nx">settings</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Settings</span><span class="p">.</span><span class="nx">AppletSettings</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">UUID</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">instance_id</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">settings</span><span class="p">.</span><span class="nx">bindProperty</span><span class="p">(</span><span class="nx">Settings</span><span class="p">.</span><span class="nx">BindingDirection</span><span class="p">.</span><span class="nx">IN</span><span class="p">,</span> <span class="dl">"</span><span class="s2">update-interval</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">update_interval</span><span class="dl">"</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">_new_freq</span><span class="p">,</span> <span class="kc">null</span><span class="p">);</span>
</code></pre></div></div>
<p>Now that we’re storing the settings value in <code class="language-plaintext highlighter-rouge">this.update_interval</code>, simply modify the loop parameter:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">_update_loop</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">_get_status</span><span class="p">();</span>
<span class="k">this</span><span class="p">.</span><span class="nx">_updateLoopID</span> <span class="o">=</span> <span class="nx">Mainloop</span><span class="p">.</span><span class="nx">timeout_add</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">update_interval</span><span class="p">,</span> <span class="nx">Lang</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">_update_loop</span><span class="p">));</span>
<span class="p">},</span>
</code></pre></div></div>
<h2 id="final-thoughts">Final thoughts</h2>
<p>When writing my applet I found the lack of extensive documentation a little disorienting.
I hope that this brief tutorial clears some doubts.</p>
<p>Remember that looking at other developer’s work is extremely helpful in many cases.</p>
<p>You can find my NordVPN applet here <a href="https://github.com/nickdurante/nordvpn-indicator-cinnamon">https://github.com/nickdurante/nordvpn-indicator-cinnamon</a></p>Nick DuranteApplets in the Cinnamon Desktop environment allow you to show information on your panel bar and execute actions from there.Spider - A SFTP client2020-06-21T00:00:00+00:002020-06-21T00:00:00+00:00https://nickdurante.github.io/project/Spider<p><img src="https://github.com/nickdurante/Spider/raw/master/fastlane/metadata/android/en-US/images/icon.png" alt="Logo" /></p>
<p>Spider is my first serious project and one I’m quite proud of.</p>
<p>First of all because it is a working and useful product and secondly because my first real contribution to the open source community and apparently it has reached more people than I’ve expected.</p>
<figure class="third ">
<a href="https://github.com/nickdurante/Spider/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/s1.png" title="Spider screenshot">
<img src="https://github.com/nickdurante/Spider/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/s1.png" alt="Screenshot 1" />
</a>
<a href="https://github.com/nickdurante/Spider/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/s2.png" title="Spider screenshot">
<img src="https://github.com/nickdurante/Spider/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/s2.png" alt="Screenshot 2" />
</a>
<a href="https://github.com/nickdurante/Spider/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/s3.png" title="Spider screenshot">
<img src="https://github.com/nickdurante/Spider/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/s3.png" alt="Screenshot 3" />
</a>
<figcaption>Screenshots from the app
</figcaption>
</figure>
<p>It started as a way to solve a need I had: I needed a <em>safe</em> way to connect to my SFTP server.
Being over paranoid as I am, I didn’t want to use a proprietary app to connect to my server which contains sensible information.
Moreover no FOSS app was able to connect to SFTP servers using a private key but only using passwords.</p>
<p>Thus began a journey of development of my first serious Android app (the first one was a small university project).</p>
<p>While I realize the UI could be improved the basic functionalities are there:</p>
<ul>
<li>Login with password and private key</li>
<li>File browsing</li>
<li>File upload/download</li>
</ul>
<p>As login methods you can use a password or your PEM private key.
At the moment only PEM keys are supported, so check to have it in the correct format.
With release 0.2.2 also encrypted PEM keys are supported.</p>
<p>You can browse your files in the SFTP server and download them with a long press, or upload new ones using the ‘+’ floating button.
It also supports swipe down to refresh.</p>
<p>I am now happy to say that it has been published on F-Droid for over a year.</p>
<p><a href="https://f-droid.org/packages/org.kknickkk.spider/"><img alt="Get it on F-Droid" height="80" src="https://gitlab.com/fdroid/artwork/raw/master/badge/get-it-on.png" /></a></p>Nick Durante