Build WriteFreely from source

WriteFreely is open source, lightweight blogging platform written in Go programming language.

Motivation to build WriteFreely

While absolute king of blogs is WordPress, which in fact powers somewhat 30% of all websites, some say it is overloaded, choosing perfect theme is extremely difficult and some (especially free) plugins are poisoned with different kinds of tracking. I do use WordPress on my main site as well, but over the years I' ve figured it seriously takes time to post an article (and I'm not yet even aquainted with Gutenberg).

WriteFreely here is a nice alternative claiming to offer a distraction-free writing experience. While supporting HTML, software mainly relies and endorses markdown. I do not plan ditching WordPress anytime soon, but I was very curious to try out lightweight platform that natively federates.

If you already have WriteFreely installed, see build and upgrade from source guide.


Prerequisites

This guide details how to build decent version of WriteFreely from source on CentOS 7 machine.

Instructions should work for other Linux distributions as well but bear in mind that some paths and details (yum vs. apt, etc.) might differ across systems.

Git version 2

It turns out having Git version 2 is required to build WriteFreely successfully. I was able to pull updates using version 1.8, but doing fresh sync didn't work afterall. So make sure you install Git version 2 on CentOS 7 machine.

First remove existing version:

yum remove git*

Note: this will remove Go as well because it is dependent on Git (therefore I am detailing Git first).

Now set up IUS repository (created by folks at Rackpace) based on this instruction:

yum install https://repo.ius.io/ius-release-el7.rpm https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm

Notice it also adds EPEL repository.

Previously it was git2u-all package, but as of November 11th, 2020 it was not found:

No package git2u-all available.

Version is actually now added into package so use following:

yum install git224-all

Also make sure to install Git support for large files:

yum install git-lfs

Check version:

git --version

And if something like git version 2.24.3 is shown, you are good to go.

Node.js

Make sure EPEL repository is installed:

yum install epel-release

No need to repeat – EPEL was already installed with IUS setup command.

Install Node.js and package manager npm:

yum install nodejs npm

Note: as of WriteFreely v0.13.0 Prose editor is included – it requires recent NodeJS version, therefore on CentOS 7 following steps must be performed to avoid below error showing up when trying to make ui later:

npm ERR! Linux 3.10.0-1160.25.1.el7.x86_64
npm ERR! argv "/usr/bin/node" "/bin/npm" "run-script" "build"
npm ERR! node v6.17.1
npm ERR! npm  v3.10.10
npm ERR! code ELIFECYCLE
npm ERR! prose@1.0.0 build: `webpack --mode production`
npm ERR! Exit status 2
npm ERR!
npm ERR! Failed at the prose@1.0.0 build script 'webpack --mode production'.
npm ERR! Make sure you have the latest version of node.js and npm installed.
npm ERR! If you do, this is most likely a problem with the prose package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR!     webpack --mode production
npm ERR! You can get information on how to open an issue for this project with:
npm ERR!     npm bugs prose
npm ERR! Or if that isn't available, you can get their info via:
npm ERR!     npm owner ls prose
npm ERR! There is likely additional logging output above.

First, check current versions on system:

# node --version
v6.17.1
# npm --version
3.10.10
# which node
/usr/bin/node
# which npm
/usr/bin/npm

Too old, need to upgrade using official repository:

# Node.js v16.x

# As root
curl -fsSL https://rpm.nodesource.com/setup_16.x | bash -

# Or as no root privileges
curl -fsSL https://rpm.nodesource.com/setup_16.x | sudo bash -

Remove old, install new:

yum remove nodejs npm

yum install nodejs

And check versions:

# node --version
v16.3.0
# npm --version
7.15.1

Good to go now – make ui should not fail afterwards.

Go

Version 1.10+ is required. Download from Go website or (preferably) install using package manager:

yum install golang go-bindata

Because you have already added epel repository, version should be decent (at the time of writing 1.13.6):

go version go1.13.6 linux/amd64

Less.js

Less.js (source) is needed to convert Less stylesheets to CSS.

npm install -g less
/usr/bin/lessc -> /usr/lib/node_modules/less/bin/lessc
- ycssmin@1.0.1 node_modules/less/node_modules/ycssmin
/usr/lib
└── less@3.9.0

npm install -g less-plugin-clean-css
/usr/lib
└── less-plugin-clean-css@1.5.1

From above output make sure to remember /usr/bin/lessc – we will need it later.

MySQL or MariaDB

WriteFreely requires MySQL (MariaDB) 5.6+. This can be an issue with CentOS / RHEL as at the time of writing it was shipping version 5.5 which fails database initialization script on tables accesstokens, appcontent, posts and users. See my comment on issue #22.

Possible solution would be adding official MariaDB repository and installing decent version (v. 10 at the time of writing).

Create system user to run WriteFreely

For security reasons it is well worth creating unprivileged system user to run Write freely. Create new user executing:

adduser writefreely

Up to this point we've been using root or sudo user to install prerequisites. Below commands should be run under unprivileged user. Switch to it by running:

su - writefreely

Make sure user has following paths in ~/.bash_profile:

export GOBIN="$HOME/go/bin"
export GOPATH="$HOME/go"

If not, add them and propagate those changes:

source /etc/profile && source ~/.bash_profile

Ready to start building

ArchLinux has WriteFreely packaged – page provides good summary of dependencies. Anyhow at this point you should be good to go with prerequisites to start building.

Download WriteFreely sources

Pull sources from GitHub:

go get -d -u -v github.com/writeas/writefreely

Parameters explained below:

-d - do not build after downloading
-u - update
-v - verbose for details

Sources of WriteFreely and included libraries should now be available at ~/go/src/github.com/writeas/writefreelycd into this folder.

If you would like to use my fork, initialize sources with following command:

go get -d -u -v code.gyt.is/writefreely

In this case afterwards cd into folder ~/go/src/code.gyt.is/writefreely.

Starting version 0.9 it is needed to explicitly tell to build with modules:

# If running Go 1.11+
export GO111MODULE=on

Otherwise such error would come up with next step:

../../sitemap.go:23:22: not enough arguments in call to stm.NewSitemap
	have ()
	want (int)
../../sitemap.go:78:4: index must be non-negative integer constant
../../sitemap.go:78:4: cannot use p.Post.Slug.NullString.String (type string) as type []interface {} in array or slice literal
../../sitemap.go:79:4: index must be non-negative integer constant
../../sitemap.go:79:18: cannot use "weekly" (type string) as type []interface {} in array or slice literal
../../sitemap.go:80:4: index must be non-negative integer constant
../../sitemap.go:80:4: cannot use true (type bool) as type []interface {} in array or slice literal
../../sitemap.go:81:4: index must be non-negative integer constant
../../sitemap.go:81:4: cannot use p.Post.Updated (type time.Time) as type []interface {} in array or slice literal
../../sitemap.go:86:33: index must be non-negative integer constant
../../sitemap.go:86:33: too many errors
make: *** [build] Error 2

Go ahead and make:

make build   # Compile the application

At this point writefreely executable should be created at ~/go/src/github.com/writeas/writefreely/cmd/writefreely. Don't cd into that directory directly but run config.ini creation wizard:

./cmd/writefreely/writefreely --config # Create configuration file

Above step is not needed if you are updating from previous version – in such case keep config.ini file from ~/go/src/github.com/writeas/writefreely!

If doing fresh install, initialize database:

# (if you chose Multi-user setup) Import the schema with:
./cmd/writefreely/writefreely --init-db

Or upgrade if was running WriteFreely before:

./cmd/writefreely/writefreely  --migrate

If you are upgrading from previous version, follow much shorter guide how to upgrade WriteFreely from source.

Proceed with installation:

make install # Generates encryption keys; installs LESS compiler

At this point we need to generate CSS files. Problem is that LESS compiler Makefile needs little commenting – edit file less/Makefile leaving lessc path:

# ifeq ($(shell which lessc),/usr/bin/lessc)
	LESSC=/usr/bin/lessc
# else
#	LESSC=node_modules/.bin/lessc
#endif

I didn't have time to fix script yet, but on my system make ui was referring to node_modules/.bin/lessc instead of /usr/bin/lessc therefore error was thrown. So with modification it is good to go:

make ui # Generates CSS (run this whenever you update your styles)

Update on lessc issue: I have filled a pull request to include /bin/lessc to less/Makefile. It was merged and above manual fix is not needed.

And to check if application runs:

make run     # Runs the application

If application is running, quit it by hitting CTRL+C and set up system service. Before that just make sure writefreely binary appears at ~/go/bin folder. Command make run should have placed it there already but if not, copy binary from cmd/writefreely.

Install system service

Define system service in file /etc/systemd/system/writefreely.service:

[Unit]
Description=Write Freely Instance
After=syslog.target network.target mariadb.service

[Service]
Type=simple
StandardOutput=syslog
StandardError=syslog
WorkingDirectory=/home/writefreely/go/src/github.com/writeas/writefreely
ExecStart=/home/writefreely/go/bin/writefreely
Restart=always
; run as writefreely user
User=writefreely
Group=writefreely

[Install]
WantedBy=multi-user.target

Mind WorkingDirectory – in this folder WriteFreely will expect config.ini file as well as folders containing templates and static files.

Pay attention – service should run under unprivileged user!

Refresh services configuration:

systemctl daemon-reload

Now service can be started:

systemctl start writefreely

And enable service to start on next system restart:

systemctl enable writefreely

Setup reverse proxy (Nginx)

Nginx texmplate from WriteFreely guide should be good to go:

server {
    listen 80;
    listen [::]:80;

    server_name example.com;

    gzip on;
    gzip_types
      application/javascript
      application/x-javascript
      application/json
      application/rss+xml
      application/xml
      image/svg+xml
      image/x-icon
      application/vnd.ms-fontobject
      application/font-sfnt
      text/css
      text/plain;
    gzip_min_length 256;
    gzip_comp_level 5;
    gzip_http_version 1.1;
    gzip_vary on;

    location ~ ^/.well-known/(webfinger|nodeinfo|host-meta) {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_pass http://127.0.0.1:8080;
        proxy_redirect off;
    }

    location ~ ^/(css|img|js|fonts)/ {
				root /home/writefreely/go/src/github.com/writeas/writefreely/static;
        # Optionally cache these files in the browser:
        # expires 12M;
    }

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_pass http://127.0.0.1:8080;
        proxy_redirect off;
    }
}

This is default for unsecure connection on port 80. I recomment setting up WriteFreely on secure server block and redirect port 80 connections (both with and without www.) as well as secure connection with www. to straightforward instance domain – in my case it is fedi.dev.

SELinux

If SELinux is enabled in your CentOS / RHEL installation (default), below error will pop up in Nginx error log:

2019/03/02 01:19:46 [crit] 4508#0: *1 connect() to 127.0.0.1:8080 failed (13: Permission denied) while connecting to upstream, client: xxx.xxx.xxx.xxx, server: fedi.dev, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:8080/",host: "fedi.dev"

In /var/log/audit/audit.log this message will follow:

type=AVC msg=audit(1551482556.752:6031): avc:  denied  { name_connect } for  pid=4576 comm="nginx" dest=8080 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:http_cache_port_t:s0 tclass=tcp_socket permissive=0

Create an exception by running:

setsebool httpd_can_network_connect on -P

As of April, 2020 I discovered that fresh CentOS 7 installation would cause more issues when trying to setup WriteFreely. In default nginx log /var/log/nginx/error.log (or another depending on your server config) following errors started popping up:

2020/04/02 15:02:19 [error] 9409#0: *12 open() "/home/writefreely/go/src/code.gyt.is/writefreely/static/css/write.css" failed (13: Permission denied), client: 10.0.2.2, server: _, request: "GET /css/write.css HTTP/1.1", host: "127.0.0.1:9980"

As Linux file permissions were set properly, this suggested SELinux is still kicking in. After a lot of messing around I figured it is easier to generate rules based on errors and warning generated.

First install some utilities:

yum install -y policycoreutils-python

On fresh CentOS 7 machine I could see following in the logs:

# cat /var/log/audit/audit.log | grep nginx | grep denied
type=AVC msg=audit(1585828926.295:187): avc:  denied  { read } for  pid=9411 comm="nginx" name="write.css" dev="dm-2" ino=2150102457 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0
type=AVC msg=audit(1585828926.296:188): avc:  denied  { read } for  pid=9411 comm="nginx" name="h.js" dev="dm-2" ino=3221225570 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0
type=AVC msg=audit(1585828926.418:189): avc:  denied  { read } for  pid=9411 comm="nginx" name="h.js" dev="dm-2" ino=3221225570 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0
type=AVC msg=audit(1585828926.424:190): avc:  denied  { read } for  pid=9411 comm="nginx" name="webfont.js" dev="dm-2" ino=3221225576 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0
type=AVC msg=audit(1585828932.741:191): avc:  denied  { read } for  pid=9409 comm="nginx" name="write.css" dev="dm-2" ino=2150102457 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0
type=AVC msg=audit(1585828933.608:192): avc:  denied  { read } for  pid=9409 comm="nginx" name="write.css" dev="dm-2" ino=2150102457 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0
type=AVC msg=audit(1585828934.757:193): avc:  denied  { read } for  pid=9409 comm="nginx" name="write.css" dev="dm-2" ino=2150102457 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0
type=AVC msg=audit(1585828939.390:194): avc:  denied  { read } for  pid=9409 comm="nginx" name="write.css" dev="dm-2" ino=2150102457 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0

Now as those errors are present, run several times following commands:

cat /var/log/audit/audit.log | grep nginx | grep denied | audit2allow -M nginx
semodule -i nginx.pp

This website had good suggestion to run above commands in case 403 forbiddden error still comes up several times to make sure all forbids are captured by audit2allow.

There is a guide how to create SELinux rules from audit log events I've put together, check it out.

That's it, you have successfully built and launched WriteFreely on CentOS :)

Make sure to create admin user:

~/go/bin/writefreely --create-admin username:password

And start writing!

My fork

I do have a fork of WriteFreely on GitHub for pull request purposes. While there is no good reason you might clone from my repository instead of official one, for the reference here it is:

go get -d -u -v code.gyt.is/writefreely

Notice I use code.gyt.is as placeholder. It is nothing but static website with few meta tags to tell go get where to pull code from. In essence it actually points to my repository on GitHub, but just in case I want to switch to (most likely) on-premises Gitea, I won't need to update this documentation. Sources are pulled to following folder then:

~/go/src/code.gyt.is/writefreely

In case certain branch is preferred, just checkout it, e.g.:

git checkout oauth-gitea

And start building from here.

Update: my pull request to add Gitea as OAuth client in WriteFreely was merged into develop!

Further reading

Feedback

Comments or any feedback? Reply me @gytis@mastodon.lt on fediverse or e-mail g@gyt.is.

Thanks

Updates

Tags: #server #go #golang #less #build #writefreely #nginx #centos #rhel #selinux


My name is Gytis Repečka, I am Solution Architect and data professional. I enjoy using, promoting and contributing to open source software and love communicating about tech to both advanced and non-tech people. Visit Inretio for consulting services. Comment by mentioning me @gytisrepecka@social.gyt.is on Fediverse.