Drupal blog posts http://tech.oeru.org/ en Creating strong random passwords http://tech.oeru.org/creating-strong-random-passwords <span class="field field--name-title field--type-string field--label-hidden">Creating strong random passwords</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--random-passwords"> <span class="field__item-wrapper"><a href="/taxonomy/term/81" hreflang="en">random passwords</a></span> </div> <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--password-keeper"> <span class="field__item-wrapper"><a href="/taxonomy/term/61" hreflang="en">password keeper</a></span> </div> </div> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> <span class="field field--name-created field--type-created field--label-hidden">Mon 13/09/2021 - 12:26</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>Throughout our Free and Open Source Software tutorials, we need to specify passwords for things. Creating random passwords is surprisingly hard, but we've found a method that's very serviceable and makes it easy to do as we all should: ensure every separate identity or service has a <a href="https://xkcd.com/936/"><strong>strong</strong></a> password that is unique to that identity and application (i.e. never use the same password in more than one place). We also <em>strongly encourage</em> you all to track your passwords using a <a href="/node/25">password manager</a>!</p> <p>To generate decent random passwords, we use <code>pwgen</code>, which you can easily use on your server (log in - via SSH or using your hosting provider's console to your server - as root or, even better as a non-root user with <code>sudo</code> privileges) by typing at the terminal command prompt:</p> <p><code>sudo apt-get install pwgen</code></p> <p>I don't tend to use special characters in passwords stored in configuration files because they can lead to syntax parsing issues when put into configuration files. Instead, I just make them fairly long. To create a random password, I use this</p> <p><code>pwgen -s 19 1</code></p> <p>which returns a single 19 character-long password with a mixture of letters (lower and uppercase) and digits. A few examples: <code>HxF0GAyS1jw63Dy3T5K avZ5qj4xt0tTS0ONyLo 43IJZbZxLrKJSegZhyR</code>.</p> <p><em>Note: your passwords are likely to appear, in clear text, in your terminal window after you've created them, which is a temporary security threat.</em> Once you've got them entered where they need to be, I recommend running <code>CTRL-L</code> in your terminal window which will clear the visible text from past commands and give you a default command prompt (your terminal session will still remember past commands if you click <code>up arrow</code>).</p> <p>You can create all the passwords you need to follow one of our tutorials up front and then copy and paste them somewhere useful, like into a text editor on your desktop, from which you can easily copy and paste them and track where they belong. The ones you use to log into remote services yourself should be stored in a password manager (as recommended above!).</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=43&amp;2=field_blog_comments&amp;3=comment" token="BUNjQwNtY7O4jXHa9zY4bExoCTes3MS2LjSioD2BKtI"></drupal-render-placeholder> </div> </section> Mon, 13 Sep 2021 00:26:04 +0000 dave 43 at http://tech.oeru.org OERF FOSS Tech Hosting Conventions http://tech.oeru.org/oerf-foss-tech-hosting-conventions <span class="field field--name-title field--type-string field--label-hidden">OERF FOSS Tech Hosting Conventions</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--overview"> <span class="field__item-wrapper"><a href="/taxonomy/term/76" hreflang="en">overview</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--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--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--ssh"> <span class="field__item-wrapper"><a href="/taxonomy/term/77" hreflang="en">ssh</a></span> </div> <div class="field__item field__item--restic"> <span class="field__item-wrapper"><a href="/taxonomy/term/78" hreflang="en">restic</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 class="field__item field__item--git"> <span class="field__item-wrapper"><a href="/taxonomy/term/79" hreflang="en">git</a></span> </div> <div class="field__item field__item--gitlab"> <span class="field__item-wrapper"><a href="/taxonomy/term/80" hreflang="en">gitlab</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"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> <span class="field field--name-created field--type-created field--label-hidden">Mon 30/08/2021 - 11:44</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-08/SSH_Session_Screenshot_about.oerfoundation.org-20210830.png?itok=yu7PbTtx" title="A snapshot of an SSH session on about.oerfoundation.org, located in San Francisco, CA, USA, and its performance stats as viewed from Christchurch, New Zealand. " data-colorbox-gallery="gallery-field_image-ua8-LkAmUsQ" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;A snapshot of an SSH session on about.oerfoundation.org, located in San Francisco, CA, USA, and its performance stats as viewed from Christchurch, New Zealand. &quot;}"><img src="/sites/default/files/styles/medium/public/2021-08/SSH_Session_Screenshot_about.oerfoundation.org-20210830.png?itok=6cfu9o9O" width="220" height="105" alt="A snapshot of an SSH session on about.oerfoundation.org, located in San Francisco, CA, USA, and its performance stats as viewed from Christchurch, New Zealand. " 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-08/Editing_tech.oeru_.org_on_about.oerfoundation.org-20210830.png?itok=-EfYK9sU" title="Editing the docker-compose.yml file for this site using the Vim editor on about.oerfoundation.org via SSH." data-colorbox-gallery="gallery-field_image-ua8-LkAmUsQ" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;Editing the docker-compose.yml file for this site using the Vim editor on about.oerfoundation.org via SSH.&quot;}"><img src="/sites/default/files/styles/medium/public/2021-08/Editing_tech.oeru_.org_on_about.oerfoundation.org-20210830.png?itok=npeVOBsh" width="220" height="176" alt="Editing the docker-compose.yml file for this site using the Vim editor on about.oerfoundation.org via SSH." 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-08/Screenshot%202021-08-30%20at%2016-21-09%20OERu.png?itok=TwAuGCsB" title="A snapshot of OERu FOSS software projects in our Gitlab instance. All are visible to anyone and we invite contributions." data-colorbox-gallery="gallery-field_image-ua8-LkAmUsQ" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;A snapshot of OERu FOSS software projects in our Gitlab instance. All are visible to anyone and we invite contributions.&quot;}"><img src="/sites/default/files/styles/medium/public/2021-08/Screenshot%202021-08-30%20at%2016-21-09%20OERu.png?itok=3FmBKhhs" width="202" height="220" alt="A snapshot of OERu FOSS software projects in our Gitlab instance. All are visible to anyone and we invite contributions." 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>At the <a href="https://oerfoundation.org">OER Foundation</a> we host <a href="/node/34">an array</a> of Free and Open Source Software (FOSS) services. To keep things manageable, we have established a set of conventions to which we adhere, and I'm describing them here in case it's helpful to others who can either learn from, or improve on, our experience.</p> <p>To begin with, we use <a href="https://ubuntu.com">Ubuntu Linux</a> for all of our hosts. We deploy the current Long Term Support (LTS) version of Ubuntu (at the time of this writing, it's 20.04) on commodity Linux hosting services, like Digital Ocean and Hetzner.</p> <h2><a id="user-content-host-access" href="#host-access" name="host-access" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Host access</h2> <p>We access all our hosts on the command line, via secure, end-to-end encrypted connections, using <a href="https://openssh.com">OpenSSH</a>. We don't use a server management tool (neither an open source one, like ISPConfig or Webmin, nor proprietary ones like Plex or CPanel). They would just slow us down. We prefer the efficiency and power afforded us by SSH. At any given time, I'm logged into a half a dozen remote systems, issuing updates, deploying code, diagnosing issues, checking resource usage, shoring up disk space, or tweaking Docker configurations.</p> <h2><a id="user-content-software-on-the-host" href="#software-on-the-host" name="software-on-the-host" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Software on the host</h2> <p>On the host, we run the following:</p> <ol><li>We run <a href="https://etckeeper.branchable.com/">etckeeper</a> on each server to track changes in configuration. In general, we have these changes pushed to a central server to provide a reference in the event of a disaster (e.g. the server is compromised and needs to be 'nuked from orbit'...).</li> <li>The <a href="https://nginx.org">Nginx</a> web server - it's our reverse proxy of choice. All of our <a href="/node/11">Let's Encrypt</a> certificates terminate at the host server, greatly simplifying the deployment of the individual services running on it.</li> <li>The <a href="https://postfix.org">Postfix</a> SMTP server - it's our outgoing mailserver of choice, and we <a href="/node/28">set it up as a smarthost</a>, so that the server can send status and administrative messages to us via our <a href="https://mailcow.email">MailCow</a> email infrastructure using authenticating secure SMTP.</li> <li>The <a href="https://docs.docker.com/get-started/overview/">Docker</a> containerisation framework using the <a href="https://docs.docker.com/compose/">Docker Compose</a> system to coordinate collections of containers that work together to provide specific services. Docker allows us to run a bunch of effectively independent Linux systems, each with potentially different sets of complex software dependencies, efficiently and reliably on a single host.</li> <li>For each service, with an individual domain or subdomain name, we get a <a href="https://letsencrypt.org">Let's Encrypt</a> <a href="https://en.wikipedia.org/wiki/Transport_Layer_Security#SSL_1.0,_2.0,_and_3.0">SSL certificate</a> using the letsencrypt scripting toolchain. It is free of cost, and <a href="/node/11">easy to build into our workflow</a>, so it's a 'no-brainer' as far as we're concerned, ensuring all of our sites protect the data of their users with full encrypted links!</li> <li>For applications we deploy that depend on the MySQL database, we run a single instance of the fully MySQL-compatible <a href="https://mariadb.org/">MariaDB</a> on our host, and the Docker containers connect to it via the internal network. We do it this way to easy the process of backing up the various MariaDB databases, which would be much more fiddly if we were running a bunch of individual MariaDB or MySQL instances in Docker containers. We use MariaDB because we prefer its development model, design decisions, and the fact that it's not being run by the Oracle Corporation, which owns the MySQL project.</li> <li>We use <a href="https://restic.net/">Restic</a> to perform remote encrypted incremental file backups for the filesystems on each server. We send them to a development server we have which has oodles of disk space (<a href="https://en.wikipedia.org/wiki/Btrfs">BTRFS</a> <a href="https://en.wikipedia.org/wiki/Standard_RAID_levels#RAID_1">RAID1</a> if you're curious) for safekeeping.</li> </ol><p>When each new Ubuntu LTS release comes out, we tend to create an install image for our commodity hosting provider (most recently, Digital Ocean), which has all these things pre-installed.</p> <h2><a id="user-content-docker-deployments" href="#docker-deployments" name="docker-deployments" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Docker deployments</h2> <p>For the various services we deploy using Docker - more specifically, using the very handy Docker Compose scripting toolchain, we have a bunch of conventional practices.</p> <ol><li>We put all our Docker Composer recipes in a common directory: <code>/home/docker</code> - each service goes in a directory using the domain name of that service. For example, this Tech blog, tech.oeru.org is in the directory <code>/home/docker/tech.oeru.org</code> on our server about.oerfoundation.org. Each service directory has a <code>docker-compose.yml</code> file which defines the collection of Docker containers making up each service. It also specifies the local directors in which data we want to make persistent, even surviving the removal of relevant Docker containers. In the case of this site, the containers include an Nginx webserver container which submits requests delivered to it by our hosts's Nginx reverse proxy to a PHP scripting engine container which, in turn, consults the Drupal 8 source code making up the site, and the MariaDB on the host which stores the data. There is also a PHP container which automatically runs the behind-the-scenes automated 'cron' tasks that every Drupal site requires.</li> <li>We store that per-service persistent data in a similarly named directory under <code>/home/data</code>, so this site's data is stored in <code>/home/data/tech.oeru.org</code>. That data includes, in the case of this site, an Nginx directory, with a generic Drupal website configuration, and a directory to contain all the Drupal 8 core source code and that for theme, module, and library dependencies. In a few cases, where we're using tools with specialised Docker deployment practices, like Mailcow, BigBlueButton, and Discourse, the persistent data for the site is stored under the <code>/home/docker</code> directory to avoid unnecessarily complicating our use of those tools. So our conventions are just that - not hard and fast, if there's a good reason to compromise them.</li> </ol><h2><a id="user-content-keeping-track" href="#keeping-track" name="keeping-track" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Keeping track</h2> <p>For all of our service configurations, and, in particular our Docker deployments, we use <a href="https://git-scm.com">Git</a> to provide source code versioning and management. We also use it to deploy code that we have developed ourselves. Where possible, we use (and contribute back to!) upstream git repositories supplied by the communities surrounding many of the FOSS services we offer. We see that as doing our part to be good, contributing FOSS community members.</p> <p>All of our git repositories are held on our own, self-hosted <a href="https://about.gitlab.com/community/">Gitlab</a> instance: <a href="https://git.oeru.org/explore/projects">https://git.oeru.org</a> - anyone is welcome to peruse the repositories, and we invite anyone interested in contributing to <a href="/contact">request a an account</a> (give us an idea of what you're interested in doing and how you'd like to participate!).</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=39&amp;2=field_blog_comments&amp;3=comment" token="mc3_yQFNlAQZsoRapw9jke6XUslZiwMrWV52Y56Nyas"></drupal-render-placeholder> </div> </section> Sun, 29 Aug 2021 23:44:43 +0000 dave 39 at http://tech.oeru.org Building a Course Site with WordPress on Ubuntu 20.04 using Docker Compose http://tech.oeru.org/building-course-site-wordpress-ubuntu-2004-using-docker-compose <span class="field field--name-title field--type-string field--label-hidden">Building a Course Site with WordPress on Ubuntu 20.04 using 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--wordpress"> <span class="field__item-wrapper"><a href="/taxonomy/term/35" hreflang="en">wordpress</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--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--multisite"> <span class="field__item-wrapper"><a href="/taxonomy/term/36" hreflang="en">multisite</a></span> </div> <div class="field__item field__item--redis"> <span class="field__item-wrapper"><a href="/taxonomy/term/21" hreflang="en">redis</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--php"> <span class="field__item-wrapper"><a href="/taxonomy/term/40" hreflang="en">php</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"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> <span class="field field--name-created field--type-created field--label-hidden">Tue 24/08/2021 - 16: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/2021-09/Screenshot%202021-09-03%20at%2015-48-16%20https%20course%20oeru%20org_register-enrol_anon.png?itok=yna1rg6z" title="An anonymous view of an OERu course with Register Enrol link shown (top right)." data-colorbox-gallery="gallery-field_image-ua8-LkAmUsQ" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;An anonymous view of an OERu course with Register Enrol link shown (top right).&quot;}"><img src="/sites/default/files/styles/medium/public/2021-09/Screenshot%202021-09-03%20at%2015-48-16%20https%20course%20oeru%20org_register-enrol_anon.png?itok=CY0E87Dh" width="220" height="156" alt="An anonymous view of an OERu course with Register Enrol link shown (top right)." 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-09/Screenshot%202021-09-03%20at%2015-49-57%20https%20course%20oeru%20org_register-enrol_loggedin.png?itok=DyccmnI7" title="An OERu course with a logged in and registered user, see Register Enrol link (top right)" data-colorbox-gallery="gallery-field_image-ua8-LkAmUsQ" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;An OERu course with a logged in and registered user, see Register Enrol link (top right)&quot;}"><img src="/sites/default/files/styles/medium/public/2021-09/Screenshot%202021-09-03%20at%2015-49-57%20https%20course%20oeru%20org_register-enrol_loggedin.png?itok=31IN-ffG" width="182" height="220" alt="An OERu course with a logged in and registered user, see Register Enrol link (top right)" 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-09/Screenshot%202021-09-06%20at%2014-54-40%20https%20course%20oeru%20org_lida101_wenotesfeed.png?itok=54vVTHcL" title="An OERu course WEnotes feed aggregation page. Note the sources from which feed messages are drawn - our WEnotes aggregator checks many sources including personal blogs that learners have registered." data-colorbox-gallery="gallery-field_image-ua8-LkAmUsQ" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;An OERu course WEnotes feed aggregation page. Note the sources from which feed messages are drawn - our WEnotes aggregator checks many sources including personal blogs that learners have registered.&quot;}"><img src="/sites/default/files/styles/medium/public/2021-09/Screenshot%202021-09-06%20at%2014-54-40%20https%20course%20oeru%20org_lida101_wenotesfeed.png?itok=07Lv9rfB" width="220" height="156" alt="An OERu course WEnotes feed aggregation page. Note the sources from which feed messages are drawn - our WEnotes aggregator checks many sources including personal blogs that learners have registered." 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>The <a href="https://oeru.org">OERu</a> offers accredited tertiary (University level) courses to learners anywhere on the Internet, whether they are using desktop computers or mobile devices.</p> <p>Instead of using a Learning Management System to frame the courses, the OERu Courses are hosted in a <a href="https://wordpress.org">WordPress</a> <a href="https://wordpress.org/support/article/create-a-network/">MultiSite</a> (originally called a "Network" of sites within a single WordPress installation) implementation, with each course a 'subsite' represented by sub-directory below the main site. For example, the first <a href="https://oeru.org/learning-in-a-digital-age/">Learning in a Digital Age</a> micro-course, "<a href="https://oeru.org/oeru-partners/otago-polytechnic/digital-literacies-for-online-learning/">Digital literacies for online learning</a>" with the course code <em>LiDA 101</em>, can be found at <a href="https://course.oeru.org/lida101">https://course.oeru.org/lida101</a>. This post explains how you can replicate our fully Free and Open Source Software large-scale Open Educational Resource (OER) course delivery platform at negligible cost.</p> <ul class="table-of-contents"><li> <p><a href="#step-one---a-suitable-host">Step one - a suitable host</a></p> <ul><li> <p><a href="#get-your-domain-lined-up">Get your Domain lined up</a></p> </li> <li> <p><a href="#log-into-your-server">Log into your server</a></p> </li> <li> <p><a href="#sort-out-your-details">Sort out your details</a></p> </li> </ul></li> <li> <p><a href="#step-two---prepare-the-host">Step two - prepare the host</a></p> <ul><li> <p><a href="#set-up-docker-and-docker-compose">Set up Docker and Docker-Compose</a></p> </li> </ul></li> <li> <p><a href="#step-three---configure-your-domain">Step three - configure your domain</a></p> <ul><li> <p><a href="#set-up-nginx-reverse-proxy">Set up Nginx reverse proxy</a></p> </li> <li> <p><a href="#set-up-lets-encrypt-for-the-domain">Set up Let's Encrypt for the domain</a></p> </li> </ul></li> <li> <p><a href="#step-four---get-the-code">Step four - get the code</a></p> <ul><li> <p><a href="#wordpress-source-code">WordPress source code</a></p> </li> <li> <p><a href="#oeru-theme">OERu Theme</a></p> </li> <li> <p><a href="#customise-wp-configphp">Customise wp-config.php</a></p> </li> </ul></li> <li> <p><a href="#step-five---set-up-docker-compose-for-the-site">Step five - set up Docker Compose for the site</a></p> <ul><li> <p><a href="#launch-containers">Launch containers</a></p> </li> </ul></li> <li> <p><a href="#step-six---set-up-your-site">Step Six - set up your site</a></p> <ul><li> <p><a href="#install-wordpress">Install WordPress</a></p> </li> <li> <p><a href="#enable-the-oeru-course-theme">Enable the OERu Course theme</a></p> </li> <li> <p><a href="#create-an-admin-user">Create an admin user</a></p> </li> <li> <p><a href="#enable-multisite">Enable multisite</a></p> </li> <li> <p><a href="#enable-the-relevant-plugins">Enable the relevant plugins</a></p> </li> <li> <p><a href="#third-party-plugins">Third Party plugins</a></p> </li> <li> <p><a href="#oeru-plugins">OERu plugins</a></p> </li> </ul></li> <li> <p><a href="#step-seven---celebrate">Step Seven - celebrate!</a></p> </li> <li> <p><a href="#snapshotting-your-first-oer-course">Snapshotting your first OER course!</a></p> </li> <li> <p><a href="#enabling-oerus-register-enrol-functionality">Enabling OERu's Register Enrol functionality</a></p> </li> <li> <p><a href="#data-backups">Data Backups</a></p> </li> <li> <p><a href="#staying-up-to-date">Staying up-to-date</a></p> </li> <li> <p><a href="#acknowledgements">Acknowledgements</a></p> </li> </ul><p>To host our WordPress Multisite, we use a quartet of <a href="https://docs.docker.com/get-started/overview/">Docker</a> containers - a <a href="https://en.wikipedia.org/wiki/Redis">Redis</a> caching server, an <a href="https://en.wikipedia.org/wiki/Nginx">Nginx</a> webserver, and two servers running the <a href="https://en.wikipedia.org/wiki/PHP">PHP script interpreter</a> in <a href="https://www.php.net/manual/en/install.fpm.php">FPM</a> mode, with the first being the main workhorse responding to queries sent to it by the Nginx server... The other PHP container is responsible for running cron (scheduled) tasks in the background. It's all driven by <a href="https://docs.docker.com/compose/">Docker Compose</a> (which coordinates the individual Docker containers) on an Ubuntu Linux host. The host also runs an Nginx instance to act as a '<a href="https://www.nginx.com/resources/glossary/reverse-proxy-server/">reverse proxy</a>', and the endpoint of our <a href="https://en.wikipedia.org/wiki/Transport_Layer_Security#SSL_1.0,_2.0,_and_3.0">SSL</a> (we use <a href="/node/11">Let's Encrypt</a> certificates). This is all consistent with our <a href="/node/39">FOSS Docker-based hosting conventions</a>.</p> <p>Although our process to get one of the these sites up and running is made up of simple steps, there are a bunch of them, so get comfortable and buckle yourself in and prepare for a fun ride! <em>Just beware - this is a pretty audacious tutorial describing an advanced production-ready system with lots of moving parts doing a serious amount of stuff behind the scenes, so this process is likely to take a few hours from start to finish and isn't for the faint of heart.</em></p> <h2><a id="user-content-step-one---a-suitable-host" href="#step-one---a-suitable-host" name="step-one---a-suitable-host" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Step one - a suitable host</h2> <p>The first step is to get yourself an entry-level virtual server or compute instance somewhere.</p> <p>I generally use <a href="https://digitalocean.com">DigitalOcean</a> (I have no affiliation with the company), but there are many other commodity hosting services (check out <a href="https://vultr.com">Vultr</a> or <a href="https://linode.com">Linode</a>, for example) around the world which offer comparably (or better) spec'd servers for USD5.00/month, or USD60.00/year. For that you get a Gigabyte (GB) of RAM, a processor, and 40GB of SSD (Static Storage Device = faster) storage.</p> <p>A server (a "Droplet" in Digital Ocean parlance) with a GB of RAM and 20+ GB of disk space will be sufficient for this sort of service. If you expect it to have heavy traffic, or you might want to add more services, you might want to invest in a higher-spec server up-front (because, among other things, it'll offer you more disk space). Most of our servers are USD40/month instances (USD480/year) which buys 8GB of RAM, 4 virtual processors, and 160GB of disk space.</p> <p>I suggest you create an account for yourself on your chosen hosting provider (and I encourage you 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 20.04 (or the most recent 'Long Term Support' (LTS) version - the next will be 22.04, in April 2022) in the zone nearest to you.</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 - that should allow you to log in without needing a password!</p> <p>You'll need to note the server's IPv4 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 IPv6 address, which will be a set of 8 four hex character 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 can <a href="https://www.digitalocean.com/community/tutorials/how-to-use-ssh-to-connect-to-a-remote-server-in-ubuntu">log into it via SSH</a>.</p> <p>Once you get 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> <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>If you want to use your domain for other things besides your WordPress instance, I'd encourage you to use a subdomain, like (my usual choice) is "course.domainname", namely the subdomain "course" of "domainname".</p> <p>Once you have selected and registered your domain, you can set up (usually through a web interface provided by the registrar) an "A Record" which associates your website's name to the IPv4 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 your WordPress service. Nowadays, <em>if your Domain Name host offers it (some don't, meaning they're way behind the times),</em> it's also important to define an IPv6 record, which is called an "AAAA Record"... 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 interface requests... but in most cases that'll be set to a default of an hour automatically.</p> <h3><a id="user-content-log-into-your-server" href="#log-into-your-server" name="log-into-your-server" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Log into your server</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 Putty...</p> <p>This will log you into your server as the 'root' user. 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><br /><code>adduser $U ssh</code><br /><code>adduser $U sudoers</code></p> <p>You'll also want to a set a password for user [username]:</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> <code>ssh-keygen -t rsa -b 2048</code> <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> <h3><a id="user-content-sort-out-your-details" href="#sort-out-your-details" name="sort-out-your-details" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Sort out your details</h3> <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> <strong>[domain name]</strong> - 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: <code>course.oeru.org</code> </li> <li> <strong>[port]</strong> - this is an unused port, i.e. not used by any other service, that will be used by the Nginx reverse proxy to talk to your Docker Nginx webserver container. A conventional option would be 8080... If that's already in use, try 8081, etc. You can check what ports are in use with the command <code>sudo netstat -punta</code> - the ports are listed after the ':' in each case</li> <li>MariaDB/MySQL database details <ul><li> <strong>[database root password]</strong> - the administrative user (root) password for this server - see <a href="/node/43">our tutorial on creating strong random passwords</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> <li> <strong>[database name]</strong> - the name of the database for this specific WordPress site - example <code>wordpress</code> </li> <li> <strong>[database user]</strong> - a separate username for this WordPress database - example <code>wordpress</code> (but it can be different too)- <strong>note</strong>: you'll be creating <em>two</em> database users with this same username and password, but that's intended.</li> <li> <strong>[database password]</strong> a separate password for this WordPress database user</li> </ul></li> <li>Authenticating SMTP details - this is required so your WordPress site can send emails to users - crucial things like email address validation and password recovery emails... <ul><li> <strong>[smtp host]</strong> - the domain name or IPv4 or IPv6 address of an SMTP server</li> <li> <strong>[smtp reply-to-email address]</strong> - a monitored email to which people can send email related to this WordPress site, e.g. webmaster@[domain name]</li> <li> <strong>[smtp user]</strong> - the username (often an email address) used to authenticate against your SMTP server, provided by your email provider.</li> <li> <strong>[smtp password]</strong> - the accompanying password, provided by your email provider.</li> </ul></li> <li>Wordpress configuration values <ul><li> <strong>[redis password]</strong> - another random password, this time for the caching service we'll set up to make your WordPress site faster than billy-o.</li> <li> <strong>[wordpress keys and salts]</strong> - a series of random numbers to make your WordPress site far more secure than it would otherwise be - <em>these can be generated automatically using the approach described below!</em>.</li> </ul></li> <li>For those incorporating our WEnotes system, you'll need the following (these values are optional and can be ignored!) <ul><li> <strong>[couchdb host]</strong> - the domain name of the couchdb host - in the case of the OERu, our host is couch.oerfoundation.org. Yours might be different.</li> <li> <strong>[couchdb mention database]</strong> - the name of the designated 'mention' database on the couchdb host.</li> <li> <strong>[couchdb user]</strong> - a couchdb username, provided by whoever manages your couchdb host</li> <li> <strong>[couchdb password]</strong> - a couchdb password, also provided by whoever manages your couchdb host</li> </ul></li> <li>Docker.com login details (you'll need a username and password).</li> </ul><p><strong>Note</strong>: not all values in all files surrounded by [] need to be replaced! If they're not included in the list above, leave them as you find them!</p> <h2><a id="user-content-step-two---prepare-the-host" href="#step-two---prepare-the-host" name="step-two---prepare-the-host" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Step two - prepare the host</h2> <p>Preparing the host involves ensuring your firewall, UFW, is configured properly, installing the Nginx webserver to act as your host's reverse proxy, and installing MariaDB (MySQL compatible but better, for a variety of reasons including that it's not controlled by Oracle) and configuring it properly. Then we can create the MariaDB database specifically for this WordPress installation. We also suggest you install Postfix so your server can send out email to you, and finally, we'll ensure that your server knows how to launch Docker containers and manage them with Docker Compose.</p> <p>Before we do anything else, let's make sure your Ubuntu package repository is up-to-date.</p> <p><code>sudo apt-get update</code></p> <p>If you pause this build process for more than a few hours, it pays to run it again before you continue on.</p> <h4><a id="user-content-firewall-with-ufw" href="#firewall-with-ufw" name="firewall-with-ufw" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Firewall with UFW</h4> <p>No computer system is ever full secure - there're always exploits waiting to be found, so security is a process of maintaining vigilance. Part of that is reducing exposure - minimising your "attack surface". Use a firewall - <code>ufw</code> is installed on Ubuntu by default and is easy to set up and maintain. Make sure you've got exceptions for SSH (without them, you could lock yourself out of your machine! Doh!).</p> <p>Run the following commands to allow your Docker containers to talk to other services on your host.</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>Specifically for Docker's benefit, you need to tweak the default Forwarding rule (I use <code>vim</code> as my editor. An alternative, also installed by default on Ubuntu, <code>nano</code>, is probably easier to use for simple edits like this, so I'll use <code>nano</code> here):</p> <p><code>sudo nano /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').</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 nano /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>and finally restart the network stack and ufw on your server</p> <p><code>sudo systemctl restart systemd-networkd</code><br /><code>sudo service ufw restart</code></p> <h4><a id="user-content-installing-the-nginx-webserverreverse-proxy" href="#installing-the-nginx-webserverreverse-proxy" name="installing-the-nginx-webserverreverse-proxy" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Installing the Nginx webserver/reverse proxy</h4> <p>In the configuration I'm describing here, you'll need a webserver running on the server - it'll be acting as a reverse proxy for the Docker-based Nginx instance described below. I prefer the efficiency of Nginx and clarity of Nginx configurations over those of Apache and other open source web servers. Here's how you install it.</p> <p><code>sudo apt-get install nginx-full</code></p> <p>To allow <code>nginx</code> to be visible via ports 80 and 443, run</p> <p><code>sudo ufw allow "Nginx Full"</code></p> <p>To check that all worked, you can put <code>http://[domain name]</code> into your browser's address bar, and you should see a default "NGINX" page...</p> <p>Note: make sure your hosting service is not blocking these ports at some outer layer (depending on who's providing that hosting service you may have to set up port forwarding).</p> <h4><a id="user-content-installing-mariadb" href="#installing-mariadb" name="installing-mariadb" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Installing MariaDB</h4> <p>MariaDB is effectively a drop-in alternative to MySQL 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. 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 set up the database which will hold WordPress' data. 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>Enter your root password when prompted and then replace the following [database-related tokens] to create the database with the right language encoding, along with access to the right separate user:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">CREATE DATABASE <span class="br0">[</span>database name<span class="br0">]</span> CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER <span class="st0">"[database user]"</span><span class="sy0">@</span><span class="st0">"%"</span> IDENTIFIED BY <span class="st0">"[database password]"</span>; GRANT ALL ON <span class="br0">[</span>database name<span class="br0">]</span>.<span class="sy0">*</span> to <span class="st0">"[database user]"</span><span class="sy0">@</span><span class="st0">"%"</span>; FLUSH PRIVILEGES;</pre></div></div> <p>The last line ensures that MariaDB has updated its internal permissions to recognise your new user. To exit the SQL client, just type <code>\q</code> and ENTER.</p> <h4><a id="user-content-sending-emails" href="#sending-emails" name="sending-emails" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Sending emails</h4> <p>Because a server like this one is set up to perform lots of rather complex jobs to perform, it's vital that your server has the ability to send you emails to alert you of problems, like failed updates or backups. We encourage you to follow our instructions on <a href="/node/28">how to configure your server to use the <code>Postfix</code> SMTP server to send out email, using your Authenticating SMTP details</a>.</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> <h3><a id="user-content-set-up-docker-and-docker-compose" href="#set-up-docker-and-docker-compose" name="set-up-docker-and-docker-compose" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Set up Docker and Docker-Compose</h3> <p>First, you need to set up <a href="https://docs.docker.com/install/linux/docker-ce/ubuntu/">Docker support</a> on your server - use the 'repository method' for Ubuntu 20.04 and choose the 'x86_64 / amd64' tab!</p> <p>Also, if you're using a non-root user, follow the complete instructions including <em><a href="https://docs.docker.com/engine/install/linux-postinstall/">setting up Docker for your non-root user</a></em>.</p> <p>The way I implement this set of containers is to use <a href="https://docs.docker.com/compose/">Docker Compose</a>) which depends on the Python script interpreter (version 3+). I suggest using the latest installation instructions provided by the Docker community. Of the options provided, I use the 'alternative instructions', employing <a href="https://docs.docker.com/compose/install/#install-using-pip">the 'pip' approach</a>. This is what I usually do (to summarise the pip instructions):</p> <p>The firrst step is to Install Ubuntu's Python3 pip which is a bit outdated...</p> <p><code>sudo apt install python3-pip</code></p> <p>use the Ubuntu instance, called pip3 to install the latest Python 3 pip</p> <p><code>sudo pip install -U pip</code></p> <p>and (finally) install the docker-compose script:</p> <p><code>sudo pip install -U docker-compose</code></p> <h4><a id="user-content-set-up-our-conventional-directories" href="#set-up-our-conventional-directories" name="set-up-our-conventional-directories" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Set up our conventional directories</h4> <p>To set up your server, I recommend setting up a place for your Docker containers as per our <a href="/node/39">Docker-related conventions</a>:</p> <p><code>sudo mkdir -p /home/data/[domain name]</code><br /><code>sudo mkdir -p /home/docker/[domain name]</code></p> <h2><a id="user-content-step-three---configure-your-domain" href="#step-three---configure-your-domain" name="step-three---configure-your-domain" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Step three - configure your domain</h2> <h3><a id="user-content-set-up-nginx-reverse-proxy" href="#set-up-nginx-reverse-proxy" name="set-up-nginx-reverse-proxy" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Set up Nginx reverse proxy</h3> <p>A reverse proxy is a website intermediary. It accepts requests from the Internet, like a normal webserver would, but instead of having direct access to the data being requested, it, in turn, makes a request from another webserver either on the same host, or on a different one, and passes the results of that request back to the requester. In this case, the Nginx instance on the host is making a request of a <em>different host</em> that happens to reside on the same host instance as a Docker 'container'.</p> <p>Our convention is to create an Nginx reverse proxy configuration file with the same name as our [domain name], so in the case of, say, our Course WordPress Multisite, the file would be <code>/etc/nginx/sites-available/course.oeru.org</code>. Create a file in your <code>/etc/nginx/sites-available</code> with the following (again, replacing the [values] with your own values.</p> <p><code>sudo nano /etc/nginx/sites-available/[domain name]</code></p> <p>and copy and paste this in (and remember to replace the [tokens] with your relevant variables!):</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="co0">#</span> <span class="co0"># Set [domain name] and [port] below to make this work</span> <span class="co0">#</span> <span class="co0"># HTTP does *soft* redirect to HTTPS</span> <span class="co0">#</span> 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 <span class="br0">[</span>domain name<span class="br0">]</span>; root <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>scr; index index.php;   <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"># for let's encrypt renewals!</span> 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="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">#</span> <span class="co0"># HTTPS</span> <span class="co0">#</span> <span class="co0"># This assumes you're using Let's Encrypt for your SSL certs (and why wouldn't</span> <span class="co0"># you!?)... https://letsencrypt.org</span> server <span class="br0">{</span> <span class="co0"># add [IP-Address:]443 ssl 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; <span class="co0"># Note: these are *temporary* certificates, created when your host was set up</span> <span class="co0"># they are only in use to get Nginx to start up properly and let you create your let's encrypt certificates!</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"># these will be used after we finish the Let's Encrypt process</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> ssl_protocols TLSv1 TLSv1.1 TLSv1.2; <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; keepalive_timeout 20s;   server_name <span class="br0">[</span>domain name<span class="br0">]</span>; root <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>src; index index.php;   <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;   location <span class="sy0">/</span> <span class="br0">{</span> <span class="co0"># a good value for [port] is 8080, unless it's already in use by another service on your server...</span> proxy_pass http:<span class="sy0">//</span>127.0.0.1:<span class="br0">[</span>port<span class="br0">]</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-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-Host <span class="re1">$server_name</span>; proxy_set_header X-Forwarded-Proto https; proxy_connect_timeout <span class="nu0">900</span>; proxy_send_timeout <span class="nu0">900</span>; proxy_read_timeout <span class="nu0">900</span>; send_timeout <span class="nu0">900</span>; <span class="br0">}</span> <span class="co0">#</span> <span class="co0"># These "harden" your security</span> add_header <span class="st_h">'Access-Control-Allow-Origin'</span> <span class="st0">"*"</span>; <span class="co0"># from https://gist.github.com/Stanback/7145487</span> add_header <span class="st_h">'Access-Control-Allow-Credentials'</span> <span class="st_h">'true'</span> always; add_header <span class="st_h">'Access-Control-Allow-Methods'</span> <span class="st_h">'GET, POST, PUT, DELETE, OPTIONS'</span> always; add_header <span class="st_h">'Access-Control-Allow-Headers'</span> <span class="st_h">'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With'</span> always; <span class="co0">#</span> <span class="co0"># for H5P embedding</span> <span class="co0">#add_header 'X-Frame-Options' 'ALLOW-FROM https://h5p.oeru.org';</span> <span class="co0"># required to be able to read Authorization header in frontend</span> add_header <span class="st_h">'Access-Control-Expose-Headers'</span> <span class="st_h">'Authorization'</span> always; <span class="co0"># tested at https://csp-evaluator.withgoogle.com/</span> <span class="co0"># works, but only B+ on MozOBs https://observatory.mozilla.org/analyze.html</span> add_header X-XSS-Protection <span class="st0">"1; mode=block"</span>; <span class="br0">}</span></pre></div></div> <p>Having created that file, we now have to create the <code>ssl_dhparam</code> file we referenced, staring by installing OpenSSL tools:</p> <p><code>sudo apt-get install openssl</code></p> <p>by running (warning - this can take quite a long time - like 5-15 minutes in my experience, depending on a lot of factors - the system needs to generate sufficient entropy to achieve acceptable randomness):</p> <p><code>sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096</code></p> <p>When that's done, you should see there's a file here: <code>ls -l /etc/ssl/certs/dhparam.pem</code></p> <h3><a id="user-content-set-up-lets-encrypt-for-the-domain" href="#set-up-lets-encrypt-for-the-domain" name="set-up-lets-encrypt-for-the-domain" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Set up Let's Encrypt for the domain</h3> <p>We've already written a guide to setting up and securing your domain with <a href="/node/11">Let's Encrypt</a> but here're the relevant details:</p> <p><code>sudo mkdir /etc/nginx/includes</code></p> <p>and edit the following file</p> <p><code>sudo nano /etc/nginx/includes/letsencrypt.conf</code></p> <p>to make sure it has the following content:</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, make sure your designated Let's Encrypt directory exists (note - you only need to do this once on a given host):</p> <p><code>sudo mkdir /var/www/letsencrypt</code></p> <p>Now, we'll make sure Nginx is aware of your configuration, which you do like this (substituting [domain name] with your domain!):</p> <p><code>sudo ln -sf /etc/nginx/sites-available/[domain name] /etc/nginx/sites-enabled</code></p> <p>then make sure Nginx is happy with your configuration syntax:</p> <p><code>sudo nginx -t</code></p> <p>and fix any typos which might've crept in. If it says your configurations are okay, then make your configuration live:</p> <p><code>sudo service nginx reload</code></p> <p>Once this is done, you can check to see if things are working properly by entering <code>http://[domain name]</code> in your browser's address bar. It <em>should</em> redirect you to <code>http**s**://[domain name]</code> <em>and</em> give you an error that there's a 'mismatch' in your certificates... which there is: my configuration approach means the server and your reverse proxy configuration are <em>temporarily</em> using the default "SnakeOil" SSL certificate pair (required to get Nginx to start in SSL mode), but your Nginx configuration is working to the extent required for us to request our Let's Encrypt certificate!</p> <p>And here's how we actually generate your SSL certificat via Let's Encrypt (replacing [domain name] as appropriate):</p> <p><code>sudo letsencrypt --webroot -w /var/www/letsencrypt -d [domain name]</code></p> <p>If it works, it gratifyingly results in a message that starts with "Congratulations"! Well done if you got that! Note, if you get an error, make sure your domain is properly configured to point to your server! Also, there could be a delay in that configuration change taking effect due to the vaguiries of the DNS system. If it worked, you should have a set of certificates in the directories, currently commented out with leading "#"s, in the Nginx configuration file above. You'll now need to re-edit it</p> <p><code>sudo nano /etc/nginx/sites-available/[domain name]</code></p> <p>to change it so the relevant part looks like this (again, ensuring your [domain name] is in place in the relevant locations!):</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"> <span class="co0"># Note: these are *temporary* certificates, created when your host was set up</span> <span class="co0"># they are only in use to get Nginx to start up properly and let you create your let's encrypt certificates!</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"># these will be used after we finish the Let's Encrypt process</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>and then we need to do our obligatory configuration test:</p> <p><code>sudo nginx -t</code></p> <p>and if Nginx is happy, reload the configuration to put it into effect:</p> <p><code>sudo service nginx reload</code></p> <p>Now, if you point your browser at <code>http://[domain name]</code>, it should automatically redirect to <code>https://[domain name]</code> without any errors, except that it won't yet have any content to show you, so you might get a 404 error, which is expected!</p> <h2><a id="user-content-step-four---get-the-code" href="#step-four---get-the-code" name="step-four---get-the-code" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Step four - get the code</h2> <p>Next we have to get all the relevant code for the WordPress site and its dependencies.</p> <h3><a id="user-content-wordpress-source-code" href="#wordpress-source-code" name="wordpress-source-code" class="heading-permalink" aria-hidden="true" title="Permalink"></a>WordPress source code</h3> <p>The latest source code for WordPress is always available from <a href="https://wordpress.org/latest.tar.gz">https://wordpress.org/latest.tar.gz</a> - we'll get a copy of it now and put it in the right place:</p> <p><code>cd /home/data/[domain name]</code><br /><code>sudo wget https://wordpress.org/latest.tar.gz</code><br /><code>sudo tar xvfz latest.tar.gz &amp;&amp; sudo mv wordpress src</code></p> <p>After this, you should find a directory, <code>src</code> in your [domain name] directory. That contains all the default source code for WordPress including default themes and plugins.</p> <p>Next, we have to make sure the default theme is in place, and then set up the WordPress multisite configuration process.</p> <p>Later we'll get the assortment of third party plugins and custom OERu plugins needed to flesh out the WordPress functionality we need.</p> <h3><a id="user-content-oeru-theme" href="#oeru-theme" name="oeru-theme" class="heading-permalink" aria-hidden="true" title="Permalink"></a>OERu Theme</h3> <p>First, we'll get the OERu theme, which is specially designed to provide an accessible desktop <em>or mobile</em> experience, given that many - even a majority - of our learners are in the developing world, where mobile computing is dominant!</p> <p>We'll go to the theme directory - you should already be in <code>/home/data/[domain name]</code>. From there we go into the theme directory:</p> <p><code>cd src/wp-content/themes</code></p> <p>and we issue a <code>git clone</code> command to retrieve the latest version of the OERu theme from our git repository:</p> <p><code>git clone https://git.oeru.org/oeru/oeru_course.git oeru-course</code></p> <p>Then it's time to customise our WordPress configuration.</p> <h3><a id="user-content-customise-wp-configphp" href="#customise-wp-configphp" name="customise-wp-configphp" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Customise wp-config.php</h3> <p>First we need to create your wp-config.php file in your <code>src</code> directory:</p> <p><code>sudo nano wp-config.php</code></p> <p>Note: you'll need your password for Redis and a set of <strong>[wordpress keys and salts]</strong>, both of which are essentially just random numbers that are used to make you site far more secure. You can use the <a href="https://api.wordpress.org/secret-key/1.1/salt/">this link</a> to generate a set of random values suitable for copying-and-pasting into you <code>wp-config.php</code> file in your <code>src</code> directory - here's an example of the output I just requested (<strong>don't use these, generate your own</strong>):</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">define<span class="br0">(</span><span class="st_h">'AUTH_KEY'</span>, <span class="st_h">'?4^Huc~R1=WW+T_p~.0dH$XJ`&gt;U*MoreMmZ{@tORSFG3aX37#ZS+0ou{j^DS3{f&lt;'</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'SECURE_AUTH_KEY'</span>, <span class="st_h">'7.k4htjPnrA/?6JJlogA4Wp*o|,&amp;&amp;&gt;;20ppqeqHq#gI &lt;%gDz[o( hpRRB|!jws%'</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'LOGGED_IN_KEY'</span>, <span class="st_h">'fTX,WkI=doAUE%?{zHp5.?fN%WWtBuy~`Scntr&lt;]I1WvlF6i=7J kjO0Z%%~Z-`N'</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'NONCE_KEY'</span>, <span class="st_h">'?p&amp;]/*(G-+W!0#[&amp;Y6KKj)j Ok5QI(SUc@@rv,ivtF&gt; AR;Yv+Yu#&gt;$B$&lt;P9Ld|j'</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'AUTH_SALT'</span>, <span class="st_h">'H6s2H~KP]Z7YXTFt|8[Lgz[1~5wF+PJzxR^KW$|he+9|RF/vi@}/|&lt;8bkC:w)qW%'</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'SECURE_AUTH_SALT'</span>, <span class="st_h">'@- j6Kn CjP/mdbmLXtkC+&gt;1&gt;+H-8pXETlJ4+]b-9x_/t*.D}VA1w&lt;^A?0 R&lt;f+1'</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'LOGGED_IN_SALT'</span>, <span class="st_h">' y9:oh)]nA$}%9N-xk1MAQN1bH 8z{UD/e~K|G5{(9y|,n2E*,KwYPIf~HwhHT J'</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'NONCE_SALT'</span>, <span class="st_h">'|B?p#Q4|.=[VL8)2AX;zy-R2;x#dqIo=!C3,;OACT%-uaQ7Li5KSVSSnLahwlZ+o'</span><span class="br0">)</span>;</pre></div></div> <p>This is what your <code>wp-config.php</code> file should look like - copy and paste the following into the file, replacing the relevant [tokens] with your versions (in particular note that the above wordpress keys and salts need to go where I've got the token [wordpress keys and salts] below):</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="sy0">&lt;</span>?php <span class="sy0">/**</span> <span class="sy0">*</span> The base configuration <span class="kw1">for</span> WordPress <span class="sy0">*</span> <span class="sy0">*</span> The wp-config.php creation script uses this <span class="kw2">file</span> during the <span class="sy0">*</span> installation. You don<span class="st_h">'t have to use the web site, you can * copy this file to "wp-config.php" and fill in the values. * * This file contains the following configurations: * * * MySQL settings * * Secret keys * * Database table prefix * * ABSPATH * * @link https://codex.wordpress.org/Editing_wp-config.php * * @package WordPress */   // ** MySQL settings - You can get this info from your web host ** // /** The name of the database for WordPress */ define('</span>DB_NAME<span class="st_h">', '</span><span class="br0">[</span>database name<span class="st_h">');   /** MySQL database username */ define('</span>DB_USER<span class="st_h">', '</span><span class="br0">[</span>database user<span class="br0">]</span><span class="st_h">');   /** MySQL database password */ define('</span>DB_PASSWORD<span class="st_h">', '</span><span class="br0">[</span>database password<span class="br0">]</span><span class="st_h">');   /** MySQL hostname */ //define('</span>DB_HOST<span class="st_h">', '</span>10.10.10.1<span class="st_h">'); define('</span>DB_HOST<span class="st_h">', '</span>172.17.0.1<span class="st_h">');   /** Database Charset to use in creating database tables. */ define('</span>DB_CHARSET<span class="st_h">', '</span>utf8mb4<span class="st_h">'); //define('</span>DB_CHARSET<span class="st_h">', '</span>utf8<span class="st_h">');   /** The Database Collate type. Don'</span>t change this <span class="kw1">if</span> <span class="kw1">in</span> doubt. <span class="sy0">*/</span> define<span class="br0">(</span><span class="st_h">'DB_COLLATE'</span>, <span class="st_h">''</span><span class="br0">)</span>;   <span class="sy0">/**</span><span class="co0">#@+</span> <span class="sy0">*</span> Authentication Unique Keys and Salts. <span class="sy0">*</span> <span class="sy0">*</span> Change these to different unique phrases<span class="sy0">!</span> <span class="sy0">*</span> You can generate these using the <span class="br0">{</span><span class="sy0">@</span><span class="kw2">link</span> https:<span class="sy0">//</span>api.wordpress.org<span class="sy0">/</span>secret-key<span class="sy0">/</span><span class="nu0">1.1</span><span class="sy0">/</span>salt<span class="sy0">/</span> WordPress.org secret-key service<span class="br0">}</span> <span class="sy0">*</span> You can change these at any point <span class="kw1">in</span> <span class="kw1">time</span> to invalidate all existing cookies. This will force all <span class="kw2">users</span> to have to log <span class="kw1">in</span> again. <span class="sy0">*</span> <span class="sy0">*</span> <span class="sy0">@</span>since 2.6.0 <span class="sy0">*/</span> <span class="br0">[</span>wordpress keys and salts<span class="br0">]</span>   <span class="sy0">/**</span> <span class="sy0">*</span> WordPress Database Table prefix. <span class="sy0">*</span> <span class="sy0">*</span> You can have multiple installations <span class="kw1">in</span> one database <span class="kw1">if</span> you give each <span class="sy0">*</span> a unique prefix. Only numbers, letters, and underscores please<span class="sy0">!</span> <span class="sy0">*/</span> <span class="re1">$table_prefix</span> = <span class="st_h">'wp_'</span>;   <span class="sy0">/**</span> <span class="sy0">*</span> For developers: WordPress debugging mode. <span class="sy0">*</span> <span class="sy0">*</span> Change this to <span class="kw2">true</span> to <span class="kw3">enable</span> the display of notices during development. <span class="sy0">*</span> It is strongly recommended that plugin and theme developers use WP_DEBUG <span class="sy0">*</span> <span class="kw1">in</span> their development environments. <span class="sy0">*</span> <span class="sy0">*</span> For information on other constants that can be used <span class="kw1">for</span> debugging, <span class="sy0">*</span> visit the Codex. <span class="sy0">*</span> <span class="sy0">*</span> <span class="sy0">@</span><span class="kw2">link</span> https:<span class="sy0">//</span>codex.wordpress.org<span class="sy0">/</span>Debugging_in_WordPress <span class="sy0">*/</span> define<span class="br0">(</span><span class="st_h">'WP_DEBUG'</span>, <span class="kw2">false</span><span class="br0">)</span>; <span class="sy0">//</span>define<span class="br0">(</span><span class="st_h">'WP_DEBUG'</span>, <span class="kw2">true</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'CONCATENATE_SCRIPTS'</span>, <span class="kw2">false</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'SCRIPT_DEBUG'</span>, <span class="kw2">true</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'WP_DEBUG'</span>, <span class="kw2">true</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'WP_DISABLE_FATAL_ERROR_HANDLER'</span>, <span class="kw2">true</span> <span class="br0">)</span>; <span class="sy0">//</span> <span class="nu0">5.2</span> and later   <span class="sy0">/*</span> We are behind a reverse proxy <span class="sy0">*/</span> <span class="kw1">if</span> <span class="br0">(</span>isset<span class="br0">(</span><span class="re1">$_SERVER</span><span class="br0">[</span><span class="st_h">'HTTP_X_FORWARDED_FOR'</span><span class="br0">]</span><span class="br0">)</span><span class="br0">)</span> <span class="br0">{</span> <span class="re1">$forwarded_address</span> = explode<span class="br0">(</span><span class="st_h">','</span>, <span class="re1">$_SERVER</span><span class="br0">[</span><span class="st_h">'HTTP_X_FORWARDED_FOR'</span><span class="br0">]</span><span class="br0">)</span>; <span class="re1">$_SERVER</span><span class="br0">[</span><span class="st_h">'REMOTE_ADDR'</span><span class="br0">]</span> = <span class="re1">$forwarded_address</span><span class="br0">[</span><span class="nu0">0</span><span class="br0">]</span>; <span class="br0">}</span> <span class="kw1">if</span> <span class="br0">(</span>isset<span class="br0">(</span><span class="re1">$_SERVER</span><span class="br0">[</span><span class="st_h">'HTTP_X_FORWARDED_PROTO'</span><span class="br0">]</span><span class="br0">)</span> <span class="sy0">&amp;&amp;</span> <span class="re1">$_SERVER</span><span class="br0">[</span><span class="st_h">'HTTP_X_FORWARDED_PROTO'</span><span class="br0">]</span> == <span class="st_h">'https'</span><span class="br0">)</span> <span class="br0">{</span> <span class="re1">$_SERVER</span><span class="br0">[</span><span class="st_h">'HTTPS'</span><span class="br0">]</span> = <span class="st_h">'on'</span>; <span class="br0">}</span>   <span class="sy0">/*</span> Disable the default <span class="st_h">'ad hoc'</span> cron mechanism. We<span class="st_h">'ll use actual cron instead. */ define('</span>DISABLE_WP_CRON<span class="st_h">', true);   /* That'</span>s all, stop editing<span class="sy0">!</span> Happy blogging. <span class="sy0">*/</span>   <span class="sy0">/**</span> Absolute path to the WordPress directory. <span class="sy0">*/</span> <span class="kw1">if</span> <span class="br0">(</span> <span class="sy0">!</span>defined<span class="br0">(</span><span class="st_h">'ABSPATH'</span><span class="br0">)</span> <span class="br0">)</span> define<span class="br0">(</span><span class="st_h">'ABSPATH'</span>, <span class="kw2">dirname</span><span class="br0">(</span>__FILE__<span class="br0">)</span> . <span class="st_h">'/'</span><span class="br0">)</span>;   <span class="sy0">/*</span> Multisite <span class="sy0">*/</span> <span class="sy0">//</span> see https:<span class="sy0">//</span>wordpress.org<span class="sy0">/</span>support<span class="sy0">/</span>article<span class="sy0">/</span>create-a-network<span class="sy0">/</span> define<span class="br0">(</span><span class="st_h">'WP_ALLOW_MULTISITE'</span>, <span class="kw2">true</span><span class="br0">)</span>; <span class="sy0">/*</span>define<span class="br0">(</span><span class="st_h">'MULTISITE'</span>, <span class="kw2">true</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'SUBDOMAIN_INSTALL'</span>, <span class="kw2">false</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'DOMAIN_CURRENT_SITE'</span>, <span class="st_h">'[domain name]'</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'PATH_CURRENT_SITE'</span>, <span class="st_h">'/'</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'SITE_ID_CURRENT_SITE'</span>, <span class="nu0">1</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'BLOG_ID_CURRENT_SITE'</span>, <span class="nu0">1</span><span class="br0">)</span>;<span class="sy0">*/</span>   <span class="sy0">/*</span> disable trash, immediately permanently delete <span class="sy0">*/</span> define<span class="br0">(</span><span class="st_h">'EMPTY_TRASH_DAYS'</span>, <span class="nu0">0</span><span class="br0">)</span>; <span class="sy0">/*</span> <span class="kw1">set</span> the default theme <span class="kw1">for</span> the network <span class="sy0">*/</span> define<span class="br0">(</span><span class="st_h">'WP_DEFAULT_THEME'</span>, <span class="st_h">'oeru_course'</span><span class="br0">)</span>;   <span class="sy0">/**</span> Caching-related configuration <span class="sy0">*/</span> <span class="sy0">/**</span> Redis <span class="sy0">*/</span> define<span class="br0">(</span><span class="st_h">'WP_REDIS_HOST'</span>, <span class="st_h">'redis'</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'WP_REDIS_PASSWORD'</span>, <span class="st_h">'[redis password]'</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'WP_REDIS_PATH'</span>, <span class="st_h">'/tmp/cache'</span><span class="br0">)</span>;   <span class="sy0">/*</span> WEnotes plugin configuration, commented out by default <span class="sy0">*/</span> <span class="sy0">/*</span> this is optional<span class="br0">(</span><span class="sy0">!!</span><span class="br0">)</span> - only use <span class="kw1">if</span> you<span class="st_h">'re deploying the OERu WEnotes stack - contact us if you want help! */ /*define('</span>WENOTES_HOST<span class="st_h">', '</span><span class="br0">[</span>couchdb host<span class="br0">]</span><span class="st_h">'); define('</span>WENOTES_PORT<span class="st_h">', '</span><span class="nu0">80</span><span class="st_h">'); define('</span>WENOTES_DB<span class="st_h">', '</span><span class="br0">[</span>couchdb mention database<span class="br0">]</span><span class="st_h">'); define('</span>WENOTES_USER<span class="st_h">', '</span><span class="br0">[</span>couchdb user<span class="br0">]</span><span class="st_h">'); define('</span>WENOTES_PASS<span class="st_h">', '</span><span class="br0">[</span>couchdb password<span class="br0">]</span><span class="st_h">');*/   // go to /wp-admin/maint/repair.php to see if a repair is needed... //define( '</span>WP_ALLOW_REPAIR<span class="st_h">', true );     /** Sets up WordPress vars and included files. */ require_once(ABSPATH . '</span>wp-settings.php<span class="st_h">');</span></pre></div></div> <h2><a id="user-content-step-five---set-up-docker-compose-for-the-site" href="#step-five---set-up-docker-compose-for-the-site" name="step-five---set-up-docker-compose-for-the-site" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Step five - set up Docker Compose for the site</h2> <p>The reciipe fo the four Docker containers that together provide the WordPress multisite service is pretty straightforward. All you need to do is go into <code>/home/docker/[domain name]</code> and</p> <p><code>nano docker-compose.yml</code></p> <p>and enter the following, replacing the [tokens] as usual - note, if you don't have the <em>same value</em> for [port] specified below as you do in your 'reverse proxy' configuration above, <em>nothing</em> will work:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">version: <span class="st0">"3"</span>   services: redis: image: redis:alpine command: redis-server <span class="re5">--requirepass</span> <span class="br0">[</span>redis password<span class="br0">]</span> networks: default: aliases: - redis.<span class="br0">[</span>domain name<span class="br0">]</span> php: image: oeru<span class="sy0">/</span>php74-fpm links: - 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>src:<span class="sy0">/</span>var<span class="sy0">/</span>www<span class="sy0">/</span>html environment: - <span class="re2">SMTP_HOST</span>=<span class="br0">[</span>smtp host<span class="br0">]</span> - <span class="re2">SMTP_PORT</span>=<span class="nu0">587</span> - <span class="re2">SMTP_REPLYTO_EMAIL</span>=<span class="br0">[</span>smtp reply-to email address<span class="br0">]</span> - <span class="re2">SMTP_AUTH_USER</span>=<span class="br0">[</span>smtp user<span class="br0">]</span> - <span class="re2">SMTP_AUTH_PASSWORD</span>=<span class="br0">[</span>smtp password<span class="br0">]</span> restart: unless-stopped networks: default: aliases: - <span class="br0">[</span>domain name<span class="br0">]</span> nginx: image: oeru<span class="sy0">/</span>nginx-buster-wp links: - php - redis ports: - <span class="st0">"127.0.0.1:[port]:80"</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>nginx<span class="sy0">/</span>conf.d:<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><span class="br0">[</span>domain name<span class="br0">]</span><span class="sy0">/</span>nginx<span class="sy0">/</span>cache:<span class="sy0">/</span>var<span class="sy0">/</span>cache<span class="sy0">/</span>nginx - <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>src:<span class="sy0">/</span>var<span class="sy0">/</span>www<span class="sy0">/</span>html restart: unless-stopped networks: default: aliases: - nginx.<span class="br0">[</span>domain name<span class="br0">]</span> cron: image: oeru<span class="sy0">/</span>php74-fpm-cron-wp links: - php - nginx 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>src:<span class="sy0">/</span>var<span class="sy0">/</span>www<span class="sy0">/</span>html environment: - <span class="re2">SMTP_HOST</span>=<span class="br0">[</span>smtp host<span class="br0">]</span> - <span class="re2">SMTP_PORT</span>=<span class="nu0">587</span> - <span class="re2">SMTP_REPLYTO_EMAIL</span>=<span class="br0">[</span>smtp reply-to email address<span class="br0">]</span> - <span class="re2">SMTP_AUTH_USER</span>=<span class="br0">[</span>smtp user<span class="br0">]</span> - <span class="re2">SMTP_AUTH_PASSWORD</span>=<span class="br0">[</span>smtp password<span class="br0">]</span> restart: unless-stopped networks: default: aliases: - cron.<span class="br0">[</span>domain name<span class="br0">]</span></pre></div></div> <h3><a id="user-content-launch-containers" href="#launch-containers" name="launch-containers" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Launch containers</h3> <p>Once that's done you can download the relevant containers (you may need to set up an account on <a href="https://hub.docker.com">https://hub.docker.com</a> first - do that and then run <code>docker login</code> before doing the following):</p> <p><code>docker-compose pull &amp;&amp; docker-compose up -d &amp;&amp; docker-compose logs -f</code></p> <p>which will show you the combined logs of the four containers and should give you some insights if something goes wrong. If all is well, you can type <code>CTRL-C</code> to exit it. The Docker containers (to see what's running, you can run <code>docker-compose ps</code>) will run until you explicitly stop them (<code>docker-compose stop</code>). Unless you stop them prior to lock down, they will automatically restart anytime your server reboots.</p> <h2><a id="user-content-step-six---set-up-your-site" href="#step-six---set-up-your-site" name="step-six---set-up-your-site" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Step Six - set up your site</h2> <h3><a id="user-content-install-wordpress" href="#install-wordpress" name="install-wordpress" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Install WordPress</h3> <p>You should now be able to point your browser at <code>https://[your domain]</code> and you should automatically be redirected to the WordPress site install script, with all the database details already entered. You'll need to fill in the configuration fields and create an admin user.</p> <h3><a id="user-content-enable-the-oeru-course-theme" href="#enable-the-oeru-course-theme" name="enable-the-oeru-course-theme" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Enable the OERu Course theme</h3> <p>You should already be in the <code>/home/docker/[domain name]</code> directory, but if not,</p> <p><code>cd /home/docker/[domain name]</code></p> <p>and then you can enable the OERu Course theme, <code>oeru-course</code>, using WordPress's command line utility, <code>wp</code>, via your PHP container:</p> <p><code>docker-compose exec -u www-data php wp theme enable oeru-course</code></p> <p>You should get the message <code>Success: Enabled the 'OERu Course' theme.</code> if all went well.</p> <h3><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</h3> <p>Your admin user should probably be an account not normally used by a person. I usually create an admin user with a username of 'admin', a random password (using pwgen as above) and a role-based email like webmaster@[domain name] (if your domain name has email services) or some similarly useful generic email (using that protects against the 'tyranny of the individual', e.g. if you leave the organisation in whose interest you're setting up this site, and those left behind will have to make sense of this site and keep it running!</p> <p>Later one, if you want to log into the site, you can always get a login prompt by going to https://[domain name]/admin/ ...</p> <h3><a id="user-content-enable-multisite" href="#enable-multisite" name="enable-multisite" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Enable multisite</h3> <p>The WordPress configuration we've set up in the wp-config.php file should have enabled the inbuilt functionality for enabling 'multisite' mode (or 'network', as it used to be called, i.e. for a "Network of Blogs", back when WordPress was more exclusively used for blogging). <a href="https://wordpress.org/support/article/create-a-network/">WordPress developer documentation on 'Creating a network'</a> might be a useful reference if you run into any trouble.</p> <p>Logged in as your admin user, in the admin menu structure go to Administration &gt;Tools &gt; Network Setup</p> <p>You will be asked ot provide your Network Title and Network Admin Email (which can be the same as your current admin email).</p> <p>You will also be asked whether you want to use the 'Sub-domains' or 'Sub-directories' structure for your network. Select the <strong>Sub-directories</strong> option (you will need to make changes your Nginx configurations and the wp-config.php configuration to use Sub-domains, which are beyond the scope of this howto), which means that each of your subsites will have a separate directory under your main site [domain name]. For example, on the OERu Course site, course.oeru.org, the Learning in a Digital Age 101 course sub-site is course.oeru.org/lida101. If you chose to use the Sub-domain option, you would instead reference that course as lida101.course.oeru.org.</p> <p>Once you've enabled multisite, you need to update your wp-config.php -</p> <p><code>sudo nano wp-config.php</code></p> <p>and make the following change, to comment out the WP_ALLOW_MULTISITE variable, and uncomment the various MULTISITE-related settings:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="sy0">/*</span> Multisite <span class="sy0">*/</span> <span class="sy0">//</span> see https:<span class="sy0">//</span>wordpress.org<span class="sy0">/</span>support<span class="sy0">/</span>article<span class="sy0">/</span>create-a-network<span class="sy0">/</span> <span class="sy0">//</span>define<span class="br0">(</span><span class="st_h">'WP_ALLOW_MULTISITE'</span>, <span class="kw2">true</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'MULTISITE'</span>, <span class="kw2">true</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'SUBDOMAIN_INSTALL'</span>, <span class="kw2">false</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'DOMAIN_CURRENT_SITE'</span>, <span class="st_h">'[domain name]'</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'PATH_CURRENT_SITE'</span>, <span class="st_h">'/'</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'SITE_ID_CURRENT_SITE'</span>, <span class="nu0">1</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'BLOG_ID_CURRENT_SITE'</span>, <span class="nu0">1</span><span class="br0">)</span>;</pre></div></div> <p>After you've saved that, and refreshed the page on your WordPress site, you should be looking at a fully functioning multisite!</p> <h3><a id="user-content-enable-the-relevant-plugins" href="#enable-the-relevant-plugins" name="enable-the-relevant-plugins" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Enable the relevant plugins</h3> <h3><a id="user-content-third-party-plugins" href="#third-party-plugins" name="third-party-plugins" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Third Party plugins</h3> <p>Now we need to get the specific OERu plugins that are not (yet) available through the WordPress plugin site.</p> <p>The ones we use by default are</p> <p><code> advanced-responsive-video-embedder, check-email, disable-comments, h5p, hypothesis, redis-cache, safe-redirect-manager, unconfirmed, wp-security-audit-log</code></p> <p>As we did with the OERu Course theme, we'll install and active these plugins using WordPress's command line utility, <code>wp</code>, via your PHP container:</p> <p><code>cd /home/docker/[domain name]</code></p> <p>you can see the names of all your running containers by running this:</p> <p><code>docker-compose ps</code></p> <p>and you can download and activate all these plugins in 'network' (aka multisite) mode like this:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">docker-compose <span class="kw3">exec</span> <span class="re5">-u</span> www-data php wp plugin <span class="kw2">install</span> advanced-responsive-video-embedder <span class="re5">--activate-network</span> docker-compose <span class="kw3">exec</span> <span class="re5">-u</span> www-data php wp plugin <span class="kw2">install</span> check-email <span class="re5">--activate-network</span> docker-compose <span class="kw3">exec</span> <span class="re5">-u</span> www-data php wp plugin <span class="kw2">install</span> disable-comments <span class="re5">--activate-network</span> docker-compose <span class="kw3">exec</span> <span class="re5">-u</span> www-data php wp plugin <span class="kw2">install</span> h5p <span class="re5">--activate-network</span> docker-compose <span class="kw3">exec</span> <span class="re5">-u</span> www-data php wp plugin <span class="kw2">install</span> hypothesis <span class="re5">--activate-network</span> docker-compose <span class="kw3">exec</span> <span class="re5">-u</span> www-data php wp plugin <span class="kw2">install</span> redis-cache <span class="re5">--activate-network</span> docker-compose <span class="kw3">exec</span> <span class="re5">-u</span> www-data php wp plugin <span class="kw2">install</span> safe-redirect-manager <span class="re5">--activate-network</span> docker-compose <span class="kw3">exec</span> <span class="re5">-u</span> www-data php wp plugin <span class="kw2">install</span> unconfirmed <span class="re5">--activate-network</span> docker-compose <span class="kw3">exec</span> <span class="re5">-u</span> www-data php wp plugin <span class="kw2">install</span> wp-security-audit-log <span class="re5">--activate-network</span></pre></div></div> <p>These plugins should work as required without any specific configuration, but you're welcome to have a look at what they're doing and tweak them as you see fit.</p> <h3><a id="user-content-oeru-plugins" href="#oeru-plugins" name="oeru-plugins" class="heading-permalink" aria-hidden="true" title="Permalink"></a>OERu plugins</h3> <p>The final step is to install and enable the custom-developed plugins required to make this WordPress installation meet the requirements of an OERu Course site.</p> <p>At the OERu (and the OER Foundation, who coordinates the OERu and employs me) we make extensive use of Git to manage our source code and to deploy it on our various servers. We'll use it here.</p> <p>You'll need to go back to the plugin directory:</p> <p><code>cd /home/data/[domain name]/src/wp-content/plugins</code></p> <p>and run the following:</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="kw2">git clone</span> https:<span class="sy0">//</span>git.oeru.org<span class="sy0">/</span>oeru<span class="sy0">/</span>blog-feed-finder.git blog-feed-finder <span class="kw2">git clone</span> https:<span class="sy0">//</span>git.oeru.org<span class="sy0">/</span>oeru<span class="sy0">/</span>oeru-h5p-tools.git oeru-h5p-tools <span class="kw2">git clone</span> https:<span class="sy0">//</span>git.oeru.org<span class="sy0">/</span>oeru<span class="sy0">/</span>register-enrol.git register-enrol <span class="kw2">git clone</span> https:<span class="sy0">//</span>git.oeru.org<span class="sy0">/</span>oeru<span class="sy0">/</span>wenotes.git wenotes <span class="kw2">git clone</span> https:<span class="sy0">//</span>git.oeru.org<span class="sy0">/</span>oeru<span class="sy0">/</span>wpms-activity-register.git wpms-activity-register <span class="kw2">git clone</span> https:<span class="sy0">//</span>git.oeru.org<span class="sy0">/</span>oeru<span class="sy0">/</span>wpms-mautic.git wpms-mautic</pre></div></div> <p>and the WEnotes plugin has a further dependency, our WEnotes Aggregator code.</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="kw3">cd</span> wenotes <span class="kw2">git clone</span> https:<span class="sy0">//</span>git.oeru.org<span class="sy0">/</span>oeru<span class="sy0">/</span>wenotes-aggregator.git wenotes-aggregator</pre></div></div> <p>And we have to make a couple tweaks to configurations of a couple plugins.</p> <h4><a id="user-content-wenotes-tweaks" href="#wenotes-tweaks" name="wenotes-tweaks" class="heading-permalink" aria-hidden="true" title="Permalink"></a>WEnotes tweaks</h4> <p>You'll want to set your WENOTES_SOURCE_NAME and WENOTES_SOURCE_URL so that your WEnotes are properly attributed.</p> <p>[geshifilter-]cd /home/data/[domain name]/src/wp-content/plugins sudo nano wenotes/wenotes.php [/geshifilter-]</p> <p>and edit the following values (between the ' ') to be suitable for your organisation! Put in your preferred URL as well.</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1">define<span class="br0">(</span><span class="st_h">'WENOTES_SOURCE_NAME'</span>, <span class="st_h">'OERu Course Site'</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'WENOTES_SOURCE_URL'</span>, <span class="st_h">'course.oeru.org'</span><span class="br0">)</span>;</pre></div></div> <h4><a id="user-content-register-enrol-tweaks" href="#register-enrol-tweaks" name="register-enrol-tweaks" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Register Enrol tweaks</h4> <p>Similarly, you are likely to want to change the following settings in the Register Enrol plugin configuration to reflect your site and organisation. First, edit <code>register-enrol.php</code></p> <p><code>sudo nano register-enrol/register-enrol.php</code></p> <p>and alter the following values (between the ' ') to suit (we don't recommend change other settings unless you know what you're doing!):</p> <div class="geshifilter"><div class="bash geshifilter-bash"><pre class="de1"><span class="sy0">//</span> support <span class="kw2">link</span> <span class="kw1">for</span> <span class="kw2">users</span> of this plugin... define<span class="br0">(</span><span class="st_h">'ORE_SUPPORT_FORUM'</span>, <span class="st_h">'https://forums.oeru.org/t/register-enrol'</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'ORE_SUPPORT_BLOG'</span>, <span class="st_h">'https://course.oeru.org/support/studying-courses/register-enrol/'</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'ORE_SUPPORT_CONTACT'</span>, <span class="st_h">'https://oeru.org/contact-us/'</span><span class="br0">)</span>; define<span class="br0">(</span><span class="st_h">'ORE_SUPPORT_PASSWORD_MANAGER'</span>, <span class="st_h">'https://course.oeru.org/lida102/learning-pathways/digital-environments/online-hygiene/#Password_managers'</span><span class="br0">)</span>;</pre></div></div> <p>and</p> <p>define('ORE_DEFAULT_FROM_EMAIL', '<a href="mailto:webmaster@oerfoundation.org">webmaster@oerfoundation.org</a>');</p> <h4><a id="user-content-mautic-integration-tweaks" href="#mautic-integration-tweaks" name="mautic-integration-tweaks" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Mautic Integration tweaks</h4> <p>You might also want to set up an OERu Partner record for your organisation (if you're not already a partner!) in our <a href="https://mautic.com">Mautic</a> integration plugin in the file <code>wpms-mautic/includes/mautic-sync.php</code> in the <code>$partner_names</code> array and then and set your <code>MAUTIC_DEFAULT_PARTNER</code> to have your partner name in <code>wpms-mautic/mautic-app.php</code>.</p> <h4><a id="user-content-reasserting-ownership" href="#reasserting-ownership" name="reasserting-ownership" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Reasserting ownership</h4> <p>And a final step - this is crucial, as it will allow you to keep your WordPress instance up-to-date using the various WordPress standard approaches - is to ensure that the webserver user, <code>www-data</code> is the owner of the source code in your site:</p> <p><code>sudo chown -R www-data /home/data/[domain name]/src</code></p> <p>That's it. Done. Phew.</p> <h2><a id="user-content-step-seven---celebrate" href="#step-seven---celebrate" name="step-seven---celebrate" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Step Seven - celebrate!</h2> <p>You've done it! You've got yourself a new Course WordPress multisite! Which, if I do say so myself, is an impressive accomplishment in it's own right.</p> <p>Of course, it might not be as exciting as actually having a real live course hosted on your Course WordPress multisites... so there's one more step (maybe take a quick break to celebrate getting to this milestone before proceeding).</p> <h2><a id="user-content-snapshotting-your-first-oer-course" href="#snapshotting-your-first-oer-course" name="snapshotting-your-first-oer-course" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Snapshotting your first OER course!</h2> <p>There are quite a few courses (fully accredited!) available to push to your Course multisite on <a href="https://wikieducator.org">WikiEducator</a> where we work with educators around the world to assemble OER-based courses, usually split into discrete 'micro-courses' that can be assembled to meet the credit requirement conventions in different parts of the world.</p> <p>Here is an example course you can add to verify the process: the first micro-course in our (award winning) <a href="">Learning in a digital age</a>, called <a href="https://wikieducator.org/Learning_in_a_digital_age/_Outline_LiDA101">Digital literacies for online learning</a>. That link points to the <em>Outline</em> for the course, which in turn shows all the resources making up the course materials in the hierarchy that will become the navigation of the resulting Course site on your WordPress Multisite. The way e get it there is by using our "<a href="/node/15">Course Snapshot</a>" process which converts that WikiEducator content in the outline into a WordPress content archive that it then pushes onto your designated Course subsite.</p> <p>Before we can run a snapshot, you'll need to</p> <ol><li> <a href="https://wikieducator.org/Special:RequestAccount">request a WikiEducator account</a> - unfortunately this is a moderated process (i.e. a person at the OER Foundation has to review your request) because we have lots of problems with would-be spammers. So this could take a fair while depending on when you request it (we're on NZ time). So much for instant gratification. Sorry about that.</li> <li>you'll need to create a subsite (you don't want to replace your multisite's base site).</li> </ol><p>To do the latter, when you're logged into your WordPress multisite as the admin user, use the top menu to go to 'My Sites' -&gt; 'Network Admin' -&gt; 'Sites' and click the 'Add New' button. You'll need to specify a "Site Address (URL)" which is just the path following [domain name] in referencing your site. For example, the LiDA 101 course on the OERu's Course site is <code>https://course.oeru.org/lida101</code> where <code>lida101</code> is that path. You could use that same path here if you want (it's appropriate for this course).</p> <p>You'll also need a title - you could use "Digital literacies for online learning" - and pick a Site Language (the default is probably what you want). For the 'Admin Email', use the email of your admin user, as WordPress will then make the user with that email, namely your admin user, the administrator of that lida1010 subsite, which is what we want.</p> <p>Next, we go back to the WikiEducator page for the LiDA 101 outline above and find the 'Request snapshot' button near the top of the page. This is the important bit - <strong>Note: doing this will remove any existing content in the subsite you specify! Make sure that's what you're intending</strong> - clicking it will give you a dialog box into which you'll enter the full location of your subsite, which will look like <code>https://[domain name]/[subsite name]</code> (for example, in the OERu case it is <code>https://course.oeru.org/lida101</code>) , making suitable [token] substitutions, of course, and enter as your <em>WordPress</em> admin user and password. Clicking "Push snapshot to WordPress" sets things in motion, and, all going well, you should receive an email in a few minutes (5-20, usually) when the system has got to your request (it does them on a first-come, first-served basis) and processed it successfully. If you get that email, have another look at your site! It should look very similar to what you see on <code>https://course.oeru.org/lida101</code> with the main difference being (possibly) the colour scheme and the lack of a Login/Register prompt (top right). That can be fixed as I explain next.</p> <h2><a id="user-content-enabling-oerus-register-enrol-functionality" href="#enabling-oerus-register-enrol-functionality" name="enabling-oerus-register-enrol-functionality" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Enabling OERu's Register Enrol functionality</h2> <p>The interface the OER Foundation has developed to help learners register for the site and enrol in specific courses is not enabled by default and needs to be turned on for any given Course sub-site (this is useful for courses that are visible on the site, but not yet ready to accept enrolments). To do so, go to the relevant course site dashboard in the WordPress menu, and select Appearance -&gt; Customize. In the resulting theme customisation side panel, selelct 'Site Navigation', and then set 'Show the login option?' to 'Yes' and 'Publish' to save the setting. Your login prompt should show near the top right corner of all the pages <em>in that course site</em> at that point.</p> <p>Also note, you can change the pre-set colour palette for your course sites on a per-site basis using the menu combination OERu Theme -&gt; Colour Scheme...</p> <h2><a id="user-content-data-backups" href="#data-backups" name="data-backups" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Data Backups</h2> <p>Your MariaDB will already be getting backed up daily via the <code>automysqlbackup</code> script installed towards the start of this process, but you also want to have backups of files and configurations on your server, and you want to have <em>backups on a different server</em>, i.e. remote to your server, as a matter of disaster-recovery prudence.</p> <p>We will be describing how we do remote, encrypted, incremental backups in a separate how-to and will link to it here as soon as it's available.</p> <h2><a id="user-content-staying-up-to-date" href="#staying-up-to-date" name="staying-up-to-date" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Staying up-to-date</h2> <p>You should log into your site as your admin user or as your personal user (granted Administrator permissions by your admin user!) periodically to see if there are any updates available for your site - if there are, you can update them as per the instructions provided by the site.</p> <p>The OERu plugins and themes undergo periodic improvements or bug fixes. To find out about them, you'll need to keep track of the various Git repositories on <a href="https://git.oeru.org/explore/projects">our Gitlab instance</a>.</p> <p>These are the relevant repositories:</p> <ul><li> <a href="https://git.oeru.org/oeru/oeru_course">OERu's Course Theme</a> </li> <li> <a href="https://git.oeru.org/oeru/blog-feed-finder">OERu's Blog Feed Finder</a> </li> <li> <a href="https://git.oeru.org/oeru/oeru-h5p-tools">OERu's H5P tools</a> </li> <li> <a href="https://git.oeru.org/oeru/register-enrol">OERu's Register Enrol</a> </li> <li> <a href="https://git.oeru.org/oeru/wenotes">OERu's WEnotes</a> </li> <li> <a href="https://git.oeru.org/oeru/wenotes-aggregator">OERu's WEnotes Aggregator</a> </li> <li> <a href="https://git.oeru.org/oeru/wpms-activity-register">OERu's Activity Register</a> </li> <li> <a href="https://git.oeru.org/oeru/wpms-mautic">OERu's Mautic Integration</a> </li> </ul><p>We're always delighted to <a href="/contact">hear from folks</a> using any of these components and invite people to collaborate with us on improving any and all of these software projects, including providing access to our Gitlab!</p> <p>Also, from time to time, we might update the Docker containers we use for this service, and you're always welcome to make use of our updated containers. If you have any questions, feel free to <a href="/contact">get in touch</a> or leave a comment below!!</p> <h2><a id="user-content-acknowledgements" href="#acknowledgements" name="acknowledgements" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Acknowledgements</h2> <p>Many thanks to the good folks at the Samoan Ministry for Education, Sport, and Culture and UNESCO in Apia for motivating me to write this up, and to educational technologist luminary <a href="https://downes.ca">Stephen Downes</a> for unexpectedly finding this tutorial and then (even more unexpectedly) heroically and comprehensively going through it and providing extensive editorial input (<a href="https://www.youtube.com/watch?v=PBTkZvKf080">part 1</a> and <a href="https://www.youtube.com/watch?v=rqryD1Qi-ws">part 2</a>), most (if not all) of which I've since incorporated! Thanks Stephen - see people were listening (just not in real-time)!</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=38&amp;2=field_blog_comments&amp;3=comment" token="pD7Al-LnIZTaG3QCT3T8JzCIHMbfkyXNmQSpAk-VQmo"></drupal-render-placeholder> </div> </section> Tue, 24 Aug 2021 04:00:22 +0000 dave 38 at http://tech.oeru.org Setting up your own BitWarden password manager and sync server http://tech.oeru.org/setting-your-own-bitwarden-password-manager-and-sync-server <span class="field field--name-title field--type-string field--label-hidden">Setting up your own BitWarden password manager and sync 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--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--docker"> <span class="field__item-wrapper"><a href="/taxonomy/term/16" hreflang="en">docker</a></span> </div> <div class="field__item field__item--rust"> <span class="field__item-wrapper"><a href="/taxonomy/term/59" hreflang="en">rust</a></span> </div> <div class="field__item field__item--bitwarden"> <span class="field__item-wrapper"><a href="/taxonomy/term/60" hreflang="en">BitWarden</a></span> </div> <div class="field__item field__item--password-keeper"> <span class="field__item-wrapper"><a href="/taxonomy/term/61" hreflang="en">password keeper</a></span> </div> <div class="field__item field__item--privacy"> <span class="field__item-wrapper"><a href="/taxonomy/term/62" hreflang="en">privacy</a></span> </div> <div class="field__item field__item--security"> <span class="field__item-wrapper"><a href="/taxonomy/term/63" hreflang="en">security</a></span> </div> <div class="field__item field__item--vaultwarden"> <span class="field__item-wrapper"><a href="/taxonomy/term/82" hreflang="en">Vaultwarden</a></span> </div> </div> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> <span class="field field--name-created field--type-created field--label-hidden">Sat 21/08/2021 - 11:30</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/2019-04/bitwarden_desktop_app.png?itok=9ZE2TxZh" title="BitWarden desktop app (Electron-based)" data-colorbox-gallery="gallery-field_image-ua8-LkAmUsQ" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;BitWarden desktop app (Electron-based)&quot;}"><img src="/sites/default/files/styles/medium/public/2019-04/bitwarden_desktop_app.png?itok=hekOd__9" width="220" height="169" alt="BitWarden desktop app (Electron-based)" 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/2019-04/bitwarden_web_interface_0.png?itok=QShOqyOM" title="BitWarden website interface" data-colorbox-gallery="gallery-field_image-ua8-LkAmUsQ" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;BitWarden website interface&quot;}"><img src="/sites/default/files/styles/medium/public/2019-04/bitwarden_web_interface_0.png?itok=QitHloR0" width="220" height="158" alt="BitWarden website interface" 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/2019-04/bitwarden_desktop_app_logged_in_0.png?itok=FAbPSjim" title="Desktop BitWarden app, with user logged in. " data-colorbox-gallery="gallery-field_image-ua8-LkAmUsQ" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;Desktop BitWarden app, with user logged in. &quot;}"><img src="/sites/default/files/styles/medium/public/2019-04/bitwarden_desktop_app_logged_in_0.png?itok=p9Oarp8h" width="220" height="156" alt="Desktop BitWarden app, with user logged in. " 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/2019-04/bitwarden_adding_new_item_via_web_interface_0.png?itok=_PTdZqjB" title="Web interface for BitWarden with user logged in." data-colorbox-gallery="gallery-field_image-ua8-LkAmUsQ" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;Web interface for BitWarden with user logged in.&quot;}"><img src="/sites/default/files/styles/medium/public/2019-04/bitwarden_adding_new_item_via_web_interface_0.png?itok=YQftj3vf" width="220" height="209" alt="Web interface for BitWarden with user logged in." 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>One of the key requirements of pursuing Good Digital Hygiene is <em>using strong passwords</em>, and a <em>different strong password for every application</em>. This is relatively easy to do in theory, <em>with the aid of clever software</em>, but it's something desperately few people do well in practice. I'm going to explain how I've addressed this issue of digital hygiene for myself, and how you can do it for yourself, <em>and your entire family, social circle, or community</em>.</p> <p>Password Managers (or keepers or safes) have emerged as that "clever software". A good password manager has to do a bunch of things to be really useful:</p> <ol><li>It needs to store your passwords somewhere in an encrypted form (so if someone gets your password database, they can't work out your entire collection of passwords). You only need to remember <em>one </em><em>really strong password/phrase to unlock all of them. </em></li> <li>It needs to work in whatever context you need a password. Like <ol><li>your desktop/laptop, where you need to remember logins for a variety of apps and services,</li> <li>in your browser (for web apps that require authentication), and</li> <li>on your mobile platforms (because most services you use via apps or browsers require authentication)</li> </ol></li> <li>It needs to be cross platform <ol><li>must support Windows, MacOS, and Linux OSs,</li> <li>must support extensions for many browsers like Firefox, Chrome/<a href="https://chromium.org" title="The open source project that underlies Chrome and is 99% identical.">Chromium</a>, Safari, and others, and</li> <li>must support mobile OSs like iOS and Android.</li> </ol></li> <li>It needs to sync data in a timely manner among all the different contexts in which a given user needs it.</li> </ol><p>That's a lot of requirements. There're quite a few efforts that have had a crack at solving this.</p> <p>The <a href="https://www.keepassx.org/">KeePassX</a> community has been addressing this for ages and has created a comprehensive (if variable) ecosystem of apps which work across all of the required platforms, but only with a lot of work.</p> <p>In the proprietary world, there're many options, with a few front runners like 1Password and <a href="https://lastpass.com">LastPass</a>. The former doesn't work on Linux, so it only gets a passing reference and no link :) (update 2019-05-31 - <a href="https://1password.com">1Password</a> has added Linux support). The latter, which I used (grudgingly, mostly because I couldn't get KeePassX to work for me) for a few years, works across all the platforms relevant to me, but it was becoming progressively more invasive and annoying to use. Also, because it has a <em>lot</em> of users, and stores everything (albeit, encrypted) in a centralised cloud repository, it's a <em>big target</em>. Also, with its largely proprietary code, I wasn't happy trusting it. </p> <p>Then I heard about <a href="https://bitwarden.com">BitWarden</a>. They offered a commercial service (with a free tier) that I could quickly try... they supported all the OSs, mobile and desktop, and browsers that I use... <em>and they release their entire codebase </em>(server and clients) <em><strong>under open source licenses.</strong></em> I tried it, it worked for me, I was sold!</p> <p><strong>Update 2020-12-20</strong>: here's a <a href="https://kevq.uk/are-password-managers-worth-it/">nice explanation of why you'd want a password manager</a> and even <a href="https://kevq.uk/bitwarden-an-open-source-alternative-to-lastpass/">a comparison between widely used (proprietary) LastPass and (open source) BitWarden</a>. People reading this might also be interested in learning <a href="https://kevq.uk/how-websites-check-your-password/">how websites check your password.<em>.. without storing a copy of your password</em></a>! Thanks for providing your CC-BY-SA licensed works for us all Kev!</p> <p>Then I decided I wanted to run my own BitWarden server, rather than use their commercial centralised cloud platform (because, as with LastPass, it's a tempting target). That's when I found out the server of BitWarden was written using Microsoft technologies, C# (yeah, it's mostly open source, but it's dirty to me due to its Microsoft legacy), and MS SQL Server, which is a nasty proprietary dependency (especially given how basic the database requirements for this sort of application are).</p> <p>So I was devastated that I couldn't set up my own server without compromising my iron-clad anti-Microsoft position (I've managed to maintain it for the past 25 years)... until another Free and Open Source Software aficionado pointed me at Daniel Garcia's work! Daniel has implemented a <a href="https://github.com/dani-garcia/bitwarden_rs">full (unofficial) BitWarden work-alike using a fully FOSS stack</a>: the Rust language, storing data in SQLite, and (quite thoughtfully) re-using other open source licensed components of the BitWarden system that don't have proprietary dependencies, including the website code and layout (which is part of the server). (he calls the server he's developed <a href="https://github.com/dani-garcia/vaultwarden">VaultWarden</a> to distinguish it from the BitWarden code base. The front-end BitWarden apps talk to VaultWarden the same way!)</p> <p>Daniel's server implementation also unlocks all the "premium" services that BitWarden offers through their hosted service, too... so that's a nice bonus.</p> <p>Another open source developer, <a href="https://github.com/mprasil">mpasil</a>, has created <a href="https://github.com/mprasil/bitwarden_rs">a "fork" of Daniel's project</a> from which he maintains an up-to-date Docker container on hub.docker.com. <strong>Thanks to both Daniel Garcia and mpasil's efforts</strong>, it turns out to be quite straightforward to set up your own Docker-based BitWarden-compatible service!</p> <h2>Creating your own BitWarden Service</h2> <h3>Set up a Virtual Server</h3> <p>The first step is to get yourself an entry-level virtual server or compute instance somewhere. I generally use DigitalOcean (I have no affiliation with the company), but there are many other commodity hosting services (check out Vultr or Linode, for example) around the world which offer comparably (or better) spec'd servers for <strong>USD5.00/month</strong>, or <strong>USD60.00/year</strong> - I encourage you to do a bit of research. For that you get a Gigabyte (GB) of RAM, a processor, and 40GB of SSD (Static Storage Device = faster) storage. That's <em>oodles</em> of grunt for what this application requires.</p> <p>I suggest you create an account for yourself (and I encourage you to use Two Factor Authentication, aka 2FA) and create an Ubuntu 18.04 (or the most recent LTS version - the next will be 20.04, in April 2020 :) ) in the zone nearest to you. You'll need to note the server's IP address (it'll be a series of 4 numbers, 0-254, separated by full stops, e.g. 103.99.72.244). With that, you can <a href="https://www.digitalocean.com/community/tutorials/how-to-use-ssh-to-connect-to-a-remote-server-in-ubuntu">log into it via SSH</a>.</p> <h3>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 <a href="https://metaname.net">Metaname</a> (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>If you want to use your domain for other things besides your BitWarden instance, I'd encourage you to use a <em>subdomain</em>, like (my usual choice) is "safe.domainname", namely the subdomain "safe" of "domainname".</p> <p>Once you have selected and registered your domain, you can set up (usually through a web interface provided by the registrar) an "A Record" which associates your website's name to the IP address of your server. So you should just be able to enter your server's IP address, the domain name (or sub-domain) you want to use for your BitWarden service, and that's it. For a password safe, I tend to use the subdomain "safe", so, for example, safe.mydomain.nz or similar.</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>You should be able to test that your A Record has been set correctly by SSHing to your domain name rather than the IP address. It should (after you accept the SSH warning that the server's name has changed) work the same way your original SSH login did.</p> <h3>Set up a Docker Server</h3> <p>Once I've first logged into it as the "root" (full admin) user, here's what I usually do:</p> <ol><li>I create an "unprivileged user", either with my name "dave" or sometimes an "ubuntu" user (some hosting providers create a default unprivileged user of "ubuntu" when you create an Ubuntu-based virtual machine. Some create a "debian" user for Debian-based VMs, etc.) via<br /><code>adduser ubuntu</code></li> <li>I install a few core applications: my preferred editor <a href="https://en.wikipedia.org/wiki/Vim_(text_editor)">vim</a> (<a href="https://en.wikipedia.org/wiki/GNU_nano">nano</a> is another easy option and comes pre-installed on Ubuntu), version control system, <a href="https://en.wikipedia.org/wiki/Git">git</a>, and a very handy configuration tracker, <a href="http://joeyh.name/code/etckeeper/">etckeeper</a>:<br /><code>apt-get update &amp;&amp; apt-get install vim git etckeeper</code></li> <li>I do some basic configuration of git (replace the [tokens] with the real values for you, minus the []):<br /><code>git config --global user.email "[your email]"<br /> git config --global user.name "[your full name, e.g. Jane Doe]"</code></li> <li>Initialise etckeeper - it will track configuration changes you make to your system which can be invaluable in replicating a server or working out what's changed if something breaks.<br /><code>etckeeper init<br /> etckeeper commit -m "initial commit of BitWarden host"</code></li> <li>Install Docker dependencies:<br /><code>apt-get install apt-transport-https ca-certificates curl software-properties-common pwgen</code><br /> Install secure key needed to add the docker.com package repository to your system<br /><code>curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -</code><br /> Confirm the key is valid<br /><code>apt-key fingerprint 0EBFCD88</code><br /> (you should see something like "<code>uid [ unknown] Docker Release (CE deb) &lt;docker@docker.com&gt;</code>" among the 4 lines)</li> <li>Add the repository for your Ubuntu version (this will pick it automatically)<br /><code>add-apt-repository    "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"</code></li> <li>Update the package repository to include the packages from docker.com<br /><code>apt-get update</code></li> <li>Install the Community Edition of the Docker service<br /><code>apt-get install docker-ce</code></li> <li>Add your unprivileged user ("ubuntu" in this case - substitute the unprivileged user you created!) to a new "docker" group and add that user to other useful groups:<br /><code>groupadd docker<br /> adduser ubuntu<br /> adduser ubuntu sudoers<br /> adduser ubuntu admin</code><br /><code>adduser ubuntu docker</code></li> <li>Create an SSH key for your unprivileged user and allow logins for that user from external connection:<br /><code>sudo -Hu ubuntu ssh-keygen -t rsa<br /> cp /root/.ssh/authorized_keys /home/ubuntu/.ssh/<br /> chown ubuntu:ubuntu /home/ubuntu/.ssh/<br /> adduser ubuntu ssh</code></li> <li>Install the Python packaging system, "pip" to allow you to install and maintain the Docker Compose framework for managing collections of Docker containers:<br /><code>apt install python-pip<br /> pip install -U pip<br /> pip install docker-compose</code></li> <li>Set a convenience variable for [your domain] here (note: it'll only be recognised for this session, i.e. until you log out):<br /><code>DOMAIN=[your domain]<br /> USER=[unprivileged user, e.g. ubuntu]</code><br /> Below, anytime you see $DOMAIN in a command, it'll be replaced by whatever you put in for [your domain] and similarly $USER...</li> <li>Create directories to hold both the Docker Compose configurations and the persistent data you don't want to lose if you remove your Docker containers (namely your password database and configuration information):<br /><code>mkdir -p /home/docker/$DOMAIN &amp;&amp; mkdir -p /home/data/$DOMAIN<br /> chown -R ${USER}:${USER} /home/data /home/docker/</code></li> <li>Install the NGINX (pronounced "Engine X") webserver which will act as a reverse proxy for the BitWarden service and terminate the encryption via HTTPS:<br /><code>apt-get install nginx-full</code></li> <li>Configure the server's firewill and make an exception for SSH and NGINX services<br /><code>ufw allow OpenSSH<br /> ufw allow "Nginx Full"<br /> ufw enable</code><br /> Check that its running via<br /><code>ufw status</code></li> <li>Create a directory for including files for NGINX<br /><code>cd /etc/nginx</code><br /><code>mkdir includes</code><br /> Choose your text editor for editing files. Here're options for Vim or Nano - you can install and select others. Setting the EDIT shall variable allows you to copy and paste these commands regardless of which editor you prefer as it'll replace the value of $EDIT with the full path to your preferred editor.<br /><code>EDIT=`which nano`</code> or <code>EDIT=`which vim`</code></li> <li>To support encrypted data transfer between external devices and your server using HTTPS,  you need a valid SSL certificate. Until recently, these were costly and hard to get. With <a href="/protecting-your-users-lets-encrypt-ssl-certs">Let's Encrypt</a>, they've become a straightforward and essential part of any good (user-respecting) web site or service. To facilitate getting and periodically renewing your SSL certificate, you need to create the file letsencrypt.conf:<br /><code>$EDIT includes/letsencrypt.conf</code><br /> and enter the following content: <p><blockcode><code>#############################################################################<br /> # Configuration file for Let's Encrypt ACME Challenge location<br /> # This file is already included in listen_xxx.conf files.<br /> # Do NOT include it separately!<br /> #############################################################################<br /> #<br /> # This config enables to access /.well-known/acme-challenge/xxxxxxxxxxx<br /> # on all our sites (HTTP), including all subdomains.<br /> # This is required by ACME Challenge (webroot authentication).<br /> # You can check that this location is working by placing ping.txt here:<br /> # /var/www/letsencrypt/.well-known/acme-challenge/ping.txt<br /> # And pointing your browser to:<br /> # http://xxx.domain.tld/.well-known/acme-challenge/ping.txt<br /> #<br /> # Sources:<br /> # https://community.letsencrypt.org/t/howto-easy-cert-generation-and-renewal-with-nginx/3491<br /> #<br /> # Rule for legitimate ACME Challenge requests<br /> location ^~ /.well-known/acme-challenge/ {<br />     default_type "text/plain";<br />     # this can be any directory, but this name keeps it clear<br />     root /var/www/letsencrypt;<br /> }<br /> # Hide /acme-challenge subdirectory and return 404 on all requests.<br /> # It is somewhat more secure than letting Nginx return 403.<br /> # Ending slash is important!<br /> location = /.well-known/acme-challenge/ {<br />     return 404;<br /> }</code></blockcode></p> </li> <li> <p>Now you need to create the directory described in the letsencrypt.conf file:<br /><code>mkdir /var/www/letsencrypt</code></p> </li> <li> <p>Create "<a href="https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html#Forward_Secrecy_&amp;_Diffie_Hellman_Ephemeral_Parameters">forward secrecy &amp; Diffie Hellman ephemeral parameters</a>" to make your server more secure... The result will be a secure signing key stored in <code>/etc/ssl/certs/dhparam.pem</code> (note, getting enough "entropy" to generate sufficient randomness to calculate this will take a few minutes!<code>):</code><br /><code>openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096</code></p> </li> <li> <p>and then you need to create the reverse proxy configuration file as follows:<br /><code>cd ../sites-available</code><br /><br /> and fill it with this content, replacing all [tokens] with your relevant values:<br /><blockcode><code>#<br /> # HTTP does *soft* redirect to HTTPS<br /> #<br /> server {<br />     # add [IP-Address:]80 in the next line if you want to limit this to a single interface<br />     listen 0.0.0.0:80;<br />     </code></blockcode><blockcode><code>server_name [your domain];<br />     root /home/data/[your domain];<br />     index index.php;<br /><br />     # change the file name of these logs to include your server name<br />     # if hosting many services...<br />     access_log /var/log/nginx/[your domain]_access.log;<br />     error_log /var/log/nginx/[your domain]_error.log;  <br />     include includes/letsencrypt.conf;</code></blockcode><blockcode><br /><br /><code>    # redirect all HTTP traffic to HTTPS.<br />     location / {<br />         return  302 https://[your domain]$request_uri;<br />     }<br /> }</code></blockcode><br /> and make the configuration available to NGINX by linking the file from sites-available into sites-enabled (you can disable the site by removing the link and reloading NGINX)<br /><code>ln -sf sites-available/bitwarden sites-enabled/bitwarden</code><br /> Check to make sure NGINX is happy with the configuration<br /><code>nginx -t </code><br /> If you don't get any errors, you can restart NGINX<br /><code>service nginx restart</code><br /> and it should be configured properly to respond to requests at <code>http://[your domain]/.well-known/acme-challenge/ </code>which is required for creating a Let's Encrypt certificate.<br /><code>$EDIT sites-available/bitwarden</code></p> </li> <li> <p>So now we can create the certificate. You'll need to install the letscencrypt scripts:<br /><code>apt-get install letsencrypt</code><br /> You will be asked to enter some information about yourself, including an email address - this is necessary so that the letsencrypt service can email you if any of your certificates are not successfully updated (they need to be renewed every few weeks - normally this happens automatically!) so that you site and users aren't affected by an expired SSL certificate (a bad look!). Trust me, these folks are the good guys.<br /> You create a certificate for [your domain] with the following command (with relevant substitutions):<br /><code>letsencrypt certonly --webroot -w /var/www/letsencrypt -d $DOMAIN</code><br /> If the process works, you should see a "Congratulations!" message.</p> </li> <li> <p>Edit the nginx configuration file for the BitWarden service again<br /><code>$EDIT sites-available/bitwarden</code><br /> and add the following to the bottom of <code>file (starting the line below the final "}")<br /><blockcode>#<br /> # HTTPS<br /> #<br /> # This assumes you're using Let's Encrypt for your SSL certs (and why wouldn't<br /> # you!?)... https://letsencrypt.org<br /> server {<br />     # add [IP-Address:]443 ssl in the next line if you want to limit this to a single interface<br />     listen 0.0.0.0:443 ssl;<br />     ssl on;<br />     ssl_certificate /etc/letsencrypt/live/[your domain]/fullchain.pem;<br />     ssl_certificate_key /etc/letsencrypt/live/[your domain]/privkey.pem;<br />     ssl_protocols TLSv1 TLSv1.1 TLSv1.2;<br />     # to create this, see https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html<br />     ssl_dhparam /etc/ssl/certs/dhparam.pem;<br />     keepalive_timeout 20s;</blockcode></code><blockcode></blockcode><blockcode><br /><code>    server_name [your domain];<br />     root /home/data/[your domain];<br />     index index.php;</code></blockcode><blockcode><br /><code>    # change the file name of these logs to include your server name<br />     # if hosting many services...<br />     access_log /var/log/nginx/[your domain]_access.log;<br />     error_log /var/log/nginx/[your domain]_error.log;</code></blockcode><blockcode><br /><br /><code>    location /notifications/hub/negotiate {<br />         proxy_pass http://127.0.0.1:8080;<br />         proxy_set_header Upgrade $http_upgrade;<br />         proxy_set_header Connection "upgrade";<br />         proxy_set_header Host $http_host;<br />         proxy_set_header X-Real-IP $remote_addr;<br />         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;<br />         proxy_set_header X-Forwarded-Host $server_name;<br />         proxy_set_header X-Forwarded-Proto https;<br />         proxy_connect_timeout 2400;<br />         proxy_read_timeout 2400;<br />         proxy_send_timeout 2400;<br />     }</code></blockcode><blockcode><br /><code>    location / {<br />         proxy_pass http://127.0.0.1:8080;<br />         proxy_set_header Upgrade $http_upgrade;<br />         proxy_set_header Connection "upgrade";<br />         proxy_set_header Host $http_host;<br />         proxy_set_header X-Real-IP $remote_addr;<br />         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;<br />         proxy_set_header X-Forwarded-Host $server_name;<br />         proxy_set_header X-Forwarded-Proto https;<br />         proxy_connect_timeout 2400;<br />         proxy_read_timeout 2400;<br />         proxy_send_timeout 2400;<br />     }</code></blockcode><blockcode><br /><code>    location /notifications/hub {<br />         proxy_pass http://127.0.0.1:3012;<br />         proxy_set_header Upgrade $http_upgrade;<br />         proxy_set_header Connection "upgrade";<br />     }<br />     #<br />     # These "harden" your security<br />     add_header 'Access-Control-Allow-Origin' "*";<br /> }</code></blockcode></p> </li> <li>You should now be able to run<br /><code>nginx -t </code><br /> again, and it you haven't got an accidental errors in the files, it should return no errors. You can restart nginx to make sure it picks up your SSL certificates...<br /><code>service nginx restart</code></li> </ol><p>Now everything is read to set up your BitWarden Docker containers!</p> <h3>Setting up your BitWarden "rust" service</h3> <p>Before we start this part, you'll need a few bits of information. First, you'll need a 64 character random string to be your "admin token"... you can create that like this:<br /><code>pwgen -y 64 1</code></p> <p>copy the result (highlight the text and hit CTRL+SHIFT+C) and paste it somewhere so you can copy-and-paste it into the file below later.</p> <p>Also, if you want your BitWarden server to be able to send out emails, like for password recovery, you'll need to have an "authenticating SMTP email account"... I would recommend setting one up specifically for this purpose. You can use a random gmail account or any other email account that lets you send mail by logging into an SMTP (Simple Mail Transfer Protocol) server, i.e. most mail servers. You'll need to know the SMTP [host name], the [port] (usually 465 or 587), the [login security] (usually "true" or "TLS"), and your authenticating [username] (possibly this is also the email address) and [password]. You'll also need a "[from email] like bitwarden@[your domain] or similar, which will be the sender of email from your server.</p> <p>You're going to be setting up your configuration in the directory we created earlier, so run<br /><code>cd /home/docker/$DOMAIN</code></p> <p>and there<br /><code>$EDIT docker-compose.yml</code></p> <p>copy-and-pasting in the following, replacing the [tokens] appropriately:</p> <p><blockcode><code>version: "3"<br /> services:<br />     app:<br />         image: vaultwarden/server<br />         environment:<br />             - DOMAIN=https://[your domain]<br />             - WEBSOCKET_ENABLED=true<br />             - SIGNUPS_ALLOWED=false<br />             - LOG_FILE="/data/bitwarden.log"<br />             - INVITATIONS_ALLOWED=true<br />             - ADMIN_TOKEN=[admin token]<br />             - SMTP_HOST=[host name]<br />             - SMTP_FROM=[from email]<br />             - SMTP_PORT=[port]<br />             - SMTP_SSL=[login security]<br />             - SMTP_USERNAME=[username]<br />             - SMTP_PASSWORD=[password]<br />         volumes:<br />             - /home/data/[your domain]/data/:/data/<br />         ports:<br />             - "127.0.0.1:8080:80"<br />             - "127.0.0.1:3012:3012"<br />         restart:<br />             unless-stopped</code></blockcode></p> <p><em>Note that the indentation has to be exact in this file - Docker Compose will complain otherwise.</em></p> <p>With the docker-compose file completed, you're ready to "pull" your package!</p> <p><code>docker-compose pull</code></p> <p>This will download the BitWarden Docker container from hub.docker.com. Then all you need to do is start it:</p> <p><code>docker-compose up -d &amp;&amp; docker-compose logs -f</code></p> <p>the "up -d" option actually starts the container called "app" which is actually your BitWarden rust server in "daemon" mode, which means it'll keep running unless you tell it to stop. If that's successful, it automatically then shows you the logs of that container. You can exit at any time with CTRL-C which will put you back on the command prompt. If you <em>do</em> want the container to stop, just run</p> <p><code>docker-compose stop</code></p> <p>If your start up was successful, you should see a message like this (albeit your version number could be higher - 1.9.0 is the current version of the Rust implementation at the time of writing):</p> <p><blockcode><code>/--------------------------------------------------------------------\<br /> |                       Starting Bitwarden_RS                        |<br /> |                           Version 1.9.0                            |<br /> |--------------------------------------------------------------------|<br /> | This is an *unofficial* Bitwarden implementation, DO NOT use the   |<br /> | official channels to report bugs/features, regardless of client.   |<br /> | Report URL: https://github.com/dani-garcia/bitwarden_rs/issues/new |<br /> \--------------------------------------------------------------------/</code></blockcode></p> <p>You should now be able to point your browser at <code>http://[your domain]</code> which, in turn, should automatically redirect you to <code><strong>https://</strong>[your domain]</code> and you should see the BitWarden web front end similar to that shown in the attached screen shot!</p> <h3>First Login!</h3> <p>To do your initial login by going to <code><strong>https://</strong>[your domain]<strong>/admin/</strong> and</code> you'll be asked to provide your "admin token" (a random string you created earlier for your docker-compose.yml file, where you should be able to find it) to create a first user with administration privileges. That will allow you to create your initial personal user and other useful stuff.</p> <p>For additional info on setting up these services - and new options as Daniel and his co-developers add them in - consult the <a href="https://github.com/dani-garcia/bitwarden_rs">repository pages</a> and <a href="https://github.com/dani-garcia/bitwarden_rs/issues">issues</a> and for Docker-specific questions, look at <a href="https://github.com/mprasil/bitwarden_rs">mpasil's pages</a>.</p> <h3>Sending Emails</h3> <p>It'll be worth testing if your email services work, like by requesting a password hint! You should be able to see what the server's doing via the</p> <p><code>docker-compose logs -f</code></p> <h2>Tips</h2> <p>I recommend <em>not </em>including your login credentials to your BitWarden instance in your BitWarden database ;) that's the one thing you need to remember. If you need to write it down somewhere, then do so (but make sure you don't include <em>all</em> the info needed to log in on the same piece of paper, that's just asking for trouble).</p> <p>Also, you can easily configure all the BitWarden clients - browser plugins, mobile apps, or the desktop app -  to use your server rather than BitWarden's default hosted service. Just click the "gear" settings icon on each app's interface, and set the "Self-Hosted Environment" Server URL to be your server, i.e. https://[your domain]</p> <h3>Backing it all up</h3> <p>I've created a SQLite backup script (which maintains automatic versioned hourly, daily, weekly, monthly, and yearly database dumps, the content in which is encrypted) <a href="/automatic-versioned-backups-sqlite-docker-compose-container">described in more detail in another post</a>...</p> <h3>Two Factor Authentication</h3> <p>This configuration should allow you to simply turn on Two Factor Authentication for any given BitWarden user.</p> <h3>Keeping it up-to-date</h3> <p>One of the best things about this Docker configuration is that it's very straightforward to upgrade your installation to Daniel's (via mpasil's Docker work) latest server version. Just log into the server as your unprivileged user,</p> <p><code>cd /home/docker/[your domain]<br /> docker-compose  pull<br /> docker-compose up -d &amp;&amp; docker-compose logs -f</code></p> <p>The whole process shouldn't take much more than a minute, with a few seconds downtime only as your new Docker BitWarden container is being created...</p> <p>Hope this helps a few folks! If you find any of the above doesn't work, please let me know in the comments. I'll do my best to make sure this how-to is accurate and up-to-date, and I'll do my best to assist people if they're having trouble.</p> <p>Have (secure and private) fun!</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-164" about="/comment/164" 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/164#comment-164" class="permalink" rel="bookmark" hreflang="en">Excellent blog</a> <span class="comment__new marker marker--success hidden" data-comment-timestamp="1571734038"></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="">cocoonkid (not verified)</span></span> </span> <span class="comment__pubdate">Wed 09/10/2019 - 00:19 <span property="schema:dateCreated" content="2019-10-08T11:19:00+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>Really appreciate the document. </p> <p>Would love to see these too:</p> <p>To do your initial login, I believe (I&#039;ll test this and update this howto!) you&#039;ll be asked to provide your &quot;admin token&quot; to create a first user with administration privileges.</p> <p>&amp;</p> <p>I&#039;ll add information on my SQLite backup scripts (which maintain automatic versioned hourly, daily, weekly, monthly, and yearly database dumps, the content in which is encrypted)...</p> <p>Thanks!</p> </div> </div> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=164&amp;1=default&amp;2=en&amp;3=" token="SQxJH8_hmUFGP5a1HuPe5YGDeqKAH7IJWa-lrTf5AVs"></drupal-render-placeholder> </div> </div> </article> <div class="indented"><article data-comment-user-id="1" id="comment-167" about="/comment/167" 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/167#comment-167" class="permalink" rel="bookmark" hreflang="en">Fair call! Leave it with me…</a> <span class="comment__new marker marker--success hidden" data-comment-timestamp="1571734079"></span> </h3> <div class="comment__meta"> <div class="comment__submitted"> <span class="comment__author"><span rel="schema:author"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> </span> <span class="comment__pubdate">Tue 22/10/2019 - 21:47 <span property="schema:dateCreated" content="2019-10-22T08:47:59+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/164#comment-164" class="permalink" rel="bookmark" hreflang="en">Excellent blog</a> by <span lang="" typeof="schema:Person" property="schema:name" datatype="">cocoonkid (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>Fair call! Leave it with me...</p> </div> </div> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=167&amp;1=default&amp;2=en&amp;3=" token="5EsOUo5fz0dhfO2bRk_l-ywwj6OH5l8p9nz86QNDu_A"></drupal-render-placeholder> </div> </div> </article> <div class="indented"><article data-comment-user-id="1" id="comment-342" about="/comment/342" 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/342#comment-342" class="permalink" rel="bookmark" hreflang="en">Backup script update...</a> <span class="comment__new marker marker--success hidden" data-comment-timestamp="1581631657"></span> </h3> <div class="comment__meta"> <div class="comment__submitted"> <span class="comment__author"><span rel="schema:author"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> </span> <span class="comment__pubdate">Fri 14/02/2020 - 11:07 <span property="schema:dateCreated" content="2020-02-13T22:07:37+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/167#comment-167" class="permalink" rel="bookmark" hreflang="en">Fair call! Leave it with me…</a> by <span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</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>I've published a new post which describes the SQLite backup script and points to the publicly visible git repo, linked in the article now. I've also updated the question of the admin code...</p> </div> </div> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=342&amp;1=default&amp;2=en&amp;3=" token="ukuAKkHDAuErmOn-ice3ptjHTOoqGV13cGbtjg82D9k"></drupal-render-placeholder> </div> </div> </article> </div></div><article data-comment-user-id="0" id="comment-788" about="/comment/788" 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/788#comment-788" class="permalink" rel="bookmark" hreflang="en">Thank You!</a> <span class="comment__new marker marker--success hidden" data-comment-timestamp="1599790208"></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="">SS (not verified)</span></span> </span> <span class="comment__pubdate">Tue 08/09/2020 - 07:06 <span property="schema:dateCreated" content="2020-09-07T19:06:51+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>This is the best tutorial I&#039;ve come across for getting BitWarden up and running on a Pi.</p> <p>It worked the first time through. There a couple nice things that aren&#039;t obvious to the newbie that I tracked down after everything was up and running. Setting a docker restart policy for the BW container that ensures that the container comes up after a power interruption was a pleasant surprise. </p> <p>One thing that isn&#039;t clear to the absolute newbie is that it&#039;s probably better to set up on a subdomain rather than a domain if you want to use NGINX for serving other things. I wasn&#039;t thinking about that when I did the initial setup, and that&#039;s leading to rework... Ideally, I&#039;d like to have BitWarden sitting on a nonobvious subdomain, and serve up other things on the obvious ones, like the bare domain or <a href="http://www.example.xyz">www.example.xyz</a>.</p> <p>Again, thanks for a great post.</p> </div> </div> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=788&amp;1=default&amp;2=en&amp;3=" token="9n0zD1HbCjrKLmdvYGdyrEz07IZBRPpAphuhUIdiciQ"></drupal-render-placeholder> </div> </div> </article> <div class="indented"><article data-comment-user-id="1" id="comment-793" about="/comment/793" 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/793#comment-793" class="permalink" rel="bookmark" hreflang="en">Use a subdomain!</a> <span class="comment__new marker marker--success hidden" data-comment-timestamp="1599792389"></span> </h3> <div class="comment__meta"> <div class="comment__submitted"> <span class="comment__author"><span rel="schema:author"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> </span> <span class="comment__pubdate">Fri 11/09/2020 - 14:46 <span property="schema:dateCreated" content="2020-09-11T02:46:29+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/788#comment-788" class="permalink" rel="bookmark" hreflang="en">Thank You!</a> by <span lang="" typeof="schema:Person" property="schema:name" datatype="">SS (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>Thanks for the suggestion, SS - I've added a note regarding using subdomains! And thanks for the positive feedback - hope your BitWarden is providing you with what you need!</p> </div> </div> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=793&amp;1=default&amp;2=en&amp;3=" token="mDd3QaMURnHddjJwNSFd8gkASpIOnAaSZq2qA6Z7QIM"></drupal-render-placeholder> </div> </div> </article> </div><article data-comment-user-id="0" id="comment-791" about="/comment/791" 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/791#comment-791" class="permalink" rel="bookmark" hreflang="en">Login</a> <span class="comment__new marker marker--success hidden" data-comment-timestamp="1599790281"></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="">Daniel Lamb (not verified)</span></span> </span> <span class="comment__pubdate">Fri 11/09/2020 - 04:20 <span property="schema:dateCreated" content="2020-09-10T16:20:37+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>Hello,<br /> sorry might be being thick here but trying to login after follwing the setup guide and dont know what I should put in the username, I am using the token generated and in the yaml file.</p> <p>Daniel</p> </div> </div> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=791&amp;1=default&amp;2=en&amp;3=" token="2-gy49yCLhcMgXJLWSm-gmP574jf4pcpE4YajzKnBZ8"></drupal-render-placeholder> </div> </div> </article> <div class="indented"><article data-comment-user-id="1" id="comment-792" about="/comment/792" 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/792#comment-792" class="permalink" rel="bookmark" hreflang="en">First login...</a> <span class="comment__new marker marker--success hidden" data-comment-timestamp="1599792248"></span> </h3> <div class="comment__meta"> <div class="comment__submitted"> <span class="comment__author"><span rel="schema:author"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> </span> <span class="comment__pubdate">Fri 11/09/2020 - 14:44 <span property="schema:dateCreated" content="2020-09-11T02:44:08+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/791#comment-791" class="permalink" rel="bookmark" hreflang="en">Login</a> by <span lang="" typeof="schema:Person" property="schema:name" datatype="">Daniel Lamb (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>Hi Daniel,</p> <p>Sorry that I wasn't clear - to be honest, I'm not sure what the latest versions use for the first login, but from memory, the very first visitor to a new BitWarden site sees a special login form asking them to enter their Admin Token which then allows them to set up their normal user login details (an email address and passphrase)... If you <em>don't </em>see something like that, then I would expect you could remove the Sqlite db file forcing a reinitialisation of the system.</p> </div> </div> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=792&amp;1=default&amp;2=en&amp;3=" token="vah-Jl2qUpIYJyQ7onBYSC8CaaSRZgWoiMkqd4n-2B4"></drupal-render-placeholder> </div> </div> </article> </div><article data-comment-user-id="0" id="comment-797" about="/comment/797" 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/797#comment-797" class="permalink" rel="bookmark" hreflang="en">Can you update this for…</a> <span class="comment__new marker marker--success hidden" data-comment-timestamp="1607799359"></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="">Terry (not verified)</span></span> </span> <span class="comment__pubdate">Sun 13/12/2020 - 04:54 <span property="schema:dateCreated" content="2020-12-12T15:54:15+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>Can you update this for Ubuntu 20.xx and Python3? I&#039;ve tried to deploy, and failed.</p> </div> </div> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=797&amp;1=default&amp;2=en&amp;3=" token="JNMITOrOBjpIJ84n2lKVRIjo67jxAlzBDQN4oiaLke8"></drupal-render-placeholder> </div> </div> </article> <div class="indented"><article data-comment-user-id="1" id="comment-798" about="/comment/798" 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/798#comment-798" class="permalink" rel="bookmark" hreflang="en">Hi Terry, If you want to…</a> <span class="comment__new marker marker--success hidden" data-comment-timestamp="1607807686"></span> </h3> <div class="comment__meta"> <div class="comment__submitted"> <span class="comment__author"><span rel="schema:author"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> </span> <span class="comment__pubdate">Sun 13/12/2020 - 10:14 <span property="schema:dateCreated" content="2020-12-12T21:14:46+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/797#comment-797" class="permalink" rel="bookmark" hreflang="en">Can you update this for…</a> by <span lang="" typeof="schema:Person" property="schema:name" datatype="">Terry (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>Hi Terry,</p> <p>If you want to use Python3 for Docker Compose, it's easy enough (although it's confusing depending on which distro you're running and whether Python3 is default or has to be designated as <code>python3</code> ... assuming you've got Python3 already installed, you can install pip via <code>sudo apt-get install python3-pip</code> and then you might need to use "pip3" installed by the package manager to install "pip"... e.g. <code>pip3 install pip</code> via the python package (which is independent of your package manager). You should then be able to install and update docker-compose via pip, e.g. <code>sudo pip install docker-compose</code> or, to upgrade, <code>sudo pip install -U docker-compose</code>. And if you want to upgrade pip itself, if you've previously installed pip via pip3, you can use <code>sudo pip install -U pip</code>. It's tricky when you have distro-managed installs and python-installed stuff, but hopefully you can get to the bottom of it - might require a bit of fiddling depending precisely which version/distro of Linux you're using.</p> </div> </div> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=798&amp;1=default&amp;2=en&amp;3=" token="WTcKgRcmhSZALC_EZySMiwlOW0eOH36nw2wInHSL268"></drupal-render-placeholder> </div> </div> </article> <div class="indented"><article data-comment-user-id="0" id="comment-799" about="/comment/799" 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/799#comment-799" class="permalink" rel="bookmark" hreflang="en">Turns out it wasn&#039;t anything…</a> <span class="comment__new marker marker--success hidden" data-comment-timestamp="1607972415"></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="">Terry (not verified)</span></span> </span> <span class="comment__pubdate">Tue 15/12/2020 - 01:20 <span property="schema:dateCreated" content="2020-12-14T12:20:22+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/798#comment-798" class="permalink" rel="bookmark" hreflang="en">Hi Terry, If you want to…</a> by <span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</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>Turns out it wasn&#039;t anything to do with python3 or pip, I had previously managed to have them installed &amp; working properly.<br /> Looks like it was something with the docker images host on the weekend when I was doing this as the docker pull command always errored out. I just did it now (Monday morning) and the pull worked, and docker up was successful.<br /> Thanks for the great tutorial!</p> </div> </div> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=799&amp;1=default&amp;2=en&amp;3=" token="aGMZHpXsn6-Wvj9zGwd7njwt44jqKGsy2xJDZOP41CI"></drupal-render-placeholder> </div> </div> </article> <div class="indented"><article data-comment-user-id="1" id="comment-800" about="/comment/800" 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/800#comment-800" class="permalink" rel="bookmark" hreflang="en">I&#039;m pleased to hear it all…</a> <span class="comment__new marker marker--success hidden" data-comment-timestamp="1607972457"></span> </h3> <div class="comment__meta"> <div class="comment__submitted"> <span class="comment__author"><span rel="schema:author"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> </span> <span class="comment__pubdate">Tue 15/12/2020 - 08:00 <span property="schema:dateCreated" content="2020-12-14T19:00:57+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/799#comment-799" class="permalink" rel="bookmark" hreflang="en">Turns out it wasn&#039;t anything…</a> by <span lang="" typeof="schema:Person" property="schema:name" datatype="">Terry (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>I'm pleased to hear it all worked out for you! Enjoy!</p> </div> </div> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=800&amp;1=default&amp;2=en&amp;3=" token="paAK9EdaOFVkj1k6yyTUH6vafn7J3iOShwHJ1ZKj7QU"></drupal-render-placeholder> </div> </div> </article> </div></div></div><article data-comment-user-id="0" id="comment-804" about="/comment/804" 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/804#comment-804" class="permalink" rel="bookmark" hreflang="en">we&#039;ve set up our own locally…</a> <span class="comment__new marker marker--success hidden" data-comment-timestamp="1616005887"></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="">Jeff (not verified)</span></span> </span> <span class="comment__pubdate">Thu 18/03/2021 - 04:21 <span property="schema:dateCreated" content="2021-03-17T15:21:57+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>we&#039;ve set up our own locally hosted bitwarden, &quot;oursite.bitwarden.com&quot; but want to either block or redirect our users from being able to inadvertently go to the main web-based bitwarden.com can you provide suggestions?</p> </div> </div> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=804&amp;1=default&amp;2=en&amp;3=" token="oRxs6opoJeCa_tsOx_ISwx8am50pKkA07oeZQsnUgzQ"></drupal-render-placeholder> </div> </div> </article> <div class="indented"><article data-comment-user-id="1" id="comment-805" about="/comment/805" 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/805#comment-805" class="permalink" rel="bookmark" hreflang="en">Interesting question... I&#039;m…</a> <span class="comment__new marker marker--success hidden" data-comment-timestamp="1616006000"></span> </h3> <div class="comment__meta"> <div class="comment__submitted"> <span class="comment__author"><span rel="schema:author"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> </span> <span class="comment__pubdate">Thu 18/03/2021 - 07:33 <span property="schema:dateCreated" content="2021-03-17T18:33:20+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/804#comment-804" class="permalink" rel="bookmark" hreflang="en">we&#039;ve set up our own locally…</a> by <span lang="" typeof="schema:Person" property="schema:name" datatype="">Jeff (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>Interesting question... I'm afraid I can't think of an easy way to achieve that... :/ I'd say the best thing is to create BW users for your users and install and configure their clients for them. That way, they won't be able to lot into the bitwarden.com service with the same credentials....</p> </div> </div> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=805&amp;1=default&amp;2=en&amp;3=" token="3iv1o5vcthvfWKPqQDTTZJ9Sh8SKnyT6WiOZm9hEpJg"></drupal-render-placeholder> </div> </div> </article> </div><article data-comment-user-id="0" id="comment-812" about="/comment/812" 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/812#comment-812" class="permalink" rel="bookmark" hreflang="en">Anybody who has the URL can…</a> <span class="comment__new marker marker--success hidden" data-comment-timestamp="1629771059"></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="">Philippe (not verified)</span></span> </span> <span class="comment__pubdate">Tue 24/08/2021 - 14:09 <span property="schema:dateCreated" content="2021-08-24T02:09:48+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>Anybody who has the URL can open an Bitwarden account on a self hosted installation. How do I control who can create a new account?</p> </div> </div> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=812&amp;1=default&amp;2=en&amp;3=" token="CTzzNjnBjtIR96MLtSMFqjSId0xTu7vR_0xyaxdkMto"></drupal-render-placeholder> </div> </div> </article> <div class="indented"><article data-comment-user-id="1" id="comment-813" about="/comment/813" 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/813#comment-813" class="permalink" rel="bookmark" hreflang="en">If you log in as an admin …</a> <span class="comment__new marker marker--success hidden" data-comment-timestamp="1629771146"></span> </h3> <div class="comment__meta"> <div class="comment__submitted"> <span class="comment__author"><span rel="schema:author"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> </span> <span class="comment__pubdate">Tue 24/08/2021 - 14:12 <span property="schema:dateCreated" content="2021-08-24T02:12:26+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/812#comment-812" class="permalink" rel="bookmark" hreflang="en">Anybody who has the URL can…</a> by <span lang="" typeof="schema:Person" property="schema:name" datatype="">Philippe (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>If you log in as an admin (use the /admin URL - the password is in your docker-compose.yml file) you can disable random sign-ups (e.g. and make it invite-only). You can also disable any existing accounts you don't want there.</p></div> </div> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=813&amp;1=default&amp;2=en&amp;3=" token="dbxstyS8y3OQ1Z6Vz7AIyPds-SIKszQNJDNpgzL4nHY"></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=25&amp;2=field_blog_comments&amp;3=comment" token="hKQtrXBA7ErwoC_B3-AO9Vd7GHqkXf0X-QAroc-dhuE"></drupal-render-placeholder> </div> </section> Fri, 20 Aug 2021 23:30:13 +0000 dave 25 at http://tech.oeru.org Configuring a Linux server to send email via the Postfix SMTP server using an external authenticating SMTP host http://tech.oeru.org/configuring-linux-server-send-email-postfix-smtp-server-using-external-authenticating-smtp-host <span class="field field--name-title field--type-string field--label-hidden">Configuring a Linux server to send email via the Postfix SMTP server using an external authenticating SMTP host</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--postfix"> <span class="field__item-wrapper"><a href="/taxonomy/term/66" hreflang="en">postfix</a></span> </div> <div class="field__item field__item--smtp"> <span class="field__item-wrapper"><a href="/taxonomy/term/67" hreflang="en">smtp</a></span> </div> <div class="field__item field__item--_804"> <span class="field__item-wrapper"><a href="/taxonomy/term/68" hreflang="en">18.04</a></span> </div> <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--foss"> <span class="field__item-wrapper"><a href="/taxonomy/term/10" hreflang="en">foss</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> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> <span class="field field--name-created field--type-created field--label-hidden">Mon 02/08/2021 - 14:08</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>Just about any and every server needs to be able to send email - whether it's end-user-email, like password recovery services for a website to emails to system administrators reporting on the status of system backups and errors. The problem is that it's <em>non trivial</em> (understatement) to set up a mail server properly.</p> <p>This howto assumes you have a Linux server (these instructions are for Ubuntu 18.04 and 20.04, although it should work on earlier versions of Ubuntu server and Debian Linux with minor changes, and the concepts will be very similar on other Linuxen) with a static IP address, with one or more fully-qualified-domain-names (fdqn) pointing at that address, and you have SSH-based access to it. I've <a href="/setting-your-own-bitwarden-password-keeper-and-sync-server">previously provided tips</a> on how to get to this stage.</p> <h2>Authenticating SMTP</h2> <p>To send email, you need access to a server, somewhere on the Internet, that provides the <a href="https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol">Simple Mail Transfer Protocol</a> (SMTP) service. It's an open standard, and for most of the history of the Internet, email services have been mostly provided by Free and Open Source Software (FOSS) tools - the first SMTP was called "<a href="https://en.wikipedia.org/wiki/Sendmail">Sendmail</a>" and it was fully FOSS, and it's still in use today (although it has mostly been superseded by faster, more secure systems, the best of which are also FOSS).</p> <p>At the OERu, we use the <a href="https://mailcow.github.io/mailcow-dockerized-docs/" title="Dockerised MailCow">Docker-based installation of the amazing, completely FOSS MailCow project</a> to provide our organisational email services. I might cover that set up in a future tutorial here, because MailCow makes an otherwise almost intractable problem - hosting your own email service - much more tractable. Having a MailCow set up means we can offer "full service" email for any number of domains and users and aliases with all the bells and whistles including incoming and outgoing mail with all the virus scanning (we don't really need it because we use Linux desktops, but for other folks it's useful) and dynamic spam filtering services you'd expect from a much larger operation: <a href="https://mailcow.email/">Team MailCow</a> have done an amazing job in pulling together a comprehensive set of FOSS applications to provide all the conceivable requirements of a full-fledged, multi-domain email system, including shared calendaring, contacts, and webmail. A great companion to your organisation's MailCow server would be a <a href="/setting-your-own-bitwarden-password-keeper-and-sync-server">BitWarden password safe</a> server (also FOSS)... just sayin'.</p> <p>So, now, assuming that we have a MailCow server or some other functionally equivalent SMTP service available (apparently you can <a href="https://support.google.com/a/answer/2956491">do this with Gmail</a>, if you're a paying using although because of Google's terms of use, we recommend finding a more trustworthy solution), we have the option of "authenticated SMTP" for outgoing email using credentials we can set up. For example, in MailCow, we can specify a domain we host, like say <strong>oeru.org</strong> (and for which we've defined an MX record and a few other relevant records as guided by MailCow administrative web interface). On top of that, we can specify a mailbox for a dedicated "send stuff from remote relay hosts" email address using that domain, like <strong><a href="mailto:smtp@oeru.org">smtp@oeru.org</a></strong>, with a strong password. With that, we can <em>securely </em>send email using that email address as the username and that password from <em>anywhere we have access to the Internet</em>.</p> <p>The <strong>only tricky part</strong> is that we have to ensure that whatever "reply to" email address we specify from our applications, say <strong><a href="mailto:notifications@tech.oeru.org">notifications@tech.oeru.org</a></strong>, is using a domain we <em>also host on the same server, </em>and that there's an <em>email alias</em> of that email address defined and set as "allow to send from <a href="mailto:smtp@oeru.org">smtp@oeru.org</a>" in the MailCow interface. If we haven't made sure of that, our mail server is likely to reject sending emails with that "mismatching" email address. This is a basic spam deterrence measure, which is for the best, despite sometimes making a email system administrator's life harder.</p> <p>Once we've got that (and it's easy once you've done it once or twice - I'm mostly writing this down now so I don't have to try to re-remember every time I need to set up a new server - and I hope it helps others, too), we can set up any server we control to send secure (and spam-filter-resilient) email. For what it's worth, too, MailCow uses Postfix as its SMTP server component (there're a bunch of other components, too).</p> <h2>Postfix SMTP with SmartHost</h2> <p>The first thing you need to do to create a postfix <a href="https://en.wikipedia.org/wiki/Smart_host">smarthost</a> is to install the postfix application on a new server (this assumes you're logged in with a user who has "sudo" - aka admin - permissions):</p> <p><code>sudo apt update &amp;&amp; sudo apt 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,</li> <li>the domain name and port (in the form <code>[smtp server domain]:[port]</code>, e.g. <code>smtp.oeru.org:587</code> ) 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.</li> </ul><p>After that's done, you can proceed.</p> <h2>Next Steps</h2> <p>For the rest of this tutorial, you'll need to do the following. First, select your text editor. I use vim, but if you're new to the command line, I recommend using nano - it's more straightforward:</p> <p><code>EDIT=`which nano`</code> or <code>EDIT=`which vim`</code></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 <em>your email :) </em>)</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>The resulting file only needs one line with three bits of information:</p> <p><code>[smtp server domain] [user name]:[password]</code></p> <p>for example:</p> <p><code>smtp.oeru.org smtp@oeru.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><code>sudo $EDIT /etc/postfix/main.cf</code></p> <p>If your SMTP server uses port 25 (the default for <em>unencrypted</em> 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 (<em>at least</em>) 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 = [your server name]:[server port] </code></p> <p>or, for example:</p> <p><code>relayhost = smtp.oeru.org:465 </code></p> <p>Next, add the following lines at the bottom of the file:</p> <p><code># added to configure accessing the relay host via authenticating SMTP<br /> smtp_sasl_auth_enable = yes<br /> smtp_sasl_password_maps = hash:/etc/postfix/relay_password<br /> smtp_sasl_security_options =</code><br /><code>smtp_tls_security_level = encrypt</code></p> <p><code># add this if you're using Ubuntu 20.04, and comment out (with a "#") the </code><br /><code># earlier line smtp_tls_security_level = may to save errors in 'postfix check'<br /> # and uncomment this line (by removing the #)<br /> #smtp_tls_wrappermode = yes</code><br />  </p> <p>Save the file, and then check that your syntax is correct:</p> <p><code>sudo postfix check</code></p> <p>If it is (running the command returns no errors, and it might not return anything at all - that's a good thing!), then you can run</p> <p><code>sudo postfix reload</code></p> <p>to get postfix to reload its configurations and you can test out your new smarthost-configured SMTP server!</p> <p>If not, the output of the check command will usually give you a helpful insight into what is wrong with your configuration... you'll also find that looking at the mail log is very helpful and offers great insights:</p> <p><code>sudo less +G /var/log/mail.log</code></p> <p>and if you're not able to fix it based on those, you'll find postfix is widely documented and has rich set of easily discoverable resources out there on the web - a search engine is your best resource!</p> <h2>Testing your outgoing email</h2> <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> <p><code>Subject: Testing from your.relay.server.domain&lt;ENTER&gt;<br /> Testing postfix remote host&lt;ENTER&gt;<br /> &lt;CTRL-D&gt;<br /> Cc:&lt;ENTER&gt;</code></p> <p>Typing &lt;CTRL-D&gt; (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 &lt;ENTER&gt;, 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 above. Hit &lt;SHIFT-F&gt; to have the log update in real time.</p> <h2>Done</h2> <p>Now you've got working outgoing email from your server. That means many higher-level web applications you might install on your infrastructure will work out-of-the-box, because what you've set up, for example, enables the default PHP email service and that used by other stacks.</p> <h2>Sending from Docker Containers</h2> <p>You can configure your server so you can reference it from services you run from Docker containers on your host. You do this by referencing the host, like via an ad hoc SMTP server on your container like <a href="https://marlam.de/msmtp/">msmtp</a>, and you can just reference it as 172.17.0.1, which is the default base IP for Docker hosts from the perspective of Docker containers. You might find it's different on your particular install. In that case, you have to make your Postfix SmartHost accept email for sending from the Docker containers on that server. There're quite a few examples of that among <a href="https://git.oeru.org/explore/projects?utf8=%E2%9C%93&amp;name=docker&amp;sort=latest_activity_desc">my Docker recipes on the OERu's git repository</a>.</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=28&amp;2=field_blog_comments&amp;3=comment" token="lLRkGAi5P6j9iM99_jKG1YxvJLITyB02GoT7oM-A7oA"></drupal-render-placeholder> </div> </section> Mon, 02 Aug 2021 02:08:28 +0000 dave 28 at http://tech.oeru.org Protecting your users with Let's Encrypt SSL Certs http://tech.oeru.org/protecting-your-users-lets-encrypt-ssl-certs <span class="field field--name-title field--type-string field--label-hidden">Protecting your users with Let&#039;s Encrypt SSL Certs</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--lets-encrypt"> <span class="field__item-wrapper"><a href="/taxonomy/term/17" hreflang="en">let&#039;s encrypt</a></span> </div> <div class="field__item field__item--install"> <span class="field__item-wrapper"><a href="/taxonomy/term/11" hreflang="en">install</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--_404"> <span class="field__item-wrapper"><a href="/taxonomy/term/13" hreflang="en">14.04</a></span> </div> <div class="field__item field__item--_604"> <span class="field__item-wrapper"><a href="/taxonomy/term/27" hreflang="en">16.04</a></span> </div> <div class="field__item field__item--_804"> <span class="field__item-wrapper"><a href="/taxonomy/term/68" hreflang="en">18.04</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> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> <span class="field field--name-created field--type-created field--label-hidden">Thu 08/07/2021 - 14:23</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><a class="visually-hidden focusable skip-link" href="https://tech.oeru.org/node/add/blog_post#main-content">Sk</a>For any website that requires anyone (users or even just a few admins) to transfer secrets to and from it, you want to ensure the data is end-to-end encrypted. Today various browsers (like Firefox) give warnings when you're sending secret data (like passwords) "in the clear", namely unencrypted. In early 2017, Google <a href="http://searchengineland.com/google-starts-giving-ranking-boost-secure-httpsssl-sites-199446">added further urgency</a> to doing the right thing for your users. </p> <p>In the past, getting an SSL certificate to achieve encryption for your domain (the little "lock" icon in browser address bar indicating that your communication with the site is encrypted), was a complicated, expensive proposition, requiring a lot of annoying and time consuming "identity verification" (sometimes via post in the dark old days) and a fee of, in some cases, a couple hundred dollars per year paid to your "SSL Cert Provider" to pay for those administrative services along with the software needed to gin up a long prime number to act as your encryption key (the long string of characters making up your SSL certificate).</p> <p>Thankfully, thanks to the efforts of the <a href="https://letsencrypt.org" title="Let's Encrypt - democratising SSL and making it ubquitous.">Let's Encrypt</a> community, the process is both far far easier, and free of cost. Now there really isn't an excuse for not having an SSL certificate on your site.</p> <p>Members of the Let's Encrypt community have provided a range of useful open source tools you can use to create and maintain certificates on your hosting infrastructure (e.g. the Virtual Machine (VM) on which you're installing web services detailed in the howtos on this site!). In this case we're going to use a tool, "<a href="https://certbot.eff.org/">certbot</a>" provided by the good folks at the <a href="https://eff.org">Electronic Frontier Foundation</a>. For VMs running Ubuntu 14.04 or 16.04 (the Long Term Support (LTS) versions of the Ubuntu Linux platform) which are what we use, the install is easy - at your VM command line, run:</p> <pre> <code>sudo add-apt-repository ppa:certbot/certbot sudo apt-get update sudo apt-get install certbot </code></pre> <p>We run Nginx on our VMs, which makes one or more hosted services (normally running in Docker containers) available on the Internet. Strictly speaking, we don't use full "end-to-end" encryption - in our case, on the server-end the encryption terminates at the Nginx server. We, perhaps cavalierly, assume that transfer between the host machine and a Docker container running on that host will be implicitly secure... The only way it could be compromised is if the VM itself is compromised, in which case, the Docker containers running on it could be, too. Avoiding having secure transfer between Nginx on the VM host and the various Docker containers also substantially simplifies setting up each application.</p> <p>Thanks to a service which Nginx provides SNI (or <a href="https://en.wikipedia.org/wiki/Server_Name_Indication">Server Name Indication</a>) - Apache and a few other web servers also provide this - which removes the historical limitation that meant you could only have one SSL certificate per IP address on a web server. The only downside of SNI is that some older browsers (and platforms) don't support it. Since those older technologies are rapidly dying out and it's quite expensive and difficult to have a single IP address for each SSL service on a server, we accept this compromise.</p> <h2>The Let's Encrypt Cert Process</h2> <p>Here's (roughly) how the process works:</p> <ol><li>Point your domain (via A or CNAME record) to point to the/an external IP address on your VM.</li> <li>Set up a domain (or domains) for non-secure hosting (on port 80) via your Nginx instance.</li> <li>The domain's configuration must include a special directory reserved for Let's Encrypt verification content.</li> <li>You request that certbot (on the VM) acquires a certificate for that domain (or domains) at which point <ol><li>the certbot writes a file with a hard-to-guess name to that special directory and requests that the Let's Encrypt infrastructure checks the domain name from outside</li> <li>Let's Encrypt checks that domain name and special directory to see that the expected number appears there, thus verifying that the requesting party actually has the ability to set content at this hard-to-guess filename, and therefore has legitimate claim to being the party controlling the domain name and server.</li> <li>Let's Encrypt registers the certificate request in the name of the party running the certbot (so it can, for example, send emails to the administrator warning them that the certificate needs to be renewed - which happens every 8 weeks or so), and</li> <li>Let's Encrypt sends verification back to your VM's certbot telling it to complete the certificate generation, which it then (digitally) signs in the name of the Let's Encrypt Certificate Authority (which, in turn, is recognised by almost all web browsers out-of-the-box - no mean feat, I can tell you).</li> </ol></li> <li>You get an alert telling you that you have created a valid SSL certificate.</li> <li>You alter your Nginx domain configuration to <ol><li>redirect connections to port 80 (un-encrypted) to port 443 (encrypted), and</li> <li>you set up the 443 configuration including your new certificates.</li> </ol></li> <li>You reload your Nginx configuration, and your site will now be end-to-end encrypted.</li> </ol><h2>The Let's Encrypt Snippet</h2> <p>To make it easy to include the relevant directory info, I recommend that you create a new file in your Nginx configuration (substitute your preferred text editor for "vim" in the following - "nano" is a good choice if you haven't already got a preference):</p> <p><code>sudo mkdir /etc/nginx/includes<br /> sudo vim /etc/nginx/includes/letsencrypt.conf</code></p> <p>and make sure it has the following content (note, I learned this thanks to <a href="https://community.letsencrypt.org/t/how-to-nginx-configuration-to-enable-acme-challenge-support-on-all-http-virtual-hosts/5622">someone else's howto</a> on the global Internet knowledge commons :))</p> <p><code># Rule for legitimate ACME Challenge requests<br /> location ^~ /.well-known/acme-challenge/ {<br />     default_type "text/plain";<br />     # this can be any directory, but this name keeps it clear<br />     root /var/www/letsencrypt;<br /> }</code></p> <p><code># Hide /acme-challenge subdirectory and return 404 on all requests.<br /> # It is somewhat more secure than letting Nginx return 403.<br /> # Ending slash is important!<br /> location = /.well-known/acme-challenge/ {<br />     return 404;<br /> }</code></p> <p>Next, make sure your directory exists (note - you only need to do this once per VM) - it shouldn't need an special permissions - it'll be written by the "root" user, and needs to be readable by the Nginx user, usually "www-data" on a Debian or Ubuntu Linux instance.</p> <p><code>mkdir /var/www/letsencrypt</code></p> <h2>Example Nginx Domain Configuration - unencrypted</h2> <p>Here's an example of a pre-cert Nginx domain configuration for example.org and <a href="http://www.example.org">www.example.org</a> (I usually name the configuration file after the main domain it concerns, so my file would be /etc/nginx/sites-available/example.org) - this should also let you do initial test of your app to make sure it works, before adding the additional complexity of SSL. (<em>Replace example.com (and <a href="http://www.example.com">www.example.com</a>) with your own domain!</em>):</p> <p><code>server {</code></p> <p><code>    listen 80; # this is one of our external IPs on the server.<br />     #listen   [::]:80 default ipv6only=on; ## listen for ipv6<br /><br />     # this root directory isn't really relevant in a proxy situation</code><br /><code>    # so I usually set it to the system default<br />     root /usr/share/nginx/www;<br />     index index.html index.htm;<br /><br />     server_name example.org www.example.org;<br /><br />     access_log /var/log/nginx/example.org_access.log;<br />     error_log /var/log/nginx/example.org_error.log;</code></p> <p><code>    # this is where we include the snippet<br />     include /etc/nginx/includes/letsencrypt.conf;</code></p> <p><code>    # this is just an example of a "proxy" configuration<br />     # for, say, a Docker-based service, exposed on the VM's<br />     # local port 8081<br />     location / {<br />         proxy_read_timeout      300;<br />         proxy_connect_timeout   300;<br />         proxy_redirect          off;<br />         proxy_set_header    Host                $http_host;<br />         proxy_set_header    X-Real-IP           $remote_addr;<br />         proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;<br />         proxy_set_header    X-Forwarded-Proto   $scheme;<br />         proxy_pass      http://127.0.0.1:8081;<br />     }</code><br /><code>}</code></p> <p>You can make sure that the configuration is visible to Nginx by adding it into the "sites-enabled" directory via a file link</p> <p><code>sudo ln -sf /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled </code></p> <p>Test things to make sure the new configuration doesn't have typos or bad references:</p> <p><code>sudo nginx -t</code></p> <p>and if not, make it live:</p> <p><code>sudo service nginx reload</code></p> <p>You should now be able to go to <a href="http://example.com">http://example.com</a> (or your domain) and you'll hopefully get your proxied application (if it's set up) or an Nginx error (see you nginx error file for more info!).</p> <p>Now it's time to request the certificate!</p> <h2>Example Certbot invocation</h2> <p>Once cerbot is installed, and a domain is configured, it's pretty straightforward to get a certificate.</p> <p>On the first invocation of certbot, you might get a coloured interface that requests your user details (e.g. name and email address) so that Let's Encrypt can register them for the purposes of future emails. They email if one of your certificates is on the verge of expiring, or if there's been a change to Let's Encrypt policy or process. It's worth being on the list. </p> <p>You can request your certificate with the following:</p> <p><code>sudo certbot certonly --webroot -w /var/www/letsencrypt -d example.org -d www.example.org</code></p> <p>If it works, it gratifyingly results in a message that starts with "Congratulations"!</p> <h2>Example Nginx Domain Configuration - unencrypted</h2> <p>Once you've got your certificate, you can reference it in your configuration. We normally set up a redirect from the unencrypted version of the site to the encrypted on (except for the Let's Encrypt verification directory!):</p> <p><code>server {<br />     listen 80; # listen on port 80 on all IPv4 addresses</code><br /><code>    listen [::]:80; # listen on port 80 on all IPv6 addresses</code><br /><code> <br />     root /var/www/html;<br />     index index.html index.htm;<br /><br />     server_name example.org www.example.org;<br /><br />     access_log /var/log/nginx/example.org_access.log;<br />     error_log /var/log/nginx/example.org_error.log;</code></p> <p><code>    include /etc/nginx/includes/letsencrypt.conf;</code></p> <p><code>    # a 302 is a "soft" redirect. A 301 can never be reversed.<br />     location / {<br />         return 302 https://chat.oeru.org$request_uri;<br />     }       <br /> }<br /><br /> server {</code><br /><span style="font-family:monospace"><span style="color:#000000;background-color:#ffffff;">    listen 443 ssl; # listen on port 443 on all IPv4 addresses</span></span><br /><span style="font-family:monospace"><span style="color:#000000;background-color:#ffffff;">    listen [::]:443 ssl; # listen on port 443 on all IPv6 addresses</span></span><br /><br /><code>    # this is a deprecated rule, not required in Ubuntu 20.04's nginx<br />     # ssl on; <br />     ssl_certificate /etc/letsencrypt/live/example.org/fullchain.pem;<br />     ssl_certificate_key /etc/letsencrypt/live/example.org/privkey.pem;</code><br /><code>    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;<br />     ssl_dhparam /etc/ssl/certs/dhparam.pem;<br />     keepalive_timeout 20s;</code></p> <p><code>    access_log /var/log/nginx/example.org_access.log;<br />     error_log /var/log/nginx/example.org_error.log;</code></p> <p><code>    root /var/www/html;<br />     index index.html index.htm;</code><br /><br /><code>    server_name example.org www.example.org;<br />    <br />     # this is just an example of a "proxy" configuration<br />     # for, say, a Docker-based service, exposed on the VM's<br />     # local port 8081<br />     location / {<br />         proxy_read_timeout      300;<br />         proxy_connect_timeout   300;<br />         proxy_redirect          off;<br />         proxy_set_header    Host                $http_host;<br />         proxy_set_header    X-Real-IP           $remote_addr;<br />         proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;<br />         proxy_set_header    X-Forwarded-Proto   $scheme;<br />         proxy_pass      http://127.0.0.1:8081;<br />     }</code><br /><code>}</code></p> <p>Note - you also need to set up the <code>ssl_dhparam</code> file for this configuration to work. You can do this based on <a href="https://michael.lustfield.net/nginx/getting-a-perfect-ssl-labs-score" title="Setting hp ssl_dhparam">these instructions</a> after installing OpenSSL tools:</p> <p><code>sudo apt-get install openssl</code></p> <p>by running (warning - this can take quite a long time, 5-15 minutes in my experience - the system needs to generate sufficient <a href="https://en.wikipedia.org/wiki/Entropy">entropy</a> to achieve acceptable randomness):</p> <pre class="literal-block"> <code>openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096</code> </pre> <p>When you've set up the file, you can enable it:</p> <p><code>sudo ln -sf /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled</code></p> <p>Test the file to ensure there aren't any syntax errors before reloading nginx:</p> <p><code>sudo nginx -t</code></p> <p>If this shows an error, you'll need to fix the file. If all's well, reload nginx to enable the new configuration:</p> <p><code>sudo service nginx reload</code></p> <p>You should now be able to point your browser at your domain name, and it should automatically redirect you to <a href="https://example.org">https://example.org</a> - and (based on the above configuration, <a href="https://www.example.org">https://www.example.org</a> should work too. You might want to redirect <a href="http://www.example.org">www.example.org</a> to example.org or visa versa).</p> <p>A word to the wise - if it doesn't work, check your firewall settings!</p> <p><strong>Update:</strong> discovered this <a href="https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-14-04">very well done how-to</a> on Let's Encrypt that offers additional background to this one.</p> <h2>Ongoing Certificate Maintenance</h2> <p>One of the nice things about EFF's certbot is that when it's installed, it also installs a nightly cron task (see <code>/etc/cron.d/certbot</code>) which checks all domains registered on the server for renewals. Assuming your domains have been configured in Nginx as described above, renewals should occur automatically, and you'll just receive a periodic email to let you know that they've happened.</p> <p>If you want to check your renewal status, you can run this:</p> <pre> <code>sudo certbot renew --dry-run</code></pre> <p>Good on you for securing your users and your site!</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=11&amp;2=field_blog_comments&amp;3=comment" token="u_cC5Zw8SKo5_CwG7txdoRLRmx6G2xIvWYWTc95IdY0"></drupal-render-placeholder> </div> </section> Thu, 08 Jul 2021 02:23:06 +0000 dave 11 at http://tech.oeru.org OERu Web Services as of February 2021 http://tech.oeru.org/oeru-web-services-february-2021 <span class="field field--name-title field--type-string field--label-hidden">OERu Web Services as of February 2021 </span> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> <span class="field field--name-created field--type-created field--label-hidden">Fri 19/02/2021 - 15:03</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="https://oeru.org">OERu</a> offers a wide array of online services, all Free and Open Source Software (FOSS), for the benefit of its global learner community, its partner representatives, and educators in general.</p> <p>As of this writing, in February 2021, we run the following production systems:</p> <ul><li style="margin: 0px; text-indent: 0px;">a <a href="https://wordpress.org/support/article/create-a-network/">WordPress multisite</a> - our course delivery site - <a href="https://course.oeru.org">https://course.oeru.org</a></li> <li style="margin: 0px; text-indent: 0px;">3 standalone <a href="https://wordpress.org">WordPress</a> instances - organisational content managed websites: <a href="https://coep.nz">https://coep.nz</a>, <a href="https://oer4covid.oeru.org">https://oer4covid.oeru.org</a>, <a href="https://oerfoundation.org">https://oerfoundation.org</a></li> <li style="margin: 0px; text-indent: 0px;">2 <a href="https://drupal.org">Drupal</a> 8 sites - our information tech site (this site!) and our H5P learning object builders - <a href="https://tech.oeru.org">https://tech.oeru.org</a>, <a href="https://h5p.oeru.org">https://h5p.oeru.org</a></li> <li style="margin: 0px; text-indent: 0px;">2 <a href="https://www.discourse.org/">Discoursen</a> - market leading, full-featured forums - <a href="https://community.oeru.org">https://community.oeru.org</a>, <a href="https://forums.oeru.org">https://forums.oeru.org</a>, <a href="https://test.forum.oeru.org">https://test.forum.oeru.org</a></li> <li style="margin: 0px; text-indent: 0px;"><a href="https://nextcloud.com">NextCloud</a> - <a href="https://docs.oeru.org">https://docs.oeru.org</a> - for file sharing and many other uses, including collaborative online document/spreadsheet/presentation authoring with two "office" services: <ul><li style="margin: 0px; text-indent: 0px;">CollaboraOffice - <a href="https://collab.oeru.org">https://collab.oeru.org</a> - and</li> <li style="margin: 0px; text-indent: 0px;">OnlyOffice - <a href="https://onlyoffice.oeru.org">https://onlyoffice.oeru.org</a></li> </ul></li> <li style="margin: 0px; text-indent: 0px;"><a href="https://bigbluebutton.org">BigBlueButton</a> - a large scale real-time video conferencing and educational collaboration platform - <a href="https://speak.oeru.org">https://speak.oeru.org</a>) along with a <ul><li style="margin: 0px; text-indent: 0px;"><a href="https://docs.bigbluebutton.org/2.2/setup-turn-server.html">COTURN</a> server - <a href="https://turn.oerfoundation.org">https://turn.oerfoundation.org</a></li> </ul></li> <li style="margin: 0px; text-indent: 0px;"><a href="https://github.com/mautic/mautic">Mautic</a> - mailing list automation management - <a href="https://mautic.oeru.org">https://mautic.oeru.org</a></li> <li style="margin: 0px; text-indent: 0px;"><a href="https://rocket.chat">Rocket.Chat</a> - rich chat service - <a href="https://chat.oeru.org">https://chat.oeru.org</a> - comparable to Slack or DIscord (but better, because it's open source, and we control the data).</li> <li style="margin: 0px; text-indent: 0px;"><a href="https://keycloak.org">Keycloak</a> - authentication and identity management system for Single Sign-on system (not yet launched) - <a href="https://login.oeru.org">https://login.oeru.org</a></li> <li style="margin: 0px; text-indent: 0px;"><a href="https://joinmastodon.org">Mastodon</a> - Twitter-esque but distributed and federated open source social network - <a href="https://mastodon.oeru.org">https://mastodon.oeru.org</a></li> <li style="margin: 0px; text-indent: 0px;"><a href="https://mailcow.email/">Mailcow</a> - 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> - supplying all email services for the oeru.org, oerfoundation.org, and coep.nz domains.</li> <li style="margin: 0px; text-indent: 0px;"><a href="https://yourls.org">YourLS</a> link shortener - <a href="https://oer.nz">https://oer.nz</a></li> <li style="margin: 0px; text-indent: 0px;"><a href="https://matomo.org">Matomo</a> - website analytics (like Google Analytics, but without leaking private information) - <a href="https://statz.oeru.org">https://statz.oeru.org</a></li> <li style="margin: 0px; text-indent: 0px;"><a href="https://mahara.org">Mahara</a> - online academic portfolio tool - <a href="https://portfolio.oerfoundation.org">https://portfolio.oerfoundation.org</a></li> <li style="margin: 0px; text-indent: 0px;"><a href="https://moodle.org">Moodle</a> - market leading learning management system - <a href="https://moodle.oeru.org">https://moodle.oeru.org</a></li> <li style="margin: 0px; text-indent: 0px;"><a href="https://silverstripe.com">SilverStripe</a> - our main OERu website - <a href="https://oeru.org">https://oeru.org</a></li> <li style="margin: 0px; text-indent: 0px;"><a href="https://mediawiki.org">MediaWiki</a> - collaboration platform for OER development built on the same platform on which Wikipedia is built - <a href="https://wikieducator.org">https://wikieducator.org</a></li> <li style="margin: 0px; text-indent: 0px;"><a href="https://gibtlab.com">GitLab</a> - a software developer version control/collaboration tool (<a href="https://git.oeru.org">https://git.oeru.org</a>)</li> <li style="margin: 0px; text-indent: 0px;"><a href="https://limesurvey.com">LimeSurvey</a> - survey tool (<a href="https://survey.oeru.org">https://survey.oeru.org</a>)</li> <li style="margin: 0px; text-indent: 0px;"><a href="https://bitwarden.com">BitWarden</a> - password manager and cross-device sync service - <a href="https://safe.oeru.org">https://safe.oeru.org</a> - our <a href="https://tech.oeru.org/setting-your-own-bitwarden-password-manager-and-sync-server">howto for hosting your own</a>.</li> <li style="margin: 0px; text-indent: 0px;"><a href="https://github.com/ether/etherpad-lite">Etherpad-lite</a> - simple collaborative editing platform - <a href="https://etherpad.oeru.org">https://etherpad.oeru.org</a></li> <li style="margin: 0px; text-indent: 0px;"><a href="https://sourceforge.net/projects/semanticscuttle/">Semantic Scuttle</a> instance - a collaborative public website bookmarking service - <a href="https://bookmarks.oeru.org">https://bookmarks.oeru.org</a></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/">Docker</a> and <a href="https://docs.docker.com/compose/">Docker Compose</a>. In future, we expect to move to "just-in-time" scaling via Kubernetes (or similar).</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-802" about="/comment/802" 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/802#comment-802" class="permalink" rel="bookmark" hreflang="en">Wow, what an inspiring set…</a> <span class="comment__new marker marker--success hidden" data-comment-timestamp="1615533175"></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="">Arnold Schrijver (not verified)</span></span> </span> <span class="comment__pubdate">Fri 12/03/2021 - 19:22 <span property="schema:dateCreated" content="2021-03-12T06:22:27+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>Wow, what an inspiring set of services! Very cool. I just wanted to provide my 2cts on a number of candidates that may be interesting to have a look at, plus some thoughts :)</p> <p>- I&#039;d consider replacing Rocket.Chat for Matrix Communities + Chatrooms and have the instance federate with the &#039;matriverse&#039;.</p> <p>- Instead of Mastodon I&#039;d go for Pleroma as they are more lightweight in resources, feel more modern, more standards-compliant, and as a way to help equalize power balance on the fediverse.</p> <p>- I found it as an exemplar of domain-driven design production app (not using it), but Open Edx might be worth looking at: <a href="https://open.edx.org/">https://open.edx.org/</a></p> <p>- I&#039;d look into having a static site generator part of the stack. Jekyll is easiest, but maybe Hugo because it is more versatile.</p> <p>- For a static site CMS that also has a user-friendly UI you can look at Grav. It can be installed on a shared hosting provider by just copying via SFTP in most cases.</p> <p>- I&#039;ve looked at Keycloak in the past, but never used. I hear it is hard to set up and maintain. Not quite there yet, but interesting, is Authelia. Recently featured on Hacker News. It places all the user management, Auth/authz, SSO on your reverse proxy, alleviating your apps from the responsibility. See <a href="https://github.com/authelia/authelia">https://github.com/authelia/authelia</a> and check the Roadmap.</p> <p>- Personally, though less feature-complete, I like Gitea to Gitlab. The former is true FOSS (I use it on codeberg.org and feels great), and the latter has big corp in the driver seat (wouldn&#039;t be surprised by an e.g. Amazon takeover in the future).</p> <p>- As a CI/CD to Gitea a self-hosted Drone is a good option. <a href="https://www.drone.io/">https://www.drone.io/</a></p> <p>- Though not fully feature-wise on par with Semantic Scuttle, the Tiny Tiny RSS is actively maintained and easily installs on shared hosting providers. See <a href="https://tt-rss.org/">https://tt-rss.org/</a></p> </div> </div> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=802&amp;1=default&amp;2=en&amp;3=" token="fE6sxNe93HepnenJxJ7FEkPq6KkKCWoCfAweI4-t7fk"></drupal-render-placeholder> </div> </div> </article> <div class="indented"><article data-comment-user-id="1" id="comment-803" about="/comment/803" 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/803#comment-803" class="permalink" rel="bookmark" hreflang="en">Wow, I&#039;m aware of a few of…</a> <span class="comment__new marker marker--success hidden" data-comment-timestamp="1615790253"></span> </h3> <div class="comment__meta"> <div class="comment__submitted"> <span class="comment__author"><span rel="schema:author"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> </span> <span class="comment__pubdate">Fri 12/03/2021 - 20:15 <span property="schema:dateCreated" content="2021-03-12T07:15:46+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/802#comment-802" class="permalink" rel="bookmark" hreflang="en">Wow, what an inspiring set…</a> by <span lang="" typeof="schema:Person" property="schema:name" datatype="">Arnold Schrijver (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>Nice! I'm aware of a few of those and am keeping an eye on them, but a few others are new to me, and much appreciated! I'm a big fan of Matrix (and Element/Synapse) but when I was choosing a FOSS chat system, Rocket.Chat was by far the most mature... and it's still very impressive, but I'm keeping tabs on Matrix clients (I'm a big fan of open standards) and I'll implement any R.C plugins that allow us to be Matrix compliant!</p> <p>I'm familiar with OpenEdX (my colleague in the OERF was, I believe, involved in that project in the early days) but for various reasons, we decided it's not appropriate for what we're doing...</p> <p>Regarding Mastodon and Pleroma (which is an annoyingly unpronouncable name), I'm pretty happy with former at this point...</p> <p>I've played around with Grav a fair bit and I like quite a few of its ideas (I'm a big fan of Markdown) over the past couple years (including building a couple sites with it), but have decided to stick with technologies (like Drupal) that I already know quite a lot better...</p> <p>We're definitely looking for something a bit more "current" than Semantic Scuttle... will have a look at tt-rss.org to see if it addresses our use cases... I've recently encountered (and am looking at) <a href="https://bitbucket.org/bibsonomy/bibsonomy/wiki/Home#markdown-header-bibsonomy">https://bitbucket.org/bibsonomy/bibsonomy/wiki/Home#markdown-header-bib…</a> - jury's still out...</p> <p>As for Gitea and I agree with the assessment of Gitlab... I reserve the right to switch to SourceHut or Gitea down the rack... waiting for the time being, but watching things closely! Thanks very much Arnold!</p> </div> </div> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=803&amp;1=default&amp;2=en&amp;3=" token="PfZubMiXEWNPZ062xBV2LmtmV2ID8SE490RgL6hekiM"></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=34&amp;2=field_blog_comments&amp;3=comment" token="sgOtHytFH-tlBvE92Egrl915iOiI8OoBRkegQgmByR4"></drupal-render-placeholder> </div> </section> Fri, 19 Feb 2021 02:03:48 +0000 dave 34 at http://tech.oeru.org 2020 OERF Open Technology Overview http://tech.oeru.org/2020-oerf-open-technology-overview <span class="field field--name-title field--type-string field--label-hidden">2020 OERF Open Technology Overview</span> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> <span class="field field--name-created field--type-created field--label-hidden">Wed 29/07/2020 - 14: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>As a matter of principle, the OER Foundation makes use of Free and Open Source Software (FOSS) wherever possible. Partnering with the Foundation provides an opportunity to share the experience of an organisation with hands-on experience in hosting all its technology infrastructure using FOSS.</p> <p>The foundation has implemented a component-based approach selecting “best-of-breed” open technologies for assembling the OERu’s Digital Learning Environment (DLE) rather than providing learning materials through a single application like a Learning Management System (LMS).</p> <p>There are myriad reasons for taking this approach.</p> <p>FOSS tools are highly advanced and have a low fixed cost as well as <em>negligible variable costs</em> for provision. FOSS tools offer superb functionality and are subject to neither per-server or “per-seat” license fees when self-hosted. If, for convenience, you choose to buy a hosted service featuring a FOSS tool, you always have the option of moving to a self-hosted instance if the service provider’s pricing or terms of use become oppressive.</p> <p>Hosting our own FOSS tools provides the massive advantage - albeit one largely ignored by software users in general - that the Foundation can determine our own Terms of Use, designed to support and enable our learners, rather than impose upon their privacy or legal rights by forcing onerous terms on them as most proprietary hosted services do. This is contrary to most educational software use, where learners are, in effect, forced to accept the terms of use imposed by corporate vendors.</p> <p>With FOSS tools, our learners <em>never have to buy third party software</em>. This means that there are no external cost-barriers to their participation.</p> <p>FOSS tools can be glued together in ways that appear seamless for end users with open standards-based integration approaches, or, in the worst case, small strategic custom development projects. We can achieve incredible integrations using this approach, including our <a href="/wikieducator-notes-oerus-course-feed-aggregation-and-messaging-system">WEnotes</a> system, which discovers course-related posts created by learners in a variety of online tools and services, and <em>brings the Internet to them</em> - it aggregates them for learners to see in the context of each course via its “course feed” like <a href="https://course.oeru.org/lida101/interactions/course-feed/">this one</a> for our Learning in a Digital Age 101 course, aka LiDA101.</p> <p>FOSS tools are managed by communities, often global, who take a shared interest in their projects’ survival, sustainability, and perpetual improvement. They are seldom motivated by profit. Proprietary software tools can be affected by their sole vendor going out of business, being acquired (with the tool merged with other tools or shut down), or the vendor can alter their terms of use at their whimsy.</p> <p>In stark contrast, FOSS tools continue to exist as long as they provide value to the community that sustains them. If a tool does end up going dormant, the community of those dependent on the tool typically share the responsibility of building a migration path to a successor technology rather than it being the sole responsibility of one vendor or forcing users to fend for themselves.</p> <p>Because of its low fixed and variable costs and the distributed development and maintenance responsibilities typifying FOSS projects, we believe <em>our approach results in a sustainable and equitable Digital Learning Environment</em>.</p> <p>A loosely-coupled tools approach allows us to adopt “best of breed” FOSS tools in each software domain and focus our energies on connecting them all in a sensible way that empowers our learners to participate. We can trial and swap emerging standout candidates in and out of our software stack as desired, with no licensing or contractual implications and minimal impact on our learners and collaborators. To keep our staff time costs and risks low, we have adopted common patterns for deploying and managing our tool collection, including the widespread use of Docker server virtualisation and the unparalleled Linux server hosting ecosystem and associated application stacks - almost all modern web-based software is built on one or another FOSS stack (like those centred around PHP, Python, Ruby on Rails, NodeJS, and Java) - you can <a href="/2018-update-oeru-technology-stack">learn more about the specific technologies</a> in our "tech stack". This offers us a tremendous diversity of FOSS applications and allows us to remain current with the constantly changing and often fickle expectations of Internet users. Moreover, we completely avoid the restriction of needing to find tools integrated with a monolithic LMS (in the case of FOSS LMSs like Moodle or Canvas) or somehow anointed or bundled by the LMS’ authors (in the case of proprietary LMSs like Blackboard). Those integrated tools have limited applicability, namely only working properly in the context of a subset of LMSs, and they are therefore seldom as advanced or long-lived as their stand-alone point solution FOSS alternatives.</p> <p>Last, by adopting an array of loosely-coupled FOSS tools, our partners (and non-partners alike) can pick and choose to replicate one or more of the FOSS technologies we use and integrate them into their own technology mixes. We actively lower the barriers to those institutions and individuals benefiting from that FOSS <a href="/">by providing overviews and how-tos</a>.</p> <p>Thanks to our <a href="/working-home">use of open technologies</a> within the Foundation and in collaboration with our Partners, as well as our many years experience as a “distributed organisation”, we were able to <a href="https://oer4covid.oeru.org/resources/community-tools/">respond immediately and decisively to the COVID 19 pandemic</a>, providing crucial technologies, like real-time chat and large scale video conferencing, to our partners and the global community of educators.</p> <p>With all these clear advantages, we are frankly astounded that the rest of the education sector has not adopted the same FOSS approach. We're keen to help those of you out there who recognise this opportunity to put it into practice!</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=33&amp;2=field_blog_comments&amp;3=comment" token="ImPsGPbN_ZXzsN8aUD8FSS9-rzWeJ3G1Q_PxUVa7dcY"></drupal-render-placeholder> </div> </section> Wed, 29 Jul 2020 02:56:50 +0000 dave 33 at http://tech.oeru.org Working from home http://tech.oeru.org/working-home <span class="field field--name-title field--type-string field--label-hidden">Working from home</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--covid-19"> <span class="field__item-wrapper"><a href="/taxonomy/term/73" hreflang="en">covid-19</a></span> </div> <div class="field__item field__item--working-from-home"> <span class="field__item-wrapper"><a href="/taxonomy/term/74" hreflang="en">working from home</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"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> <span class="field field--name-created field--type-created field--label-hidden">Wed 18/03/2020 - 09:11</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 light of the current global pandemic, there's lots of interest out there on "working remotely" or "working from home". We've been doing a for years, and it has a lot of advantages... here's how we do it....</p> <p>The OER Foundation (we're the charitable foundation that coordinates the <a href="https://oeru.org">OERu</a>) is a "remote organisation". There is no central office - after all only two of us work for the Foundation full time. We both work from our homes, in <a href="https://osm.org/go/r9ZYCbT?layers=N">Mosgiel</a> and <a href="https://osm.org/go/uoyJ~MZ-?layers=N">Christchurch</a>, two centres about 5 hours drive apart in the South Island of New Zealand. We also have colleagues, who dedicate part of their time to our projects, based at <a href="https://op.ac.nz">Otago Polytechnic</a> in Dunedin. Despite our small (but plucky) team, we collaborate with educators at<em> <a href="https://oeru.org/oeru-partners">40+ tertiary institution partners</a> (universities and polytechnics) in 5 continents around the world who amplify our efforts</em>...  Luckily for us, we both have the benefit of high speed internet access and a knowledge of Free and Open Source Software (FOSS) and how to make it work for our requirements - and scale for a <em>global</em> audience of educators and learners!</p> <h2>How we work</h2> <p>At the OER Foundation, we're task oriented (not time oriented - no timesheets). Every week, I have a set of standard tasks I undertake, as do my colleagues, and we have to get them done. For example, I "go around the (virtual) traps" at the start of each week to ensure all our remote systems are up-to-date and secure as possible. When those are done, we have a bunch of longer term (important-but-not-urgent) projects that are under way, scheduled in a kanban board (we use either our NextCloud's "Desk" plug-in or the very fine <a href="https;//kanboard.net">Kanboard</a> FOSS app) or by agreement.</p> <p>During the day we have frequent communication and collaboration among our small team, as well as with the broader group of OERu partner representatives - our Rocket.Chat system is our virtual meeting room and water cooler for both meeting and informal discussion.</p> <p>A few of us meet online for a video chat each day to catch up on the days work, talk strategy, and socialise a bit. We usually have our chats using Jitsi Meet, which is triggered by Rocket.Chat. Lately, we've been using Big Blue Button a lot more for our catchups, just to mix it up a little - they're both great for small groups (Big Blue Button also supports much larger groups).</p> <p>Here's a table laying out our "work from home" stack:</p> <table style="width: 90%; padding: 4px 8px; border: 0px black solid" summary="The FOSS tools we use to work remotely, all the time."><tbody><tr><th>Technology category</th> <th>Specific FOSS technology</th> <th>Replaces</th> </tr><tr><th>Desktop/Laptop Computers</th> <td>Lenovo Thinkpads</td> <td> </td> </tr><tr><th>Operating System</th> <td><a href="https://ubuntu.com/download/desktop/thank-you?version=18.04.4&amp;architecture=amd64">Ubuntu Linux</a> or <a href="https://linuxmint.com/download.php">Linux Mint</a></td> <td>Microsoft Windows, Apple MacOS</td> </tr><tr><th>Email service</th> <td><a href="https://mailcow.email/">Mailcow</a> (self-hosted - <a href="https://github.com/mailcow/mailcow-dockerized">how to do it</a>)(<a href="https://www.servercow.de/mailcow?lang=en#managed">commercial service</a>)</td> <td>Google Mail, Microsoft Outlook, Fastmail</td> </tr><tr><th>Email client (reading/composing)</th> <td><a href="https://www.thunderbird.net/en-US/">Thunderbird</a> (desktop), <a href="https://sogo.nu/about.html">SOGo</a> (webmail - built into Mailcow)</td> <td>Microsoft Outlook, Apple MailApp, Gmail, Hotmail, etc.</td> </tr><tr><th>Web browser</th> <td><a href="https://www.chromium.org/Home">Chromium</a> or <a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a> or <a href="https://brave.com/">Brave</a></td> <td>Google Chrome, Microsoft IE or Edge, Apple Safari</td> </tr><tr><th>Chat service, video conf</th> <td><a href="https://rocket.chat">Rocket.Chat</a> (self-hosted - <a href="/upgrading-rocketchat-10x-and-mongodb-40">how to do it</a>)(<a href="https://rocket.chat/pricing#cloud">commercial service</a>) - (access via browser, or <a href="https://rocket.chat/install">desktop and mobile</a> interfaces)</td> <td>Slack, Discord, Gitter, Microsoft Teams</td> </tr><tr><th>Document management and sharing</th> <td><a href="https://nextcloud.com">NextCloud Hub</a> (self-hosted - <a href="/installing-nextcloud-hub-onlyoffice-ubuntu-1804">how to do it</a>)(<a href="https://nextcloud.com/pricing/">commercial service</a> - gov't and education discounts available)</td> <td>Dropbox, Google Drive, Microsoft OneDrive</td> </tr><tr><th>Collaborative documents</th> <td>NextCloud's <a href="https://onlyoffice.com">OnlyOffice</a> integration (self-hosted - <a href="/installing-nextcloud-hub-onlyoffice-ubuntu-1804">how to do it</a>)(<a href="https://nextcloud.com/pricing/">commercial service</a> - gov't and education discounts available)</td> <td>Google Docs, MS Office 365</td> </tr><tr><th>Kanban project management</th> <td>NextCloud's <a href="https://apps.nextcloud.com/apps/deck">Deck app</a> or <a href="https://kanboard.org">Kanboard</a> (commercial service)</td> <td> <p> </p> <p>Trello, MS Project</p> </td> </tr><tr><th>Shared calendaring and contacts</th> <td>NextCloud's <a href="https://apps.nextcloud.com/apps/calendar">Calendaring</a> and <a href="https://apps.nextcloud.com/apps/contacts">Contact</a> apps</td> <td>MS Outlook, iCal, Google Calendars</td> </tr><tr><th>Scheduling (timezone aware)</th> <td>NextCloud's Poll app (part of NextCloud)</td> <td>Doodle Polls</td> </tr><tr><th>Password manager (share and personal)</th> <td><a href="https://bitwarden.com/">BitWarden</a> (self-hosted - <a href="/setting-your-own-bitwarden-password-keeper-and-sync-server">how to do it</a>)</td> <td>LastPass, 1Password, Keepassx, etc.</td> </tr><tr><th>Video Conferencing (&lt; 8 participants)</th> <td><a href="https://meet.jit.si">Jitsi Meet</a> (externally hosted) browser-based or mobile apps (all FOSS)</td> <td>Google Hangouts, Apple Facetime, Microsoft Skype</td> </tr><tr><th>Video Conferencing (webinars, hundreds of participants, breakout rooms, etc.)</th> <td><a href="https://bigbluebutton.org">Big Blue Button</a> (self-hosted)(commercial service <a href="https://moodlecloud.com/app/en/">one</a>, <a href="https://blindsidenetworks.com/services/">two</a>) browser-based, mobile apps available - if you want to try it yourself (gratis) go to the NZOSS' "<a href="https://nzoss.nz/workfromhome">Work from home</a>" page.</td> <td>Zoom</td> </tr><tr><th>Social Media</th> <td><a href="https://joinmastodon.org">Mastodon</a> (self-hosted)</td> <td>Twitter</td> </tr></tbody></table><p>Outside of that stack of software applications, we use lots of other FOSS apps on the desktop, like <a href="https://inkscape.org">Inkscape</a> (for vector drawings, replacing Adobe Illustrator), <a href="https://krita.org">Krita</a> and <a href="https://www.gimp.org">Gimp</a> (to modify images, replacing Adobe Photoshop), <a href="https://kdenlive.org">Kdenlive</a> (to edit video), <a href="https://libreoffice.org">LibreOffice</a> (to edit PDFs, documents, spreadsheets, drawings, presentations, replacing MS Office), <a href="https://www.audacityteam.org/">Audacity</a> (editing audio like voice-overs and podcasts), and many others, and many dozens of development tools to produce digital artefacts. If you're looking for a FOSS tool to do a job, <a href="/contact">let us know</a> - perhaps we can help you find one that's suitable!</p> <p>If you want to learn more about our cloud infrastructure - which involves a whole additional constellation of FOSS technologies - here's a <a href="/2018-update-oeru-technology-stack">review of our 2018 technology stack</a>, which is still current, with which we are providing a full online university...</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=31&amp;2=field_blog_comments&amp;3=comment" token="sLz-SwBuWdXbxVlQ0hryx85rnXi263GuAbWsaTp-zmk"></drupal-render-placeholder> </div> </section> Tue, 17 Mar 2020 20:11:13 +0000 dave 31 at http://tech.oeru.org Automatic versioned backups of Sqlite in a Docker Compose container http://tech.oeru.org/automatic-versioned-backups-sqlite-docker-compose-container <span class="field field--name-title field--type-string field--label-hidden">Automatic versioned backups of Sqlite in a Docker Compose container</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--sqlite"> <span class="field__item-wrapper"><a href="/taxonomy/term/72" hreflang="en">sqlite</a></span> </div> <div class="field__item field__item--bash"> <span class="field__item-wrapper"><a href="/taxonomy/term/58" hreflang="en">bash</a></span> </div> <div class="field__item field__item--backup"> <span class="field__item-wrapper"><a href="/taxonomy/term/57" hreflang="en">backup</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> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> <span class="field field--name-created field--type-created field--label-hidden">Tue 04/02/2020 - 12:17</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>For relatively lightweight applications that are either in development, or single user, or have limited requirements for concurrency and massive data sets, <a href="https://en.wikipedia.org/wiki/SQLite">SQLite</a> is a superb, full-function, but compact, <em>almost ubiquitous</em> database (it's used on every mobile device, for example).</p> <p>What's more, it's also Free and Open Source Software (FOSS) - its code has been dedicated to the public domain - making it an ideal tool for a principled open organisation like the OER Foundation to incorporate in its fully Free and Open Source Software stack.</p> <p>We use SQLite for quite a few things here at the OER Foundation. For example, this implementation of the backup script was developed for the very impressive fully FOSS version of the <a href="/setting-your-own-bitwarden-password-keeper-and-sync-server">password manager BitWarden </a>we use to manage our secrets at the OER Foundation. Because the data it holds, although very compact, is very precious, we want to back it up right, and ensure we can go back in time and recreate the data from the past (like, if we realised at some point that in the past we'd deleted the wrong secrets and needed to recover them! It hasn't happened yet, but it's inevitable it <em>will happen</em> at some point). That's why we've created this backup solution that creates dated dumps of an SQLite database that it keeps to ensure that relevant past versions of the data can be recovered, but without storing more data than necessary (a full hard disk is never fun).</p> <p>Those among you who know about SQLite might rightly point out that an SQLite database (unlike more powerful databases) is simply a single file that can be copied to make a backup. In this case, we're using SQLite's ".backup" command to create the database backup, which we see as a mechanism for future-proofing - at some future time, it might be that the option to copy the file without corrupting the database may no longer be available, but we'd expect that the .backup command would continue to work.</p> <p>This backup process involves 3 files: the backup BASH script itself, which has all the smarts, the .conf file which defines the file paths and other details specific to your installation - like where the SQLite database actually is, and where to put the backup files - and a cron file which runs the script automatically when you tell it to run on your server.</p> <p>We've got the <a href="https://git.oeru.org/dave/sqlite-backup">whole thing</a> in our <a href="https://git.oeru.org">Gitlab instance</a>, which is the best place to learn more about how to use it - see the <a href="https://git.oeru.org/dave/sqlite-backup/blob/master/README.md">README.md</a> for installation instructions!</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=30&amp;2=field_blog_comments&amp;3=comment" token="LUsqyib8Nl62guCFKZHLxak9ymOGimis4ahdLWzFAfE"></drupal-render-placeholder> </div> </section> Mon, 03 Feb 2020 23:17:20 +0000 dave 30 at http://tech.oeru.org