<?xml version="1.0" encoding="utf-8" ?>

<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>screaming.computer</title>
		<description>Blog of Adam Rogers - videographer by day, photographer/tech enthusiast/mad scientist by night.</description>
		<link>https://screaming.computer/blog/</link>
		<image>
			<title>screaming.computer</title>
			<link>https://screaming.computer/blog/</link>
			<url>https://screaming.computer/rss-144x144.png</url>
			<height>144</height>
			<width>144</width>
		</image>
		<atom:link href="https://screaming.computer/blog/rss/" rel="self" type="application/rss+xml" />

		<copyright>Copyright 2019-2026 by Adam Rogers</copyright>
		<lastBuildDate>Thu, 29 Feb 2024 19:13:46 GMT</lastBuildDate>
		<webMaster>webmaster -NOSPAM- @ screaming . computer (Webmaster)</webMaster>
		<managingEditor>echo -NOSPAM- @ screaming . computer (Adam Rogers)</managingEditor>
		<language>en-ca</language>
		<docs>http://blogs.law.harvard.edu/tech/rss</docs>

		<item>
			<title>Secure Socket Layer</title>
			<link>https://screaming.computer/blog/2023-12/secure-socket-layer</link>
			<description>
&lt;p&gt;This &lt;strong&gt;SCREAMING.COMPUTER&lt;/strong&gt; website finally has an SSL certificate, which means you can access it using &lt;a href="https://screaming.computer/blog/"&gt;HTTPS&lt;/a&gt;, like any well-behaved site in 2023!&lt;/p&gt;

&lt;p&gt;The old HTTP version is still accessible, but HTTPS is the new default. This should make Google happier (it seems to downrank non-SSL sites) as well as being better supported in all modern web browsers.&lt;/p&gt;

&lt;p&gt;If you have any &lt;a href="http://screaming.computer/"&gt;http://screaming.computer&lt;/a&gt; bookmarks, go ahead and add a little &amp;lsquo;S&amp;rsquo; to upgrade to the secure site: &lt;a href="https://screaming.computer/"&gt;&lt;strong&gt;https&lt;/strong&gt;://screaming.computer&lt;/a&gt;.&lt;/p&gt;
			</description>
			<guid isPermaLink="true">https://screaming.computer/blog/2023-12/secure-socket-layer</guid>
			<pubDate>Fri, 22 Dec 2023 16:07:37 GMT</pubDate>
			<source url="https://screaming.computer/blog/rss/">screaming.computer</source>
		</item>

		<item>
			<title>Random Garfield Comic Titles</title>
			<link>https://screaming.computer/blog/2023-12/random-garfield-comic-titles</link>
			<description>
&lt;p&gt;Let's generate some random no-name &lt;em&gt;Garfield&lt;/em&gt; comics.&lt;/p&gt;

&lt;p&gt;To begin, we need an unsettlingly-derivative name for the cat, and a suitable subtitle. We're really leaning in to the &amp;lsquo;discount,&amp;rsquo; &amp;lsquo;dollar-store,&amp;rsquo; &amp;ldquo;we have &lt;em&gt;Garfield&lt;/em&gt; at home&amp;rdquo; aspect of these titles.&lt;/p&gt;

&lt;p&gt;Let's use the proper &lt;em&gt;Garfield&lt;/em&gt; fonts, &amp;ldquo;&lt;a href="https://fontsgeek.com/fonts/Alfredo-Heavy-Regular"&gt;Alfredo Heavy Regular&lt;/a&gt;&amp;rdquo; for the title and &amp;ldquo;&lt;a href="https://fontsgeek.com/fonts/Garfield-Regular"&gt;Garfield Regular&lt;/a&gt;&amp;rdquo; for the tagline:&lt;/p&gt;

&lt;p&gt;&lt;!-- &lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20231221-garf_title1.png" alt="Girfont, the pampered character"&gt;&lt;br&gt;
--&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20231221-garf_title2.png" alt="Ferferd, the lazy pet"&gt;&lt;br&gt;
&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20231221-garf_title3.png" alt="Ratfied, the hungry feline"&gt;&lt;br&gt;
&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20231221-garf_title4.png" alt="Garboond, the fat character"&gt;&lt;br&gt;
&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20231221-garf_title5.png" alt="Jorfiend, the grumpy feline"&gt;&lt;br&gt;
&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20231221-garf_title0.png" alt="Gamfielt, the lazy cat"&gt;&lt;br&gt;
&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20231221-garf_title7.png" alt="Darfeeg, the overweight cat"&gt;&lt;br&gt;
&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20231221-garf_title6.png" alt="Gokfield, the clever cartoon"&gt;&lt;!-- &lt;br&gt;
&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20231221-garf_title8.png" alt="Gamdild, the comical pet"&gt;&lt;br&gt;
&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20231221-garf_title9.png" alt="Glazfierk, the sarcastic cat"&gt; --&gt;&lt;/p&gt;

&lt;p&gt;Then of course we resize to the &lt;a href="https://screaming.computer/blog/2020-02/install-and-test-the-escpos-php-library"&gt;receipt printer&lt;/a&gt; width and &lt;a href="https://screaming.computer/blog/2023-05/imagick-image-dithering-for-thermal-receipt-printers"&gt;crunch it down&lt;/a&gt; with 1-bit dithering:&lt;/p&gt;

&lt;p&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20231217-garf_title_dither2.png" alt="Gugfend, the grumpy feline"&gt;&lt;br&gt;
&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20231217-garf_title_dither5.png" alt="Dapield, the sarcastic cat"&gt;&lt;br&gt;
&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20231217-garf_title_dither4.png" alt="Gorfoog, the lazy animal"&gt;&lt;/p&gt;

&lt;p&gt;The name-generation algorithm is rather carefully thought-out... we want maximum weirdness while maintaining similarity to the original name (hence the &lt;var&gt;score&lt;/var&gt; variable). Cadence and syllables all factor in to the word components.&lt;/p&gt;

&lt;p&gt;Notice the weighted-random tagging appended to each string value, which specifies the relative likelihood that any given item will be chosen.&lt;/p&gt;

&lt;!-- HTML generated using hilite.me // Replace initial div and pre with code.codeArea --&gt;

&lt;code&gt;&lt;span&gt;function&lt;/span&gt; &lt;span&gt;GarfTaglineGenerate&lt;/span&gt;() &lt;span&gt;:&lt;/span&gt; string
{
    &lt;span&gt;$tag&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;&amp;#39;&amp;#39;&lt;/span&gt;;
    &lt;span&gt;$tag&lt;/span&gt; &lt;span&gt;.=&lt;/span&gt; WeightedStringRand (&lt;span&gt;array&lt;/span&gt; (&lt;span&gt;&amp;#39;lazy|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;fat|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;hungry|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;grumpy|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;pampered|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;sarcastic|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;overweight|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;comical|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;furry|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;funny|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;amusing|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;clever|1&amp;#39;&lt;/span&gt;)) &lt;span&gt;.&lt;/span&gt; &lt;span&gt;&amp;#39; &amp;#39;&lt;/span&gt;;
    &lt;span&gt;$tag&lt;/span&gt; &lt;span&gt;.=&lt;/span&gt; WeightedStringRand (&lt;span&gt;array&lt;/span&gt; (&lt;span&gt;&amp;#39;cat|6&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;feline|5&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;pet|4&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;animal|4&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;cartoon|3&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;creature|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;character|1&amp;#39;&lt;/span&gt;));
    &lt;span&gt;return&lt;/span&gt; (&lt;span&gt;$tag&lt;/span&gt;);
}
&lt;/code&gt;

&lt;code&gt;&lt;span&gt;function&lt;/span&gt; &lt;span&gt;GarfNameGenerate&lt;/span&gt;() &lt;span&gt;:&lt;/span&gt; string
{
    &lt;span&gt;do&lt;/span&gt;
    {
        &lt;span&gt;$name&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;&amp;#39;&amp;#39;&lt;/span&gt;;
        &lt;span&gt;$score&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;0&lt;/span&gt;;

        &lt;span&gt;// --- G ----&lt;/span&gt;
        &lt;span&gt;$name&lt;/span&gt; &lt;span&gt;.=&lt;/span&gt; (&lt;span&gt;$part&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; WeightedStringRand (&lt;span&gt;array&lt;/span&gt; (&lt;span&gt;&amp;#39;G|10&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;X|0&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;D|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;F|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;B|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;R|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;J|2&amp;#39;&lt;/span&gt;)));
        &lt;span&gt;if&lt;/span&gt; (&lt;span&gt;$part&lt;/span&gt; &lt;span&gt;==&lt;/span&gt; &lt;span&gt;&amp;#39;G&amp;#39;&lt;/span&gt;)
            &lt;span&gt;$score&lt;/span&gt; &lt;span&gt;+=&lt;/span&gt; &lt;span&gt;2&lt;/span&gt;;

        &lt;span&gt;// --- . ----&lt;/span&gt;
        &lt;span&gt;$part&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; WeightedStringRand (&lt;span&gt;array&lt;/span&gt; (&lt;span&gt;&amp;#39;|10&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;r|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;w|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;l|2&amp;#39;&lt;/span&gt;));
        &lt;span&gt;if&lt;/span&gt; (&lt;span&gt;$part&lt;/span&gt; &lt;span&gt;==&lt;/span&gt; &lt;span&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span&gt;||&lt;/span&gt; mb_endswithi (&lt;span&gt;$name&lt;/span&gt;, &lt;span&gt;$part&lt;/span&gt;) &lt;span&gt;||&lt;/span&gt; &lt;span&gt;in_array&lt;/span&gt; (&lt;span&gt;$name&lt;/span&gt; &lt;span&gt;.&lt;/span&gt; &lt;span&gt;$part&lt;/span&gt;, &lt;span&gt;array&lt;/span&gt; (&lt;span&gt;&amp;#39;Xw&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;Rw&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;Jw&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;Fw&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;Bw&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;Xr&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;Jr&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;Rl&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;Xl&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;Dl&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;Jl&amp;#39;&lt;/span&gt;)))
            &lt;span&gt;$score&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;;
        &lt;span&gt;else&lt;/span&gt;
            &lt;span&gt;$name&lt;/span&gt; &lt;span&gt;.=&lt;/span&gt; &lt;span&gt;$part&lt;/span&gt;;

        &lt;span&gt;// --- a ----&lt;/span&gt;
        &lt;span&gt;$name&lt;/span&gt; &lt;span&gt;.=&lt;/span&gt; (&lt;span&gt;$part&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; WeightedStringRand (&lt;span&gt;array&lt;/span&gt; (&lt;span&gt;&amp;#39;a|5&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;e|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;i|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;o|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;u|1&amp;#39;&lt;/span&gt;)));
        &lt;span&gt;if&lt;/span&gt; (&lt;span&gt;$part&lt;/span&gt; &lt;span&gt;==&lt;/span&gt; &lt;span&gt;&amp;#39;a&amp;#39;&lt;/span&gt;)
            &lt;span&gt;$score&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;;

        &lt;span&gt;// --- r ----&lt;/span&gt;
        &lt;span&gt;$name&lt;/span&gt; &lt;span&gt;.=&lt;/span&gt; (&lt;span&gt;$part&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; WeightedStringRand (&lt;span&gt;array&lt;/span&gt; (&lt;span&gt;&amp;#39;r|5&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;n|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;l|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;s|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;d|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;t|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;b|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;k|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;m|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;g|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;f|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;z|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;x|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;p|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;|1&amp;#39;&lt;/span&gt;)));
        &lt;span&gt;if&lt;/span&gt; (&lt;span&gt;$part&lt;/span&gt; &lt;span&gt;==&lt;/span&gt; &lt;span&gt;&amp;#39;r&amp;#39;&lt;/span&gt;)
            &lt;span&gt;$score&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;;

        &lt;span&gt;// --- f ----&lt;/span&gt;
        &lt;span&gt;do&lt;/span&gt;   {
            &lt;span&gt;$part&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; WeightedStringRand (&lt;span&gt;array&lt;/span&gt; (&lt;span&gt;&amp;#39;f|10&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;d|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;g|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;s|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;n|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;t|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;b|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;k|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;m|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;g|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;z|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;x|0&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;p|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;ph|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;sh|1&amp;#39;&lt;/span&gt;));
        } &lt;span&gt;while&lt;/span&gt; ((&lt;span&gt;$part&lt;/span&gt; &lt;span&gt;!=&lt;/span&gt; &lt;span&gt;&amp;#39;f&amp;#39;&lt;/span&gt; &lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt; mb_endswithi (&lt;span&gt;$name&lt;/span&gt;, mb_firstchar (&lt;span&gt;$part&lt;/span&gt;)))
                &lt;span&gt;||&lt;/span&gt; &lt;span&gt;in_array&lt;/span&gt; (mb_lastchar (&lt;span&gt;$name&lt;/span&gt;) &lt;span&gt;.&lt;/span&gt; &lt;span&gt;$part&lt;/span&gt;, &lt;span&gt;array&lt;/span&gt; (&lt;span&gt;&amp;#39;zs&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;zsh&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;sz&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;xz&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;xs&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;xsh&amp;#39;&lt;/span&gt;)));
        &lt;span&gt;$name&lt;/span&gt; &lt;span&gt;.=&lt;/span&gt; &lt;span&gt;$part&lt;/span&gt;;
        &lt;span&gt;if&lt;/span&gt; (&lt;span&gt;$part&lt;/span&gt; &lt;span&gt;==&lt;/span&gt; &lt;span&gt;&amp;#39;f&amp;#39;&lt;/span&gt;)
            &lt;span&gt;$score&lt;/span&gt; &lt;span&gt;+=&lt;/span&gt; &lt;span&gt;3&lt;/span&gt;;

        &lt;span&gt;// --- ie ----&lt;/span&gt;
        &lt;span&gt;$name&lt;/span&gt; &lt;span&gt;.=&lt;/span&gt; (&lt;span&gt;$part&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; WeightedStringRand (&lt;span&gt;array&lt;/span&gt; (&lt;span&gt;&amp;#39;ie|5&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;ee|3&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;oo|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;ae|0&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;ou|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;a|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;e|3&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;i|3&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;o|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;u|2&amp;#39;&lt;/span&gt;)));
        &lt;span&gt;if&lt;/span&gt; (&lt;span&gt;$part&lt;/span&gt; &lt;span&gt;==&lt;/span&gt; &lt;span&gt;&amp;#39;ie&amp;#39;&lt;/span&gt;)
            &lt;span&gt;$score&lt;/span&gt; &lt;span&gt;+=&lt;/span&gt; &lt;span&gt;2&lt;/span&gt;;
        &lt;span&gt;else&lt;/span&gt; &lt;span&gt;if&lt;/span&gt; (&lt;span&gt;in_array&lt;/span&gt; (&lt;span&gt;$part&lt;/span&gt;, &lt;span&gt;array&lt;/span&gt; (&lt;span&gt;&amp;#39;ee&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;e&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;i&amp;#39;&lt;/span&gt;)))
            &lt;span&gt;$score&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;;

        &lt;span&gt;// --- ld ----&lt;/span&gt;
        &lt;span&gt;$name&lt;/span&gt; &lt;span&gt;.=&lt;/span&gt; (&lt;span&gt;$part&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; WeightedStringRand (&lt;span&gt;array&lt;/span&gt; (&lt;span&gt;&amp;#39;ld|5&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;d|3&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;g|3&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;lk|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;ng|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;nk|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;k|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;rk|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;rd|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;rf|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;lt|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;t|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;nd|2&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;nt|2&amp;#39;&lt;/span&gt;,
                                                    &lt;span&gt;&amp;#39;rds|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;pt|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;md|0&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;r|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;s|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;ss|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;ck|1&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;ll|1&amp;#39;&lt;/span&gt;)));
        &lt;span&gt;if&lt;/span&gt; (&lt;span&gt;$part&lt;/span&gt; &lt;span&gt;==&lt;/span&gt; &lt;span&gt;&amp;#39;ld&amp;#39;&lt;/span&gt;)
            &lt;span&gt;$score&lt;/span&gt; &lt;span&gt;+=&lt;/span&gt; &lt;span&gt;3&lt;/span&gt;;
        &lt;span&gt;else&lt;/span&gt; &lt;span&gt;if&lt;/span&gt; (mb_lastchar (&lt;span&gt;$part&lt;/span&gt;) &lt;span&gt;==&lt;/span&gt; &lt;span&gt;&amp;#39;d&amp;#39;&lt;/span&gt;)
            &lt;span&gt;$score&lt;/span&gt; &lt;span&gt;+=&lt;/span&gt; &lt;span&gt;2&lt;/span&gt;;

    } &lt;span&gt;while&lt;/span&gt; (&lt;span&gt;$score&lt;/span&gt; &lt;span&gt;&amp;lt;&lt;/span&gt; &lt;span&gt;7&lt;/span&gt; &lt;span&gt;||&lt;/span&gt; mb_containsanyi (&lt;span&gt;$name&lt;/span&gt;, &lt;span&gt;array&lt;/span&gt; (
            &lt;span&gt;&amp;#39;garfield&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;iemd&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;rir&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;rur&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;ooll&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;eell&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;eeld&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;oold&amp;#39;&lt;/span&gt;,
            &lt;span&gt;&amp;#39;god&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;denied&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;golfing&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;golfer&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;refund&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;getfound&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;gunfiend&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;food&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;feed&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;girl&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;barf&amp;#39;&lt;/span&gt;,
            &lt;span&gt;&amp;#39;damn&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;piss&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;fuck&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;shit&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;dick&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;cunt&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;kunt&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;fag&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;porn&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;suck&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;jiz&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;homo&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;poop&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;cock&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;rape&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;kill&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;drug&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;ass&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;gook&amp;#39;&lt;/span&gt;))
            &lt;span&gt;||&lt;/span&gt; mb_startsanyi (&lt;span&gt;$name&lt;/span&gt;, &lt;span&gt;array&lt;/span&gt; (&lt;span&gt;&amp;#39;fat&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;fap&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;gag&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;dad&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;jap&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;bad&amp;#39;&lt;/span&gt;))
            &lt;span&gt;||&lt;/span&gt; mb_endsanyi   (&lt;span&gt;$name&lt;/span&gt;, &lt;span&gt;array&lt;/span&gt; (&lt;span&gt;&amp;#39;fat&amp;#39;&lt;/span&gt;, &lt;span&gt;&amp;#39;find&amp;#39;&lt;/span&gt;)));

    &lt;span&gt;return&lt;/span&gt; (&lt;span&gt;$name&lt;/span&gt;);
}
&lt;/code&gt;

&lt;p&gt;As a final step we also must filter out inappropriate words and substrings from the random results. Some filters just clear out awkward sequences of consonants and vowels, while others filter out hateful terms and inappropriate sexual references. It's a bit weird to have to put these phrases into code, but it's necessary to produce sanitized results.&lt;/p&gt;

&lt;p&gt;The support functions should be obvious, but the &lt;a href="https://screaming.computer/blog/files/20231221-garfield_name_generator.zip"&gt;full source code, plus fonts&lt;/a&gt; are available for download for reference.&lt;/p&gt;

&lt;p&gt;Observe some potential results from the random title generator:&lt;/p&gt;

&lt;pre&gt;Gazfink, the grumpy animal
Gemfield, the lazy feline
Berfuld, the overweight animal
Gerzold, the grumpy pet
Glunfeld, the lazy cat
Forfild, the clever creature
Gapfeek, the hungry animal
Donfield, the lazy cartoon
Gufieg, the comical pet
Gamberd, the clever feline
Rarfing, the fat pet
Gazfeeg, the fat cat
Gamfarf, the pampered creature
Garfierf, the fat cartoon
Gapfipt, the clever cat
Jorfurd, the amusing cat
Gamdeed, the clever animal
Gokfilk, the grumpy cartoon
Gerfouss, the amusing cat
Gizfeld, the lazy creature
Gofieg, the pampered pet
Jarkild, the grumpy cat
Glatfeeg, the overweight animal
Gerfeeng, the furry pet
Faffiet, the fat feline
Gafet, the grumpy pet
Gonfierds, the comical character
Ganshild, the fat cat
Grarfied, the grumpy creature
Danfod, the fat feline&lt;/pre&gt;

&lt;p&gt;All-in-all, there are approximately 45,000 possible names, plus 84 unique subtitles. Seems like a good start for a random &lt;em&gt;Garfield&lt;/em&gt; generator.&lt;/p&gt;
			</description>
			<guid isPermaLink="true">https://screaming.computer/blog/2023-12/random-garfield-comic-titles</guid>
			<pubDate>Sun, 17 Dec 2023 09:18:07 GMT</pubDate>
			<source url="https://screaming.computer/blog/rss/">screaming.computer</source>
		</item>

		<item>
			<title>[UPDATED] Set up a LAMP server VM in UnRAID</title>
			<link>https://screaming.computer/blog/2023-09/updated-set-up-a-lamp-server-vm-in-unraid</link>
			<description>
&lt;div id="guide_top"&gt;
&lt;p&gt;&lt;small&gt;Guide v6.4 / Updated 2023-11-24 / First published 2023-09-24 / &lt;span&gt;echo (at) screaming (dot) computer&lt;/span&gt;&lt;/small&gt;&lt;/p&gt;

&lt;div&gt;
&lt;p&gt;This is an updated version of the original &lt;a href="https://screaming.computer/blog/2020-01/set-up-a-lamp-server-vm-in-unraid"&gt;LAMP server VM in UnRAID&lt;/a&gt; setup guide.&lt;br&gt;
New in this version: LTS release of Linux Mint; upgrade to PHP 8; switch from MySQL to MariaDB&lt;/p&gt;
&lt;/div&gt;

&lt;h3 id="guide_preface"&gt;Preface&lt;/h3&gt;

&lt;p&gt;Over the years while working on various projects, I've often wished for a fully-featured web development environment confined to my local network.  This would allow for offline development (before uploading to a public server), and also allow hosting of local projects that are never meant to be online.&lt;/p&gt;

&lt;p&gt;This is an update of my &lt;a href="https://screaming.computer/blog/2020-01/set-up-a-lamp-server-vm-in-unraid"&gt;previous &lt;strong&gt;LAMP setup guide&lt;/strong&gt;&lt;/a&gt;, where we host a &lt;strong&gt;LAMP server&lt;/strong&gt; in an &lt;strong&gt;UnRAID VM&lt;/strong&gt;. Updated to use &lt;strong&gt;PHP 8&lt;/strong&gt; and replacing MySQL with (open source, drop-in replacement) &lt;a href="https://mariadb.com/"&gt;&lt;strong&gt;MariaDB&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Large portions of this guide are unchanged from the previous version.&lt;/p&gt;

&lt;p&gt;Note that I will refer to MariaDB as &amp;ldquo;MySQL&amp;rdquo; throughout, for consistency with traditional LAMP. MariaDB advertises substantial compatibility with MySQL, and all MySQL commands are aliased to the MariaDB executables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Please Note:&lt;/em&gt;&lt;/strong&gt; The choice of software and settings are entirely based on what I imagine my personal needs to be, and depend upon my current (possibly flawed) understanding of the software involved. &lt;strong&gt;This guide might not be suitable for you&lt;/strong&gt;, and even if it is, &lt;strong&gt;it might contain errors&lt;/strong&gt;. Proceed at your own discretion.&lt;/p&gt;

&lt;p&gt;Content may be updated and expanded over time.... take note of the &amp;ldquo;Updated&amp;rdquo; date, above.&lt;/p&gt;


&lt;h3 id="guide_overview"&gt;Overview&lt;/h3&gt;

&lt;p&gt;Operating system:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;Linux Mint Xfce 21.2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Server:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;Apache 2.4&lt;/li&gt;
	&lt;li&gt;MariaDB 10.11&lt;/li&gt;
	&lt;li&gt;PHP 8.2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additional features:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;phpMyAdmin 5.1&lt;/li&gt;
	&lt;li&gt;Samba network file sharing&lt;/li&gt;
	&lt;li&gt;Virtual hosts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;*&amp;nbsp;version numbers as of this writing&lt;/em&gt;&lt;/p&gt;


&lt;h3 id="guide_accounts"&gt;1. User accounts&lt;/h3&gt;

&lt;p&gt;Once you have completed all steps in this guide, a variety of accounts and users will exist.&lt;/p&gt;

&lt;p&gt;Anywhere you see a &lt;var&gt;VARIABLE&lt;/var&gt; in this document, substitute your chosen value.  To change passwords later, refer to appendix A.&lt;/p&gt;


&lt;h4&gt;1.1 Linux Users&lt;/h4&gt;

&lt;p&gt;Linux Mint does not have a separate user named &lt;code&gt;root&lt;/code&gt;.  The first user you create during installation gets root privileges; in this guide the primary admin user is &lt;var&gt;VMUSER&lt;/var&gt;.&lt;/p&gt;

&lt;table&gt;
&lt;tbody&gt;
	&lt;tr&gt;
		&lt;td&gt;&lt;code&gt;root&lt;/code&gt;&lt;/td&gt;
		&lt;td&gt;&lt;var&gt;VMPASS&lt;/var&gt;&lt;/td&gt;
		&lt;td&gt;Virtual root user; not a true account&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;&lt;var&gt;VMUSER&lt;/var&gt;&lt;/td&gt;
		&lt;td&gt;&lt;var&gt;VMPASS&lt;/var&gt;&lt;/td&gt;
		&lt;td&gt;Primary admin user; has root privileges; member of the &lt;code&gt;www-data&lt;/code&gt; usergroup&lt;/td&gt;
	&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;


&lt;h4&gt;1.2 SQL Users&lt;/h4&gt;

&lt;table&gt;
&lt;tbody&gt;
	&lt;tr&gt;
		&lt;td&gt;&lt;code&gt;root&lt;/code&gt;&lt;/td&gt;
		&lt;td&gt;&lt;var&gt;SQLPASS&lt;/var&gt;&lt;/td&gt;
		&lt;td&gt;MySQL administrator; requires local superuser privileges to use&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;&lt;var&gt;SQLUSER&lt;/var&gt;&lt;/td&gt;
		&lt;td&gt;&lt;var&gt;SQLPASS&lt;/var&gt;&lt;/td&gt;
		&lt;td&gt;MySQL administrator; used to access phpMyAdmin&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;&lt;var&gt;DBUSER&lt;/var&gt;&lt;/td&gt;
		&lt;td&gt;&lt;var&gt;DBPASS&lt;/var&gt;&lt;/td&gt;
		&lt;td&gt;Limited user with permission to manage only the &lt;var&gt;DBNAME&lt;/var&gt; database&lt;/td&gt;
	&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;You should create separate low-privilege users for accessing your databases from PHP projects, such as &lt;var&gt;DBUSER&lt;/var&gt;, above; refer to &lt;a href="https://docs.phpmyadmin.net/en/latest/privileges.html"&gt;phpMyAdmin user management&lt;/a&gt;, and section 8 of this guide.&lt;/p&gt;


&lt;h4&gt;1.3 Samba User&lt;/h4&gt;

&lt;table&gt;
&lt;tbody&gt;
	&lt;tr&gt;
		&lt;td&gt;&lt;var&gt;SMBUSER&lt;/var&gt;&lt;/td&gt;
		&lt;td&gt;&lt;var&gt;SMBPASS&lt;/var&gt;&lt;/td&gt;
		&lt;td&gt;Used to connect to the network share &lt;code&gt;\\mint-vm\www&lt;/code&gt;; member of the &lt;code&gt;www-data&lt;/code&gt; usergroup&lt;/td&gt;
	&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;


&lt;h4&gt;1.4 Linux Usergroups&lt;/h4&gt;

&lt;table&gt;
&lt;tbody&gt;
	&lt;tr&gt;
		&lt;td&gt;&lt;code&gt;www-data&lt;/code&gt;&lt;/td&gt;
		&lt;td&gt;Any member of this group has full R/W access to web files, locally via &lt;code&gt;/var/www&lt;/code&gt; or remotely via &lt;code&gt;\\mint-vm\www&lt;/code&gt;&lt;/td&gt;
	&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;


&lt;h3 id="guide_vm"&gt;2. UnRAID VM setup&lt;/h3&gt;

&lt;p&gt;Download a long-term support (LTS) release of &lt;a href="https://linuxmint.com/download_all.php"&gt;Linux Mint Xfce 64-bit&lt;/a&gt;&lt;br&gt;
Place the ISO file in UnRAID &lt;code&gt;/mnt/user/isos&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;UnRAID [VMs] tab &amp;rarr; [Add VM] &amp;rarr; [Linux]&lt;/p&gt;

&lt;p&gt;Name:  &lt;kbd&gt;Linux Mint Xfce&lt;/kbd&gt;&lt;br&gt;
Initial Memory:  &lt;kbd&gt;8192 MB&lt;/kbd&gt;&lt;br&gt;
Max Memory:  &lt;kbd&gt;8192 MB&lt;/kbd&gt;&lt;br&gt;
USB Controller:  &lt;kbd&gt;3.0 (qemu XHCI)&lt;/kbd&gt;&lt;br&gt;
OS Install ISO:  &lt;em&gt;[select downloaded ISO file]&lt;/em&gt;&lt;br&gt;
Primary vDisk Size:  &lt;kbd&gt;48G&lt;/kbd&gt;&lt;br&gt;
[Create]&lt;/p&gt;

&lt;p&gt;Launch [VNC Remote] from &amp;ldquo;Linux Mint Xfce&amp;rdquo; VM dropdown menu&lt;/p&gt;


&lt;h3 id="guide_linux"&gt;3. Linux installation&lt;/h3&gt;

&lt;p&gt;From the boot menu, run [Start Linux Mint]&lt;br&gt;
From the desktop, run [Install Linux Mint]&lt;br&gt;
Accept all defaults, then [Install Now]&lt;/p&gt;

&lt;p&gt;Your name:  &lt;kbd&gt;Administrator&lt;/kbd&gt;&lt;br&gt;
Your computer's name:  &lt;kbd&gt;mint-vm&lt;/kbd&gt;&lt;br&gt;
Pick a username:  &lt;kbd&gt;&lt;var&gt;VMUSER&lt;/var&gt;&lt;/kbd&gt;&lt;br&gt;
Choose a password:  &lt;kbd&gt;&lt;var&gt;VMPASS&lt;/var&gt;&lt;/kbd&gt;&lt;br&gt;
Confirm your password:  &lt;kbd&gt;&lt;var&gt;VMPASS&lt;/var&gt;&lt;/kbd&gt;&lt;br&gt;
Log in automatically:  &lt;em&gt;yes&lt;/em&gt;&lt;br&gt;
[Continue]&lt;/p&gt;

&lt;p&gt;Wait for installation, then [Restart Now]&lt;br&gt;
&amp;ldquo;Please remove the installation medium then press enter&amp;rdquo; &amp;rarr; just press enter&lt;br&gt;
Wait for restart&lt;br&gt;
Uncheck [Show this dialog at startup] on Welcome screen, then close it&lt;/p&gt;

&lt;p&gt;Open [Update Manager] from the tray&lt;br&gt;
[Edit] menu &amp;rarr; [Preferences] &amp;rarr; Notifications: set all values to &lt;kbd&gt;90&lt;/kbd&gt; days&lt;br&gt;
Close [Preferences] window&lt;br&gt;
Install all available updates&lt;/p&gt;

&lt;p&gt;Open [System Reports] from the tray (warning icon with exclamation mark)&lt;br&gt;
[Ignore this report] for each detected issue&lt;/p&gt;

&lt;p&gt;Open [Power Manager] from the tray (lightning bolt icon)&lt;br&gt;
[Display] tab &amp;rarr; uncheck [Display power management], set [Blank after] to never&lt;br&gt;
[Security] tab &amp;rarr; [Automatically lock the session] &amp;rarr; [Never]&lt;/p&gt;

&lt;p&gt;Start Menu &amp;rarr; All Applications &amp;rarr; [Settings Manager]&lt;br&gt;
[Desktop] icon &amp;rarr; [Icons] tab &amp;rarr; Default Icons: &amp;#9745;&amp;nbsp;Removable Devices&lt;/p&gt;

&lt;p&gt;Start Menu &amp;rarr; All Applications &amp;rarr; [Synaptic Package Manager]&lt;br&gt;
[Status] button &amp;rarr; [Installed] &amp;rarr; Uninstall packages by clicking the checkbox and selecting &amp;ldquo;Mark for complete removal&amp;rdquo;&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;celluloid&lt;/li&gt;
	&lt;li&gt;drawing&lt;/li&gt;
	&lt;li&gt;growisofs&lt;/li&gt;
	&lt;li&gt;hexchat&lt;/li&gt;
	&lt;li&gt;hexchat-common&lt;/li&gt;
	&lt;li&gt;hypnotix&lt;/li&gt;
	&lt;li&gt;libreoffice-common&lt;/li&gt;
	&lt;li&gt;libreoffice-*&lt;/li&gt;
	&lt;li&gt;mythes-*&lt;/li&gt;
	&lt;li&gt;pix&lt;/li&gt;
	&lt;li&gt;pix-data&lt;/li&gt;
	&lt;li&gt;rhythmbox&lt;/li&gt;
	&lt;li&gt;rhythmbox-data&lt;/li&gt;
	&lt;li&gt;thunderbird&lt;/li&gt;
	&lt;li&gt;transmission-common&lt;/li&gt;
	&lt;li&gt;uno-libs-private&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;[Apply] button &amp;rarr; [Apply]&lt;/p&gt;

&lt;p&gt;Start Menu &amp;rarr; All Applications &amp;rarr; [Session and Startup]&lt;br&gt;
[Application Autostart] tab &amp;rarr; Uncheck programs:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;mintwelcome&lt;/li&gt;
	&lt;li&gt;PulseAudio Sound System&lt;/li&gt;
	&lt;li&gt;Update Manager&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start Menu &amp;rarr; Power Icon &amp;rarr; [Restart]&lt;/p&gt;


&lt;h3 id="guide_lan"&gt;4. LAN configuration&lt;/h3&gt;

&lt;p&gt;These instructions apply to routers running &lt;em&gt;Tomato&lt;/em&gt; firmware; tested on &lt;em&gt;&lt;a href="https://exotic.se/freshtomato/"&gt;FreshTomato&lt;/a&gt; v2018.4&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;var&gt;STATIC-IP&lt;/var&gt; is an IP address suitable for your LAN; ex: &lt;code&gt;192.168.0.55&lt;/code&gt;&lt;br&gt;
&lt;var&gt;HOSTNAME&lt;/var&gt; is a local hostname for your server; using an unassigned TLD such as &lt;em&gt;.home&lt;/em&gt; or &lt;em&gt;.lan&lt;/em&gt; is recommended (avoid &lt;em&gt;.local&lt;/em&gt; as it is reserved for use with mDNS); ex: &lt;code&gt;mint-vm.home&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Log in to your router&lt;/p&gt;


&lt;h4&gt;4.1 Set a static IP address&lt;/h4&gt;

&lt;p&gt;[Status] &amp;rarr; [Device List] &amp;rarr; &lt;em&gt;(your VM)&lt;/em&gt; &amp;rarr; [static]&lt;br&gt;
Set a &lt;var&gt;STATIC-IP&lt;/var&gt; address, [Add], [Save]&lt;/p&gt;

&lt;p&gt;[Status] &amp;rarr; [Device List] &amp;rarr; &lt;em&gt;(your VM)&lt;/em&gt; &amp;rarr; &lt;em&gt;(click on lease time to delete current lease)&lt;/em&gt;&lt;br&gt;
Deleting the lease will expire the existing IP allocation&lt;/p&gt;

&lt;p&gt;Reboot the Mint VM, then verify the static IP address is in use&lt;/p&gt;


&lt;h4&gt;4.2 Set a local hostname for the server&lt;/h4&gt;

&lt;p&gt;[Advanced] &amp;rarr; [DHCP/DNS] &amp;rarr; [Dnsmasq Custom Configuration]&lt;/p&gt;
&lt;p&gt;&lt;code&gt;local-ttl=1&lt;br&gt;
address=/.&lt;var&gt;HOSTNAME&lt;/var&gt;/&lt;var&gt;STATIC-IP&lt;/var&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;[Save]&lt;/p&gt;

&lt;p&gt;This allows any computer on your LAN to access the Mint VM via the host name &lt;code&gt;&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt; or any subdomain &lt;code&gt;*.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/p&gt;


&lt;h3 id="guide_server"&gt;5. Install server software&lt;/h3&gt;

&lt;p&gt;Inside the Mint VM, open terminal and run these commands&lt;/p&gt;


&lt;h4&gt;5.1 Prepare to install AMP&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo apt update &amp;amp;&amp;amp; sudo apt -y upgrade&lt;br&gt;
sudo reboot&lt;/kbd&gt;&lt;/p&gt;


&lt;h4&gt;5.2 Install MariaDB SQL server&lt;/h4&gt;

&lt;p&gt;Find out what Ubuntu version your Mint OS is based on; we need the &lt;var&gt;DISTRIB_CODENAME&lt;/var&gt;:&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;cat /etc/upstream-release/lsb-release&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;In my case, Linux Mint 21.2 is based on Ubuntu 22.04 &amp;ldquo;&lt;code&gt;jammy&lt;/code&gt;&amp;rdquo;. The MariaDB respository &lt;a href="https://mariadb.com/kb/en/mariadb-package-repository-setup-and-usage/"&gt;only supports particular versions&lt;/a&gt;, so keep an eye on any error message generated from the CURL command below.&lt;/p&gt;

&lt;p&gt;We want to install a long-term support (LTS) release of MariaDB. Check the &lt;a href="https://mariadb.com/kb/en/new-and-old-releases/"&gt;releases list&lt;/a&gt;, note the newest &amp;ldquo;&lt;em&gt;long-term MariaDB stable release&lt;/em&gt;&amp;rdquo; version number, &lt;var&gt;MARIADB-VERSION&lt;/var&gt;. As of this writing, the latest LTS version is &lt;code&gt;10.11&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;curl -LsS https://r.mariadb.com/downloads/mariadb_repo_setup | sudo bash -s -- --os-type=ubuntu --os-version=&lt;var&gt;DISTRIB_CODENAME&lt;/var&gt; --mariadb-server-version=&amp;quot;mariadb-&lt;var&gt;MARIADB-VERSION&lt;/var&gt;&amp;quot;&lt;br&gt;
sudo apt-get install mariadb-server mariadb-client -y&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;mysql -V&lt;br&gt;
sudo service mariadb status | grep "Active:"&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo mysql_secure_installation&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;Enter current password for root (enter for none): &lt;em&gt;[press enter]&lt;/em&gt;&lt;br&gt;
Switch to unix_socket authentication [Y/n]: &lt;kbd&gt;N&lt;/kbd&gt;&lt;br&gt;
Change the root password? [Y/n]: &lt;kbd&gt;Y&lt;/kbd&gt; &amp;rarr; &lt;var&gt;SQLPASS&lt;/var&gt; &amp;rarr; &lt;var&gt;SQLPASS&lt;/var&gt;&lt;br&gt;
Remove anonymous users? [Y/n]: &lt;kbd&gt;Y&lt;/kbd&gt;&lt;br&gt;
Disallow root login remotely? [Y/n]: &lt;kbd&gt;Y&lt;/kbd&gt;&lt;br&gt;
Remove test database and access to it? [Y/n]: &lt;kbd&gt;Y&lt;/kbd&gt;&lt;br&gt;
Reload privilege tables now? [Y/n]: &lt;kbd&gt;Y&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Edit the line containing &lt;code&gt;bind-address&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bind-address = 0.0.0.0&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo service mariadb restart&lt;/code&gt;&lt;/p&gt;


&lt;h4&gt;5.3 Install Apache web server&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo apt-get install apache2 apache2-utils -y&lt;br&gt;
apache2 -v&lt;br&gt;
sudo service apache2 status | grep "Active:"&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Test Apache: Open Firefox, view &lt;a href="http://localhost"&gt;&lt;code&gt;http://localhost&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h4&gt;5.4 Install PHP and extensions&lt;/h4&gt;

&lt;p&gt;Add the &lt;code&gt;ondrej&lt;/code&gt; repository for access to newer PHP versions, check available versions, then install PHP.&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo add-apt-repository ppa:ondrej/php&lt;br&gt;
sudo apt-get update&lt;br&gt;
apt-cache policy php&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo apt-get install php php-common php-cli libapache2-mod-php -y&lt;br&gt;
php -v&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Install PHP extensions:&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo apt-get install php-mysql php-mbstring php-curl php-xml php-intl php-gd php-imagick -y&lt;br&gt;
sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Test PHP:&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano /var/www/html/phpinfo.php&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;?php phpinfo(); ?&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;Open Firefox, view &lt;a href="http://localhost/phpinfo.php"&gt;&lt;code&gt;http://localhost/phpinfo.php&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h4&gt;5.5 Install phpMyAdmin and configure SQL user&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo apt-get install phpmyadmin -y&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;when prompted for a webserver, &lt;strong&gt;press &lt;em&gt;space&lt;/em&gt;&lt;/strong&gt; to tick the box selecting &lt;em&gt;apache2&lt;/em&gt;&lt;br&gt;
when prompted to configure database, select &lt;em&gt;yes&lt;/em&gt;&lt;br&gt;
when prompted for MySQL password, enter &lt;kbd&gt;&lt;var&gt;SQLPASS&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo mysql -uroot -p&lt;var&gt;SQLPASS&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DROP USER 'phpmyadmin'@'localhost';&lt;br&gt;
CREATE USER '&lt;var&gt;SQLUSER&lt;/var&gt;'@'%' IDENTIFIED BY '';&lt;br&gt;
GRANT ALL PRIVILEGES ON *.* TO '&lt;var&gt;SQLUSER&lt;/var&gt;'@'%' WITH GRANT OPTION;&lt;br&gt;
FLUSH PRIVILEGES;&lt;br&gt;
SELECT User,Host FROM mysql.user;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+Z (to exit)&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;mysqladmin --user=&lt;var&gt;SQLUSER&lt;/var&gt; password "&lt;var&gt;SQLPASS&lt;/var&gt;"&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/dbconfig-common/phpmyadmin.conf&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Edit the line containing &lt;code&gt;dbc_dbuser&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dbc_dbuser='&lt;var&gt;SQLUSER&lt;/var&gt;'&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/phpmyadmin/config-db.php&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Edit the line containing &lt;code&gt;$dbuser&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$dbuser='&lt;var&gt;SQLUSER&lt;/var&gt;';&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo service mysql restart&lt;br&gt;
sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Open Firefox, view &lt;a href="http://localhost/phpmyadmin"&gt;&lt;code&gt;http://localhost/phpmyadmin&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
Username:  &lt;kbd&gt;&lt;var&gt;SQLUSER&lt;/var&gt;&lt;/kbd&gt;&lt;br&gt;
Password:  &lt;kbd&gt;&lt;var&gt;SQLPASS&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;


&lt;h4&gt;5.6 Enable &amp;ldquo;nologin&amp;rdquo; user accounts&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano /etc/shells&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Append this line to the end of the file:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/usr/sbin/nologin&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;


&lt;h4&gt;5.7 Set permissions on web folder&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo adduser $USER www-data&lt;br&gt;
sudo chown $USER:www-data -R /var/www&lt;br&gt;
sudo chmod u=rwX,g=srwX,o=rX -R /var/www&lt;br&gt;
sudo chmod 0664 /var/www/html/*&lt;/kbd&gt;&lt;/p&gt;


&lt;h4&gt;5.8 Allow .htaccess configuration in web folder&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/apache2/apache2.conf&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Inside configuration block &lt;code&gt;&amp;lt;Directory /var/www/&amp;gt;&lt;/code&gt;, alter the &lt;code&gt;AllowOverride&lt;/code&gt; line:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AllowOverride All&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;


&lt;h4&gt;5.9 Enable mod_rewrite Apache module&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo a2enmod rewrite&lt;br&gt;
sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;


&lt;h4&gt;5.10 Increase PHP upload and POST size limits&lt;/h4&gt;

&lt;p&gt;PHP defaults to 8 megabytes max POST size and 2 megabytes max file upload size. Changing these limits so they match can make debugging easier. Select a limit &lt;var&gt;MAXUPLOAD&lt;/var&gt; megabytes that is suitable for your projects.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; the path below may require updating to match the installed PHP version.&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/php/&lt;var&gt;8.2&lt;/var&gt;/apache2/php.ini&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Alter the line containing &lt;code&gt;post_max_size&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;post_max_size = &lt;var&gt;MAXUPLOAD&lt;/var&gt;M&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Alter the line containing &lt;code&gt;upload_max_filesize&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;upload_max_filesize = &lt;var&gt;MAXUPLOAD&lt;/var&gt;M&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;


&lt;h4&gt;5.11 Enable PHP error reporting&lt;/h4&gt;

&lt;p&gt;If you're using this server for development purposes, you will want to allow PHP to display errors. The &lt;a href="https://www.php.net/manual/en/errorfunc.configuration.php#ini.display-errors"&gt;&lt;code&gt;display_errors&lt;/code&gt; setting&lt;/a&gt; should be &lt;code&gt;On&lt;/code&gt; for development, and &lt;code&gt;Off&lt;/code&gt; for production/live servers.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; the path below may require updating to match the installed PHP version.&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/php/&lt;var&gt;8.2&lt;/var&gt;/apache2/php.ini&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Alter the line containing &lt;code&gt;display_errors&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;display_errors = On&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Alternately, you can control this setting on a script-by-script basis by calling the PHP &lt;code&gt;ini_set()&lt;/code&gt; function:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ini_set ('display_errors', 'On');&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Error reporting can be further controlled by the &lt;a href="https://www.php.net/manual/en/errorfunc.configuration.php#ini.error-reporting"&gt;&lt;code&gt;error_reporting&lt;/code&gt; setting in &lt;code&gt;php.ini&lt;/code&gt;&lt;/a&gt; or the corresponding &lt;a href="https://www.php.net/manual/en/function.error-reporting.php"&gt;&lt;code&gt;error_reporting()&lt;/code&gt; function&lt;/a&gt; in your PHP script. For production I use a value of &lt;code&gt;0&lt;/code&gt;, or &lt;code&gt;(E_ALL | E_STRICT)&lt;/code&gt; for development.&lt;/p&gt;


&lt;h4&gt;5.12 Clear command-line history and reboot&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;history -cw&lt;br&gt;
sudo reboot&lt;/kbd&gt;&lt;/p&gt;


&lt;h3 id="guide_samba"&gt;6. Install and configure Samba&lt;/h3&gt;

&lt;h4&gt;6.1 Install Samba&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo apt-get update&lt;br&gt;
sudo apt-get install samba -y&lt;/kbd&gt;&lt;/p&gt;


&lt;h4&gt;6.2 Create Samba user&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo useradd -s /usr/sbin/nologin &lt;var&gt;SMBUSER&lt;/var&gt;&lt;br&gt;
sudo passwd &lt;var&gt;SMBUSER&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;when prompted for password, enter &lt;kbd&gt;&lt;var&gt;SMBPASS&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;sudo adduser &lt;var&gt;SMBUSER&lt;/var&gt; www-data&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo smbpasswd -a &lt;var&gt;SMBUSER&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;when prompted for password, enter &lt;kbd&gt;&lt;var&gt;SMBPASS&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;sudo smbpasswd -e &lt;var&gt;SMBUSER&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;


&lt;h4&gt;6.3 Add network share&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/samba/smb.conf&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Append these configuration lines to the end of the file:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;[www]&lt;br&gt;
&amp;nbsp;&amp;nbsp;comment = Web Data&lt;br&gt;
&amp;nbsp;&amp;nbsp;path = /var/www&lt;br&gt;
&amp;nbsp;&amp;nbsp;guest ok = no&lt;br&gt;
&amp;nbsp;&amp;nbsp;read only = no&lt;br&gt;
&amp;nbsp;&amp;nbsp;writeable = yes&lt;br&gt;
&amp;nbsp;&amp;nbsp;browseable = yes&lt;br&gt;
&amp;nbsp;&amp;nbsp;valid users = +www-data&lt;br&gt;
&amp;nbsp;&amp;nbsp;create mask = 0664&lt;br&gt;
&amp;nbsp;&amp;nbsp;directory mask = 0775&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo service smbd restart&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Validate your Samba configuration:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;testparm -s /etc/samba/smb.conf&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;normalized configuration file is printed; look for error messages&lt;/p&gt;


&lt;h4&gt;6.4 Test network share&lt;/h4&gt;

&lt;p&gt;On Windows you may need to flush all open network connections to use new credentials; run this from a command prompt:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;net use * /delete&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;From a Windows PC, connect to the Samba share:&lt;br&gt;
Start Menu &amp;rarr; Run &amp;rarr; &lt;kbd&gt;"\\mint-vm\www"&lt;/kbd&gt;&lt;br&gt;
Username:  &lt;kbd&gt;&lt;var&gt;SMBUSER&lt;/var&gt;&lt;/kbd&gt;&lt;br&gt;
Password:  &lt;kbd&gt;&lt;var&gt;SMBPASS&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;


&lt;h3 id="guide_vhosts"&gt;7. Configure Apache virtual hosts&lt;/h3&gt;

&lt;p&gt;* Configuration tested on Apache v2.4&lt;/p&gt;
&lt;p&gt;* For detailed reference, refer to the Apache documentation:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;&lt;a href="https://httpd.apache.org/docs/current/vhosts/name-based.html"&gt;Name-based Virtual Host Support&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="https://httpd.apache.org/docs/current/mod/core.html"&gt;Core Apache Configuration Directives&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;* Refer to section 4 in this guide to define a local hostname&lt;/p&gt;

&lt;p&gt;Enable multi-project hosting using Apache virtual hosts; this example configures 4 hosts: &lt;em&gt;delta&lt;/em&gt;, &lt;em&gt;gamma&lt;/em&gt;, &lt;em&gt;phpmyadmin&lt;/em&gt;, and a default host:&lt;/p&gt;
&lt;table&gt;
&lt;tbody&gt;
	&lt;tr&gt;&lt;td&gt;&lt;code&gt;delta.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/td&gt;
		&lt;td&gt;&lt;code&gt;/var/www/delta&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
	&lt;tr&gt;&lt;td&gt;&lt;code&gt;gamma.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/td&gt;
		&lt;td&gt;&lt;code&gt;/var/www/gamma&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
	&lt;tr&gt;&lt;td&gt;&lt;code&gt;phpmyadmin.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/td&gt;
		&lt;td&gt;&lt;code&gt;/usr/share/phpmyadmin&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
	&lt;tr&gt;&lt;td&gt;&lt;code&gt;&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/td&gt;
		&lt;td&gt;&lt;code&gt;/var/www/www&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Note that in this configuration the default host is served for &lt;strong&gt;any&lt;/strong&gt; request by IP address and &lt;strong&gt;any&lt;/strong&gt; request to an unknown hostname. This behaviour has security implications: if your server becomes publicly-accessible, your default host might be served.  Therefore we remove all admin-related content (such as phpMyAdmin) from this area.&lt;/p&gt;

&lt;p&gt;In this example we keep no content on the default host; all real content is served from specific subdomains.&lt;/p&gt;


&lt;h4&gt;7.1 Remove default host access to phpMyAdmin&lt;/h4&gt;

&lt;p&gt;Since phpMyAdmin will be accessed under its own subdomain, we should remove it from the default host; this allows us to explicitly control who can access it:&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/phpmyadmin/apache.conf&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Comment out (prepend a &lt;code&gt;#&lt;/code&gt; character) this &lt;code&gt;Alias&lt;/code&gt; line:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;#Alias /phpmyadmin /usr/share/phpmyadmin&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;phpMyAdmin can no longer be accessed from &lt;a href="http://localhost/phpmyadmin"&gt;&lt;code&gt;http://localhost/phpmyadmin&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h4&gt;7.2 Change the default host's server name and document root&lt;/h4&gt;

&lt;p&gt;By default Apache serves web files from &lt;code&gt;/var/www/html&lt;/code&gt;; we change this directory to better integrate with a multi-host setup:&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/apache2/sites-enabled/000-default.conf&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Insert the following line at the top of the file:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ServerName &lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This comment may be worth adding inside the &lt;code&gt;&amp;lt;VirtualHost&amp;gt;&lt;/code&gt; configuration block:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;# This first virtual host definition becomes the default.&lt;br&gt;
# Default is served for all requests not matching hosts below,&lt;br&gt;
# including requests by IP and requests for undefined hosts.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Add a &lt;code&gt;ServerAlias&lt;/code&gt; line inside the &lt;code&gt;&amp;lt;VirtualHost&amp;gt;&lt;/code&gt; configuration block:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ServerAlias www.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Change the &lt;code&gt;DocumentRoot&lt;/code&gt; line:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DocumentRoot "/var/www/www"&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;Delete the previous &lt;code&gt;html&lt;/code&gt; directory and create a new &lt;code&gt;www&lt;/code&gt; directory&lt;br&gt;
&lt;em&gt;&lt;strong&gt;WARNING:&lt;/strong&gt; all files in &lt;code&gt;/var/www/html&lt;/code&gt; will be deleted!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;rm -r /var/www/html&lt;br&gt;
mkdir /var/www/www&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Validate your configuration and restart the server:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;apache2ctl configtest&lt;br&gt;
sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;You may implement a website on the default host, however in this example setup we prefer to respond &lt;em&gt;&amp;ldquo;404 Not Found&amp;rdquo;&lt;/em&gt; to all requests that don't match an expected virtual host subdomain:&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;nano /var/www/www/index.php&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;?php&lt;br&gt;
&amp;nbsp;&amp;nbsp;http_response_code (404);&lt;br&gt;
&amp;nbsp;&amp;nbsp;header ('Content-Type: text/plain');&lt;br&gt;
&amp;nbsp;&amp;nbsp;echo '404 Not Found';&lt;br&gt;
&amp;nbsp;&amp;nbsp;die();&lt;br&gt;
?&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;Visiting any address that resolves to this server should now yield a 404 error, generated from &lt;code&gt;/var/www/www/index.php&lt;/code&gt;; for example:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;&lt;a href="http://localhost"&gt;&lt;code&gt;http://localhost&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://STATIC-IP"&gt;&lt;code&gt;http://&lt;var&gt;STATIC-IP&lt;/var&gt;&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://HOSTNAME"&gt;&lt;code&gt;http://&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://www.HOSTNAME"&gt;&lt;code&gt;http://www.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://any-undefined-subdomain.HOSTNAME"&gt;&lt;code&gt;http://any-undefined-subdomain.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h4&gt;7.3 Reenable phpMyAdmin under a virtual host&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/apache2/sites-enabled/000-default.conf&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Append the following configuration block to the &lt;em&gt;end&lt;/em&gt; of the file:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;VirtualHost *:80&amp;gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;DocumentRoot "/usr/share/phpmyadmin"&lt;br&gt;
&amp;nbsp;&amp;nbsp;ServerName phpmyadmin.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;ServerAlias *.phpmyadmin.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;br&gt;
&amp;lt;/VirtualHost&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;Validate your configuration and restart the server:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;apache2ctl configtest&lt;br&gt;
sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Visiting &lt;a href="http://phpmyadmin.HOSTNAME"&gt;&lt;code&gt;http://phpmyadmin.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/a&gt; now accesses phpMyAdmin&lt;/p&gt;


&lt;h4&gt;7.4 Add additional virtual hosts, as desired&lt;/h4&gt;

&lt;p&gt;This example sets up two projects, &lt;em&gt;delta&lt;/em&gt; and &lt;em&gt;gamma&lt;/em&gt;, each with their own hostname and server directory.  In your configuration, you would instead use these placeholders as examples to configure your own projects.&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;mkdir /var/www/delta&lt;br&gt;
mkdir /var/www/gamma&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/apache2/sites-enabled/000-default.conf&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Append the following configuration blocks to the end of the file:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;VirtualHost *:80&amp;gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;DocumentRoot "/var/www/delta"&lt;br&gt;
&amp;nbsp;&amp;nbsp;ServerName delta.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;ServerAlias *.delta.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;br&gt;
&amp;lt;/VirtualHost&amp;gt;&lt;br&gt;&lt;br&gt;

&amp;lt;VirtualHost *:80&amp;gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;DocumentRoot "/var/www/gamma"&lt;br&gt;
&amp;nbsp;&amp;nbsp;ServerName gamma.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;ServerAlias *.gamma.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;br&gt;
&amp;lt;/VirtualHost&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;Validate your configuration and restart the server:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;apache2ctl configtest&lt;br&gt;
sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Access these sites via &lt;a href="http://delta.HOSTNAME"&gt;&lt;code&gt;http://delta.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/a&gt; and &lt;a href="http://gamma.HOSTNAME"&gt;&lt;code&gt;http://gamma.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3 id="guide_database"&gt;8. Create a database and limited SQL user&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Tip:&lt;/em&gt; Match the database name, username, and password of the database from your online hosting provider when configuring this local database.  Migrating projects between the two then involves only changing the host name of the database server.&lt;/p&gt;

&lt;p&gt;Execute this SQL code from the phpMyAdmin [SQL] tab to create a database:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CREATE DATABASE `&lt;var&gt;DBNAME&lt;/var&gt;` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Create a limited user who can only control this database:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CREATE USER '&lt;var&gt;DBUSER&lt;/var&gt;'@'%' IDENTIFIED WITH mysql_native_password AS '';&lt;br&gt;
&lt;br&gt;
GRANT USAGE ON *.* TO '&lt;var&gt;DBUSER&lt;/var&gt;'@'%' REQUIRE NONE;&lt;br&gt;
&lt;br&gt;
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER,&lt;br&gt;
CREATE TEMPORARY TABLES, CREATE VIEW, EVENT, TRIGGER, SHOW VIEW,&lt;br&gt;
CREATE ROUTINE, ALTER ROUTINE, EXECUTE ON `&lt;var&gt;DBNAME&lt;/var&gt;`.* TO '&lt;var&gt;DBUSER&lt;/var&gt;'@'%';&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;phpMyAdmin &amp;rarr; [Home] &amp;rarr; [User accounts] &amp;rarr; &lt;em&gt;(&lt;var&gt;DBUSER&lt;/var&gt;)&lt;/em&gt; &amp;rarr; [Edit privileges] &amp;rarr; [Change password]&lt;br&gt;
Assign a password &lt;var&gt;DBPASS&lt;/var&gt; to the database user&lt;/p&gt;

&lt;p&gt;From PHP, this database can now be accessed through &lt;code&gt;mysqli&lt;/code&gt; or equivalent:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$db = new mysqli ('localhost', '&lt;var&gt;DBUSER&lt;/var&gt;', '&lt;var&gt;DBPASS&lt;/var&gt;', '&lt;var&gt;DBNAME&lt;/var&gt;');&lt;/code&gt;&lt;/p&gt;


&lt;h3 id="guide_passwords"&gt;Appendix A: Changing passwords&lt;/h3&gt;

&lt;p&gt;If you need to change an account password after initial setup, use the following procedures.&lt;/p&gt;

&lt;p&gt;If you specify a password directly on the command-line, you can later clear the command-line history, for security:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;history -cw&lt;/kbd&gt;&lt;/p&gt;



&lt;h4&gt;A.1 Linux User&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo passwd &lt;var&gt;VMUSER&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo&lt;/code&gt; may ask for a password; use &lt;kbd&gt;&lt;var&gt;OLD-VMPASS&lt;/var&gt;&lt;/kbd&gt;&lt;br&gt;
when prompted for &lt;em&gt;new UNIX password&lt;/em&gt;, enter &lt;kbd&gt;&lt;var&gt;NEW-VMPASS&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo reboot&lt;/kbd&gt;&lt;/p&gt;


&lt;h4&gt;A.2 SQL User&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;mysqladmin --user=&lt;var&gt;SQLUSER&lt;/var&gt; --password=&lt;var&gt;OLD-SQLPASS&lt;/var&gt; password "&lt;var&gt;NEW-SQLPASS&lt;/var&gt;"&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/dbconfig-common/phpmyadmin.conf&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Edit the line containing &lt;code&gt;dbc_dbpass&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dbc_dbpass='&lt;var&gt;NEW-SQLPASS&lt;/var&gt;'&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/phpmyadmin/config-db.php&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Edit the line containing &lt;code&gt;$dbpass&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$dbpass='&lt;var&gt;NEW-SQLPASS&lt;/var&gt;';&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo service mysql restart&lt;br&gt;
sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;


&lt;h4&gt;A.3 Samba User&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo passwd &lt;var&gt;SMBUSER&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo&lt;/code&gt; may ask for a password; use &lt;kbd&gt;&lt;var&gt;VMPASS&lt;/var&gt;&lt;/kbd&gt;&lt;br&gt;
when prompted for &lt;em&gt;new UNIX password&lt;/em&gt;, enter &lt;kbd&gt;&lt;var&gt;NEW-SMBPASS&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;sudo smbpasswd &lt;var&gt;SMBUSER&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;when prompted for password, enter &lt;kbd&gt;&lt;var&gt;NEW-SMBPASS&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;sudo service smbd restart&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;On Windows you may need to flush all open network connections to use new credentials; run this from a command prompt:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;net use * /delete&lt;/kbd&gt;&lt;/p&gt;


&lt;h3 id="guide_networkshare"&gt;Appendix B: Mount a network share for read/&lt;wbr&gt;write access&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://screaming.computer/blog/2020-03/mount-a-network-share-in-linux"&gt;Mount a network share in Linux&lt;/a&gt;:&lt;/strong&gt; This allows your VM to read from and write to shared folders on your network.&lt;/p&gt;


&lt;h3 id="guide_projects"&gt;Appendix C: Project-specific features&lt;/h3&gt;


&lt;h4 id="guide_escpos"&gt;Install the escpos-php receipt printer library&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://screaming.computer/blog/2020-02/install-and-test-the-escpos-php-library"&gt;Install and test the &lt;code&gt;escpos-php&lt;/code&gt; library&lt;/a&gt;:&lt;/strong&gt; used for communicating with thermal receipt printers via the &lt;code&gt;ESC/POS&lt;/code&gt; protocol.&lt;/p&gt;

&lt;p&gt;Additionally, if your printer does not natively support printing &lt;a href="https://en.wikipedia.org/wiki/QR_code"&gt;QR codes&lt;/a&gt;, you can &lt;strong&gt;&lt;a href="https://screaming.computer/blog/2020-02/add-image-based-qr-code-support-to-escpos-php"&gt;add image-based QR code support&lt;/a&gt;&lt;/strong&gt; to the &lt;code&gt;escpos-php&lt;/code&gt; library by integrating the &lt;a href="http://phpqrcode.sourceforge.net/"&gt;&lt;code&gt;PhpQrCode&lt;/code&gt;&lt;/a&gt; library.&lt;/p&gt;


&lt;h4 id="guide_simplehtmldom"&gt;Install the PHP Simple HTML DOM Parser&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://screaming.computer/blog/2020-02/install-the-php-simple-html-dom-parser-for-easy-web-scraping"&gt;Install the &lt;code&gt;SimpleHtmlDom&lt;/code&gt; library&lt;/a&gt;:&lt;/strong&gt; This PHP library allows for exceptionally-easy web scraping and parsing of arbitrary HTML pages.&lt;/p&gt;


&lt;p&gt;&lt;em&gt;[end of guide]&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;&lt;!-- class=vmguide --&gt;
			</description>
			<guid isPermaLink="true">https://screaming.computer/blog/2023-09/updated-set-up-a-lamp-server-vm-in-unraid</guid>
			<pubDate>Sun, 24 Sep 2023 23:39:52 GMT</pubDate>
			<source url="https://screaming.computer/blog/rss/">screaming.computer</source>
		</item>

		<item>
			<title>IMagick image dithering for thermal receipt printers</title>
			<link>https://screaming.computer/blog/2023-05/imagick-image-dithering-for-thermal-receipt-printers</link>
			<description>
&lt;p&gt;Let's build a convenience function to load arbitrary image files and convert them to be suitable for thermal printing, using PHP libraries &lt;a href="https://screaming.computer/blog/2020-01/set-up-a-lamp-server-vm-in-unraid#installImageMagick"&gt;ImageMagick&lt;/a&gt; and &lt;code&gt;&lt;a href="https://screaming.computer/blog/2020-02/install-and-test-the-escpos-php-library"&gt;escpos-php&lt;/a&gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://screaming.computer/blog/files/20230515-developed-1540.jpg"&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20230515-developed-1540.jpg" alt="Many strips of receipt paper showing various error messages and dithered images of Spock"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Until now I've been manually preparing images (such as the &lt;a href="https://screaming.computer/blog/2020-04/it-is-decidedly-so"&gt;Magic 8-Ball&lt;/a&gt;) in Photoshop, resizing to fit the receipt printer resolution and then applying brightness adjustments and 1-bit dithering. This isn't particularly difficult, however it is an irreversible process which locks in a specific resolution and does take some fiddling. Much better to write some code to apply the necessary transformations on-demand.&lt;/p&gt;

&lt;div&gt;
&lt;p&gt;&lt;a href="https://screaming.computer/blog/files/20230514-spock-original.png"&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20230514-spock-original.png" alt="Spock (high resolution, full colour image)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://screaming.computer/blog/files/20230514-spock-dithered.png"&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20230514-spock-dithered.png" alt="Spock (resized, dithered image)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's what we want to do to convert the original image into something printable:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Load the image from disk&lt;/li&gt;
&lt;li&gt;Remove transparency and flatten layers onto a white background&lt;/li&gt;
&lt;li&gt;Convert to greyscale&lt;/li&gt;
&lt;li&gt;Optionally rotate the image (convenience feature)&lt;/li&gt;
&lt;li&gt;If too wide, resize to fit receipt paper resolution&lt;/li&gt;
&lt;li&gt;If too narrow, add padding depending on preferred left/center/right alignment&lt;/li&gt;
&lt;li&gt;Brighten the image, to compensate for perceived darkness of printed images&lt;/li&gt;
&lt;li&gt;Dither greyscale image into a 1-bit bitmap&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;

&lt;p&gt;&lt;em&gt;(Click the images to see at full size; also ensure your browser isn't downscaling &amp;mdash; dithered bitmaps have weird aliasing artifacts when scaled.)&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;PHP Code: LoadPrinterOptimizedImage&lt;/h3&gt;

&lt;p&gt;The function implementation is relatively straightforward, but note that it doesn't do any error-checking. (Bad programmer, no cookie!) We assume &lt;code&gt;&lt;a href="https://screaming.computer/blog/2020-01/set-up-a-lamp-server-vm-in-unraid#installImageMagick"&gt;php-imagick&lt;/a&gt;&lt;/code&gt; and &lt;code&gt;&lt;a href="https://screaming.computer/blog/2020-02/install-and-test-the-escpos-php-library"&gt;escpos-php&lt;/a&gt;&lt;/code&gt; are installed and available.&lt;/p&gt;

&lt;!-- HTML generated using hilite.me // Replace initial div and pre with code.codeArea --&gt;
&lt;code&gt;&lt;span&gt;define&lt;/span&gt; (&lt;span&gt;&amp;#39;PAPER_WIDTH_PIXELS&amp;#39;&lt;/span&gt;,  &lt;span&gt;384&lt;/span&gt;);
&lt;span&gt;define&lt;/span&gt; (&lt;span&gt;&amp;#39;PRINTER_IMAGE_GAMMA&amp;#39;&lt;/span&gt;, &lt;span&gt;1.8&lt;/span&gt;);

&lt;span&gt;function&lt;/span&gt; &lt;span&gt;LoadPrinterOptimizedImage&lt;/span&gt; (string &lt;span&gt;$filename&lt;/span&gt;,
                int &lt;span&gt;$rotate&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;0&lt;/span&gt;,
                int &lt;span&gt;$align&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; Imagick&lt;span&gt;::&lt;/span&gt;&lt;span&gt;ALIGN_CENTER&lt;/span&gt;,
                float &lt;span&gt;$gamma&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; PRINTER_IMAGE_GAMMA
                ) &lt;span&gt;:&lt;/span&gt; ImagickEscposImage
{
    &lt;span&gt;$imagick&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;new&lt;/span&gt; Imagick();
    &lt;span&gt;$imagick&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;readImage&lt;/span&gt; (&lt;span&gt;$filename&lt;/span&gt;);

    &lt;span&gt;// Remove transparency, flatten layers onto a white background&lt;/span&gt;
    &lt;span&gt;$imagick&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;setImageBackgroundColor&lt;/span&gt; (&lt;span&gt;&amp;#39;white&amp;#39;&lt;/span&gt;);
    &lt;span&gt;$imagick&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;setImageAlphaChannel&lt;/span&gt; (Imagick&lt;span&gt;::&lt;/span&gt;&lt;span&gt;ALPHACHANNEL_REMOVE&lt;/span&gt;);
    &lt;span&gt;$imagick&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;mergeImageLayers&lt;/span&gt; (Imagick&lt;span&gt;::&lt;/span&gt;&lt;span&gt;LAYERMETHOD_FLATTEN&lt;/span&gt;);

    &lt;span&gt;// Convert to greyscale&lt;/span&gt;
    &lt;span&gt;$imagick&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;transformImageColorspace&lt;/span&gt; (Imagick&lt;span&gt;::&lt;/span&gt;&lt;span&gt;COLORSPACE_GRAY&lt;/span&gt;);

    &lt;span&gt;// Optionally rotate the image, constrain to 90 degree increments&lt;/span&gt;
    &lt;span&gt;$rotate&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;90&lt;/span&gt; &lt;span&gt;*&lt;/span&gt; ((int) round (&lt;span&gt;$rotate&lt;/span&gt; &lt;span&gt;/&lt;/span&gt; &lt;span&gt;90.0&lt;/span&gt;));
    &lt;span&gt;if&lt;/span&gt; (&lt;span&gt;$rotate&lt;/span&gt;)
    {
        &lt;span&gt;$imagick&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;rotateImage&lt;/span&gt; (&lt;span&gt;&amp;#39;white&amp;#39;&lt;/span&gt;, &lt;span&gt;$rotate&lt;/span&gt;);
        &lt;span&gt;$imagick&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;setImagePage&lt;/span&gt; (&lt;span&gt;$imagick&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;getImageWidth&lt;/span&gt;(), &lt;span&gt;$imagick&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;getImageHeight&lt;/span&gt;(), &lt;span&gt;0&lt;/span&gt;, &lt;span&gt;0&lt;/span&gt;);
    }

    &lt;span&gt;// Store image dimensions&lt;/span&gt;
    &lt;span&gt;$imgWidth&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;$fileWidth&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;$imagick&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;getImageWidth&lt;/span&gt;();
    &lt;span&gt;$imgHeight&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;$fileHeight&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;$imagick&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;getImageHeight&lt;/span&gt;();

    &lt;span&gt;// Shrink image if too wide&lt;/span&gt;
    &lt;span&gt;if&lt;/span&gt; (&lt;span&gt;$fileWidth&lt;/span&gt; &lt;span&gt;&amp;gt;&lt;/span&gt; PAPER_WIDTH_PIXELS)
    {
        &lt;span&gt;$imgHeight&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; (int) round ((&lt;span&gt;1.0&lt;/span&gt; &lt;span&gt;*&lt;/span&gt; &lt;span&gt;$fileHeight&lt;/span&gt; &lt;span&gt;*&lt;/span&gt; PAPER_WIDTH_PIXELS) &lt;span&gt;/&lt;/span&gt; &lt;span&gt;$fileWidth&lt;/span&gt;);
        &lt;span&gt;$imgWidth&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; PAPER_WIDTH_PIXELS;
        &lt;span&gt;$imagick&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;resizeImage&lt;/span&gt; (&lt;span&gt;$imgWidth&lt;/span&gt;, &lt;span&gt;$imgHeight&lt;/span&gt;, Imagick&lt;span&gt;::&lt;/span&gt;&lt;span&gt;FILTER_LANCZOS&lt;/span&gt;, &lt;span&gt;0.5&lt;/span&gt;);
        &lt;span&gt;$imagick&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;setImagePage&lt;/span&gt; (&lt;span&gt;$imgWidth&lt;/span&gt;, &lt;span&gt;$imgHeight&lt;/span&gt;, &lt;span&gt;0&lt;/span&gt;, &lt;span&gt;0&lt;/span&gt;);
    }

    &lt;span&gt;// Align image if too narrow&lt;/span&gt;
    &lt;span&gt;else&lt;/span&gt; &lt;span&gt;if&lt;/span&gt; (&lt;span&gt;$fileWidth&lt;/span&gt; &lt;span&gt;&amp;lt;&lt;/span&gt; PAPER_WIDTH_PIXELS)
    {
        &lt;span&gt;switch&lt;/span&gt; (&lt;span&gt;$align&lt;/span&gt;)
        {
            &lt;span&gt;case&lt;/span&gt; Imagick&lt;span&gt;::&lt;/span&gt;&lt;span&gt;ALIGN_CENTER&lt;/span&gt; &lt;span&gt;:&lt;/span&gt;
                &lt;span&gt;$borderWidth&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; (int) ((PAPER_WIDTH_PIXELS &lt;span&gt;-&lt;/span&gt; &lt;span&gt;$fileWidth&lt;/span&gt;) &lt;span&gt;/&lt;/span&gt; &lt;span&gt;2&lt;/span&gt;);
                &lt;span&gt;break&lt;/span&gt;;

            &lt;span&gt;case&lt;/span&gt; Imagick&lt;span&gt;::&lt;/span&gt;&lt;span&gt;ALIGN_RIGHT&lt;/span&gt; &lt;span&gt;:&lt;/span&gt;
                &lt;span&gt;$borderWidth&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; PAPER_WIDTH_PIXELS &lt;span&gt;-&lt;/span&gt; &lt;span&gt;$fileWidth&lt;/span&gt;;
                &lt;span&gt;break&lt;/span&gt;;

            &lt;span&gt;// Assume printer will left-align image by default&lt;/span&gt;
            &lt;span&gt;default&lt;/span&gt; &lt;span&gt;:&lt;/span&gt;
                &lt;span&gt;$borderWidth&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;0&lt;/span&gt;;
        }

        &lt;span&gt;// Add a white border as an easy way to generate padding&lt;/span&gt;
        &lt;span&gt;// Overflowing border pixels are cropped off, below&lt;/span&gt;
        &lt;span&gt;if&lt;/span&gt; (&lt;span&gt;$borderWidth&lt;/span&gt; &lt;span&gt;&amp;gt;&lt;/span&gt; &lt;span&gt;0&lt;/span&gt;)
        {
            &lt;span&gt;$imgWidth&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;$fileWidth&lt;/span&gt; &lt;span&gt;+&lt;/span&gt; &lt;span&gt;$borderWidth&lt;/span&gt; &lt;span&gt;+&lt;/span&gt; &lt;span&gt;$borderWidth&lt;/span&gt;;
            &lt;span&gt;$imagick&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;borderImage&lt;/span&gt; (&lt;span&gt;&amp;#39;white&amp;#39;&lt;/span&gt;, &lt;span&gt;$borderWidth&lt;/span&gt;, &lt;span&gt;0&lt;/span&gt;);
            &lt;span&gt;$imagick&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;setImagePage&lt;/span&gt; (&lt;span&gt;$imgWidth&lt;/span&gt;, &lt;span&gt;$imgHeight&lt;/span&gt;, &lt;span&gt;0&lt;/span&gt;, &lt;span&gt;0&lt;/span&gt;);
        }
    }

    &lt;span&gt;// Crop image to guarantee it doesn&amp;#39;t exceed printable width&lt;/span&gt;
    &lt;span&gt;$imgWidth&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; min (&lt;span&gt;$imgWidth&lt;/span&gt;, PAPER_WIDTH_PIXELS);
    &lt;span&gt;$imagick&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;cropImage&lt;/span&gt; (&lt;span&gt;$imgWidth&lt;/span&gt;, &lt;span&gt;$imgHeight&lt;/span&gt;, &lt;span&gt;0&lt;/span&gt;, &lt;span&gt;0&lt;/span&gt;);
    &lt;span&gt;$imagick&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;setImagePage&lt;/span&gt; (&lt;span&gt;$imgWidth&lt;/span&gt;, &lt;span&gt;$imgHeight&lt;/span&gt;, &lt;span&gt;0&lt;/span&gt;, &lt;span&gt;0&lt;/span&gt;);

    &lt;span&gt;// Apply gamma to brighten image while keeping black and white points&lt;/span&gt;
    &lt;span&gt;$imagick&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;gammaImage&lt;/span&gt; (&lt;span&gt;$gamma&lt;/span&gt;);

    &lt;span&gt;// Dither! Use palette from built-in bitmap of alternating black/white pixels&lt;/span&gt;
    &lt;span&gt;$palette&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;new&lt;/span&gt; Imagick();
    &lt;span&gt;$palette&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;newPseudoImage&lt;/span&gt; (&lt;span&gt;32&lt;/span&gt;, &lt;span&gt;32&lt;/span&gt;, &lt;span&gt;&amp;#39;pattern:GRAY50&amp;#39;&lt;/span&gt;);
    &lt;span&gt;$imagick&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;remapImage&lt;/span&gt; (&lt;span&gt;$palette&lt;/span&gt;, Imagick&lt;span&gt;::&lt;/span&gt;&lt;span&gt;DITHERMETHOD_FLOYDSTEINBERG&lt;/span&gt;);

    &lt;span&gt;// Dither! (doesn&amp;#39;t look as nice as floyd-steinberg)&lt;/span&gt;
    &lt;span&gt;//$imagick-&amp;gt;quantizeImage (2, Imagick::COLORSPACE_GRAY, 0, true, false);&lt;/span&gt;

    &lt;span&gt;// Threshold image at 50% (higher values means more goes to black)&lt;/span&gt;
    &lt;span&gt;$imagick&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;thresholdImage&lt;/span&gt; (&lt;span&gt;0.5&lt;/span&gt; &lt;span&gt;*&lt;/span&gt; &lt;span&gt;$imagick&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;getQuantum&lt;/span&gt;());

    &lt;span&gt;// Convert Imagick object to ImagickEscposImage&lt;/span&gt;
    &lt;span&gt;$img&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;new&lt;/span&gt; ImagickEscposImage (&lt;span&gt;null&lt;/span&gt;, &lt;span&gt;false&lt;/span&gt;);
    &lt;span&gt;$img&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;readImageFromImagick&lt;/span&gt; (&lt;span&gt;$imagick&lt;/span&gt;);
    &lt;span&gt;return&lt;/span&gt; (&lt;span&gt;$img&lt;/span&gt;);
}
&lt;/code&gt;

&lt;p&gt;The resulting &lt;code&gt;ImagickEscposImage&lt;/code&gt; object can be passed to the &lt;code&gt;Printer::bitImage()&lt;/code&gt; function for printing.&lt;/p&gt;

&lt;h3&gt;Dithering Methods&lt;/h3&gt;

&lt;p&gt;There are a few ways to downsample a greyscale image into a dithered 1-bit image in ImageMagick. Using &lt;code&gt;&lt;a href="https://www.php.net/manual/en/imagick.remapimage.php"&gt;remapImage&lt;/a&gt;&lt;/code&gt; allows you to specify &lt;a href="https://en.wikipedia.org/wiki/Floyd%E2%80%93Steinberg_dithering"&gt;Floyd-Steinberg dithering&lt;/a&gt;, which has more pleasing results than some other methods.&lt;/p&gt;

&lt;p&gt;An alternative method is to call &lt;code&gt;&lt;a href="https://www.php.net/manual/en/imagick.quantizeimage.php"&gt;quantizeImage&lt;/a&gt;&lt;/code&gt;, which produces adequate results in less code. It's unclear what dithering method is implemented here, but I prefer the results of &lt;code&gt;remapImage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;ImageMagick also has various ways to perform an &lt;a href="https://en.wikipedia.org/wiki/Ordered_dithering"&gt;ordered dither&lt;/a&gt;, but the results are inferior for this use case.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://screaming.computer/blog/files/20230515-developed-1553.jpg"&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20230515-developed-1553.jpg" alt="Strips of receipt paper showing images of Spock, rendered using various dithering techniques"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Redundant Code&lt;/h3&gt;

&lt;p&gt;There is some unnecessary code in the function above. It's there since I can't be bothered to learn the detailed internal workings of PHP IMagick, and including it makes the code theoretically more robust, without hurting anything.&lt;/p&gt;

&lt;p&gt;Calling &lt;code&gt;&lt;a href="https://www.php.net/manual/en/imagick.setimagepage.php"&gt;setImagePage&lt;/a&gt;&lt;/code&gt; after each image manipulation is a means to ensure the virtual &amp;ldquo;canvas&amp;rdquo; of the image matches the actual dimensions of the image at each step. Likely unnecessary, but I'm unclear about what situations IMagick would allow these to become mismatched, so I leave it in.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;&lt;a href="https://www.php.net/manual/en/imagick.thresholdimage.php"&gt;thresholdImage&lt;/a&gt;&lt;/code&gt; is definitely not needed after the dither, but I like to call it on every bitmap before printing to ensure we're dealing with a purely black-and-white image. Doesn't hurt anything here, but feel free to delete.&lt;/p&gt;

&lt;p&gt;Finally, I include some &lt;code&gt;(int)&lt;/code&gt; casts and explicit floating point values in a few bits of math throughout. I truly have no idea what the type promotion rules are in a weakly-typed language like PHP, so I'm coding based on my knowledge of C, and just assuming PHP is similar. Probably some unnecessary casts and values in there, but no harm done.&lt;/p&gt;

&lt;h3&gt;Spock Module&lt;/h3&gt;

&lt;p&gt;Since Spock has been such a cooperative test subject during development, let's give him his own &lt;strong&gt;screaming.computer&lt;/strong&gt; module.&lt;/p&gt;

&lt;!-- HTML generated using hilite.me // Replace initial div and pre with code.codeArea --&gt;
&lt;code&gt;&lt;span&gt;function&lt;/span&gt; &lt;span&gt;RunModule_spock&lt;/span&gt; (Printer &lt;span&gt;$printer&lt;/span&gt;) &lt;span&gt;:&lt;/span&gt; bool
{
    &lt;span&gt;$printer&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;setEmphasis&lt;/span&gt; (&lt;span&gt;true&lt;/span&gt;);
    &lt;span&gt;$printer&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;text&lt;/span&gt; (&lt;span&gt;&amp;quot;Spock&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;);
    &lt;span&gt;$printer&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;setEmphasis&lt;/span&gt; (&lt;span&gt;false&lt;/span&gt;);

    &lt;span&gt;$quotes&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;array&lt;/span&gt; (
        &lt;span&gt;&amp;#39;Live long and prosper&amp;#39;&lt;/span&gt;,
        &lt;span&gt;&amp;#39;Peace, and long life&amp;#39;&lt;/span&gt;,
        &lt;span&gt;&amp;#39;Infinite diversity in infinite combinations&amp;#39;&lt;/span&gt;,
        &lt;span&gt;&amp;#39;Logic is the beginning of wisdom, not the end&amp;#39;&lt;/span&gt;,
        &lt;span&gt;&amp;#39;Challenge your preconceptions, or they will challenge you&amp;#39;&lt;/span&gt;,
        &lt;span&gt;&amp;#39;The needs of the many outweigh the needs of the few, or the one&amp;#39;&lt;/span&gt;,
        &lt;span&gt;&amp;#39;Once you have eliminated the impossible, whatever remains, however improbable, must be the truth&amp;#39;&lt;/span&gt;,
        &lt;span&gt;&amp;#39;Insufficient facts always invite danger&amp;#39;&lt;/span&gt;,
        &lt;span&gt;&amp;#39;I have been, and always shall be, your friend&amp;#39;&lt;/span&gt;,
        &lt;span&gt;&amp;#39;Humans smile with so little provocation&amp;#39;&lt;/span&gt;,
        &lt;span&gt;&amp;quot;My congratulations, Captain \u{2014} a dazzling display of logic&amp;quot;&lt;/span&gt;,
        &lt;span&gt;&amp;#39;Computers make excellent and efficient servants, but I have no wish to serve under them&amp;#39;&lt;/span&gt;,
        &lt;span&gt;&amp;#39;I will do whatever logically needs to be done&amp;#39;&lt;/span&gt;,
        &lt;span&gt;&amp;#39;Time is fluid... like a river with currents, eddies, backwash&amp;#39;&lt;/span&gt;,
        &lt;span&gt;&amp;#39;Change is the essential process of all existence&amp;#39;&lt;/span&gt;,
        &lt;span&gt;&amp;#39;Insults are effective only where emotion is present&amp;#39;&lt;/span&gt;,
        &lt;span&gt;&amp;#39;It is only logical&amp;#39;&lt;/span&gt;
        );

    &lt;span&gt;$printer&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;setFont&lt;/span&gt; (Printer&lt;span&gt;::&lt;/span&gt;&lt;span&gt;FONT_B&lt;/span&gt;);
    &lt;span&gt;$printer&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;text&lt;/span&gt; (mb_padstr (mb_wordwrap (&lt;span&gt;&amp;quot;\u{201C}&amp;quot;&lt;/span&gt; &lt;span&gt;.&lt;/span&gt; RandomElementFromArray (&lt;span&gt;$quotes&lt;/span&gt;) &lt;span&gt;.&lt;/span&gt; &lt;span&gt;&amp;quot;\u{201D}&amp;quot;&lt;/span&gt;, PAPER_WIDTH_FONTB_CHARS &lt;span&gt;-&lt;/span&gt; &lt;span&gt;1&lt;/span&gt;), &lt;span&gt;1&lt;/span&gt;, &lt;span&gt;0&lt;/span&gt;) &lt;span&gt;.&lt;/span&gt; &lt;span&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;\n\n&lt;/span&gt;&lt;span&gt;&amp;quot;&lt;/span&gt;);
    &lt;span&gt;$printer&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;setFont&lt;/span&gt; (Printer&lt;span&gt;::&lt;/span&gt;&lt;span&gt;FONT_A&lt;/span&gt;);

    &lt;span&gt;$img&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; LoadPrinterOptimizedImage (__DIR__ &lt;span&gt;.&lt;/span&gt; &lt;span&gt;&amp;#39;/spock.png&amp;#39;&lt;/span&gt;);
    &lt;span&gt;$printer&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;bitImage&lt;/span&gt; (&lt;span&gt;$img&lt;/span&gt;);

    &lt;span&gt;return&lt;/span&gt; (&lt;span&gt;true&lt;/span&gt;);
}
&lt;/code&gt;

&lt;div&gt;
&lt;p&gt;&lt;a href="https://screaming.computer/blog/files/20230515-developed-1555.jpg"&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20230515-developed-1555.jpg" alt="Receipt paper with an image of Spock and a quote: I have been, and always shall be, your friend"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nothing fancy here. Grab a random quote, align and wrap the text, resize and dither our image, then print it all out.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;mb_padstr&lt;/code&gt; function is new code I wrote to add left and right padding to each line of a wrapped string. Many receipt printers include &lt;code&gt;ESC/POS&lt;/code&gt; functions to natively set left and right column limits for string output (which would achieve the same effect), but I find it easier to just manipulate the string in PHP and output normally.&lt;/p&gt;

&lt;p&gt;You can see the effect of &lt;code&gt;mb_padstr&lt;/code&gt; where both lines of the quote are left-indented by one space.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bonus:&lt;/strong&gt;&amp;nbsp; One of the &amp;ldquo;Spock quotes&amp;rdquo; is &lt;a href="https://memory-alpha.fandom.com/wiki/Velik"&gt;not a Spock quote at all&lt;/a&gt;. Before you check that link, do you know which one?&lt;/p&gt;

&lt;p&gt;I maintain that wise Mr. Spock was likely to have said this at some point as well, so it's acceptable to include here.&lt;/p&gt;
&lt;/div&gt;
			</description>
			<guid isPermaLink="true">https://screaming.computer/blog/2023-05/imagick-image-dithering-for-thermal-receipt-printers</guid>
			<pubDate>Mon, 15 May 2023 03:15:11 GMT</pubDate>
			<source url="https://screaming.computer/blog/rss/">screaming.computer</source>
		</item>

		<item>
			<title>Purity Ring — Fineshrine</title>
			<link>https://screaming.computer/blog/2021-02/purity-ring-fineshrine</link>
			<description>
&lt;p&gt;Best new (to me) song I've heard in months! Courtesy of &lt;a href="https://www.imdb.com/title/tt9499954/"&gt;Letterkenny&lt;/a&gt;.&lt;/p&gt;

&lt;div&gt;&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/YxJ_5ln1x40" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;&lt;/div&gt;

&lt;p&gt;Listen to it &lt;strong&gt;loud&lt;/strong&gt;.&lt;br&gt;What a vibe!&lt;/p&gt;
			</description>
			<guid isPermaLink="true">https://screaming.computer/blog/2021-02/purity-ring-fineshrine</guid>
			<pubDate>Tue, 02 Feb 2021 04:35:53 GMT</pubDate>
			<source url="https://screaming.computer/blog/rss/">screaming.computer</source>
		</item>

		<item>
			<title>Philosophical Nonsense Generator</title>
			<link>https://screaming.computer/blog/2020-10/philosophical-nonsense-generator</link>
			<description>
&lt;p&gt;&lt;strong&gt;Credit:&lt;/strong&gt; This module is a direct adaptation of the &lt;a href="http://sebpearce.com/bullshit/"&gt;New Age Bullshit Generator&lt;/a&gt; by &lt;a href="http://sebpearce.com/"&gt;Seb Pearce&lt;/a&gt;, using all his original text. His &lt;a href="https://github.com/sebpearce/bullshit"&gt;JavaScript code&lt;/a&gt; was &lt;a href="https://github.com/sebpearce/bullshit/tree/master/php"&gt;ported to PHP&lt;/a&gt; by Mark Hazlewood, which I then refactored and largely rewrote.&lt;/p&gt;

&lt;p&gt;As a module for the &lt;strong&gt;screaming.computer&lt;/strong&gt;, I'm tentatively calling it the &lt;strong&gt;Philos-o-matic: Affirmational Platitude Generator&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://screaming.computer/blog/files/20201031-developed-1463c.jpg"&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20201031-developed-1463c.jpg" alt="Many sheets of receipt paper printed with diagnostic info and test-prints of the Philos-o-matic"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Hogwash that looks compelling&lt;/h3&gt;

&lt;p&gt;Seb Pearce describes how the idea came while watching a particular pseudoscience-advocate involved in a philosophy debate:&lt;/p&gt;

&lt;blockquote cite="http://sebpearce.com/blog/bullshit"&gt;&lt;p&gt;The thing that really impressed me, though, was his command of the vocabulary of &lt;a href="https://rationalwiki.org/wiki/Woo"&gt;&lt;em&gt;woo&lt;/em&gt;&lt;/a&gt;... This word is a blanket term for pseudoscience, New Age thinking, dubious alternative medicine and other things that reek of the heady fumes of snake oil.&amp;nbsp; ...&lt;/p&gt;
&lt;p&gt;As I sat there listening to the debates, I thought to myself, &amp;ldquo;This all sounds like random sequences of buzzwords. I bet I could write code to generate it.&amp;rdquo; It seemed like not only a fun side project, but a great way to prove how easy it is to make hogwash that looks compelling. It might help show that it's the language games and emotions that lure people into this stuff.&lt;/p&gt;
&lt;footer&gt;&lt;p&gt;&lt;cite&gt;&amp;mdash; &lt;a href="http://sebpearce.com/blog/bullshit"&gt;On the New Age Bullshit Generator and parodying woo&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;p&gt;The resulting script generates delightful pseudo-inspirational, pseudo-philosophical fragments of nonsense.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://screaming.computer/blog/files/20201031-developed-1458c.jpg"&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20201031-developed-1458c.jpg" alt="Six Philos-o-matic printouts, each with a few sentences of generated platitudes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At a low level, the core code simply generates &lt;em&gt;N&lt;/em&gt; sentences on a given &lt;em&gt;topic&lt;/em&gt; from the available templates. My implementation follows a simple randomized structure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Optional:&lt;/strong&gt; 1 sentence on a random topic&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Always:&lt;/strong&gt; 3 sentences on a common topic&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optional:&lt;/strong&gt; 1 or 2 sentences on separate topics&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The sentences themselves are templated, with random words inserted according to type.&lt;/p&gt;

&lt;p&gt;For example: &lt;code&gt;&amp;quot;The goal of &lt;var&gt;#fixedNP&lt;/var&gt; is to plant the seeds of &lt;var&gt;#nMass&lt;/var&gt; rather than &lt;var&gt;#nMassBad&lt;/var&gt;.&amp;quot;&lt;/code&gt;
can become &lt;em&gt;&amp;ldquo;The goal of ultrasonic energy is to plant the seeds of self-actualization rather than dogma.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is all fairly straightforward random-text-generation stuff, but I have to give kudos to the high-quality templates written by Seb Pearce &amp;mdash; the resulting text is highly readable and often charming.&lt;/p&gt;


&lt;h3&gt;Receipt printer word wrapping&lt;/h3&gt;

&lt;p&gt;On the receipt printer and &lt;strong&gt;screaming.computer&lt;/strong&gt; side of things, I finally wrote a multibyte-safe word-wrapping function; important for blocks of text since the receipt printer is only 32 characters wide at &lt;code&gt;FONT_A&lt;/code&gt; size.&lt;/p&gt;

&lt;p&gt;(PHP has the &lt;a href="https://www.php.net/manual/en/function.wordwrap"&gt;wordwrap function&lt;/a&gt;, but it's no good for Unicode strings.)&lt;/p&gt;

&lt;p&gt;My &lt;code&gt;mb_wordwrap&lt;/code&gt; function inserts a Unicode &lt;em&gt;soft break&lt;/em&gt; (U+2028) at all potential wrap points (surrounding all hard breaks, following runs of whitespace, following runs of dashes/hyphens). The string is then exploded at the soft breaks and each chunk is added to the current line if it will fit. When a line gets too long, a hard break is inserted, then we make a new line and carry on with the next chunk.&lt;/p&gt;

&lt;p&gt;Compare the default hard-wrap at 32 characters with the intelligent word-wrap:&lt;/p&gt;

&lt;div&gt;
&lt;div&gt;&lt;pre&gt;We are at a crossroads of life-f
orce and materialism. Bondage is
 born in the gap where passion h
as been excluded.&lt;/pre&gt;&lt;/div&gt;

&lt;div&gt;&lt;pre&gt;We are at a crossroads of life-
force and materialism. Bondage
is born in the gap where passion
has been excluded.&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Again, this is all fairly trivial infrastructure code, but even &amp;lsquo;trivial&amp;rsquo; code takes time to design and implement.&lt;/p&gt;


&lt;h3&gt;Unicode character mismapping&lt;/h3&gt;

&lt;p&gt;Some of the template strings contain curly-quotes, em-dashes, and other extended Unicode characters.&lt;/p&gt;

&lt;div&gt;
&lt;p&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20201031-developed-1450c.jpg" alt="Printout of extended Unicode characters"&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://screaming.computer/blog/2020-02/install-and-test-the-escpos-php-library"&gt;escpos receipt printing library&lt;/a&gt; ostensibly handles the conversion from UTF-8 to the printer's various native codepages, however I've observed a few glitches where extended characters are printed incorrectly. (Often the right-single-quote U+2019 prints as an accented-lowercase-o for some reason).&lt;/p&gt;

&lt;p&gt;A cursory glance at the &lt;code&gt;Printer::text&lt;/code&gt; function leads to the &lt;code&gt;EscposPrintBuffer::writeText&lt;/code&gt; function, which has some pretty inscrutable codepage-switching code.... I'll study this and debug it eventually, but not today.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;In the meantime I yield to the code and simply replace the problematic characters with their nearest ASCII-equivalent.&lt;/p&gt;


&lt;h3&gt;Words to change your life&lt;/h3&gt;

&lt;p&gt;This concludes the initial implementation of module #3 for the &lt;strong&gt;screaming.computer&lt;/strong&gt;!  At this time, I feel it's important to recall the ancient wisdom:&lt;/p&gt;

&lt;div&gt;
&lt;p&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20201031-developed-1453c.jpg" alt="Printout of a generated platitude"&gt;&lt;/p&gt;
&lt;div&gt;&lt;pre&gt;It can be difficult to know
where to begin. Entity, look
within and empower yourself. How
should you navigate this non-
local solar system?

By evolving, we believe.&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Say it with me:&amp;nbsp; &lt;em&gt;&lt;strong&gt;&amp;ldquo;By evolving, we believe!&amp;rdquo;&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
			</description>
			<guid isPermaLink="true">https://screaming.computer/blog/2020-10/philosophical-nonsense-generator</guid>
			<pubDate>Sat, 31 Oct 2020 18:38:05 GMT</pubDate>
			<source url="https://screaming.computer/blog/rss/">screaming.computer</source>
		</item>

		<item>
			<title>Dotter Dotter (projects archive)</title>
			<link>https://screaming.computer/blog/2020-09/dotter-dotter</link>
			<description>
&lt;p&gt;Approximately one million years ago (on a &lt;a href="https://web.archive.org/web/20110302140433/http://blog.jargon.ca/archive/"&gt;previous blog&lt;/a&gt; circa 2008) I reblogged &lt;em&gt;&lt;a href="http://dotter.exblog.jp/"&gt;Dotter Dotter's 3D pixelcraft&lt;/a&gt;&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://dotter.exblog.jp/7700526/"&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20200919-dotter-b0092714-3424178.jpg" alt="Dotter Dotter: Super Mario Galaxy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div&gt;
&lt;p&gt;&lt;a href="http://dotter.exblog.jp/9445976/"&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20200919-dotter-b0092714-21204640.jpg" alt="Dotter Dotter: Rock In Japan"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote cite="http://www.offworld.com/2008/12/dotter-dotters-3d-pixelcraft.html"&gt;&lt;p&gt;More retro inspiration: there's something about how the shading softens the hard pixel lines in &lt;a href="https://web.archive.org/web/20090221014456/http://tibori.com/games/index.html"&gt;Tibori Design&lt;/a&gt;'s &lt;a href="http://dotter.exblog.jp/"&gt;Dotter Dotter&lt;/a&gt; series that makes me almost lust for either a next-gen game done up with the renderer or &amp;#8212; fire up your 3D printers &amp;#8212; figure playsets of each. Nintendo's already essentially done the latter nearly spot on with &lt;a href="http://www.jbox.com/IMAGE/9focd"&gt;their Super Mario Bros. dioramas&lt;/a&gt;, so it's up to &lt;em&gt;somebody&lt;/em&gt; now to do the former.&lt;/p&gt;
&lt;footer&gt;&lt;p&gt;&lt;cite&gt;&amp;mdash; &lt;a href="https://web.archive.org/web/20090427055949/http://www.offworld.com/2008/12/dotter-dotters-3d-pixelcraft.html"&gt;Offworld&lt;/a&gt;, via &lt;a href="https://web.archive.org/web/20091215064031/http://www.4colorrebellion.com/archives/2008/12/05/8-bit-in-3d/"&gt;4 color rebellion&lt;/a&gt;, via &lt;a href="https://web.archive.org/web/20091205080852/http://chunnel.tv/art-design/350/8-Bit-in-3D"&gt;Chunnel.tv&lt;/a&gt;, December 2008&lt;/cite&gt;&lt;/p&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'm quite fond of the &lt;a href="https://en.wikipedia.org/wiki/The_Legend_of_Zelda"&gt;Legend of Zelda&lt;/a&gt; series, and these &lt;em&gt;Dotter Dotter&lt;/em&gt; renderings were particularly inspiring:&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="http://dotter.exblog.jp/4579726/"&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20200919-dotter-b0092714-1224131.jpg" alt="Dotter Dotter: Zelda2"&gt;&lt;/a&gt;&lt;a href="http://dotter.exblog.jp/4579707/"&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20200919-dotter-b0092714-21205551-crop.jpg" alt="Dotter Dotter: Zelda"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Coincidentally I'd recently been experimenting with 3D modelling and rendering using &lt;a href="https://www.newtek.com/lightwave/"&gt;NewTek Lightwave&lt;/a&gt; (version 9!) and since I'm a sucker for any NES nostalgia, I was compelled to try my hand at recreating something in this vein.&lt;/p&gt;

&lt;p&gt;As a superfan of the &lt;a href="https://en.wikipedia.org/wiki/Metroid"&gt;Metroid&lt;/a&gt; series, it was a natural choice to adapt a classic location:&lt;/p&gt;

&lt;p&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20200919-metroid-untitled2.png" alt="Screenshot of Metroid (NES Game)"&gt;&lt;/p&gt;

&lt;p&gt;Comparing the file-creation dates on all related source files, apparently I spent nearly &lt;em&gt;eight weeks&lt;/em&gt; on this, though I never published the result. I remember lots of intense modelling sessions, and I wrote a bunch of custom code as a Lightwave plugin to assist with the pseudo-voxel modelling process. Everything was pumped through an &lt;em&gt;ambient occlusion&lt;/em&gt; renderer module (running on a Pentium 4), then composited in layers.&lt;/p&gt;

&lt;p&gt;Though I vaguely recall working on multiple scenes, this image is the only finalized render I have from this effort. I took some minimal artistic license with the block placement, but each cube face is pixel- and colour-accurate.&lt;/p&gt;

&lt;p&gt;Behold, my 3D interpretation of &lt;a href="https://en.wikipedia.org/wiki/Metroid_(video_game)"&gt;NES Metroid&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://screaming.computer/blog/files/20200919-metroidb-composite-2020-a.jpg"&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20200919-metroidb-composite-2020-a.jpg" alt="3D interpretation of Metroid (NES Game)"&gt;&lt;/a&gt;&lt;/p&gt;
			</description>
			<guid isPermaLink="true">https://screaming.computer/blog/2020-09/dotter-dotter</guid>
			<pubDate>Sat, 19 Sep 2020 03:36:21 GMT</pubDate>
			<source url="https://screaming.computer/blog/rss/">screaming.computer</source>
		</item>

		<item>
			<title>MySQL Character Sets: utf8 is a Lie</title>
			<link>https://screaming.computer/blog/2020-07/mysql-character-sets-utf8-is-a-lie</link>
			<description>
&lt;p&gt;So apparently everyone but myself knew that in MySQL, the &lt;code&gt;utf8&lt;/code&gt; character set is not true UTF-8, but a broken subset that supports only 3-byte characters. Also apparently &lt;code&gt;utf8_general_ci&lt;/code&gt; is hopelessly defective and should never be used.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hmmmph.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What you &lt;em&gt;really&lt;/em&gt; want is &lt;code&gt;utf8mb4&lt;/code&gt; and &lt;code&gt;utf8mb4_unicode_ci&lt;/code&gt;. This gives you true UTF-8 support and standards-compliant sorting.&lt;/p&gt;

&lt;p&gt;Spent part of an afternoon updating all my code and converting my databases to support the correct charset. Should be good-to-go now.&lt;/p&gt;

&lt;p&gt;Also updated the database-creation code in my &lt;a href="https://screaming.computer/blog/2020-01/set-up-a-lamp-server-vm-in-unraid#guide_database"&gt;LAMP Server Setup Guide&lt;/a&gt;.&lt;/p&gt;
			</description>
			<guid isPermaLink="true">https://screaming.computer/blog/2020-07/mysql-character-sets-utf8-is-a-lie</guid>
			<pubDate>Sun, 19 Jul 2020 20:38:44 GMT</pubDate>
			<source url="https://screaming.computer/blog/rss/">screaming.computer</source>
		</item>

		<item>
			<title>One Year of Screaming</title>
			<link>https://screaming.computer/blog/2020-07/one-year-of-screaming</link>
			<description>
&lt;p&gt;This week marks &lt;em&gt;one year&lt;/em&gt; since &lt;a href="https://screaming.computer/blog/2019-07/project"&gt;the inspiration&lt;/a&gt; for the &lt;strong&gt;screaming.computer&lt;/strong&gt; project first came to me.&lt;/p&gt;

&lt;p&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/screaming-computer.png" alt="Retro computer with screaming mouth on-screen"&gt;&lt;/p&gt;

&lt;p&gt;I've made gradual progress, working in fits and starts, and taking plenty of detours along the way. This is my ideal mode of development. Even the project goals remain intentionally underdeveloped, which is a clever way for me to shoehorn any interest-of-the-moment under the &lt;strong&gt;screaming.computer&lt;/strong&gt; banner. Not sure why I find this helpful, but I do.&lt;/p&gt;

&lt;p&gt;I do have plans, though, and an awesomely-odd end result in mind. It may take a while to get there, but I'm thoroughly enjoying the journey. As I mentioned in the first post here, this has been a platform to exercise my creativity and tickle the odd recesses of my brain. What more could I ask for?!&lt;/p&gt;

&lt;p&gt;Here's to another year of screaming.&lt;/p&gt;

&lt;p&gt;Cheers!&lt;/p&gt;
			</description>
			<guid isPermaLink="true">https://screaming.computer/blog/2020-07/one-year-of-screaming</guid>
			<pubDate>Fri, 10 Jul 2020 15:31:13 GMT</pubDate>
			<source url="https://screaming.computer/blog/rss/">screaming.computer</source>
		</item>

		<item>
			<title>Amazing Mazes of Varying Difficulty</title>
			<link>https://screaming.computer/blog/2020-06/amazing-mazes-of-varying-difficulty</link>
			<description>
&lt;p&gt;Module number two for the &lt;strong&gt;screaming.computer&lt;/strong&gt; is a &lt;strong&gt;Maze Generator&lt;/strong&gt;!&lt;/p&gt;

&lt;div&gt;
&lt;p&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20200614-maze.png" alt="Maze"&gt;&lt;/p&gt;

&lt;p&gt;After much research into &lt;a href="https://en.wikipedia.org/wiki/Maze_generation_algorithm"&gt;maze generation algorithms&lt;/a&gt;, I realized it was fascinating, but a deeper rabbit-hole than I wanted to explore at the moment. Thankfully there are plenty of pre-written maze generators available online, ready to use with some minor tweaks.&lt;/p&gt;

&lt;p&gt;I grabbed &lt;a href="https://github.com/chimericdream/PHP-Maze-Generator"&gt;Bill Parrott's PHP maze class&lt;/a&gt; since it seemed pretty straightforward. It generates random mazes of arbitrary size, but unfortunately only renders them to an HTML-based text format.&lt;/p&gt;

&lt;p&gt;I modified the display algorithm to instead generate a PNG image via &lt;a href="https://www.php.net/manual/en/book.imagick.php"&gt;ImageMagick&lt;/a&gt; (as in the example image here), and expanded the code to allow for arbitrary cell size.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;Once the image generation was in place, I needed to adapt it to the constraints of my &lt;a href="https://screaming.computer/blog/2020-02/connect-a-thermal-receipt-printer-to-a-linux-vm-in-unraid"&gt;thermal receipt printer&lt;/a&gt;. Finished off a roll of paper during the dev process; thankfully I bought a big multipack of supplies.&lt;/p&gt;

&lt;p&gt;The major issue that immediately presented itself was the printer failed to print the top and bottom borders of the maze image. As far as I can tell, the printer just can't handle a solid horizontal line. I tried increasing the line thickness, but to no avail. Something about the thermal printing process responds poorly to large streaks of pure black (I saw this with the dithered &lt;a href="https://screaming.computer/blog/2020-04/it-is-decidedly-so"&gt;Magic 8-Ball&lt;/a&gt; image as well).&lt;/p&gt;

&lt;p&gt;The solution I came up with was to rotate the image, thereby causing perfectly-horizontal lines to break up over multiple rows. I experimented with various degrees of rotation; 30&amp;deg; and 45&amp;deg; rotations looked most visually pleasing, but the necessary reduction in size was too much for my taste. I settled on a counterclockwise 1&amp;deg; rotation. It looks a little crooked, but serves the purpose of fixing the horizontal line printing issue.&lt;/p&gt;

&lt;p&gt;Various stages of development and testing can be seen here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://screaming.computer/blog/files/20200614-developed-1443.jpg"&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20200614-developed-1443.jpg" alt="Assortment of receipt paper printed with mazes and programming debug messages"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code was then integrated into my standardized module interface. This means when the &lt;strong&gt;screaming.computer&lt;/strong&gt; is run, a module is randomly selected and executed. So far I have just two: the &lt;em&gt;8-Ball&lt;/em&gt; and this &lt;em&gt;Maze&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;Maze Difficulty&lt;/h3&gt;

&lt;p&gt;In the spirit of variety, I randomly select a cell size and maze height (within sensible constraints given the printer resolution). The smaller the cell the more &amp;ldquo;difficult&amp;rdquo; the maze is considered to be. Mix in some fun strings to describe the difficulties, and boom, we're done:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$randDifficulty = random_int (0, 1000) / 1000.0;&lt;br&gt;
$cellSize = (int) round (($maxCellSize - $minCellSize) * (1.0 - $randDifficulty)) + $minCellSize;&lt;br&gt;&lt;br&gt;
$difficulties = array (&lt;span&gt;'Trivial',&lt;/span&gt;
&lt;span&gt;'No sweat',&lt;/span&gt;
&lt;span&gt;'Easy-peasy',&lt;/span&gt;
&lt;span&gt;'Pretty easy',&lt;/span&gt;
&lt;span&gt;'Not so hard',&lt;/span&gt;
&lt;span&gt;'Make you think',&lt;/span&gt;
&lt;span&gt;'Extra medium',&lt;/span&gt;
&lt;span&gt;'Time consuming',&lt;/span&gt;
&lt;span&gt;'Challenging',&lt;/span&gt;
&lt;span&gt;'Expert',&lt;/span&gt;
&lt;span&gt;'Painful',&lt;/span&gt;
&lt;span&gt;'Hurt me'&lt;/span&gt;);&lt;br&gt;&lt;br&gt;
$difficultyStr = mb_strtoupper ($difficulties[(int) round ($randDifficulty * (count ($difficulties) - 1))]);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://screaming.computer/blog/files/20200614-developed-1434.jpg"&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20200614-developed-1434.jpg" alt="4 mazes of increasing difficulty printed on receipt paper"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's a-&lt;em&gt;maze&lt;/em&gt;-ing!&lt;/p&gt;
			</description>
			<guid isPermaLink="true">https://screaming.computer/blog/2020-06/amazing-mazes-of-varying-difficulty</guid>
			<pubDate>Sun, 14 Jun 2020 05:25:54 GMT</pubDate>
			<source url="https://screaming.computer/blog/rss/">screaming.computer</source>
		</item>

		<item>
			<title>It is decidedly so</title>
			<link>https://screaming.computer/blog/2020-04/it-is-decidedly-so</link>
			<description>
&lt;p&gt;The &lt;strong&gt;screaming.computer&lt;/strong&gt; has its first module: The &lt;a href="https://en.wikipedia.org/wiki/Magic_8-Ball"&gt;Magic 8-Ball&lt;/a&gt;. Not exactly a poetry-generator, but it's got to start somewhere!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://screaming.computer/blog/files/20200501-developed-1422.jpg"&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20200501-developed-1422.jpg" alt="Receipt paper printed with debug messages and Magic 8-Ball answers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20200501-developed-1423.jpg" alt="Receipt showing a Magic 8-Ball and the answer 'Ask again later.'"&gt;&lt;/p&gt;

&lt;p&gt;Infrastructure is in place to randomly load and execute any available module (although just this one exists at the moment), optionally with weighted probability of selection, to account for some modules having more or less variety of output.&lt;/p&gt;

&lt;p&gt;The Magic 8-Ball seemed like a straightforward proof-of-concept.  Perhaps next I'll do a fortune cookie....  Development on the complex stuff like poetry-generation continues in the background, but it's nice to be able to quickly integrate these simpler ideas as they pop into my head.&lt;/p&gt;
			</description>
			<guid isPermaLink="true">https://screaming.computer/blog/2020-04/it-is-decidedly-so</guid>
			<pubDate>Thu, 30 Apr 2020 23:27:38 GMT</pubDate>
			<source url="https://screaming.computer/blog/rss/">screaming.computer</source>
		</item>

		<item>
			<title>Mount a Network Share in Linux</title>
			<link>https://screaming.computer/blog/2020-03/mount-a-network-share-in-linux</link>
			<description>
&lt;div&gt;
&lt;p&gt;To access a shared folder on your network from Linux Mint, it must be mounted to the local Linux filesystem. Getting the read/&lt;wbr&gt;write permissions correct is a bit tricky, and often glossed over in other tutorials.&lt;/p&gt;

&lt;p&gt;This guide, as usual, assumes your system is configured per the &lt;a href="https://screaming.computer/blog/2020-01/set-up-a-lamp-server-vm-in-unraid"&gt;LAMP server setup guide&lt;/a&gt;. Furthermore, I assume your network share is wide open (permitting anonymous access) and you want to allow full read/&lt;wbr&gt;write access from Linux. If your use-case is different, you'll need to adjust the mount parameters.&lt;/p&gt;

&lt;p&gt;Since I want to access this share from PHP and Apache, I will be setting its group to the &lt;code&gt;www-data&lt;/code&gt; usergroup and enabling group read/write permissions.&lt;/p&gt;


&lt;h3&gt;Create a directory for your mount point&lt;/h3&gt;

&lt;p&gt;Create a directory, then set owner and permissions:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;sudo mkdir /media/&lt;var&gt;my-network-share&lt;/var&gt;&lt;br&gt;
sudo chown $USER:www-data -R /media/&lt;var&gt;my-network-share&lt;/var&gt;&lt;br&gt;
sudo chmod 02775 /media/&lt;var&gt;my-network-share&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;


&lt;h3&gt;Mount a network share from terminal&lt;/h3&gt;

&lt;p&gt;Mount the folder using the &lt;code&gt;mount&lt;/code&gt; command (all one line, no spaces after commas):&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;sudo mount
&lt;span&gt;-t cifs&lt;/span&gt;
&lt;span&gt;-o username=$USER,&lt;/span&gt;&lt;wbr
&gt;&lt;span&gt;password=,&lt;/span&gt;&lt;wbr
&gt;&lt;span&gt;uid=$(id -u),&lt;/span&gt;&lt;wbr
&gt;&lt;span&gt;gid=$(id -g www-data),&lt;/span&gt;&lt;wbr
&gt;&lt;span&gt;file_mode=0664,&lt;/span&gt;&lt;wbr
&gt;&lt;span&gt;dir_mode=02775&lt;/span&gt;
//&lt;var&gt;SHARE-IP&lt;/var&gt;/&lt;var&gt;SHARE-PATH&lt;/var&gt;
/media/&lt;var&gt;my-network-share&lt;/var&gt;/&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;&lt;var&gt;SHARE-IP&lt;/var&gt; is the IP address of the system which has the shared folder. (You should also be able to use its &lt;em&gt;name&lt;/em&gt;, however on my system the name wouldn't resolve and rather than fixing that issue I just used the IP instead.) &lt;var&gt;SHARE-PATH&lt;/var&gt; is the shared folder name or path.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;uid&lt;/code&gt; and &lt;code&gt;gid&lt;/code&gt; parameters mean the mounted directory will be owned by you (the current Linux user) and the &lt;code&gt;www-data&lt;/code&gt; Linux usergroup.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;file_mode&lt;/code&gt; and &lt;code&gt;dir_mode&lt;/code&gt; parameters set the permissions on the mounted directory so that both owner and group can read and write.&lt;/p&gt;

&lt;p&gt;This network share will remain mounted &lt;strong&gt;until the next reboot&lt;/strong&gt;. See below for persistent mounts.&lt;/p&gt;


&lt;h3&gt;Unmount all shares&lt;/h3&gt;

&lt;p&gt;&lt;kbd&gt;sudo umount -a -t cifs -l&lt;/kbd&gt;&lt;/p&gt;


&lt;h3&gt;Auto-mount a network share on every boot&lt;/h3&gt;

&lt;p&gt;To have the network share automatically mounted on every boot, add it to the &lt;code&gt;fstab&lt;/code&gt; file. This requires slightly different syntax, and you must manually determine your user and group ID numbers in advance.&lt;/p&gt;

&lt;p&gt;Find your &lt;var&gt;USERID&lt;/var&gt; number:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;id -u&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Find the &lt;var&gt;GROUPID&lt;/var&gt; number for the &lt;code&gt;www-data&lt;/code&gt; usergroup:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;id -g www-data&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;nano&lt;/code&gt; to append a line to the &lt;code&gt;fstab&lt;/code&gt; file (all one line, no spaces after commas):&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/fstab&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;//&lt;var&gt;SHARE-IP&lt;/var&gt;/&lt;var&gt;SHARE-PATH&lt;/var&gt; /media/&lt;var&gt;my-network-share&lt;/var&gt; cifs
&lt;span&gt;username=&lt;var&gt;USERNAME&lt;/var&gt;,&lt;/span&gt;&lt;wbr
&gt;&lt;span&gt;password=,&lt;/span&gt;&lt;wbr
&gt;&lt;span&gt;uid=&lt;var&gt;USERID&lt;/var&gt;,&lt;/span&gt;&lt;wbr
&gt;&lt;span&gt;gid=&lt;var&gt;GROUPID&lt;/var&gt;,&lt;/span&gt;&lt;wbr
&gt;&lt;span&gt;file_mode=0664,&lt;/span&gt;&lt;wbr
&gt;&lt;span&gt;dir_mode=02775&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;To immediately mount via the &lt;code&gt;fstab&lt;/code&gt; file, run:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;sudo mount -a&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;To ensure everything works as expected, reboot your system, then check the share:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;sudo reboot&lt;br&gt;
ls -l /media/&lt;var&gt;my-network-share&lt;/var&gt;/&lt;/kbd&gt;&lt;/p&gt;
&lt;/div&gt;
			</description>
			<guid isPermaLink="true">https://screaming.computer/blog/2020-03/mount-a-network-share-in-linux</guid>
			<pubDate>Thu, 26 Mar 2020 15:21:47 GMT</pubDate>
			<source url="https://screaming.computer/blog/rss/">screaming.computer</source>
		</item>

		<item>
			<title>Scraping the News</title>
			<link>https://screaming.computer/blog/2020-03/scraping-the-news</link>
			<description>
&lt;p&gt;Over the past five weeks I've been busy working on a web scraper to gather daily news content from the web.  The scraper has been running for about a month, and has so far collected over 10,000 unique articles. It continues to run, gathering new content in realtime.&lt;/p&gt;

&lt;h3&gt;Scraper activity summary&lt;/h3&gt;
&lt;div&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
	&lt;th&gt;Source&lt;/th&gt;
	&lt;th&gt;Total indexed URLs&lt;/th&gt;
	&lt;th&gt;Total parsed URLs&lt;/th&gt;
	&lt;th&gt;Daily scraped URLs&lt;br&gt;(7-day average)&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;News Site A&lt;/td&gt;&lt;td&gt;2899&lt;/td&gt;&lt;td&gt;2668&lt;/td&gt;&lt;td&gt;105&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;News Site B&lt;/td&gt;&lt;td&gt;3202&lt;/td&gt;&lt;td&gt;2904&lt;/td&gt;&lt;td&gt;99&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;News Site C&lt;/td&gt;&lt;td&gt;5617&lt;/td&gt;&lt;td&gt;4649&lt;/td&gt;&lt;td&gt;206&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tfoot&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;11718&lt;/td&gt;&lt;td&gt;10221&lt;/td&gt;&lt;td&gt;410&lt;/td&gt;&lt;/tr&gt;
&lt;/tfoot&gt;
&lt;/table&gt;
&lt;/div&gt;

&lt;p&gt;At this time the article text is simply being stored in a database along with some minimal metadata (date of publication, etc.). The eventual goal is to use this text as source material for the &lt;strong&gt;screaming.computer&lt;/strong&gt;'s generative algorithms. There is also great potential to run various statistical analyses on the text. All of that is still to come.&lt;/p&gt;

&lt;h3&gt;Scraping process&lt;/h3&gt;

&lt;p&gt;Never having written a web scraper before, I stuck to a straightforward approach using PHP and the &lt;a href="https://screaming.computer/blog/2020-02/install-the-php-simple-html-dom-parser-for-easy-web-scraping"&gt;Simple HTML DOM Parser&lt;/a&gt; library. The resulting indexer/&lt;wbr&gt;scraper is only as sophisticated as it needs to be to get the job done. It follows a three-stage process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Indexing&lt;/strong&gt;&lt;br&gt;
Gather links to potential news articles from a news site's main page.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scraping&lt;/strong&gt;&lt;br&gt;
Screen out unwanted links; download the page; verify it conforms to article format.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parsing&lt;/strong&gt;&lt;br&gt;
Parse the article page; strip out unwanted content (related links sections, pullquotes, embedded multimedia, etc.); reduce to plain text; store headline, publication date, and article text in database.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All this is scheduled using &lt;a href="https://linuxize.com/post/scheduling-cron-jobs-with-crontab/"&gt;cron jobs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There is a bunch of logic to filter out duplicate articles (news sites &lt;em&gt;love&lt;/em&gt; to provide the same content under multiple headlines and URLs). The code to normalize the article text (weeding out unwanted bits of the web page) is customized for each site. This custom code is necessarily brittle and threatens to break at any moment, but such is the nature of web scraping!&lt;/p&gt;

&lt;p&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20200317-newspapers.png" alt="Stack of newspapers"&gt;&lt;/p&gt;

&lt;h3&gt;Next steps&lt;/h3&gt;

&lt;p&gt;The indexer/&lt;wbr&gt;scraper/&lt;wbr&gt;parser framework is robust enough that I can add additional news sources in the future if desired. Each new source takes a few days to customize, including many rounds of reviewing and correcting the results to handle edge-cases.&lt;/p&gt;

&lt;p&gt;With the current sources combined, I'm getting about 350 successfully-parsed articles per day. This should be sufficient to move on to the next stage of the project: breaking the articles into component parts and performing basic text analysis.&lt;/p&gt;
			</description>
			<guid isPermaLink="true">https://screaming.computer/blog/2020-03/scraping-the-news</guid>
			<pubDate>Tue, 17 Mar 2020 21:44:35 GMT</pubDate>
			<source url="https://screaming.computer/blog/rss/">screaming.computer</source>
		</item>

		<item>
			<title>Saturday Night Coding Fuel</title>
			<link>https://screaming.computer/blog/2020-03/saturday-night-coding-fuel</link>
			<description>
&lt;p&gt;YouTube randomly suggested this live DJ set by Belgium-based &lt;a href="https://www.youtube.com/channel/UCg2JFUP67ZdKzehy8TWMUmw/about"&gt;Amelie Lens&lt;/a&gt;, and it makes the perfect background for some weekend evening coding.&lt;/p&gt;

&lt;div&gt;&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/RcVnqFxWMDM" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;&lt;/div&gt;

&lt;p&gt;Been a while since I've listened to some proper techno.&lt;/p&gt;

&lt;p&gt;As one commenter put it, &amp;ldquo;come for the music, stay for the cat.&amp;rdquo;&lt;/p&gt;
			</description>
			<guid isPermaLink="true">https://screaming.computer/blog/2020-03/saturday-night-coding-fuel</guid>
			<pubDate>Sun, 01 Mar 2020 01:47:32 GMT</pubDate>
			<source url="https://screaming.computer/blog/rss/">screaming.computer</source>
		</item>

		<item>
			<title>Install the PHP Simple HTML DOM Parser for easy web scraping</title>
			<link>https://screaming.computer/blog/2020-02/install-the-php-simple-html-dom-parser-for-easy-web-scraping</link>
			<description>
&lt;div&gt;

&lt;p&gt;When brainstorming for this project, I knew I'd eventually need to write a &lt;a href="https://en.wikipedia.org/wiki/Web_scraping"&gt;web scraper&lt;/a&gt; to gather text content for the &lt;strong&gt;screaming.computer&lt;/strong&gt;. I did quite a bit of Googling, and none of the scraping options looked promising. The available PHP libraries tended to be wildly overcomplicated for my needs, or very out-of-date, or just unnecessarily difficult to use. There were also a bunch of cloud-based scrapers and a few browser plugins, but these have dependencies I'd rather avoid.&lt;/p&gt;

&lt;p&gt;Discovering the &lt;a href="https://simplehtmldom.sourceforge.io/"&gt;PHP Simple HTML DOM Parser&lt;/a&gt; library was like striking gold. It's up-to-date, easy-to-use, and still quite fully-featured.&lt;/p&gt;


&lt;h3&gt;Install SimpleHtmlDom using composer&lt;/h3&gt;

&lt;p&gt;Having &lt;a href="https://screaming.computer/blog/2020-02/install-and-test-the-escpos-php-library"&gt;previously installed &lt;code&gt;composer&lt;/code&gt;&lt;/a&gt; to manage another library, it's already available on my &lt;a href="https://screaming.computer/blog/2020-01/set-up-a-lamp-server-vm-in-unraid"&gt;LAMP server&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Create a directory for the &lt;code&gt;SimpleHtmlDom&lt;/code&gt; library:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;mkdir /var/www/_simplehtmldom&lt;br&gt;
cd /var/www/_simplehtmldom&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Attempt to install the library stable version:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;composer require simplehtmldom/simplehtmldom&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Could not find a version of package simplehtmldom/simplehtmldom matching your minimum-stability (stable).&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Attempt to install any available version of the library:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;composer require simplehtmldom/simplehtmldom:*&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Your requirements could not be resolved to an installable set of packages.&lt;br&gt;
The requested package simplehtmldom/simplehtmldom * is satisfiable by simplehtmldom/simplehtmldom&lt;wbr&gt;[2.0-RC2, dev-master] but these conflict with your requirements or minimum-stability.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Okay, fine, we'll install the Release Candidate by name:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;composer require simplehtmldom/simplehtmldom:2.0-RC2&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Clearly I don't know anything about how to use &lt;code&gt;composer&lt;/code&gt;, but we have success nonetheless!&lt;/p&gt;


&lt;h3&gt;Scrape the web using PHP&lt;/h3&gt;

&lt;p&gt;This PHP fragment shows one basic use of &lt;code&gt;SimpleHtmlDom&lt;/code&gt; &amp;mdash; loading the CBC website and printing a list of all links on the page:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;require_once ('/var/www/_simplehtmldom/vendor/autoload.php');&lt;br&gt;
use simplehtmldom\HtmlWeb;&lt;br&gt;
&lt;br&gt;
$webParser = new HtmlWeb();&lt;br&gt;
$htmlDoc = $webParser-&amp;gt;load ('https://www.cbc.ca/news/');&lt;br&gt;
foreach ($htmlDoc-&amp;gt;find ('a') as $anchor)&lt;br&gt;
&amp;nbsp;&amp;nbsp;echo $anchor-&amp;gt;href, '&amp;lt;br&amp;gt;';&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;There are also some &lt;a href="https://simplehtmldom.sourceforge.io/manual.htm"&gt;very helpful examples&lt;/a&gt; at the &lt;code&gt;SimpleHtmlDom&lt;/code&gt; site.&lt;/p&gt;

&lt;/div&gt;
			</description>
			<guid isPermaLink="true">https://screaming.computer/blog/2020-02/install-the-php-simple-html-dom-parser-for-easy-web-scraping</guid>
			<pubDate>Mon, 17 Feb 2020 01:38:07 GMT</pubDate>
			<source url="https://screaming.computer/blog/rss/">screaming.computer</source>
		</item>

		<item>
			<title>Add image-based QR code support to escpos-php</title>
			<link>https://screaming.computer/blog/2020-02/add-image-based-qr-code-support-to-escpos-php</link>
			<description>
&lt;div&gt;

&lt;p&gt;The &lt;a href="https://screaming.computer/blog/2020-02/connect-a-thermal-receipt-printer-to-a-linux-vm-in-unraid"&gt;thermal receipt printer&lt;/a&gt; I am currently using doesn't natively supporting printing &lt;a href="https://en.wikipedia.org/wiki/QR_code"&gt;QR codes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We can extend the &lt;a href="https://screaming.computer/blog/2020-02/install-and-test-the-escpos-php-library"&gt;&lt;code&gt;escpos-php&lt;/code&gt; library&lt;/a&gt; to add image-based QR code support through the &lt;a href="http://phpqrcode.sourceforge.net/"&gt;&lt;code&gt;PhpQrCode&lt;/code&gt;&lt;/a&gt; library, &lt;a href="https://mike42.me/blog/2015-04-howto-qrcodes-on-receipts-with-escpos-php"&gt;as indicated in this tutorial&lt;/a&gt; by mike42 (the author of &lt;code&gt;escpos-php&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20200216-qr.png" alt="QR code"&gt;&lt;/p&gt;

&lt;p&gt;These instructions assume a &lt;a href="https://screaming.computer/blog/2020-01/set-up-a-lamp-server-vm-in-unraid"&gt;LAMP server&lt;/a&gt; with &lt;a href="https://screaming.computer/blog/2020-02/install-and-test-the-escpos-php-library"&gt;&lt;code&gt;escpos-php&lt;/code&gt;&lt;/a&gt; has been configured per previous guides.&lt;/p&gt;


&lt;h3&gt;Install GD and PhpQrCode&lt;/h3&gt;

&lt;p&gt;Install the &lt;a href="https://www.php.net/manual/en/book.image.php"&gt;&lt;code&gt;GD&lt;/code&gt; graphics library for PHP&lt;/a&gt; (verify your PHP version, alter GD library name as appropriate)&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;php --version&lt;br&gt;
sudo apt-get install php&lt;var&gt;7.2&lt;/var&gt;-gd&lt;br&gt;
sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Download the &lt;a href="http://sourceforge.net/projects/phpqrcode/files/"&gt;&lt;code&gt;PhpQrCode&lt;/code&gt;&lt;/a&gt; library&lt;br&gt;
Unzip it into &lt;code&gt;/var/www/_phpqrcode&lt;/code&gt;&lt;/p&gt;


&lt;h3&gt;Subclass the escpos-php Printer class&lt;/h3&gt;

&lt;p&gt;We can create a new &lt;code&gt;QrImgPrinter&lt;/code&gt; class which extends the escpos-php &lt;code&gt;Printer&lt;/code&gt; class, but implements the &lt;code&gt;qrCode&lt;/code&gt; function using image-based techniques rather than native printer support.&lt;/p&gt;

&lt;p&gt;This code is based on the &lt;a href="https://mike42.me/blog/2015-04-howto-qrcodes-on-receipts-with-escpos-php"&gt;mike42 tutorial&lt;/a&gt;, but is updated to fix a few bugs and work with the current versions of &lt;code&gt;PhpQrCode&lt;/code&gt; and &lt;code&gt;escpos-php&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Create a new PHP file &lt;code&gt;/var/www/_escpos/QrImgPrinter.php&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;?php&lt;br&gt;
require_once ('/var/www/_escpos/vendor/autoload.php');&lt;br&gt;
use Mike42\Escpos\Printer;&lt;br&gt;
use Mike42\Escpos\EscposImage;&lt;br&gt;
&lt;br&gt;
require_once ('/var/www/_phpqrcode/qrlib.php');&lt;br&gt;
&lt;br&gt;
&lt;br&gt;
class QrImgPrinter extends Printer {&lt;br&gt;
&lt;br&gt;
&amp;nbsp;function qrCode (string $content, int $ec = self::QR_ECLEVEL_L,&lt;br&gt;
&amp;nbsp;&amp;nbsp;int $size = 3, int $model = self::QR_MODEL_2)&lt;br&gt;
&amp;nbsp;{&lt;br&gt;
&amp;nbsp;&amp;nbsp;// Validate inputs&lt;br&gt;
&amp;nbsp;&amp;nbsp;self::validateInteger ($ec, 0, 3, __FUNCTION__);&lt;br&gt;
&amp;nbsp;&amp;nbsp;self::validateInteger ($size, 1, 16, __FUNCTION__);&lt;br&gt;
&amp;nbsp;&amp;nbsp;self::validateInteger ($model, 1, 3, __FUNCTION__);&lt;br&gt;
&amp;nbsp;&amp;nbsp;if (strlen ($content) &amp;lt; 1)&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return;&lt;br&gt;
&lt;br&gt;
&amp;nbsp;&amp;nbsp;// Only Model 2 supported in phpqrcode&lt;br&gt;
&amp;nbsp;&amp;nbsp;$model = self::QR_MODEL_2;&lt;br&gt;
&lt;br&gt;
&amp;nbsp;&amp;nbsp;// Generate filename for temp file&lt;br&gt;
&amp;nbsp;&amp;nbsp;$tmpfname = tempnam (sys_get_temp_dir(), 'escpos-php');&lt;br&gt;
&lt;br&gt;
&amp;nbsp;&amp;nbsp;// Create QR code in temp file and print it&lt;br&gt;
&amp;nbsp;&amp;nbsp;QRcode::png ($content, $tmpfname, $ec, $size, 0, false);&lt;br&gt;
&amp;nbsp;&amp;nbsp;$img = EscposImage::load ($tmpfname, false);&lt;br&gt;
&amp;nbsp;&amp;nbsp;$this-&amp;gt;bitImage ($img);&lt;br&gt;
&lt;br&gt;
&amp;nbsp;&amp;nbsp;// Delete temp file&lt;br&gt;
&amp;nbsp;&amp;nbsp;unlink ($tmpfname);&lt;br&gt;
&amp;nbsp;}&lt;br&gt;
}&lt;br&gt;
?&amp;gt;&lt;/code&gt;&lt;/p&gt;


&lt;h3&gt;Use the QrImgPrinter class to print QR codes&lt;/h3&gt;

&lt;p&gt;In your existing receipt printing code, include the &lt;code&gt;QrImgPrinter.php&lt;/code&gt; file and change your class instantiation from &lt;code&gt;new Printer&lt;/code&gt; to &lt;code&gt;new QrImgPrinter&lt;/code&gt;. Then call the &lt;code&gt;qrCode&lt;/code&gt; member function normally to print a QR code as an image:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$connector = new FilePrintConnector ('php://stdout');&lt;br&gt;
$printer = new QrImgPrinter ($connector);&lt;br&gt;
$printer-&amp;gt;initialize();&lt;br&gt;
$printer-&amp;gt;qrCode ('http://screaming.computer', Printer::QR_ECLEVEL_L, 6);&lt;br&gt;
$printer-&amp;gt;text (&amp;quot;\n&amp;quot;); // Flush&lt;br&gt;
$printer-&amp;gt;close();&lt;/code&gt;&lt;/p&gt;


&lt;/div&gt;
			</description>
			<guid isPermaLink="true">https://screaming.computer/blog/2020-02/add-image-based-qr-code-support-to-escpos-php</guid>
			<pubDate>Sun, 16 Feb 2020 01:31:20 GMT</pubDate>
			<source url="https://screaming.computer/blog/rss/">screaming.computer</source>
		</item>

		<item>
			<title>Install and test the escpos-php library</title>
			<link>https://screaming.computer/blog/2020-02/install-and-test-the-escpos-php-library</link>
			<description>
&lt;div&gt;


&lt;p&gt;Most thermal receipt printers are controlled using the &lt;code&gt;ESC/POS&lt;/code&gt; protocol. This is a standard set of control codes to control font size and style, print images and barcodes, etc. (though not all printers support all features &amp;mdash; some testing is generally required). The &lt;a href="https://github.com/mike42/escpos-php"&gt;&lt;code&gt;escpos-php&lt;/code&gt; library&lt;/a&gt; is a fantastic piece of software which wraps this protocol in an easy-to-use set of PHP classes.&lt;/p&gt;

&lt;p&gt;Installing the &lt;code&gt;escpos-php&lt;/code&gt; library is fairly easy, however it uses the &lt;a href="https://getcomposer.org/"&gt;&lt;code&gt;composer&lt;/code&gt;&lt;/a&gt; dependency manager which I was previously unfamiliar with.&lt;/p&gt;

&lt;p&gt;These are the steps I took to install &lt;code&gt;escpos-php&lt;/code&gt; on my &lt;a href="https://screaming.computer/blog/2020-01/set-up-a-lamp-server-vm-in-unraid"&gt;Linux server VM&lt;/a&gt; and print using my &lt;a href="https://screaming.computer/blog/2020-02/connect-a-thermal-receipt-printer-to-a-linux-vm-in-unraid"&gt;thermal receipt printer&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;Install composer:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;sudo apt install composer&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Install the &lt;code&gt;php-intl&lt;/code&gt; module:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;sudo apt-get install php-intl&lt;br&gt;
sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Create a folder for &lt;code&gt;escpos-php&lt;/code&gt; in your web directory, then install it:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;mkdir /var/www/_escpos&lt;br&gt;
cd /var/www/_escpos&lt;br&gt;
composer require mike42/escpos-php&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Create a PHP file &lt;code&gt;printerTest.php&lt;/code&gt; in a suitable location:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;require_once ('/var/www/_escpos/vendor/autoload.php');&lt;br&gt;
use Mike42\Escpos\Printer;&lt;br&gt;
use Mike42\Escpos\PrintConnectors\FilePrintConnector;&lt;br&gt;&lt;br&gt;
$connector = new FilePrintConnector ('php://stdout');&lt;br&gt;
$printer = new Printer ($connector);&lt;br&gt;&lt;br&gt;
$printer-&amp;gt;initialize();&lt;br&gt;
$printer-&amp;gt;text ("test\n");&lt;br&gt;
$printer-&amp;gt;close();&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This PHP file will output the binary control codes to &lt;code&gt;stdout&lt;/code&gt;. We run the program and pipe the output to our printer as follows:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;php printerTest.php | lp -s -d &lt;var&gt;THERMALPRINTER&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;In theory, we could use a &lt;code&gt;CupsPrintConnector&lt;/code&gt; instead of the &lt;code&gt;FilePrintConnector&lt;/code&gt; which would save us the step of redirecting the output. However, in my (very brief) test, the output seems to go into a print queue and must be manually released before it will print. This may have been a one-time hiccup or there may be a workaround, but using the &lt;code&gt;FilePrintConnector&lt;/code&gt; avoided the issue and suits my needs for now.&lt;/p&gt;


&lt;/div&gt;
			</description>
			<guid isPermaLink="true">https://screaming.computer/blog/2020-02/install-and-test-the-escpos-php-library</guid>
			<pubDate>Tue, 11 Feb 2020 03:41:13 GMT</pubDate>
			<source url="https://screaming.computer/blog/rss/">screaming.computer</source>
		</item>

		<item>
			<title>[SOLVED] Connect a thermal receipt printer to a Linux VM in UnRAID</title>
			<link>https://screaming.computer/blog/2020-02/connect-a-thermal-receipt-printer-to-a-linux-vm-in-unraid</link>
			<description>
&lt;div&gt;
&lt;p&gt;&lt;small&gt;Guide v1.6 / Updated 2020-02-08 / First published 2020-02-07 / &lt;span&gt;echo (at) screaming (dot) computer&lt;/span&gt;&lt;/small&gt;&lt;/p&gt;


&lt;p&gt;I experienced many problems trying to access my &lt;a href="https://www.amazon.ca/gp/product/B06XWCMV75/"&gt;thermal printer&lt;/a&gt; from my &lt;a href="https://screaming.computer/blog/2020-01/set-up-a-lamp-server-vm-in-unraid"&gt;Linux VM running in UnRAID&lt;/a&gt;.  After many diagnostics and lots of troubleshooting, I identified a two-part solution:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install a &lt;a href="https://www.amazon.ca/gp/product/B07GQPV54N/"&gt;USB expansion card&lt;/a&gt; into the UnRAID server and &lt;a href="https://forums.unraid.net/topic/35112-guide-passthrough-entire-pci-usb-controller/"&gt;pass it through&lt;/a&gt; for the exclusive use of the VM. This addresses low-level USB errors in the virtual machine.&lt;/li&gt;

&lt;li&gt;Install a generic &lt;a href="https://en.wikipedia.org/wiki/CUPS"&gt;CUPS&lt;/a&gt; driver and use the &lt;code&gt;lp&lt;/code&gt; command to send data to the printer. This addresses the issue where &lt;code&gt;usblp&lt;/code&gt; won't mount the device.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20200204-thermal-printer.jpg" alt="Thermal receipt printer"&gt;&lt;/p&gt;


&lt;h3&gt;POS58 thermal receipt printer&lt;/h3&gt;

&lt;p&gt;This particular thermal printer is listed as:&lt;/p&gt;

&lt;p&gt;Manufacturer=&lt;wbr&gt;&lt;em&gt;GD32 Microelectronics&lt;/em&gt;&lt;br&gt;
Product=&lt;wbr&gt;&lt;em&gt;POS58 USB Printer&lt;/em&gt;&lt;br&gt;
USB device: &lt;em&gt;Winbond Electronics Corp. Virtual Com Port&lt;/em&gt;&lt;br&gt;
Vendor &amp;amp; Product ID: &lt;em&gt;0416:5011&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It's sold under the name &amp;ldquo;&lt;a href="https://www.amazon.ca/gp/product/B06XWCMV75/"&gt;58MM USB Thermal Receipt Printer, Symcode&lt;/a&gt;&amp;rdquo; on Amazon.ca.&lt;/p&gt;


&lt;h3&gt;USB diagnostics in Linux&lt;/h3&gt;

&lt;p&gt;Some miscellaneous Linux commands which are helpful for diagnosing USB-connected devices:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;lsusb&lt;br&gt;
lsusb -v&lt;br&gt;
ls -l /dev/usb&lt;br&gt;
usb-devices&lt;br&gt;
lspci | grep USB&lt;br&gt;
dmesg&lt;/kbd&gt;&lt;/p&gt;

&lt;h3&gt;Failure part 1: libusb errors in UnRAID VM&lt;/h3&gt;

&lt;p&gt;With the thermal printer plugged into the UnRAID system (prior to adding the USB expansion card) and passed through to the VM, the VM system log showed the following errors when booting:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;libusb: error [op_set_interface] setintf failed error -1 errno 110&lt;br&gt;
libusb: error [op_clear_halt] clear_halt failed error -1 errno 71&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Under &lt;kbd&gt;lsusb&lt;/kbd&gt; it appeared as a &lt;em&gt;Winbond Electronics Corp. Virtual Com Port&lt;/em&gt;, and running &lt;kbd&gt;usb-devices&lt;/kbd&gt; showed the device as a &lt;em&gt;POS58 USB Printer&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The printer appeared to be mounted correctly by the &lt;code&gt;usblp&lt;/code&gt; kernel module at &lt;code&gt;/dev/usb/lp0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Inital attempts to access the printer failed due to lack of user permissions. The &lt;code&gt;lp0&lt;/code&gt; device was part of the &lt;code&gt;lp&lt;/code&gt; usergroup, so I added my current user to that group:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;sudo usermod -a -G lp $USER&lt;br&gt;
sudo reboot&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Even with correct permissions, sending data to the printer always failed with I/O errors:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;echo "test" &gt;&gt; /dev/usb/lp0&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bash: echo: write error: Input/output error&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;...and the VM system log showed this error:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;libusb: error [submit_bulk_transfer] submiturb failed error -1 errno=2&lt;/code&gt;&lt;/p&gt;


&lt;h3&gt;Solution part 1: Add a USB expansion card dedicated for VM use&lt;/h3&gt;

&lt;p&gt;Install your USB expansion card and boot your UnRAID server.&lt;/p&gt;

&lt;p&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20200204-usb-expansion.jpg" alt="PCIe USB expansion card"&gt;&lt;/p&gt;

&lt;p&gt;UnRAID [Tools] tab &amp;rarr; [System Devices]&lt;/p&gt;

&lt;p&gt;Find the PCI ID of your newly-installed card and save it for later use; it is in the form &lt;code&gt;XXXX:XXXX&lt;/code&gt;, where the X's are hexadecimal digits. My expansion card appeared as:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;IOMMU group 13: [1106:3483] 03:00.0 USB controller: VIA Technologies, Inc. VL805 USB 3.0 Host Controller (rev 01)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;so the ID number is &lt;code&gt;1106:3483&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;WARNING:&lt;/strong&gt; Do not get this ID number wrong. Be sure you have the ID of the correct device. Using the wrong ID in the following steps can cause serious issues for UnRAID. Editing the following settings can cause your UnRAID server to become unbootable. Proceed at your own discretion.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;UnRAID [Main] tab &amp;rarr; [Flash] device &amp;rarr; [Syslinux Configuration] &amp;rarr; click in the [unRAID OS] textbox&lt;/p&gt;

&lt;p&gt;On the &lt;code&gt;append&lt;/code&gt; line, add a &lt;code&gt;vfio-pci.ids&lt;/code&gt; parameter with the ID of your USB expansion card; this causes UnRAID to avoid &amp;ldquo;claiming&amp;rdquo; this device on startup, allowing it to be passed through for the exclusive use of a VM.&lt;/p&gt;

&lt;p&gt;After editing, my &lt;code&gt;append&lt;/code&gt; line in the &lt;code&gt;unRAID OS&lt;/code&gt; section looked as follows:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;append initrd=/bzroot vfio-pci.ids=1106:3483&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;[Apply], [Reboot] your UnRAID server&lt;/p&gt;

&lt;p&gt;UnRAID [VMs] tab &amp;rarr; &lt;em&gt;(your VM)&lt;/em&gt; &amp;rarr; [Edit]&lt;/p&gt;

&lt;p&gt;USB Controller: &lt;kbd&gt;3.0 (qemu XHCI)&lt;/kbd&gt;&lt;br&gt;
Other PCI Devices: &lt;em&gt;[check the box for your expansion card]&lt;/em&gt;&lt;br&gt;
[Update]&lt;/p&gt;

&lt;p&gt;If you had previous USB devices passed-through to the VM which are no longer connected, you may receive this error: &lt;em&gt;&amp;ldquo;internal error: unknown pci source type 'vendor'&amp;rdquo;&lt;/em&gt; &amp;mdash; The easiest way to resolve this is to reconnect the USB devices, deselect them in the VM configuration, then remove the devices.&lt;/p&gt;

&lt;p&gt;UnRAID [VMs] tab &amp;rarr; &lt;em&gt;(your VM)&lt;/em&gt; &amp;rarr; [Start]&lt;br&gt;
UnRAID [VMs] tab &amp;rarr; &lt;em&gt;(your VM)&lt;/em&gt; &amp;rarr; [Logs]&lt;/p&gt;

&lt;p&gt;Watch the log window for errors. Mine booted without issue.&lt;/p&gt;

&lt;p&gt;Using VNC to access the VM, open a terminal. Run &lt;kbd&gt;lsusb&lt;/kbd&gt; to observe your USB expansion card.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Successfully resolved:&lt;/strong&gt; boot-time &lt;code&gt;libusb&lt;/code&gt; errors. USB ports are now functioning correctly.&lt;/p&gt;


&lt;h3&gt;Failure part 2: usblp fails to mount the thermal printer device&lt;/h3&gt;

&lt;p&gt;Plug the thermal printer into the USB expansion card, run &lt;kbd&gt;ls /dev/usb&lt;/kbd&gt; to see the mounted device (expecting &lt;code&gt;lp0&lt;/code&gt; or similar), then test the device while watching the log:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;echo "test" &gt;&gt; /dev/usb/lp0&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If your printer prints the word &amp;ldquo;&lt;code&gt;test&lt;/code&gt;,&amp;rdquo; congratulations, it works!&lt;/p&gt;

&lt;p&gt;Unfortunately this was unsuccessful for me.  Running &lt;kbd&gt;ls /dev/usb&lt;/kbd&gt; came up empty. Curiously, &lt;code&gt;usblp&lt;/code&gt; was not mounting the printer now even though it did so prior to using the expansion card.&lt;/p&gt;

&lt;p&gt;Running &lt;kbd&gt;dmesg&lt;/kbd&gt; revealed the following error:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;usblp: can't set desired altsetting 0 on interface 0&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;My expansion card appeared to be successfully configured, but this particular model of USB printer seems to behave in quirky or non-standards-compliant ways.&lt;/p&gt;

&lt;p&gt;Others have observed similar issues with this model of printer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://raspberrypi.stackexchange.com/questions/96488/esc-pos-printer-not-detected-by-raspberry-pi"&gt;ESC/POS Printer not detected by Raspberry Pi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://forums.anandtech.com/threads/how-to-get-usb-thermal-printer-to-work-in-linux.2564142/"&gt;How to get USB thermal printer to work in Linux?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It appears that &lt;code&gt;usblp&lt;/code&gt; is failing to initialize and mount the printer.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(If, in your case, you see the device is mounted but it still generates I/O errors, look into blacklisting &lt;code&gt;usblp&lt;/code&gt; and try the CUPS solution, below.)&lt;/em&gt;&lt;/p&gt;


&lt;h3&gt;Solution part 2: Install CUPS driver, use lp command to print&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;* Solution found in this AskUbuntu post: &lt;a href="https://askubuntu.com/questions/110892/print-via-terminal-without-usblp/110940
"&gt;Print via terminal without usblp&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the Linux Mint GUI, open [Start Menu] &amp;rarr; [System] &amp;rarr; [Printers] &amp;rarr; [+Add]&lt;/p&gt;

&lt;p&gt;In the list of [Devices] there was an entry for &amp;ldquo;&lt;code&gt;Unknown (Printer)&lt;/code&gt;,&amp;rdquo; described as &lt;em&gt;&amp;ldquo;A printer connected to a USB port.&amp;rdquo;&lt;/em&gt; This was the thermal printer.&lt;/p&gt;

&lt;p&gt;Devices: &lt;code&gt;Unknown (Printer)&lt;/code&gt;&lt;br&gt;
[Forward]&lt;/p&gt;

&lt;p&gt;Select printer from database: &lt;em&gt;yes&lt;/em&gt;&lt;br&gt;
Makes: &lt;code&gt;Generic&lt;/code&gt;&lt;br&gt;
[Forward]&lt;/p&gt;

&lt;p&gt;Models: &lt;code&gt;Raw Queue&lt;/code&gt;&lt;br&gt;
[Forward]&lt;/p&gt;

&lt;p&gt;Printer Name: &lt;kbd&gt;&lt;var&gt;THERMALPRINTER&lt;/var&gt;&lt;/kbd&gt;&lt;br&gt;
[Apply]&lt;/p&gt;

&lt;p&gt;Open terminal, test the printer:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;echo "test" | lp -d &lt;var&gt;THERMALPRINTER&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;The printer successfully printed &amp;ldquo;&lt;code&gt;test&lt;/code&gt;&amp;rdquo; for me.&lt;/p&gt;

&lt;p&gt;&lt;img style="max-width: 100%;" src="https://screaming.computer/blog/files/20200208-printer-test-cups.jpg" alt="Successful test of receipt printer"&gt;&lt;/p&gt;

&lt;p&gt;You can suppress the job ID messages on &lt;code&gt;stdout&lt;/code&gt; using the &lt;code&gt;-s&lt;/code&gt; option. See the &lt;a href="https://linux.die.net/man/1/lp"&gt;&lt;code&gt;lp&lt;/code&gt; man page&lt;/a&gt; for full &lt;code&gt;lp&lt;/code&gt; command syntax.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Successfully resolved:&lt;/strong&gt; Use a generic CUPS driver to access the USB printer, in situations where &lt;code&gt;usblp&lt;/code&gt; fails to mount the device.&lt;/p&gt;


&lt;/div&gt;
			</description>
			<guid isPermaLink="true">https://screaming.computer/blog/2020-02/connect-a-thermal-receipt-printer-to-a-linux-vm-in-unraid</guid>
			<pubDate>Sat, 08 Feb 2020 03:08:45 GMT</pubDate>
			<source url="https://screaming.computer/blog/rss/">screaming.computer</source>
		</item>

		<item>
			<title>Set up a LAMP server VM in UnRAID</title>
			<link>https://screaming.computer/blog/2020-01/set-up-a-lamp-server-vm-in-unraid</link>
			<description>
&lt;div id="guide_top"&gt;
&lt;p&gt;&lt;small&gt;Guide v5.12 / Updated 2020-07-19 / First published 2020-01-21 / &lt;span&gt;echo (at) screaming (dot) computer&lt;/span&gt;&lt;/small&gt;&lt;/p&gt;

&lt;div&gt;
&lt;p&gt;This guide has been superceded by an updated version: &lt;a href="https://screaming.computer/blog/2023-09/updated-set-up-a-lamp-server-vm-in-unraid"&gt;[Updated 2023-09] LAMP server VM in UnRAID&lt;/a&gt; setup guide.&lt;br&gt;
New in that version: LTS release of Linux Mint; upgrade to PHP 8; switch from MySQL to MariaDB&lt;/p&gt;
&lt;/div&gt;

&lt;h3 id="guide_preface"&gt;Preface&lt;/h3&gt;

&lt;p&gt;Over the years while working on various projects, I've often wished for a fully-featured web development environment confined to my local network.  This would allow for offline development (before uploading to a public server), and also allow hosting of local projects that are never meant to be online.&lt;/p&gt;

&lt;p&gt;After a few unsatisfactory experiments with running a WAMP stack on my laptop, I decided to dive in and configure a proper LAMP server on a Linux virtual machine.  This approach was ideal since I already had a home server running UnRAID, which has great VM support.&lt;/p&gt;

&lt;p&gt;This guide was written primarily for myself as I set up the VM and configured Linux, and installed/configured all the various features and settings that are important for my needs.  There was a lot of trial-and-error to get everything working, so I wanted to ensure the &amp;ldquo;correct&amp;rdquo; procedures were captured somewhere. The guide grew to the point where it seemed sensible to share publicly in the hopes that others might find it helpful.  Here it is.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Please Note:&lt;/em&gt; The choice of software and settings are entirely based on what I imagine my personal needs to be, and depend upon my current (possibly flawed) understanding of the software involved. This guide might not be suitable for you, and even if it is, it might contain errors. Proceed at your own discretion.&lt;/p&gt;

&lt;p&gt;Content may be updated and expanded over time... take note of the &amp;ldquo;Updated&amp;rdquo; date, above.&lt;/p&gt;


&lt;h3 id="guide_overview"&gt;Overview&lt;/h3&gt;

&lt;p&gt;Platform:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;Linux Mint Xfce 19.3&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Server:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;Apache 2.4&lt;/li&gt;
	&lt;li&gt;MySQL 5&lt;/li&gt;
	&lt;li&gt;PHP 7.2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additional features:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;phpMyAdmin 4.6&lt;/li&gt;
	&lt;li&gt;ImageMagick 6.9 PHP plugin&lt;/li&gt;
	&lt;li&gt;Samba network file sharing&lt;/li&gt;
	&lt;li&gt;Virtual hosts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;*&amp;nbsp;version numbers as of this writing&lt;/em&gt;&lt;/p&gt;


&lt;h3 id="guide_accounts"&gt;1. User accounts&lt;/h3&gt;

&lt;p&gt;Once you have completed all steps in this guide, a variety of accounts and users will exist.&lt;/p&gt;

&lt;p&gt;Anywhere you see a &lt;var&gt;VARIABLE&lt;/var&gt; in this document, substitute your chosen value.  To change passwords later, refer to appendix A.&lt;/p&gt;


&lt;h4&gt;1.1 Linux Users&lt;/h4&gt;

&lt;p&gt;Linux Mint does not have a separate user named &lt;code&gt;root&lt;/code&gt;.  The first user you create during installation gets root privileges; in this guide the primary admin user is &lt;var&gt;VMUSER&lt;/var&gt;.&lt;/p&gt;

&lt;table&gt;
&lt;tbody&gt;
	&lt;tr&gt;
		&lt;td&gt;&lt;code&gt;root&lt;/code&gt;&lt;/td&gt;
		&lt;td&gt;&lt;var&gt;VMPASS&lt;/var&gt;&lt;/td&gt;
		&lt;td&gt;Virtual root user; not a true account&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;&lt;var&gt;VMUSER&lt;/var&gt;&lt;/td&gt;
		&lt;td&gt;&lt;var&gt;VMPASS&lt;/var&gt;&lt;/td&gt;
		&lt;td&gt;Primary admin user; has root privileges; member of the &lt;code&gt;www-data&lt;/code&gt; usergroup&lt;/td&gt;
	&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;


&lt;h4&gt;1.2 SQL Users&lt;/h4&gt;

&lt;table&gt;
&lt;tbody&gt;
	&lt;tr&gt;
		&lt;td&gt;&lt;code&gt;root&lt;/code&gt;&lt;/td&gt;
		&lt;td&gt;&lt;var&gt;SQLPASS&lt;/var&gt;&lt;/td&gt;
		&lt;td&gt;MySQL administrator; requires local superuser privileges to use&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;&lt;var&gt;SQLUSER&lt;/var&gt;&lt;/td&gt;
		&lt;td&gt;&lt;var&gt;SQLPASS&lt;/var&gt;&lt;/td&gt;
		&lt;td&gt;MySQL administrator; used to access phpMyAdmin&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;&lt;var&gt;DBUSER&lt;/var&gt;&lt;/td&gt;
		&lt;td&gt;&lt;var&gt;DBPASS&lt;/var&gt;&lt;/td&gt;
		&lt;td&gt;Limited user with permission to manage only the &lt;var&gt;DBNAME&lt;/var&gt; database&lt;/td&gt;
	&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;You should create separate low-privilege users for accessing your databases from PHP projects, such as &lt;var&gt;DBUSER&lt;/var&gt;, above; refer to &lt;a href="https://docs.phpmyadmin.net/en/latest/privileges.html"&gt;phpMyAdmin user management&lt;/a&gt;, and section 8 of this guide.&lt;/p&gt;


&lt;h4&gt;1.3 Samba User&lt;/h4&gt;

&lt;table&gt;
&lt;tbody&gt;
	&lt;tr&gt;
		&lt;td&gt;&lt;var&gt;SMBUSER&lt;/var&gt;&lt;/td&gt;
		&lt;td&gt;&lt;var&gt;SMBPASS&lt;/var&gt;&lt;/td&gt;
		&lt;td&gt;Used to connect to the network share &lt;code&gt;\\mint-vm\www&lt;/code&gt;; member of the &lt;code&gt;www-data&lt;/code&gt; usergroup&lt;/td&gt;
	&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;


&lt;h4&gt;1.4 Linux Usergroups&lt;/h4&gt;

&lt;table&gt;
&lt;tbody&gt;
	&lt;tr&gt;
		&lt;td&gt;&lt;code&gt;www-data&lt;/code&gt;&lt;/td&gt;
		&lt;td&gt;Any member of this group has full R/W access to web files, locally via &lt;code&gt;/var/www&lt;/code&gt; or remotely via &lt;code&gt;\\mint-vm\www&lt;/code&gt;&lt;/td&gt;
	&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;


&lt;h3 id="guide_vm"&gt;2. UnRAID VM setup&lt;/h3&gt;

&lt;p&gt;Download Mint Xfce 64-bit ISO from &lt;a href="https://www.linuxmint.com/download.php"&gt;https://www.linuxmint.com/download.php&lt;/a&gt;&lt;br&gt;
Place the ISO file in UnRAID &lt;code&gt;/mnt/user/isos&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;UnRAID [VMs] tab &amp;rarr; [Add VM] &amp;rarr; [Linux]&lt;/p&gt;

&lt;p&gt;Name:  &lt;kbd&gt;Linux Mint Xfce&lt;/kbd&gt;&lt;br&gt;
Initial Memory:  &lt;kbd&gt;1024 MB&lt;/kbd&gt;&lt;br&gt;
Max Memory:  &lt;kbd&gt;1024 MB&lt;/kbd&gt;&lt;br&gt;
OS Install ISO:  &lt;em&gt;[select downloaded ISO file]&lt;/em&gt;&lt;br&gt;
Primary vDisk Size:  &lt;kbd&gt;16G&lt;/kbd&gt;&lt;br&gt;
[Create]&lt;/p&gt;

&lt;p&gt;Launch [VNC Remote] from &amp;ldquo;Linux Mint Xfce&amp;rdquo; VM dropdown menu&lt;/p&gt;


&lt;h3 id="guide_linux"&gt;3. Linux installation&lt;/h3&gt;

&lt;p&gt;From the boot menu, run [Start Linux Mint]&lt;br&gt;
From the desktop, run [Install Linux Mint]&lt;br&gt;
Accept all defaults, then [Install Now]&lt;/p&gt;

&lt;p&gt;Your name:  &lt;kbd&gt;Administrator&lt;/kbd&gt;&lt;br&gt;
Your computer's name:  &lt;kbd&gt;mint-vm&lt;/kbd&gt;&lt;br&gt;
Pick a username:  &lt;kbd&gt;&lt;var&gt;VMUSER&lt;/var&gt;&lt;/kbd&gt;&lt;br&gt;
Choose a password:  &lt;kbd&gt;&lt;var&gt;VMPASS&lt;/var&gt;&lt;/kbd&gt;&lt;br&gt;
Confirm your password:  &lt;kbd&gt;&lt;var&gt;VMPASS&lt;/var&gt;&lt;/kbd&gt;&lt;br&gt;
Log in automatically:  &lt;em&gt;yes&lt;/em&gt;&lt;br&gt;
[Continue]&lt;/p&gt;

&lt;p&gt;Wait for installation, then [Restart Now]&lt;br&gt;
&amp;ldquo;Please remove the installation medium then press enter&amp;rdquo; &amp;rarr; just press enter&lt;br&gt;
Wait for restart&lt;br&gt;
Uncheck [Show this dialog at startup] on Welcome screen, then close it&lt;/p&gt;

&lt;p&gt;Open [Update Manager] from the tray&lt;br&gt;
Install all available updates&lt;/p&gt;

&lt;p&gt;Open [System Reports] from the tray (warning icon with exclamation mark)&lt;br&gt;
[Ignore this problem] for each detected issue&lt;/p&gt;

&lt;p&gt;Open [Power Manager] from the tray (lightning bolt icon)&lt;br&gt;
[Display] tab &amp;rarr; uncheck [Display power management], set [Blank after] to never&lt;br&gt;
[Security] tab &amp;rarr; [Automatically lock the session] &amp;rarr; [Never]&lt;/p&gt;


&lt;h3 id="guide_lan"&gt;4. LAN configuration&lt;/h3&gt;

&lt;p&gt;These instructions apply to routers running &lt;em&gt;Tomato&lt;/em&gt; firmware; tested on &lt;em&gt;&lt;a href="https://exotic.se/freshtomato/"&gt;FreshTomato&lt;/a&gt; v2018.4&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;var&gt;STATIC-IP&lt;/var&gt; is an IP address suitable for your LAN; ex: &lt;code&gt;192.168.0.55&lt;/code&gt;&lt;br&gt;
&lt;var&gt;HOSTNAME&lt;/var&gt; is a local hostname for your server; using an unassigned TLD such as &lt;em&gt;.home&lt;/em&gt; or &lt;em&gt;.lan&lt;/em&gt; is recommended (avoid &lt;em&gt;.local&lt;/em&gt; as it is reserved for use with mDNS); ex: &lt;code&gt;mint-vm.home&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Log in to your router&lt;/p&gt;


&lt;h4&gt;4.1 Set a static IP address&lt;/h4&gt;

&lt;p&gt;[Status] &amp;rarr; [Device List] &amp;rarr; &lt;em&gt;(your VM)&lt;/em&gt; &amp;rarr; [static]&lt;br&gt;
Set a &lt;var&gt;STATIC-IP&lt;/var&gt; address, [Add], [Save]&lt;/p&gt;

&lt;p&gt;[Status] &amp;rarr; [Device List] &amp;rarr; &lt;em&gt;(your VM)&lt;/em&gt; &amp;rarr; &lt;em&gt;(click on lease time to delete current lease)&lt;/em&gt;&lt;br&gt;
Deleting the lease will expire the existing IP allocation&lt;/p&gt;

&lt;p&gt;Reboot the Mint VM, then verify the static IP address is in use&lt;/p&gt;


&lt;h4&gt;4.2 Set a local hostname for the server&lt;/h4&gt;

&lt;p&gt;[Advanced] &amp;rarr; [DHCP/DNS] &amp;rarr; [Dnsmasq Custom Configuration]&lt;/p&gt;
&lt;p&gt;&lt;code&gt;local-ttl=1&lt;br&gt;
address=/.&lt;var&gt;HOSTNAME&lt;/var&gt;/&lt;var&gt;STATIC-IP&lt;/var&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;[Save]&lt;/p&gt;

&lt;p&gt;This allows any computer on your LAN to access the Mint VM via the host name &lt;code&gt;&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt; or any subdomain &lt;code&gt;*.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/p&gt;


&lt;h3 id="guide_server"&gt;5. Install server software&lt;/h3&gt;

&lt;p&gt;Inside the Mint VM, open terminal and run these commands&lt;/p&gt;


&lt;h4&gt;5.1 Install LAMP&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo apt-get update &amp;amp;&amp;amp; sudo apt-get dist-upgrade -y&lt;br&gt;
sudo reboot&lt;br&gt;
sudo apt-get install lamp-server^ -y&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Test Apache:  Open Firefox, view &lt;a href="http://localhost"&gt;&lt;code&gt;http://localhost&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h4&gt;5.2 Test PHP&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano /var/www/html/phpinfo.php&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;?php phpinfo(); ?&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo service apache2 restart&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Open Firefox, view &lt;a href="http://localhost/phpinfo.php"&gt;&lt;code&gt;http://localhost/phpinfo.php&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h4&gt;5.3 Install phpMyAdmin and configure SQL user&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo apt-get install phpmyadmin -y&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;when prompted for a webserver, &lt;strong&gt;press &lt;em&gt;space&lt;/em&gt;&lt;/strong&gt; to tick the box selecting &lt;em&gt;apache2&lt;/em&gt;&lt;br&gt;
when prompted to configure database, select &lt;em&gt;yes&lt;/em&gt;&lt;br&gt;
when prompted for MySQL password, enter &lt;kbd&gt;&lt;var&gt;SQLPASS&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo mysql -uroot -p&lt;var&gt;SQLPASS&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&lt;del&gt;DROP USER 'root'@'localhost';&lt;/del&gt;&lt;br&gt;
DROP USER 'phpmyadmin'@'localhost';&lt;br&gt;
CREATE USER '&lt;var&gt;SQLUSER&lt;/var&gt;'@'%' IDENTIFIED BY '';&lt;br&gt;
GRANT ALL PRIVILEGES ON *.* TO '&lt;var&gt;SQLUSER&lt;/var&gt;'@'%' WITH GRANT OPTION;&lt;br&gt;
FLUSH PRIVILEGES;&lt;br&gt;
SELECT User,Host FROM mysql.user;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+Z (to exit)&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;mysqladmin --user=&lt;var&gt;SQLUSER&lt;/var&gt; password "&lt;var&gt;SQLPASS&lt;/var&gt;"&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/dbconfig-common/phpmyadmin.conf&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Edit the line containing &lt;code&gt;dbc_dbuser&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dbc_dbuser='&lt;var&gt;SQLUSER&lt;/var&gt;'&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/phpmyadmin/config-db.php&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Edit the line containing &lt;code&gt;$dbuser&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$dbuser='&lt;var&gt;SQLUSER&lt;/var&gt;';&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo service mysql restart&lt;br&gt;
sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Open Firefox, view &lt;a href="http://localhost/phpmyadmin"&gt;&lt;code&gt;http://localhost/phpmyadmin&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
Username:  &lt;kbd&gt;&lt;var&gt;SQLUSER&lt;/var&gt;&lt;/kbd&gt;&lt;br&gt;
Password:  &lt;kbd&gt;&lt;var&gt;SQLPASS&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;


&lt;h4&gt;5.4 Enable &amp;ldquo;nologin&amp;rdquo; user accounts&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano /etc/shells&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Append this line to the end of the file (no trailing newline):&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/usr/sbin/nologin&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;


&lt;h4&gt;5.5 Set permissions on web folder&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo adduser $USER www-data&lt;br&gt;
sudo chown $USER:www-data -R /var/www&lt;br&gt;
sudo chmod u=rwX,g=srwX,o=rX -R /var/www&lt;br&gt;
sudo chmod 0664 /var/www/html/*&lt;/kbd&gt;&lt;/p&gt;


&lt;h4&gt;5.6 Allow .htaccess configuration in web folder&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/apache2/apache2.conf&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Inside configuration block &lt;code&gt;&amp;lt;Directory /var/www/&amp;gt;&lt;/code&gt;, alter the &lt;code&gt;AllowOverride&lt;/code&gt; line:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AllowOverride All&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;


&lt;h4&gt;5.7 Enable mod_rewrite Apache module&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo a2enmod rewrite&lt;br&gt;
sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;


&lt;h4 id="installImageMagick"&gt;5.8 Install ImageMagick plugin for PHP&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo apt-get install php-imagick&lt;br&gt;
sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;


&lt;h4&gt;5.9 Increase PHP upload and POST size limits&lt;/h4&gt;

&lt;p&gt;PHP defaults to 8 megabytes max POST size and 2 megabytes max file upload size. Changing these limits so they match can make debugging easier. Select a limit &lt;var&gt;MAXUPLOAD&lt;/var&gt; megabytes that is suitable for your projects.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; the path below may require updating to match the installed PHP version.&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/php/&lt;var&gt;7.2&lt;/var&gt;/apache2/php.ini&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Alter the line containing &lt;code&gt;post_max_size&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;post_max_size = &lt;var&gt;MAXUPLOAD&lt;/var&gt;M&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Alter the line containing &lt;code&gt;upload_max_filesize&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;upload_max_filesize = &lt;var&gt;MAXUPLOAD&lt;/var&gt;M&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;


&lt;h4&gt;5.10 Enable PHP error reporting&lt;/h4&gt;

&lt;p&gt;If you're using this server for development purposes, you will want to allow PHP to display errors. The &lt;a href="https://www.php.net/manual/en/errorfunc.configuration.php#ini.display-errors"&gt;&lt;code&gt;display_errors&lt;/code&gt; setting&lt;/a&gt; should be &lt;code&gt;On&lt;/code&gt; for development, and &lt;code&gt;Off&lt;/code&gt; for production/live servers.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; the path below may require updating to match the installed PHP version.&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/php/&lt;var&gt;7.2&lt;/var&gt;/apache2/php.ini&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Alter the line containing &lt;code&gt;display_errors&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;display_errors = On&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Alternately, you can control this setting on a script-by-script basis by calling the PHP &lt;code&gt;ini_set()&lt;/code&gt; function:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ini_set ('display_errors', 'On');&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Error reporting can be further controlled by the &lt;a href="https://www.php.net/manual/en/errorfunc.configuration.php#ini.error-reporting"&gt;&lt;code&gt;error_reporting&lt;/code&gt; setting in &lt;code&gt;php.ini&lt;/code&gt;&lt;/a&gt; or the corresponding &lt;a href="https://www.php.net/manual/en/function.error-reporting.php"&gt;&lt;code&gt;error_reporting()&lt;/code&gt; function&lt;/a&gt; in your PHP script. For production I use a value of &lt;code&gt;0&lt;/code&gt;, or &lt;code&gt;(E_ALL | E_STRICT)&lt;/code&gt; for development.&lt;/p&gt;


&lt;h4&gt;5.11 Reboot&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo reboot&lt;/kbd&gt;&lt;/p&gt;


&lt;h3 id="guide_samba"&gt;6. Install and configure Samba&lt;/h3&gt;

&lt;h4&gt;6.1 Install Samba&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo apt-get update&lt;br&gt;
sudo apt-get install samba&lt;/kbd&gt;&lt;/p&gt;


&lt;h4&gt;6.2 Create Samba user&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo useradd -s /usr/sbin/nologin &lt;var&gt;SMBUSER&lt;/var&gt;&lt;br&gt;
sudo passwd &lt;var&gt;SMBUSER&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;when prompted for password, enter &lt;kbd&gt;&lt;var&gt;SMBPASS&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;sudo adduser &lt;var&gt;SMBUSER&lt;/var&gt; www-data&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo smbpasswd -a &lt;var&gt;SMBUSER&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;when prompted for password, enter &lt;kbd&gt;&lt;var&gt;SMBPASS&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;sudo smbpasswd -e &lt;var&gt;SMBUSER&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;


&lt;h4&gt;6.3 Add network share&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/samba/smb.conf&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Append these configuration lines to the end of the file:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;[www]&lt;br&gt;
&amp;nbsp;&amp;nbsp;comment = Web Data&lt;br&gt;
&amp;nbsp;&amp;nbsp;path = /var/www&lt;br&gt;
&amp;nbsp;&amp;nbsp;guest ok = no&lt;br&gt;
&amp;nbsp;&amp;nbsp;read only = no&lt;br&gt;
&amp;nbsp;&amp;nbsp;writeable = yes&lt;br&gt;
&amp;nbsp;&amp;nbsp;browseable = yes&lt;br&gt;
&amp;nbsp;&amp;nbsp;valid users = +www-data&lt;br&gt;
&amp;nbsp;&amp;nbsp;create mask = 0664&lt;br&gt;
&amp;nbsp;&amp;nbsp;directory mask = 0775&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo service smbd restart&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Validate your Samba configuration:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;testparm -s /etc/samba/smb.conf&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;normalized configuration file is printed; look for error messages&lt;/p&gt;


&lt;h4&gt;6.4 Test network share&lt;/h4&gt;

&lt;p&gt;Validate your network share:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;smbtree&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;look for &lt;code&gt;\\MINT-VM\www&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;From a Windows PC, connect to the Samba share:&lt;br&gt;
Start Menu &amp;rarr; Run &amp;rarr; &lt;kbd&gt;"\\mint-vm\www"&lt;/kbd&gt;&lt;br&gt;
Username:  &lt;kbd&gt;&lt;var&gt;SMBUSER&lt;/var&gt;&lt;/kbd&gt;&lt;br&gt;
Password:  &lt;kbd&gt;&lt;var&gt;SMBPASS&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;


&lt;h3 id="guide_vhosts"&gt;7. Configure Apache virtual hosts&lt;/h3&gt;

&lt;p&gt;* Configuration tested on Apache v2.4&lt;/p&gt;
&lt;p&gt;* For detailed reference, refer to the Apache documentation:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;&lt;a href="https://httpd.apache.org/docs/current/vhosts/name-based.html"&gt;Name-based Virtual Host Support&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="https://httpd.apache.org/docs/current/mod/core.html"&gt;Core Apache Configuration Directives&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;* Refer to section 4 in this guide to define a local hostname&lt;/p&gt;

&lt;p&gt;Enable multi-project hosting using Apache virtual hosts; this example configures 4 hosts: &lt;em&gt;delta&lt;/em&gt;, &lt;em&gt;gamma&lt;/em&gt;, &lt;em&gt;phpmyadmin&lt;/em&gt;, and a default host:&lt;/p&gt;
&lt;table&gt;
&lt;tbody&gt;
	&lt;tr&gt;&lt;td&gt;&lt;code&gt;delta.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/td&gt;
		&lt;td&gt;&lt;code&gt;/var/www/delta&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
	&lt;tr&gt;&lt;td&gt;&lt;code&gt;gamma.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/td&gt;
		&lt;td&gt;&lt;code&gt;/var/www/gamma&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
	&lt;tr&gt;&lt;td&gt;&lt;code&gt;phpmyadmin.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/td&gt;
		&lt;td&gt;&lt;code&gt;/usr/share/phpmyadmin&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
	&lt;tr&gt;&lt;td&gt;&lt;code&gt;&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/td&gt;
		&lt;td&gt;&lt;code&gt;/var/www/www&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Note that in this configuration the default host is served for &lt;strong&gt;any&lt;/strong&gt; request by IP address and &lt;strong&gt;any&lt;/strong&gt; request to an unknown hostname. This behaviour has security implications: if your server becomes publicly-accessible, your default host might be served.  Therefore we remove all admin-related content (such as phpMyAdmin) from this area.&lt;/p&gt;

&lt;p&gt;In this example we keep no content on the default host; all real content is served from specific subdomains.&lt;/p&gt;


&lt;h4&gt;7.1 Remove default host access to phpMyAdmin&lt;/h4&gt;

&lt;p&gt;Since phpMyAdmin will be accessed under its own subdomain, we should remove it from the default host; this allows us to explicitly control who can access it:&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/phpmyadmin/apache.conf&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Comment out (prepend a &lt;code&gt;#&lt;/code&gt; character) this &lt;code&gt;Alias&lt;/code&gt; line:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;#Alias /phpmyadmin /usr/share/phpmyadmin&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;phpMyAdmin can no longer be accessed from &lt;a href="http://localhost/phpmyadmin"&gt;&lt;code&gt;http://localhost/phpmyadmin&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h4&gt;7.2 Change the default host's server name and document root&lt;/h4&gt;

&lt;p&gt;By default Apache serves web files from &lt;code&gt;/var/www/html&lt;/code&gt;; we change this directory to better integrate with a multi-host setup:&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/apache2/sites-enabled/000-default.conf&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Insert the following line at the top of the file:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ServerName &lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This comment may be worth adding inside the &lt;code&gt;&amp;lt;VirtualHost&amp;gt;&lt;/code&gt; configuration block:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;# This first virtual host definition becomes the default.&lt;br&gt;
# Default is served for all requests not matching hosts below,&lt;br&gt;
# including requests by IP and requests for undefined hosts.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Add a &lt;code&gt;ServerAlias&lt;/code&gt; line inside the &lt;code&gt;&amp;lt;VirtualHost&amp;gt;&lt;/code&gt; configuration block:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ServerAlias www.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Change the &lt;code&gt;DocumentRoot&lt;/code&gt; line:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DocumentRoot "/var/www/www"&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;Delete the previous &lt;code&gt;html&lt;/code&gt; directory and create a new &lt;code&gt;www&lt;/code&gt; directory&lt;br&gt;
&lt;em&gt;&lt;strong&gt;WARNING:&lt;/strong&gt; all files in &lt;code&gt;/var/www/html&lt;/code&gt; will be deleted!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;rm -r /var/www/html&lt;br&gt;
mkdir /var/www/www&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Validate your configuration and restart the server:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;apache2ctl configtest&lt;br&gt;
sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;You may implement a website on the default host, however in this example setup we prefer to respond &lt;em&gt;&amp;ldquo;404 Not Found&amp;rdquo;&lt;/em&gt; to all requests that don't match an expected virtual host subdomain:&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;nano /var/www/www/index.php&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;?php&lt;br&gt;
&amp;nbsp;&amp;nbsp;http_response_code (404);&lt;br&gt;
&amp;nbsp;&amp;nbsp;header ('Content-Type: text/plain');&lt;br&gt;
&amp;nbsp;&amp;nbsp;echo '404 Not Found';&lt;br&gt;
&amp;nbsp;&amp;nbsp;die();&lt;br&gt;
?&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;Visiting any address that resolves to this server should now yield a 404 error, generated from &lt;code&gt;/var/www/www/index.php&lt;/code&gt;; for example:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;&lt;a href="http://localhost"&gt;&lt;code&gt;http://localhost&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://STATIC-IP"&gt;&lt;code&gt;http://&lt;var&gt;STATIC-IP&lt;/var&gt;&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://HOSTNAME"&gt;&lt;code&gt;http://&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://www.HOSTNAME"&gt;&lt;code&gt;http://www.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://any-undefined-subdomain.HOSTNAME"&gt;&lt;code&gt;http://any-undefined-subdomain.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h4&gt;7.3 Reenable phpMyAdmin under a virtual host&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/apache2/sites-enabled/000-default.conf&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Append the following configuration block to the &lt;em&gt;end&lt;/em&gt; of the file:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;VirtualHost *:80&amp;gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;DocumentRoot "/usr/share/phpmyadmin"&lt;br&gt;
&amp;nbsp;&amp;nbsp;ServerName phpmyadmin.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;ServerAlias *.phpmyadmin.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;br&gt;
&amp;lt;/VirtualHost&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;Validate your configuration and restart the server:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;apache2ctl configtest&lt;br&gt;
sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Visiting &lt;a href="http://phpmyadmin.HOSTNAME"&gt;&lt;code&gt;http://phpmyadmin.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/a&gt; now accesses phpMyAdmin&lt;/p&gt;


&lt;h4&gt;7.4 Add additional virtual hosts, as desired&lt;/h4&gt;

&lt;p&gt;This example sets up two projects, &lt;em&gt;delta&lt;/em&gt; and &lt;em&gt;gamma&lt;/em&gt;, each with their own hostname and server directory.  In your configuration, you would instead use these placeholders as examples to configure your own projects.&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;mkdir /var/www/delta&lt;br&gt;
mkdir /var/www/gamma&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/apache2/sites-enabled/000-default.conf&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Append the following configuration blocks to the end of the file:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;VirtualHost *:80&amp;gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;DocumentRoot "/var/www/delta"&lt;br&gt;
&amp;nbsp;&amp;nbsp;ServerName delta.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;ServerAlias *.delta.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;br&gt;
&amp;lt;/VirtualHost&amp;gt;&lt;br&gt;&lt;br&gt;

&amp;lt;VirtualHost *:80&amp;gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;DocumentRoot "/var/www/gamma"&lt;br&gt;
&amp;nbsp;&amp;nbsp;ServerName gamma.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;ServerAlias *.gamma.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;br&gt;
&amp;lt;/VirtualHost&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;Validate your configuration and restart the server:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;apache2ctl configtest&lt;br&gt;
sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Access these sites via &lt;a href="http://delta.HOSTNAME"&gt;&lt;code&gt;http://delta.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/a&gt; and &lt;a href="http://gamma.HOSTNAME"&gt;&lt;code&gt;http://gamma.&lt;var&gt;HOSTNAME&lt;/var&gt;&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3 id="guide_database"&gt;8. Create a database and limited SQL user&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Tip:&lt;/em&gt; Match the database name, username, and password of the database from your online hosting provider when configuring this local database.  Migrating projects between the two then involves only changing the host name of the database server.&lt;/p&gt;

&lt;p&gt;Execute this SQL code from the phpMyAdmin [SQL] tab to create a database:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CREATE DATABASE `&lt;var&gt;DBNAME&lt;/var&gt;` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Create a limited user who can only control this database:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CREATE USER '&lt;var&gt;DBUSER&lt;/var&gt;'@'%' IDENTIFIED WITH mysql_native_password AS '';&lt;br&gt;
&lt;br&gt;
GRANT USAGE ON *.* TO '&lt;var&gt;DBUSER&lt;/var&gt;'@'%' REQUIRE NONE;&lt;br&gt;
&lt;br&gt;
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER,&lt;br&gt;
CREATE TEMPORARY TABLES, CREATE VIEW, EVENT, TRIGGER, SHOW VIEW,&lt;br&gt;
CREATE ROUTINE, ALTER ROUTINE, EXECUTE ON `&lt;var&gt;DBNAME&lt;/var&gt;`.* TO '&lt;var&gt;DBUSER&lt;/var&gt;'@'%';&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;phpMyAdmin &amp;rarr; [Home] &amp;rarr; [User accounts] &amp;rarr; &lt;em&gt;(&lt;var&gt;DBUSER&lt;/var&gt;)&lt;/em&gt; &amp;rarr; [Edit privileges] &amp;rarr; [Change password]&lt;br&gt;
Assign a password &lt;var&gt;DBPASS&lt;/var&gt; to the database user&lt;/p&gt;

&lt;p&gt;From PHP, this database can now be accessed through &lt;code&gt;mysqli&lt;/code&gt; or equivalent:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$db = new mysqli ('localhost', '&lt;var&gt;DBUSER&lt;/var&gt;', '&lt;var&gt;DBPASS&lt;/var&gt;', '&lt;var&gt;DBNAME&lt;/var&gt;');&lt;/code&gt;&lt;/p&gt;


&lt;h3 id="guide_passwords"&gt;Appendix A: Changing passwords&lt;/h3&gt;

&lt;p&gt;If you need to change an account password after initial setup, use the following procedures:&lt;/p&gt;


&lt;h4&gt;A.1 Linux User&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo passwd &lt;var&gt;VMUSER&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo&lt;/code&gt; may ask for a password; use &lt;kbd&gt;&lt;var&gt;OLD-VMPASS&lt;/var&gt;&lt;/kbd&gt;&lt;br&gt;
when prompted for &lt;em&gt;new UNIX password&lt;/em&gt;, enter &lt;kbd&gt;&lt;var&gt;NEW-VMPASS&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo reboot&lt;/kbd&gt;&lt;/p&gt;


&lt;h4&gt;A.2 SQL User&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;mysqladmin --user=&lt;var&gt;SQLUSER&lt;/var&gt; --password=&lt;var&gt;OLD-SQLPASS&lt;/var&gt; password "&lt;var&gt;NEW-SQLPASS&lt;/var&gt;"&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/dbconfig-common/phpmyadmin.conf&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Edit the line containing &lt;code&gt;dbc_dbpass&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dbc_dbpass='&lt;var&gt;NEW-SQLPASS&lt;/var&gt;'&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo nano -w /etc/phpmyadmin/config-db.php&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;Edit the line containing &lt;code&gt;$dbpass&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$dbpass='&lt;var&gt;NEW-SQLPASS&lt;/var&gt;';&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ctrl+O, Enter (to save)&lt;br&gt;
Ctrl+X (to exit nano)&lt;/p&gt;

&lt;p&gt;&lt;kbd&gt;sudo service mysql restart&lt;br&gt;
sudo service apache2 restart&lt;/kbd&gt;&lt;/p&gt;


&lt;h4&gt;A.3 Samba User&lt;/h4&gt;

&lt;p&gt;&lt;kbd&gt;sudo passwd &lt;var&gt;SMBUSER&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo&lt;/code&gt; may ask for a password; use &lt;kbd&gt;&lt;var&gt;VMPASS&lt;/var&gt;&lt;/kbd&gt;&lt;br&gt;
when prompted for &lt;em&gt;new UNIX password&lt;/em&gt;, enter &lt;kbd&gt;&lt;var&gt;NEW-SMBPASS&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;sudo smbpasswd &lt;var&gt;SMBUSER&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;when prompted for password, enter &lt;kbd&gt;&lt;var&gt;NEW-SMBPASS&lt;/var&gt;&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;sudo service smbd restart&lt;/kbd&gt;&lt;/p&gt;

&lt;p&gt;On Windows you may need to flush all open network connections to use new credentials; run this from a command prompt:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;net use * /delete&lt;/kbd&gt;&lt;/p&gt;


&lt;h3 id="guide_usb"&gt;Appendix B: Add a USB expansion card &amp;amp; connect a thermal printer&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://screaming.computer/blog/2020-02/connect-a-thermal-receipt-printer-to-a-linux-vm-in-unraid"&gt;Connect a thermal receipt printer to a Linux VM in UnRAID&lt;/a&gt;:&lt;/strong&gt; this guide provides troubleshooting suggestions and solutions for connecting a USB printer to the VM. It also outlines how to add a USB expansion card for exclusive use of the virtual machine.&lt;/p&gt;


&lt;h3 id="guide_escpos"&gt;Appendix C: Install the escpos-php receipt printer library&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://screaming.computer/blog/2020-02/install-and-test-the-escpos-php-library"&gt;Install and test the &lt;code&gt;escpos-php&lt;/code&gt; library&lt;/a&gt;:&lt;/strong&gt; used for communicating with thermal receipt printers via the &lt;code&gt;ESC/POS&lt;/code&gt; protocol.&lt;/p&gt;

&lt;p&gt;Additionally, if your printer does not natively support printing &lt;a href="https://en.wikipedia.org/wiki/QR_code"&gt;QR codes&lt;/a&gt;, you can &lt;strong&gt;&lt;a href="https://screaming.computer/blog/2020-02/add-image-based-qr-code-support-to-escpos-php"&gt;add image-based QR code support&lt;/a&gt;&lt;/strong&gt; to the &lt;code&gt;escpos-php&lt;/code&gt; library by integrating the &lt;a href="http://phpqrcode.sourceforge.net/"&gt;&lt;code&gt;PhpQrCode&lt;/code&gt;&lt;/a&gt; library.&lt;/p&gt;


&lt;h3 id="guide_simplehtmldom"&gt;Appendix D: Install the PHP Simple HTML DOM Parser&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://screaming.computer/blog/2020-02/install-the-php-simple-html-dom-parser-for-easy-web-scraping"&gt;Install the &lt;code&gt;SimpleHtmlDom&lt;/code&gt; library&lt;/a&gt;:&lt;/strong&gt; This PHP library allows for exceptionally-easy web scraping and parsing of arbitrary HTML pages.&lt;/p&gt;


&lt;h3 id="guide_networkshare"&gt;Appendix E: Mount a network share for read/&lt;wbr&gt;write access&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://screaming.computer/blog/2020-03/mount-a-network-share-in-linux"&gt;Mount a network share in Linux&lt;/a&gt;:&lt;/strong&gt; This allows your VM to read from and write to shared folders on your network.&lt;/p&gt;


&lt;p&gt;&lt;em&gt;[end of guide]&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;&lt;!-- class=vmguide --&gt;
			</description>
			<guid isPermaLink="true">https://screaming.computer/blog/2020-01/set-up-a-lamp-server-vm-in-unraid</guid>
			<pubDate>Tue, 21 Jan 2020 23:35:00 GMT</pubDate>
			<source url="https://screaming.computer/blog/rss/">screaming.computer</source>
		</item>

		<item>
			<title>A Verse of Unknown Origin</title>
			<link>https://screaming.computer/blog/2020-01/a-verse-of-unknown-origin</link>
			<description>
&lt;div&gt;&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/A_WW7SeJ_JE" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;&lt;/div&gt;

&lt;p&gt;May everyone be happy.&lt;br&gt;
May everyone be free of disease.&lt;br&gt;
May auspiciousness be seen everywhere.&lt;br&gt;
May suffering belong to no one.&lt;br&gt;
Peace.&lt;/p&gt;
			</description>
			<guid isPermaLink="true">https://screaming.computer/blog/2020-01/a-verse-of-unknown-origin</guid>
			<pubDate>Sun, 05 Jan 2020 03:33:34 GMT</pubDate>
			<source url="https://screaming.computer/blog/rss/">screaming.computer</source>
		</item>

	</channel>
</rss>
