Setting up a website using blogdown/RStudio, hosted on a Synology NAS.
Prerequisites
- RStudio.
- blogdown and dependencies: Yihui Xie et al.’s Creating Websites with R Markdown (the “blogdown book”) is well worth reading in its entirety, but this post assumes you have read at least the Installation section (and followed the instructions therein).
- A Synology NAS1.
Initial setup
We will broadly follow setup suggested in the blogdown book getting started section.
First, though, we set up/initialise a remote git repository. If you don’t have
the Synology git package installed already, install it from Package Center, then
ssh
into the server with admin privileges and execute a git init
(repalcing
/volume1/git/michaelbolgernet.git
with your desired repository path/name; the
--bare
option is required for a remote repository):
git init --bare /volume1/git/michaelbolgernet.git
Alternatively, any other git host will do (or none, though then disregard other git-relevant instructions below).
Clone the empty repository onto your local machine in RStudio via File > New Project > Version Control > Git, then enter the repository URL etc:
Or clone the repository directly in the Terminal:
git clone ssh://[email protected]:2222/volume1/git/michaelbolgernet.git michaelbolger.net
In either case, the URL format is the same:
protocol://username@[domain or IP address][:port if non-standard]/path/to/repository.git /path/to/localrepository
Next, run File > New Project again, but this time selecting the New Directory > Website using blogdown option to create the basic site framework:
Generally, a new RStudio project should be created in an empty directory. For these purposes, however, the project directory we have just created by cloning the bare remote repository is sufficiently empty.
- The ‘Install Hugo automatically’ option does nothing if Hugo is already installed, and so can be left enabled.
- ‘Add sample blog posts’ adds a sample R Markdown post from the blogdown package, which may be useful in helping understand the capabilities of R Markdown, but can be ignored if you are already familiar with this.
- ‘Add the example site of the theme’ copies the example site for the theme
into the main site directory. While optional–these files are anyway available
within the
themes/<theme name>/exampleSite
directory–it is nevertheless helpful, especially when first setting up/exploring a site.
Regarding selecting a theme, don’t get side-tracked by shiny things–you will waste a lot of time before you end up back with the blogdown default theme (Lithium) or another one of the blogdown-recommended themes (see the getting started section of the blogdown book)2. See Themes, below, for further discussion on selecting a Hugo theme.
config.toml
The config.toml
file in the site’s root directory is the main configuration
file. Edit this file as required3. My config.toml
ended up like this (material
deviations from the default Lithium config.toml
are commented):
baseurl = "http://michaelbolger.net/"
languageCode = "en-us"
title = "michaelbolger.net"
theme = "hugo-lithium"
author = "Michael Bolger"
# googleAnalytics = ""
# disqusShortname = "michaelbolger"
ignoreFiles = ["\\.Rmd$", "\\.Rmarkdown$", "_files$", "_cache$"]
# Amend default Lithium permalink behaviour.
[permalinks]
post = "/post/:year-:month-:day-:slug/"
[[menu.main]]
name = "Home"
url = "/"
weight = 1
[[menu.main]]
name = "About"
url = "/about/"
[[menu.main]]
name = "Projects"
url = "/projects/"
# [[menu.main]]
# name = "GitHub"
# url = "https://github.com/rstudio/blogdown"
[[menu.main]]
name = "Twitter"
url = "https://twitter.com/michaelbolger"
[params]
description = "Tech and other musings, primarily relating to Apple, Synology, and R."
# options for highlight.js (version, additional languages, and theme)
highlightjsVersion = "9.12.0"
highlightjsCDN = "//cdnjs.cloudflare.com/ajax/libs"
highlightjsLang = ["r", "yaml", "ini", "bash"]
highlightjsTheme = "github"
# MathJaxCDN = "//cdnjs.cloudflare.com/ajax/libs"
# MathJaxVersion = "2.7.5"
# path to the favicon, under "static"
favicon = "favicon.ico"
[params.logo]
url = "logo.png"
width = 50
height = 50
alt = "Logo"
I changed the permalink behaviour as I feel, for a low-volume site, having nested directories for each month/year is too crufty. See the configuration section of the blogdown book for further details on these settings.
Dependencies
By default, the Lithium theme has support for Google Analytics, Disqus comments,
highlight.js syntax highlighting, and MathJax math expressions. I will not use
Google Analytics and so commented it out of config.toml
4.
To enable Disqus comments, you have to register to install Disqus on your
site (select ‘Install Disqus on my site’ if
prompted). Enter your website name, customising the ‘Shortname’ as required.
This is the name that should be entered against the disqusShortname
in
config.toml
. Amend other site settings in Disqus as appropriate, although
Disqus will work with default values. By default, the Lithium theme shows
comments on every page, including static pages like About and
Project pages. To suppress comments on any page, add
disable_comments: true
to the page’s YAML metadata.
highlight.js works out of the box. The only change I made to config.toml
was
to add support for toml
and bash
as additional languages (highlightjsLang = ["r", "yaml", "toml", "bash"]
).
I’m unlikely to use math expressions on my site, so I have commented out the
MathJax parameters, although the related JavaScript is still copied across to
the public
folder when the site is built.
Content
Create source documents for any pages you specified in config.toml
–there is
an About page in the Lithium example site, but you’ll need to edit its content,
and the Projects page does not exist. From the RStudio Addins toolbar button,
select New Post then amend as appropirate:
Note, to create a top-level page, ensure ‘Subdirectory’ is blank.
Create a new post in the same way:
When you create a post in this way, the date you include for the post is included as part of the filename of the file (and in the YAML metadate). You can amend the suggested filename when creating a post, but I like to keep the date in there so that sorting the files by name in a directory listing also sorts them by date.
You can use a date in the future for a post that is a work-in-progress.
Similarly, you can add draft: true
to the post’s YAML metadata. In each case,
such posts are rendered locally (e.g. when using the Serve Site add-in), but are
not rendered when building the site for deployment5.
You can use the Update Metadata add-in to, well, update a post’s metadata:
As noted in the blogdown
book, this is primarily
useful for updating categories and tags as it prompts you with those you have
already used in other posts. Changing the date
or slug
(or title
, if it is
used to define your permalinks), or selecting the ‘Rename file if the date is
changed’ option, should be done with caution, as these actions will likely break
any existing links to the post. In general, if a post is new and there are no
other references to it in your blog, it’s safe to change these metatdata at will
(though see caveat in Images and static files, below), but it is best avoided
post publication.
(R) Markdown
When creating a post in blogdown, you have a choice of Markdown versions–the
blogdown book has a
section on this. My
primary motivation for using blogdown is R Markdown support, so .Rmd
is a
natural choice. However, as explained in the blogdown book, posts in .Rmd
format create intermediate .html
files in the content
directory and so, when
writing entirely text-based posts with simple formatting, I will sometimes use
.md
files. However, even if a post does not contain R code that is intended to
be run, it may be useful to use the .Rmd
format so as to take advantage of
formatting options that are only available when using .Rmd
, such as syntax
highlighting and proper footnotes (you can have footnotes in .md
posts but, at
least with the Lithium theme, you don’t get the cute return arrow (↩︎) and in the
source .md
file, you can’t break your footnote over multiple lines).
Files using the alternative R Markdown file format, .Rmarkdown
, have certain
limitations compared to .Rmd
files, and still create intermediate files (in
this case, .md
files), so I will use .Rmd
or .md
.
Images and static files
To insert an image into a post, use the Insert Image add-in (Addins > Insert Image):
Leave the ‘Alternative text’ field blank–blogdown (and/or the Lithium theme) seems to insert the text as body text, rather than creating a HTML alt text tag.
When using the Insert Image add-in, the image is copied to a location in the
static
directory. By default, the directory is the filename of the post
(without extension) plus _files
, and the relevant file path is written into
the post. If the filename of the post is later changed (perhaps when updating
the metadata and selecting the ‘Rename file if the date is changed’ option), the
image file in the static directory is not updated. As the paths to the images in
the post are also not updated, the links to the images will still work, but if
any further images are inserted, a new _files
directory will be created for
the post.
A further consequence of this workflow is that these are one-time actions, not
part of the automated process of building the site. Accordingly, if you have
images (or other assets) in static
that were put there for a now-deleted post,
they will be orphaned. These can be tidied manually, but there is no
straight-forward automated way of clearing them out6.
Code chunks
The great power of R Markdown comes when using code chunks. Code chunks can be
used to run any supported code (R, bash, as well as Python/SQL/etc. if supported
on your system). Typically, in the context of a blogdown site, code will be used
to create output to be included in the post, e.g. a chart–the code is run when
the site is built, and the output is copied as part of the generated static
files to the public
directory. Another use for code chunks is to display code
with syntax highlighting, which is great for readability, particularly for
longer sections of code.
However, by default, code included in a code chunk is run as-is, and there
is no restriction on what it can do. This is potentially Very Important,
depending on what code you include in your code chunks. Say you want to display
the bash command echo Hi > ~/hi.txt
, nicely formatted in a code block with
proper bash syntax highlighting:
echo Hi > ~/hi.txt
You might include the following code chunk in your source R Markdown file:
```{bash}
echo Hi > hi.txt
```
If you did this with default settings, you would be leaving a message to yourself in your root user directory, every time you built your site:
Imagine the mess you could get yourself into if you were writing a post on dangerous terminal commands, say7.
Chunk options
To protect against such issues, we set appropriate chunk options8.
When writing a tutorial-type post such as this one, I often include code that I
ran when implementing the project that I’m documenting, but which, as in the
examples above, I don’t want to run each time the post is rendered (for example,
the git
commands in Initial setup). To display sample code in a chunk
option, but not run it, set the chunk option eval=FALSE
:
```{bash eval=FALSE}
echo Hi > hi.txt
```
To be on the safe side, I set global chunk options for .Rmd
posts by including
the following code chunk immediately after the YAML metadata, right at the top
of the post.
```{r include=FALSE}
knitr::opts_chunk$set(eval=FALSE, warning=FALSE, message=FALSE)
```
In this chunk:
include=FALSE
supresses any output from the code in this chunk being included in the rendered web page (even thoughopts_chunk
doesn’t return anything anyway–better safe than sorry).eval=FALSE
is the important bit: when set toFALSE
, code in chunks is not run. Once this is set as a global option, the code in any code chunks is not run by default. To run the code in a particular chunk, set the chunk optioneval=TRUE
for that chunk.warning=FALSE, message=FALSE
: these chunk options suppress warnings and messages (messages are warning-like messages emitted bymessage()
) in the chunk output. If using R code to create a chart, for example, we don’t want warnings about unknown database field types being imported as character, say.
Finally, for syntax highlighting to work correctly, add the relevant language
code to the chunk. The ‘Insert’ drop-down in a .Rmd
document’s toolbar already
populates this for the languages it supports, but you can also add additional
languages; for example, the chunk options for the config.toml
code block
above look like this:
```{toml}
baseurl = "http://michaelbolger.net/"
...
```
Be sure that any languages you use are also specified in the config.toml
highlightjsLang
parameter9.
Site preview
To preview your site, use the Serve Site add-in–this will start an internal web
server and serve your site locally on your computer. The console output will
tell you the IP address and port to view your site, e.g. 127.0.0.1:4321
.
Committing
This completes the basic setup but before we commit our changes, we should
ignore unnecessary files to keep the remote repository clean. Amend the root
.gitignore
file as follows:
# RStudio defaults
.Rproj.user
.Rhistory
.RData
.Ruserdata
# macOS
.DS_Store
# blogdown generated files
content/**/*.html
public/
We can ignore the public
directory because its contents are generated
automatically. As noted, .Rmd
files clutter up the contents
directory with
corresponding .html
files, so these can be ignored too. The /**/
syntax
ignores matching files in the contents
directory and any sub-directories.
Commit changes using RStudio’s git functionality, or directly at the Terminal:
git add -A
git commit -m "Initial commit of blogdown site."
git push origin master
Hosting setup
To host a website on a Synology, it must be running a web server–this is managed using the Web Station package. On your Synology, install the Web Station package from the Package Center. The default Web Station settings are fine to leave as is.
Deploying your site
To deploy your site:
- Session > Restart R in RStudio.
- Delete the local copy of your site’s
public
directory (don’t worry–it’s automatically re-created). - Run
build_site()
to build the site for deployment. 2022-04-21 update: from version 0.21 ofblogdown
(October 2020),build_site()
no longer recompiles R Markdown files by default. To recompile all R Markdown files when rebuilding the site, usebuild_site(build_rmd = TRUE)
, or one of the otherbuild_rmd
options available. - Delete the default contents of the
web
shared folder on your Synology (index.html
andweb_images
directory), and copy all of the contents of your site’spublic
directory to here. The easiest way to copy across your site is to log in to your Synology and use the File Station app–you can drag and drop from the Finder directly inweb
folder in File Station.
You can now access and browse your site, while on the same network, by entering the IP address of your Synology:
Routing
Update 2020-11-18:
Synology keeps losing its router-assigned static IP address. Current MAC address: 00:11:32:5B:BB:0E. Set static IP address via the Synology instead.
Synology-declared MAC address: 00-11-32-5B-BB-0D Mac address per router: 00:11:32:5B:BB:0E (for ‘wifi-bridge-2’, the switch?)
Your site is now on your Synology, but nobody outside of your network can see it because your domain does not yet point to it. To fix this:
- Your Synology must be accessible from the internet. This means that the appropriate ports must be forwarded from your router to your Synology, and you need to have a way of keeping track of your public IP address (unless you have a fixed IP address, which is generally not the case for most home broadband services).
- The DNS settings for your domain name must ultimately resolve to your Synology.
Port forwarding and DNS
The easiest way forward the relevant ports (80 and 443) is to use Synology’s EZ-Internet wizard10. This should work if you have Universal Plug and Play (UPnP) enabled on your router.
If you choose not to enable UPnP, or if the wizard doesn’t work, you need to
manually forward ports 80 and 443 on your router to your Synology–how to do
this is router-specific, but there should be the facility to forward ports
somewhere within your router’s management page. Port 80 is for regular (http
)
traffic, whereas 443 is for encrypted (https
) traffic. Even though encryption
is not (yet) setup, it’s fine to forward
both ports.
Once port forwarding is complete, your Synology is accessible from the internet, at the IP address allocated to you by your ISP (you can check what this is with Google). Confirm this by navigating to your public IP address–you should see your site.
Dynamic DNS
IP addresses assigned for home broadband services are generally not static. A dynamic DNS service will keep track of your public IP address and let you access your Synology using an unchanging domain name. Synology supports several such services, and offers its own. The walk-through below uses the Synology service, but the steps should be equivalent for others.
First, register for a Synology account11. Follow the prompts to register an account (home user is fine). While registering, I recommend signing up for the NAS-related security advisory emails. I also recommend turning on two-factor authentication, and of course, using a strong, unique password (and a password manager).
Once registered, log into your account on your Synology (Control Panel > Info Center > Synology Account tab). Then go to Control Panel > External Access > DDNS tab and select Add. Complete as appropriate, then select ‘Test Connection’–you should get a ‘Status: Normal’ result. Your public IP address is auto-detected.
Once complete, you should be able to connect to your Synology at your_subdomain.synology.me
.
Domain name routing
You now need to connect your actual domain name with your DDNS domain name. First of all, you need a domain name. If you don’t have one already, I strongly recommend Hover. Once you have registered your domain, select the DNS tab and then ‘Add a Record’:
This adds a ‘CNAME’ record to your domain name’s DNS settings. In this example
www.michaelbolger.net
points to michaelbolger.synology.me
, which through the
Synology DDNS service resolves to your home network’s current public IP address
(and hence to your Synology, via port 80 that you forwarded from your router). Your site should now be accessible at www.yourdomain.net
.
Note that you need to include the www
part of the domain. Forwarding an apex
domain (i.e., michaelbolger.net
, without any subdomain) requires using
Cloudflare to manage your domain’s DNS settings,
which will be the subject of a later
post.
Updating
To update, follow the steps set out in Deploying your site, above, bearing in mind that any changes you deploy are now available for all the world to see12. To recap:
- Session > Restart R in RStudio.
- Delete the local copy of your site’s
public
directory. - Run
build_site()
to build the site for deployment. - Copy all of the contents of your site’s
public
directory to theweb
directory of your site (select ‘Overwrite’).
If you have made any changes that change filenames/paths or delete files (e.g.
removing an inserted image), make sure you manually delete the corresponding
files in your web
directory, otherwise they will still be available, even if
they are not linked from anywhere else on your site. A belt-and-braces approach
is delete the entire contents of your web
directory and re-upload the whole
site.
This overwrite/delete and re-upload deployment workflow is ineffiient, and may become cumbersome as your site grows larger–it won’t all be available for the period between the delete and the completion of the upload, for example–but should be manageable for sites with a modest number of pages/posts. We will look at more efficient deployment methods in a later post.
One gotcha to look out for is, if you are making edits to a post that has already been published, when you re-upload your site and navigate to the post in your browser, you may still see the old version that was cached by your browser. If you reload, it should load the new version (unless you are being served a cached-version from elsewhere, in which case you might just have to wait until that cache is refreshed).
Themes
This is down here because I think your first priority should be getting a working site up and running, rather than agonising over selecting and tweaking a theme.
When selecting a theme, consider what you want, and in what order. I wanted:
- blogdown/R Markdown/RStudio compatability
- Responsive design
- Top navigation elements (i.e., no side-bar)
- Syntax highlighting
- Disqus support
- Sparse design aesthetic
- Custom fonts
Then, focus on finding a theme that matches the functionality you want, rather than one you find aesthetically pleasing. I initially settled on themes that matched the design aesthetic I was looking for, for example Introduction, but this proved far too complex for me to get it to play well with blogdown. I then tried Black & Light which is a much simpler theme, but even then I spent fruitless hours trying to get it to work seamlessly with blogdown. So finally I came back to the default Lithium theme, which supports all of the core functionality I want, and I will work on tweaking its aesthetics to make it more to my liking.
Next steps
- Add TLS encryption with Let’s Encrypt.
- Enable Disqus commenting on posts (but not static pages).
- Use
git
functionaltiy to maintain link to upstream theme code, while allowing for tweaks specific to this site. - Use custom fonts.
- Tweak Lithium theme to make the look and feel more to my liking.
I have a DS916+ Synology, with DSM version 6.2.1, but any modern Synology with a recent version of DSM should work.↩︎
I know: I did.↩︎
If you did not enable the ‘Add the example site of the theme’ option when creating the new website project, copy the theme
config.toml
(e.g.themes/hugo-lithium/exampleSite/config.toml
) to your site’s root directory, overwriting the minimal defaults file that was generated during the New Project process.↩︎There is a references to Google Analytics in
partials/footer.html
template, but this is inactive without a valid Google Analytics ID.↩︎Note, however, that any images for your draft post that have been placed in the
static
directory (by using the Insert Image add-in) are copied to thepublic/static
directory, and so can be found on your deployed site, although they will not be linked anywhere.↩︎In the RStudio Build tab, in the More dropdown, there is a ‘Clean All’ command, however beware of this. This calls the
clean_site
function from the rmarkdown package, which deletes unnecessary files (thepublic
directory and the.html
file counterparts to any.Rmd
posts in thecontents
directory), but it also deletes the contents of thestatic
directory. If you used the ‘Insert Image’ add-in, this is where the image files are copied, so you will lose all your images. More generally, the RStudio Build tab is an interface to the rmarkdown package rather than to blogdown, and so is best ignored.↩︎It’s actually even worse if you are using the Serve Site add-in to live preview your site–the site is built (and hence, any code in code chunks executed) every time you save, so a stray
rm -rf /
in a code chunk could delete your whole filesystem the next time you save.↩︎Chunk options are an essential part of what makes R Markdown so useful, but they are extensive and can be a bit overwhelming, so I always keep a tab with the chunk options reference page handy.↩︎
Specifying
toml
in chunk options seems to work insofar as syntax highlighting is applied, but with a warning when building the site (Unknown language engine 'toml' (must be registered via knit_engines$set())
). Seeing as it works, I’ve not investigated this further.↩︎There is another way to access Synologys from the internet, which is to use the Quickconnect feature, however this does not seem to work for websites hosted with Web Station–it’s primarily intended for accessing built-in Synology services like Drive without having to deal with port forwarding, DDNS etc.↩︎
The Synology account registration page can be a bit flakey–the registration process would not complete for me with Safari on a Mac, but worked OK with Chrome on a PC.↩︎
See Hosting multiple sites on one Synology for details on how to set up a staging version of your website alongside your live one.↩︎