<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Zack</title>
    <description>The latest articles on DEV Community by Zack (@schollz).</description>
    <link>https://dev.to/schollz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F35304%2Fbdd42c2b-c0e8-4e43-9979-003e0a8526e6.png</url>
      <title>DEV Community: Zack</title>
      <link>https://dev.to/schollz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/schollz"/>
    <language>en</language>
    <item>
      <title>⚱️ A Python script to register for a pottery class</title>
      <dc:creator>Zack</dc:creator>
      <pubDate>Tue, 18 Feb 2020 17:40:11 +0000</pubDate>
      <link>https://dev.to/schollz/a-python-script-to-register-for-a-pottery-class-172k</link>
      <guid>https://dev.to/schollz/a-python-script-to-register-for-a-pottery-class-172k</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---IpnyB8o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://schollz.com/img/pottery.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---IpnyB8o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://schollz.com/img/pottery.jpg" alt="My elephant pinch pots"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://schollz.com/blog/pottery/"&gt;X-post from schollz.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've been waiting a long time to take a pottery class. In a city of almost 1 million people, there are surprisingly few pottery classes and there are even fewer pottery classes that happen outside working hours. One of the classes I knew about had only 12 available seats that instantly sold out when registration opened. In fact, I missed the registration for this class several times already.&lt;/p&gt;

&lt;p&gt;I learned that the demand was so great for this pottery class that the website maintainers had to pick a random time to open registration to prevent the servers from crashing. Since I couldn't spend all day refreshing the pottery website to see if registration was available, I decided to write a program to help sign up for this class.&lt;/p&gt;

&lt;h2&gt;
  
  
  A script to alert when a website changes
&lt;/h2&gt;

&lt;p&gt;I made sure not to miss the registration this time by writing a script that monitors the registration website and sends an alert to my phone when it changes (when registration opens). Their are tons of online services that do this, but they cost money if you want to check a site more often than once every 15 minutes. However, writing this code is incredibly easy and satisfying. &lt;/p&gt;

&lt;p&gt;The script I wrote is in Python which does the image processing and the alerting. The website snapshots are taken with a Node script (using &lt;a href="https://github.com/puppeteer/puppeteer"&gt;puppeteer&lt;/a&gt;) which is run from the Python script. &lt;/p&gt;

&lt;p&gt;I learned some subtle things about website tracking while doing this - namely website scraping is easier if you can block ads and that SMTP is the best free notification service. More on that below, but if you just want the code, the script and the instructions for using it are on my Github: &lt;a href="https://github.com/schollz/websitechanges"&gt;https://github.com/schollz/websitechanges&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Block the ads before taking a snapshot
&lt;/h3&gt;

&lt;p&gt;The screenshot of the website is downloaded by using &lt;code&gt;puppeteer&lt;/code&gt;, which is very easy to do (its one of the &lt;a href="https://github.com/puppeteer/puppeteer/blob/master/examples/screenshot.js"&gt;examples&lt;/a&gt;!). &lt;/p&gt;

&lt;p&gt;However, one subtlety here is that I need to compare two screenshots in time for changes. Since ads can change every time you reload a page, I realized it is important to remove ads to get a reproducible view of the website. This is really easy to do with &lt;code&gt;puppeteer&lt;/code&gt;. First download a &lt;a href="https://github.com/StevenBlack/hosts"&gt;hosts file&lt;/a&gt; and then load it into the Node script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;hosts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="c1"&gt;//now we read the host file&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;hostFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hosts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;hosts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;hostFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hostFile&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;charAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;frags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hostFile&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;frags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0.0.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;frags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And then in &lt;code&gt;puppeteer&lt;/code&gt; you can block all the requests to everythin in the HOSTS file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* ... puppeteer setup omitted */&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setRequestInterception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;request&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;frags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;frags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// just abort if found&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This way, all the ads are removed and you just get a blank space or no space where they were.&lt;/p&gt;

&lt;h3&gt;
  
  
  SMTP is the easiest, cheapest way to send notifications
&lt;/h3&gt;

&lt;p&gt;The pottery website registration could occur at anytime in the middle of the night (it ended up being at 4:43 AM). I needed a way that the website change could notify me, namely by sending a text message. I can use my phone to play a loud sound when the message arrives. But how to send a text message? &lt;/p&gt;

&lt;p&gt;It turns out to be very easy! To send a notification to your phone, you simply send an email! Your phone provider usually supplies an email address for your phone. Here's the ones I know of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verizon: &lt;code&gt;PHONENUMBER@vtext.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Sprint: &lt;code&gt;PHONENUMBER@messaging.sprintpcs.com&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But, then, how do you send a email from a program? You can use email API service. However some of these, like mailgun, entice you with an offer of a free tier only to &lt;a href="https://news.ycombinator.com/item?id=22192543"&gt;later remove the free-tier&lt;/a&gt;. But the alternative is easy, fast, and free.&lt;/p&gt;

&lt;p&gt;The alternative is to use &lt;em&gt;SMTP&lt;/em&gt; which is provided with almost any free email service. For example, you can use a new Gmail account with a random username and password. To enable SMTP in Gmail:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If you are using a remote server, read the caveat at the bottom ofo this page.&lt;/li&gt;
&lt;li&gt;Go to the "Settings", e.g. click on the "Gears" icon and select "Settings".&lt;/li&gt;
&lt;li&gt;Click on "Forwarding and POP/IMAP".&lt;/li&gt;
&lt;li&gt;Enable "IMAP Access" and/or "POP Download"&lt;/li&gt;
&lt;li&gt;Goto &lt;a href="https://myaccount.google.com/lesssecureapps"&gt;https://myaccount.google.com/lesssecureapps&lt;/a&gt; and turn "Allow less secure apps" to "ON".&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it! Now you can send emails in Python using a function like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;smtplib&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;email.mime.text&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MIMEText&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;email.mime.image&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MIMEImage&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;email.mime.multipart&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MIMEMultipart&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;smtpemail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;smtppass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attachment&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;img_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attachment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"rb"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MIMEMultipart&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Subject"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"From"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;smtpemail&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"To"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;

    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MIMEText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MIMEImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attachment&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;smtplib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SMTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"smtp.gmail.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"587"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ehlo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;starttls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ehlo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;smtpemail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;smtppass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sendmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"From"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"To"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now you can have your program send a notification to your phone, with an image of the changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Back to Pottery
&lt;/h2&gt;

&lt;p&gt;I wrote this script the night before the pottery class registration was set to take place. The exact time the registration was set to open was random. But then, at 4:43 am, I got a notification:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--p3tpLPkm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://schollz.com/img/pottery-message.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--p3tpLPkm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://schollz.com/img/pottery-message.jpg" alt="Notification on my phone after my script detected a change."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I checked the website and saw that indeed the registration had opened and I got myself registered! &lt;/p&gt;

&lt;p&gt;Interestingly even though I thought I'd be the first there were already two other people registered by the time I was done registering! That means I'm not the first to do this type of thing for this particular class. But my code is open-source at &lt;a href="https://github.com/schollz/websitechanges"&gt;https://github.com/schollz/websitechanges&lt;/a&gt; so I hope everyone else will have a chance to try it too.&lt;/p&gt;








&lt;p&gt;Gmail SMTP getting blocked&lt;/p&gt;

&lt;p&gt;There is a caveat about using SMTP with Gmail. Gmail will tend to block SMTP access if you create the account on one computer and then use it on a remote server (like Digital Ocean). &lt;/p&gt;

&lt;p&gt;To get around this, make sure to create the account on the remote server, if that's where you plan to use it. &lt;/p&gt;

&lt;p&gt;To do that, SSH into the remote server using&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh -D 8123 -C -N user@remoteserver
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;-D&lt;/code&gt; parameter will bind a SOCKS port to &lt;code&gt;8123&lt;/code&gt;. Now goto Firefox settings and change the SOCKS port to &lt;code&gt;8123&lt;/code&gt;. Now Firefox will use your remote server and you can setup SMTP remotely. Then change it back when you're done!&lt;/p&gt;

</description>
      <category>python</category>
      <category>node</category>
      <category>changedetection</category>
    </item>
    <item>
      <title>🎹🌎 Pianos for Travelers</title>
      <dc:creator>Zack</dc:creator>
      <pubDate>Thu, 19 Dec 2019 15:39:38 +0000</pubDate>
      <link>https://dev.to/schollz/pianos-for-travelers-433a</link>
      <guid>https://dev.to/schollz/pianos-for-travelers-433a</guid>
      <description>&lt;p&gt;Pianists are lucky. The piano is an instrument that - unlike trumpets, saxophones, etc. - sometimes is available to play &lt;em&gt;for free&lt;/em&gt;. These &lt;em&gt;free-to-play&lt;/em&gt; pianos are called "public pianos" (or "street pianos") which often appear in airports, train stations and parks during good weather. &lt;/p&gt;

&lt;p&gt;Pianists know that these public pianos exist, no one knows where most of these pianos are and as such they exist in the world as a sort of magical entity whose secret location needs to be discovered. I am attempting to discover the location of all of them, and make a map of every piano in the world. &lt;/p&gt;

&lt;p&gt;The result of this attempt is a website: &lt;a href="https://pianos.travel" rel="noopener noreferrer"&gt;Pianos for Travelers&lt;/a&gt;. This blog post tells about how the website was made.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the data
&lt;/h2&gt;

&lt;p&gt;I estimate that there are thousands of public pianos in the world and at least one in every major city in the world. Obviously I cannot go to every city in the world to find every piano, so I am attempting to use the internet to find their locations. &lt;/p&gt;

&lt;p&gt;There is a handful of websites that compile piano lists including &lt;a href="https://airport-pianos.fandom.com/wiki/Airport_pianos_Wiki" rel="noopener noreferrer"&gt;a slowly updated wiki&lt;/a&gt; and &lt;a href="https://www.pianoplayersclub.com/" rel="noopener noreferrer"&gt;an out-of-date map&lt;/a&gt;, forums, subreddits and etc. I collected hundreds of piano locations going through these types of websites and manually cataloging their coordinates in a file.&lt;/p&gt;

&lt;p&gt;After going through every website I could find, I only had a few hundred pianos - there were still over a thousand out there. Luckily, I realized that social media actually has tracked locations of people using the hashtags &lt;code&gt;#publicpiano&lt;/code&gt; and I was able to use APIs to collect their coordinates as well. This netted hundreds of more pianos.&lt;/p&gt;

&lt;p&gt;At this point, until I find another source of pianos, I will be bootstrapping myself and heeding user input to gather the locations of more pianos. This is usually where the other websites failed, and mine might too, so I'm still looking for alternatives to this practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the website
&lt;/h2&gt;

&lt;p&gt;I designed the &lt;em&gt;&lt;a href="https://pianos.travel" rel="noopener noreferrer"&gt;Pianos for Travelers&lt;/a&gt;&lt;/em&gt; site with my good friend. We used a very simple stack - Go std-lib http router, with &lt;a href="https://www.postgresql.org/" rel="noopener noreferrer"&gt;Postgres 12&lt;/a&gt; backend, &lt;a href="https://jquery.com/" rel="noopener noreferrer"&gt;JQuery v3&lt;/a&gt; frontend, and using &lt;a href="https://tachyons.io/" rel="noopener noreferrer"&gt;Tachyons&lt;/a&gt; for CSS. (I actually disagree that Postgres is very simple, but it is very powerful when it comes to GIS information). &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fschollz.com%2Fimg%2Fpianostravel.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fschollz.com%2Fimg%2Fpianostravel.png" alt="Pianos for Travelers website"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Go language constantly impresseses me with how fast we can move into production - for example we needed a CAPTCHA and found an &lt;a href="https://github.com/dchest/captcha" rel="noopener noreferrer"&gt;amazing package by dchest&lt;/a&gt; that was basically a dropin into our std-lib web server. Same thing happened when we switched to Websockets. We found  the entire site took less than two weeks to build (and we only spent our free time on it).&lt;/p&gt;

&lt;h2&gt;
  
  
  Shipping the website
&lt;/h2&gt;

&lt;p&gt;The website, &lt;em&gt;&lt;a href="https://pianos.travel" rel="noopener noreferrer"&gt;Pianos for Travelers&lt;/a&gt;&lt;/em&gt;, is live now. We posted about it on sites that might have interest in places with pianos (Piano forums, subreddits) and it gained a little traction. It is up for perpetuity now, though so I'm hoping it provides some useful information to the people that find it.&lt;/p&gt;

</description>
      <category>go</category>
      <category>website</category>
      <category>jquery</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Now what do I read? A web app for blazing fast book recommendations.</title>
      <dc:creator>Zack</dc:creator>
      <pubDate>Wed, 04 Sep 2019 13:16:41 +0000</pubDate>
      <link>https://dev.to/schollz/now-what-do-i-read-a-web-app-for-blazing-fast-book-recommendations-1jhd</link>
      <guid>https://dev.to/schollz/now-what-do-i-read-a-web-app-for-blazing-fast-book-recommendations-1jhd</guid>
      <description>&lt;p&gt;&lt;small&gt;
&lt;em&gt;&lt;a href="https://nowwhatdoiread.com/blog"&gt;(x-post from nowwhatdoiread.com)&lt;/a&gt;&lt;/em&gt;
&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fLaseduM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://nowwhatdoiread.com/static/catonbooks.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fLaseduM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://nowwhatdoiread.com/static/catonbooks.png" alt="Cat on books"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What book are you reading &lt;em&gt;right now&lt;/em&gt;? Do you &lt;em&gt;enjoy&lt;/em&gt; it?&lt;/p&gt;

&lt;p&gt;If you answer “&lt;em&gt;yes&lt;/em&gt;” to either of those questions then stop reading this blog and go read your book!&lt;/p&gt;

&lt;p&gt;If you answer “&lt;em&gt;no&lt;/em&gt;” to either of those questions then its &lt;em&gt;time&lt;/em&gt;. Its time to get a new book to read.&lt;/p&gt;

&lt;p&gt;“But,” you profess, “there is nothing to read.” I believe you. I believe because you’ve asked your friends, you’ve searched Amazon.com, you’ve checked Goodreads.com, you’ve browsed the library shelves, the Barnes &amp;amp; Noble shelves, and no single book has caught your attention. And, now, you’ve rightfully concluded that there is &lt;em&gt;nothing to read&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Don’t give up the search, yet. There &lt;em&gt;is&lt;/em&gt; a book out there just for you - a book that is so &lt;em&gt;good&lt;/em&gt; that you won’t put it down even when it’s 11:30 pm and you have to wake up at 5 am for work.&lt;/p&gt;

&lt;p&gt;How do I know there is a book out there for you? I know because I’ve been in the same situation. I’ve searched, and gotten tired of searching and gave up. But then I found a better way to search. This better search led me to a book out there, written just for me. This way to find books has worked not just once or twice but &lt;em&gt;every time&lt;/em&gt;. Now, I’m able to find an amazing book anytime I have the question: &lt;strong&gt;“now, what do I read?”&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id="a-new-way-to-search-for-books"&gt;A new way to search for books&lt;/h2&gt;

&lt;p&gt;How do I find my next books to read? I do this the same way everyone does in 2019: using a &lt;em&gt;web app&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A web app is just a fancy name for a computer program that is accessible on the internet. A web app’s computer program can hold information about millions of books inside its circuit brain. It can use its gigahertz processor to find the most relevant book suggestions and use the internet to display them on a website. Unlike your friends and friendly librarian, a web app can take any request, at any time, and give you an unbiased book suggestion in milliseconds.&lt;/p&gt;

&lt;p&gt;I made such a web app with my computer. And, since my computer is not owned by Amazon / Barne’s and Noble / Powells or other online booksellers, it won’t try to manipulate you because it wants you to buy something.&lt;sup class="footnote-ref" id="fnref:2"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Note: My web app &lt;em&gt;does&lt;/em&gt; use Amazon Affiliate links which sends me money for link clicks. This money pays for hosting ($12/year) after which I direct all profits from to supporting the actual Amazon via the &lt;a href="https://www.amazonconservation.org/"&gt;Amazon Conservation Association&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My web app is further optimized to only show the best books with similar traits and exclude books that are sequels or multiple books from the same author.&lt;sup class="footnote-ref" id="fnref:1"&gt;2&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;The web app written in Golang. The key to its speed is utilizing &lt;a href="https://github.com/dgraph-io/badger"&gt;the Badger keystore&lt;/a&gt; and precomputing all the similarities for a million books. Fuzzy searching is provided using the amazingly fast &lt;a href="https://github.com/junegunn/fzf"&gt;junegunn/fzf&lt;/a&gt; library. Images are inlined with base64 to make all the assets bundled and its minified with &lt;a href="https://github.com/tdewolff/minify"&gt;tdewolff/minify&lt;/a&gt; to get that extra boost in speed. Also, there is no Javascript so the server does the heavy lifting making the rendering appear instant.&lt;/p&gt;

&lt;p&gt;The web app is called &lt;a href="https://nowwhatdoiread.com"&gt;&lt;code&gt;nowwhatdoiread.com&lt;/code&gt;&lt;/a&gt;. Go ahead, try it.&lt;/p&gt;

&lt;h2 id="an-example-of-using-a-href-https-nowwhatdoiread-com-target-blank-em-nowwhatdoiread-com-em-a"&gt;An example of using &lt;a href="https://nowwhatdoiread.com"&gt;&lt;em&gt;nowwhatdoiread.com&lt;/em&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Here’s an example from my life. For some reason, I read &lt;a&gt;&lt;em&gt;Boy’s Life&lt;/em&gt; by Robert McCammon&lt;/a&gt;. I thought it was a biography but it turned out to have magic and horror. I don’t normally read horror but I loved this book. I thought that there wouldn’t be many books like this.&lt;/p&gt;

&lt;p&gt;I used &lt;a href="https://nowwhatdoiread.com"&gt;&lt;code&gt;nowwhatdoiread.com&lt;/code&gt;&lt;/a&gt; and entered “&lt;em&gt;Boy’s Life&lt;/em&gt;” and found &lt;a href="https://nowwhatdoiread.com/book/471747102-weaveworld-by-clive-barker?q=weaveworld"&gt;&lt;em&gt;Weaveworld&lt;/em&gt; by Clive Barker&lt;/a&gt;. This book blew me away and I liked it more than &lt;em&gt;Boy’s Life.&lt;/em&gt; But, after finishing &lt;em&gt;Weaveworld&lt;/em&gt; I was again dismayed. It was so good and unique I thought I wouldn’t find anything close to it again. But then, I asked the web app for books similar to &lt;em&gt;Weaveworld&lt;/em&gt; and found &lt;a href="https://nowwhatdoiread.com/book/2364775265-the-library-at-mount-char-by-scott-hawkins"&gt;&lt;em&gt;The Library at Mount Char&lt;/em&gt; by Scott Hawkins&lt;/a&gt;. This book was even better!&lt;/p&gt;

&lt;p&gt;I still find great and relevant book suggestions using &lt;a href="https://nowwhatdoiread.com"&gt;&lt;code&gt;nowwhatdoiread.com&lt;/code&gt;&lt;/a&gt;. The last book I put in &lt;em&gt;The Library at Mount Char&lt;/em&gt; and now I’m currently reading &lt;a href="https://nowwhatdoiread.com/book/3143870535-kraken-by-china-mieville"&gt;&lt;em&gt;Kraken&lt;/em&gt; by China Miéville&lt;/a&gt;, which I’m enjoying.&lt;/p&gt;

&lt;p&gt;This example led me down a particular set of genres, but I’ve found the genre doesn’t matter and I’ve gotten good recommendations for all other sorts of books. I’ve found book suggestions for all genres, as there as the web app doesn’t have any bias towards a particular genre.&lt;/p&gt;

&lt;h2 id="the-cycle-continues"&gt;The cycle continues&lt;/h2&gt;

&lt;p&gt;Every book I read now gives me more books to read with the help of this web app, &lt;a href="https://nowwhatdoiread.com"&gt;&lt;code&gt;nowwhatdoiread.com&lt;/code&gt;&lt;/a&gt;. Though I made it mostly for myself, I’m opening it up to the world so that you can find a book and support a charity at the same time.&lt;/p&gt;

&lt;p&gt;Maybe it will work for you. Maybe it won’t.&lt;/p&gt;

&lt;p&gt;In any case, I hope that instead of asking &lt;em&gt;“now, what do I read?”&lt;/em&gt; you can just say &lt;em&gt;“now, I read.”&lt;/em&gt;&lt;/p&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ELZRROlk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://nowwhatdoiread.com/static/cat.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ELZRROlk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://nowwhatdoiread.com/static/cat.png"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>books</category>
      <category>go</category>
      <category>webapp</category>
    </item>
    <item>
      <title>💻↔️🖥️ Transferring files between two computers</title>
      <dc:creator>Zack</dc:creator>
      <pubDate>Sat, 04 May 2019 01:51:36 +0000</pubDate>
      <link>https://dev.to/schollz/transferring-files-between-two-computers-21m8</link>
      <guid>https://dev.to/schollz/transferring-files-between-two-computers-21m8</guid>
      <description>&lt;p&gt;There are a lot of ways to transfer files.&lt;sup id="fnref1"&gt;1&lt;/sup&gt; &lt;/p&gt;

&lt;p&gt;Above the basic necessity of preserving the data during the transfer, sharing data should be &lt;em&gt;fast&lt;/em&gt;, &lt;em&gt;secure&lt;/em&gt;, and -- most of all -- &lt;em&gt;easy&lt;/em&gt;. Most file transfer utilities I've used encompass two thirds of these qualities. I've wanted to make a utility that encompasses all three, without compromising any one of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  relay &amp;gt; uploading
&lt;/h2&gt;

&lt;p&gt;File transfers should be as fast as possible - the time it takes to transfer the file is often spent waiting. Faster file transfers means less waiting.&lt;/p&gt;

&lt;p&gt;A common way to transfer a file is to first upload data to a server, and then, once uploaded, the link is shared with someone who goes to download it.&lt;sup id="fnref2"&gt;2&lt;/sup&gt; This method is easy, but slow -- the transfer rate of the file is half the harmonic mean of the upload and download speeds which makes it slower than either just uploading or downloading.&lt;sup id="fnref3"&gt;3&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;There are better ways than sequentially uploading and downloading. Instead, you can use a &lt;strong&gt;relay server&lt;/strong&gt; to create a full-duplex real-time communication layer between the two computers so that "uploading" and "downloading" occur simultaneously between the two computers. This effectively increases the transfer rate because its not sequential and only limited by the slower of the two transfer modes (uploading or downloading).&lt;/p&gt;

&lt;h2&gt;
  
  
  key exchange &amp;gt; passwords
&lt;/h2&gt;

&lt;p&gt;Transferring data should be secure. Whenever you share data, it goes through a network of servers that can "see" the data, so it should be encrypted to stay private. A common way to do end-to-end encryption is with a password. You use a password to encrypt your file, then send the password-protected file, and after which the other person uses the same password to decrypt it. &lt;/p&gt;

&lt;p&gt;Passwords are easy, but passwords can be weak. It turns out &lt;a href="https://www.troyhunt.com/86-of-passwords-are-terrible-and-other-statistics/"&gt;many people use the same passwords&lt;/a&gt; so that breaking an encryption can be as easy as just trying &lt;a href="https://haveibeenpwned.com/Passwords"&gt;each password in a list of pwned accounts&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Weak passwords can be used to make strong passwords using &lt;a href="https://github.com/schollz/pake"&gt;PAKE&lt;/a&gt;: &lt;em&gt;password authenticated key exchange&lt;/em&gt;. PAKE is a cryptographic method where two people share a password which is then used -- via back-and-forth communication -- to generate a strong key. The strong key can then be used for all further encryption. Since the two people generate the strong key by exchanging information, no one else could possibly learn the strong key even if they have the original password.&lt;sup id="fnref4"&gt;4&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;There are other ways to exchange a key, but in general &lt;em&gt;you can get better security by exchanging a key rather than using a single password&lt;/em&gt; on both computers.&lt;/p&gt;

&lt;h2&gt;
  
  
  no port forwarding &amp;gt; port forwarding
&lt;/h2&gt;

&lt;p&gt;Finally, transferring data should be simple, ideally with a single command and no maintenance or other programs needed.&lt;/p&gt;

&lt;p&gt;Often it is not easy though. &lt;/p&gt;

&lt;p&gt;Another common way to transfer a file is to use SSH or FTP. Both protocols are ubiquitous and relatively simple to use, but their simplicity relies on one of the computers to be running a server (like &lt;code&gt;openssh&lt;/code&gt;) &lt;strong&gt;and&lt;/strong&gt; it requires the server computer to have port forwarding enabled. It can be pretty much assumed that &lt;em&gt;most&lt;/em&gt; pairs of computers don't meet these requirements (say, two computers in a café) so that makes these common utilities hard to use in most cases.&lt;/p&gt;

&lt;p&gt;File transfers can be easier by eliminating the need for hosting a server or port forwarding. Again, using a &lt;strong&gt;relay server&lt;/strong&gt; allows any two computers to connect to one another without resorting to port forwarding or fiddling with a server.&lt;/p&gt;

&lt;h2&gt;
  
  
  croc = fast + secure + easy
&lt;/h2&gt;

&lt;p&gt;I've been working on an open-source tool that takes these ideas to heart: &lt;a href="https://github.com/schollz/croc"&gt;croc&lt;/a&gt;. Because its open-source and implemented in a language that easily cross compiles (Go) it can be implemented anywhere and thus will (hopefully) always be a fast, secure, and really easy way to send data. &lt;/p&gt;

&lt;p&gt;AFAIK, &lt;em&gt;croc&lt;/em&gt; is the only CLI file-transfer tool does &lt;strong&gt;all&lt;/strong&gt; of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;allows &lt;strong&gt;any two computers&lt;/strong&gt; to transfer data (using a relay)&lt;/li&gt;
&lt;li&gt;provides &lt;strong&gt;end-to-end encryption&lt;/strong&gt; (using PAKE)&lt;/li&gt;
&lt;li&gt;enables easy &lt;strong&gt;cross-platform&lt;/strong&gt; transfers (Windows, Linux, Mac)&lt;/li&gt;
&lt;li&gt;allows &lt;strong&gt;multiple file&lt;/strong&gt; transfers&lt;/li&gt;
&lt;li&gt;allows &lt;strong&gt;resuming transfers&lt;/strong&gt; that are interrupted&lt;/li&gt;
&lt;li&gt;does &lt;em&gt;not&lt;/em&gt; require a server or port forwarding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In developing &lt;em&gt;croc&lt;/em&gt; I took a lot of inspiration from other CLI tools like &lt;a href="https://github.com/zerotier/toss"&gt;toss&lt;/a&gt; and &lt;a href="https://github.com/warner/magic-wormhole"&gt;magic-wormhole&lt;/a&gt; which had some but not all of the qualities above.&lt;sup id="fnref5"&gt;5&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;If you'd like to give it a try, the code is available online at &lt;a href="https://github.com/schollz/croc"&gt;https://github.com/schollz/croc&lt;/a&gt;.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;I wrote about &lt;a href="https://schollz.com/software/sending-a-file/"&gt;sending a file in 2017&lt;/a&gt; and have tried to maintain a list of ways to send files since then. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Some good server-based file-sharing tools: &lt;a href="https://send-anywhere.com"&gt;https://send-anywhere.com&lt;/a&gt;, &lt;a href="https://send.firefox.com"&gt;https://send.firefox.com&lt;/a&gt;, &lt;a href="https://ipfs.io/"&gt;https://ipfs.io/&lt;/a&gt;  ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;For example, if you are transferring a file by uploading at 5 Mbit/s and then, after finishing the upload, downloading at 8 Mbit/s, then the effective transfer rate is 1/2 * 2/(1/5+1/8) = 3.1 Mbit/s - which is slower than either. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;PAKE additionally will prevent eavesdroppers. If anyone "listens in" on the information exchange, then all parties will end up with different strong keys and no one can decrypt anything between them, alerting the users that a eavesdropper is present. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;&lt;a href="https://github.com/zerotier/toss"&gt;toss&lt;/a&gt; cleverly encodes port information in the code phrase, making it simple but it requires using connected computers (no firewalls) and the long random-ish code phrase is hard to "tell" someone. &lt;a href="https://github.com/warner/magic-wormhole"&gt;magic-wormhole&lt;/a&gt; has most everything (currently its missing capabilities for multiple file transfers and file resuming), but it requires installing lots of the Python ecosystem which is tricky for non-developers (and Windows users). ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>filetransfer</category>
      <category>commandline</category>
      <category>go</category>
    </item>
    <item>
      <title>Project graveyard</title>
      <dc:creator>Zack</dc:creator>
      <pubDate>Sun, 19 Aug 2018 16:23:31 +0000</pubDate>
      <link>https://dev.to/schollz/project-graveyard-2494</link>
      <guid>https://dev.to/schollz/project-graveyard-2494</guid>
      <description>&lt;p&gt;I like to write code and always have - my &lt;a href="https://github.com/schollz/BandGenerator"&gt;earliest project is a band name generator&lt;/a&gt; from 2003. On Github  I have &lt;a href="https://github.com/schollz?utf8=%E2%9C%93&amp;amp;tab=repositories&amp;amp;q=&amp;amp;type=source&amp;amp;language="&gt;over 230 repos&lt;/a&gt;, but I also have some on &lt;a href="https://bitbucket.org/schollz/"&gt;Bitbucket&lt;/a&gt;, &lt;a href="https://gitlab.com/schollz"&gt;Gitlab&lt;/a&gt;, and my own &lt;a href="https://fossil.schollz.com/"&gt;self-hosted fossil&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Like &lt;a href="https://dev.to/t/graveyard"&gt;others&lt;/a&gt;, I've had projects that I stopped working on and stopped using altogether - thus relegating them to the project graveyard. Here is a sliver of projects that ended up not meeting my expectations and have been buried in their grave of 1's and 0's.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  🌅 &lt;a href="https://github.com/schollz/sundial"&gt;sundial&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Get the next sunset and sunrise time based on latitude and longitude.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I write most programs in Python and Go but I specifically wrote this one in C because I was planning on using it on an Arduino. I wanted an Arduino to know the time of sunrise and sunsets so it could be used to open/close a chicken coop door to let the chickens out at the right times.&lt;/p&gt;

&lt;p&gt;The code here works great, however I when I put it on an Arduino it was always off by hours. I learned the error occurred because the &lt;a href="https://www.arduino.cc/reference/en/language/variables/data-types/double/"&gt;Uno boards use a double with 32-bit precision, not 64-bit&lt;/a&gt; so there wasn't enough precision to actually make the mathematical calculations in the code. A hardware issue which I didn't see anyway around - oh well, to the grave you go.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I learned&lt;/strong&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Equations for calculating sunrise/sunset times.&lt;/li&gt;
&lt;li&gt;Someone, somewhere, may find your code useful. I got a &lt;a href="https://github.com/schollz/sundial/pull/1"&gt;huge PR&lt;/a&gt; out of the blue from someone who found this code immensely useful even though it had no use to me anymore.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  🥚 &lt;a href="https://github.com/schollz/food-identicon"&gt;Food identicons&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;A identicon made of foods.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I thought since people recognize food really well and are used to seeing recipes, that you could make an identicon that could uniquely identify some bytes with 9 pictures of food arranged in a square.&lt;/p&gt;

&lt;p&gt;The program basically works, but it turned out to be hard to get nice pictures of food that are easily discernible so the identicons looked strange and indecipherable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I learned&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How hard it is to find and collect high quality images.&lt;/li&gt;
&lt;li&gt;How to work with images in Go.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  🎶 &lt;a href="https://github.com/schollz/musicsaur"&gt;musicsaur&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Music synchronization across browsers.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is the one that saddens me the most. I was living in a two story house and I wanted to easily play music on both stories with two computers, so I wrote this program to synchronize music &lt;em&gt;playing through your browser&lt;/em&gt;! It could could also play it headless on Raspberry Pis which was great fun.&lt;/p&gt;

&lt;p&gt;I was never really satisfied that it worked &lt;em&gt;well enough&lt;/em&gt;. Though it worked, every once and awhile you'd get a song that had &amp;gt;30 ms latency between the start times and then it would sound wonky. I used Javascript to control the skipping, but that is always problematic because Javascript is slow and can take ~0-10 ms to accurately skip to the position and it was near impossible to figure out exactly how much time is needed to account for the skip.&lt;/p&gt;

&lt;p&gt;If you want to try, it still works, but you just need to use commit &lt;code&gt;b775910&lt;/code&gt; from the &lt;code&gt;tcolgate/mp3&lt;/code&gt; library since they made a breaking change since I've played with it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I learned&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How hard it is to work with streaming audio.&lt;/li&gt;
&lt;li&gt;How to use HTML5 audio in Javascript&lt;/li&gt;
&lt;li&gt;Time synchronization routines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  🎲 &lt;a href="https://github.com/schollz/no-dice"&gt;No dice&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Generating random numbers from squiggles.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I had an idea of generating random numbers just by drawing quick squiggles and counting the intersections and then doing some modulus. Of course its slower than dice, but its better than nothing if you have no dice. I did some coding to figure out how "random" this can be, but realized that since humans are doing the drawing I'd need to sample from actual humans instead of simulating human drawing with this code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I learned&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How hard it is to simulate human actions.&lt;/li&gt;
&lt;li&gt;How to do bezier curves&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  📘 &lt;a href="https://github.com/schollz/bol"&gt;bol&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;A distributed, synchronized journal.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;All my life I've wanted to create the perfect distributed, synchronized journal. Nothing has ever satisfied. This is one attempt that I abandoned. It had a nice idea and worked across platforms, but I just didn't like using that much because of the UI and didn't have the will to make improve UX. I stopped using it so it became abandoned.&lt;/p&gt;

&lt;p&gt;Since &lt;a href="https://github.com/schollz/bol"&gt;bol&lt;/a&gt; I've worked on a totally CLI version, &lt;a href="https://github.com/schollz/gojot"&gt;gojot&lt;/a&gt;. However I've abandoned and &lt;a href="https://github.com/schollz/gojot"&gt;rewritten gojot 5 times&lt;/a&gt; and I still don't use it now. Lately I've been pretty happy with a web version of another program I wrote, &lt;a href="https://github.com/schollz/cowyo"&gt;cowyo&lt;/a&gt; but I've actually found myself recently rewriting this one into &lt;a href="https://rwtxt.com"&gt;rwtxt&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I learned:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How hard UI/UX design is and how hard I am to please&lt;/li&gt;
&lt;li&gt;How hard it is to make distributed programs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  👥 &lt;a href="https://github.com/schollz/kiki"&gt;kiki&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;A distributed, offline-friendly social network.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There are all sorts of reasons to have alternatives to Facebook, so I decided to try to make one myself. I took a lot of inspiration from &lt;a href="https://www.scuttlebutt.nz/"&gt;scuttlebutt&lt;/a&gt; but wanted to make something that was so easy to use that my mom could use it and wanted something that could be made private if I wanted to.&lt;/p&gt;

&lt;p&gt;I had a lot of fun writing the code, but in the end I realized I wouldn't use it. To use it would encourage other people to use it and then I'd be stuck with all the problems facing other social networks - how to regulate bad behavior while encouraging openness, etc. I had no desire to write code to regulate content but that what you have to do nowadays if you have a social network.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I learned:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How hard social networks are to maintain&lt;/li&gt;
&lt;li&gt;Creating distributed message passing system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  🎹 &lt;a href="https://github.com/schollz/pyplayerpiano"&gt;pypiano&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Let your computer play a piano duet with you.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here is one happy story. I wanted to create an AI to play music with me at the piano. The obvious way to do this at the time was to use Python - they have a great gaming (&lt;a href="https://www.pygame.org/wiki/GettingStarted"&gt;pygame&lt;/a&gt;) library which handles realtime MIDI out-of-the-box. It turned out, though, that development in Windows was super problematic and I found pygame would work with some functions but not with others (see my entire complaints here: &lt;a href="https://rpiai.com/piano"&gt;https://rpiai.com/piano&lt;/a&gt;). So I gave up and abandoned it.&lt;/p&gt;

&lt;p&gt;But, this is a happy story because the project came back from the grave. I had an inclination it'd be easier to write this in Go because it requires so many little async processes, easily done with goroutines. After a few more non-programming obstacles I was happily using the code: &lt;a href="https://github.com/schollz/PIanoAI"&gt;PianoAI&lt;/a&gt;. Maybe its my favorite project I've ever made.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I learned:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How hard it is to do realtime applications, especially with MIDI&lt;/li&gt;
&lt;li&gt;How MIDI works&lt;/li&gt;
&lt;li&gt;Heuristically learning synthetic piano lines&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;I could go on and on and on. At least I can say that I've genuinely learned a lot from writing &lt;em&gt;and abandoning&lt;/em&gt; code - trying (and maybe failing) is my favorite way to learn. I will continue to abandon projects, I think because I just like the journey more than the destination, even if that destination is the graveyard.&lt;/p&gt;

</description>
      <category>graveyard</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Self-hosting with fossil, an alternative to git</title>
      <dc:creator>Zack</dc:creator>
      <pubDate>Tue, 14 Aug 2018 14:27:12 +0000</pubDate>
      <link>https://dev.to/schollz/self-hosting-with-fossil-an-alternative-to-git-33bk</link>
      <guid>https://dev.to/schollz/self-hosting-with-fossil-an-alternative-to-git-33bk</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6gfay7btgpjwz2ab6jht.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6gfay7btgpjwz2ab6jht.png" alt="Fossil image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Github, Gitlab, Bitbucket are all web interfaces for &lt;em&gt;git&lt;/em&gt;. They all do similar things that add on features that aren't implicit in &lt;em&gt;git&lt;/em&gt;, like issues and wiki, etc. &lt;/p&gt;

&lt;p&gt;Unbeknownst to some, &lt;em&gt;fossil&lt;/em&gt; is an alternative to &lt;em&gt;git&lt;/em&gt; that actually has built-in issues, wiki, and easily self-hosts. This is a quickstart to get started with hosting and cloning &lt;em&gt;fossil&lt;/em&gt; repos. The &lt;a href="http://www.fossil-scm.org/index.html/doc/2010-01-01/www/quickstart.wiki" rel="noopener noreferrer"&gt;official quickstart&lt;/a&gt; is very good, although I realized a few tricks in getting the fossil hosting to work on HTTPS behind a reverse-proxy and getting fossil to work with Go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you begin
&lt;/h2&gt;

&lt;p&gt;First, download fossil. There are already binaries available for &lt;em&gt;fossil&lt;/em&gt; for Windows, OS X and Linux. You can find them &lt;a href="https://www.fossil-scm.org/xfer/uv/download.html" rel="noopener noreferrer"&gt;at fossil-scm.org&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  New fossil
&lt;/h2&gt;

&lt;p&gt;Making a new fossil is easy - you can make a new fossil with the &lt;code&gt;init&lt;/code&gt; subcommand.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ fossil init -A schollz 3.fossil
project-id: 7165366dc9d0d3c827573c8ef12cb760f2caa236
server-id:  56adcebbf59b06eb4b621d3daca59f5aa87a9a08
admin-user: schollz (initial password is "35a6g1")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will make a new repository, &lt;code&gt;3.fossil&lt;/code&gt;, with a user &lt;code&gt;schollz&lt;/code&gt; and default password &lt;code&gt;35a6g1&lt;/code&gt;. Make sure to save each fossil repo with the extension &lt;code&gt;.fossil&lt;/code&gt; - this is very important if you want to do hosting.&lt;/p&gt;

&lt;p&gt;You can then change the default password for &lt;code&gt;schollz&lt;/code&gt;. Here you have to enter the password on the command-line, but I like to add it to a file to prevent it from showing up in the bash history.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ vim pass # enter your password in this file
$ fossil user password schollz `cat pass` -R 3.fossil
$ rm pass # this ensures your password doesn't enter the bash history
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want to add another user? Its also easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ fossil user new user2 user2@somewhere.com -R 3.fossil
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you clone the fossil (see below) you will be able to enter the username and password to authenticate the transfer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosting fossils
&lt;/h2&gt;

&lt;p&gt;Say now you have several fossils in the same folder. Make sure that each has the &lt;code&gt;.fossil&lt;/code&gt; suffix - this is very important.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ls fossils
1.fossil 2.fossil 3.fossil
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you can start a server that uses HTTPS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ fossil server . --https --port 8079 --repolist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--https&lt;/code&gt; flag implies HTTPS, so you have to host a reverse-proxy that uses SSL. Its super easy to do this if you are using &lt;a href="https://caddyserver.com/download" rel="noopener noreferrer"&gt;Caddy&lt;/a&gt; as a reverse-proxy (which easily adds HTTPS). In this case your &lt;a href="https://caddyserver.com/tutorial/caddyfile" rel="noopener noreferrer"&gt;Caddyfile&lt;/a&gt; will look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fossil.schollz.com {
        proxy / 127.0.0.1:8079 {
                transparent
        }
        gzip
        log logs/fossil.schollz.com.log
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where you just replace &lt;code&gt;fossil.schollz.com&lt;/code&gt; with &lt;code&gt;yourdomain.com&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloning fossil
&lt;/h2&gt;

&lt;p&gt;Cloning is easy, just make sure to include your user name so you will have the rights to push changes (unless you want to just have your own copy). If you are using "&lt;code&gt;fossil server .&lt;/code&gt;" like above, then you need to add &lt;code&gt;/repo&lt;/code&gt; to tell it you want to clone &lt;code&gt;repo.fossil&lt;/code&gt;. For instance, to clone &lt;code&gt;3.fossil&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ fossil clone https://schollz@fossil.schollz.com/3 3.fossil
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will be prompted for the password, which is the same password that you set up top. Now you can open the repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ fossil open 3.fossil
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Things are then pretty similar to &lt;em&gt;git&lt;/em&gt;. You can add and commit files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ touch README.md
$ fossil add README.md
$ fossil commit README.md # this will automatically push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can easily create pull (get latest changes):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ fossil sync
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Private and public repos
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Private repos
&lt;/h3&gt;

&lt;p&gt;It's pretty easy to make your fossil private. Just goto Admin -$ Security Audit and then click "Take it private".&lt;/p&gt;

&lt;h3&gt;
  
  
  Public repos
&lt;/h3&gt;

&lt;p&gt;This is the default. However, sometimes if you take your repo private you'd like to make it public again. Making your repository public again is a little more involved. &lt;/p&gt;

&lt;p&gt;Basically there are two types of users: "nobody" and "anonymous" users. The "anonymous" refers to permissions given to &lt;em&gt;anyone who logs in&lt;/em&gt;. The "nobody" refers to the permissions given to &lt;em&gt;anyone who visits&lt;/em&gt; the site. To make a repo public again you have to redefine the permissions for both these types of users by going to Admin -$ Users.&lt;/p&gt;

&lt;p&gt;The typical permissions for "nobody" for public access are "gjozr":&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F10tgg2s6zxsjm2o7hiue.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F10tgg2s6zxsjm2o7hiue.PNG" alt="Permissions for public access for "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The typical permissions for "anonymous" for public access are "chmn":&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5j91o75cvvikfeybl5l8.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5j91o75cvvikfeybl5l8.PNG" alt="Permissions for public access for "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure for &lt;code&gt;Go&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;In order to use &lt;em&gt;fossil&lt;/em&gt; with &lt;code&gt;go get&lt;/code&gt; you need to make sure you have the right &lt;a href="https://golang.org/cmd/go/#hdr-Remote_import_paths" rel="noopener noreferrer"&gt;remote import paths&lt;/a&gt; specified in the &lt;code&gt;meta&lt;/code&gt; tag.&lt;/p&gt;

&lt;p&gt;Basically, if you are following the above and you have a fossil repo hosted at &lt;code&gt;https://yourdomain.com/hello-world&lt;/code&gt; then you need to include the meta tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;meta name="go-import" content="yourdomain.com/hello-world fossil https://yourdomain.com/hello-world"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can do this by changing the default fossil skin (Admin -$ Skins). There is a really nice &lt;a href="http://fossil.include-once.org/fossil-skins/raw/googlecode.txt?name=1c1738c248dc1f5784e402a466e926bfd9a703e4" rel="noopener noreferrer"&gt;Google Code skin&lt;/a&gt; available. For convenience, I already have the &lt;a href="https://cowyo.com/fossil_css/raw" rel="noopener noreferrer"&gt;CSS&lt;/a&gt;, &lt;a href="https://cowyo.com/fossil_header2/raw" rel="noopener noreferrer"&gt;Header&lt;/a&gt;, and &lt;a href="https://cowyo.com/fossil_footer/raw" rel="noopener noreferrer"&gt;Footer&lt;/a&gt;. Just edit each component (Step 4 under Skins) and then check both boxes in Step 7 and hit "Publish Draft1".&lt;/p&gt;

&lt;h2&gt;
  
  
  General
&lt;/h2&gt;

&lt;p&gt;I like to have the &lt;code&gt;README.md&lt;/code&gt; in the main repo be the first thing you see on the web UI - just like Github/Gitlab/Bitbucket. To do this, make a &lt;code&gt;README.md&lt;/code&gt; file and then goto Admin -$ Configuration and look for the &lt;strong&gt;Index Page&lt;/strong&gt; and change it to &lt;code&gt;/doc/tip/README.md&lt;/code&gt;. Make sure to then press "Apply Changes" at the top.&lt;/p&gt;

&lt;p&gt;Another trick - you can add &lt;code&gt;fossil&lt;/code&gt; as a parameter to your &lt;code&gt;~/.zshrc&lt;/code&gt; if&lt;br&gt;
you are using &lt;em&gt;oh-my-zsh&lt;/em&gt; (if you aren't using it, why?). Just make sure&lt;br&gt;
you have this line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plugins=(git history fossil) 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>versioncontrol</category>
      <category>git</category>
      <category>fossil</category>
    </item>
  </channel>
</rss>
