OERu Technology Blog - Making FOSS tech knowledge available to all. http://tech.oeru.org/ en Introducing the OERu Tech Blog http://tech.oeru.org/intro <span class="field field--name-title field--type-string field--label-hidden">Introducing the OERu Tech Blog</span> <div class="field field-node--field-blog-tags field-name-field-blog-tags field-type-entity-reference field-label-above"> <h3 class="field__label">Blog tags</h3> <div class="field__items"> <div class="field__item field__item--free--open-source"> <span class="field__item-wrapper"><a href="/taxonomy/term/6" hreflang="en">free &amp; open source</a></span> </div> <div class="field__item field__item--technology"> <span class="field__item-wrapper"><a href="/taxonomy/term/7" hreflang="en">technology</a></span> </div> <div class="field__item field__item--kanban"> <span class="field__item-wrapper"><a href="/taxonomy/term/8" hreflang="en">kanban</a></span> </div> <div class="field__item field__item--devops"> <span class="field__item-wrapper"><a href="/taxonomy/term/9" hreflang="en">devops</a></span> </div> <div class="field__item field__item--foss"> <span class="field__item-wrapper"><a href="/taxonomy/term/10" hreflang="en">foss</a></span> </div> </div> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><a title="View user profile." href="/user/1" lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="" class="username">dave</a></span> <span class="field field--name-created field--type-created field--label-hidden">Thu 08/09/2016 - 13:00</span> <div class="clearfix text-formatted field field-node--body field-name-body field-type-text-with-summary field-label-hidden"> <div class="field__items"> <div class="field__item"><p>The <a href="http://oeru.org">Open Education Resource universitas</a> (OERu) is an open organisation from top to bottom. Our entire technological infrastructure (with a <a href="/node/1" target="_blank">couple exceptions</a>) is built with and on Free and Open Source Software to which I normally refer as "FOSS".</p> <p>We are committed to using FOSS for our infrastructure because:</p> <ul><li>it is consistent with our "open" philosophy for education - if our educational materials and all our OERu planning processes are "open on principle", then delivering them on "closed" (or, more precisely, "proprietary") technologies would be a clanging bit of <a href="https://en.wikipedia.org/wiki/Cognitive_dissonance">cognitive dissonance</a>. There's even a word for it: "prefigurative" - use means that are consistent with your desired end.</li> <li>it allows us to use "best-of-breed" technologies (in the web-space, most innovation happens as FOSS first), combining many single-purpose solutions together in a modular way - via the open standards, open data formats, and open APIs they all support - to form a robust, feature-rich and secure communications and collaboration infrastructure. If a better solution to one of our requirements emerges, we can rapidly adapt our systems to adopt it, ensuring we are responsive and offer a fresh, state-of-the-art, well-supported and technically excellent platform.</li> <li>our partner institutions collaborators become familiar with these tools, and if they find any of them valuable, they can champion their adoption by their home institutions, helped by the instructions provided by this blog or our FOSS code repositories. The money saved and flexibility gained by our partner institutions by doing so are likely to be of substantially greater value than their annual OERu membership fees!</li> <li>Perhaps most importantly, none of our collaborators or learners are <em>ever</em> compelled to accept dubious (and usually exploitative) proprietary software Terms and Conditions to be part of our community. We respect our community's right to privacy and personal data sovereignty from the ground up.</li> </ul><h2>Physical Infrastructure</h2> <p>Our infrastructure is currently hosted on FOSS <a href="https://ubuntu.com">Ubuntu</a> and <a href="https://debian.org">Debian</a> Linux systems provides by four IaaS (Infrastructure as a Service) providers:</p> <ol><li>our <a href="https://www.mediawiki.org">MediaWiki</a> infrastructure, the star of which is <a href="https://wikieducator.org">Wikieducator.org</a>, is hosted on Amazon's "Elastic Compute" EC technology (located in the US, in our case), running Ubuntu Linux 14.04.</li> <li>the rest of our self-hosted infrastructure, hosted on a high-specification virtual machine, "Hetzner", also running Ubuntu Linux 14.04 (based in Germany). More on the many services provided by Hetzner below.</li> <li>more recently, we have created hosting for some of our new services on <a href="http://azure.com" title="Microsoft's Infrastructure-as-a-service Platform.">Azure</a>, taking advantage of an annual grant given to qualifying non-profit organisations by Microsoft. We run Ubuntu Linux 16.04 hosts there. We ensure we do not make use of any proprietary Azure capabilities in any of our automated scripts so that we can shift hosting providers with minimal cost and inconvenience if/when Microsoft changes their free-hosting policy.</li> <li>in 2018, we also received sponsored hosting capacity from NZ's own cloud service provider - whose cloud offering is entirely open source software built on open stack - <a href="https://catalystcloud.nz">Catalyst Cloud</a>. We host our <a href="https://git.oeru.org">code repositories</a> on their infrastructure. Many thanks to Catalyst!</li> </ol><p>Incidentally, the FOSS operating system, Linux, <a href="https://w3techs.com/technologies/details/os-unix/all/all">is the most widely used hosting platform on the Internet </a>today, and <a href="https://w3techs.com/blog/entry/ubuntu_became_the_most_popular_linux_distribution_for_web_servers">Ubuntu is the most widely used</a> "distribution" of Linux.</p> <h2>Externally hosted</h2> <p>We also make use of some externally hosted commercial FOSS services (we pay them for their services) to provide all the functionality we require:</p> <ul><li><a href="http://onlinegroups.net">OnlineGroups.Net</a> for <a href="http://groups.oeru.org">our family of mailing lists</a>.</li> <li><a href="https://mautic.net">Mautic</a> for our newsletter and user-engagement needs (Update 2017-06-16: due to substantial price increases for the hosted Mautic service, we are moving to a self-hosted version <a href="/installing-mautic-php7-fpm-docker-nginx-and-mariadb-ubuntu-1604">set up like this</a>).</li> <li>Update 2017-06-16: we have adopted a new open source Kanban planning tool, <a href="http://kanboard.net">Kanboard</a>, and we're supporting the developer by paying for the hosted service.</li> </ul><h2>Web Applications</h2> <p>We host and maintain a number of websites "that do stuff", otherwise known as web applications. These include:</p> <ul><li>Our <a href="https://course.oeru.org">Course website</a> which acts as a platform for per-course and per-cohort course websites, generated automatically via an OERu innovation: our <a href="/oeru-mediawiki-wordpress-snapshot-toolchain">course "snapshot" process</a> from learning materials formulated on Wikieducator that are transformed into fully-formed, partner-institution-branded websites. Built on the <a href="https://wordpress.org">WordPress</a> blog platform, running in "multisite" mode.</li> <li>Our main <a href="https://oeru.org">OERu Website</a> - which provides information about the organisation relevant to both learners and partners. It is built on the <a href="https://silverstripe.org">Silverstripe</a> Content Management System (CMS).</li> <li>This Technology Blog... which is built on the <a href="https://drupal.org">Drupal</a> CMS (version 8).</li> </ul><h2>Web Services</h2> <p>To maintain control and flexibility, we self-host a myriad of useful web-based resources and services. These include:</p> <ul><li>our <a href="https://cloud.oeru.org">data sharing/digital artefact-storage site</a>, comparable to having our own "Dropbox", is <a href="https://nextcloud.org">NextCloud</a>.</li> <li>our <a href="https://etherpad.oerfoundation.org">collaborative document editing</a> platform is <a href="https://github.com/ether/etherpad-lite">Etherpad-Lite</a>.</li> <li>our two "next generation" online forums, <a href="https://community.oeru.org">Community</a> (for educators and OERu collaborators) and <a href="https://forums.oeru.org">Forums</a> (for learners), built on <a href="http://www.discourse.org/">Discourse</a>.</li> <li>our <a href="https://chat.oeru.org">chat system</a>, a <a href="https://rocket.chat">Rocket.Chat</a> instance, similar to the proprietary Slack platform, replaces our venerable geeks-only platform, IRC (Internet Relay Chat).</li> <li>our <a href="https://plan.oeru.org">planning system</a>, an instance of the <a href="http://wekan.org">Wekan</a> "virtual <a href="https://en.wikipedia.org/wiki/Kanban_board">Kanban board</a>" (Update 2017-06-16: although we still use this a bit, we have found we prefer <a href="http://kanboard.net">Kanboard</a>)</li> <li>our <a href="https://oer.nz/admin">link shortening</a> service is an instance of <a href="http://yourls.org/">YourLS</a>.</li> <li>our <a href="https://mantis.oeru.org">issue tracking</a> service is an instance of <a href="https://www.mantisbt.org">Mantis Bug Tracker</a> (Update 2017-06-16: we've retired this as it wasn't quite the right fit for our users)</li> <li>our integrated <a href="https://links.oeru.org">link sharing "course resource bank"</a> is <a href="https://semanticscuttle.sourceforge.net">Semantic Scuttle</a>.</li> <li>our <a href="https://stats.oeru.org">website usage tracking</a> system built with <a href="http://piwik.org">Piwik</a>.</li> <li>Update 2017-06-16: we have recently set up a <a href="https://github.com/tootsuite/mastodon" title="The Mastodon Federated Social Network">Mastodon</a> instance to facilitate training our learners in the use of social networking without having to resort to a proprietary freedom-compromised platform. Here's <a href="/installing-mastodon-docker-compose-ubuntu-1604">how we did it</a>.</li> <li>Update 2017-06-16: we have a <a href="https://nextcloud.com" title="Open source, self-hosted web-based multi-tenented file store similar to Dropbox, but with more freedom.">NextCloud</a> instance, linked to a <a href="https://www.collaboraoffice.com/">Collabora Online office suite</a> instance, a concurrent editing application similar to Google Docs/Sheets. Howto coming soon!</li> <li>Update 2017-09-22: we have a <a href="https://limesurvey.org" title="Comprehensive open source online survey and polling tool.">Lime Survey</a> instance, replacing our use of Google Forms for conducting web-based surveys and polls.</li> </ul><h2>Code Repositories</h2> <p>We have a convention of documenting (including instructions, source code, and configuration examples) all of our individual implementations FOSS implementations. We have multiple projects for public reference stored at both Bitbucket (we have repositories in both the <a href="https://bitbucket.org/oerf/">Wikieducator</a> and the <a href="https://bitbucket.org/oerf/">OER Foundation</a> projects - Bitbucket is a source code "<a href="https://en.wikipedia.org/wiki/Forge_(software)">forge</a>" run by Atlassian in Australia) and<a href="github.com/oeru"> </a><a href="https://bitbucket.org/wikieducator/">Github</a><a href="github.com/oeru"> </a>(a forge run by Github in the US).</p> <p>Over time, a few of us will be writing up some blog posts on specific technologies to introduce them in a gentle way to those for whom terms like "git" and "pull request" do not yet have a respectable technology-related connotation.</p> <h2>Our Toolbox</h2> <p>To build and maintain our infrastructure, we use a cornucopia of additional FOSS tools. Among these are text editors, monitoring systems, backup tools, debugging environments, "<a href="https://en.wikipedia.org/wiki/DevOps">devops</a>" platforms, container technologies, and many others. We'll no doubt cover some of these in future blog posts.</p> <p>A couple noteworthy tools that all of our institutional partners should know about include:</p> <ul><li>we get all of our SSL (Secure Sockets Layer) certificates, that help keep our users' information secure and private by providing browser-to-server end-to-end encryption from <a href="https://letsencrypt.org/">Let's Encrypt</a>, at no cost. We encourage everyone else to do likewise! Here's our <a href="/protecting-your-users-lets-encrypt-ssl-certs">Let's Encrypt howto</a>.</li> <li><a href="https://en.wikipedia.org/wiki/OpenSSH">OpenSSH</a> (Secure SHell) - which ships with all Linux systems of which we're aware - it's the way we access all of our remote systems securely from anywhere.</li> </ul></div> </div> </div> <section class="field field-node--field-blog-comments field-name-field-blog-comments field-type-comment field-label-above comment-wrapper"> <a name="comments"></a> <div class="comment-form-wrapper"> <h2 class="comment-form__title">Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=2&amp;2=field_blog_comments&amp;3=comment" token="CdJY_SzH-b9OZT5uqHtipOPQseJjEJj5aRpmwEa4k8M"></drupal-render-placeholder> </div> </section> Thu, 08 Sep 2016 01:00:54 +0000 dave 2 at http://tech.oeru.org http://tech.oeru.org/intro#comments Join the Fediverse: installing Mastodon 4.0 on Ubuntu 22.04 with Docker Compose http://tech.oeru.org/join-fediverse-installing-mastodon-40-ubuntu-2204-docker-compose <span class="field field--name-title field--type-string field--label-hidden">Join the Fediverse: installing Mastodon 4.0 on Ubuntu 22.04 with Docker Compose</span> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><a title="View user profile." href="/user/1" lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="" class="username">dave</a></span> <span class="field field--name-created field--type-created field--label-hidden">Fri 04/11/2022 - 10:31</span> <div class="field field-node--field-image field-name-field-image field-type-image field-label-hidden has-multiple"> <figure class="field-type-image__figure image-count-1"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-11/Screenshot%202022-11-15%20at%2012-05-24%20FOSSDLE%20Social%20-%20Mastodon.png?itok=QBrCpIpr" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;A look at the front page of a (nearly) new Mastodon Instance with the default &#039;dark&#039; theme. In this case, it&#039;s https://social.fossdle.org...&quot;}" role="button" title="A look at the front page of a (nearly) new Mastodon Instance with the default &#039;dark&#039; theme. In this case, it&#039;s https://social.fossdle.org..." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;A look at the front page of a (nearly) new Mastodon Instance with the default &#039;dark&#039; theme. In this case, it&#039;s https://social.fossdle.org...&quot;}"><img src="/sites/default/files/styles/medium/public/2022-11/Screenshot%202022-11-15%20at%2012-05-24%20FOSSDLE%20Social%20-%20Mastodon.png?itok=JYOJOjky" width="220" height="169" alt="A look at the front page of a (nearly) new Mastodon Instance with the default &#039;dark&#039; theme. In this case, it&#039;s https://social.fossdle.org..." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-2"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-11/Screenshot%202022-11-15%20at%2012-02-12%20FOSSDLE%20Social%20-%20Mastodon.png?itok=6dlYxyFJ" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;A look at the front page of a (nearly) new Mastodon Instance, configured to use the &#039;light&#039; theme. In this case, it&#039;s https://social.fossdle.org...&quot;}" role="button" title="A look at the front page of a (nearly) new Mastodon Instance, configured to use the &#039;light&#039; theme. In this case, it&#039;s https://social.fossdle.org..." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;A look at the front page of a (nearly) new Mastodon Instance, configured to use the &#039;light&#039; theme. In this case, it&#039;s https://social.fossdle.org...&quot;}"><img src="/sites/default/files/styles/medium/public/2022-11/Screenshot%202022-11-15%20at%2012-02-12%20FOSSDLE%20Social%20-%20Mastodon.png?itok=eHKILHrW" width="220" height="170" alt="A look at the front page of a (nearly) new Mastodon Instance, configured to use the &#039;light&#039; theme. In this case, it&#039;s https://social.fossdle.org..." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-3"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-11/Screenshot%202022-11-15%20at%2012-18-29%20Dashboard%20-%20FOSSDLE%20Social%20-%20Mastodon.png?itok=PCAgBKBF" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;The Mastodon Admin dashboard (note, this is with the &#039;light&#039; theme already enabled on a nearly new instance)... &quot;}" role="button" title="The Mastodon Admin dashboard (note, this is with the &#039;light&#039; theme already enabled on a nearly new instance)... " data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;The Mastodon Admin dashboard (note, this is with the &#039;light&#039; theme already enabled on a nearly new instance)... &quot;}"><img src="/sites/default/files/styles/medium/public/2022-11/Screenshot%202022-11-15%20at%2012-18-29%20Dashboard%20-%20FOSSDLE%20Social%20-%20Mastodon.png?itok=IeeiQUff" width="135" height="220" alt="The Mastodon Admin dashboard (note, this is with the &#039;light&#039; theme already enabled on a nearly new instance)... " loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-4"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-11/Screenshot%202022-11-15%20at%2012-20-30%20Site%20settings%20-%20FOSSDLE%20Social%20-%20Mastodon.png?itok=SV_x03Rz" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;The Admin Settings page showing the settings we use on the social.fossdle.org site.&quot;}" role="button" title="The Admin Settings page showing the settings we use on the social.fossdle.org site." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;The Admin Settings page showing the settings we use on the social.fossdle.org site.&quot;}"><img src="/sites/default/files/styles/medium/public/2022-11/Screenshot%202022-11-15%20at%2012-20-30%20Site%20settings%20-%20FOSSDLE%20Social%20-%20Mastodon.png?itok=h9Q7qf_Q" width="79" height="220" alt="The Admin Settings page showing the settings we use on the social.fossdle.org site." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-5"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-11/Screenshot%202022-11-15%20at%2012-21-54%20Server%20rules%20-%20FOSSDLE%20Social%20-%20Mastodon.png?itok=uIM1EgdA" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;The &#039;house rules&#039; on the social.fossdle.org instance. &quot;}" role="button" title="The &#039;house rules&#039; on the social.fossdle.org instance. " data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;The &#039;house rules&#039; on the social.fossdle.org instance. &quot;}"><img src="/sites/default/files/styles/medium/public/2022-11/Screenshot%202022-11-15%20at%2012-21-54%20Server%20rules%20-%20FOSSDLE%20Social%20-%20Mastodon.png?itok=KypnXkLm" width="220" height="216" alt="The &#039;house rules&#039; on the social.fossdle.org instance. " loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-6"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-11/Screenshot%202022-11-15%20at%2012-27-03%20FOSSDLE%20Social%20-%20Mastodon.png?itok=fFWccbxW" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;This is the default Mastodon instance view (with the &#039;light&#039; theme enabled). &quot;}" role="button" title="This is the default Mastodon instance view (with the &#039;light&#039; theme enabled). " data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;This is the default Mastodon instance view (with the &#039;light&#039; theme enabled). &quot;}"><img src="/sites/default/files/styles/medium/public/2022-11/Screenshot%202022-11-15%20at%2012-27-03%20FOSSDLE%20Social%20-%20Mastodon.png?itok=HH1hi_Bs" width="188" height="220" alt="This is the default Mastodon instance view (with the &#039;light&#039; theme enabled). " loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-7"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-11/Screenshot%202022-11-15%20at%2012-23-34%20Appearance%20-%20FOSSDLE%20Social%20-%20Mastodon.png?itok=4YNlpOSK" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;The personal preferences page, where you can (dis|e)nable the &#039;advanced web interface&#039; as well as set your preferred theme - &#039;light&#039; for me in this case.&quot;}" role="button" title="The personal preferences page, where you can (dis|e)nable the &#039;advanced web interface&#039; as well as set your preferred theme - &#039;light&#039; for me in this case." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;The personal preferences page, where you can (dis|e)nable the &#039;advanced web interface&#039; as well as set your preferred theme - &#039;light&#039; for me in this case.&quot;}"><img src="/sites/default/files/styles/medium/public/2022-11/Screenshot%202022-11-15%20at%2012-23-34%20Appearance%20-%20FOSSDLE%20Social%20-%20Mastodon.png?itok=syh1t4BJ" width="199" height="220" alt="The personal preferences page, where you can (dis|e)nable the &#039;advanced web interface&#039; as well as set your preferred theme - &#039;light&#039; for me in this case." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-8"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-11/Screenshot%202022-11-15%20at%2012-25-20%20FOSSDLE%20Social%20-%20Mastodon.png?itok=38wCJ2nA" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;My view of the &#039;advanced&#039; Mastodon interface. &quot;}" role="button" title="My view of the &#039;advanced&#039; Mastodon interface. " data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;My view of the &#039;advanced&#039; Mastodon interface. &quot;}"><img src="/sites/default/files/styles/medium/public/2022-11/Screenshot%202022-11-15%20at%2012-25-20%20FOSSDLE%20Social%20-%20Mastodon.png?itok=mdWbu_5f" width="220" height="185" alt="My view of the &#039;advanced&#039; Mastodon interface. " loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-9"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-11/Screenshot%202022-11-15%20at%2012-36-26%20Explore%20FOSSDLE%20Social%20-%20Mastodon.png?itok=hRPyCvJz" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;The current &#039;profile directory&#039; of publicly listed users on social.fossdle.org.&quot;}" role="button" title="The current &#039;profile directory&#039; of publicly listed users on social.fossdle.org." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;The current &#039;profile directory&#039; of publicly listed users on social.fossdle.org.&quot;}"><img src="/sites/default/files/styles/medium/public/2022-11/Screenshot%202022-11-15%20at%2012-36-26%20Explore%20FOSSDLE%20Social%20-%20Mastodon.png?itok=U23oYyXu" width="188" height="220" alt="The current &#039;profile directory&#039; of publicly listed users on social.fossdle.org." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> </div> <div class="clearfix text-formatted field field-node--body field-name-body field-type-text-with-summary field-label-hidden"> <div class="field__items"> <div class="field__item"><p>In the past week or two, with Elon Musk completing his purchase and take-over of Twitter, there has been a torrent of defections from the centralised, closed, for-profit platform to its antithesis, the <a href="https://en.wikipedia.org/wiki/Fediverse">Fediverse</a> - it's a portmanteau of "federation" and "universe". The Fediverse, in short, is a <a href="https://fediverse.party">set of Free and Open Source Software (FOSS) applications</a> created by different quite independent communities of developers, sharing one thing in common: the (somewhat magical) <a href="https://activitypub.rocks/">ActivityPub</a> <a href="https://www.w3.org/TR/activitypub/">open standard</a> for data characterisation and exchange. They are deployed independently by interested individuals and communities to form a 'decentralised network' of ActivityPub-enabled services (<a href="https://fediverse.info/">here's a quick explanation video</a>). Of these many applications (with new ones popping up every day!), probably the most ambitious and widely known is <a href="https://joinmastodon.org">Mastodon</a> (which also features <a href="https://peertube.togart.de/videos/watch/7a087e5f-68e0-4359-8035-bfd9752e9b2e">a short video explanation</a>).</p> <p>For those not (yet) familiar with it, Mastodon is a 'micro-blogging' platform superficially somewhat similar to Twitter, but different in many important details. It is also a collection of FOSS applications, notably an 'instance', aka a server (the subject of this how-to), and a variety of 'clients' that support people interacting with one or more instances. A user installs a client on their phone or desktop, or simply uses a web browser, opening the instance's web address (URL) in a tab. Either the tab or the client becomes their portal for engaging with the Mastodon instance and the various ActivityPub streams the user selects to see via that instance. That selection is done via 'following' other users on the same or different instances, whether those instances are also Mastodon or one of the other Fediverse applications (<a href="https://fediverse.info/explore/projects">here's a fairly complete list</a>). Users can further 'shape' their feeds by employing filters selecting or eliminating those containing specific 'hashtags' (a hashtag is just a word - or CamelCaseCollectionOfWords - in a post or 'toot' that has a '#' hash symbol at the front of it) and/or by blocking/muting specific other Fediverse participants whose contributions they prefer to avoid.</p> <p>We, here at the OER Foundation, have been running Mastodon for 6 years now, but the sudden spike in interest and changes to the various dependent technologies since our <a href="/node/14">previous Mastodon howto</a> have motivated me to create this streamlined, updated version. We're very keen to see <em>lots</em> of individual academic institutions running Mastodon instances on behalf of their learners and broader educational communities! The cost is fairly trivial (USD30-50/month) for an instance with a thousand or two users, especially for an institution.</p> <ul class="table-of-contents"><li> <p><a href="#tips-for-this-tutorial">Tips for this tutorial</a></p> </li> <li> <p><a href="#create-a-virtual-private-server">Create a Virtual Private Server</a></p> <ul><li> <p><a href="#vps-properties">VPS Properties:</a></p> </li> </ul></li> <li> <p><a href="#key-variables-for-you-mastodon">Key variables for you Mastodon</a></p> <ul><li> <p><a href="#get-your-domain-lined-up">Get your Domain lined up</a></p> </li> <li> <p><a href="#set-up-an-unprivileged-user-for-yourself">Set up an unprivileged user for yourself</a></p> </li> </ul></li> <li> <p><a href="#configure-the-vps">Configure the VPS</a></p> <ul><li> <p><a href="#configuring-your-firewall">Configuring your firewall</a></p> </li> <li> <p><a href="#install-the-nginx">Install the NGINX</a></p> </li> </ul></li> <li> <p><a href="#outgoing-vps-email">Outgoing VPS Email</a></p> </li> <li> <p><a href="#docker-compose-and-lets-encrypt">Docker Compose and Let's Encrypt</a></p> </li> <li> <p><a href="#install-the-mastodon-docker-recipe">Install the Mastodon Docker recipe</a></p> </li> <li> <p><a href="#nginx-reverse-proxy-configuration">NGINX reverse proxy configuration</a></p> </li> <li> <p><a href="#build-your-mastodon">Build your Mastodon</a></p> </li> <li> <p><a href="#create-your-admin-user">Create your Admin user</a></p> </li> <li> <p><a href="#basic-instance-configuration">Basic Instance configuration</a></p> </li> <li> <p><a href="#other-considerations">Other considerations</a></p> <ul><li> <p><a href="#backing-up-the-mastodon-database">Backing up the Mastodon Database</a></p> </li> <li> <p><a href="#backing-up-your-vps-files-and-configurations-incuding-your-mastodon">Backing up your VPS' files and configurations, incuding your Mastodon</a></p> </li> <li> <p><a href="#upgrading-your-mastodon-instance-to-newer-versions">Upgrading your Mastodon instance to newer versions</a></p> </li> </ul></li> </ul><h2><a id="user-content-tips-for-this-tutorial" href="#tips-for-this-tutorial" name="tips-for-this-tutorial" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Tips for this tutorial</h2> <p>This tutorial is aimed at adventuresome would-be system administrators. I endeavour not to assume any specialised knowledge on your part, and try to provide useful tips and exposition along the way to help you build a valid mental model of what you're doing. At the same, this is not a trivial process. Luckily, if you try it out, and decide not to follow through, so long as you <em>delete your VPS</em>, you should not be out-of-pocket by more than a few cents.</p> <p>If this is your first attempt at 'self-hosting', and you <em>do</em> follow through, this could be the start of a new era in your technical status - you could realised that 'agency' you always wanted. Plus, I'll be very impressed by your esprit de corps and tenancity!</p> <p>With this tutorial, I'm assuming you've got a computer with an Internet connection, that can run SSH (all modern systems should do that) and you can copy-and-paste stuff from this tutorial (in your browser) into either a terminal window (in which you're SSH'd into your VPS) or into a text editor. Note, if you find it difficult to paste into a terminal window, try using CTRL+SHIFT+V (CTRL+V is already used as a short-cut for something else in UNIX terminals since long before the Windows world started using CTRL+C and CTRL+V).</p> <p>When I provide files you need to copy, look for the placeholders with values you need to substitute (search-and-replace) in square brackets - [] - with your own values. I assume you'll be able to do that in a text editor on your desktop.</p> <h2><a id="user-content-create-a-virtual-private-server" href="#create-a-virtual-private-server" name="create-a-virtual-private-server" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Create a Virtual Private Server</h2> <p>The first step is to create a place to host the Mastodon instance. You can run it on a local piece of hardware of sufficient capacity, but make sure you've got a <em>fast</em> and symmetrical (as fast to upload as to download!) connection. If (as with most residential Internet services) your upload is much slower than your download (often 1:10 ratio) your server is going to be very slow for external people, especially if streaming video. Also, don't undertake this unless you have a flat-rate data connection.</p> <p>The more cost-effective approach in our experience, is to secure a low cost commodity Linux Virtual Private Server running Ubuntu Linux 22.04 (the latest "Long Term Support" version). That's what we'll assume you're running for this tutorial. We have used quite a few Linux VPSs commodity providers. Known good options are Digital Ocean (who recently raised their prices significantly), Linode, Vultr, Hetzner, and TurnkeyLinux. There are many (hundreds) of other credible options. We recommend you find one hosted in the network epicentre (which isn't necessarily the same as the 'geographic' epicentre) of your audience.</p> <p>If you have trouble getting a VPS, you might find <a href="https://vimeo.com/684028258">this video</a> I created for provisioning a VPS, using Digital Ocean as an example. In my experience, the process for provisioning VPSs on other platforms is very similar. You'll find this process <em>much</em> easier than using either Microsoft Azure or Amazon AWS, which we do not recommend. Their systems are unnecessarily complex, proprietary (they will lock you in), and about 10 times more expensive than commodity hosting options.</p> <h3><a id="user-content-vps-properties" href="#vps-properties" name="vps-properties" class="heading-permalink" aria-hidden="true" title="Permalink"></a>VPS Properties:</h3> <p>We recommend that, for a Mastodon instance of modest size (say up to a few hundred active users) you provision a VPS with the following spec (go as high as you can afford to). You should be able to upgrade those specs in realtime if required, except for your disk space. You can, however, provision a secondary storage space (you can start small and increase it as you need to). I will cover setting this up, as it'll make your life far far easier in the medium-long term.</p> <ul><li>8-16 GB RAM (a small instance can be run on 4GB RAM, so you could start with that, as RAM can be upgraded easily)</li> <li>2-4 Virtual CPUs</li> <li>80-160 GB Disk space (NVME disk is faster than SSD which is faster than spinning disk space)</li> <li>running Ubuntu Linux 22.04 (the current Long Term Support version)</li> <li>extra storage - 20-40GB extra space (can be expanded on fairly short notice)</li> </ul><p>You'll need to create an account for yourself on your chosen hosting provider (it's a good idea to use Two Factor Authentication, aka 2FA, on your hosting account so that no one can log in as you and, say, delete your server unexpectedly - you'll find instructions on how to set up 2FA on your hosting provider's site) and create an Ubuntu @2.04 (or the most recent 'Long Term Support' (LTS) version) - 24.04 is likely to come out in April 2024) in the 'zone' nearest to you (or your primary audience, if that's different).</p> <p>If you don't already have an SSH key on your computer, I encourage you to <a href="https://support.atlassian.com/bitbucket-cloud/docs/set-up-an-ssh-key/">create one</a> and specify the <strong>public key</strong> in the process of creating your server - specifying the 'public key' of your SSH identity during the server creation process that should allow you to log in without needing a password!</p> <p>You'll need to note the server's <strong>IPv4</strong> address (it'll be a series of 4 numbers, 0-254, separated by full stops, e.g. 103.99.72.244), and you should also be aware that your server will have a newer <strong>IPv6</strong> address, which will be a set of 8 four <em>hex character</em> values (each hex character can have one of 16 values: 0-9,A-F) separated by colons, e.g. 2604:A880:0002:00D0:0000:0000:20DE:9001. With one or the other of those IPs, you should be able to <a href="https://www.digitalocean.com/community/tutorials/how-to-use-ssh-to-connect-to-a-remote-server-in-ubuntu">log into your new server via SSH</a>. If you're on a UNIX command line (e.g. a Linux or MacOS desktop), do this in a terminal. On Windows, I understand people use a tool called Putty for SSH, in which case follow the app's instructions.</p> <p><code>ssh [your server IPv4 or IPv6]</code></p> <p>followed by the ENTER key (that'll be true for any line of commands I provide).</p> <p>In some cases, depending on your hosting provider, you'll have a password to enter, or if you've specified your pre-existing public SSH key, you shouldn't need to enter a password at all, you should be logged in. To check what user you care, you can type</p> <p><code>whoami</code></p> <p>If it returns <code>root</code> (there's also a convention of using a '#' as the command prompt), you're the root or super-admin of the server. If not, you're a normal user (some hosting providers have a convention of giving you a default user of "ubuntu" user perhaps "debian") with a prompt that is, by convention a '$'.</p> <p>Now that you're logged in, it's worth doing an upgrade of your server's Ubuntu system! Do that as follows:</p> <p><code>sudo apt-get update &amp;&amp; sudo apt-get dist-upgrade</code></p> <p>Usually the user, even if it's not the root user, will have the ability to use the <code>sudo</code> command modifier - that means "do this action as the root (aka the 'Super User', thus 'su' for short) user" - you may be asked to enter your password as a security precaution the first time you run a command prefaced by <code>sudo</code>. Enter it, and it should run the command. Plus, the system shouldn't bother you for it again unless you leave your terminal unused for a while and come back to it.</p> <p>At this point, I also like to install some cool software called 'etckeeper' which records configuration changes on your VPS for future reference (and recovering from administrative mess-ups):</p> <p><code>sudo apt-get install etckeeper</code></p> <p>which will also install some dependencies, including the very important (and relevant later on) 'git' version control system.</p> <h2><a id="user-content-key-variables-for-you-mastodon" href="#key-variables-for-you-mastodon" name="key-variables-for-you-mastodon" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Key variables for you Mastodon</h2> <p>To set up you Mastodon, you'll need a few crucial bits of information related to your system's identity and external systems you'll need it to interact with. For example, as mentioned before, you'll need a domain name. For the rest of this tutorial, we'll use the convention of representing those variables as a name inside [], or, for the domain name you've picked, [domain name].</p> <p>Here's a list of variables you'll need to know to complete the rest of this tutorial:</p> <p>[ipv4] and [ipv6] - your VPS' IPv4 and IPv6 addresses (the latter can be ignored if your cloud provider doesn't support IPv6 addresses) as described above. [domain name] - the fully qualified domain name or subdomain by which you want your WPMS to be accessed. You must have full domain management ability on this domain. Example: mastodon.oeru.org - that's the mastodon subdomain of the oeru.org domain. Authenticating SMTP details - this is required so your web service can send emails to users - crucial things like email address validation and password recovery emails... [smtp server] - the domain name or IPv4 or IPv6 address of an SMTP server [smtp port] - the port number on the server that is listening for your connection. By convention it's likely to be 465 or 587, or possibly 25. [smtp reply-to-email] - a monitored email to which people can send email related to this WordPress site, e.g. notifications@[domain name] [smtp user] - the username (often an email address) used to authenticate against your SMTP server, provided by your email provider. [smtp password] - the accompanying password, provided by your email provider. [your email] - an email address to which system-related emails can be sent to you, perhaps something like webmaster@[domain name]. [vps username] - the username you use on your server (by convention, these are one word, and all lower case). [mastodon username]</p> <h3><a id="user-content-get-your-domain-lined-up" href="#get-your-domain-lined-up" name="get-your-domain-lined-up" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Get your Domain lined up</h3> <p>You will want to have a domain to point at your server, so you don't have to remember the IP number. There're are thousands of domain "registrars" in the world who'll help you do that... You just need to "register" a name, and you pay yearly fee (usually between USD10-30 depending on the country and the "TLD" (Top Level Domain. There're national ones like .nz, .au, .uk, .tv, .sa, .za, etc., or international domains (mostly associated with the US) like .com, .org, .net, and a myriad of others. Countries decide on how much their domains wholesale for and registrars add a margin for the registration service).</p> <p>Here in NZ, I use the services of Metaname (they're local to me in Christchurch, and I know them personally and trust their technical capabilities). If you're not sure who to use, ask your friends. Someone's bound to have recommendations (either positive or negative, in which case you'll know who to avoid).</p> <p>Once you have selected and registered your domain, you can 'manage your Zone' to set up (usually through a web interface provided by the registrar) an <strong>A Record</strong> which associates your website's name to the <strong>IPv4</strong> address of your server. So you should just be able to enter your server's IPv4 address, the domain name (or sub-domain) you want to use for the web service you want to set up.</p> <p>Nowadays, <em>if your Domain Name host offers it (some don't, meaning you might be better off with a different one),</em> it's also important to define an <strong>IPv6</strong> record, which is called an <strong>AAAA Record</strong>... you put in your IPv6 address instead of your IPv4 one.</p> <p>You might be asked to set a "Time-to-live" (which has to do with the length of time Domain Name Servers are asked to "cache" the association that the A Record specifies) in which case you can put in 3600 seconds or an hour depending on the time units your registrar's interface requests... but in most cases that'll be set to a default of an hour automatically.</p> <h3><a id="user-content-set-up-an-unprivileged-user-for-yourself" href="#set-up-an-unprivileged-user-for-yourself" name="set-up-an-unprivileged-user-for-yourself" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Set up an unprivileged user for yourself</h3> <p>You should be able to test that your A and AAAA Records have been set correctly by logging into your server via SSH using your domain name rather than the IPv4 or IPv6 address you used previously. It should (after you accept the SSH warning that the server's name has a new name) work the same way your original SSH login did.</p> <p>This will log you into your server as it did the first time, either as 'root' or the default unprivileged user. It's not considered good practice to access your server as root (it's too easy to completely screw it up by accident). Either way, best practice is to create your own separate 'non-root' user who has 'sudo' privileges and the ability to log in via SSH. If you are <em>currently logged in as 'root'</em>, you can create a normal user for yourself via (replace [vps username] with your chosen username - in my case, I'd use <code>U=dave</code>):</p> <p><code>U=[vps username]</code><br /><code>adduser $U</code><br /><code>adduser $U ssh</code><br /><code>adduser $U admin</code><br /><code>adduser $U sudo</code></p> <p>You'll also want to a set a password for user [vps username] (we have a tutorial on <a href="/node/43">creating good passwords</a>):</p> <p><code>passwd $U</code></p> <p>then become that user temporarily (note, the root user can 'become' another user without needing to enter a password) and create an SSH key and, in the process, the <code>.ssh</code> directory (directories starting with a '.' are normally 'hidden' - you can show them in a directory listing via <code>ls -a</code>) for the file into which to put your public SSH key:</p> <p><code>su $U</code><br /><code>ssh-keygen -t rsa -b 2048</code><br /><code>nano ~/.ssh/authorized_keys</code></p> <p>and in that file, copy and paste (without spaces on either end) your <em>current computer's</em> <strong>public</strong> ssh key (<em>never publish</em> your private key anywhere!), save and close the file.</p> <p>From that point, you should be able to SSH to your server via <code>ssh [vps username]@[domain name]</code> without needing to enter a password.</p> <p>These instructions use 'sudo' in front of commands because I assume you're using a non-root user. The instructions will still work fine even if you're logged in as 'root' (the 'sudo' will be ignored).</p> <h2><a id="user-content-configure-the-vps" href="#configure-the-vps" name="configure-the-vps" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Configure the VPS</h2> <p>First things first. Let's make sure you've got the time zone set appropriately for your instance. It'll probably default to 'UTC' (Greenwich Mean Time). For our servers, I tend to pick 'Pacific/Auckland' which is our time zone. Run this</p> <p><code>sudo dpkg-reconfigure tzdata</code></p> <p>and pick the appropriate timezone. You can just leave it running UTC, but you might find it tricky down the track if you're looking at logs and having to constantly convert the times into your timezone.</p> <p>In the rest of this tutorial, we're going to be editing quite a few files via the command line. If you're new to this, I recommend using the 'nano' text editor which is installed by default on Ubuntu Linux systems. It's fairly simple, and all of its options are visible in the text-based interface. I tend to use a far more powerful but far less beginner-friendly editor called 'vim'. There're other editors people might choose, too. To use your preferred editor for the rest of the tutorial, enter the following to set an environment variable EDIT, specifying your preferred editor, e.g.:</p> <p><code>EDIT=</code>which nano``</p> <p>or, if you're like me</p> <p><code>EDIT=</code>which vim``</p> <p>so that subsequent references to $EDIT will invoke your preferred editor. Note the command <code>which nano</code> is a script which finds the full path to the named command, in this case 'nano'. Putting it inside the backquotes `` means 'replace with the value the script returns', so it sets the value of EDIT to the path of the nano command.</p> <h3><a id="user-content-configuring-your-firewall" href="#configuring-your-firewall" name="configuring-your-firewall" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Configuring your firewall</h3> <p>Let's configure our firewall. We work on the basis of allowing in <em>only</em> what we want to let in (i.e. 'default deny').</p> <p>First we'll enable the use of SSH through the firewall (<em>not doing this could lock us out of your machine!</em>)</p> <p><code>sudo ufw allow ssh</code><br /></p> <p>while we're here, we'll also enable data transfer from the internal (to the VPS) Docker virtual network and the IP range it uses for Docker containers:</p> <p><code>sudo ufw allow in on docker0</code><br /><code>sudo ufw allow from 172.0.0.0/8 to any</code></p> <p>Then we'll enable forwarding from internal network interfaces as required for Docker containers to be able to talk to the outside world:</p> <p><code>sudo $EDIT /etc/default/ufw</code></p> <p>and copy the line <code>DEFAULT_FORWARD_POLICY="DROP"</code> tweak it to look like this (commenting out the default, but leaving it there for future reference!):</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co0">#DEFAULT_FORWARD_POLICY="DROP"</span> <span class="re2">DEFAULT_FORWARD_POLICY</span>=<span class="st0">"ACCEPT"</span></pre></div></div> <p>and then save and exit the file (CTRL-X and then 'Y' if your editor is nano).</p> <p>You also have to edit <code>/etc/ufw/sysctl.conf</code> and remove the "#" at the start of the following lines, so they look like this:</p> <p><code>sudo $EDIT /etc/ufw/sysctl.conf</code></p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co0"># Uncomment this to allow this host to route packets between interfaces</span> net<span class="sy0">/</span>ipv4<span class="sy0">/</span><span class="re2">ip_forward</span>=<span class="nu0">1</span> net<span class="sy0">/</span>ipv6<span class="sy0">/</span>conf<span class="sy0">/</span>default<span class="sy0">/</span><span class="re2">forwarding</span>=<span class="nu0">1</span> net<span class="sy0">/</span>ipv6<span class="sy0">/</span>conf<span class="sy0">/</span>all<span class="sy0">/</span><span class="re2">forwarding</span>=<span class="nu0">1</span></pre></div></div> <p>Then we need to restart the network stack to apply that configuration change</p> <p><code>sudo systemctl restart systemd-networkd</code></p> <p>Next we have to enable the UFW firewall to start at boot time.</p> <p><code>sudo $EDIT /etc/ufw/ufw.conf</code></p> <p>And set the ENABLED variable near the top:</p> <p><code>ENABLED=yes</code></p> <h3><a id="user-content-install-the-nginx" href="#install-the-nginx" name="install-the-nginx" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Install the NGINX</h3> <p>Next we need to install the NGINX webserver and reverse-proxy, as well as the Let's Encrypt SSL certificate generator, both of which are crucial for any secure webservices you might want to host. NGINX is a more efficient and flexible alternative to the older Apache webserver you might've seen elsewhere.</p> <p><code>sudo apt-get install nginx-full letsencrypt</code></p> <p>Having installed it, we need to create firewall rules to allow external services to see it:</p> <p><code>sudo ufw allow 'Nginx Full'</code></p> <p>You can check if the firewall rules you requested have been enabled:</p> <p><code>sudo ufw status</code></p> <p>Finally, we'll turn UFW on with all of our rules in place:</p> <p><code>sudo ufw enable</code></p> <h2><a id="user-content-outgoing-vps-email" href="#outgoing-vps-email" name="outgoing-vps-email" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Outgoing VPS Email</h2> <p>It's very useful for your server to be able to send out emails, like status emails to administrators (perhaps you) about things requiring their attention, e.g. the status of backups, pending security updates, expiring SSL certificates, etc. To do this, we'll set up the industrial strength Postfix SMTP server, which is pretty quick and easy. First we install Postfix.</p> <p><code>sudo apt-get install postfix bsd-mailx</code></p> <p>During the install, you'll be asked to select a bunch of configuration parameters. Select the defaults except:</p> <ul><li>Select "Internet Site with Smarthost",</li> <li>fill in the domain name for your server [domain name],</li> <li>the [smtp server] name and [smtp port] (in the form [smtp server]:[smtp port], e.g. smtp.oeru.org:587 ) of your "smarthost" who'll be doing the authenticating SMTP for you, and</li> <li>the email address to which you want to receive system-related messages, [your email].</li> </ul><p>After that's done, we set a default address for the server to mail to, to [your email] selected above. First</p> <p><code>sudo $EDIT /etc/aliases</code></p> <p>We need to make sure the "root" user points to a real email address. Add a line at the bottom which says (replacing [your email] with your email :) )</p> <p><code>root: [your email]</code></p> <p>After which you'll need to convert the aliases file into a form that postfix can process, simply by running this:</p> <p><code>sudo newaliases</code></p> <p>Then we have to define the authentication credentials required to convince your mail server that you're you!</p> <p><code>sudo $EDIT /etc/postfix/relay_password</code></p> <p>and enter a single line in this format:</p> <p><code>[smtp server] [smtp user]:[smtp password]</code></p> <p>as an example, this is more or less what I've got for my system. Note that the [smtp user] in my case is an email address (this is common with many smtp system - the user is the same as the email address):</p> <p><code>smtp.oerfoundation.org smtp-work@fossdle.org:SomeObscurePassw0rd</code></p> <p>then save the file and, like the aliases file, run the conversion process (which uses a slightly different mechanism):</p> <p><code>sudo postmap /etc/postfix/relay_password</code></p> <p>Finally, we'll edit the main configuration file for Postfix to tell it about all this stuff:</p> <p>sudo $EDIT /etc/postfix/main.cf</p> <p>If your SMTP server uses port 25 (the default for unencrypted SMTP) you don't have to change anything, although most people nowadays prefer to use StartTLS or otherwise encrypted transport to at least ensure that your SMTP authentication details (at least) are transferred encrypted. That means using port 587 or 465. If you're using either of those ports, find the "relayhost = [your server name]" line... and add your port number after a colon, like this</p> <p><code>relayhost = [smtp server]:[smtp port]</code></p> <p>or, for example:</p> <p><code>relayhost = smtp.oerfoundation.org:465</code></p> <p>Then we have to update the configuration for Postfix to ensure that it knows about the details we've just defined (this command will automatically back up the original default configuration so you can start from scratch with the template below):</p> <p><code>sudo mv /etc/postfix/main.cf /etc/postfix/main.cf.orig &amp;&amp; sudo $EDIT /etc/postfix/main.cf</code></p> <p>You can just copy-and-paste the following into it, substituting your specific values for the [tokens].</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co0"># See /usr/share/postfix/main.cf.dist for a commented, more complete version</span>   <span class="co0"># Debian specific: Specifying a file name will cause the first</span> <span class="co0"># line of that file to be used as the name. The Debian default</span> <span class="co0"># is /etc/mailname.</span> <span class="co0">#myorigin = /etc/mailname</span>   smtpd_banner = <span class="re1">$myhostname</span> ESMTP <span class="re1">$mail_name</span> <span class="br0">(</span>Ubuntu<span class="br0">)</span> biff = no   <span class="co0"># appending .domain is the MUA's job.</span> append_dot_mydomain = no   <span class="co0"># Uncomment the next line to generate "delayed mail" warnings</span> <span class="co0">#delay_warning_time = 4h</span>   readme_directory = no   <span class="co0"># See http://www.postfix.org/COMPATIBILITY_README.html -- default to 3.6 on</span> <span class="co0"># fresh installs.</span> compatibility_level = <span class="nu0">3.6</span>   <span class="co0"># TLS parameters</span> <span class="re2">smtpd_tls_cert_file</span>=<span class="sy0">/</span>etc<span class="sy0">/</span>ssl<span class="sy0">/</span>certs<span class="sy0">/</span>ssl-cert-snakeoil.pem <span class="re2">smtpd_tls_key_file</span>=<span class="sy0">/</span>etc<span class="sy0">/</span>ssl<span class="sy0">/</span>private<span class="sy0">/</span>ssl-cert-snakeoil.key <span class="re2">smtpd_tls_security_level</span>=may <span class="re2">smtp_tls_CApath</span>=<span class="sy0">/</span>etc<span class="sy0">/</span>ssl<span class="sy0">/</span>certs <span class="co0">#smtp_tls_security_level=may</span> smtp_tls_session_cache_database = btree:<span class="co1">${data_directory}</span><span class="sy0">/</span>smtp_scache smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination myhostname = <span class="br0">[</span>domain name<span class="br0">]</span> alias_maps = hash:<span class="sy0">/</span>etc<span class="sy0">/</span>aliases alias_database = hash:<span class="sy0">/</span>etc<span class="sy0">/</span>aliases myorigin = <span class="sy0">/</span>etc<span class="sy0">/</span>mailname mydestination = <span class="re1">$myhostname</span>, work.fossdle.org, localhost.fossdle.org, localhost relayhost = <span class="br0">[</span>smtp server<span class="br0">]</span>:<span class="br0">[</span>smtp port<span class="br0">]</span> mynetworks = 127.0.0.0<span class="sy0">/</span><span class="nu0">8</span> <span class="br0">[</span>::ffff:127.0.0.0<span class="br0">]</span><span class="sy0">/</span><span class="nu0">104</span> <span class="br0">[</span>::<span class="nu0">1</span><span class="br0">]</span><span class="sy0">/</span><span class="nu0">128</span> mailbox_size_limit = <span class="nu0">0</span> recipient_delimiter = + inet_interfaces = all inet_protocols = all   <span class="co0"># added to configure accessing the relay host via authenticating SMTP</span> smtp_sasl_auth_enable = <span class="kw2">yes</span> smtp_sasl_password_maps = hash:<span class="sy0">/</span>etc<span class="sy0">/</span>postfix<span class="sy0">/</span>relay_password smtp_sasl_security_options = noanonymous smtp_tls_security_level = encrypt   <span class="co0"># if you're using Ubuntu prior to 20.04, uncomment (remove the #) the</span> <span class="co0"># earlier line smtp_tls_security_level = may to save errors in 'postfix check'</span> <span class="co0"># and comment this line (by adding a # at the start)</span> smtp_tls_wrappermode = <span class="kw2">yes</span></pre></div></div> <p>Once you've created that <code>main.cf</code> file, you can double check that your config is valid:</p> <p><code>sudo postfix check</code></p> <p>and if it's all ok, you can get Postfix to re-read its configuration:</p> <p><code>sudo postfix reload</code></p> <p>You can then try sending an email so see if it works!</p> <p>By default, a command line application called "mail" is installed as part of the bsd-mailx package we installed alongside postfix. You can use it to send test email from the command line on your host to verify you've got things working correctly! The stuff in &lt;&gt; are the keys to hit at the end of the line...</p> <p><code>$ mail you@email.domain&lt;ENTER&gt;</code></p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">Subject: Testing from your.relay.server.domain<span class="sy0">&lt;</span>ENTER<span class="sy0">&gt;</span> Testing postfix remote host<span class="sy0">&lt;</span>ENTER<span class="sy0">&gt;</span> <span class="sy0">&lt;</span>CTRL-D<span class="sy0">&gt;</span> Cc:<span class="sy0">&lt;</span>ENTER<span class="sy0">&gt;</span></pre></div></div> <p>Typing (hold down the Control or Ctrl key on your keyboard and press the "d" key) will finish your message, showing you a "CC:" field, in which you can type in other email addresses if you want to test sending to multiple addresses. When you then hit , it will attempt to send this email. It might take a few minutes to work its way through to the receiving email system (having to run the gauntlet of spam and virus filters on the way).</p> <p>You can also always check the postfix system logs to see what postfix thinks about it using the command:</p> <p><code>sudo less +G /var/log/mail.log</code></p> <p>Hit to have the log update in real time.</p> <h2><a id="user-content-docker-compose-and-lets-encrypt" href="#docker-compose-and-lets-encrypt" name="docker-compose-and-lets-encrypt" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Docker Compose and Let's Encrypt</h2> <p>The next step is to set up the file structure for holding your Docker configurations and the data your Docker containers will access. This is my convention, so you're welcome to do things different, but this is a 'known good' approach.</p> <p>First let's install Docker Compose (and its dependencies, like the whole Docker subsystem) and the <a href="http://letsencrypt.org/">Let's Encrypt</a> scripts that let you procure no-cost Secure Sockets Layer certificates to secure access to your Mastodon (and anything else you might want to host on this server).</p> <p><code>sudo apt install docker-compose letsencrypt</code></p> <p>Now we create the set of directories I use for holding Docker Compose configurations (<code>/home/docker</code>) and the persistent data the Docker containers create (<code>/home/data</code>)</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="re2">D</span>=<span class="br0">[</span>domain name<span class="br0">]</span> <span class="kw2">sudo</span> <span class="kw2">mkdir</span> <span class="re5">-p</span> <span class="sy0">/</span>home<span class="sy0">/</span>data<span class="sy0">/</span><span class="re1">$D</span> <span class="kw2">sudo</span> <span class="kw2">mkdir</span> <span class="re5">-p</span> <span class="sy0">/</span>home<span class="sy0">/</span>docker<span class="sy0">/</span><span class="re1">$D</span></pre></div></div> <p>It's helpful to make sure that your non-root user can also read and write files in these directories:</p> <p>U=[vps username] sudo chown -R $U /home/docker sudo chown -R $U /home/data [/code]</p> <h2><a id="user-content-install-the-mastodon-docker-recipe" href="#install-the-mastodon-docker-recipe" name="install-the-mastodon-docker-recipe" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Install the Mastodon Docker recipe</h2> <p>Now we have a place to put the really key bit - the code for running Mastodon via Docker Compose - first we go to the right place to set things up:</p> <p><code>cd /home/docker</code></p> <p>and then we use an amazing tool called '<a href="https://git-scm.com/book/en/v2/Getting-Started-About-Version-Control">git</a>' to download the canonical code created by the Mastodon developers. Of course, replace [domain name] in the following with the one you've selected (for the record, this command should work without <code>sudo</code> because we changed the ownership of the director in the previous step to belong to your unprivileged user).</p> <p><code>git clone https://github.com/tootsuite/mastodon.git [domain name]</code></p> <p>Then you can go into the directory</p> <p><code>cd [domain name]</code></p> <p>Have a look around. Use <code>ls -la</code> to see what files and directories (including 'hidden' ones, whose names start with a '.') the project includes. They key files in this case are the <code>.env.production.sample</code> and <code>docker-compose.yml</code>. You can ignore the rest for the time being.</p> <p>The first thing we want to do is create a <code>.env.production</code> file - this will hold <em>all the crucial details of your Mastodon instance</em>. Do it like this:</p> <p><code>cp .env.production.sample .env.production</code></p> <p>and then edit it (if EDIT isn't defined, because you're in a different session now, you might need to reset it as described above):</p> <p><code>$EDIT .env.production</code></p> <p>The following is what I've got (replace the [tokens] as usual). Make sure you get your SMTP details right - if you don't, you can fix them, but you won't get any emails from the instance until you do! Note that there're five (5) special numbers that you'll need to set later - but don't worry about them for the moment.</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co0"># This is a sample configuration file. You can generate your configuration</span> <span class="co0"># with the `rake mastodon:setup` interactive setup wizard, but to customize</span> <span class="co0"># your setup even further, you'll need to edit it manually. This sample does</span> <span class="co0"># not demonstrate all available configuration options. Please look at</span> <span class="co0"># https://docs.joinmastodon.org/admin/config/ for the full documentation.</span>   <span class="co0"># Note that this file accepts slightly different syntax depending on whether</span> <span class="co0"># you are using `docker-compose` or not. In particular, if you use</span> <span class="co0"># `docker-compose`, the value of each declared variable will be taken verbatim,</span> <span class="co0"># including surrounding quotes.</span> <span class="co0"># See: https://github.com/mastodon/mastodon/issues/16895</span>   <span class="co0"># Federation</span> <span class="co0"># ----------</span> <span class="co0"># This identifies your server and cannot be changed safely later</span> <span class="co0"># ----------</span> <span class="re2">LOCAL_DOMAIN</span>=<span class="br0">[</span>domain name<span class="br0">]</span> <span class="re2">LOCAL_HTTPS</span>=<span class="kw2">true</span> <span class="re2">ALTERNATE_DOMAINS</span>=<span class="br0">[</span>second domain name<span class="br0">]</span>   <span class="co0"># Redis</span> <span class="co0"># -----</span> <span class="re2">REDIS_HOST</span>=redis <span class="re2">REDIS_PORT</span>=<span class="nu0">6379</span>   <span class="co0"># PostgreSQL</span> <span class="co0"># ----------</span> <span class="re2">DB_HOST</span>=db <span class="re2">DB_USER</span>=postgres <span class="re2">DB_NAME</span>=postgres <span class="re2">DB_PASS</span>= <span class="re2">DB_PORT</span>=<span class="nu0">5432</span>   <span class="co0"># Elasticsearch (optional)</span> <span class="co0"># ------------------------</span> <span class="re2">ES_ENABLED</span>=<span class="kw2">false</span> <span class="co0">#ES_HOST=localhost</span> <span class="co0">#ES_PORT=9200</span> <span class="co0"># Authentication for ES (optional)</span> <span class="co0">#ES_USER=elastic</span> <span class="co0">#ES_PASS=password</span>   <span class="co0"># Secrets</span> <span class="co0"># -------</span> <span class="co0"># Make sure to use `rake secret` to generate secrets</span> <span class="co0"># -------</span> <span class="re2">PAPERCLIP_SECRET</span>=LongSecretNumberOne <span class="re2">SECRET_KEY_BASE</span>=LongSecretNumberTwo <span class="re2">OTP_SECRET</span>=LongSecretNumberThree   <span class="co0"># Web Push</span> <span class="co0"># --------</span> <span class="co0"># Generate with `rake mastodon:webpush:generate_vapid_key`</span> <span class="co0"># --------</span> <span class="co0">#VAPID_PRIVATE_KEY=</span> <span class="co0">#VAPID_PUBLIC_KEY=</span> <span class="re2">VAPID_PRIVATE_KEY</span>=QuiteALotShorterSecretNumberOne <span class="re2">VAPID_PUBLIC_KEY</span>=SlightlyShorterSecretNumberTwo     <span class="co0"># Sending mail</span> <span class="co0"># ------------</span> <span class="re2">SMTP_SERVER</span>=<span class="br0">[</span>smtp server<span class="br0">]</span> <span class="re2">SMTP_PORT</span>=<span class="br0">[</span>smtp port<span class="br0">]</span> <span class="re2">SMTP_LOGIN</span>=<span class="br0">[</span>smtp user<span class="br0">]</span> <span class="re2">SMTP_PASSWORD</span>=<span class="br0">[</span>smtp password<span class="br0">]</span> <span class="re2">SMTP_FROM_ADDRESS</span>=<span class="br0">[</span>smtp reply-to-email<span class="br0">]</span>   <span class="co0"># File storage (optional)</span> <span class="co0"># -----------------------</span> <span class="co0"># #S3_ENABLED=true</span> <span class="co0">#S3_BUCKET=files.example.com</span> <span class="co0">#AWS_ACCESS_KEY_ID=</span> <span class="co0">#AWS_SECRET_ACCESS_KEY=</span> <span class="co0">#S3_ALIAS_HOST=files.example.com</span></pre></div></div> <p>Once you've save that, we can create the <code>docker-compose.yml</code> file. I recommend creating a backup version of the original file in case you want to refer back to it:</p> <p><code>cp docker-compose.yml docker-compose.yml.orig</code></p> <p>Then you can edit the file via</p> <p><code>$EDIT docker-compose.yml</code></p> <p>Replace any [tokens] with your values. Note that the version of Mastodon at the time of this writing is 4.0.1 - to use a different version (e.g. if you are reading this tutorial after Eugen releases Mastodon 4.x (see the <a href="https://github.com/mastodon/mastodon/releases">current release</a>) you'll just want to replace all occurrences of 4.0.1 below with the appropriate version number.</p> <p>Note, the 'build' commands are commented out here as we're using pre-built containers rather than building them locally (as that takes a lot of time and potentially requires a fair bit of disk space, at least temporarily). If you want to do a build, you'll want to uncomment those lines.</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">version: <span class="st_h">'3'</span> services: db: restart: unless-stopped image: postgres:<span class="nu0">14</span>-alpine shm_size: 256mb networks: - internal_network healthcheck: test: <span class="br0">[</span><span class="st_h">'CMD'</span>, <span class="st_h">'pg_isready'</span>, <span class="st_h">'-U'</span>, <span class="st_h">'postgres'</span><span class="br0">]</span> volumes: - <span class="sy0">/</span>home<span class="sy0">/</span>data<span class="sy0">/</span><span class="br0">[</span>domain name<span class="br0">]</span><span class="sy0">/</span>postgres14:<span class="sy0">/</span>var<span class="sy0">/</span>lib<span class="sy0">/</span>postgresql<span class="sy0">/</span>data environment: - <span class="st_h">'POSTGRES_HOST_AUTH_METHOD=trust'</span>   redis: restart: unless-stopped image: redis:<span class="nu0">6</span>-alpine networks: - internal_network healthcheck: test: <span class="br0">[</span><span class="st_h">'CMD'</span>, <span class="st_h">'redis-cli'</span>, <span class="st_h">'ping'</span><span class="br0">]</span> volumes: - <span class="sy0">/</span>home<span class="sy0">/</span>data<span class="sy0">/</span><span class="br0">[</span>domain name<span class="br0">]</span><span class="sy0">/</span>redis:<span class="sy0">/</span>data   <span class="co0">## this is commented out because most Mastodon admins don't use it.</span> <span class="co0">## that's because the disk space it uses grows quickly and without bound...</span> <span class="co0"># es:</span> <span class="co0"># restart: unless-stopped</span> <span class="co0"># image: docker.elastic.co/elasticsearch/elasticsearch-oss:7.10.2</span> <span class="co0"># environment:</span> <span class="co0"># - "ES_JAVA_OPTS=-Xms512m -Xmx512m"</span> <span class="co0"># - "cluster.name=es-mastodon"</span> <span class="co0"># - "discovery.type=single-node"</span> <span class="co0"># - "bootstrap.memory_lock=true"</span> <span class="co0"># networks:</span> <span class="co0"># - internal_network</span> <span class="co0"># healthcheck:</span> <span class="co0"># test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"]</span> <span class="co0"># volumes:</span> <span class="co0"># - /home/data/[domain name]/elasticsearch:/usr/share/elasticsearch/data</span> <span class="co0"># ulimits:</span> <span class="co0"># memlock:</span> <span class="co0"># soft: -1</span> <span class="co0"># hard: -1</span>   web: <span class="co0">#build: .</span> image: tootsuite<span class="sy0">/</span>mastodon:v4.0.1 restart: unless-stopped env_file: .env.production command: <span class="kw2">bash</span> <span class="re5">-c</span> <span class="st0">"rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"</span> networks: - external_network - internal_network healthcheck: <span class="co0"># prettier-ignore</span> test: <span class="br0">[</span><span class="st_h">'CMD-SHELL'</span>, <span class="st_h">'wget -q --spider --proxy=off localhost:3000/health || exit 1'</span><span class="br0">]</span> ports: - <span class="st_h">'127.0.0.1:3000:3000'</span> depends_on: - db - redis volumes: - <span class="sy0">/</span>home<span class="sy0">/</span>data<span class="sy0">/</span><span class="br0">[</span>domain name<span class="br0">]</span><span class="sy0">/</span>public<span class="sy0">/</span>system:<span class="sy0">/</span>mastodon<span class="sy0">/</span>public<span class="sy0">/</span>system   streaming: <span class="co0">#build: .</span> image: tootsuite<span class="sy0">/</span>mastodon:v4.0.1 restart: unless-stopped env_file: .env.production command: node .<span class="sy0">/</span>streaming networks: - external_network - internal_network healthcheck: <span class="co0"># prettier-ignore</span> test: <span class="br0">[</span><span class="st_h">'CMD-SHELL'</span>, <span class="st_h">'wget -q --spider --proxy=off localhost:4000/api/v1/streaming/health || exit 1'</span><span class="br0">]</span> ports: - <span class="st_h">'127.0.0.1:4000:4000'</span> depends_on: - db - redis   sidekiq: <span class="co0">#build: .</span> image: tootsuite<span class="sy0">/</span>mastodon:v4.0.1 restart: unless-stopped env_file: .env.production command: bundle <span class="kw3">exec</span> sidekiq depends_on: - db - redis networks: - external_network - internal_network volumes: - <span class="sy0">/</span>home<span class="sy0">/</span>data<span class="sy0">/</span><span class="br0">[</span>domain name<span class="br0">]</span><span class="sy0">/</span>public<span class="sy0">/</span>system:<span class="sy0">/</span>mastodon<span class="sy0">/</span>public<span class="sy0">/</span>system healthcheck: test: <span class="br0">[</span><span class="st_h">'CMD-SHELL'</span>, <span class="st0">"ps aux | grep '[s]idekiq\ 6' || false"</span><span class="br0">]</span>   <span class="co0">## If you need more workers uncomment the following. Add up to 3 more sidekiq containers (up to 5 total)</span> <span class="co0"># sidekiq2:</span> <span class="co0"># #build: .</span> <span class="co0"># image: tootsuite/mastodon:v4.0.1</span> <span class="co0"># restart: unless-stopped</span> <span class="co0"># env_file: .env.production</span> <span class="co0"># command: bundle exec sidekiq</span> <span class="co0"># depends_on:</span> <span class="co0"># - db</span> <span class="co0"># - redis</span> <span class="co0"># networks:</span> <span class="co0"># - external_network</span> <span class="co0"># - internal_network</span> <span class="co0"># volumes:</span> <span class="co0"># - /home/data/[domain name]/public/system:/mastodon/public/system</span> <span class="co0"># healthcheck:</span> <span class="co0"># test: ['CMD-SHELL', "ps aux | grep '[s]idekiq\ 6' || false"]</span>   networks: external_network: internal_network: internal: <span class="kw2">true</span></pre></div></div> <p>Note: the port numbers in the <code>docker-compose.yml</code> file need to match those in the NGINX reverse proxy configuration.</p> <p>Once you've got this file configured, you should be ready to 'pull' your Docker containers! To do that, run</p> <p><code>docker-compose pull</code></p> <p>Once that's finished, you'll need to get three secret numbers: SECRET_KEY_BASE, OTP_SECRET, and PAPERCLIP_SECRET. Run the following <em>three times</em> and record the numbers (doesn't matter which one goes where, but once you set them, you <em>don't</em> want to change or lose them!):</p> <p><code>docker-compose run --rm web bundle exec rake secret</code></p> <p>Then you need to get the two VAPID keys - running this will provide both:</p> <p><code>docker-compose run --rm web bundle exec rake mastodon:webpush:generate_vapid_key</code></p> <p>Copy these into your <code>.env.production</code> - I encourage you to record them elsewhere as well - a <a href="/node/25">password manager</a> is a good place!</p> <p><code>$EDIT .env.production</code></p> <p>After that, your Mastodon is ready to roll! But there's one more crucial step - we need to set up the <em>reverse proxy</em> server which provides the secure (encrypted) access to your instant for you and all your users.</p> <h2><a id="user-content-nginx-reverse-proxy-configuration" href="#nginx-reverse-proxy-configuration" name="nginx-reverse-proxy-configuration" class="heading-permalink" aria-hidden="true" title="Permalink"></a>NGINX reverse proxy configuration</h2> <p>For the reverse proxy, we'll use the NGINX web server we installed earlier. It stores all of its configuration in the directoy <code>/etc/nginx</code>.</p> <p>The first thing we'll do is create a couple directories we need:</p> <p>This is where Let's Encrypt will look for a secret code we put here to verify that we own the domain we're requesting an SSL certificate for:</p> <p><code>sudo mkdir /var/www/letsencrypt</code></p> <p>Then we create a place to the Let's Encrypt NGINX configuration details</p> <p><code>sudo mkdir /etc/nginx/includes</code></p> <p>And then we create that configuration file</p> <p><code>sudo $EDIT /etc/nginx/includes/letsencrypt.conf</code></p> <p>into which we copy-and-paste the following (no [tokens] to replace in this one!)</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co0"># Rule for legitimate ACME Challenge requests</span> location ^~ <span class="sy0">/</span>.well-known<span class="sy0">/</span>acme-challenge<span class="sy0">/</span> <span class="br0">{</span> default_type <span class="st0">"text/plain"</span>; <span class="co0"># this can be any directory, but this name keeps it clear</span> root <span class="sy0">/</span>var<span class="sy0">/</span>www<span class="sy0">/</span>letsencrypt; <span class="br0">}</span>   <span class="co0"># Hide /acme-challenge subdirectory and return 404 on all requests.</span> <span class="co0"># It is somewhat more secure than letting Nginx return 403.</span> <span class="co0"># Ending slash is important!</span> location = <span class="sy0">/</span>.well-known<span class="sy0">/</span>acme-challenge<span class="sy0">/</span> <span class="br0">{</span> <span class="kw3">return</span> <span class="nu0">404</span>; <span class="br0">}</span></pre></div></div> <p>Next we have to create the reverse proxy configuration file for our Mastodon domain name (and any other domain names we might want to use):</p> <p><code>sudo $EDIT /etc/nginx/sites-avalable/[domain name]</code></p> <p>Again, we have the convention of using the [domain name] to identify this file. It's self-documenting. Copy-and-paste the following into, making [token] substitutions. Note that the port numbers (usually after a ':') need to correspond to the port numbers specified in the <code>docker-compose.yml</code> file.</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">map <span class="re1">$http_upgrade</span> <span class="re1">$connection_upgrade</span> <span class="br0">{</span> default upgrade; <span class="st_h">''</span> close; <span class="br0">}</span>   proxy_cache_path <span class="sy0">/</span>var<span class="sy0">/</span>cache<span class="sy0">/</span>nginx <span class="re2">levels</span>=<span class="nu0">1</span>:<span class="nu0">2</span> <span class="re2">keys_zone</span>=CACHE:10m <span class="re2">inactive</span>=7d <span class="re2">max_size</span>=1g;   <span class="co0"># this configuration redirects any attempts to connect to your instance insecurely (via http rather than https)</span> <span class="co0"># to your preferred [domain name] via https.</span> server <span class="br0">{</span> listen <span class="nu0">80</span>; listen <span class="br0">[</span>::<span class="br0">]</span>:<span class="nu0">80</span>; <span class="co0"># you can optionally include one or more other domains (e.g. [second domain name]) here</span> <span class="co0"># - just separate by spaces.</span> server_name <span class="br0">[</span>domain name<span class="br0">]</span>; root <span class="sy0">/</span>var<span class="sy0">/</span>www<span class="sy0">/</span>html;   include includes<span class="sy0">/</span>letsencrypt.conf;   <span class="co0"># change the file name of these logs to include your server name</span> <span class="co0"># if hosting many services...</span> access_log <span class="sy0">/</span>var<span class="sy0">/</span>log<span class="sy0">/</span>nginx<span class="sy0">/</span><span class="br0">[</span>domain name<span class="br0">]</span>_access.log; error_log <span class="sy0">/</span>var<span class="sy0">/</span>log<span class="sy0">/</span>nginx<span class="sy0">/</span><span class="br0">[</span>domain name<span class="br0">]</span>_error.log;   <span class="co0"># redirect all HTTP traffic to HTTPS.</span> location <span class="sy0">/</span> <span class="br0">{</span> <span class="kw3">return</span> <span class="nu0">302</span> https:<span class="sy0">//</span><span class="br0">[</span>domain name<span class="br0">]</span><span class="re1">$request_uri</span>; <span class="br0">}</span> <span class="br0">}</span>   <span class="co0">## optional - if you want your Mastodon to respond to a [second domain name]</span> <span class="co0">## this will perform a redirection from this domain to your main domain.</span> <span class="co0">##</span> <span class="co0">## to use it, uncomment the first # in each line following</span> <span class="co0">#server {</span> <span class="co0"># listen 443 ssl http2;</span> <span class="co0"># listen [::]:443 ssl http2;</span> <span class="co0"># server_name [second domain name];</span> <span class="co0">#</span> <span class="co0"># ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;</span> <span class="co0"># ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;</span> <span class="co0">## ssl_certificate /etc/letsencrypt/live/[domain name]/fullchain.pem;</span> <span class="co0">## ssl_certificate_key /etc/letsencrypt/live/[domain name]/privkey.pem;</span> <span class="co0"># ssl_protocols TLSv1 TLSv1.1 TLSv1.2;</span> <span class="co0"># # from https://0x39b.fr/post/nginx_security/</span> <span class="co0"># ssl_session_timeout 1d;</span> <span class="co0"># ssl_session_cache shared:SSL:50m;</span> <span class="co0"># #ssl_session_tickets off;</span> <span class="co0"># ssl_prefer_server_ciphers on;</span> <span class="co0"># ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';</span> <span class="co0"># # OCSP Stapling ---</span> <span class="co0"># # fetch OCSP records from URL in ssl_certificate and cache them</span> <span class="co0"># ssl_stapling on;</span> <span class="co0"># ssl_stapling_verify on;</span> <span class="co0"># # to create this, see https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html</span> <span class="co0"># ssl_dhparam /etc/ssl/certs/dhparam.pem;</span> <span class="co0">#</span> <span class="co0"># # for let's encrypt renewals!</span> <span class="co0"># include includes/letsencrypt.conf;</span> <span class="co0">#</span> <span class="co0"># keepalive_timeout 70;</span> <span class="co0"># sendfile on;</span> <span class="co0"># client_max_body_size 80M;</span> <span class="co0">#</span> <span class="co0"># # change the file name of these logs to include your server name</span> <span class="co0"># # if hosting many services...</span> <span class="co0"># access_log /var/log/nginx/[domain name]_access.log;</span> <span class="co0"># error_log /var/log/nginx/[domain name]_error.log;</span> <span class="co0">#</span> <span class="co0"># # redirect all HTTP traffic to HTTPS.</span> <span class="co0"># location / {</span> <span class="co0"># return 302 https://[domain name]$request_uri;</span> <span class="co0"># }</span> <span class="co0">#}</span> <span class="co0">## end optional *second* domain name</span>   server <span class="br0">{</span> listen <span class="nu0">443</span> ssl http2; listen <span class="br0">[</span>::<span class="br0">]</span>:<span class="nu0">443</span> ssl http2; <span class="co0"># if you have a [second domain]</span> server_name <span class="br0">[</span>domain name<span class="br0">]</span>;   ssl_certificate <span class="sy0">/</span>etc<span class="sy0">/</span>ssl<span class="sy0">/</span>certs<span class="sy0">/</span>ssl-cert-snakeoil.pem; ssl_certificate_key <span class="sy0">/</span>etc<span class="sy0">/</span>ssl<span class="sy0">/</span>private<span class="sy0">/</span>ssl-cert-snakeoil.key; <span class="co0"># ssl_certificate /etc/letsencrypt/live/[domain name]/fullchain.pem;</span> <span class="co0"># ssl_certificate_key /etc/letsencrypt/live/[domain name]/privkey.pem;</span> ssl_protocols TLSv1 TLSv1.1 TLSv1.2; <span class="co0"># from https://0x39b.fr/post/nginx_security/</span> ssl_session_timeout 1d; ssl_session_cache shared:SSL:50m; <span class="co0">#ssl_session_tickets off;</span> ssl_prefer_server_ciphers on; ssl_ciphers <span class="st_h">'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'</span>; <span class="co0"># OCSP Stapling ---</span> <span class="co0"># fetch OCSP records from URL in ssl_certificate and cache them</span> ssl_stapling on; ssl_stapling_verify on; <span class="co0"># to create this, see https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html</span> ssl_dhparam <span class="sy0">/</span>etc<span class="sy0">/</span>ssl<span class="sy0">/</span>certs<span class="sy0">/</span>dhparam.pem;   <span class="co0"># for let's encrypt renewals!</span> include includes<span class="sy0">/</span>letsencrypt.conf;   keepalive_timeout <span class="nu0">70</span>; sendfile on; client_max_body_size 80M;   <span class="co0"># change the file name of these logs to include your server name</span> <span class="co0"># if hosting many services...</span> access_log <span class="sy0">/</span>var<span class="sy0">/</span>log<span class="sy0">/</span>nginx<span class="sy0">/</span><span class="br0">[</span>domain name<span class="br0">]</span>_access.log; error_log <span class="sy0">/</span>var<span class="sy0">/</span>log<span class="sy0">/</span>nginx<span class="sy0">/</span><span class="br0">[</span>domain name<span class="br0">]</span>_error.log;   <span class="co0"># from https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Production-guide.md</span> <span class="kw2">gzip</span> on; gzip_vary on; gzip_proxied any; gzip_comp_level <span class="nu0">6</span>; gzip_buffers <span class="nu0">16</span> 8k; gzip_http_version <span class="nu0">1.1</span>; gzip_types text<span class="sy0">/</span>plain text<span class="sy0">/</span>css application<span class="sy0">/</span>json application<span class="sy0">/</span>javascript text<span class="sy0">/</span>xml application<span class="sy0">/</span>xml application<span class="sy0">/</span>xml+rss text<span class="sy0">/</span>javascript;     add_header Strict-Transport-Security <span class="st0">"max-age=31536000; includeSubDomains"</span>;   location <span class="sy0">/</span> <span class="br0">{</span> try_files <span class="re1">$uri</span> <span class="sy0">@</span>proxy; <span class="br0">}</span>   location ~ ^<span class="sy0">/</span><span class="br0">(</span>emoji<span class="sy0">|</span>packs<span class="sy0">|</span>system<span class="sy0">/</span>accounts<span class="sy0">/</span>avatars<span class="sy0">|</span>system<span class="sy0">/</span>media_attachments<span class="sy0">/</span>files<span class="br0">)</span> <span class="br0">{</span> add_header Cache-Control <span class="st0">"public, max-age=31536000, immutable"</span>; add_header Strict-Transport-Security <span class="st0">"max-age=31536000"</span>; try_files <span class="re1">$uri</span> <span class="sy0">@</span>proxy; <span class="br0">}</span>   location <span class="sy0">/</span>sw.js <span class="br0">{</span> add_header Cache-Control <span class="st0">"public, max-age=0"</span>; add_header Strict-Transport-Security <span class="st0">"max-age=31536000"</span>; try_files <span class="re1">$uri</span> <span class="sy0">@</span>proxy; <span class="br0">}</span>   location <span class="sy0">@</span>proxy <span class="br0">{</span> proxy_set_header Host <span class="re1">$host</span>; proxy_set_header X-Real-IP <span class="re1">$remote_addr</span>; proxy_set_header X-Forwarded-For <span class="re1">$proxy_add_x_forwarded_for</span>; proxy_set_header X-Forwarded-Proto https; proxy_set_header Proxy <span class="st0">""</span>; proxy_pass_header Server;   proxy_pass http:<span class="sy0">//</span>127.0.0.1:<span class="nu0">3000</span>; proxy_buffering off; proxy_redirect off; proxy_http_version <span class="nu0">1.1</span>; proxy_set_header Upgrade <span class="re1">$http_upgrade</span>; proxy_set_header Connection <span class="re1">$connection_upgrade</span>;   tcp_nodelay on; <span class="br0">}</span>   location <span class="sy0">/</span>api<span class="sy0">/</span>v1<span class="sy0">/</span>streaming <span class="br0">{</span> proxy_set_header Host <span class="re1">$host</span>; proxy_set_header X-Real-IP <span class="re1">$remote_addr</span>; proxy_set_header X-Forwarded-For <span class="re1">$proxy_add_x_forwarded_for</span>; proxy_set_header X-Forwarded-Proto https; proxy_set_header Proxy <span class="st0">""</span>;   proxy_pass http:<span class="sy0">//</span>127.0.0.1:<span class="nu0">4000</span>; proxy_buffering off; proxy_redirect off; proxy_http_version <span class="nu0">1.1</span>; proxy_set_header Upgrade <span class="re1">$http_upgrade</span>; proxy_set_header Connection <span class="re1">$connection_upgrade</span>;   tcp_nodelay on; <span class="br0">}</span>   error_page <span class="nu0">500</span> <span class="nu0">501</span> <span class="nu0">502</span> <span class="nu0">503</span> <span class="nu0">504</span> <span class="sy0">/</span><span class="nu0">500</span>.html; <span class="br0">}</span></pre></div></div> <p>We initially comment out the SSL certificate paths for our [domain name] in the above configuration because we haven't yet created those certificates, and NGINX won't run with missing certificates (a trap for young players!). So we temprarily substitute the default (not domain-specific) "snakeoil" certificates that are provided with every Linux installation to act as valid certificate placeholders to run NGINX so that we can then request Let's Encrypt certificates.</p> <p>After we've save the NGINX configuration, we need to make sure it's also in the <code>sites-enabled</code> directory, so NGINX will see it (it ignores those merely in sites-available, which is sort of a holding pen for potential site configurations):</p> <p><code>sudo ln -sf /etc/nginx/sites-available/[domain name] /etc/nginx/sites-enabled/</code></p> <p>before we can test our NGINX confguration for errors, we need to address the file, <code>/etc/ssl/certs/dhparam.pem</code>, our config file references but which doesn't yet exist by creating it like this:</p> <p><code>openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096</code></p> <p>Note <strong>this process can take quite a long time</strong>, like 10-40 minutes depending on your VPS and the 'entropy' it generates. If you're short of time, cancel the one running (CTRL+C) and run it again with <code>2048</code> rather than <code>4096</code> specified. It'll make your installation marginally less secure.</p> <p>after which we can ask NGINX to test its configuration for errors:</p> <p><code>sudo nginx -t</code></p> <p>Fix any errors you might find (e.g. typos, missing punctuation, etc.) and after <code>nginx -t</code> tells you you've got valid configurations, reload NGINX to enable the new configuration:</p> <p><code>sudo service nginx reload</code></p> <p>Once that's working, your server is configured to respond to external requests for you site via [domain name] via <code>http://</code> (not encrypted), and via <code>https://</code> (encrypted, although pointing your browser at <code>https://[domain name]</code> right now will get your warnings that you've got a mismatch between your certificate and your domain name.). So, of course, the next step is to generate a certificate for [domain name].</p> <p>Let's Encrypt can award a certificate to you because it can confirm that you (who control [domain name]) also control the server. They verify it by pointing their infrastructure at your [domain name] - recalling that your domain is pointed at your VPS' IPv4 (the A record above) and (if used) IPv6 (the AAAA record above) - and checking in pre-defined location (see the letsencrypt command below) to see if, at that location they can find a secret number that you've asked them to write there. If they find it, they can trust that you control both the [domain name] and the VPS it's pointing to.</p> <p>Here's the command that will request Let's Encrypt's systems to run that check - it will verify that all the domain names specified (with a <code>-d</code> flag are a) pointing at your VPS, and b) their scripts can see the secret number the specified location, in this case in <code>/var/www/letsencrypt</code>):</p> <p><code>letsencrypt certonly --webroot -w /var/www/letsencrypt -d [domain name] -d [second domain name]</code></p> <p>Here's what you're likely to see as output from the first run of the letsencrypt script - note that it will ask you for an email address (so it can send you warnings if your certificate is going to expire, e.g. due to a problem with renewal (like if you make a configuration change that breaks the renewal process)).</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">Saving debug log to <span class="sy0">/</span>var<span class="sy0">/</span>log<span class="sy0">/</span>letsencrypt<span class="sy0">/</span>letsencrypt.log Enter email address <span class="br0">(</span>used <span class="kw1">for</span> urgent renewal and security notices<span class="br0">)</span> <span class="br0">(</span>Enter <span class="st_h">'c'</span> to cancel<span class="br0">)</span>: webmaster<span class="sy0">@</span>fossdle.org   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Please <span class="kw3">read</span> the Terms of Service at https:<span class="sy0">//</span>letsencrypt.org<span class="sy0">/</span>documents<span class="sy0">/</span>LE-SA-v1.3-September-<span class="nu0">21</span>-<span class="nu0">2022</span>.pdf. You must agree <span class="kw1">in</span> order to register with the ACME server. Do you agree? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <span class="br0">(</span>Y<span class="br0">)</span>es<span class="sy0">/</span><span class="br0">(</span>N<span class="br0">)</span>o: y   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Would you be willing, once your first certificate is successfully issued, to share your email address with the Electronic Frontier Foundation, a founding partner of the Let<span class="st_h">'s Encrypt project and the non-profit organization that develops Certbot? We'</span>d like to send you email about our work encrypting the web, EFF news, campaigns, and ways to support digital freedom. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <span class="br0">(</span>Y<span class="br0">)</span>es<span class="sy0">/</span><span class="br0">(</span>N<span class="br0">)</span>o: y Account registered. Requesting a certificate <span class="kw1">for</span> <span class="br0">[</span>domain name<span class="br0">]</span> and <span class="br0">[</span>second domain name<span class="br0">]</span>   Successfully received certificate. Certificate is saved at: <span class="sy0">/</span>etc<span class="sy0">/</span>letsencrypt<span class="sy0">/</span>live<span class="sy0">/</span><span class="br0">[</span>domain name<span class="br0">]</span><span class="sy0">/</span>fullchain.pem Key is saved at: <span class="sy0">/</span>etc<span class="sy0">/</span>letsencrypt<span class="sy0">/</span>live<span class="sy0">/</span><span class="br0">[</span>domain name<span class="br0">]</span><span class="sy0">/</span>privkey.pem This certificate expires on <span class="nu0">2023</span>-01-<span class="nu0">31</span>. These files will be updated when the certificate renews. Certbot has <span class="kw1">set</span> up a scheduled task to automatically renew this certificate <span class="kw1">in</span> the background.   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - If you like Certbot, please consider supporting our work by: <span class="sy0">*</span> Donating to ISRG <span class="sy0">/</span> Let<span class="st_h">'s Encrypt: https://letsencrypt.org/donate * Donating to EFF: https://eff.org/donate-le - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -</span></pre></div></div> <p>Ideally, you'll see a message like the above. If not, and there's an error, the error messages they provide are usually very useful and accurate. Fix the problem and try again. Note, your SSL certificate will have the name of your [domain name], even if it also provide support for [second domain name] (or third, fourth, etc.).</p> <p>Once you have a Let's Encrypt certificate, you can update our NGINX configuration:</p> <p><code>sudo $EDIT /etc/nginx/sites-available/[domain name]</code></p> <p>and swap all occurrences of</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"> ssl_certificate <span class="sy0">/</span>etc<span class="sy0">/</span>ssl<span class="sy0">/</span>certs<span class="sy0">/</span>ssl-cert-snakeoil.pem; ssl_certificate_key <span class="sy0">/</span>etc<span class="sy0">/</span>ssl<span class="sy0">/</span>private<span class="sy0">/</span>ssl-cert-snakeoil.key; <span class="co0"># ssl_certificate /etc/letsencrypt/live/[domain name]/fullchain.pem;</span> <span class="co0"># ssl_certificate_key /etc/letsencrypt/live/[domain name]/privkey.pem;</span></pre></div></div> <p>to</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co0"># ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;</span> <span class="co0"># ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;</span> ssl_certificate <span class="sy0">/</span>etc<span class="sy0">/</span>letsencrypt<span class="sy0">/</span>live<span class="sy0">/</span><span class="br0">[</span>domain name<span class="br0">]</span><span class="sy0">/</span>fullchain.pem; ssl_certificate_key <span class="sy0">/</span>etc<span class="sy0">/</span>letsencrypt<span class="sy0">/</span>live<span class="sy0">/</span><span class="br0">[</span>domain name<span class="br0">]</span><span class="sy0">/</span>privkey.pem;</pre></div></div> <p>which enables your new domain-specific SSL certificate. Check that NGINX is happy with your change:</p> <p><code>sudo nginx -t</code></p> <p>and if so,</p> <p><code>sudo service nginx reload</code></p> <p>You domain should now be fully enabled for <code>https://</code> access. Note that going to <code>http://[domain name]</code> should automatically redirect you to <code>https://[domain name]</code> because you care about your user's security! :grin:</p> <p>We're in the final stretch now!</p> <h2><a id="user-content-build-your-mastodon" href="#build-your-mastodon" name="build-your-mastodon" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Build your Mastodon</h2> <p>To actually launch your Mastodon instance you first have to create the structure for database in the PostreSQL container. You can do that Virtual</p> <p><code>docker-compose run --rm web rails db:migrate</code></p> <p>This will temporarily spin up your 'web' container (which, in turn depends on your PostgreSQL container, aka 'db') and run the 'migration' script which either updates (or creates, if it's not already there) your database tables.</p> <p>Ok - it's finally time to launch your Mastodon instance. Running this will fire up all of your containers using the values in your <code>.env.production</code> file.</p> <p><code>docker-compose up -d &amp;&amp; docker-compose logs -f --tail=100</code></p> <p>After it initiates the containers with the <code>up -d</code> command, it opens a logging interface which allow you to watch the log messages from all the containers (only including the most recent 100 lines at the time you run it), which is very helpful in the event that something goes wrong - you should be able to see what it is.</p> <p>You can always stop your Mastodon instance via</p> <p><code>docker-compose stop</code> or individual containers via, for example, the 'sidekiq' container, <code>docker-compose stop sidekiq</code>.</p> <p>Now your Mastodon should be running. You can point your browser at <code>https://[domain name]</code> and you should be greeted by a sight similar to the first screenshot above (although it'll have your site's [domain name] rather than social.fossdle,org, which is the site I recently deployed and was the basis for setting up this tutorial). Your login page will just have generic information as it hasn't been configured yet, and doesn't have any users, administrative or otherwise.</p> <p>If your site is missing the "mastodon" image below the Log In form, it might be that your 'assets' haven't been pre-compiled. If that's the case, just run this:</p> <p><code>docker-compose run --rm web rails assets:precompile</code></p> <p>which will re-compile them.</p> <h2><a id="user-content-create-your-admin-user" href="#create-your-admin-user" name="create-your-admin-user" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Create your Admin user</h2> <p>What you now need to do is create a user with [mastodon username] for the administrator of the site. That can be your own user or a dedicated admin user. It's up to you. I tend to make my personal user the admin user. You'll just follow the instructions on the start page to create a new account. All going well, you'll receive an email from the site asking you to verify your email address, and then you'll get a welcome email. At this point, you can log in, although you'll just be a generic user, not an admin. To change your user into an admin user, you'll need to run</p> <p><code>docker-compose exec -e RAILS_ENV=production streaming bin/tootctl accounts modify [mastodon username] --role admin</code></p> <p>If it's successful, you should see</p> <p><code>OK</code></p> <p>At that point, you can do a refresh of your logged in session in your Mastodon instance, and you'll be the Administrator! Congratulations! You've done it.</p> <p>Now, you'll just want to do some basic configuration of your instance... and then you can let others know about it!</p> <h2><a id="user-content-basic-instance-configuration" href="#basic-instance-configuration" name="basic-instance-configuration" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Basic Instance configuration</h2> <p>Once you've got your admin user working, you'll want to go to the "settings" for your user (the little 'gear' near the bottom right of the right column in the default interface, or at the top right of the left column of the advanced interface) and from the resulting menu, you should be able to select the 'Administration' option. The first thing to do is to go to 'Settings' and fill in as much as you need to - I've included a screen shot of our settings page on social.fossdle.org for your reference. You should also set some 'instance rules' - I've included another screenshot showing our instance rules - you can always <a href="https://social.fossdle.org/about/more">check them out for yourself</a> - you're welcome to borrow (any|every)thing from them.</p> <p>After all that's set up, you're ready to go! You can start following other users - one useful trick to know about is that you can go to any other instances you know about via their web address (or go to the <a href="https://joinmastodon.org">main Mastodon help site</a> and look for other instances) and you can explore the 'profile directory' on any other instance. Clicking on a user's name (bold text) will pop up a box in your browser allowing you to elect to follow that user by typing in your Mastodon handle - it'll be [mastodon username]@[domain name] - in the provided form, and then selecting the 'Follow' button. Alternatively, you can copy-and-paste a user handle (below a user's name in their profile directory) into the Search bar in your Mastodon interface - it should show you the same profile info in your own Mastodon's interface window, and you should be able to follow with a single click on the "Follow" button or the little 'follow user' icon (greyed out head with a + after it). If you're already following a user, you'll see a blue 'user being followed' icon (with a blue x next to it) that, if clicked, will <em>unfollow</em> that user.</p> <h2><a id="user-content-other-considerations" href="#other-considerations" name="other-considerations" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Other considerations</h2> <p>Ok, you're in business, but there're a few loose ends to tidy up (after the well-earned euphoria of your new found power and place die down a bit!). These are things that any production web service needs to have, to ensure that you're looking after both your own (and, just as important!, your fellow users') data and generally running a secure, tight ship.</p> <h3><a id="user-content-backing-up-the-mastodon-database" href="#backing-up-the-mastodon-database" name="backing-up-the-mastodon-database" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Backing up the Mastodon Database</h3> <p>The first is that you need to have regular backups of the PostgreSQL database that underlies the whole Mastodon instance. I've <a href="https://git.oeru.org/oeru/docker-compose-dbbackup">published a backup utility</a> that we use at the OER Foundation to back up our Docker containerised PostgreSQL instances every hour! By the way, <a href="https://postgresql.org/">PostgreSQL</a> is an <em>amazing</em> database, both FOSS and world-class, that's used by many of our FOSS services! We would use it over, say, the vastly more expensive proprietary database, MS SQL Server, any day of the week. In many ways, PostgreSQL is more capable, and quite a lot <em>faster</em> besides.</p> <p>I'll aim to write another (shorter!) tutorial on how to implement this system in the next week or two.</p> <h3><a id="user-content-backing-up-your-vps-files-and-configurations-incuding-your-mastodon" href="#backing-up-your-vps-files-and-configurations-incuding-your-mastodon" name="backing-up-your-vps-files-and-configurations-incuding-your-mastodon" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Backing up your VPS' files and configurations, incuding your Mastodon</h3> <p>Also, you'll need to make sure that all the important files (configuration and data) on your VPS are being backed up, and ideally sent somewhere <em>remote</em> to your VPS in case something happens to it (rare though that might be), like it gets cracked by ne'er-do-wells or your hosting company suddenly goes belly-up (haven't heard of it happening, but there's always a first time!)...</p> <p>We use an amazing FOSS tool called <a href="https://restic.net">Restic</a>. It allows us to make automated remote incremental backups of the VPS filesystem (including the frequent database dumps created by the database backup script above), and it even encrypts them on the way to protect your users' data even if the <em>backup</em> server is somehow compromised. This is best practice. I've also <a href="https://git.oeru.org/dave/restic-backup">created a script to deploy Restic</a>, and will need to write another tutorial to provide some more explanation.</p> <p>To use this script, you'll need somewhere (that's accessible from the internet) to which you can send our backups! That's either a server with big hard disks in someone's home (with an externally visible network and a properly configured router), or some other backup location, like a commodity object store (often these are advertised as 'AWS S3-compatible') or a big internet-addressible block storage device. The Restic site will provide some guidance on this.</p> <h3><a id="user-content-upgrading-your-mastodon-instance-to-newer-versions" href="#upgrading-your-mastodon-instance-to-newer-versions" name="upgrading-your-mastodon-instance-to-newer-versions" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Upgrading your Mastodon instance to newer versions</h3> <p>Last, Mastodon's developers seldom rest for long. Eugen and the rest of the Mastodon developer community are constantly looking at how they can improve Mastodon, or fix any issues that might emerge. While I was composing this tutorial, the <a href="https://github.com/mastodon/mastodon/releases">current release</a> version went from 3.5.3 -&gt; 4.0.0. -&gt; 4.0.1 in a matter of a couple hours. You don't need to upgrade your instance <em>every time</em> there's a new release, although you <em>do</em> want to apply any <em>security-specific</em> upgrades as quickly as possible to minimise the window of time that your instance is vulnerable!</p> <p>I'll need to write another tutorial on how to do updates, but you should find basic instructions on each release! If your instance is not current at the time of a new release, make sure you <em>read the intervening release notes, too</em> as there are sometimes special instructions for a given jump from one release to the next.</p> <p>Always make sure you have <em>valid</em> backups, ideally on the same VPS, before you do an upgrade so that you can roll back in the event the upgrade fails for some reason!</p> <p>Well done reading through this screed of techn-text! I hope it was a lot quicker to read than it was to write (sheesh! I think I need a lie-down). All the best with your adventures in the Fediverse as a full-fledged contributing member!!</p></div> </div> </div> <section class="field field-node--field-blog-comments field-name-field-blog-comments field-type-comment field-label-above comment-wrapper"> <a name="comments"></a> <div class="comment-form-wrapper"> <h2 class="comment-form__title">Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=54&amp;2=field_blog_comments&amp;3=comment" token="B9-JGcAklPJd4Gn3Mi7jVU_kyHopkfC0tQT73ibOWw4"></drupal-render-placeholder> </div> </section> Thu, 03 Nov 2022 21:31:11 +0000 dave 54 at http://tech.oeru.org http://tech.oeru.org/join-fediverse-installing-mastodon-40-ubuntu-2204-docker-compose#comments OERu Web Services as of August 2022 http://tech.oeru.org/oeru-web-services-august-2022 <span class="field field--name-title field--type-string field--label-hidden">OERu Web Services as of August 2022 </span> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><a title="View user profile." href="/user/1" lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="" class="username">dave</a></span> <span class="field field--name-created field--type-created field--label-hidden">Mon 08/08/2022 - 16:38</span> <div class="clearfix text-formatted field field-node--body field-name-body field-type-text-with-summary field-label-hidden"> <div class="field__items"> <div class="field__item"><p>In the intervening 17 months since my <a href="/node/34">last update</a> the <a href="https://oerfoundation.org">OER Foundation</a> (OERF) has continued to develop new online services for its global community of learners and educators. All of these services are themselves built with - and hosted on - <a href="https://en.wikipedia.org/wiki/Free_and_open-source_software">Free and Open Source Software</a> or FOSS. Each application is the work and responsibility of its own global developer community. The OERF participates in these communities at various levels as we avail ourselves of this wealth of brilliant software for the benefit of our users.</p> <p>In addition to refining our <a href="https://oeru.org">OERu</a> services over the past year, adding a few and sloughing off a few, we have also begun to assist other organisations by hosting systems on their behalf, usually with the aim of handing them over to their control after a period of system administration tuition.</p> <p>As of this writing, in August 2022, we run the following production systems:</p> <ul><li>a couple of <a href="https://wordpress.org/support/article/create-a-network/">WordPress Multisites</a>: <a id="user-content-our-main-course-delivery-site-where-each-course-is-a-subsite---httpscourseoeruorg" href="#our-main-course-delivery-site-where-each-course-is-a-subsite---httpscourseoeruorg" name="our-main-course-delivery-site-where-each-course-is-a-subsite---httpscourseoeruorg" class="heading-permalink" aria-hidden="true" title="Permalink"></a>our main course delivery site, where each course is a 'subsite' - <a href="https://course.oeru.org">https://course.oeru.org</a> <a id="user-content-our-train-the-educators-blog-and-sandbox-course-site-each-educator-gets-a-blog-subsite-for-their-own-use-and-a-course-sandbox-subsite---httpsedt4oloeruorg" href="#our-train-the-educators-blog-and-sandbox-course-site-each-educator-gets-a-blog-subsite-for-their-own-use-and-a-course-sandbox-subsite---httpsedt4oloeruorg" name="our-train-the-educators-blog-and-sandbox-course-site-each-educator-gets-a-blog-subsite-for-their-own-use-and-a-course-sandbox-subsite---httpsedt4oloeruorg" class="heading-permalink" aria-hidden="true" title="Permalink"></a>our train-the-educators blog and sandbox course site. Each educator gets a 'blog' subsite for their own use, and a 'course sandbox' subsite - <a href="https://edt4ol.oeru.org">https://edt4ol.oeru.org</a> </li> <li>a trio of standalone <a href="https://wordpress.org">WordPress</a> instances for the OERF and related initiatives: <a id="user-content-the-oerfs-own-organisational-website---httpsoerfoundationorg" href="#the-oerfs-own-organisational-website---httpsoerfoundationorg" name="the-oerfs-own-organisational-website---httpsoerfoundationorg" class="heading-permalink" aria-hidden="true" title="Permalink"></a>the OERF's own organisational website - <a href="https://oerfoundation.org">https://oerfoundation.org</a> <a id="user-content-the-centre-for-open-educational-practice-coep---httpscoepnz" href="#the-centre-for-open-educational-practice-coep---httpscoepnz" name="the-centre-for-open-educational-practice-coep---httpscoepnz" class="heading-permalink" aria-hidden="true" title="Permalink"></a>the Centre for Open Educational Practice (COEP) - <a href="https://coep.nz">https://coep.nz</a> <a id="user-content-the-oerfs-initiative-to-help-educators-in-the-developing-world-cope-with-the-challenges-of-covid19---httpsoer4covidoeruorg" href="#the-oerfs-initiative-to-help-educators-in-the-developing-world-cope-with-the-challenges-of-covid19---httpsoer4covidoeruorg" name="the-oerfs-initiative-to-help-educators-in-the-developing-world-cope-with-the-challenges-of-covid19---httpsoer4covidoeruorg" class="heading-permalink" aria-hidden="true" title="Permalink"></a>the OERF's initiative to help educators in the developing world cope with the challenges of COVID19 - <a href="https://oer4covid.oeru.org">https://oer4covid.oeru.org</a> </li> <li>a pair of <a href="https://drupal.org">Drupal</a> sites (they've been upgraded to Drupal 9.x) <a id="user-content-the-oerfs-information-tech-site-this-site---httpstechoeruorg-and" href="#the-oerfs-information-tech-site-this-site---httpstechoeruorg-and" name="the-oerfs-information-tech-site-this-site---httpstechoeruorg-and" class="heading-permalink" aria-hidden="true" title="Permalink"></a>the OERF's information tech site (this site!) - <a href="https://tech.oeru.org">https://tech.oeru.org</a>, and <a id="user-content-our-h5p-learning-object-builder-site---httpsh5poeruorg" href="#our-h5p-learning-object-builder-site---httpsh5poeruorg" name="our-h5p-learning-object-builder-site---httpsh5poeruorg" class="heading-permalink" aria-hidden="true" title="Permalink"></a>our H5P learning object builder site - <a href="https://h5p.oeru.org">https://h5p.oeru.org</a> </li> <li>a couple <a href="https://www.discourse.org/">Discourse</a> instances - Discourse is the market leading, modern, full-featured (and open source) forum: <a id="user-content-our-collaboration-and-support-site-for-educators-developing-open-educational-resources-oers---httpscommunityoeruorg" href="#our-collaboration-and-support-site-for-educators-developing-open-educational-resources-oers---httpscommunityoeruorg" name="our-collaboration-and-support-site-for-educators-developing-open-educational-resources-oers---httpscommunityoeruorg" class="heading-permalink" aria-hidden="true" title="Permalink"></a>our collaboration and support site for educators developing Open Educational Resources (OERs) - <a href="https://community.oeru.org">https://community.oeru.org</a> <a id="user-content-our-companion-site-for-folks-taking-oeru-courses-for-both-assignments-and-collaboration-with-other-learners---httpsforumsoeruorg" href="#our-companion-site-for-folks-taking-oeru-courses-for-both-assignments-and-collaboration-with-other-learners---httpsforumsoeruorg" name="our-companion-site-for-folks-taking-oeru-courses-for-both-assignments-and-collaboration-with-other-learners---httpsforumsoeruorg" class="heading-permalink" aria-hidden="true" title="Permalink"></a>our companion site for folks taking OERu courses for both assignments and collaboration with other learners - <a href="https://forums.oeru.org">https://forums.oeru.org</a> </li> <li>a couple <a href="https://nextcloud.com">NextClouds</a> for file sharing and many other uses: <a id="user-content-our-main-collaborative-document-management-system-and-collaborative-authorship-and-sharing-media-and-documents---httpsdocsoeruorg" href="#our-main-collaborative-document-management-system-and-collaborative-authorship-and-sharing-media-and-documents---httpsdocsoeruorg" name="our-main-collaborative-document-management-system-and-collaborative-authorship-and-sharing-media-and-documents---httpsdocsoeruorg" class="heading-permalink" aria-hidden="true" title="Permalink"></a>our main collaborative document management system and collaborative authorship and sharing media and documents - <a href="https://docs.oeru.org">https://docs.oeru.org</a> <a id="user-content-our-internal-collaboration-system---httpshuboeruorg" href="#our-internal-collaboration-system---httpshuboeruorg" name="our-internal-collaboration-system---httpshuboeruorg" class="heading-permalink" aria-hidden="true" title="Permalink"></a>our internal collaboration system - <a href="https://hub.oeru.org">https://hub.oeru.org</a> </li> <li>to support our NextCloud, we have integrated <a href="https://onlyoffice.com">OnlyOffice</a> allowing it to behave like a feature-rich Google Suite, but refreshingly Google-free - <a href="https://onlyoffice.oeru.org">https://onlyoffice.oeru.org</a> </li> <li>a <a href="https://bigbluebutton.org">BigBlueButton</a> instance - a large scale real-time video conferencing and educational collaboration platform - <a href="https://talk.oeru.org">https://talk.oeru.org</a> </li> <li>a <a href="https://github.com/mautic/mautic">Mautic</a> high-powered mailing list automation management system - <a href="https://mautic.oeru.org">https://mautic.oeru.org</a> </li> <li>a <a href="https://rocket.chat">Rocket.Chat</a> instance - a rich chat service comparable to Slack or Discord but fully open source (and we hold all our own data) - <a href="https://chat.oeru.org">https://chat.oeru.org</a> </li> <li>a <a href="https://keycloak.org">Keycloak</a> instance for our still work-in-progress consolidated authentication and identity management system (aka Single Sign-on) - <a href="https://login.oeru.org">https://login.oeru.org</a> </li> <li>a <a href="https://joinmastodon.org">Mastodon</a> instance - Twitter-esque but de-centralised and non-commercial federated open source social network (part of the <a href="https://en.wikipedia.org/wiki/Fediverse">Fediverse</a>) - <a href="https://mastodon.oeru.org">https://mastodon.oeru.org</a> </li> <li>a <a href="https://mailcow.email">Mailcow</a> instance for managing all our email - Mailcow is a full multi-tenanted SMTP/IMAP/POP email system with active spam filtering, user self-service, DKIM configuration/management, webmail, &amp; much more - even virus scanning for Windows clients - <a href="https://about.oerfoundation.org">https://about.oerfoundation.org</a> - it supplies email services for the oeru.org, oerfoundation.org, coep.nz domains, among others!</li> <li>an instance of <a href="https://yourls.org">YourLS</a> - a link shortener - <a href="https://oer.nz">https://oer.nz</a> </li> <li>a <a href="https://matomo.org">Matomo</a> instance - website analytics (like Google Analytics, but without infringing on the EU's <a href="https://gdpr.eu/what-is-gdpr/">GDPR legislation</a>) - <a href="https://stats.oeru.org">https://stats.oeru.org</a> </li> <li>a <a href="https://mahara.org">Mahara</a> instance - online academic portfolio tool - <a href="https://portfolio.oerfoundation.org">https://portfolio.oerfoundation.org</a> </li> <li>a <a href="https://moodle.org">Moodle</a> instance - market leading learning management system which we use primarily for offering quizzes and awarding certificates for participants - <a href="https://moodle.oeru.org">https://moodle.oeru.org</a> </li> <li>a <a href="https://silverstripe.com">SilverStripe</a> instance - our main OERu website - <a href="https://oeru.org">https://oeru.org</a> </li> <li>a <a href="https://mediawiki.org">MediaWiki</a> instance - collaboration platform for OER development built on the same platform on which Wikipedia is built. All our courses are developed here - <a href="https://wikieducator.org">https://wikieducator.org</a> </li> <li>a <a href="https://gitlab.com/rluna-gitlab/gitlab-ce">GitLab</a> - a software developer version control/collaboration tool where we make <em>all</em> our code available - <a href="https://git.oeru.org">https://git.oeru.org</a> </li> <li>a <a href="https://limesurvey.com">LimeSurvey</a> instance - survey tool - <a href="https://survey.oeru.org">https://survey.oeru.org</a> </li> <li>a <a href="https://bitwarden.com">BitWarden</a>/VaultWarden](<a href="https://github.com/dani-garcia/vaultwarden">https://github.com/dani-garcia/vaultwarden</a>) instance - password manager and cross-device sync service - <a href="https://safe.oeru.org">https://safe.oeru.org</a> (here's our <a href="/node/25">howto for hosting your own</a>)</li> <li>an instance of <a href="https://sourceforge.net/projects/semanticscuttle">Semantic Scuttle</a> - a collaborative public website bookmarking service - <a href="https://bookmarks.oeru.org">https://bookmarks.oeru.org</a> </li> </ul><p>This past year, having elected to use OnlyOffice for productivity purposes, we retired our Etherpad-light and CollaboraOffice instances.</p> <p>We also added</p> <ul><li>a Wekan instance - for project planning via the Kanban methodology. Similar to Trello (among other tools) - <a href="https://kanban.oeru.org">https://kanban.oeru.org</a>.</li> <li>a Mobilizon instance - for managing events, and people following, discussing, and signing-up for and attending them - <a href="https://events.oeru.org">https://events.oeru.org</a> </li> <li>a <a href="https://rustdesk.com">RustDesk</a> server instance, allowing us to provide anyone anywhere on just about any computing platform (Linux, Windows, MacOS, iOS, and Android) with live interactive shared desktop support</li> <li>a <a href="https://github.com/PagerTree/prometheus-grafana-alertmanager-example">server monitoring solution</a> based on Grafana, Prometheus, Node-exporter, Alertmanager, cAdvisor, and other tools, which provide a graphical monitoring solution for each of our servers.</li> </ul><p>We also maintain development and testing/staging instances of most of these services. Our services are all hosted on virtual Linux servers (we mostly run Ubuntu Linux) provided by commodity cloud hosting providers via <a href="https://docs.docker.com/engine/install/ubuntu/">Docker</a> and orchestrated via <a href="https://docs.docker.com/compose/install/">Docker Compose</a>. As we reported last year, in time it's likely we'll move to "just-in-time" scaling via Kubernetes, but for now that'd be overkill.</p> <h2><a id="user-content-hosting-on-behalf" href="#hosting-on-behalf" name="hosting-on-behalf" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Hosting on behalf</h2> <p>In the past year, we've started hosting services on behalf of other organisations, including <a href="https://col.org">Commonwealth of Learning</a>, <a href="https://oeglobal.org">Open Education Global</a>, and the government of Samoa, in particular their <a href="https://mesc.ws">Ministry of Education, Sport, and Culture</a>'s Innovative Lifelong Learning Lab (the MiLLL).</p> <p>For the MiLLL project:</p> <ul><li>a pair of WordPress Multisites, one for hosting open educational resource-based course sites - <a href="https://course.milll.ws">https://course.milll.ws</a>- and one hosting individual sites for Samoan primary and secondary schools - <a href="https://schools.milll.ws">https://schools.milll.ws</a> </li> <li>a BigBlueButton instance supporting learners, educators, and government staff's video conferencing requirements - <a href="https://bbb.milll.ws">https://bbb.milll.ws</a> </li> <li>a Mastodon instance especially for Samoans wanting to participate in digital social media - <a href="https://mastodon.milll.ws">https://mastodon.milll.ws</a> </li> <li>an instance of Rocket.Chat - <a href="https://chat.milll.ws">https://chat.milll.ws</a> </li> <li>an instance of Discourse - <a href="https://forum.milll.ws">https://forum.milll.ws</a> </li> <li>an instance of Moodle - <a href="https://moodle.milll.ws">https://moodle.milll.ws</a> </li> <li>an instance of BitWardern/VaultWarden - <a href="https://safe.milll.ws">https://safe.milll.ws</a> </li> </ul><p>For the <a href="https://col.org">Commonwealth of Learning</a>:</p> <ul><li>a WordPress Multisite for hosting OER-based course sites for the <a href="https://pacificpartnership.col.org/">Pacific Partnership in Open Distance and Flexible Learning initiative</a> - <a href="https://pacificopencourses.col.org">https://pacificopencourses.col.org</a> </li> </ul><p>For <a href="https://oeglobal.org">Open Education Global</a>:</p> <ul><li>a WordPress Multisite for hosting OER-based course sites - <a href="https://course.oeglobal.org">https://course.oeglobal.org</a> </li> </ul><h2><a id="user-content-costs-and-usage" href="#costs-and-usage" name="costs-and-usage" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Costs and Usage</h2> <p>In the past year we have served many thousands of registered users and over 200,000 anonymous learners access our courses (the full content of which are open to all without requiring authentication).</p> <p>It's also important to note that all of these services can be provided at no cost to our collaborators and learners as there are <i>no</i> per-seat license fees (nor <i>any</i> license fees) associated with any of the services. Our only costs are related to the fairly generic 'cloud'-based virtual servers (all running the FOSS Linux operating system) we hire from a host of competing commodity hosting providers.</p> <p>Our entire annual IT infrastructure cost, including for our 'on behalf' hosting partners, was comfortably less than USD10,000. What's more our usage monitoring suggests that we were operating below 10% capacity, meaning that we have a <i>lot</i> of additional headroom.</p></div> </div> </div> <section class="field field-node--field-blog-comments field-name-field-blog-comments field-type-comment field-label-above comment-wrapper"> <a name="comments"></a> <div class="comment-form-wrapper"> <h2 class="comment-form__title">Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=53&amp;2=field_blog_comments&amp;3=comment" token="7V4E5uyf0g3obVq8V89F0_43uNHUEiYYPXC9rAs6xDs"></drupal-render-placeholder> </div> </section> Mon, 08 Aug 2022 04:38:07 +0000 dave 53 at http://tech.oeru.org http://tech.oeru.org/oeru-web-services-august-2022#comments Installing and Upgrading Moodle with Docker Compose on Ubuntu 22.04 http://tech.oeru.org/installing-and-upgrading-moodle-docker-compose-ubuntu-2204 <span class="field field--name-title field--type-string field--label-hidden">Installing and Upgrading Moodle with Docker Compose on Ubuntu 22.04</span> <div class="field field-node--field-blog-tags field-name-field-blog-tags field-type-entity-reference field-label-above"> <h3 class="field__label">Blog tags</h3> <div class="field__items"> <div class="field__item field__item--ubuntu-linux"> <span class="field__item-wrapper"><a href="/taxonomy/term/12" hreflang="en">ubuntu linux</a></span> </div> <div class="field__item field__item--_204"> <span class="field__item-wrapper"><a href="/taxonomy/term/85" hreflang="en">22.04</a></span> </div> <div class="field__item field__item--docker-compose"> <span class="field__item-wrapper"><a href="/taxonomy/term/25" hreflang="en">docker compose</a></span> </div> <div class="field__item field__item--docker"> <span class="field__item-wrapper"><a href="/taxonomy/term/16" hreflang="en">docker</a></span> </div> <div class="field__item field__item--mysql"> <span class="field__item-wrapper"><a href="/taxonomy/term/83" hreflang="en">mysql</a></span> </div> <div class="field__item field__item--mariadb"> <span class="field__item-wrapper"><a href="/taxonomy/term/48" hreflang="en">mariadb</a></span> </div> <div class="field__item field__item--nginx"> <span class="field__item-wrapper"><a href="/taxonomy/term/30" hreflang="en">nginx</a></span> </div> <div class="field__item field__item--lets-encrypt"> <span class="field__item-wrapper"><a href="/taxonomy/term/17" hreflang="en">let&#039;s encrypt</a></span> </div> </div> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><a title="View user profile." href="/user/1" lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="" class="username">dave</a></span> <span class="field field--name-created field--type-created field--label-hidden">Mon 23/05/2022 - 11:13</span> <div class="field field-node--field-image field-name-field-image field-type-image field-label-hidden has-multiple"> <figure class="field-type-image__figure image-count-1"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-05/Moodle-DNS_config-cropped-highlighted.png?itok=hZ5bW5gV" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;Showing the specific &#039;CNAME&#039; line pointing moodletest.milll.ws to the server called sandbox.milll.ws.&quot;}" role="button" title="Showing the specific &#039;CNAME&#039; line pointing moodletest.milll.ws to the server called sandbox.milll.ws." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;Showing the specific &#039;CNAME&#039; line pointing moodletest.milll.ws to the server called sandbox.milll.ws.&quot;}"><img src="/sites/default/files/styles/medium/public/2022-05/Moodle-DNS_config-cropped-highlighted.png?itok=9bxNzleE" width="220" height="87" alt="Showing the specific &#039;CNAME&#039; line pointing moodletest.milll.ws to the server called sandbox.milll.ws." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-2"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-05/Moodle-DNS_config-2022-05-23-milll.ws%20-%20Metaname.png?itok=v3q9RKz5" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;Configuring a Domain Name (moodletest.milll.ws) for this Moodle instance.&quot;}" role="button" title="Configuring a Domain Name (moodletest.milll.ws) for this Moodle instance." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;Configuring a Domain Name (moodletest.milll.ws) for this Moodle instance.&quot;}"><img src="/sites/default/files/styles/medium/public/2022-05/Moodle-DNS_config-2022-05-23-milll.ws%20-%20Metaname.png?itok=HHzZlKDm" width="220" height="165" alt="Configuring a Domain Name (moodletest.milll.ws) for this Moodle instance." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-3"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-05/Moode-Install-page.png?itok=8Iq6c32v" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;Initiating the Moodle install process (no CSS). &quot;}" role="button" title="Initiating the Moodle install process (no CSS). " data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;Initiating the Moodle install process (no CSS). &quot;}"><img src="/sites/default/files/styles/medium/public/2022-05/Moode-Install-page.png?itok=fLzRGa4w" width="220" height="82" alt="Initiating the Moodle install process (no CSS). " loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-4"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-05/Moode-Install-MariaDB_driver-page.png?itok=Aex-biDT" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;The Moodle database driver selection page (no CSS).&quot;}" role="button" title="The Moodle database driver selection page (no CSS)." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;The Moodle database driver selection page (no CSS).&quot;}"><img src="/sites/default/files/styles/medium/public/2022-05/Moode-Install-MariaDB_driver-page.png?itok=yV9Eu1aR" width="220" height="82" alt="The Moodle database driver selection page (no CSS)." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-5"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-05/Moode-Install-MariaDB_install-page.png?itok=jJbDC0wz" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;The Moodle install database configuration page (no CSS).&quot;}" role="button" title="The Moodle install database configuration page (no CSS)." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;The Moodle install database configuration page (no CSS).&quot;}"><img src="/sites/default/files/styles/medium/public/2022-05/Moode-Install-MariaDB_install-page.png?itok=MFiSpaGO" width="220" height="82" alt="The Moodle install database configuration page (no CSS)." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-6"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-05/Moode-Installing-page.png?itok=DRZOwdri" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;The Moodle install page, missing CSS. &quot;}" role="button" title="The Moodle install page, missing CSS. " data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;The Moodle install page, missing CSS. &quot;}"><img src="/sites/default/files/styles/medium/public/2022-05/Moode-Installing-page.png?itok=NbWTqjli" width="220" height="113" alt="The Moodle install page, missing CSS. " loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-7"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-05/Moode-Install-admin-page-withCSS.png?itok=M2wy0fSS" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;The completed Moodle install confirmation page, with CSS enabled. &quot;}" role="button" title="The completed Moodle install confirmation page, with CSS enabled. " data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;The completed Moodle install confirmation page, with CSS enabled. &quot;}"><img src="/sites/default/files/styles/medium/public/2022-05/Moode-Install-admin-page-withCSS.png?itok=zvN4fPDG" width="179" height="220" alt="The completed Moodle install confirmation page, with CSS enabled. " loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-8"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-05/Moode-Install-site_registration.png?itok=_n0ff61n" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;Once installed, Moodle asks you to register your site so the Moodle community can count installs and you can get updates about new Moodle releases.&quot;}" role="button" title="Once installed, Moodle asks you to register your site so the Moodle community can count installs and you can get updates about new Moodle releases." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;Once installed, Moodle asks you to register your site so the Moodle community can count installs and you can get updates about new Moodle releases.&quot;}"><img src="/sites/default/files/styles/medium/public/2022-05/Moode-Install-site_registration.png?itok=5VtSCN6p" width="220" height="168" alt="Once installed, Moodle asks you to register your site so the Moodle community can count installs and you can get updates about new Moodle releases." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-9"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-05/Moode-Installed-dashboard.png?itok=pyXdyK4T" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;The first look at the Moodle admin dashboard after installation. &quot;}" role="button" title="The first look at the Moodle admin dashboard after installation. " data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;The first look at the Moodle admin dashboard after installation. &quot;}"><img src="/sites/default/files/styles/medium/public/2022-05/Moode-Installed-dashboard.png?itok=ERLiwXLf" width="220" height="168" alt="The first look at the Moodle admin dashboard after installation. " loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> </div> <div class="clearfix text-formatted field field-node--body field-name-body field-type-text-with-summary field-label-hidden"> <div class="field__items"> <div class="field__item"><p><strong>Note: work in progress</strong></p> <p><a href="https://moodle.org">Moodle</a> is probably (there's little agreement among the pundits) the <a href="https://webtechsurvey.com/technology-type/learning-management-system">market-leading</a> Free and Open Source Software (FOSS) Learning Management System (LMS). It's pretty much everywhere. Here's how you can set up and maintain your own Moodle instance(s).</p> <ul class="table-of-contents"><li> <p><a href="#prerequisites">Prerequisites</a></p> </li> <li> <p><a href="#installing-moodle-on-ubuntu-2204-with-docker-compose">Installing Moodle on Ubuntu 22.04 with Docker Compose</a></p> <ul><li> <p><a href="#configure-a-domain-name">Configure a domain name</a></p> </li> <li> <p><a href="#install-the-moodle-source-code">Install the Moodle source code</a></p> </li> <li> <p><a href="#configure-docker-compose-to-host-moodle">Configure Docker Compose to host Moodle</a></p> </li> <li> <p><a href="#create-a-secure-reverse-proxy-configuration">Create a secure reverse-proxy configuration</a></p> </li> <li> <p><a href="#installing-moodle">Installing Moodle</a></p> </li> </ul></li> <li> <p><a href="#backing-up-your-data">Backing up your data</a></p> <ul><li> <p><a href="#mariadb-database-dumps">MariaDB database dumps</a></p> </li> </ul></li> <li> <p><a href="#upgrading-moodle-with-git">Upgrading Moodle with Git</a></p> </li> </ul><h2><a id="user-content-prerequisites" href="#prerequisites" name="prerequisites" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Prerequisites</h2> <p>If you would like to host your own (and we certainly encourage it!) using the same approach we use at the <a href="https://oerfoundation.org">OER Foundation</a> here is how you get started:</p> <ol><li>Set up a Virtual Private Server (VPS) and <a href="/node/42">configure it to run Ubuntu Linux and Docker Compose</a>.</li> <li>Make sure any <a href="/node/49">administrative users can log into the VPS securely</a> via Secure Shell (SSH).</li> </ol><p>From that point, use the following process.</p> <h2><a id="user-content-installing-moodle-on-ubuntu-2204-with-docker-compose" href="#installing-moodle-on-ubuntu-2204-with-docker-compose" name="installing-moodle-on-ubuntu-2204-with-docker-compose" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Installing Moodle on Ubuntu 22.04 with Docker Compose</h2> <p>The process for completing the install involves</p> <ol><li> <p>setting up a domain name (or subdomain) to point at the server so people can easily find the Moodle site,</p> </li> <li> <p>installing all the relevant code via '<a href="https://en.wikipedia.org/wiki/Git">Git</a>' on the VPS' file system,</p> </li> <li> <p>configuring the Docker Compose file, defining the set of Docker containers comprising this site's moving parts, and 'pulling' the component containers (to download the current versions of each to our VPS),</p> </li> <li> <p>adding the 'default configuration' for the Dockerised NGINX instance, so it knows how to server Moodle from the adjacent containers,</p> </li> <li> <p>configuring the NGINX webserver running on the VPS to act as a '<a href="https://en.wikipedia.org/wiki/Reverse_proxy">reverse proxy</a>' as well as using to generate a Let's Encrypt Secure Sockets Layer (SSL) certificate to encrypt and otherwise secure users' interactions with the Moodle instance, and finally</p> </li> <li> <p>configuring the Moodle instance itself.</p> </li> </ol><h3><a id="user-content-configure-a-domain-name" href="#configure-a-domain-name" name="configure-a-domain-name" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Configure a domain name</h3> <p>Configuring a domain name requires us to register a domain with a 'domain registrar' - we use one called <a href="https://metaname.net">Metaname</a>, based here in Christchurch, New Zealand - and using the registrar's browser based configuration system to create a 'zone file' which defines the server to which the domain points. Each registrar is likely to have their own bespoke zone file configuration tools, so consider this to be just one example.</p> <p>Our convention at the OERF is to allocate a default domain name to each VPS we create. Each one hosts one or more Free and Open Source Software services. Each of those services has its own domain name. Usually we designate "A" and "AAAA" records for the default domain name pointing to the IPv4 and IPv6 addresses given to the VPS by the cloud services provider.</p> <p>For the purposes of this tutorial, we'll create a site called <strong>moodletest.milll.ws</strong> which is the <code>moodletest</code> subdomain of the <code>milll.ws</code> (Ministry for Education, Sport, and Culture's 'innovative Lifelong Learning Lab', aka MiLLL, in (western) Samoa). We use a "CNAME" to point the subdomain at the VPS' official name, <code>sandbox.milll.ws</code>, which is how its A and AAAA records identify it (via its IPv4 and IPv6 addresses).</p> <p>So our "Fully Qualified Domain Name" (FQDN) is <code>moodletest.milll.ws</code>.</p> <p>To prepare for the rest of this tutorial, we're going to set a variable for your selected domain name. Do that by typing this at your command prompt. It will set this variable ''for this session'':</p> <p><code>FQDN=your domain name</code></p> <p>in our case, it'd be</p> <p><code>FQDN=moodletest.milll.ws</code></p> <p>The following will assuming that this value is set correctly.</p> <h3><a id="user-content-install-the-moodle-source-code" href="#install-the-moodle-source-code" name="install-the-moodle-source-code" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Install the Moodle source code</h3> <p>Before we can install the Moodle source code, we have to prepare a sensible place for it to live. We do that by creating suitable directories for both the Docker configurations and the Moodle instance data and configuration as follows:</p> <p><code>sudo mkdir -p /home/docker/${FQDN} /home/data/${FQDN}</code></p> <p>That creates two directories as you can see. We need to go into the data directory:</p> <p><code>cd /home/data/${FQDN}</code></p> <p>and in there, we'll execute this git command to download the entire Moodle codebase:</p> <p><code>sudo git clone https://github.com/moodle/moodle.git src</code></p> <p>When that finishes, go into the source directory:</p> <p><code>cd src</code></p> <p>Here, we'll have to execute this new command, which we use to declare this a 'safe' git directory, which helps protect us from nefarious parties running git commands in dangerous places if they can get access to our server via some other security vulnerability...</p> <p><code>sudo git config --global --add safe.directory /home/data/${FQDN}</code></p> <p>Then we can run this comman:</p> <p><code>git branch -a</code></p> <p>This should give you a list of all the Moodle repository version 'tags'.</p> <p>In this case, we're going to install a stable (but not the latest) version of Moodle - to do that we tell git what tag we're going to track:</p> <p><code>sudo git branch --track MOODLE_310_STABLE origin/MOODLE_310_STABLE</code></p> <p>And then, to make sure that our Docker container's nginx webserver can read these files (they need to be owned by the <code>www-data</code> users, and I (user 'dave') can run git commands without needing to use <code>sudo</code>. Replace with your own user name here!</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="kw2">sudo</span> <span class="kw2">chown</span> <span class="re5">-R</span> www-data:dave ..<span class="sy0">/</span>src <span class="kw2">sudo</span> <span class="kw2">chmod</span> <span class="re5">-R</span> g+<span class="kw2">w</span> ..<span class="sy0">/</span>src <span class="kw2">ls</span> <span class="re5">-l</span> <span class="kw2">git status</span> <span class="kw2">git checkout</span> MOODLE_310_STABLE</pre></div></div> <h3><a id="user-content-configure-docker-compose-to-host-moodle" href="#configure-docker-compose-to-host-moodle" name="configure-docker-compose-to-host-moodle" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Configure Docker Compose to host Moodle</h3> <p>The first step to running anything with Docker Compose - which helps you combine a set of Docker containers together into a useful, maintainable service - is to create <code>docker-compose.yml</code> file. It usually contains the full 'recipe' for the service.</p> <p>Go to the relevant docker directory and create and edit the file:</p> <p><code>cd /home/docker/${FQDN}</code></p> <p><code>sudo nano docker-compose.yml</code></p> <p>And copy and past in this content (with <strong>FQDN</strong> occurrences and <strong>[secret password]</strong> placeholders to be replaced appropriately, of course):</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">version: <span class="st_h">'3'</span>   services: mariadb: image: mariadb:<span class="nu0">10</span> container_name: db environment: MYSQL_RANDOM_ROOT_PASSWORD: <span class="nu0">1</span> MYSQL_DATABASE: moodle MYSQL_USER: moodle MYSQL_PASSWORD: <span class="br0">[</span>secret MariaDB password<span class="br0">]</span> volumes: - <span class="sy0">/</span>home<span class="sy0">/</span>data<span class="sy0">/</span>FQDN<span class="sy0">/</span>mariadb:<span class="sy0">/</span>var<span class="sy0">/</span>lib<span class="sy0">/</span>mysql moodle: image: oeru<span class="sy0">/</span>moodle:php74-fpm environment: MOODLE_DOMAIN: FQDN MOODLE_DB_HOST: mariadb MYSQL_PORT_3306_TCP: <span class="nu0">3306</span> MOODLE_DB_NAME: moodle MOODLE_DB_USER: moodle MOODLE_DB_PASSWORD: <span class="br0">[</span>secret MariaDB password<span class="br0">]</span> volumes: - <span class="sy0">/</span>home<span class="sy0">/</span>data<span class="sy0">/</span>FQDN<span class="sy0">/</span>src:<span class="sy0">/</span>var<span class="sy0">/</span>www<span class="sy0">/</span>html - <span class="sy0">/</span>home<span class="sy0">/</span>data<span class="sy0">/</span>FQDN<span class="sy0">/</span>data:<span class="sy0">/</span>var<span class="sy0">/</span>www<span class="sy0">/</span>moodledata restart: unless-stopped depends_on: - mariadb cron: image: oeru<span class="sy0">/</span>moodle-cron:php74-fpm environment: MOODLE_DOMAIN: FQDN MOODLE_DB_HOST: mariadb MYSQL_PORT_3306_TCP: <span class="nu0">3306</span> MOODLE_DB_NAME: moodle MOODLE_DB_USER: moodle MOODLE_DB_PASSWORD: <span class="br0">[</span>secret MariaDB password<span class="br0">]</span> volumes: - <span class="sy0">/</span>home<span class="sy0">/</span>data<span class="sy0">/</span>FQDN<span class="sy0">/</span>src:<span class="sy0">/</span>var<span class="sy0">/</span>www<span class="sy0">/</span>html - <span class="sy0">/</span>home<span class="sy0">/</span>data<span class="sy0">/</span>FQDN<span class="sy0">/</span>data:<span class="sy0">/</span>var<span class="sy0">/</span>www<span class="sy0">/</span>moodledata restart: unless-stopped depends_on: - mariadb nginx: image: nginx depends_on: - moodle ports: - 127.0.0.1:<span class="nu0">8080</span>:<span class="nu0">80</span> volumes: - <span class="sy0">/</span>home<span class="sy0">/</span>data<span class="sy0">/</span>FQDN<span class="sy0">/</span>nginx:<span class="sy0">/</span>etc<span class="sy0">/</span>nginx<span class="sy0">/</span>conf.d - <span class="sy0">/</span>home<span class="sy0">/</span>data<span class="sy0">/</span>FQDN<span class="sy0">/</span>src:<span class="sy0">/</span>var<span class="sy0">/</span>www<span class="sy0">/</span>html restart: unless-stopped</pre></div></div> <p>And, naturally, save the file.</p> <p>Note, the last stanza of the <code>docker-compose.yml</code> file, defining the NGINX component, specifies a port number on the host machine (i.e. 'localhost' or 127.0.0.1) of 8080, which in turn points to port 80 on the NGINX container. If this is the only Docker Compose configuration on your server, then 8080 is probably an avaiable port to use, but if you server has lots of other Docker Compose (or other sorts of services on it) port 8080 might not be available. You can check by running</p> <p><code>sudo netstat -punta</code></p> <p>If <code>netstat</code> isn't installed, you can install it via <code>sudo apt install net-tools</code>.</p> <p>Running this command will give output that looks something like this:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">Active Internet connections <span class="br0">(</span>servers and established<span class="br0">)</span> Proto Recv-Q Send-Q Local Address Foreign Address State PID<span class="sy0">/</span>Program name tcp <span class="nu0">0</span> <span class="nu0">0</span> 0.0.0.0:<span class="nu0">80</span> 0.0.0.0:<span class="sy0">*</span> LISTEN <span class="nu0">11199</span><span class="sy0">/</span>nginx: master tcp <span class="nu0">0</span> <span class="nu0">0</span> 0.0.0.0:<span class="nu0">25</span> 0.0.0.0:<span class="sy0">*</span> LISTEN <span class="nu0">60021</span><span class="sy0">/</span>master tcp <span class="nu0">0</span> <span class="nu0">0</span> 0.0.0.0:<span class="nu0">22</span> 0.0.0.0:<span class="sy0">*</span> LISTEN <span class="nu0">59473</span><span class="sy0">/</span>sshd: <span class="sy0">/</span>usr<span class="sy0">/</span>sb tcp <span class="nu0">0</span> <span class="nu0">0</span> 127.0.0.53:<span class="nu0">53</span> 0.0.0.0:<span class="sy0">*</span> LISTEN <span class="nu0">11263</span><span class="sy0">/</span>systemd-resol tcp <span class="nu0">0</span> <span class="nu0">0</span> 127.0.0.1:<span class="nu0">8080</span> 0.0.0.0:<span class="sy0">*</span> LISTEN <span class="nu0">94497</span><span class="sy0">/</span>docker-proxy tcp <span class="nu0">0</span> <span class="nu0">0</span> 127.0.0.1:<span class="nu0">6010</span> 0.0.0.0:<span class="sy0">*</span> LISTEN <span class="nu0">13691</span><span class="sy0">/</span>sshd: dave<span class="sy0">@</span>pt tcp <span class="nu0">0</span> <span class="nu0">0</span> 127.0.0.1:<span class="nu0">6011</span> 0.0.0.0:<span class="sy0">*</span> LISTEN <span class="nu0">13097</span><span class="sy0">/</span>sshd: dave<span class="sy0">@</span>pt tcp <span class="nu0">0</span> <span class="nu0">0</span> 147.182.232.12:<span class="nu0">22</span> 203.118.159.63:<span class="nu0">39248</span> ESTABLISHED <span class="nu0">13044</span><span class="sy0">/</span>sshd: dave <span class="br0">[</span>p tcp <span class="nu0">0</span> <span class="nu0">0</span> 147.182.232.12:<span class="nu0">22</span> 202.4.38.248:<span class="nu0">54655</span> ESTABLISHED <span class="nu0">94900</span><span class="sy0">/</span>sshd: william tcp <span class="nu0">0</span> <span class="nu0">0</span> 147.182.232.12:<span class="nu0">22</span> 202.4.38.248:<span class="nu0">53108</span> ESTABLISHED <span class="nu0">91961</span><span class="sy0">/</span>sshd: sialofi tcp <span class="nu0">0</span> <span class="nu0">52</span> 147.182.232.12:<span class="nu0">22</span> 203.118.159.63:<span class="nu0">39650</span> ESTABLISHED <span class="nu0">13638</span><span class="sy0">/</span>sshd: dave <span class="br0">[</span>p tcp <span class="nu0">0</span> <span class="nu0">0</span> 147.182.232.12:<span class="nu0">22</span> 202.4.38.248:<span class="nu0">62921</span> ESTABLISHED <span class="nu0">92704</span><span class="sy0">/</span>sshd: eleanor tcp6 <span class="nu0">0</span> <span class="nu0">0</span> :::<span class="nu0">80</span> :::<span class="sy0">*</span> LISTEN <span class="nu0">11199</span><span class="sy0">/</span>nginx: master tcp6 <span class="nu0">0</span> <span class="nu0">0</span> :::<span class="nu0">25</span> :::<span class="sy0">*</span> LISTEN <span class="nu0">60021</span><span class="sy0">/</span>master tcp6 <span class="nu0">0</span> <span class="nu0">0</span> :::<span class="nu0">22</span> :::<span class="sy0">*</span> LISTEN <span class="nu0">59473</span><span class="sy0">/</span>sshd: <span class="sy0">/</span>usr<span class="sy0">/</span>sb tcp6 <span class="nu0">0</span> <span class="nu0">0</span> ::<span class="nu0">1</span>:<span class="nu0">6010</span> :::<span class="sy0">*</span> LISTEN <span class="nu0">13691</span><span class="sy0">/</span>sshd: dave<span class="sy0">@</span>pt tcp6 <span class="nu0">0</span> <span class="nu0">0</span> ::<span class="nu0">1</span>:<span class="nu0">6011</span> :::<span class="sy0">*</span> LISTEN <span class="nu0">13097</span><span class="sy0">/</span>sshd: dave<span class="sy0">@</span>pt udp <span class="nu0">0</span> <span class="nu0">0</span> 127.0.0.53:<span class="nu0">53</span> 0.0.0.0:<span class="sy0">*</span> <span class="nu0">11263</span><span class="sy0">/</span>systemd-resol</pre></div></div> <p>You'll notice that in this case, the 7th line says</p> <p><code>tcp 0 0 127.0.0.1:8080 0.0.0.0:* LISTEN 94497/docker-proxy</code></p> <p>which means that port 8080 is already in use with 127.0.0.1 on the server and trying to start my containers with that port defined would result in a 'port unavailable' error.</p> <p>Here's a quick one-liner allowing you to check whether any particular port is in use right now (replace '8080' with whatever port number you're checking):</p> <p><code>sudo netstat -punta | grep '8080'</code></p> <p>if it returns nothing, that port is ''currently'' unused. If it returns a line like</p> <p><code>tcp 0 0 127.0.0.1:8080 0.0.0.0:* LISTEN 94497/docker-proxy</code></p> <p>it's in use and you'll need to choose another port, e.g. 8081 or 8070 or something else. Ports over 8000 are unlikely to be used by most normal software so should be fair game. You can verify that your selected port is available before launching your containers.</p> <h4><a id="user-content-download-your-containers-from-docker-hub" href="#download-your-containers-from-docker-hub" name="download-your-containers-from-docker-hub" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Download your containers from Docker Hub</h4> <p>You should then be able to 'pull' the relevant Docker images (might take a while depending on your server's location and available bandwidth):</p> <p><code>sudo docker-compose pull</code></p> <p>Once that's done, we need to make sure that our container running NGINX webserver has a valid Moodle-specific configuration.</p> <h4><a id="user-content-configure-nginx-container-for-moodle" href="#configure-nginx-container-for-moodle" name="configure-nginx-container-for-moodle" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Configure NGINX container for Moodle</h4> <p>To do that, we're going to create an NGINX default configuration for the Moodle containers by creating and editing the file:</p> <p><code>sudo nano /home/data/${FQDN}/nginx/default.conf</code></p> <p>copying and pasting in the following content. It shouldn't require any changes.</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co0"># This configuration is used by a Docker container running nginx, and is not intended to be</span> <span class="co0"># directly connected to the Internet (for that we recommend using SSL on port 443).</span> <span class="co0"># You should reverse proxy this container on the Docker host using whatever webserver</span> <span class="co0"># you have running there.</span> <span class="co0">#</span> server <span class="br0">{</span> listen <span class="nu0">80</span> default_server;   <span class="co0"># default path to Mautic - from the point of view of the docker container</span> root <span class="sy0">/</span>var<span class="sy0">/</span>www<span class="sy0">/</span>html; index index.php index.html index.htm; fastcgi_keep_conn on; <span class="co0"># keep alive to the FCGI upstream</span> location <span class="sy0">/</span> <span class="br0">{</span> <span class="co0"># First attempt to serve request as file</span> try_files <span class="re1">$uri</span> <span class="re1">$uri</span><span class="sy0">/</span>index.php;   <span class="co0"># moodle rewrite rules</span> rewrite ^<span class="sy0">/</span><span class="br0">(</span>.<span class="sy0">*</span>.php<span class="br0">)</span><span class="br0">(</span><span class="sy0">/</span><span class="br0">)</span><span class="br0">(</span>.<span class="sy0">*</span><span class="br0">)</span>$ <span class="sy0">/</span><span class="re4">$1</span>?<span class="re2">file</span>=<span class="sy0">/</span><span class="re4">$3</span> <span class="kw2">last</span>; <span class="br0">}</span> <span class="co0"># php parsing</span>   location ~ ^<span class="br0">(</span>.+\.php<span class="br0">)</span><span class="br0">(</span>.<span class="sy0">*</span><span class="br0">)</span>$ <span class="br0">{</span> fastcgi_split_path_info ^<span class="br0">(</span>.+\.php<span class="br0">)</span><span class="br0">(</span>.<span class="sy0">*</span><span class="br0">)</span>$; fastcgi_index index.php; fastcgi_pass moodle:<span class="nu0">9000</span>; include <span class="sy0">/</span>etc<span class="sy0">/</span>nginx<span class="sy0">/</span>mime.types; include fastcgi_params; fastcgi_param PATH_INFO <span class="re1">$fastcgi_path_info</span>; fastcgi_param SCRIPT_FILENAME <span class="re1">$document_root</span><span class="re1">$fastcgi_script_name</span>; fastcgi_buffer_size 128k; fastcgi_buffers <span class="nu0">256</span> 4k; fastcgi_busy_buffers_size 256k; fastcgi_temp_file_write_size 256k; client_max_body_size 100M; <span class="br0">}</span> add_header <span class="st_h">'Access-Control-Allow-Origin'</span> <span class="st0">"*"</span>; <span class="br0">}</span></pre></div></div> <p>Once this is in place, you can start up the Docker containers like this (as your own user!):</p> <p><code>docker-compose up -d &amp;&amp; docker-compose logs -f</code></p> <p>which will start the containers (the <code>up -d</code>) in 'daemon mode', where they continue to run even if you log out of the server, and then show you the logging from the containers (the <code>logs -f</code>) part. To exit the log view (which will update automatically as more log messages are created), hit the <code>CTRL-C</code> key combination. If there're any errors from your individual containers, they should visible here.</p> <p>That's it, your Docker containers should be running. Once you're out of the log view, you can always check on your containers by going into the <code>/home/docker/FQDN</code> directory and executing:</p> <p><code>docker-compose ps</code></p> <p>which should show you a list of the currently running containers and their states. Here's an example:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"> Name Command State Ports <span class="re5">--------------------------------------------------------------------------------------------</span> db docker-entrypoint.sh mariadbd Up <span class="nu0">3306</span><span class="sy0">/</span>tcp moodletestmilllws_cron_1 <span class="sy0">/</span>entrypoint.sh <span class="sy0">/</span>bin<span class="sy0">/</span><span class="kw2">sh</span> <span class="re5">-c</span> ... Up <span class="nu0">9000</span><span class="sy0">/</span>tcp moodletestmilllws_moodle_1 <span class="sy0">/</span>entrypoint.sh php-fpm Up <span class="nu0">9000</span><span class="sy0">/</span>tcp moodletestmilllws_nginx_1 <span class="sy0">/</span>docker-entrypoint.sh ngin ... Up 127.0.0.1:<span class="nu0">8080</span>-<span class="sy0">&gt;</span><span class="nu0">80</span><span class="sy0">/</span>tcp</pre></div></div> <p>or, to get a bit more info, you can run</p> <p><code>docker-compose top</code></p> <p>which should show you a bit more information about the different running containers and their active processes. Here's an example:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">db UID PID PPID C STIME TTY TIME CMD <span class="re5">-----------------------------------------------------------</span> lxd <span class="nu0">12056</span> <span class="nu0">12035</span> <span class="nu0">0</span> May23 ? 00:04:06 mariadbd   moodletestmilllws_cron_1 UID PID PPID C STIME TTY TIME CMD <span class="re5">------------------------------------------------------------------------------------------------</span> root <span class="nu0">12244</span> <span class="nu0">12201</span> <span class="nu0">0</span> May23 ? 00:00:00 <span class="sy0">/</span>bin<span class="sy0">/</span><span class="kw2">sh</span> <span class="re5">-c</span> cron <span class="sy0">&amp;&amp;</span> <span class="kw2">tail</span> <span class="re5">-f</span> <span class="sy0">/</span>var<span class="sy0">/</span>log<span class="sy0">/</span>cron.log root <span class="nu0">12651</span> <span class="nu0">12244</span> <span class="nu0">0</span> May23 ? 00:00:02 cron root <span class="nu0">12652</span> <span class="nu0">12244</span> <span class="nu0">0</span> May23 ? 00:00:<span class="nu0">13</span> <span class="kw2">tail</span> <span class="re5">-f</span> <span class="sy0">/</span>var<span class="sy0">/</span>log<span class="sy0">/</span>cron.log   moodletestmilllws_moodle_1 UID PID PPID C STIME TTY TIME CMD <span class="re5">-------------------------------------------------------------------------------------------------------------</span> root <span class="nu0">12250</span> <span class="nu0">12173</span> <span class="nu0">0</span> May23 ? 00:00:<span class="nu0">12</span> php-fpm: master process <span class="br0">(</span><span class="sy0">/</span>usr<span class="sy0">/</span>local<span class="sy0">/</span>etc<span class="sy0">/</span>php-fpm.conf<span class="br0">)</span> www-data <span class="nu0">37755</span> <span class="nu0">12250</span> <span class="nu0">2</span> 01:<span class="nu0">46</span> ? 00:00:00 php-fpm: pool www www-data <span class="nu0">37757</span> <span class="nu0">12250</span> <span class="nu0">1</span> 01:<span class="nu0">46</span> ? 00:00:00 php-fpm: pool www www-data <span class="nu0">37758</span> <span class="nu0">12250</span> <span class="nu0">1</span> 01:<span class="nu0">46</span> ? 00:00:00 php-fpm: pool www www-data <span class="nu0">37759</span> <span class="nu0">12250</span> <span class="nu0">0</span> 01:<span class="nu0">46</span> ? 00:00:00 php-fpm: pool www   moodletestmilllws_nginx_1 UID PID PPID C STIME TTY TIME CMD <span class="re5">--------------------------------------------------------------------------------------------------</span> root <span class="nu0">13207</span> <span class="nu0">13179</span> <span class="nu0">0</span> May23 ? 00:00:00 nginx: master process nginx <span class="re5">-g</span> daemon off; systemd+ <span class="nu0">13333</span> <span class="nu0">13207</span> <span class="nu0">0</span> May23 ? 00:00:01 nginx: worker process systemd+ <span class="nu0">13334</span> <span class="nu0">13207</span> <span class="nu0">0</span> May23 ? 00:00:00 nginx: worker process </pre></div></div> <p>So, now we should have all the relevant containers for your Moodle instance running... Al that's left to do is hook up the reverse proxy configuration creating that last vital link: between your browser's request and the Moodle service (secured by Let's Encrypt)!</p> <h3><a id="user-content-create-a-secure-reverse-proxy-configuration" href="#create-a-secure-reverse-proxy-configuration" name="create-a-secure-reverse-proxy-configuration" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Create a secure reverse-proxy configuration</h3> <p>The first step to doing that is to create a reverse proxy configuration:</p> <p><code>sudo nano /etc/nginx/sites-available/${FQDN}</code></p> <p>Copy and paste the following into the file. Of course, you will want to replace every occurrence of 'FQDN' with your own domain name.</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">server <span class="br0">{</span> <span class="co0"># add [IP-Address:]80 in the next line if you want to limit this to a single interface</span> listen <span class="nu0">80</span>; listen <span class="br0">[</span>::<span class="br0">]</span>:<span class="nu0">80</span>;   server_name FQDN;   root <span class="sy0">/</span>var<span class="sy0">/</span>www<span class="sy0">/</span>html;   <span class="co0"># for let's encrypt renewals!</span> include includes<span class="sy0">/</span>letsencrypt.conf;   access_log <span class="sy0">/</span>var<span class="sy0">/</span>log<span class="sy0">/</span>nginx<span class="sy0">/</span>FQDN_access.log; error_log <span class="sy0">/</span>var<span class="sy0">/</span>log<span class="sy0">/</span>nginx<span class="sy0">/</span>FQDN_error.log;   <span class="co0"># redirect all HTTP traffic to HTTPS.</span> location <span class="sy0">/</span> <span class="br0">{</span> <span class="kw3">return</span> <span class="nu0">302</span> https:<span class="sy0">//</span><span class="re1">$server_name</span><span class="re1">$request_uri</span>; <span class="br0">}</span> <span class="br0">}</span>   <span class="co0"># This configuration assumes that there's an nginx container talking to the moodle PHP-fpm container,</span> <span class="co0"># and this is a reverse proxy for that Moodle instance.</span> fastcgi_cache_path <span class="sy0">/</span>etc<span class="sy0">/</span>nginx<span class="sy0">/</span>cache <span class="re2">levels</span>=<span class="nu0">1</span>:<span class="nu0">2</span> <span class="re2">keys_zone</span>=moodle:100m <span class="re2">inactive</span>=1d <span class="re2">max_size</span>=100m; fastcgi_cache_key <span class="st0">"<span class="es2">$scheme</span><span class="es2">$request_method</span><span class="es2">$host</span><span class="es2">$request_uri</span>"</span>;   server <span class="br0">{</span> <span class="co0"># add [IP-Address:]443 in the next line if you want to limit this to a single interface</span> listen <span class="nu0">443</span> ssl; listen <span class="br0">[</span>::<span class="br0">]</span>:<span class="nu0">443</span> ssl;   ssl_certificate <span class="sy0">/</span>etc<span class="sy0">/</span>ssl<span class="sy0">/</span>certs<span class="sy0">/</span>ssl-cert-snakeoil.pem; ssl_certificate_key <span class="sy0">/</span>etc<span class="sy0">/</span>ssl<span class="sy0">/</span>private<span class="sy0">/</span>ssl-cert-snakeoil.key; <span class="co0"># ssl_certificate /etc/letsencrypt/live/FQDN/fullchain.pem;</span> <span class="co0"># ssl_certificate_key /etc/letsencrypt/live/FQDN/privkey.pem;</span> ssl_protocols TLSv1 TLSv1.1 TLSv1.2;   <span class="co0"># A strong ssl_dhparam will help secure your server from certain kinds of attacks. </span> <span class="co0"># It is commented out unless you've created it (if it's uncommented but doesn't exist</span> <span class="co0"># nginx won't start). To create one, see </span> <span class="co0"># https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html</span> <span class="co0"># note: it can take 5-30 minutes to create one.</span> <span class="co0">#ssl_dhparam /etc/ssl/certs/dhparam.pem;</span> <span class="co0">#ssl_stapling on;</span> <span class="co0">#ssl_stapling_verify on;</span>   ssl_session_tickets off; resolver_timeout 5s; keepalive_timeout 20s;   server_name FQDN;   <span class="co0"># for let's encrypt renewals!</span> include includes<span class="sy0">/</span>letsencrypt.conf;   access_log <span class="sy0">/</span>var<span class="sy0">/</span>log<span class="sy0">/</span>nginx<span class="sy0">/</span>FQDN_access.log; error_log <span class="sy0">/</span>var<span class="sy0">/</span>log<span class="sy0">/</span>nginx<span class="sy0">/</span>FQDN_error.log;   client_max_body_size 100M;   location <span class="sy0">/</span> <span class="br0">{</span> proxy_pass http:<span class="sy0">//</span>127.0.0.1:<span class="nu0">8080</span>; proxy_set_header Upgrade <span class="re1">$http_upgrade</span>; proxy_set_header Connection <span class="st0">"upgrade"</span>; proxy_set_header Host <span class="re1">$http_host</span>; <span class="br0">}</span> <span class="br0">}</span></pre></div></div> <p>Once you've saved that file, we need to make sure NGINX can see the configuration - we do that by creating a 'symbolic link' between our configuration file in the <code>sites-available</code> directory and the <code>sites-enabled</code> directory as follows:</p> <p><code>sudo ln -sf /etc/nginx/sites-available/${FQDN} /etc/nginx/sites-enabled/</code></p> <p>Before we can restart NGINX with this new configuration, we have to make sure that the 'letsencrypt.conf' cited in our configuration file exists. We do that by creating a new directory:</p> <p><code>sudo mkdir /etc/nginx/includes</code></p> <p>and creating the <code>letsencrypt.conf</code> file:</p> <p><code>sudo nano /etc/nginx/includes/letsencrypt.conf</code></p> <p>into which we copy and paste the following:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co0">#############################################################################</span> <span class="co0"># Configuration file for Let's Encrypt ACME Challenge location</span> <span class="co0"># This file is already included in listen_xxx.conf files.</span> <span class="co0"># Do NOT include it separately!</span> <span class="co0">#############################################################################</span> <span class="co0">#</span> <span class="co0"># This config enables to access /.well-known/acme-challenge/xxxxxxxxxxx</span> <span class="co0"># on all our sites (HTTP), including all subdomains.</span> <span class="co0"># This is required by ACME Challenge (webroot authentication).</span> <span class="co0"># You can check that this location is working by placing ping.txt here:</span> <span class="co0"># /var/www/letsencrypt/.well-known/acme-challenge/ping.txt</span> <span class="co0"># And pointing your browser to:</span> <span class="co0"># http://xxx.domain.tld/.well-known/acme-challenge/ping.txt</span> <span class="co0">#</span> <span class="co0"># Sources:</span> <span class="co0"># https://community.letsencrypt.org/t/howto-easy-cert-generation-and-renewal-with-nginx/3491</span> <span class="co0">#</span> <span class="co0"># Rule for legitimate ACME Challenge requests</span> location ^~ <span class="sy0">/</span>.well-known<span class="sy0">/</span>acme-challenge<span class="sy0">/</span> <span class="br0">{</span> default_type <span class="st0">"text/plain"</span>; <span class="co0"># this can be any directory, but this name keeps it clear</span> root <span class="sy0">/</span>var<span class="sy0">/</span>www<span class="sy0">/</span>letsencrypt; <span class="br0">}</span> <span class="co0"># Hide /acme-challenge subdirectory and return 404 on all requests.</span> <span class="co0"># It is somewhat more secure than letting Nginx return 403.</span> <span class="co0"># Ending slash is important!</span> location = <span class="sy0">/</span>.well-known<span class="sy0">/</span>acme-challenge<span class="sy0">/</span> <span class="br0">{</span> <span class="kw3">return</span> <span class="nu0">404</span>; <span class="br0">}</span></pre></div></div> <p>In turn, we also have to make sure that the directory stipulated in the letsencrypt.conf file exists - we'll attempt to create the directory, which won't hurt anything if it already exists:</p> <p><code>sudo mkdir /var/www/letsencrypt</code></p> <p>With that done, we can test NGINX's configuration:</p> <p><code>sudo nginx -t</code></p> <p>If it's free of errors (there might be a warning related to the <code>snakeoil</code> ''temporary'' certificates we've specified. That's ok - we just specified them so that we can get NGINX restarted in a way that let's us request an SSL certificate from Let's Encrypt!), we can get NGINX to reload its configuration, so that it'll know how to respond to requests for the FQDN domain!</p> <p><code>sudo service nginx reload</code></p> <p>If that returns without errors, we're in business! It's time to make our Let's Encrypt certificate. Creating one is easy:</p> <p><code>sudo letsencrypt certonly --webroot -w /var/www/letsencrypt -d ${FQDN}</code></p> <p>In our case, it's:</p> <p><code>sudo letsencrypt certonly --webroot -w /var/www/letsencrypt -d moodletest.milll.ws</code></p> <p>Running that should trigger output like this:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">Saving debug log to <span class="sy0">/</span>var<span class="sy0">/</span>log<span class="sy0">/</span>letsencrypt<span class="sy0">/</span>letsencrypt.log Enter email address <span class="br0">(</span>used <span class="kw1">for</span> urgent renewal and security notices<span class="br0">)</span> <span class="br0">(</span>Enter <span class="st_h">'c'</span> to cancel<span class="br0">)</span>: webmaster<span class="sy0">@</span>milll.ws   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Please <span class="kw3">read</span> the Terms of Service at https:<span class="sy0">//</span>letsencrypt.org<span class="sy0">/</span>documents<span class="sy0">/</span>LE-SA-v1.2-November-<span class="nu0">15</span>-<span class="nu0">2017</span>.pdf. You must agree <span class="kw1">in</span> order to register with the ACME server. Do you agree? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <span class="br0">(</span>Y<span class="br0">)</span>es<span class="sy0">/</span><span class="br0">(</span>N<span class="br0">)</span>o: y   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Would you be willing, once your first certificate is successfully issued, to share your email address with the Electronic Frontier Foundation, a founding partner of the Let<span class="st_h">'s Encrypt project and the non-profit organization that develops Certbot? We'</span>d like to send you email about our work encrypting the web, EFF news, campaigns, and ways to support digital freedom. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <span class="br0">(</span>Y<span class="br0">)</span>es<span class="sy0">/</span><span class="br0">(</span>N<span class="br0">)</span>o: y Account registered. Requesting a certificate <span class="kw1">for</span> moodletest.milll.ws   Successfully received certificate. Certificate is saved at: <span class="sy0">/</span>etc<span class="sy0">/</span>letsencrypt<span class="sy0">/</span>live<span class="sy0">/</span>moodletest.milll.ws<span class="sy0">/</span>fullchain.pem Key is saved at: <span class="sy0">/</span>etc<span class="sy0">/</span>letsencrypt<span class="sy0">/</span>live<span class="sy0">/</span>moodletest.milll.ws<span class="sy0">/</span>privkey.pem This certificate expires on <span class="nu0">2022</span>-08-<span class="nu0">21</span>. These files will be updated when the certificate renews. Certbot has <span class="kw1">set</span> up a scheduled task to automatically renew this certificate <span class="kw1">in</span> the background. We were unable to subscribe you the EFF mailing list because your e-mail address appears to be invalid. You can try again later by visiting https:<span class="sy0">//</span>act.eff.org.   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - If you like Certbot, please consider supporting our work by: <span class="sy0">*</span> Donating to ISRG <span class="sy0">/</span> Let<span class="st_h">'s Encrypt: https://letsencrypt.org/donate * Donating to EFF: https://eff.org/donate-le - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -</span></pre></div></div> <p>... which means you've successfully received a new certificate! Now you can update your NGINX reverse proxy configuration:</p> <p><code>sudo nano /etc/nginx/sites-available/${FQDN}</code></p> <p>to change it as follows (again, make sure you've replaced FQDN with your actual domain name!)</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co0"># ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;</span> <span class="co0"># ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;</span> ssl_certificate <span class="sy0">/</span>etc<span class="sy0">/</span>letsencrypt<span class="sy0">/</span>live<span class="sy0">/</span>FQDN<span class="sy0">/</span>fullchain.pem; ssl_certificate_key <span class="sy0">/</span>etc<span class="sy0">/</span>letsencrypt<span class="sy0">/</span>live<span class="sy0">/</span>FQDN<span class="sy0">/</span>privkey.pem;</pre></div></div> <p>Once you've saved the file you can test your NGINX configuration:</p> <p><code>sudo nginx -t</code></p> <p>and if it doesn't return any errors (nor should it return warnings), you can reload NGINX's config to make your new secure configuration live!</p> <p><code>sudo service nginx reload</code></p> <h3><a id="user-content-installing-moodle" href="#installing-moodle" name="installing-moodle" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Installing Moodle</h3> <p>Now that we've filled in all the parts of the tool-chain, your server is ready to receive requests for your Moodle site! You should be able to go to your web browser on your local computer and enter the URL, replacing FQDN with your domain name of course:</p> <p><code>https://FQDN</code></p> <p>(and entering <code>http://FQDN</code> should automatically redirect to <code>https://FQDN</code> - feel free to test it!)</p> <p>And if all is well, your Docker Compose stack running Moodle should respond by serving you up a page that looks like the attached "Install page" screenshot.</p> <p>You'll probably notice it looks a bit rough, as, for the moment at least, the Cascading Style Sheets (aka CSS, the code that determines how web content looks) won't be loading correctly. We'll address that shortly.</p> <p>We need to get through the installation of Moodle first, during which time, the Moodle system creates your <code>config.php</code> file in the <code>/home/data/${FQDN}/src/</code> directory. Once that's created, you'll be able to edit the file and make the tweaks to fix this problem.</p> <p>But first you'll need to proceed with the installation process - you'll be asked to select your language. And then you can click 'next' to continue. You'll be asked to specify your database (pick 'MariaDB' in this case) and then you'll be asked for the database details, as showing in the attached screenshot.</p> <p>Your host will be <code>db</code>, your database name and user will both be <code>moodle</code> (case sensitive!) and you'll have to enter the database password you put into your <code>docker-compose.yml</code> file.</p> <p>Then you should get a page showing you that your setup meets Moodle's requirement for installation (see attached screenshot), and finally you'll be asked to proceed with the install. Do so!</p> <p>The installation will create the relevant database tables in the <code>moodle</code> database via the MariaDB container, and Moodle will create the aforementioned <code>config.php</code> file. The next thing to do is edit that file:</p> <p><code>sudo nano /home/data/${FQDN}/src/config.php</code></p> <p>where you'll tweak the configuration file so that the line</p> <p><code>$CFG-&gt;wwwroot = 'http://moodletest.milll.ws';</code></p> <p>looks like (note the shift from ''http'' to ''https''):</p> <p><code>$CFG-&gt;wwwroot = 'https://moodletest.milll.ws';</code></p> <p>And add this line above the final comment (the lines starting with '//')</p> <p><code>$CFG-&gt;sslproxy=true;</code></p> <p>which informs the Moodle system that it's sitting behind an SSL reverse proxy so it can adjust its behaviour accordingly.</p> <p>So it should look something like this:</p> <div class="codeblock geshifilter"><code><span><br /><span>&lt;?php  </span><span>// Moodle configuration file<br /><br /></span><span>unset(</span><span>$CFG</span><span>);<br />global </span><span>$CFG</span><span>;<br /></span><span>$CFG </span><span>= new </span><span>stdClass</span><span>();<br /><br /></span><span>$CFG</span><span>-&gt;</span><span>dbtype    </span><span>= </span><span>'mariadb'</span><span>;<br /></span><span>$CFG</span><span>-&gt;</span><span>dblibrary </span><span>= </span><span>'native'</span><span>;<br /></span><span>$CFG</span><span>-&gt;</span><span>dbhost    </span><span>= </span><span>'db'</span><span>;<br /></span><span>$CFG</span><span>-&gt;</span><span>dbname    </span><span>= </span><span>'moodle'</span><span>;<br /></span><span>$CFG</span><span>-&gt;</span><span>dbuser    </span><span>= </span><span>'moodle'</span><span>;<br /></span><span>$CFG</span><span>-&gt;</span><span>dbpass    </span><span>= </span><span>'[your super-secret db password]'</span><span>;<br /></span><span>$CFG</span><span>-&gt;</span><span>prefix    </span><span>= </span><span>'mdl_'</span><span>;<br /></span><span>$CFG</span><span>-&gt;</span><span>dboptions </span><span>= array (<br />  </span><span>'dbpersist' </span><span>=&gt; </span><span>0</span><span>,<br />  </span><span>'dbport' </span><span>=&gt; </span><span>3306</span><span>,<br />  </span><span>'dbsocket' </span><span>=&gt; </span><span>''</span><span>,<br />  </span><span>'dbcollation' </span><span>=&gt; </span><span>'utf8mb4_general_ci'</span><span>,<br />);<br /><br /></span><span>$CFG</span><span>-&gt;</span><span>wwwroot   </span><span>= </span><span>'https://moodletest.milll.ws'</span><span>;<br /></span><span>$CFG</span><span>-&gt;</span><span>dataroot  </span><span>= </span><span>'/var/www/moodledata'</span><span>;<br /></span><span>$CFG</span><span>-&gt;</span><span>admin     </span><span>= </span><span>'admin'</span><span>;<br /><br /></span><span>$CFG</span><span>-&gt;</span><span>directorypermissions </span><span>= </span><span>0777</span><span>;<br /><br /></span><span>$CFG</span><span>-&gt;</span><span>sslproxy</span><span>=</span><span>true</span><span>;<br /><br />require_once(</span><span>__DIR__ </span><span>. </span><span>'/lib/setup.php'</span><span>);<br /><br /></span><span>// There is no php closing tag in this file,<br />// it is intentional because it prevents trailing whitespace problems!<br /></span><span>?&gt;</span></span></code></div> <p>Save your updated config.php file, and return to the installation process - after the next button press, when the Moodle installer page loads, you should suddenly see the interface look all beautiful and tidy as the CSS suddenly loads.</p> <p>You'll be asked set some properties for your site, like giving it a name, and finally, you'll have to provide the details of an 'admin' user - I recommend creating a default admin user, 'admin', with a <a href="/node/43">very strong password</a>. You'll log in with that admin user (and provide the details to your organisation so that there's a bit of succession planning, if you're not available at some point) just this once, and immediately create a new user for ''yourself'' with your preferred details, which you'll then elevate to 'administrator' privileges, and then do all your administration of the site via your own user.</p> <p>Once you've successfully completed defining an admin user, you'll end up at the admin 'dashboard', where you can create the personal administrator user for yourself. And then you can log out as 'admin' and log in as yourself... at which point, you're done! Moodle is installed. Hazzah!</p> <h2><a id="user-content-backing-up-your-data" href="#backing-up-your-data" name="backing-up-your-data" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Backing up your data</h2> <p>The way we run Docker Compose, all the files we want to preserve are stored on the VPS's own filesystem. The Docker containers only store 'volatile' data. That means we can literally remove the Docker containers at any time, and it won't (under normal circumstances) affect our precious data. The only time it ''might'' is when data, e.g. in the database, is held in system memory rather than being written to disk.</p> <p>That means that making backups of our data can be achieve simply by copying files on the hard disk - and if we're really interested in preserving that data, we'll send it somewhere else - on a different computer, in a different geographic region, and ideally managed by a different company - to achieve suitable diversity.</p> <h3><a id="user-content-mariadb-database-dumps" href="#mariadb-database-dumps" name="mariadb-database-dumps" class="heading-permalink" aria-hidden="true" title="Permalink"></a>MariaDB database dumps</h3> <p>The only element of our stack which isn't amenable to this file copying is the data managed by MariaDB. It is ''not'' generally safe to copy the files of a running database and to expect that restarting the database with those copied files will result in an uncorrupted data set. To safely backup a database, you either have to shut it down (which is disruptive and inelegant) or you have to get it do a database 'dump' while it's running (which it can usually do very quickly, without noticable impact on the performance of the database.</p> <p>A database 'dump' is a frozen snapshot of the data in a generic form, usually SQL. It allows us to restore the database tables, the data they contain, and the relationships (indices, foreign keys, etc.) between the tables. It usually even lets us take data from an older version of MariaDB and import it into a newer version (which is very handy!).</p> <p>So we need a method for periodically - and automatically - creating database dumps of our MariaDB Moodle database. Luckily, it's not too hard. I've created this script to do it.</p> <h2><a id="user-content-upgrading-moodle-with-git" href="#upgrading-moodle-with-git" name="upgrading-moodle-with-git" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Upgrading Moodle with Git</h2></div> </div> </div> <section class="field field-node--field-blog-comments field-name-field-blog-comments field-type-comment field-label-above comment-wrapper"> <a name="comments"></a> <div class="comment-form-wrapper"> <h2 class="comment-form__title">Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=52&amp;2=field_blog_comments&amp;3=comment" token="wfi0uCDurjkXOaaTefSDHhAt_udfRED6cv3itRTTiKg"></drupal-render-placeholder> </div> </section> Sun, 22 May 2022 23:13:24 +0000 dave 52 at http://tech.oeru.org http://tech.oeru.org/installing-and-upgrading-moodle-docker-compose-ubuntu-2204#comments Upgrading a Docker-based Discourse Forum instance http://tech.oeru.org/upgrading-docker-based-discourse-forum-instance <span class="field field--name-title field--type-string field--label-hidden">Upgrading a Docker-based Discourse Forum instance</span> <div class="field field-node--field-blog-tags field-name-field-blog-tags field-type-entity-reference field-label-above"> <h3 class="field__label">Blog tags</h3> <div class="field__items"> <div class="field__item field__item--discourse"> <span class="field__item-wrapper"><a href="/taxonomy/term/19" hreflang="en">discourse</a></span> </div> <div class="field__item field__item--docker"> <span class="field__item-wrapper"><a href="/taxonomy/term/16" hreflang="en">docker</a></span> </div> </div> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><a title="View user profile." href="/user/1" lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="" class="username">dave</a></span> <span class="field field--name-created field--type-created field--label-hidden">Thu 19/05/2022 - 14:45</span> <div class="field field-node--field-image field-name-field-image field-type-image field-label-hidden has-multiple"> <figure class="field-type-image__figure image-count-1"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-05/Discourse-admin_page_upgrade_link.png?itok=SmTm00O2" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;A sample of the admin page showing a Discourse administrator that an upgrade is pending, showing the web-upgrade link.&quot;}" role="button" title="A sample of the admin page showing a Discourse administrator that an upgrade is pending, showing the web-upgrade link." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;A sample of the admin page showing a Discourse administrator that an upgrade is pending, showing the web-upgrade link.&quot;}"><img src="/sites/default/files/styles/medium/public/2022-05/Discourse-admin_page_upgrade_link.png?itok=iBEbWRyb" width="220" height="144" alt="A sample of the admin page showing a Discourse administrator that an upgrade is pending, showing the web-upgrade link." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-2"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-05/Discourse-upgrade_email_alert.png?itok=VBs3FoqS" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;A sample of the email sent to Discourse admins by the Discourse system alerting them to a new pending upgrade.&quot;}" role="button" title="A sample of the email sent to Discourse admins by the Discourse system alerting them to a new pending upgrade." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;A sample of the email sent to Discourse admins by the Discourse system alerting them to a new pending upgrade.&quot;}"><img src="/sites/default/files/styles/medium/public/2022-05/Discourse-upgrade_email_alert.png?itok=M4tLaS3O" width="220" height="140" alt="A sample of the email sent to Discourse admins by the Discourse system alerting them to a new pending upgrade." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-3"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-05/Discourse-backup_admin_page.png?itok=Lv0Hz0-Y" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;A sample Discourse system backup administration page.&quot;}" role="button" title="A sample Discourse system backup administration page." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;A sample Discourse system backup administration page.&quot;}"><img src="/sites/default/files/styles/medium/public/2022-05/Discourse-backup_admin_page.png?itok=KZaf9Y0c" width="220" height="144" alt="A sample Discourse system backup administration page." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-4"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-05/Discourse-web_upgrade.png?itok=xyJIWWoq" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;A Discourse web upgrade status page, prior to initiating the upgrade.&quot;}" role="button" title="A Discourse web upgrade status page, prior to initiating the upgrade." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;A Discourse web upgrade status page, prior to initiating the upgrade.&quot;}"><img src="/sites/default/files/styles/medium/public/2022-05/Discourse-web_upgrade.png?itok=-NQk807A" width="220" height="124" alt="A Discourse web upgrade status page, prior to initiating the upgrade." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-5"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-05/Discourse-web_upgrade_progress.png?itok=eAf0UUVc" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;A Discourse web upgrade underway. &quot;}" role="button" title="A Discourse web upgrade underway. " data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;A Discourse web upgrade underway. &quot;}"><img src="/sites/default/files/styles/medium/public/2022-05/Discourse-web_upgrade_progress.png?itok=Q6y7yjKe" width="220" height="136" alt="A Discourse web upgrade underway. " loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-6"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-05/Dicscourse-require_cli_upgrade.png?itok=VKj1mQll" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;Sample of the message you see if a particular Discourse upgrade is too complicated for the web interface.&quot;}" role="button" title="Sample of the message you see if a particular Discourse upgrade is too complicated for the web interface." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;Sample of the message you see if a particular Discourse upgrade is too complicated for the web interface.&quot;}"><img src="/sites/default/files/styles/medium/public/2022-05/Dicscourse-require_cli_upgrade.png?itok=dVBM-OW2" width="220" height="94" alt="Sample of the message you see if a particular Discourse upgrade is too complicated for the web interface." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> </div> <div class="clearfix text-formatted field field-node--body field-name-body field-type-text-with-summary field-label-hidden"> <div class="field__items"> <div class="field__item"><p><a href="https://discourse.org">Discourse</a> is the world-leading online web-based forum. It's a superb, extremely mature-and-yet-cutting edge platform. It also happens to be Free and Open Source Software, which is why we, at the <a href="https://oerfoundation.org">OER Foundation</a>, use it.</p> <p>Like all good software, it's undergoing continuous improvement by its developer community who release fairly frequent - perhaps every couple weeks - updates. Luckily, keeping your Discourse forum up-to-date isn't particularly onerous.</p> <p>Upgrading a Discourse instance deployed via Docker (as all of ours are) follows one of two possible workflows. Both usually start with either a Discourse administrator logging into the Discourse site (which she/he might not do very often) and noticing on the default admin dashboard page (see the first attached screenshot) that there is a pending upgrade, usually with a 'click here to upgrade' link. Alternatively, if an administrator is not logged in (the system is aware of this) the system sends administrators for the site an email (see the second attached screenshot for a sample) alerting them all that an upgrade is pending, with a link to the upgrade page in the Discourse administration system.</p> <h2><a id="user-content-always-backup-first" href="#always-backup-first" name="always-backup-first" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Always backup first!</h2> <p>As any good system administrator knows, their users' data is of utmost importance. So, before proceeding with any upgrade, it's crucial to make a backup of your system to ensure that if everything goes pear-shaped, you can recover and get back to where you were.</p> <p>Normally, I consider it sufficient to take a Discourse 'backup' via the admin backup interface 'immediately before doing the upgrade' (see attached screenshot), leaving ''off'' the uploaded content, as this is unlikely to be affected or removed via the upgrade process, to save space. Doing this immediately prior to the upgrade minimises the chances that any users lose data (e.g. a recent post, in the space of time between the backup and the actual upgrade or even a post they're working on when the upgrade commences - if the latter happens, as long as they don't leave the page and wait for the forum to 'come back' after the upgrade, they should be able to proceed without losing anything... but there's a small chance they could be unlucky - be mindful of this and try to a) announce upgrades on the site prior to running them, and b) try to do so outside of your busiest hours, which should be easy to determine thanks to Discourse's superb analytics (also a feature of the default admin dashboard).</p> <p>After conducting the backup, you can do the upgrade.</p> <h2><a id="user-content-upgrade-workflows" href="#upgrade-workflows" name="upgrade-workflows" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Upgrade Workflows</h2> <p>You will ''usually'' have a straightforward 'point-and-click' web browser based upgrade process, but on occasion, you will click on the 'upgrade link' and end up on a page like 'upgrade is too complicated for the web interface' message (see attached screenshot). In that latter case, things get a little more complicated. Also, such upgrades can result in somewhat more downtime for the forum, so only conduct them when you have sufficient time.</p> <h3><a id="user-content-web-based-upgrade" href="#web-based-upgrade" name="web-based-upgrade" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Web-based upgrade</h3> <p>In most cases, when you click on the "Click here to upgrade" link, you'll be taken to a page that assesses the various components (the Discourse codebase, the Docker subsystems, and any additional functional plugins) making up your Discourse install and alerts you which of those components have a pending upgrade. This can sometimes take a while (the system is having to compare what you have on your server to the official up-to-date versions of those things on the web), but when done, the page will show you 'upgrade' buttons for the components with upgrades pending as per the attached sample upgrade pages screenshot.</p> <p>Once you've got those showing, you can click the 'Upgrade' button that's highlighted (if any - that one needs to be done first, prior to the others) and then the 'Start upgrade" operation for each one. Or, sometimes, you'll see an "Upgrade all" button at the top of the page, which applies all the pending upgrades in a single operation.</p> <p>Usually, in practice, this renders your Discourse forum unusable (it'll be in 'maintenance mode' from a user's perspective) for a few seconds to a few minutes, depending on the upgrade.</p> <h3><a id="user-content-command-line-launcher-upgrade" href="#command-line-launcher-upgrade" name="command-line-launcher-upgrade" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Command line 'launcher' upgrade</h3> <p>When the upgrade is more fundamental, usually involving the Docker subsystem, you sometimes don't have the option of a web-based upgrade. Instead of the upgrade web pages from the previous section, you get message like that shown in the next screenshot, which talks about running the upgrade from the command line using the <code>launcher</code> command and doing a 'rebuild' of the system.</p> <p>In that case, you need to be able to log into the host server (usually a remote cloud-based "Virtual Private Server" or VPS) - I always use SSH to do that <a href="/node/49">see these instructions</a> from a terminal on my Linux desktop - with suitable permissions (usually the ability to run 'sudo' commands, i.e. act as the administrative or 'root' user of that server).</p> <p>Once you're logged in as a user with 'sudo' privileges, you need to go to the Docker configuration directory for that instance of Discourse - in the OER Foundation's case, I have the convention of having the installation in the <code>/home/docker/</code> directory in a subdirectory with the same name as the as the site, e.g. forum.oeru.org. In that case, I'd enter</p> <p><code>cd /home/docker/forum.oeru.org</code></p> <p>Note that we don't use the default path for our Discourse installation (<code>/var/discourse</code>), so we're being rebels here. Doing an</p> <p><code>ls -l</code></p> <p>here should give a directory that looks a bit like this (you might not have the 'oeru'-related files) when listed:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="re5">-rw-rw-r--</span> <span class="nu0">1</span> dave dave <span class="nu0">1099</span> Nov <span class="nu0">29</span> <span class="nu0">2019</span> LICENSE <span class="re5">-rw-r--r--</span> <span class="nu0">1</span> dave root <span class="nu0">8285</span> Jul <span class="nu0">24</span> <span class="nu0">2021</span> README.md <span class="re5">-rw-rw-r--</span> <span class="nu0">1</span> dave dave <span class="nu0">258</span> Dec <span class="nu0">2</span> <span class="nu0">2019</span> README.oeru drwxrwxr-x <span class="nu0">1</span> dave dave <span class="nu0">16</span> Nov <span class="nu0">29</span> <span class="nu0">2019</span> bin drwxrwxr-x <span class="nu0">1</span> dave dave <span class="nu0">16</span> Apr <span class="nu0">15</span> <span class="nu0">23</span>:<span class="nu0">36</span> cids drwxrwxr-x <span class="nu0">1</span> dave dave <span class="nu0">30</span> May <span class="nu0">19</span> 06:<span class="nu0">47</span> containers <span class="re5">-rwxrwxr-x</span> <span class="nu0">1</span> dave dave <span class="nu0">11955</span> Apr <span class="nu0">15</span> <span class="nu0">23</span>:<span class="nu0">22</span> discourse-doctor <span class="re5">-rwxr-xr-x</span> <span class="nu0">1</span> dave root <span class="nu0">27132</span> Oct <span class="nu0">21</span> <span class="nu0">2021</span> discourse-setup drwxrwxr-x <span class="nu0">1</span> dave dave <span class="nu0">192</span> Dec <span class="nu0">22</span> 04:<span class="nu0">43</span> image <span class="re5">-rwxrwxr-x</span> <span class="nu0">1</span> dave dave <span class="nu0">23538</span> Apr <span class="nu0">15</span> <span class="nu0">23</span>:<span class="nu0">22</span> launcher drwxrwxr-x <span class="nu0">1</span> dave dave <span class="nu0">120</span> Nov <span class="nu0">16</span> <span class="nu0">2021</span> samples drwxrwxr-x <span class="nu0">1</span> dave dave <span class="nu0">22</span> Nov <span class="nu0">29</span> <span class="nu0">2019</span> scripts drwxrwxr-x <span class="nu0">1</span> dave dave <span class="nu0">16</span> Nov <span class="nu0">29</span> <span class="nu0">2019</span> shared drwxrwxr-x <span class="nu0">1</span> dave dave <span class="nu0">866</span> Apr <span class="nu0">15</span> <span class="nu0">23</span>:<span class="nu0">22</span> templates drwxr-xr-x <span class="nu0">1</span> dave root <span class="nu0">130</span> Jan <span class="nu0">16</span> <span class="nu0">22</span>:<span class="nu0">42</span> tests</pre></div></div> <p>The script we're going to use is the <code>launcher</code> script, and we're going to use the site's configuration which, by our convention, will reside in <code>containers/app.yml</code>(you can see its contents by typing <code>less containers/app.yml</code> assuming your user has sufficient permissions to view it directly. If not, prepend <code>sudo</code> to your command.</p> <p>To run the upgrade, you have to complete two steps as suggested by the page you saw when you clicked on the upgrade link:</p> <p>First, ensure that the 'git' repository that was used to provide this set of files to begin with is up-to-date - if your user owns these files (as my 'dave' user does in the example above) then run this to avoid changing the ownership of the files:</p> <p><code>git pull</code></p> <p>If you don't own the files you'll need to run</p> <p><code>sudo git pull</code></p> <p>which should result in a fairly quick (a few seconds) request to the git server, pulling down files that have been changed by the developers since the last time you did a <code>git pull</code>... Note, if the pull fails, you may find that you've altered one or more files for some reason and that the change you've made would be clobbered by the pull. The easiest thing to do in that case is - for each file - to (prepending with <code>sudo</code> if required):</p> <p><code>cp [filename] [filename].bak</code></p> <p>so you have a backup copy of your changed file, and then issue (again, with <code>sudo</code> prepended if necessary):</p> <p><code>git checkout [filename]</code></p> <p>which reverts [filename] to its original git-distributed version. The <code>git pull</code> should now work.</p> <p>Second, simply type (again, prepending with <code>sudo</code> if required:</p> <p><code>./launcher rebuild app.yml</code></p> <p>This will initiate what can be a rather long process (perhaps 1 to as many as, say, 30 minutes, depending on the upgrade, the amount that needs to be downloaded, your server's network bandwidth, and a variety of other variables) which will involve downloading new Docker containers with Discourse code, as well as possibly a new PostgreSQL database container among others. Your Discourse will continue running as long as it can safely do so, but then the <code>launcher</code> script will shut down the containers and restart them. It might also be 'recompiling' aggregated resources used by Discourse, like various Ruby On Rails gems, icon files, style sheets (CSS), or Javascript files...</p> <p>Once it's done, you'll get the command prompt back. You're done! Have a look at your site and you should see you're up-to-date!</p> <p>You can log out of the server vai <code>CTRL-D</code> or typing <code>exit</code>.</p> <h3><a id="user-content-problems" href="#problems" name="problems" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Problems?</h3> <p>I have, on a few occasions, had an upgrade fail for some reason. If that happens, you should see an indicator of has caused the failure (in a few cases for me, it was the fact that the PostgreSQL database didn't have enough time to restart before the Discourse app tried to access it to run data schema upgrades, and failed as a result. Changing the 'timeout' value from 5 seconds to 60 seconds fixed it - looking on the <a href="https://meta.discourse.org">Meta Discourse</a> - Discourse's own Discourse used to discuss running Discourse - is a very good resource when trying to solve system-related problems. It's worth creating an account for yourself on it if you're going to be responsible for a Discourse instance!</p></div> </div> </div> <section class="field field-node--field-blog-comments field-name-field-blog-comments field-type-comment field-label-above comment-wrapper"> <a name="comments"></a> <div class="comment-form-wrapper"> <h2 class="comment-form__title">Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=51&amp;2=field_blog_comments&amp;3=comment" token="sEqniUwHCHUWHihRflAm8YgWK0q2UAy7k0fiHG3-09Y"></drupal-render-placeholder> </div> </section> Thu, 19 May 2022 02:45:12 +0000 dave 51 at http://tech.oeru.org http://tech.oeru.org/upgrading-docker-based-discourse-forum-instance#comments Creating Linux shell users with sudo and SSH access http://tech.oeru.org/creating-linux-shell-users-sudo-and-ssh-access <span class="field field--name-title field--type-string field--label-hidden">Creating Linux shell users with sudo and SSH access</span> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><a title="View user profile." href="/user/1" lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="" class="username">dave</a></span> <span class="field field--name-created field--type-created field--label-hidden">Tue 10/05/2022 - 11:33</span> <div class="field field-node--field-image field-name-field-image field-type-image field-label-hidden has-multiple"> <figure class="field-type-image__figure image-count-1"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-05/DigitalOceanConsoleScreenshot.png?itok=KO2YM-KO" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;Screenshot of Digital Ocean&#039;s browser-based console, logged into VPS as the root user.&quot;}" role="button" title="Screenshot of Digital Ocean&#039;s browser-based console, logged into VPS as the root user." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;Screenshot of Digital Ocean&#039;s browser-based console, logged into VPS as the root user.&quot;}"><img src="/sites/default/files/styles/medium/public/2022-05/DigitalOceanConsoleScreenshot.png?itok=4qAWYCEw" width="220" height="158" alt="Screenshot of Digital Ocean&#039;s browser-based console, logged into VPS as the root user." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-2"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-05/Screenshot%202022-05-10%20at%2013-44-16%20mesc.oerfoundation.org%20-%20DigitalOcean-modified.png?itok=FuNxpcjc" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;Screenshot of Digital Ocean browser interface, viewing a VPS, showing the link for launching the &#039;console&#039;.&quot;}" role="button" title="Screenshot of Digital Ocean browser interface, viewing a VPS, showing the link for launching the &#039;console&#039;." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;Screenshot of Digital Ocean browser interface, viewing a VPS, showing the link for launching the &#039;console&#039;.&quot;}"><img src="/sites/default/files/styles/medium/public/2022-05/Screenshot%202022-05-10%20at%2013-44-16%20mesc.oerfoundation.org%20-%20DigitalOcean-modified.png?itok=F1e41LUo" width="220" height="155" alt="Screenshot of Digital Ocean browser interface, viewing a VPS, showing the link for launching the &#039;console&#039;." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-3"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-05/default-login-prompt-screenshot.png?itok=_AfI5XKT" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;A default VPS login screen. Note, colours and fonts and sizes of things are likely to vary. &quot;}" role="button" title="A default VPS login screen. Note, colours and fonts and sizes of things are likely to vary. " data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;A default VPS login screen. Note, colours and fonts and sizes of things are likely to vary. &quot;}"><img src="/sites/default/files/styles/medium/public/2022-05/default-login-prompt-screenshot.png?itok=gS6CCN9A" width="220" height="115" alt="A default VPS login screen. Note, colours and fonts and sizes of things are likely to vary. " loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> </div> <div class="clearfix text-formatted field field-node--body field-name-body field-type-text-with-summary field-label-hidden"> <div class="field__items"> <div class="field__item"><p>With Linux servers, both local and in "the Cloud" (aka a Virtual Private Server or VPS), access is generally remote via a command line interface. This tutorial covers the process of creating normal shell (command line) users who have the ability to perform administrative tasks (via the 'sudo' - Super User DO - mechanism), as well as configuring that user to be able to log in from their workstation via SecureShell (SSH) without needing to enter a password (i.e. using public-private key pairs).</p> <p>To begin with, we need a Linux server to which we have administrative access. This can be direct access to a local Linux server on our own infrastructure, or a remote VPS for which we have the 'root' (administrative user) access, either via SSH (to a default root or unprivileged user created by the VPS provider) or via a browser-based console (which simulates sitting down at monitor/keyboard at the actual server itself).</p> <p>You'll log into your server either via your web browser (see attached images of the Digital Ocean VPS control page, with console link, and a browser-based console session, logged in as the 'root' user), or via SSH from your workstation. If you are on Linux or MacOS, you can use the in-built SSH client (you'll want to create an SSH key-pair for yourself first - on <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-ssh-keys-on-ubuntu-20-04">Linux</a>, on a <a href="https://pagely.com/quickstart/firehose/ssh/mac/generating-key-pairs/">Mac</a>, or on <a href="https://www.howtogeek.com/762863/how-to-generate-ssh-keys-in-windows-10-and-windows-11/">Windows</a>. Note: I normally leave the 'passphrase' blank - it's slightly less secure especially if your SSH keys are on a computer you share with other people, but it's a <strong>lot</strong> more convenient, and a trade-off I'm willing to make!</p> <p>You can address your server either by its IP address (either IPv4, e.g. 137.184.84.159, or if you're one of the rare far-sighted people with an IPv6 address, e.g. 2604:a880:4:1d0::4a2:c000, you can use that instead).</p> <p>If you'd like to create your own VPS using a cloud infrastructure provider like Digital Ocean, and perhaps even configure a domain name to point to it (so you can use that domain to address your server rather than an IP address, see the <a href="/node/42">first couple sections of this tutorial</a>.</p> <h2><a id="user-content-logging-in" href="#logging-in" name="logging-in" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Logging in</h2> <p>Logging into your VPS will depend on whether you're using a console or logging in via SSH. If via a console, you'll just need to enter the username (possibly 'root') and password you set when creating the VPS. If via SSH, you'll run</p> <p><code>ssh [your server IPv4 or IPv6]</code></p> <p>followed by the ENTER key (that'll be true for any line of commands I provide). If your VPS is configured for passwordless logins, you shouldn't need a password. If not, you'll need the password you created when provisioning the VPS. Usually, it's either one or the other. If you're running Putty, you'll enter the same details into the Putty interface.</p> <p>In most cases, if you've never logged into that VPS via SSH previously, you're likely to see a notification like this right after entering the command (with your VPS' hostname and IP address and its own ECDSA key fingerprint - it'll be unique to each server):</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">The authenticity of host <span class="st_h">'sandbox.milll.ws (163.12.232.115)'</span> can<span class="st_h">'t be established. ECDSA key fingerprint is SHA256:0doRcAm9kAZjraohX+CeCwv1Yv6bIbzAYUSTrWGc9Y4. Are you sure you want to continue connecting (yes/no/[fingerprint])? </span></pre></div></div> <p>Type 'yes' and ENTER. You should only ever see this once for any IP or domain name you use to access this particular VPS.</p> <p>Once you're logged in, you'll see a command line prompt, probably somewhat similar to that of the attached screenshot. The name of the VPS you've logged into and your username will normally (by convention) be specified in your command prompt. In the attached screenshot, you can see my prompt is <code>dave@mesc:~$</code>. That means I'm logged in as user <code>dave</code>, the VPS's short-name is <code>mesc</code> (it's long name is <code>mesc.oerfoundation.org</code> which is too long to include in a prompt) and my current location is <code>~</code>, the 'tilde' character which is short-hand on a UNIX shell for the user's 'home directory'. On a Linux VPS, it's likely to be in <code>/home/[username]</code> or <code>/home/dave</code> in my user's case.</p> <p>Note, on a UNIX system, directories (aka folders) are delineated with a forward slash, <code>/</code>. Windows is the only system that uses a backslash <code>\</code> to delineate directories.</p> <p>The <code>$</code> at the end of the prompt tells you you're an 'unprivileged user'. If you were logged in as an admin user, usually 'root', you'd see a <code>#</code> in your prompt to remind you that you've got unlimited power within the VPS, and to use it carefully!</p> <p>If your prompt doesn't show your username, you can find out what user you're logged in as by typing</p> <p><code>whoami</code></p> <h2><a id="user-content-creating-a-regular-user" href="#creating-a-regular-user" name="creating-a-regular-user" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Creating a regular user</h2> <p>Let's say you're creating a user with the username of 'fiafia' - <em>by convention, usernames on Linux systems are all lower case letters, without spaces or punctuation</em>. Assuming your user has 'sudo' privileges, you can create the user like this (you will be asked to enter your user's password the first time you use <code>sudo</code>, and again after not using it for a while, usually 10 minutes):</p> <p><code>sudo adduser fiafia</code></p> <p>That will request that you enter additional information, like the user's full name, a phone number, and even an office room number (you can leave out any unnecessary information), and it will create a home directory for the user, <code>/home/fiafia</code> in this case.</p> <p>It will also require you to set a password for the new user. If you're not sure how to generate a good random password, you can user our <a href="/node/43">short tutorial</a>. Should you want to change it you can set it like this - your user will need to know their password in order to run <code>sudo</code> commands:</p> <p><code>sudo passwd $U</code></p> <p>which will instruct you to enter a strong password twice (to guard against typos).</p> <p>To give fiafia the ability to log in via SSH and to allow her to run administrative commands, her user needs to be added to several 'groups', members of which get additional privileges:</p> <p><code>sudo adduser fiafia admin</code><br /><code>sudo adduser fiafia sudo</code></p> <p>Those groups, <code>ssh</code>, <code>admin</code>, and <code>sudo</code> will confer those abilities on your new user.</p> <p>If you're working with VPS running a version of Ubuntu prior to 22.04 (like 20.04) or perhaps a Debian variant, you might also need to add the user to the 'ssh' group (it appears that with 22.04, Ubuntu have decided the ssh group was unnecessary and have removed it).</p> <p>Just to be safe, if you run this on 22.04 too, it might fail to complete (saying something like "there's no 'ssh' group"), but that's not a problem.</p> <p><code>sudo adduser fiafia ssh</code></p> <p>If you wanted to create several users, you could avoid typing in each user's name so many times by instead setting a variable in the shell, say <code>U</code>, to the username and then reference the value of U, which you do by putting a '$' in front of it, like <code>$U</code>. So to create another new user, say 'masina', you could do the same thing like this, and copy and paste the following into your terminal window (If pasting via 'CTRL-V' doesn't work, try 'SHIFT+CTRL-V', similarly copy via 'SHIFT+CTRL-C'):</p> <p><code>U=masina</code><br /><code>sudo adduser $U</code><br /><code>sudo adduser $U admin</code><br /><code>sudo adduser $U sudo</code></p> <p>(Note: to repeat these and the following commands for another user, just assign a new username to U, <code>U=[newusername]</code> and re-paste the same commands with <code>$U</code>.)</p> <p>Because, by following this tutorial, your user is likely to log in without using their password, it makes sense to put it somewhere safe where they can find it. I usually do it this way (replace [password] with the same password you used for the previous command):</p> <p><code>sudo echo "[password]" &gt; /home/$U/.password</code></p> <p>which, in this case, will put the password in a 'hidden' file (files or directories with names starting with a '.' are called hidden files or directories - they won't appear in a normal directory listing, e.g. <code>ls</code> or <code>ls -l</code>. If you want to see them, try <code>ls -la</code> where the 'a' means 'all') called <code>.password</code> in the user masina's home directory, <code>/home/masina</code>.</p> <p>You will also want to make sure that your new user has permission to remove that file. The right way to do that is to transfer ownership of the file to that user:</p> <p><code>sudo chown $U /home/$U/.password</code></p> <h2><a id="user-content-creating-ssh-keys-on-your-vps" href="#creating-ssh-keys-on-your-vps" name="creating-ssh-keys-on-your-vps" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Creating SSH keys on your VPS</h2> <p>Now we can create an SSH key pair for this new user on this VPS. Helpfully, in the process, the <code>.ssh</code> (hidden) directory for the file into which to put that user's <em>workstation</em> public SSH key to allow them passwordless logins from that workstation:</p> <p><code>sudo su $U</code></p> <p>That command allows your user to "become" the user $U and execute the following commands (the 'sudo' isn't needed here as that user is working in her/his own space with her/his own files):</p> <p><code>ssh-keygen -t rsa -b 2048</code></p> <p>This command uses the SSH keygen utility to create a 2048bit keypair: a public and a private key which provide the means for engaging in full encrypted (and extremely secure!) interactions with a remote server via the SSH protocol. You'll have to hit "ENTER" three times to complete the process (as I said above, I normally leave the passphrase blank).</p> <p>Once that is done, you can create and edit a new file:</p> <p><code>nano ~/.ssh/authorized_keys</code></p> <p>In that file, you can copy and paste (without spaces on either end) that user's <em>workstation's</em> <strong>public</strong> ssh key (<em>never publish</em> a private key anywhere!), save and close the file (use CTRL-X).</p> <p>The quickest way to do that on a Linux computer is by running the command (back on your workstation!):</p> <p><code>cat ~/.ssh/id_rsa.pub</code></p> <p>which will print the contents of your public key file below it, so you can easily highlight and copy &amp; paste it (remember, a copy from a Linux terminal is SHIFT+CTRL+C).</p> <p>When you're done pasting the public key into your <code>authorized_keys</code> file, save and exit the editor (CTRL-X) and type</p> <p><code>exit</code></p> <p>The <code>exit</code> will return you to your own user. You can use CTRL-D (slightly faster to type) to do the same thing.</p> <p>From that point, that user should be able to SSH to the VPS via <code>ssh [username]@[domain name]</code> without needing to enter a password.</p> <p>These instructions use 'sudo' in front of commands because I assume you're using a non-root user. The instructions will still work fine even if you're logged in as 'root'.</p></div> </div> </div> <section class="field field-node--field-blog-comments field-name-field-blog-comments field-type-comment field-label-above comment-wrapper"> <a name="comments"></a> <div class="comment-form-wrapper"> <h2 class="comment-form__title">Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=49&amp;2=field_blog_comments&amp;3=comment" token="36KGpyt9VtFQVtBToeZLjHkiDNTzg-D6hmycl2L8HYQ"></drupal-render-placeholder> </div> </section> Mon, 09 May 2022 23:33:58 +0000 dave 49 at http://tech.oeru.org http://tech.oeru.org/creating-linux-shell-users-sudo-and-ssh-access#comments Install Rocket.Chat on Ubuntu 20.04 via Docker Compose http://tech.oeru.org/install-rocketchat-ubuntu-2004-docker-compose <span class="field field--name-title field--type-string field--label-hidden">Install Rocket.Chat on Ubuntu 20.04 via Docker Compose</span> <div class="field field-node--field-blog-tags field-name-field-blog-tags field-type-entity-reference field-label-above"> <h3 class="field__label">Blog tags</h3> <div class="field__items"> <div class="field__item field__item--ubuntu-linux"> <span class="field__item-wrapper"><a href="/taxonomy/term/12" hreflang="en">ubuntu linux</a></span> </div> <div class="field__item field__item--_004"> <span class="field__item-wrapper"><a href="/taxonomy/term/75" hreflang="en">20.04</a></span> </div> <div class="field__item field__item--rocketchat"> <span class="field__item-wrapper"><a href="/taxonomy/term/18" hreflang="en">rocket.chat</a></span> </div> <div class="field__item field__item--mongodb"> <span class="field__item-wrapper"><a href="/taxonomy/term/14" hreflang="en">mongodb</a></span> </div> <div class="field__item field__item--docker"> <span class="field__item-wrapper"><a href="/taxonomy/term/16" hreflang="en">docker</a></span> </div> <div class="field__item field__item--docker-compose"> <span class="field__item-wrapper"><a href="/taxonomy/term/49" hreflang="en">docker-compose</a></span> </div> </div> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><a title="View user profile." href="/user/1" lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="" class="username">dave</a></span> <span class="field field--name-created field--type-created field--label-hidden">Thu 03/03/2022 - 13:00</span> <div class="field field-node--field-image field-name-field-image field-type-image field-label-hidden has-multiple"> <figure class="field-type-image__figure image-count-1"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-03/RC-launchscreen.png?itok=JnRkklyO" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;Rocket.Chat initial &#039;launch&#039; screen requesting admin user credentials.&quot;}" role="button" title="Rocket.Chat initial &#039;launch&#039; screen requesting admin user credentials." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;Rocket.Chat initial &#039;launch&#039; screen requesting admin user credentials.&quot;}"><img src="/sites/default/files/styles/medium/public/2022-03/RC-launchscreen.png?itok=OjBXKXXC" width="220" height="160" alt="Rocket.Chat initial &#039;launch&#039; screen requesting admin user credentials." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-2"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-03/RC-confirmationscreen.png?itok=uO0TVfx-" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;The confirmation screen after initial registration, awaiting the email from Rocket.Chat for registering your instance.&quot;}" role="button" title="The confirmation screen after initial registration, awaiting the email from Rocket.Chat for registering your instance." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;The confirmation screen after initial registration, awaiting the email from Rocket.Chat for registering your instance.&quot;}"><img src="/sites/default/files/styles/medium/public/2022-03/RC-confirmationscreen.png?itok=xQ-NAp1u" width="220" height="160" alt="The confirmation screen after initial registration, awaiting the email from Rocket.Chat for registering your instance." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-3"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2022-03/RC-logs_confirming_instance_running.png?itok=7tPCbNHo" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;After logging in, you can look at your instances logs and either the &#039;info&#039; option or the logs can verify the details of your instance. &quot;}" role="button" title="After logging in, you can look at your instances logs and either the &#039;info&#039; option or the logs can verify the details of your instance. " data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;After logging in, you can look at your instances logs and either the &#039;info&#039; option or the logs can verify the details of your instance. &quot;}"><img src="/sites/default/files/styles/medium/public/2022-03/RC-logs_confirming_instance_running.png?itok=dY2I6OoA" width="220" height="158" alt="After logging in, you can look at your instances logs and either the &#039;info&#039; option or the logs can verify the details of your instance. " loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> </div> <div class="clearfix text-formatted field field-node--body field-name-body field-type-text-with-summary field-label-hidden"> <div class="field__items"> <div class="field__item"><p>This post is a companion to our <a href="https://vimeo.com/684017454">video tutorial on installing Rocket.Chat 4.x on an Ubuntu 20.04 server via Docker Compose</a>.</p> <p>We start with a <a href="/setting-oeru-style-virtual-ubuntu-2004-server-docker-compose">Docker Compose-configured server</a>. You can also see our video tutorials on how to create one: <a href="https://vimeo.com/684028258">complete</a>, or <a href="https://vimeo.com/677523362">starting from a DigitalOcean 'snapshot'</a>.</p> <p>From that starting point, we can install Rocket.Chat - which, incidentally, is a full-featured messaging service, similar to more heavily marketed Slack, Discord, or Microsoft Teams, but <a href="https://github.com/RocketChat/">free and open source</a>, and self-hostable (other free and open source options include Mattermost and Element/Matrix among others, by Rocket.Chat is the OERF's preference). You can also purchase it as <a href="https://rocket.chat">Software-as-a-Service</a>. It's also great for all sorts of <a href="https://rocket.chat/enterprise/integrations">integrations</a>. There <a href="https://rocket.chat/install">many ways</a> you can interact with Rocket.Chat. It can be accessed via any modern web browser, and there are dedicated desktop applications for Linux, Microsoft Windows, and Apple MacOS, as well as mobile applications for Apple iOS and Android devices.</p> <p>To complete this tutorial, you will need a fully qualified [domain name] (or subdomain) pointing to your server, a [port] number for your service - 8080 or 7080 are good options so long as they're not already in use by other services on the same server, and a set of authenticating SMTP credentials so you can configure your Rocket.Chat to send email (by default, Rocket.Chat uses TOTP (Time-based One-Time Password) sent via email for extra security).</p> <h2><a id="user-content-nginx-reverse-proxy" href="#nginx-reverse-proxy" name="nginx-reverse-proxy" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Nginx reverse proxy</h2> <p>The first step is to set up the Nginx reverse proxy configuration to allow. You should create a file in <code>/etc/nginx/sites-available</code> with your [domain name] like this:</p> <p><code>sudo nano /etc/nginx/sites-available/[domain name]</code></p> <p>and fill it with this (replacing [domain name] and [port] appropriately:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">server <span class="br0">{</span> listen <span class="nu0">80</span>; listen <span class="br0">[</span>::<span class="br0">]</span>:<span class="nu0">80</span>; server_name <span class="br0">[</span>domain name<span class="br0">]</span>;   <span class="co0">## Access and error logs.</span> access_log <span class="sy0">/</span>var<span class="sy0">/</span>log<span class="sy0">/</span>nginx<span class="sy0">/</span><span class="br0">[</span>domain name<span class="br0">]</span>_access.log; error_log <span class="sy0">/</span>var<span class="sy0">/</span>log<span class="sy0">/</span>nginx<span class="sy0">/</span><span class="br0">[</span>domain name<span class="br0">]</span>_error.log;   include includes<span class="sy0">/</span>letsencrypt.conf;   location <span class="sy0">/</span> <span class="br0">{</span> <span class="kw3">return</span> <span class="nu0">301</span> https:<span class="sy0">//</span><span class="br0">[</span>domain name<span class="br0">]</span><span class="re1">$request_uri</span>; <span class="br0">}</span> root <span class="sy0">/</span>var<span class="sy0">/</span>www<span class="sy0">/</span>html; <span class="br0">}</span>     server <span class="br0">{</span> listen <span class="nu0">443</span> ssl; listen <span class="br0">[</span>::<span class="br0">]</span>:<span class="nu0">443</span> ssl; <span class="co0">#ssl_certificate /etc/letsencrypt/live/[domain name]/fullchain.pem;</span> <span class="co0">#ssl_certificate_key /etc/letsencrypt/live/[domain name]/privkey.pem;</span> <span class="co0"># temporary cert and private key, to be superseded by those provided by Let's Encrypt above.</span> ssl_certificate <span class="sy0">/</span>etc<span class="sy0">/</span>ssl<span class="sy0">/</span>certs<span class="sy0">/</span>ssl-cert-snakeoil.pem; ssl_certificate_key <span class="sy0">/</span>etc<span class="sy0">/</span>ssl<span class="sy0">/</span>private<span class="sy0">/</span>ssl-cert-snakeoil.key; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_dhparam <span class="sy0">/</span>etc<span class="sy0">/</span>ssl<span class="sy0">/</span>certs<span class="sy0">/</span>dhparam.pem;   keepalive_timeout 20s;   root <span class="sy0">/</span>var<span class="sy0">/</span>www<span class="sy0">/</span>html; index index.html index.htm; server_name <span class="br0">[</span>domain name<span class="br0">]</span>;   <span class="co0">## Access and error logs.</span> access_log <span class="sy0">/</span>var<span class="sy0">/</span>log<span class="sy0">/</span>nginx<span class="sy0">/</span><span class="br0">[</span>domain name<span class="br0">]</span>_access.log; error_log <span class="sy0">/</span>var<span class="sy0">/</span>log<span class="sy0">/</span>nginx<span class="sy0">/</span><span class="br0">[</span>domain name<span class="br0">]</span>_error.log;   client_max_body_size 100m;   include includes<span class="sy0">/</span>letsencrypt.conf;   location <span class="sy0">/</span> <span class="br0">{</span> proxy_pass http:<span class="sy0">//</span>127.0.0.1:<span class="br0">[</span>port<span class="br0">]</span>; <span class="co0"># use 8080 or 7080 if in doubt.</span> proxy_http_version <span class="nu0">1.1</span>; proxy_set_header Upgrade <span class="re1">$http_upgrade</span>; proxy_set_header Connection <span class="st0">"upgrade"</span>; proxy_set_header Host <span class="re1">$http_host</span>; proxy_set_header X-Forwarded-Host <span class="re1">$host</span>; proxy_set_header X-Real-IP <span class="re1">$remote_addr</span>; proxy_set_header X-Forwarded-For <span class="re1">$proxy_add_x_forwarded_for</span>; proxy_set_header X-Forward-Proto http; proxy_set_header X-Nginx-Proxy <span class="kw2">true</span>; proxy_redirect off; <span class="br0">}</span> <span class="br0">}</span></pre></div></div> <p>Save and close that file. We now need to create the 'dhparam.pem' if it doesn't exist create it like this:</p> <p><code>sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048</code></p> <p>After doing that, you can test Nginx's configuration:</p> <p><code>sudo nginx -t</code></p> <p>If you don't get any errors or warnings, you can activate the new configuration:</p> <p><code>sudo service nginx reload</code></p> <p>You server should now successfully respond to your [domain name], but it'll redirect to HTTPS using the default (self-signed) certificates that are valid as far as Nginx is concerned, but your browser won't let you access the page due to those inappropriate certificates. You can test it by putting http://[domain name] into your browser and seeing if it redirects you to https://[domain name] and a 'bad certificate` (or similar) page.</p> <p>To fix that, we can now generate a Let's Encrypt certificate which will be valid.</p> <p><code>sudo letsencrypt certonly --webroot -w /var/www/letsencrypt -d [domain name]</code></p> <p>Since this is your first time running the <code>letsencrypt</code> script, you'll be asked for a contact email (so the Let's Encrypt system can warn you if your certificates are going to be expiring soon!) - you will need to provide an admin email for this. You can also opt in to allowing them to get anonymous statistics from your site (I do).</p> <p>Once you've done that, the Let's Encrypt system will verify that you (the person requesting the certificate) also controls the server that's requesting it (using the details specified in the <code>/etc/nginx/includes/letsencrypt.conf</code> file, along with data that the letsencrypt script writes into a special file in the <code>/var/www/letsencrypt</code> directory) and, all going well, you'll see a "Congratulations!" message telling you that you have new certificates for your [domain name].</p> <p>Then you can re-edit your Nginx confguration file:</p> <p><code>sudo nano /etc/nginx/sites-available/[domain name]</code></p> <p>and comment out the default certificates and uncomment the [Domain]-specific certificates, like this (we'll assume you've already got your [domain name] substituted!):</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"> ssl_certificate <span class="sy0">/</span>etc<span class="sy0">/</span>letsencrypt<span class="sy0">/</span>live<span class="sy0">/</span><span class="br0">[</span>domain name<span class="br0">]</span><span class="sy0">/</span>fullchain.pem; ssl_certificate_key <span class="sy0">/</span>etc<span class="sy0">/</span>letsencrypt<span class="sy0">/</span>live<span class="sy0">/</span><span class="br0">[</span>domain name<span class="br0">]</span><span class="sy0">/</span>privkey.pem; <span class="co0"># temporary cert and private key, to be superseded by those provided by Let's Encrypt above.</span> <span class="co0">#ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;</span> <span class="co0">#ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;</span> ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_dhparam <span class="sy0">/</span>etc<span class="sy0">/</span>ssl<span class="sy0">/</span>certs<span class="sy0">/</span>dhparam.pem;  </pre></div></div> <p>Once you've got that going, you again check to make sure your Nginx config is valid:</p> <p><code>sudo nginx -t</code></p> <p>and apply your configuration changes:</p> <p><code>sudo service nginx reload</code></p> <p>If you now go to http://[domain name] in your browser, you should be redirected to https://[domain name] and you shouldn't get any certificate errors, although you might get a "502" error because the service that reverse proxy configuration is trying to send you to doesn't yet exist! That's what we're expecting.</p> <h2><a id="user-content-rocketchat-containers-via-docker-compose" href="#rocketchat-containers-via-docker-compose" name="rocketchat-containers-via-docker-compose" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Rocket.Chat containers via Docker Compose</h2> <p>Next, we have to create the Rocket.Chat container recipe for Docker Compose. Create a Docker directory if it doesn't yet exist and a [domain name]-specific directory:</p> <p><code>sudo mkdir -p /home/docker/[domain name]</code></p> <p>and go there</p> <p><code>cd home/docker/[domain name]</code></p> <p>and create a file:</p> <p><code>nano docker-compose.yml</code></p> <p>putting this into it, replacing [domain name], [port], and the authenticating SMTP details: [smtp username], [smtp password], [smtp servername], and [smtp port]. Note the comment in the file as well if you're not sure about smtp:// or the [smtp port] to use.</p> <p>This configuration specifies MongoDB version 4.2, which is the <a href="https://docs.rocket.chat/quick-start/installing-and-updating/other-deployment-methods/manual-installation/extras/mongo-versions">recommended version</a> to use for Rocket.Chat right now. And the current version of Rocket.Chat at the time of this writing is 4.5.0.</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">version: <span class="st_h">'2'</span> services: mongo: restart: unless-stopped image: mongo:<span class="nu0">4.2</span> volumes: - <span class="sy0">/</span>home<span class="sy0">/</span>data<span class="sy0">/</span><span class="br0">[</span>domain name<span class="br0">]</span><span class="sy0">/</span>mongodb<span class="sy0">/</span>data:<span class="sy0">/</span>data<span class="sy0">/</span>db - <span class="sy0">/</span>home<span class="sy0">/</span>data<span class="sy0">/</span><span class="br0">[</span>domain name<span class="br0">]</span><span class="sy0">/</span>backups<span class="sy0">/</span>mongodb:<span class="sy0">/</span>backups command: mongod <span class="re5">--oplogSize</span> <span class="nu0">128</span> <span class="re5">--replSet</span> rs0 <span class="co0"># this container's job is just run the command to initialize the replica set.</span> <span class="co0"># it will run the command and remove himself (it will not stay running)</span> mongo-init-replica: image: mongo:<span class="nu0">4.2</span> command: <span class="st_h">'bash -c "for i in `seq 1 30`; do mongo mongo/rocketchat --eval \"rs.initiate({ _id: '</span><span class="st_h">'rs0'</span><span class="st_h">', members: [ { _id: 0, host: '</span><span class="st_h">'localhost:27017'</span><span class="st_h">' } ]})\" &amp;&amp; s=$$? &amp;&amp; break || s=$$?; echo \"Tried $$i times. Waiting 5 secs...\"; sleep 5; done; (exit $$s)"'</span> depends_on: - mongo rocketchat: restart: unless-stopped image: rocketchat<span class="sy0">/</span>rocket.chat:4.5.0 command: <span class="kw2">bash</span> <span class="re5">-c</span> <span class="st_h">'for i in `seq 1 30`; do node main.js &amp;&amp; s=$$? &amp;&amp; break || s=$$?; echo "Tried $$i times. Waiting 5 secs..."; sleep 5; done; (exit $$s)'</span> ports: - <span class="st0">"127.0.0.1:[port]:3000"</span> depends_on: - mongo environment: - <span class="re2">MONGO_URL</span>=mongodb:<span class="sy0">//</span>mongo<span class="sy0">/</span>rocket - <span class="re2">MONGO_OPLOG_URL</span>=mongodb:<span class="sy0">//</span>mongo<span class="sy0">/</span><span class="kw3">local</span> - <span class="re2">ROOT_URL</span>=https:<span class="sy0">//</span><span class="br0">[</span>domain name<span class="br0">]</span> <span class="co0"># the SMTP port is likely to be 587 or 465, and instead of smtp:// you might need to use smtps:// (note the 's' for secure).</span> - <span class="re2">MAIL_URL</span>=smtp:<span class="sy0">//</span><span class="br0">[</span>smtp username<span class="br0">]</span>:<span class="br0">[</span>smtp password<span class="br0">]</span><span class="sy0">@</span><span class="br0">[</span>smtp servername<span class="br0">]</span>:<span class="br0">[</span>smtp port<span class="br0">]</span><span class="sy0">/</span> volumes: - <span class="sy0">/</span>home<span class="sy0">/</span>data<span class="sy0">/</span><span class="br0">[</span>domain name<span class="br0">]</span><span class="sy0">/</span>uploads:<span class="sy0">/</span>app<span class="sy0">/</span>uploads</pre></div></div> <p>Save and close that file. You can then start the Docker containers by invoking Docker Compose</p> <p><code>docker-compose up -d &amp;&amp; docker-compose logs -f</code></p> <p>This command will 'pull' any missing reference containers from hub.docker.com (the default central place where many Docker containers are stored by their developers) and launch them in "daemon mode" (the '-d' flag achieves that) on your server, so that they'll keep running until you stop them. After it's done with the launching, Docker Compose will display the logging data being put out by both the MongoDB and Rocket.Chat containers.</p> <p>You can issue a <code>CTRL-C</code> to cancel out of the logs view - it won't affect the running Rocket.Chat instance.</p> <p>You can also stop Rocket.Chat by issuing:</p> <p><code>docker-compose stop</code></p> <p>and verify it status (running or not) via</p> <p><code>docker-compose ps</code></p> <p>If you then go https://[domain name] in your browser, you should get the 'initial user' start up page as shown in the attached screenshot. After filling in your content and content related to your instance, you should receive an email from both your Rocket.Chat instance and from the Rocket.Chat developers showing your instance is registered.</p> <p>You can then log in (by default, you will have to wait for an email with a TOTP code to log in) you will be able to change the multitude of settings, behaviours, and integrations that Rocket.Chat supports. See its <a href="https://docs.rocket.chat/">user documentation</a> for more information on configuring and managing your Rocket.Chat instance. Note, there are many other sources for information on Rocket.Chat configuration - search engines will help you.</p> <h2><a id="user-content-backing-up-your-data" href="#backing-up-your-data" name="backing-up-your-data" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Backing up your data</h2> <p>To back up your Rocket.Chat installation, you will need to store both the content of your MongoDB and any files or other content uploaded.</p> <p>Backing up the uploaded content involves regular file backups of the uploaded files and the docker-compose.yml configuration. Preferably, these should be stored off the original server, i.e.</p> <p>We have developed a script for backing up Docker-based MongoDB installations. You can install it as follows. First, we'll make a place to put the backup script:</p> <p><code>sudo mkdir /home/data/scripts &amp;&amp; sudo cd /home/data/scripts</code></p> <p>and then clone (i.e. download) our git repository:</p> <p><code>sudo git clone https://git.oeru.org/oeru/mongobackup.git</code></p> <p>which will create a directory <code>/home/data/scripts/mongobackup</code> - go into it</p> <p><code>sudo cd /home/data/scripts/mongobackup</code></p> <p>and create a backup configuration (substituting your domain name!) and edit the file:</p> <p><code>sudo cp default-mongo.conf-sample default-mongo.conf</code></p> <p><code>sudo nano default-mongo.conf</code></p> <p>The content should look like this:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co0">#</span> <span class="co0"># dump backup directories</span> <span class="co0">#</span> <span class="co0"># for backup archives</span> <span class="re2">BU_DIR</span>=<span class="br0">[</span>path to backup archive store<span class="br0">]</span> <span class="co0"># working directories</span> <span class="co0"># from docker-compose.yml </span> <span class="re2">BU_DIR_HOST</span>=<span class="br0">[</span>path on host to mapped mongo container backup <span class="kw2">dir</span><span class="br0">]</span> <span class="re2">BU_DIR_DOCK</span>=<span class="br0">[</span>path on mongo container to backup <span class="kw2">dir</span><span class="br0">]</span> <span class="co0">#</span> <span class="co0"># Docker Compose details</span> <span class="co0">#</span> <span class="co0"># dir containing the docker-compose.yml</span> <span class="re2">DC_DIR</span>=<span class="br0">[</span>docker-compose directory <span class="kw1">for</span> mongo container<span class="br0">]</span> <span class="co0"># name of the database container</span> <span class="re2">DC_CONTAINER</span>=<span class="br0">[</span>name of mongo container <span class="kw1">in</span> docker-compose.yml<span class="br0">]</span> <span class="co0">#</span> <span class="co0"># Reporting</span> <span class="co0">#</span> <span class="co0"># email address to send reports to, and subject</span> <span class="re2">EMAIL</span>=<span class="br0">[</span>admin email<span class="br0">]</span> <span class="re2">EMAIL_SUBJ</span>=<span class="st0">"[email subject to distinguish this email from others]"</span></pre></div></div> <p>The values you set should look like this (replace [domain name] appropriate) - you can leave the original '#' comments in place if you want:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="re2">BU_DIR</span>=<span class="sy0">/</span>home<span class="sy0">/</span>backup<span class="sy0">/</span>mongodb <span class="re2">BU_DIR_HOST</span>=<span class="sy0">/</span>home<span class="sy0">/</span>data<span class="sy0">/</span><span class="br0">[</span>domain name<span class="br0">]</span><span class="sy0">/</span>backups<span class="sy0">/</span>mongodb <span class="re2">BU_DIR_DOCK</span>=<span class="sy0">/</span>backups <span class="re2">DC_DIR</span>=<span class="sy0">/</span>home<span class="sy0">/</span>docker<span class="sy0">/</span><span class="br0">[</span>domain name<span class="br0">]</span> <span class="re2">DC_CONTAINER</span>=mongo <span class="re2">EMAIL</span>=<span class="br0">[</span>an email that you receive<span class="br0">]</span> <span class="re2">EMAIL_SUBJ</span>=<span class="st0">"MongoDB Backup for Rocket.Chat: [domain name]"</span></pre></div></div> <p>save and close that file and then we have to create the location for the backups to be stored (and, ideally, transferred to the remote backup location along with the uploaded files and docker-compose.yml file):</p> <p><code>sudo mkdir -p /home/backup/mongodb</code></p> <p>Note, that the email alerts depend on you having configured your server properly to send out email as per our instructions for <a href="/setting-oeru-style-virtual-ubuntu-2004-server-docker-compose">setting up a Docker Compose server</a>.</p> <p>You can test to make sure this all works by running</p> <p><code>sudo /home/data/scripts/mongobackup/dbbackup-mongo --hourly</code></p> <p>You should see a bunch of file paths as things are backed up - if you see nothing, you may have an error in your configuration.</p> <p>To verify a backup has been made, you can run</p> <p><code>ls -l /home/backup/mongodb/</code></p> <p>and you should see a series of files (probably 3), named after each 'database' in the mongo server, with the name 'mongo-hourly' in them followed by the server's current date and time and a <code>.tgz</code> suffix.</p> <p>If it works, copy the 'cron' task to your <code>/etc/cron.d</code> directory:</p> <p><code>sudo cp dbbackup-mongo-cron /etc/cron.d/</code></p> <p>after which, you should get hourly (and daily, weekly, monthly, and yearly) backups that tidy up after themselves...</p> <h2><a id="user-content-upgrading-your-installation" href="#upgrading-your-installation" name="upgrading-your-installation" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Upgrading your installation</h2> <p>From time to time, assuming you've registered your Rocket.Chat installation, you will receive notifications that a new version of Rocket.Chat is available. You can upgrade at your convenience by creating a dated copy of your docker-compose.yml file - you can use this convenient little command (assuming you're in /home/docker/[domain name]):</p> <p><code>sudo cp docker-compose.yml docker-compose.yml-$(date +"%Y%m%d")</code></p> <p>and make sure you disable your instance</p> <p><code>sudo docker-compose stop rocketchat</code></p> <p>and then run a mongodb backup - the easiest way to do that is to run</p> <p><code>sudo /home/data/scripts/mongobackup/dbbackup-mongo --hourly</code></p> <p>as described above. Also, assuming the data involved is relatively small compared to your available storage space, you can copy your current mongo data to make sure you can recover quickly if necessary (after stopping mongodb!):</p> <p><code>sudo docker-compose stop mongo</code></p> <p><code>sudo cp -a /home/data/[domain name]/mongodb/data /home/data/[domain name]/mongodb/data-$(date +"%Y%m%d")</code></p> <p>Then, to do the upgrade: edit <code>docker-compose.yml</code> to update the Rocket.Chat (4.5.0 in this tutorial) container number to the new version.</p> <p><code>sudo nano docker-compose yml</code></p> <p>at which point you can run:</p> <p><code>sudo docker-compose up -d &amp;&amp; sudo docker-compose logs -f</code></p> <p>which will pull the newer Rocket.Chat container, and run it, along with MongoDB, which will automatically upgrade your instance if possible (if it succeeds, you'll see a message similar to the attached log image showing the Rocket.Chat version info in the log messages), and you'll be up and running with a new version, job done!</p> <p>Enjoy your free (in both senses of the word) world-class messaging platform!</p></div> </div> </div> <section class="field field-node--field-blog-comments field-name-field-blog-comments field-type-comment field-label-above comment-wrapper"> <a name="comments"></a> <div class="comment-form-wrapper"> <h2 class="comment-form__title">Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=48&amp;2=field_blog_comments&amp;3=comment" token="jITUDdDMJ8my5zsFfo33TLH5pOTlPih-fIF4KVSqOA8"></drupal-render-placeholder> </div> </section> Thu, 03 Mar 2022 00:00:52 +0000 dave 48 at http://tech.oeru.org http://tech.oeru.org/install-rocketchat-ubuntu-2004-docker-compose#comments BBB Tutorial Schedule http://tech.oeru.org/bbb-tutorial-schedule <span class="field field--name-title field--type-string field--label-hidden">BBB Tutorial Schedule</span> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><a title="View user profile." href="/user/1" lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="" class="username">dave</a></span> <span class="field field--name-created field--type-created field--label-hidden">Mon 13/12/2021 - 10:49</span> <div class="clearfix text-formatted field field-node--body field-name-body field-type-text-with-summary field-label-hidden"> <div class="field__items"> <div class="field__item"><p>The schedule for the <a href="https://samoaksi.ws">Samoa Knowledge Society Initiative</a> (SKSI) BigBlueButton Tutorial is as follows:</p> <h3><a id="user-content-before-the-tutorial" href="#before-the-tutorial" name="before-the-tutorial" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Before the tutorial</h3> <p>Please create an account on the <a href="https://forum.milll.ws">forum</a> we have set up for the MESC Lifelong Learning Lab. (This free and open source software forum application is powered by <a href="https://discourse.org">Discourse</a>).</p> <p>It would be great if you could introduce yourself in the Introductions forum before the tutorial commences.</p> <h3><a id="user-content-start-0930" href="#start-0930" name="start-0930" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Start 09:30</h3> <p>We will all meet virtually on the OER Foundation's BigBlueButton in the SKSI room which you'll find at <a href="https://oer.nz/sksi">https://oer.nz/sksi</a></p> <p>You should be able to participate with any internet connected device with a stable connection and a modern browser.</p> <p>If you can, please use headphones as this reduces background noise from your computer which can affect others' ability to hear.</p> <p>If you have a slow connection or old computer, you might find it easier to avoid sharing your video, or even participate only as a 'listener'. You will still be able to ask questions via the BigBlueButton 'public chat'</p> <h3><a id="user-content-duration" href="#duration" name="duration" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Duration</h3> <p>The length of the tutorial is likely to be <em>3 or more hours</em>, based on previous runs. Its length will depend, in part, on interaction with participants and the speed and availability of external resources.</p> <p>We will also stop for short breaks during the tutorial.</p> <h2><a id="user-content-introduction" href="#introduction" name="introduction" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Introduction</h2> <p>MESC's ACEO ICT &amp; Media, Nehru Mauala, will introduce the session.</p> <p>We will be working through a <a href="/node/46">BBB tutorial developed specifically for SKSI</a>. Feel free to use that as a reference while we go through the process it describes. It will also serve as a reference for those participating in the tutorial afterwards.</p> <h3><a id="user-content-questions-during-the-tutorial" href="#questions-during-the-tutorial" name="questions-during-the-tutorial" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Questions during the tutorial</h3> <p>I will try ask participants if they have any questions periodically through the tutorial, but you are welcome to post any questions you have using BigBlueButton's 'public chat' any time - I'll be able to see them, and hopefully answer them while they're still pertinent to the context.</p> <h3><a id="user-content-what-is-bigbluebutton" href="#what-is-bigbluebutton" name="what-is-bigbluebutton" class="heading-permalink" aria-hidden="true" title="Permalink"></a>What is BigBlueButton?</h3> <p>We'll provide a <a href="https://docs.bigbluebutton.org/#a-brief-overview-of-bigbluebutton">quick overview of what BigBlueButton actually is</a>, what it's designed to do, and for whom.</p> <h3><a id="user-content-what-you-need-to-know-before-you-start" href="#what-you-need-to-know-before-you-start" name="what-you-need-to-know-before-you-start" class="heading-permalink" aria-hidden="true" title="Permalink"></a>What you need to know before you start!</h3> <p>First of all, you who are participating in this tutorial should know: <strong>you do not need to remember what we're doing today</strong>. It's complicated to be sure. Just watch the process and let it 'wash over you' - the tutorial is to give you a sense of what is involved, but if you undertake setting up BBB yourselves, you'll have my <a href="/node/24">detailed online tutorial</a> to help you along.</p> <p>Some of these details will only be available after creating your Digital Ocean server instance. Others you can determine prior to starting.</p> <h4><a id="user-content-server" href="#server" name="server" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Server</h4> <p>We'll need to know some details about our server, some of which will only be available after we create the server, like the IPv4 and IPv6 addresses assigned to it.</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="br0">[</span>IPv4<span class="br0">]</span> = <span class="br0">[</span>IPv6<span class="br0">]</span> = <span class="br0">[</span>Domain<span class="br0">]</span> = bbbdemo.milll.ws <span class="br0">[</span>Admin email<span class="br0">]</span> = webmaster<span class="sy0">@</span>milll.ws <span class="br0">[</span>Non-Root username<span class="br0">]</span> = dave</pre></div></div> <h4><a id="user-content-email-settings" href="#email-settings" name="email-settings" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Email settings</h4> <p>We will need the details of an "authenticating email account" that we can use to send email from the BigBlueButton service, and from the server itself.</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="br0">[</span>SMTP server<span class="br0">]</span> = about.oerfoundation.org <span class="br0">[</span>SMTP port<span class="br0">]</span> = <span class="nu0">587</span> <span class="br0">[</span>SMTP security<span class="br0">]</span> = StartTLS <span class="br0">[</span>SMTP username<span class="br0">]</span> = demosmtp<span class="sy0">@</span>milll.ws <span class="br0">[</span>SMTP password<span class="br0">]</span> = 7TLM6qGoqZXHfkDmkh6 <span class="br0">[</span>Outgoing email<span class="br0">]</span> = notifications<span class="sy0">@</span>milll.ws</pre></div></div> <h3><a id="user-content-creating-a-server" href="#creating-a-server" name="creating-a-server" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Creating a server</h3> <p>We will allocate a new virtual server using Digital Ocean (this is only one of many possible suppliers), costing US$24 per month.</p> <h3><a id="user-content-configuring-your-servers-domain" href="#configuring-your-servers-domain" name="configuring-your-servers-domain" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Configuring your server's domain</h3> <p>We will assign a domain name to the server - in this case, we'll use <code>bbbdemo.milll.ws</code> - and we'll use the Domain Name Server configuration tools of a local 'Domain name registrar', Metaname. This is only one of many thousands of registrars around the world.</p> <h3><a id="user-content-preparing-your-server" href="#preparing-your-server" name="preparing-your-server" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Preparing your server</h3> <p>Installing software and configuring it to prepare for Docker containers.</p> <h3><a id="user-content-installing-bigbluebutton" href="#installing-bigbluebutton" name="installing-bigbluebutton" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Installing BigBlueButton</h3> <p>This involves downloading a version control repository - using the market leading version control tool, 'git' (which is also open source software) - containing installation and configuration scripts and Docker container recipes made available by the BigBlueButton developer community.</p> <h3><a id="user-content-configuring-bigbluebutton" href="#configuring-bigbluebutton" name="configuring-bigbluebutton" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Configuring BigBlueButton</h3> <p>We will configure our instance of BigBlueButton and then build custom Docker containers for each of the 21 individual services that together make up a BigBlueButton.</p> <p>This process can take an hour, so we will shift to a separate instance of the BBB system that I set up by the same process earlier.</p> <h3><a id="user-content-testing-bigbluebutton" href="#testing-bigbluebutton" name="testing-bigbluebutton" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Testing BigBlueButton</h3> <p>We'll have a look at BigBlueButton in action, setting up an initial administrative user, looking at the logs to assist with debugging, and give participants a chance to try the new instance!</p> <h3><a id="user-content-discussing-future-steps" href="#discussing-future-steps" name="discussing-future-steps" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Discussing future steps</h3> <p>Due to the time constraints of this tutorial, there lots of extra details we could discuss, but we might save those for a future follow-up tutorial. Things like</p> <ul><li>how BigBlueButton recordings work</li> <li>how to add extra disk space to store things like recordings</li> <li>how to back up your recordings and user/room database securely and off-server</li> <li>how to upgrade your BigBlueButton instance</li> <li>how to keep your Ubuntu server secure and up-to-date</li> </ul><p>and many other potential questions.</p> <h2><a id="user-content-after-the-tutorial" href="#after-the-tutorial" name="after-the-tutorial" class="heading-permalink" aria-hidden="true" title="Permalink"></a>After the tutorial</h2> <p>After the tutorial, you are welcome to review this post as well as the <a href="/node/24">full self-guided tutorial for installing BigBlueButton</a> on which this tutorial is based.</p> <p>You are also welcome to ask questions related to the tutorial and any attempts you make to create your own BigBlueButton instance on our <a href="https://forum.milll.ws/t/bigbluebutton-discussion/22">Tutorial Support forum</a>.</p> <p>Many thanks for your participation!</p></div> </div> </div> <section class="field field-node--field-blog-comments field-name-field-blog-comments field-type-comment field-label-above comment-wrapper"> <a name="comments"></a> <div class="comment-form-wrapper"> <h2 class="comment-form__title">Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=47&amp;2=field_blog_comments&amp;3=comment" token="Y87W_fGuKnG-DWuUXkKidH-pJoAaAGQKjUbkUIvGWpw"></drupal-render-placeholder> </div> </section> Sun, 12 Dec 2021 21:49:43 +0000 dave 47 at http://tech.oeru.org http://tech.oeru.org/bbb-tutorial-schedule#comments Installing BigBlueButton on an OERu Docker Server http://tech.oeru.org/installing-bigbluebutton-oeru-docker-server <span class="field field--name-title field--type-string field--label-hidden">Installing BigBlueButton on an OERu Docker Server</span> <div class="field field-node--field-blog-tags field-name-field-blog-tags field-type-entity-reference field-label-above"> <h3 class="field__label">Blog tags</h3> <div class="field__items"> <div class="field__item field__item--bigbluebutton"> <span class="field__item-wrapper"><a href="/taxonomy/term/84" hreflang="en">BigBlueButton</a></span> </div> <div class="field__item field__item--ubuntu-linux"> <span class="field__item-wrapper"><a href="/taxonomy/term/12" hreflang="en">ubuntu linux</a></span> </div> <div class="field__item field__item--_004"> <span class="field__item-wrapper"><a href="/taxonomy/term/75" hreflang="en">20.04</a></span> </div> <div class="field__item field__item--docker"> <span class="field__item-wrapper"><a href="/taxonomy/term/16" hreflang="en">docker</a></span> </div> <div class="field__item field__item--docker-compose"> <span class="field__item-wrapper"><a href="/taxonomy/term/49" hreflang="en">docker-compose</a></span> </div> <div class="field__item field__item--postgresql"> <span class="field__item-wrapper"><a href="/taxonomy/term/20" hreflang="en">postgresql</a></span> </div> <div class="field__item field__item--postfix"> <span class="field__item-wrapper"><a href="/taxonomy/term/66" hreflang="en">postfix</a></span> </div> <div class="field__item field__item--nginx"> <span class="field__item-wrapper"><a href="/taxonomy/term/30" hreflang="en">nginx</a></span> </div> <div class="field__item field__item--lets-encrypt"> <span class="field__item-wrapper"><a href="/taxonomy/term/17" hreflang="en">let&#039;s encrypt</a></span> </div> </div> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><a title="View user profile." href="/user/1" lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="" class="username">dave</a></span> <span class="field field--name-created field--type-created field--label-hidden">Wed 10/11/2021 - 15:42</span> <div class="field field-node--field-image field-name-field-image field-type-image field-label-hidden has-multiple"> <figure class="field-type-image__figure image-count-1"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2021-11/Screenshot%202021-11-26%20at%2012-02-53%20Create%20Droplets%20-%20DigitalOcean.png?itok=RFCxKkbH" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;Digital Ocean &#039;Droplet&#039; creation screen showing options selected for a BBB server&quot;}" role="button" title="Digital Ocean &#039;Droplet&#039; creation screen showing options selected for a BBB server" data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;Digital Ocean &#039;Droplet&#039; creation screen showing options selected for a BBB server&quot;}"><img src="/sites/default/files/styles/medium/public/2021-11/Screenshot%202021-11-26%20at%2012-02-53%20Create%20Droplets%20-%20DigitalOcean.png?itok=qpszFHwz" width="106" height="220" alt="Digital Ocean &#039;Droplet&#039; creation screen showing options selected for a BBB server" loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-2"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2021-11/Screenshot%202021-11-22%20at%2012-42-19%20bbbtest%20milll%20ws%20-%20DigitalOcean.png?itok=9s5Cj-8F" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;Performance graphs for a Digital Ocean &#039;Droplet&#039; configured to run BBB.&quot;}" role="button" title="Performance graphs for a Digital Ocean &#039;Droplet&#039; configured to run BBB." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;Performance graphs for a Digital Ocean &#039;Droplet&#039; configured to run BBB.&quot;}"><img src="/sites/default/files/styles/medium/public/2021-11/Screenshot%202021-11-22%20at%2012-42-19%20bbbtest%20milll%20ws%20-%20DigitalOcean.png?itok=Sy3JvvuB" width="220" height="166" alt="Performance graphs for a Digital Ocean &#039;Droplet&#039; configured to run BBB." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-3"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2021-11/Screenshot%202021-11-22%20at%2012-42-52%20milll%20ws%20-%20Metaname.png?itok=eILrt0_H" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;The Metaname DNS configuration for the milll.ws domain.&quot;}" role="button" title="The Metaname DNS configuration for the milll.ws domain." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;The Metaname DNS configuration for the milll.ws domain.&quot;}"><img src="/sites/default/files/styles/medium/public/2021-11/Screenshot%202021-11-22%20at%2012-42-52%20milll%20ws%20-%20Metaname.png?itok=8iNSxMhC" width="220" height="166" alt="The Metaname DNS configuration for the milll.ws domain." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-4"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2021-11/Screenshot%202021-11-23%20at%2014-20-22%20BigBlueButton.png?itok=vRbjT27h" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;The Greenlight front-end (providing user management and &#039;room&#039; configuration) for BigBlueButton.&quot;}" role="button" title="The Greenlight front-end (providing user management and &#039;room&#039; configuration) for BigBlueButton." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;The Greenlight front-end (providing user management and &#039;room&#039; configuration) for BigBlueButton.&quot;}"><img src="/sites/default/files/styles/medium/public/2021-11/Screenshot%202021-11-23%20at%2014-20-22%20BigBlueButton.png?itok=4W4E__KP" width="220" height="144" alt="The Greenlight front-end (providing user management and &#039;room&#039; configuration) for BigBlueButton." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-5"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2021-11/Screenshot%202021-11-30%20at%2023-04-07%20The%20OERu%20BigBlueButton%20-%20Home%20Room.png?itok=ofBw87DD" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;Joining a BBB session, you get the option of using a microphone to participate or being a &#039;listen-only&#039; participant.&quot;}" role="button" title="Joining a BBB session, you get the option of using a microphone to participate or being a &#039;listen-only&#039; participant." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;Joining a BBB session, you get the option of using a microphone to participate or being a &#039;listen-only&#039; participant.&quot;}"><img src="/sites/default/files/styles/medium/public/2021-11/Screenshot%202021-11-30%20at%2023-04-07%20The%20OERu%20BigBlueButton%20-%20Home%20Room.png?itok=RsOTQSIE" width="220" height="133" alt="Joining a BBB session, you get the option of using a microphone to participate or being a &#039;listen-only&#039; participant." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-6"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2021-11/Screenshot%202021-11-30%20at%2023-04-22%20The%20OERu%20BigBlueButton%20-%20Home%20Room%20-%20echo%20test.png?itok=H8WlXV4f" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;If you select &quot;microphone&quot;, you will next be asked to speak into it and do an &#039;echo test&#039; to calibrate BBB&#039;s echo cancellation algorithms.&quot;}" role="button" title="If you select &quot;microphone&quot;, you will next be asked to speak into it and do an &#039;echo test&#039; to calibrate BBB&#039;s echo cancellation algorithms." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;If you select &quot;microphone&quot;, you will next be asked to speak into it and do an &#039;echo test&#039; to calibrate BBB&#039;s echo cancellation algorithms.&quot;}"><img src="/sites/default/files/styles/medium/public/2021-11/Screenshot%202021-11-30%20at%2023-04-22%20The%20OERu%20BigBlueButton%20-%20Home%20Room%20-%20echo%20test.png?itok=KvueJqpL" width="220" height="133" alt="If you select &quot;microphone&quot;, you will next be asked to speak into it and do an &#039;echo test&#039; to calibrate BBB&#039;s echo cancellation algorithms." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-7"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2021-11/Screenshot%202021-11-30%20at%2023-04-49%20The%20OERu%20BigBlueButton%20-%20Home%20Room-webcam.png?itok=pEnZWxzO" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;And you can share your webcam feed, too. &quot;}" role="button" title="And you can share your webcam feed, too. " data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;And you can share your webcam feed, too. &quot;}"><img src="/sites/default/files/styles/medium/public/2021-11/Screenshot%202021-11-30%20at%2023-04-49%20The%20OERu%20BigBlueButton%20-%20Home%20Room-webcam.png?itok=tXwJ7Cpf" width="220" height="133" alt="And you can share your webcam feed, too. " loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-8"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2021-11/Screenshot%202021-11-26%20at%2015-03-34%20The%20OERu%20BigBlueButton%20-%20Home%20Room-withModeratorMenu.png?itok=wkyM4MSu" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;BigBlueButton conversation window in a browser, with the moderator tools open, showing link to breakout rooms and other moderation functions.&quot;}" role="button" title="BigBlueButton conversation window in a browser, with the moderator tools open, showing link to breakout rooms and other moderation functions." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;BigBlueButton conversation window in a browser, with the moderator tools open, showing link to breakout rooms and other moderation functions.&quot;}"><img src="/sites/default/files/styles/medium/public/2021-11/Screenshot%202021-11-26%20at%2015-03-34%20The%20OERu%20BigBlueButton%20-%20Home%20Room-withModeratorMenu.png?itok=tsVPmMgZ" width="220" height="143" alt="BigBlueButton conversation window in a browser, with the moderator tools open, showing link to breakout rooms and other moderation functions." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-9"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2021-11/Screenshot%202021-11-26%20at%2015-00-54%20The%20OERu%20BigBlueButton%20-%20Home%20Room.png?itok=55K_wxG1" aria-controls="colorbox" aria-label="{&quot;alt&quot;:&quot;BigBlueButton in effect with 3 participants (all the author from different devices). Note the public chat.&quot;}" role="button" title="BigBlueButton in effect with 3 participants (all the author from different devices). Note the public chat." data-colorbox-gallery="gallery-field_image-wqYQwMR0G38" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;BigBlueButton in effect with 3 participants (all the author from different devices). Note the public chat.&quot;}"><img src="/sites/default/files/styles/medium/public/2021-11/Screenshot%202021-11-26%20at%2015-00-54%20The%20OERu%20BigBlueButton%20-%20Home%20Room.png?itok=j1O8G0e-" width="220" height="143" alt="BigBlueButton in effect with 3 participants (all the author from different devices). Note the public chat." loading="lazy" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> </div> <div class="clearfix text-formatted field field-node--body field-name-body field-type-text-with-summary field-label-hidden"> <div class="field__items"> <div class="field__item"><p>This tutorial was developed for the <a href="https://samoaksi.ws/">Samoan Knowledge Society Initiative</a>, in which the OER Foundation is very pleased to be involved. (Update 2022-02-10: we have also created a <a href="https://vimeo.com/663520867">video tutorial</a> based on this written tutorial)</p> <p><a href="https://bigbluebutton.org">BigBlueButton</a> (BBB) is a full-featured video conferencing system designed for large scale remote learning at the university level. In some ways, BBB goes beyond feature parity with widely used proprietary video conferencing systems like Zoom or Microsoft Teams, Webex, or Google Hangouts. It's main differences are that BBB</p> <ul><li>was designed from the gound up for educational use, and</li> <li>is Free and Open Source Software (FOSS) and anyone can install it (as we describe in this tutorial) at no cost other than whatever cost you pay for hosting the application and a bit of your time.</li> </ul><p>You can read all about BigBlueButton and how it works using the <a href="https://docs.bigbluebutton.org">excellent documentation</a> provided by its development community.</p> <p>There are no per-user license fees (or <em>any</em> fees), and your users don't need to install special software, vastly simplifying its adoption: BBB works brilliantly on any desktop computer, laptop, or mobile device (tablet or phone) with a modern web browser that implements the <a href="https://webrtc.org">WebRTC</a> open web standard (which is all modern browsers).</p> <p>The <a href="https://github.com/bigbluebutton/">source code for BBB's internal components, and its many ancillary tools</a> is available for anyone to learn from or improve - that's the fundamental benefit of FOSS. Since the outbreak of COVID19, the team at a small Ottowa, Canada-based company, Blindside Networks, has been working tirelessly with the BBB community to improve the BBB code, which is being used all over the world by millions of people every day.</p> <p>This tutorial walks you through provisioning and building your own BigBlueButton instance on a server running <a href="https://ubuntu.com/download/server">Ubuntu GNU/Linux</a> - version 20.04 is the Long Term Support (LTS) version as of this writing - using <a href="https://www.docker.com/docker-community">Docker</a> containers for the various software services and components, with the containers managed by <a href="https://docs.docker.com/compose/">Docker Compose</a>.</p> <p><em>Table of contents</em></p> <ul class="table-of-contents"><li> <p><a href="#introduction">Introduction</a></p> <ul><li> <p><a href="#references">References</a></p> </li> <li> <p><a href="#copying-and-pasting">Copying and pasting</a></p> </li> </ul></li> <li> <p><a href="#information-you-need-to-know">Information you need to know</a></p> </li> <li> <p><a href="#preferences">Preferences</a></p> </li> <li> <p><a href="#set-up-your-virtual-server">Set up your virtual server</a></p> </li> <li> <p><a href="#configure-your-domain-to-point-at-your-server">Configure your Domain to point at your Server</a></p> <ul><li> <p><a href="#best-practice-access-your-server-as-a-non-root-user">Best Practice: access your server as a non-root user</a></p> </li> </ul></li> <li> <p><a href="#a-few-post-install-updates">A few post-install updates</a></p> <ul><li> <p><a href="#update-the-servers-hosts-file">Update the server's hosts file</a></p> </li> </ul></li> <li> <p><a href="#firewall-configuration">Firewall Configuration</a></p> </li> <li> <p><a href="#install-postfix-so-the-server-can-send-email">Install Postfix so the server can send email</a></p> <ul><li> <p><a href="#email-test">Email test</a></p> </li> </ul></li> <li> <p><a href="#install-nginx-webserver-and-lets-encrypt-tools">Install Nginx webserver and Let's Encrypt tools</a></p> </li> <li> <p><a href="#set-up-coturn-certificates">Set up COTURN certificates</a></p> </li> <li> <p><a href="#set-up-nginx-reverse-proxy-configuration">Set up Nginx Reverse Proxy Configuration</a></p> <ul><li> <p><a href="#extra-security-with-dhparampem">Extra security with dhparam.pem</a></p> </li> </ul></li> <li> <p><a href="#install-docker">Install Docker</a></p> </li> <li> <p><a href="#install-docker-compose">Install Docker-compose</a></p> </li> <li> <p><a href="#git-clone-bbb-docker-repository">Git-Clone BBB Docker repository</a></p> </li> <li> <p><a href="#configure-your-bbb">Configure your BBB</a></p> <ul><li> <p><a href="#fix-minor-configuration-error-that-blocks-jodconverter">Fix minor configuration error that blocks JODConverter</a></p> </li> </ul></li> <li> <p><a href="#build-your-bbb">Build your BBB</a></p> </li> <li> <p><a href="#visit-your-new-bbb">Visit your new BBB</a></p> </li> <li> <p><a href="#create-an-admin-user">Create an admin user:</a></p> </li> <li> <p><a href="#run-your-first-conference">Run your first conference</a></p> </li> <li> <p><a href="#next-steps">Next Steps</a></p> </li> </ul><h2><a id="user-content-introduction" href="#introduction" name="introduction" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Introduction</h2> <h3><a id="user-content-references" href="#references" name="references" class="heading-permalink" aria-hidden="true" title="Permalink"></a>References</h3> <p>For this tutorial, we are using a <a href="https://digitalocean.com">Digital Ocean</a> 'Droplet' as our cloud server. You can use an almost identical process to provision a server from any one of a thousand other commodity GNU/Linux cloud hosting providers.</p> <p>To demonstrate the process of defining a sub domain and its IPv4 and IPv6 addresses (via A and AAAA records respectively) I'm using my preferred local domain registrar <a href="https://metaname.net">Metaname</a> (based here in Christchurch, New Zealand, as am I. I know the developer/owner of the service and I trust him). You can use any domain registrar that gives you the ability to configure your 'DNS zone file'. Some of might have your own DNS server or institutional name servers, in which case you might have to request these changes be made on your behalf.</p> <h3><a id="user-content-copying-and-pasting" href="#copying-and-pasting" name="copying-and-pasting" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Copying and pasting</h3> <p>We will be providing a bunch of command lines that you can copy and paste into a terminal on the computer you're using to access your virtual server. Links like</p> <p><code>this one</code></p> <p>are intended for you to copy and paste into the command line. On Linux, please note that in some cases if "CTRL-V" doesn't work to paste, try using "CTRL+SHIFT+V" (that's because in some terminals, "CTRL+V" had a pre-existing purpose as terminals have been around since long before desktops and 'copy-paste" were invented and the arbitrary CTRL-C, CTRL-X, CTRL-V key combos were chosen by Microsoft without any consideration for prior art). Similarly, if you want to copy <em>from</em> a terminal, you might need to use "CTRL+SHIFT+C". Try it.</p> <p>When we ask you edit a file (using the editor you choose to assign to the EDIT shell variable below), we expect you to complete the recommended changes, and then <em>save the file</em> and exit back to the command line.</p> <p>We'll aim to show you what to change using a 'code' box:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">Like this one <span class="kw2">which</span> is suitable <span class="kw1">for</span> multi-line content <span class="br0">(</span>like what you <span class="kw2">find</span> <span class="kw1">in</span> a <span class="kw2">file</span><span class="br0">)</span></pre></div></div> <h2><a id="user-content-information-you-need-to-know" href="#information-you-need-to-know" name="information-you-need-to-know" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Information you need to know</h2> <p>Info you need to have handy to complete this tutorial:</p> <ul><li>You need to know the IPv4 address of your server. Token: [IPv4] - it'll be in the form of nnn.nnn.nnn.nnn where nnn = 0-254, example value 143.110.228.88</li> <li>If your server has one, you need to know its IPv6 address. Token: [IPv6] - it'll be in the form of hhhh:hhhh:hhhh:hhhh:hhhh:hhhh:hhhh where h = 0-9a-f, i.e. a hex value, example 2604:a880:4:1d0::402:b000</li> <li>You need to have a domain name (or a sub domain name) to point at your server's IPv4 and IPv6. Token: [Domain], example domain bbbtest.milll.ws (in this case, bbbtest is the subdomain of the milll.ws domain)</li> <li>The email address your server should send status or admin-related email to. Token [Admin email], example webmaster@[Domain]</li> <li>You'll need a set of authenticating SMTP account settings including - these can be used both on the host and in the BBB installation: <ul><li>[SMTP server] - the domain name or IP address of an existing SMTP server, e.g. about.oerfoundation.org (our one), or smtp.googlemail.com,</li> <li>[SMTP port] - the port number for the service you're using. It'll usually be one of 587 or 465. Older, unauthenticated SMTP servers used to use port 25, but that's now blocked by most ISPs due to its abuse by spammers.</li> <li>[SMTP security] - usually this'll be something like 'SSL' (usually associated with port 465) or 'StartTLS' (usually associated with port 587).</li> <li>[SMTP username] - the username - often an email address - for the authenticating SMTP service, and lastly,</li> <li>[SMTP password] - the password accompanying the username.</li> </ul></li> <li>You'll want an email address that you can check to send test emails to: token [Test email], e.g. <a href="mailto:you@youremailprovider.tld">you@youremailprovider.tld</a>, and finally</li> <li>You'll want a 'from' email address that users of your system will see when they get emails from you. Token: [Outgoing email], e.g. <a href="mailto:notifications@milll.ws">notifications@milll.ws</a>.</li> </ul><h2><a id="user-content-preferences" href="#preferences" name="preferences" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Preferences</h2> <p>You'll need to copy and paste the following lines into any terminal you're using to complete this tutorial, so that that session is aware of your preferred values:</p> <p><code>EDIT=$(which nano)</code></p> <p><code>SITE=bbbtest.milll.ws</code></p> <p>We're going to reference these periodically in the following process, so it's important you set these correctly. To verify them, you can run this in any terminal at any time to verify that the values are still defined and current:</p> <p><code>echo "Our variables = $EDIT and $SITE"</code></p> <h2><a id="user-content-set-up-your-virtual-server" href="#set-up-your-virtual-server" name="set-up-your-virtual-server" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Set up your virtual server</h2> <p>You'll need to log into your cloud service provider's dashboard - in our case, we've used DigitalOcean. That means pointing our browser to <a href="https://digitalocean.com">https://digitalocean.com</a> and either using "Log In" (if we already have an account) or "Sign Up" (if we don't).</p> <p>Once you're logged in, you'll want to go to 'Create' and select 'Droplets' (Digital Ocean calls each of their virtual servers a "Droplet").</p> <p>We've included a screenshot of the Create Droplet page, with the relevant options selected. They are as follows:</p> <ul><li>Choose an image: we choose 'Ubuntu 20.04 LTS'</li> <li>Choose a plan: we choose 'Basic' <ul><li>for CPU options: we choose 'Premium AMD with NVMe SSD'</li> <li>for the size, we choose either the '$24/mo' (choose the '$48/mo' option if you want to be able to support large groups, like 100+ simultaneous participants).</li> </ul></li> <li>Choose a datacenter region: we choose 'San Franciso, zone 3' (which we've determined to be in the 'centre of the internet', i.e. the most central point to give the best website performance to a global audience. You might want to try a different place depending on your location in the globe and your audience).</li> <li>VPC Network - we just leave the default.</li> <li>Select additional options: we check 'IPv6' and 'Monitoring' but <em>not</em> User data.</li> <li>Authentication: if we have added one or more SSH keys to our DigitalOcean profile select 'SSH keys'. If not, you can select 'Password'. It's far more efficient and secure to use SSH whereever possible.</li> <li>We're ony creating 1 Droplet and we Choose a hostname of [Domain]</li> <li>We don't need to add any tags</li> <li>We <em>won't</em> Add backups.</li> </ul><p>Then we can <code>Create Droplet</code>...</p> <p>When the Droplet is finished, we can get the [IPv4] and [IPv6] addresses.</p> <p>We can also either log in via the command line from a local GNU/Linux or UNIX (e.g. MacOS) terminal or via a graphical SSH client (on Microsoft Windows, most people use Putty) via <code>ssh root@[IPv4]</code> or <code>ssh root@[IPv6]</code> (replacing those tokens with their actual IPv4 or IPv6 addresses). If we've set a key, we should get logged in without the need to enter any passwords. If not, we have to enter a password provided by DigitalOcean.</p> <h2><a id="user-content-configure-your-domain-to-point-at-your-server" href="#configure-your-domain-to-point-at-your-server" name="configure-your-domain-to-point-at-your-server" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Configure your Domain to point at your Server</h2> <p>Once you have selected and registered your domain with a domain registrar - we are using Metaname - you can set up (usually through a web interface provided by the registrar)</p> <ul><li>an "A Record" which associates your website's name (your [Domain], e.g. milll.ws, or a subdomain of that domain, e.g. bbb.milll.ws, where 'bbb' is the subdomain part) to the IPv4 address of your server. You should just be able to enter your server's IPv4 address, the domain name (or sub-domain) you want to use for your server.</li> <li>if your registrar offers it it's also important to define an IPv6 record, which is called an "AAAA Record"... you put in the same domain name or subdomain and then your IPv6 address instead of your IPv4 one.</li> </ul><p>I've attached an screenshot of the Metaname interface for configuring these DNS zone records.</p> <p>You might be asked to set a "Time-to-live" (which has to do with the length of time Domain Name Servers are asked to "cache" the association that the A Record specifies) in which case you can put in 3600 seconds or an hour depending on the time units your interface requests... but in most cases that'll be set to a default of an hour automatically.</p> <p>Once your domain A and AAAA records are configured, you should be able to log into your server via <code>ssh root@[Domain]</code>, or, for example, <code>ssh root@bbb.milll.ws</code>. Although it should be instant, depending on your registrar, it might take as long as a few hours (even 24) for your domain name assignments to propogate through the DNS network and be available on your computer.</p> <h3><a id="user-content-best-practice-access-your-server-as-a-non-root-user" href="#best-practice-access-your-server-as-a-non-root-user" name="best-practice-access-your-server-as-a-non-root-user" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Best Practice: access your server as a non-root user</h3> <p>You should be able to test that your A and AAAA Records have been set correctly by logging into your server via SSH using your domain name rather than the IPv4 or IPv6 address you used previously. It should (after you accept the SSH warning that the server's name has a new name) work the same way your original SSH login did. On Linux, you'd SSH via a terminal and enter <code>ssh root@[domain name]</code>. I think you can do similar on MacOS and on Windows, I believe people typically use software called <a href="https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html">Putty</a>...</p> <p>But this will log you into your server as the 'root' user.</p> <p>It's not considered good practice to access your server as root (it's too easy to completely screw it up). Best practice is to create a separate 'non-root' user who has 'sudo' privileges and the ability to log in via SSH. If you are <em>currently logged in as 'root'</em>, you can create a normal user for yourself via (replace [username] with your chosen username):</p> <p><code>U=[username]</code><br /><code>adduser $U</code></p> <p>You will be required to set a password for the user here. Then you can add the new user to several relevant groups that confer the required administrative capabilities.</p> <p><code>adduser $U ssh</code><br /><code>adduser $U admin</code><br /><code>adduser $U sudo</code></p> <p>If you want to change the password for user [username] you can run:</p> <p><code>passwd $U</code></p> <p>then become that user temporarily (note, the root user can 'become' another user without needing to enter a password) and create an SSH key and, in the process, the <code>.ssh</code> directory (directories starting with a '.' are normally 'hidden') for the file into which to put your public SSH key:</p> <p><code>su $U</code><br /><code>ssh-keygen -t rsa -b 2048</code><br /><code>nano ~/.ssh/authorized_keys</code></p> <p>and in that file, copy and paste (without spaces on either end) your <em>current computer's</em> <strong>public</strong> ssh key (<em>never publish</em> your private key anywhere!), save and close the file.</p> <p>From that point, you should be able to SSH to your server via <code>ssh [username]@[domain name]</code> without needing to enter a password.</p> <p>These instructions use 'sudo' in front of commands because I assume you're using a non-root user. The instructions will still work fine even if you're logged in as 'root'.</p> <p>The rest of the tutorial can be run as your 'sudo-capable' non-root user.</p> <h2><a id="user-content-a-few-post-install-updates" href="#a-few-post-install-updates" name="a-few-post-install-updates" class="heading-permalink" aria-hidden="true" title="Permalink"></a>A few post-install updates</h2> <h3><a id="user-content-update-the-servers-hosts-file" href="#update-the-servers-hosts-file" name="update-the-servers-hosts-file" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Update the server's hosts file</h3> <p>This is required due to some quirks of the auto-detection of your domain name used by BigBlueButton - you'll need it later.</p> <p><code>sudo $EDIT /etc/hosts</code></p> <p>and add (or make sure it already has) the following:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">127.0.1.1 <span class="br0">[</span>First part of domain<span class="br0">]</span> 127.0.0.1 localhost <span class="br0">[</span>IPv4<span class="br0">]</span> <span class="br0">[</span>Domain<span class="br0">]</span> <span class="br0">[</span>IPv6<span class="br0">]</span> <span class="br0">[</span>Domain<span class="br0">]</span></pre></div></div> <p>For example - my test system has these details:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">127.0.1.1 bbbtest 127.0.0.1 localhost 143.110.228.88 bbbtest.milll.ws <span class="nu0">2604</span>:a880:<span class="nu0">4</span>:1d0::<span class="nu0">402</span>:b000 bbbtest.milll.ws</pre></div></div> <p>Do an initial post-install update to the latest software versions:</p> <p><code>sudo apt-get update &amp;&amp; sudo apt-get dist-upgrade</code></p> <p>and add a couple useful apps that, for example, track changes to our system configuration for future reference (etckeeper) and allow us to do network troubleshooting (net-tools):</p> <p><code>sudo apt-get install -y etckeeper net-tools</code></p> <p>We also want to enable console logins in the event we have trouble using SSH:</p> <p><code>wget -qO- https://repos-droplet.digitalocean.com/install.sh | sudo bash</code></p> <h2><a id="user-content-firewall-configuration" href="#firewall-configuration" name="firewall-configuration" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Firewall Configuration</h2> <p>We need to configure firewall for admin access via SSH and access to internet for Docker containers</p> <p><code>sudo ufw allow OpenSSH</code></p> <p><code>sudo ufw allow in on docker0</code></p> <p><code>sudo ufw allow from 172.0.0.0/8 to any</code></p> <p>Then we need to adjust the default firewall policy:</p> <p><code>sudo $EDIT /etc/default/ufw</code></p> <p>You'll find this:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co0"># Set the default forward policy to ACCEPT, DROP or REJECT. Please note that</span> <span class="co0"># if you change this you will most likely want to adjust your rules</span> <span class="re2">DEFAULT_FORWARD_POLICY</span>=<span class="st0">"DROP"</span></pre></div></div> <p>which you need to change to</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co0"># Set the default forward policy to ACCEPT, DROP or REJECT. Please note that</span> <span class="co0"># if you change this you will most likely want to adjust your rules</span> <span class="re2">DEFAULT_FORWARD_POLICY</span>=<span class="st0">"ACCEPT"</span></pre></div></div> <p>Next you need to edit this file</p> <p><code>sudo $EDIT /etc/ufw/sysctl.conf</code></p> <p>And change (near the top of the file):</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co0"># Uncomment this to allow this host to route packets between interfaces</span> <span class="co0">#net/ipv4/ip_forward=1</span> <span class="co0">#net/ipv6/conf/default/forwarding=1</span> <span class="co0">#net/ipv6/conf/all/forwarding=1</span></pre></div></div> <p>to</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co0"># Uncomment this to allow this host to route packets between interfaces</span> net<span class="sy0">/</span>ipv4<span class="sy0">/</span><span class="re2">ip_forward</span>=<span class="nu0">1</span> net<span class="sy0">/</span>ipv6<span class="sy0">/</span>conf<span class="sy0">/</span>default<span class="sy0">/</span><span class="re2">forwarding</span>=<span class="nu0">1</span> net<span class="sy0">/</span>ipv6<span class="sy0">/</span>conf<span class="sy0">/</span>all<span class="sy0">/</span><span class="re2">forwarding</span>=<span class="nu0">1</span></pre></div></div> <p>(removing the "#" uncomments those lines)</p> <p>Next, you have to restart the server's networking stack to apply the changes you've made:</p> <p><code>sudo systemctl restart systemd-networkd</code></p> <p>Luckily, this is generally instantaneous, so your connection to your server shouldn't be interrupted.</p> <p>Then you either start or (if it's already running for some reason) restart the firewall</p> <p><code>sudo ufw enable</code></p> <p>or</p> <p><code>sudo service ufw restart</code></p> <p>(running both won't do any harm)</p> <p>And we also update the firewall configuration to tell it to automatically start at boot time.</p> <p><code>sudo $EDIT /etc/ufw/ufw.conf</code></p> <p>Change <code>ENABLED=no</code> to <code>ENABLED=yes</code>.</p> <p>Next we need to set up some specialised rules for the "COTURN" functionality of the BigBlueButton system. <a href="https://coturn.github.io/">COTURN</a> is a FOSS application that provides both <a href="https://www.youtube.com/watch?v=4dLJmZOcWFc">TURN and STUN (video)</a> (<a href="https://blog.ivrpowers.com/post/technologies/what-is-stun-turn-server/">alternative non-video reference</a>) functionality which are core to the WebRTC protocol on which modern video conferencing applications are built, and are necessary for your users to connect their audio and video to a session, particularly if they're behind an institutional firewall.</p> <p><em>Note</em>: in some cases, <em>institutions will have implemented a rather over-the-top-paranoid 'default deny' for video conference hosts</em> which will block access to your BBB instance unless you can get them to add your site's domain name to their institutional whitelist. Sadly, there's no easy way around this.</p> <p><code>sudo ufw allow 7443/tcp</code></p> <p><code>sudo ufw allow 16384:32768/udp</code></p> <p><code>sudo ufw allow 3478/udp</code></p> <p><code>sudo ufw allow 5349/tcp</code></p> <p><code>sudo ufw allow from 10.7.7.0/24</code></p> <p>Ok, that's it for the firewall shenanigans.</p> <h2><a id="user-content-install-postfix-so-the-server-can-send-email" href="#install-postfix-so-the-server-can-send-email" name="install-postfix-so-the-server-can-send-email" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Install Postfix so the server can send email</h2> <p>It's generally a good idea to make sure any server you build can send email, and has a sensible default email address to send stuff to: stuff like administrative messages alerting you to failed tasks, or other system problems. We recommend that you designate an email address for whoever's responsible for this server. We've got a more <a href="/node/28">comprehsensive howto</a> for setting this up if you're wanting extra details.</p> <p>We achieve the outgoing mail functionality using the industrial strength Postfix SMTP server (it's widely used by ISPs for large-scale email services) along with a command line SMTP client that allows us to test our solution from the command line:</p> <p><code>sudo apt install postfix bsd-mailx</code></p> <p>During this installation, you'll be asked by the installer to make some decisions to do preliminary configuration of the Postfix installation. Select the default asnwers except as follows:</p> <ul><li>Select "Internet Site with Smarthost",</li> <li>fill in the [Domain] name you've chosen for your server,</li> <li>the domain name or IP address and port (in the form [SMTP server]:[port], examples might be smtp.oeru.org:465, or 10.11.143.22:465) of your "smarthost" who'll be doing the authenticating SMTP for you - note: <strong>our configuration works with port 465</strong>, and</li> <li>the email address to which you want to receive system-related messages, namely [Admin email].</li> </ul><p>Once that's installed, we need to set up our default email address for this server:</p> <p><code>sudo $EDIT /etc/aliases</code></p> <p>This file will containerd</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co0"># See man 5 aliases for format</span> postmaster: root</pre></div></div> <p>update it to include an email address to send stuff intended for the system admin (aka 'root'):</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co0"># See man 5 aliases for format</span> postmaster: root   root: <span class="br0">[</span>Admin email<span class="br0">]</span></pre></div></div> <p>after writing that, we have to convert that file into a form understood by Postix:</p> <p><code>sudo newaliases</code></p> <p>Next, we need to create a new file with the SMTP 'relay host' aka 'smart host' details:</p> <p><code>sudo $EDIT /etc/postfix/relay_password</code></p> <p>In it you need to put the following:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="br0">[</span>SMTP Server<span class="br0">]</span> <span class="br0">[</span>SMTP username<span class="br0">]</span>:<span class="br0">[</span>SMTP password<span class="br0">]</span></pre></div></div> <p>Here's an example:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">about.oerfoundation.org demosmtp<span class="sy0">@</span>milll.ws:7TLM6qGoqZXHfkDmkh6</pre></div></div> <p>Once you've set that up, we have to again prepare that file for use by Postfix:</p> <p><code>sudo postmap /etc/postfix/relay_password</code></p> <p>Finally, we need to update Postfix's main configuration to tell it to use our authenticating SMTP details:</p> <p><code>sudo $EDIT /etc/postfix/main.cf</code></p> <p>You'll need to make main.cf look like this - specifically commenting out <code>smtp_tls_security_level=may</code> by adding a '#' at the start of the line, and then adding the details below <code># added to configure accessing the relay host via authenticating SMTP</code>. You should also take this opportunity to confirm that your [Domain], [SMTP server] and [SMTP port] are set correctly.</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">smtpd_banner = <span class="re1">$myhostname</span> ESMTP <span class="re1">$mail_name</span> <span class="br0">(</span>Ubuntu<span class="br0">)</span> biff = no   <span class="co0"># appending .domain is the MUA's job.</span> append_dot_mydomain = no   <span class="co0"># Uncomment the next line to generate "delayed mail" warnings</span> <span class="co0">#delay_warning_time = 4h</span>   readme_directory = no   <span class="co0"># See http://www.postfix.org/COMPATIBILITY_README.html -- default to 2 on</span> <span class="co0"># fresh installs.</span> compatibility_level = <span class="nu0">2</span>   <span class="co0"># TLS parameters</span> <span class="re2">smtpd_tls_cert_file</span>=<span class="sy0">/</span>etc<span class="sy0">/</span>ssl<span class="sy0">/</span>certs<span class="sy0">/</span>ssl-cert-snakeoil.pem <span class="re2">smtpd_tls_key_file</span>=<span class="sy0">/</span>etc<span class="sy0">/</span>ssl<span class="sy0">/</span>private<span class="sy0">/</span>ssl-cert-snakeoil.key <span class="re2">smtpd_tls_security_level</span>=may   <span class="re2">smtp_tls_CApath</span>=<span class="sy0">/</span>etc<span class="sy0">/</span>ssl<span class="sy0">/</span>certs <span class="co0">## Commented out for SmartHost configuration</span> <span class="co0">#smtp_tls_security_level=may</span> smtp_tls_session_cache_database = btree:<span class="co1">${data_directory}</span><span class="sy0">/</span>smtp_scache     smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination myhostname = <span class="br0">[</span>Domain<span class="br0">]</span> alias_maps = hash:<span class="sy0">/</span>etc<span class="sy0">/</span>aliases alias_database = hash:<span class="sy0">/</span>etc<span class="sy0">/</span>aliases myorigin = <span class="sy0">/</span>etc<span class="sy0">/</span>mailname mydestination = <span class="re1">$myhostname</span>, bbbtest.milll.ws, localhost.milll.ws, localhost relayhost = <span class="br0">[</span>SMTP server<span class="br0">]</span>:<span class="nu0">465</span> mynetworks = 127.0.0.0<span class="sy0">/</span><span class="nu0">8</span> <span class="br0">[</span>::ffff:127.0.0.0<span class="br0">]</span><span class="sy0">/</span><span class="nu0">104</span> <span class="br0">[</span>::<span class="nu0">1</span><span class="br0">]</span><span class="sy0">/</span><span class="nu0">128</span> mailbox_size_limit = <span class="nu0">0</span> recipient_delimiter = + inet_interfaces = all inet_protocols = all   <span class="co0"># added to configure accessing the relay host via authenticating SMTP</span> smtp_sasl_auth_enable = <span class="kw2">yes</span> smtp_sasl_password_maps = hash:<span class="sy0">/</span>etc<span class="sy0">/</span>postfix<span class="sy0">/</span>relay_password smtp_sasl_security_options = smtp_tls_security_level = encrypt   <span class="co0"># add this if you're using Ubuntu 20.04, and comment out (with a "#") the</span> <span class="co0"># earlier line smtp_tls_security_level = may to save errors in 'postfix check'</span> <span class="co0"># and uncomment this line (by removing the #)</span> smtp_tls_wrappermode = <span class="kw2">yes</span></pre></div></div> <p>Here's an example of what the final code could look like (don't use these exact values as they won't work for you):</p> <p>First, I commented this out:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co4">#</span><span class="re2">smtp_tls_security_level</span>=may</pre></div></div> <p>Made sure this was my [Domain]</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">myhostname = bbbtest.milll.ws</pre></div></div> <p>Made sure this had my [SMTP server] and [SMTP port] details:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">relayhost = about.oerfoundation.org:<span class="nu0">465</span></pre></div></div> <p>And I added this at the end:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co0"># added to configure accessing the relay host via authenticating SMTP</span> smtp_sasl_auth_enable = <span class="kw2">yes</span> smtp_sasl_password_maps = hash:<span class="sy0">/</span>etc<span class="sy0">/</span>postfix<span class="sy0">/</span>relay_password smtp_sasl_security_options = smtp_tls_security_level = encrypt   <span class="co0"># add this if you're using Ubuntu 20.04, and comment out (with a "#") the</span> <span class="co0"># earlier line smtp_tls_security_level = may to save errors in 'postfix check'</span> <span class="co0"># and uncomment this line (by removing the #)</span> smtp_tls_wrappermode = <span class="kw2">yes</span></pre></div></div> <p>After this, the Postfix configuration is done. We can check that we haven't got any typos via</p> <p><code>sudo postfix check</code></p> <p>If not, we can apply our configuration changes:</p> <p><code>sudo postfix reload</code></p> <p>and we can confirm it all worked correctly by checking the log file for Postfix:</p> <p><code>sudo less +G /var/log/mail.log</code></p> <p>Note: you might see a warning like this: <code>postfix/postfix-script: warning: symlink leaves directory: /etc/postfix/./makedefs.out</code> - it's spurious and you don't need to worry about it.</p> <h3><a id="user-content-email-test" href="#email-test" name="email-test" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Email test</h3> <p>Next we can test our outgoing SMTP by sending a test message via the command line - <em>use a test email that you can check!</em>:</p> <p><code>mail [Test email]</code></p> <p>for example:</p> <p><code>mail demo@oerfoundation.org</code></p> <p>After you hit Enter, you'll be shown</p> <p><code>Subject:</code> {Enter an email subject, e.g. "Test email from [Domain]"}</p> <p>After that, hit Enter, and you'll be shown a blank line. On that line:</p> <p>{Enter your email text, e.g. "This is just a test."}</p> <p>Then, to finish your email, type <em>CTRL-D</em> which will show you another line</p> <p><code>CC:</code> {Enter any CC email addresses, e.g. <a href="mailto:mybackupemail@anotherprovider.tld">mybackupemail@anotherprovider.tld</a> }</p> <p>After you hit Enter, your email should be sent if all your configuration options are accepted by your remote host (the SMTP server at the address [SMTP server] with all the other details you entered).</p> <p>You can check if it worked by looking at the Postfix log again:</p> <p><code>sudo less +G /var/log/mail.log</code></p> <p>If it sent the email, you'll see something like (but with different IP addresses, serial numbers, and addresses) - the key bit is the <code>status=sent</code> bit and the <code>250</code> code from the SMTP server:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">08FBD2B501C: <span class="re2">to</span>=<span class="sy0">&lt;</span>demo<span class="sy0">@</span>oerfoundation.org<span class="sy0">&gt;</span>, <span class="re2">relay</span>=dovecot<span class="br0">[</span>172.22.1.250<span class="br0">]</span>:<span class="nu0">24</span>, <span class="re2">delay</span>=<span class="nu0">1.3</span>, <span class="re2">delays</span>=<span class="nu0">1.3</span><span class="sy0">/</span><span class="nu0">0.02</span><span class="sy0">/</span><span class="nu0">0</span><span class="sy0">/</span><span class="nu0">0.04</span>, <span class="re2">dsn</span>=2.0.0, <span class="re2">status</span>=sent <span class="br0">(</span><span class="nu0">250</span> 2.0.0 <span class="sy0">&lt;</span>demo<span class="sy0">@</span>oerfoundation.org<span class="sy0">&gt;</span> MMokCa2TpWEffAAAPgmdMA Saved<span class="br0">)</span></pre></div></div> <p>You can also check for receipt of email and verify receipt (note, if you don't get it quickily, check your email spam folder).</p> <h2><a id="user-content-install-nginx-webserver-and-lets-encrypt-tools" href="#install-nginx-webserver-and-lets-encrypt-tools" name="install-nginx-webserver-and-lets-encrypt-tools" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Install Nginx webserver and Let's Encrypt tools</h2> <p><code>sudo apt install -y nginx-full ca-certificates letsencrypt ssl-cert</code></p> <p>You need to tell your firewall to open the ports that the Nginx webserver will use:</p> <p><code>sudo ufw allow "Nginx Full"</code></p> <p>And then we need to create a special configuration for Let's Encrypt and then an identify verification directory:</p> <p><code>sudo mkdir /etc/nginx/includes</code></p> <p><code>sudo mkdir /var/www/letsencrypt</code></p> <p>To create the specific configuration, we create this file:</p> <p><code>sudo $EDIT /etc/nginx/includes/letsencrypt.conf</code></p> <p>And fill it with this information:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co0"># Rule for legitimate ACME Challenge requests</span> location ^~ <span class="sy0">/</span>.well-known<span class="sy0">/</span>acme-challenge<span class="sy0">/</span> <span class="br0">{</span> default_type <span class="st0">"text/plain"</span>; <span class="co0"># this can be any directory, but this name keeps it clear</span> root <span class="sy0">/</span>var<span class="sy0">/</span>www<span class="sy0">/</span>letsencrypt; <span class="br0">}</span>   <span class="co0"># Hide /acme-challenge subdirectory and return 404 on all requests.</span> <span class="co0"># It is somewhat more secure than letting Nginx return 403.</span> <span class="co0"># Ending slash is important!</span> location = <span class="sy0">/</span>.well-known<span class="sy0">/</span>acme-challenge<span class="sy0">/</span> <span class="br0">{</span> <span class="kw3">return</span> <span class="nu0">404</span>; <span class="br0">}</span></pre></div></div> <p>We'll reference this file in our Nginx configuration file for the reverse proxy functionality.</p> <h2><a id="user-content-set-up-coturn-certificates" href="#set-up-coturn-certificates" name="set-up-coturn-certificates" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Set up COTURN certificates</h2> <p>In preparation for creating our Let's Encrypt SSL certificate for our [Domain], we're going to set up a 'hook' which is triggered when our SSL certificate is created, and it copies our current certificate to a place where the COTURN server can find it.</p> <p><code>sudo $EDIT /etc/letsencrypt/renewal-hooks/deploy/coturn.sh</code></p> <p>Copy and past the following into your file (<strong>replacing [Domain] with your domain name</strong>):</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co0">#!/bin/bash</span>   <span class="re2">DOMAIN</span>=<span class="br0">[</span>Domain<span class="br0">]</span> <span class="re2">DEST</span>=<span class="sy0">/</span>etc<span class="sy0">/</span>coturn-ssl   <span class="co0">#if [[ 1 == 1 ]]; then</span> <span class="kw1">if</span> <span class="br0">[</span><span class="br0">[</span> <span class="re1">$RENEWED_DOMAINS</span> == <span class="sy0">*</span><span class="st0">"<span class="es2">$DOMAIN</span>"</span><span class="sy0">*</span> <span class="br0">]</span><span class="br0">]</span>; <span class="kw1">then</span> <span class="kw2">cp</span> <span class="re5">-L</span> <span class="sy0">/</span>etc<span class="sy0">/</span>letsencrypt<span class="sy0">/</span>live<span class="sy0">/</span><span class="co1">${DOMAIN}</span><span class="sy0">/</span>fullchain.pem <span class="re1">$DEST</span> <span class="kw2">cp</span> <span class="re5">-L</span> <span class="sy0">/</span>etc<span class="sy0">/</span>letsencrypt<span class="sy0">/</span>live<span class="sy0">/</span><span class="co1">${DOMAIN}</span><span class="sy0">/</span>privkey.pem <span class="re1">$DEST</span> <span class="kw3">echo</span> <span class="st0">"updated <span class="es2">$DOMAIN</span> certificates in <span class="es2">$DEST</span>"</span> <span class="kw1">fi</span></pre></div></div> <p>Next, make that script executable:</p> <p><code>sudo chmod a+x /etc/letsencrypt/renewal-hooks/deploy/coturn.sh</code></p> <p>so that it is run automatically anytime the relevant certificate is created or renewed.</p> <p>After that, we have to create a place for the COTURN-specific certificates to go:</p> <p><code>sudo mkdir /etc/coturn-ssl</code></p> <h2><a id="user-content-set-up-nginx-reverse-proxy-configuration" href="#set-up-nginx-reverse-proxy-configuration" name="set-up-nginx-reverse-proxy-configuration" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Set up Nginx Reverse Proxy Configuration</h2> <p>Next we have to set up our Nginx webserver on our virtual server, which will be the Docker container host for our BigBlueButton system. The reverse proxy configuration will allow the virtual server to receive web requests from BBB users and pass them securely through to the right Docker container.</p> <p>Create a suitable configuration file - at the OERF we use the convention of calling our configuration</p> <p><code>sudo $EDIT /etc/nginx/sites-available/$SITE</code></p> <p>In this file, you'll past in the following, replacing [Domain] as appropriate - now might be a good idea to try out search and replace in your text editor!</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">server <span class="br0">{</span> listen <span class="nu0">80</span>; <span class="co0"># if your host doens't support IPv6, comment out the following line</span> listen <span class="br0">[</span>::<span class="br0">]</span>:<span class="nu0">80</span>; server_name <span class="br0">[</span>Domain<span class="br0">]</span>;   access_log <span class="sy0">/</span>var<span class="sy0">/</span>log<span class="sy0">/</span>nginx<span class="sy0">/</span><span class="br0">[</span>Domain<span class="br0">]</span>.access.log; error_log <span class="sy0">/</span>var<span class="sy0">/</span>log<span class="sy0">/</span>nginx<span class="sy0">/</span><span class="br0">[</span>Domain<span class="br0">]</span>.error.log;   include includes<span class="sy0">/</span>letsencrypt.conf;   <span class="co0"># redirect all HTTP traffic to HTTPS.</span> location <span class="sy0">/</span> <span class="br0">{</span> <span class="kw3">return</span> <span class="nu0">302</span> https:<span class="sy0">//</span><span class="re1">$server_name</span><span class="re1">$request_uri</span>; <span class="br0">}</span> <span class="br0">}</span>   server <span class="br0">{</span> listen <span class="nu0">443</span> ssl http2 default_server; <span class="co0"># if your host doens't support IPv6, comment out the following line</span> listen <span class="br0">[</span>::<span class="br0">]</span>:<span class="nu0">443</span> ssl http2 default_server; server_name <span class="br0">[</span>Domain<span class="br0">]</span>;   <span class="co0"># We comment these out *after* we have successfully geneated our Let's Encrypt certificate for [Domain].</span> ssl_certificate <span class="sy0">/</span>etc<span class="sy0">/</span>ssl<span class="sy0">/</span>certs<span class="sy0">/</span>ssl-cert-snakeoil.pem; ssl_certificate_key <span class="sy0">/</span>etc<span class="sy0">/</span>ssl<span class="sy0">/</span>private<span class="sy0">/</span>ssl-cert-snakeoil.key; <span class="co0"># we start with these commented out until after we can generate our Let's Encrypt certificate for [Domain]!</span> <span class="co0">#ssl_certificate /etc/letsencrypt/live/[Domain]/fullchain.pem;</span> <span class="co0">#ssl_certificate_key /etc/letsencrypt/live/[Domain]/privkey.pem;</span> ssl_dhparam <span class="sy0">/</span>etc<span class="sy0">/</span>ssl<span class="sy0">/</span>certs<span class="sy0">/</span>dhparam.pem;   access_log <span class="sy0">/</span>var<span class="sy0">/</span>log<span class="sy0">/</span>nginx<span class="sy0">/</span><span class="br0">[</span>Domain<span class="br0">]</span>.access.log; error_log <span class="sy0">/</span>var<span class="sy0">/</span>log<span class="sy0">/</span>nginx<span class="sy0">/</span><span class="br0">[</span>Domain<span class="br0">]</span>.error.log;   location <span class="sy0">/</span> <span class="br0">{</span> proxy_http_version <span class="nu0">1.1</span>; <span class="co0"># for BBB 2.4, the port used is 48087. For earlier versions of BBB, it's 8080</span> proxy_pass http:<span class="sy0">//</span>127.0.0.1:<span class="nu0">48087</span>; proxy_set_header Host <span class="re1">$host</span>; proxy_set_header X-Real-IP <span class="re1">$remote_addr</span>; proxy_set_header X-Forwarded-For <span class="re1">$proxy_add_x_forwarded_for</span>; proxy_set_header X-Forwarded-Proto <span class="re1">$scheme</span>; proxy_set_header Upgrade <span class="re1">$http_upgrade</span>; <span class="co0">#proxy_set_header Connection $connection_upgrade;</span> proxy_set_header Connection <span class="st0">"Upgrade"</span>; proxy_cache_bypass <span class="re1">$http_upgrade</span>; <span class="br0">}</span> <span class="br0">}</span></pre></div></div> <p><em><strong>Update 2021-12-30</strong></em> With the release of BBB 2.4 a few weeks ago, the Docker configuration now has one 'breaking' change: the port has been changed to 48087! So, if you are installing an older version of BBB, the previous default was port 8080. If you get a 502 error after setting up your containers, check to make sure you haven't been bitten by this little detail!</p> <p>Right - back to the process...</p> <p>After creating it, we have to 'enable' the configuration:</p> <p><code>sudo ln -sf /etc/nginx/sites-available/$SITE /etc/nginx/sites-enabled/</code></p> <p>It's also not a bad idea to disable the Nginx default configuration, as it can sometimes interfere with things:</p> <p><code>sudo rm /etc/nginx/sites-enabled/default</code></p> <h3><a id="user-content-extra-security-with-dhparampem" href="#extra-security-with-dhparampem" name="extra-security-with-dhparampem" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Extra security with dhparam.pem</h3> <p>We're going to create a 'dhparam' certificate for your configuration. These take a long time to generate - between 10-30 minutes, depending not on the speed of your computer, but on the rate at which it creates 'random events' that allow it to create a suitablely complex random prime number.</p> <p><code>sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048</code></p> <p>If your time isn't limited, you can increase the size of your dhparam from 2048 to 4096 - it'll take quite a lot longer to create.</p> <p>Ok - now that we've created our dhparam.pem, referenced in our Nginx configuration for [Domain], we should have everything in place. We can now test Nginx's configuration:</p> <p><code>sudo nginx -t</code></p> <p>If you don't get any errors or warnings, you can activate the new configuration:</p> <p><code>sudo service nginx reload</code></p> <p>You server should now successfully respond to your [Domain], but it'll redirect to HTTPS using the default (self-signed) certificates that are valid as far as Nginx is concerned, but your browser won't let you access the page due to those inappropriate certificates. You can test it by putting <code>http://[Domain]</code> into your browser and seeing if it redirects you to <code>https://[Domain]</code> and a 'bad certificate` (or similar) page.</p> <p>To fix that, we can now generate a Let's Encrypt certificate which <em>will</em> be valid.</p> <p><code>sudo letsencrypt certonly --webroot -w /var/www/letsencrypt -d ${SITE}</code></p> <p>Since this is your first time running the letsencrypt script, you'll be asked for a contact email (so the Let's Encrypt system can warn you if your certificates are going to be expiring soon!) - use your [Admin email] for this. You can also opt in to allowing them to get anonymous statistics from your site.</p> <p>Once you've done that, the Let's Encrypt system will verify that you (the person requesting the certificate) also controls the server that's requesting it (using the details specified in the <code>/etc/nginx/includes/letsencrypt.conf</code> file, along with data that the letsencrypt script writes into a special file in the <code>/var/www/letsencrypt</code> directory) and, all going well, you'll see a "Congratulations!" message telling you that you have new certificates for your [Domain].</p> <p>Now is a good time to check if your renew-hook worked properly - there should now be two files in <code>/etc/coturn-ssl</code>, namely <code>fullchain.pem</code> and <code>privkey.pem</code>. If that's not the case, something might have gone wrong.</p> <p>Then you can re-edit your Nginx confguration file:</p> <p><code>sudo $EDIT /etc/nginx/sites-available/$SITE</code></p> <p>and comment out the default certificates and uncomment the [Domain]-specific certificates, like this (we'll assume you've already got your [Domain] substituted!):</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">  ...   <span class="co0"># We comment these out *after* we have successfully geneated our Let's Encrypt certificate for [Domain].</span> <span class="co0">#ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;</span> <span class="co0">#ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key; </span> <span class="co0"># we start with these commented out until after we can generate our Let's Encrypt certificate for [Domain]!</span> ssl_certificate <span class="sy0">/</span>etc<span class="sy0">/</span>letsencrypt<span class="sy0">/</span>live<span class="sy0">/</span><span class="br0">[</span>Domain<span class="br0">]</span><span class="sy0">/</span>fullchain.pem; ssl_certificate_key <span class="sy0">/</span>etc<span class="sy0">/</span>letsencrypt<span class="sy0">/</span>live<span class="sy0">/</span><span class="br0">[</span>Domain<span class="br0">]</span><span class="sy0">/</span>privkey.pem; ssl_dhparam <span class="sy0">/</span>etc<span class="sy0">/</span>ssl<span class="sy0">/</span>certs<span class="sy0">/</span>dhparam.pem;   ...  </pre></div></div> <p>Once you've got that going, you again check to make sure your Nginx config is valid:</p> <p><code>sudo nginx -t</code></p> <p>and apply your configruation changes:</p> <p><code>sudo service nginx reload</code></p> <p>If you now go to <code>http://[Domain]</code> in your browser, you should be redirected to <code>https://[Domain]</code> and you shouldn't get any <em>certificate</em> errors, although you might get a "502" error because the service that reverse proxy configuration is trying to send you to doesn't yet exist! That's what we're expecting.</p> <p>One more thing - we need to make sure that the <code>coturn.sh</code> renewal hook script has run. We can check to make sure that there are two files, <code>fullchain.pem</code> and <code>privkey.pem</code> in the <code>/etc/coturn-ssl</code> directory. If we start the Docker containers before these files exist, the Docker daemon creates them <em>as directories</em> by default which can lead to all sorts of trickiness. Run</p> <p><code>sudo ls -l /etc/coturn-ssl/</code></p> <p>and make sure you get a result like this:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="re5">-rw-r--r--</span> <span class="nu0">1</span> root root <span class="nu0">5588</span> Dec <span class="nu0">13</span> 08:<span class="nu0">42</span> fullchain.pem <span class="re5">-rw-------</span> <span class="nu0">1</span> root root <span class="nu0">1704</span> Dec <span class="nu0">13</span> 08:<span class="nu0">42</span> privkey.pem</pre></div></div> <h2><a id="user-content-install-docker" href="#install-docker" name="install-docker" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Install Docker</h2> <p>Now we have to set up the Docker container support by first registering a new package source...</p> <p><code>sudo apt-get install -y apt-transport-https curl gnupg lsb-release</code></p> <p><code>sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg</code></p> <p><code>sudo echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list &gt; /dev/null</code></p> <p>updating our list of available packages to include the Docker-related packages...</p> <p><code>sudo apt-get update</code></p> <p>and then install them:</p> <p><code>sudo apt-get install -y docker-ce docker-ce-cli containerd.io</code></p> <p>Which installs a bunch of dependencies as well.</p> <p>To make sure that our non-root user can talk to the Docker server, we need to jump through a couple more hoops:</p> <p><code>sudo addgroup docker</code></p> <p><code>sudo adduser dave docker</code></p> <p>After doing this, the easiest thing is to log out and log back in again to make sure my user can access the new permissions. To confirm that my user has docker group privileges, I can run</p> <p><code>id</code></p> <p>which should give a result like</p> <p><code>uid=1000(dave) gid=1000(dave) groups=1000(dave),4(adm),27(sudo),1001(docker)</code></p> <p>with the latter being the confirmation.</p> <h2><a id="user-content-install-docker-compose" href="#install-docker-compose" name="install-docker-compose" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Install Docker-compose</h2> <p>Now we're almost to the BigBlueButton part - we just need to install the Docker Compose framework:</p> <p><code>sudo apt install python3-pip</code></p> <p><code>sudo pip install -U pip</code></p> <p><code>sudo pip install -U docker-compose</code></p> <p>Once you've done that, you should be able to run <code>docker-compose</code> at your command prompt and it should give you the docker-compose help page. If so, great work! Almost there.</p> <h2><a id="user-content-git-clone-bbb-docker-repository" href="#git-clone-bbb-docker-repository" name="git-clone-bbb-docker-repository" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Git-Clone BBB Docker repository</h2> <p>Now it's time to install the set of Docker containers that make up the BigBlueButton stack of coordinated services.</p> <p>We make a place for the docker configuration and data to live:</p> <p><code>sudo mkdir -p /home/docker/</code></p> <p>go into it</p> <p><code>cd /home/docker</code></p> <p>and then we use the magic of 'git' (if you don't know it, this is one of the single most powerful tools programmers use - everyone would benefit from knowing how 'version control' works - git is by far the most widely used version control aka 'source code control' system in the world. It's open source.) to 'clone' the BigBlueButton developers' Docker definitions code:</p> <p><code>sudo git clone -b main --recurse-submodules https://github.com/bigbluebutton/docker.git bbb-docker</code></p> <p>That command puts all the code into a directory called <code>bbb-docker</code> so let's go there:</p> <p><code>cd bbb-docker</code></p> <p>and in there, we run this command to gram a second layer of code that is referenced by the first layer we already downloaded in the previous step:</p> <p><code>sudo git submodule update --init</code></p> <p>Finally, we run this handy script provided by the BBB developers to set up a working Docker Compose configuration:</p> <p><code>sudo ./scripts/setup</code></p> <p>This will script will ask you some questions about your system. These are the questions <em>and</em> the answers we'll use - note, where I've written [IPv4] and [IPv6] below, you should see the actual IPv4 and IPv6 addresses for your server:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">Should greenlight be included? <span class="br0">(</span>y<span class="sy0">/</span>n<span class="br0">)</span>: y Should an automatic HTTPS Proxy be included? <span class="br0">(</span>y<span class="sy0">/</span>n<span class="br0">)</span>: n Should a coturn be included? <span class="br0">(</span>y<span class="sy0">/</span>n<span class="br0">)</span>: y Coturn needs TLS to <span class="kw1">function</span> properly. Since automatic HTTPS Proxy is disabled, you must provide a relative or absolute path to your certificates. Please enter path to cert.pem: <span class="sy0">/</span>etc<span class="sy0">/</span>coturn-ssl<span class="sy0">/</span>fullchain.pem Please enter path to key.pem: <span class="sy0">/</span>etc<span class="sy0">/</span>coturn-ssl<span class="sy0">/</span>privkey.pem Should a Prometheus exporter be included? <span class="br0">(</span>y<span class="sy0">/</span>n<span class="br0">)</span>: y Please enter the domain name: bbbtest.milll.ws Should the recording feature be included? IMPORTANT: this is currently a big privacy issues, because it will record everything <span class="kw2">which</span> happens <span class="kw1">in</span> the conference, even when the button suggests, that it does not. <span class="kw2">make</span> sure that you always get people<span class="st_h">'s consent, before they join a room! https://github.com/bigbluebutton/bigbluebutton/issues/9202 Choice (y/n): y Is [IPv4] your external IPv4 address? (y/n): y Is [IPv6] your external IPv6 address? (y/n): y</span></pre></div></div> <p>Once you finish answering these questions, the script creates a file called <code>.env</code> (the leading '.' means it's a 'hidden' file that won't show up in normal directory listings - you have to know it's there, as it holds important system values and shouldn't be deleted.</p> <h2><a id="user-content-configure-your-bbb" href="#configure-your-bbb" name="configure-your-bbb" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Configure your BBB</h2> <p>But now, we're going to tweak it, because it holds all the important customised values we need to configure our BigBlueButton service.</p> <p><code>sudo $EDIT .env</code></p> <p>When you're editing the file, scroll down through it and adjust the values you find as follows.</p> <p>Uncomment this one</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="re2">ALLOW_MAIL_NOTIFICATIONS</span>=<span class="kw2">true</span></pre></div></div> <p>Set the following</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="re2">SMTP_SERVER</span>=<span class="br0">[</span>SMTP server<span class="br0">]</span> <span class="re2">SMTP_PORT</span>=<span class="br0">[</span>SMTP port<span class="br0">]</span> <span class="re2">SMTP_DOMAIN</span>=<span class="br0">[</span>Domain<span class="br0">]</span> <span class="re2">SMTP_USERNAME</span>=<span class="br0">[</span>SMTP username<span class="br0">]</span> <span class="re2">SMTP_PASSWORD</span>=<span class="br0">[</span>SMTP password<span class="br0">]</span> <span class="re2">SMTP_AUTH</span>=plain <span class="re2">SMTP_STARTTLS_AUTO</span>=<span class="kw2">true</span> <span class="co0">#</span> <span class="re2">SMTP_SENDER</span>=<span class="br0">[</span>Outgoing email<span class="br0">]</span></pre></div></div> <p>and finally set</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="re2">DEFAULT_REGISTRATION</span>=invite</pre></div></div> <p>This last one makes it possible for you to invite external people to make use of your BigBlueButton instance - they can create a log in and create their own rooms over which they'll have some control. These can be people in your organisation or community for whom you want your BBB to be available as a resource.</p> <h3><a id="user-content-fix-minor-configuration-error-that-blocks-jodconverter" href="#fix-minor-configuration-error-that-blocks-jodconverter" name="fix-minor-configuration-error-that-blocks-jodconverter" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Fix minor configuration error that blocks JODConverter</h3> <p>One of the containers used in the BBB stack is called <a href="https://github.com/bigbluebutton/docker/tree/develop/mod/jodconverter">JODConverter</a> which, in standard Free and Open Source Software tradition, makes use of an <a href="https://github.com/EugenMayer/docker-image-jodconverter">'upstream' development</a> by another developer, <a href="https://github.com/EugenMayer">EugenMayer</a>, which in turn makes use of an <a href="https://hub.docker.com/r/bellsoft/liberica-openjdk-debian">upstream development</a>...</p> <p>The issue is that a recent update (in early December 2021) has broken the process of launching this container. This is a big problem because the JODConverter provides the services of converting uploaded presentations and downloaded documents (like BBB's Public Chat or Shared Notes) in various useful file formats.</p> <p>Turns out the fix is easy. While still in <code>bbb-docker</code>, Just run</p> <p><code>sudo $EDIT mod/jodconverter/Dockerfile</code></p> <p>and add the following line to the very bottom of the file:</p> <p><code>CMD ["--spring.config.additional-location=optional:/etc/app/"]</code></p> <p>Save it, and you're done. I have <a href="https://github.com/bigbluebutton/docker/issues/178">submitted an issue</a> with the fix I've found to the <code>bigbluebutton/docker</code> project.</p> <p>Once that configuration is done, it's finally time to...</p> <h2><a id="user-content-build-your-bbb" href="#build-your-bbb" name="build-your-bbb" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Build your BBB</h2> <p>The first time it's run, this command will trigger the building of a set of no less than 22 separate Docker containers, each running its own crucial service as part of the BigBlueButton stack.</p> <p><code>sudo docker-compose up -d</code></p> <p><em>Note: this process can take a LONG time, like an hour or more</em> depending on your server's internet connection speed.</p> <p>If you want to see how long it takes the first time, run this instead:</p> <p><code>sudo time docker-compose up -d</code></p> <p>which will give you a readout of the time the command takes to complete.</p> <h2><a id="user-content-visit-your-new-bbb" href="#visit-your-new-bbb" name="visit-your-new-bbb" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Visit your new BBB</h2> <p>Once you next see the command prompt ($ or #), it means that your BBB system is starting up. You can see the status of the containers by running</p> <p><code>sudo docker-compose ps</code></p> <p>which should give you something that looks like this, once everything is running (it might take a few minutes!):</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"> Name Command State Ports <span class="re5">-----------------------------------------------------------------------------------------------------------</span> bbb-docker_apps-akka_1 <span class="sy0">/</span>bin<span class="sy0">/</span><span class="kw2">sh</span> <span class="re5">-c</span> dockerize - ... Up bbb-docker_bbb-web_1 <span class="sy0">/</span>entrypoint.sh Up <span class="br0">(</span>healthy<span class="br0">)</span> bbb-docker_coturn_1 docker-entrypoint.sh <span class="re5">--ext</span> ... Up bbb-docker_etherpad_1 <span class="sy0">/</span>entrypoint.sh Up <span class="nu0">9001</span><span class="sy0">/</span>tcp bbb-docker_freeswitch_1 <span class="sy0">/</span>bin<span class="sy0">/</span><span class="kw2">sh</span> <span class="re5">-c</span> <span class="sy0">/</span>entrypoint.sh Up bbb-docker_fsesl-akka_1 <span class="sy0">/</span>bin<span class="sy0">/</span><span class="kw2">sh</span> <span class="re5">-c</span> dockerize - ... Up bbb-docker_greenlight_1 bin<span class="sy0">/</span>start Up 10.7.7.1:<span class="nu0">5000</span>-<span class="sy0">&gt;</span><span class="nu0">80</span><span class="sy0">/</span>tcp bbb-docker_html5-backend-<span class="nu0">1</span>_1 <span class="sy0">/</span>entrypoint.sh Up bbb-docker_html5-backend-<span class="nu0">2</span>_1 <span class="sy0">/</span>entrypoint.sh Up bbb-docker_html5-frontend-<span class="nu0">1</span>_1 <span class="sy0">/</span>entrypoint.sh Up bbb-docker_html5-frontend-<span class="nu0">2</span>_1 <span class="sy0">/</span>entrypoint.sh Up bbb-docker_jodconverter_1 <span class="sy0">/</span>docker-entrypoint.sh <span class="re5">--sp</span> ... Up bbb-docker_kurento_1 <span class="sy0">/</span>entrypoint.sh Up <span class="br0">(</span>healthy<span class="br0">)</span> bbb-docker_mongodb_1 docker-entrypoint.sh mongo ... Up <span class="br0">(</span>healthy<span class="br0">)</span> <span class="nu0">27017</span><span class="sy0">/</span>tcp bbb-docker_nginx_1 <span class="sy0">/</span>docker-entrypoint.sh ngin ... Up bbb-docker_periodic_1 <span class="sy0">/</span>entrypoint.sh Up bbb-docker_postgres_1 docker-entrypoint.sh postgres Up <span class="br0">(</span>healthy<span class="br0">)</span> <span class="nu0">5432</span><span class="sy0">/</span>tcp bbb-docker_prometheus-exporter_1 python server.py Up <span class="nu0">9688</span><span class="sy0">/</span>tcp bbb-docker_recordings_1 <span class="sy0">/</span>bin<span class="sy0">/</span><span class="kw2">sh</span> <span class="re5">-c</span> <span class="sy0">/</span>entrypoint.sh Up bbb-docker_redis_1 docker-entrypoint.sh redis ... Up <span class="br0">(</span>healthy<span class="br0">)</span> <span class="nu0">6379</span><span class="sy0">/</span>tcp bbb-docker_webhooks_1 <span class="sy0">/</span>bin<span class="sy0">/</span><span class="kw2">sh</span> <span class="re5">-c</span> <span class="sy0">/</span>entrypoint.sh Up bbb-docker_webrtc-sfu_1 .<span class="sy0">/</span>docker-entrypoint.sh npm ... Up 127.0.0.1:<span class="nu0">3008</span>-<span class="sy0">&gt;</span><span class="nu0">3008</span><span class="sy0">/</span>tcp</pre></div></div> <p>At that point, you can visit <code>https://[Domain]</code> and instead of a 502 error, you should see the BigBlueButton 'Greenlight' front page.</p> <p>Now the final step...</p> <h2><a id="user-content-create-an-admin-user" href="#create-an-admin-user" name="create-an-admin-user" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Create an admin user:</h2> <p>To create an admin user, you run this:</p> <p><code>sudo docker-compose exec greenlight bundle exec rake admin:create</code></p> <p>It will give you a login email address and a randomly generated password. Use these to log in. In the profile (top right Admin menu dropdown), you can alter the admin user's email to a real email (perhaps [Admin email]?), and change the password if you like.</p> <p>Then you can go back to the Admin menu and select "Organisation" which should put you on the Manage Users page. You then invite yourself to join as a user by sending yourself an email (which, if your SMTP settings are correct, will work). If you don't receive it in a minute or two, check your spam folder.</p> <p>Either log out of Greenlight before clicking the link in the email or open the link (by copying and pasting it) in a different browser where you're not logged in to this Greenlight instance, and create a user account for yourself.</p> <p>Then log back into Greenlight as the Admin user (if you've previously logged out) and refresh the "Manage Users" page. You should find your newly created user. Select 'Edit' from the vertical 3 dotted menu. On the "Update your Account Info" page, set the User Role for your user to 'Admin" and click the "Update" button. You should now be able to log out as Admin, and back in as your own user, but with administrative privileges.</p> <h2><a id="user-content-run-your-first-conference" href="#run-your-first-conference" name="run-your-first-conference" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Run your first conference</h2> <p>Now it's time to try running a video conference. Click on the "Home" link (top right menu) and you should see that you have a default room called "Home Room" with a URL specified in the form [Domain]/b/[first 3 letters of your name]-[3 random hex digits]-[3 more random hex digits]. For me, it might look something like this: https://[Domain]/b/dav-a5b-73z</p> <p>Click "Start" to initiate a conference in that Home Room.</p> <p>In the conference room, you should select 'Microphone' so you can test speaking and listening, and also test your video camera (assming you have access to both on the computing device you're using to access BBB - note, a modern cellphone normally offers all of these.</p> <p><em>We always encourage people participating in video conferences (regardless of the technology) to employ headphones to separate the audio from the conference form their input. It greatly improves the audio quality for all involved!</em>.</p> <p>If you select 'Microphone', there will be a brief delay whil your browser negotiates with the COTURN server in your BBB stack to link your data and audio channels. Once it's done that, it will give you an "echo test" window (see included screen shots). You should speak into your microphone when you see that screen and check whether you can hear yourself. This will confirm that your audio settings are right (or not) and will help the BBB system adjust its echo cancellation algorithms.</p> <p>You can then also clidk the 'camera' icon (bottom middle of the page) to activate your webcam if you have one (or choose one if you have multiple cameras) as well as the video quality.</p> <p>You can also try to 'Start recording' if you want to test your ability to record a session. Note that there is a delay after the end of a session in which you've recorded before the recording is displayed on the 'room' page in Greenlight. It might take minutes or even hourse to generate depending on the power of your server and the length of the session.</p> <p>To see other administrative functionality including moderation and breakout rooms, click on the "gear" icon next to the "Users" heading in the left hand column. You can also experiment with the "Public Chat" and the "Shared Notes". Both can be saved at any time (via the top right 3 dot menu in that section) and will be included in any recordings you make.</p> <p><em>Note</em> the contents of Public Chat and Shared Notes will be wiped at the end of a session unless you explicitly save them or record the session (at least briefly at the end).</p> <p>You can provide that "room address" to anyone and they can join your room (via an modern browser) when you have a session running. Alternatively, you can click on the 3 dotted menu associated with your room below the 'Search for room" form, and change the default properties of your room, including configuring it to let anyone who knows the room's address start a session. You can also create additional rooms (as an Admin user, you can set the limits on many of these properties via Organisation in the top right menu under your user name.</p> <p>Have fun with your new, world class, cost-effective, large-scale BigBlueButton video conferencing application!</p> <h2><a id="user-content-next-steps" href="#next-steps" name="next-steps" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Next Steps</h2> <p>In a future tutorial, we'll provide information on how to troubleshoot BBB issues, how to upgrade it as new versions are made available by developers, and how to ensure that your recordings, user database, and configuration are backed up incrementally, encrypted, in remote storage.</p></div> </div> </div> <section class="field field-node--field-blog-comments field-name-field-blog-comments field-type-comment field-label-above comment-wrapper"> <a name="comments"></a> <h2 class="comment-field__title">Blog comments</h2> <article data-comment-user-id="0" id="comment-826" about="/comment/826" typeof="schema:Comment" class="comment js-comment by-anonymous has-title clearfix"> <div class="comment__container"> <h3 property="schema:name" datatype="" class="comment__title"> <a href="/comment/826#comment-826" class="permalink" rel="bookmark" hreflang="en">It says in the nginx-config…</a> <span class="comment__new marker marker--success hidden" data-comment-timestamp="1641281859"></span> </h3> <div class="comment__meta"> <div class="comment__submitted"> <span class="comment__author"><span rel="schema:author"><span lang="" typeof="schema:Person" property="schema:name" datatype="">LPup (not verified)</span></span> </span> <span class="comment__pubdate">Tue 04/01/2022 - 20:32 <span property="schema:dateCreated" content="2022-01-04T07:32:09+00:00" class="rdf-meta hidden"></span> </span> </div> </div> <div class="comment__content"> <div property="schema:text" class="clearfix text-formatted field field-comment--comment-body field-name-comment-body field-type-text-long field-label-hidden"> <div class="field__items"> <div property="schema:text" class="field__item"><p>It says in the nginx-config to change the port for the proxy and not forget to change it in the .env file. Where do I need to put what into the .env file for using a different port than 8080?</p> </div> </div> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=826&amp;1=default&amp;2=en&amp;3=" token="ab5iIZGMF89MSnCPgbrcuI7yHqfoKEyjDb9VtJ299fg"></drupal-render-placeholder> </div> </div> </article> <div class="indented"><article data-comment-user-id="1" id="comment-827" about="/comment/827" typeof="schema:Comment" class="comment js-comment by-node-author has-title clearfix"> <div class="comment__container"> <h3 property="schema:name" datatype="" class="comment__title"> <a href="/comment/827#comment-827" class="permalink" rel="bookmark" hreflang="en">Hmm - thanks for pointing…</a> <span class="comment__new marker marker--success hidden" data-comment-timestamp="1641282307"></span> </h3> <div class="comment__meta"> <div class="comment__submitted"> <span class="comment__author"><span rel="schema:author"><a title="View user profile." href="/user/1" lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="" class="username">dave</a></span> </span> <span class="comment__pubdate">Tue 04/01/2022 - 20:44 <span property="schema:dateCreated" content="2022-01-04T07:44:02+00:00" class="rdf-meta hidden"></span> </span> </div> </div> <div class="comment__content"> <p class="comment__parent visually-hidden">In reply to <a href="/comment/826#comment-826" class="permalink" rel="bookmark" hreflang="en">It says in the nginx-config…</a> by <span lang="" typeof="schema:Person" property="schema:name" datatype="">LPup (not verified)</span></p> <div property="schema:text" class="clearfix text-formatted field field-comment--comment-body field-name-comment-body field-type-text-long field-label-hidden"> <div class="field__items"> <div property="schema:text" class="field__item"><p>Hmm - thanks for pointing that out. Yes, I think I made an error with that instruction (I've updated the Nginx config). If you implement BBB today via this set of instructions, you'll be installing BBB 2.4 - port 8080 was used in 2.3 and earlier versions. For 2.4, use port 48087. To be honest, I'm not sure of the right way to alter it if you have to due to other services running on the same system. Writing it manually in the docker-compose.yml file will be overwritten the next time you do an update... It might be necessary to change the port specified in the relevant container's Dockerfile under <code>mods</code>...</p></div> </div> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=827&amp;1=default&amp;2=en&amp;3=" token="IQwvVUQ-NlVwsrO5mMYIbQJJDVotfViSFCA7rOx4_Aw"></drupal-render-placeholder> </div> </div> </article> </div> <div class="comment-form-wrapper"> <h2 class="comment-form__title">Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=46&amp;2=field_blog_comments&amp;3=comment" token="bVuNCPDsukUifzhtwLxvL6UanvQCaG2zyJeoiuzQckE"></drupal-render-placeholder> </div> </section> Wed, 10 Nov 2021 02:42:41 +0000 dave 46 at http://tech.oeru.org http://tech.oeru.org/installing-bigbluebutton-oeru-docker-server#comments Installing MariaDB on Ubuntu 20.04 http://tech.oeru.org/installing-mariadb-ubuntu-2004 <span class="field field--name-title field--type-string field--label-hidden">Installing MariaDB on Ubuntu 20.04 </span> <div class="field field-node--field-blog-tags field-name-field-blog-tags field-type-entity-reference field-label-above"> <h3 class="field__label">Blog tags</h3> <div class="field__items"> <div class="field__item field__item--mysql"> <span class="field__item-wrapper"><a href="/taxonomy/term/83" hreflang="en">mysql</a></span> </div> <div class="field__item field__item--mariadb"> <span class="field__item-wrapper"><a href="/taxonomy/term/48" hreflang="en">mariadb</a></span> </div> </div> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><a title="View user profile." href="/user/1" lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="" class="username">dave</a></span> <span class="field field--name-created field--type-created field--label-hidden">Thu 30/09/2021 - 11:56</span> <div class="clearfix text-formatted field field-node--body field-name-body field-type-text-with-summary field-label-hidden"> <div class="field__items"> <div class="field__item"><p>At the OER Foundation, we have <a href="/node/39">the convention</a> of using Docker to deploy most components of the <a href="/node/34">web services we offer</a>. One notable exception to that is the way we deploy our most widely used database. Rather than deploying a separate MySQL/MariaDB container for every service on a given host, we instead deploy a single instance running on the host. We do this for a couple reasons:</p> <ol><li>MySQL/MariaDB are mature and are generally backwards compatible, and (importantly),</li> <li>it's easier to back up a single instance using <code>automysqlbackup</code> or our <a href="https://git.nzoss.nz/lightweight/dbbackup">hourly backup script</a> for some hosts.</li> </ol><p>So why am I constantly talking about 'MySQL/MariaDB'? <a href="https://en.wikipedia.org/wiki/MariaDB">MariaDB</a> is effectively a drop-in alternative to <a href="https://en.wikipedia.org/wiki/MySQL">MySQL</a> and we prefer it because it's not controlled by Oracle and has a more active developer community. On Ubuntu, MariaDB pretends to be MySQL for compatibility purposes, so don't be weirded out by the interchangeable names below.</p> <p>You'll replace the similarly [named] variables in the configuration files and command lines below. These are the values you need to find or create.</p> <ul><li>MariaDB/MySQL database details <ul><li> <strong>[database root password]</strong> - the administrative user (root) password for this server - use a <a href="/node/43">random password</a> </li> <li> <strong>[your database password]</strong> - if you, optionally, want to set up an admin user for yourself on this server. Paired with <strong>[your username]</strong> on the server.</li> </ul></li> </ul><p>Install the latest versions of the server and client like this.</p> <p><code>sudo apt-get install mariadb-server mariadb-client</code></p> <p>You need to set a root (admin) user password - you might want to create a <code>/root/.my.cnf</code> file containing the following (replacing [mysql root password]) to let you access MariaDB without a password from the commandline:</p> <p><code>sudo nano /root/.my.cnf </code></p> <p>and put the following info into it</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="br0">[</span>client<span class="br0">]</span> <span class="re2">user</span>=root <span class="re2">password</span>=<span class="br0">[</span>database root password<span class="br0">]</span></pre></div></div> <p>You should now be able to type <code>mysql</code> at the command prompt (note, the name mysql is used for backward compatibility with many implementations where MariaDB is being used to replace MySQL).</p> <p><strong>Optional MySQL non-root user</strong></p> <p>If you're happy to use your root user to access MySQL, e.g. <code>sudo mysql</code> (which uses 'sudo' to access it via the root user), then you can safely ignore the rest of this section.</p> <p>If you're accessing the server via a non-root user (which is a good idea, and is the reason we use <code>sudo</code> in this howto), you might want to create a similar <code>~/.my.cnf</code> file in your directory , with your username in place of <code>root</code>, and a <em>different password</em>. That will allow you to work with the MariaDB client without needing to enter the root credentials each time.</p> <p>To make it work, you'll need to run the following as the MySQL admin user - this should be the default on this new install - remember to replace your [tokens]!). This creates <em>two</em> users with the same credentials that will allow you to log in either from the same server (i.e. 'localhost') or from any of your Docker containers (often useful for debugging!), namely the wildcard '%'. <strong>Remember</strong>: if you change the user's details, you'll have to do it for both the localhost and '%' users.</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">CREATE USER <span class="st0">"[your username]"</span><span class="sy0">@</span><span class="st0">"localhost"</span> IDENTIFIED BY <span class="st0">"[your database password]"</span>; CREATE USER <span class="st0">"[your username]"</span><span class="sy0">@</span><span class="st0">"%"</span> IDENTIFIED BY <span class="st0">"[your database password]"</span>; GRANT ALL ON <span class="sy0">*</span>.<span class="sy0">*</span> to <span class="st0">"[your username]"</span><span class="sy0">@</span><span class="st0">"localhost"</span> WITH GRANT OPTION; GRANT ALL ON <span class="sy0">*</span>.<span class="sy0">*</span> to <span class="st0">"[your username]"</span><span class="sy0">@</span><span class="st0">"%"</span> WITH GRANT OPTION; FLUSH PRIVILEGES;</pre></div></div> <p>Don't be alarmed if MySQL tells you "0 rows affected" when you create a user - unless you see a specific 'error', it's still creating them.</p> <p><strong>End optional MySQL non-root user section</strong></p> <p>Tweak the configuration so that it's listening on the right internal network device.</p> <p><code>sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf</code></p> <p>and copy the bind-address line and adjust so it looks like this - we want MariaDB to be listening on all interfaces, not just localhost (<code>127.0.0.1</code>)...</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co0"># Instead of skip-networking the default is now to listen only on</span> <span class="co0"># localhost which is more compatible and is not less secure.</span> <span class="co0">#bind-address = 127.0.0.1</span> bind-address = 0.0.0.0</pre></div></div> <p>Then restart MariaDB:</p> <p><code>sudo service mysql restart</code></p> <p>It should now be listening on MySQL/MariaDB's default port <code>3306</code> on all interfaces, i.e. <code>0.0.0.0</code>. For safety's sake, external access to the MariaDB server is blocked by your UFW firewall.</p> <p>Now you should be able to log into the MariaDB client on the host (if you've created a <code>.my.cnf</code> file in your home directory as describe above, you won't need to enter your username and password):</p> <p><code>mysql -u root -p</code></p> <p>You should now be able to create new databases as in our other tutorials... And in case you're stuck, to exit the SQL client, just type <code>\q</code> and ENTER.</p> <h4><a id="user-content-regular-automatic-database-backups" href="#regular-automatic-database-backups" name="regular-automatic-database-backups" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Regular automatic database backups</h4> <p>Finally, it's a good idea (but optional - if you're in a hurry, you can do this later) to make sure that your server is maintaining backups of your database - in this case, we'll use the <code>automysqlbackup</code> script to automatically maintain a set of dated daily database backups. It's easy to install, and the database backups will be in <code>/var/lib/automysqlbackup</code> in dated folders and files. <strong>If you haven't set up Postfix in the previous step, just beware you will be asked to set it up when installing automysqlbackup</strong>.</p> <p><code>sudo apt-get install automysqlbackup</code></p> <p>That's all there is to it. It should run automatically every night and store a set of historical SQL snapshots that may well save your bacon sometime down the track!</p></div> </div> </div> <section class="field field-node--field-blog-comments field-name-field-blog-comments field-type-comment field-label-above comment-wrapper"> <a name="comments"></a> <div class="comment-form-wrapper"> <h2 class="comment-form__title">Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=44&amp;2=field_blog_comments&amp;3=comment" token="n5lqti7xqRsZSZoiUepP0EFyiX3UevLR_3QNKVa7GeU"></drupal-render-placeholder> </div> </section> Wed, 29 Sep 2021 22:56:01 +0000 dave 44 at http://tech.oeru.org http://tech.oeru.org/installing-mariadb-ubuntu-2004#comments