Bookstack is a Wiki app. But it has several features that make it a good place to store internal documentation:
-
It supports multiple Wikis (which it calls "books"). It allows organization of Wikis (by placing "books" in "shelves"). This means it's possible to put internal docs for multiple services in one place, while allowing each service to remain self-contained.
-
Searching and linking are built in. Content is indexed automatically, so you can use one search page to search (if you want) across everything you can access. It's also possible to make intra- and inter-book links.
-
Built-in diagrams. Bookstack uses diagrams.net for diagrams, storing the diagram data as page data.
-
It can be connected to SAML, so we don't need to set up separate authentication in order to run it.
-
Bookstack has an API, and can also call Webhooks when things happen.
Bookstack uses a MariaDB databasae to store things like articles, and it uses a path on the filesystem to store things like attachments.
Bookstack, MariaDB, etc. all have good container implementations, so for portability, we use those!
-
The Containerhost: Our containerhost runs LXC, and inside that VM we run Docker. Within that system, system-wide environment variables are used to pass important paths to Docker, which passes them into the containers.
In order to run Docker inside of an LXC container-based VM, three security settings must be enabled:
-
security.nesting
: This allows creating containers inside of containers. -
security.syscalls.intercept.mknod
: This is needed by Systemd. -
security.syscalls.intercept.setxattr
: Extended attributes are used by Docker'soverlay2
storage driver, which uses overlay file systems to mount a container image.
-
-
The Bookstack-with-Let'sEncrypt container: This image starts with the LinuxServer.io Bookstack container, and adds support for Let's Encrypt. It has its own repository.
-
The MariaDB container: This runs our database! This is the
mariadb:10.11.2
image, pulled from Docker Hub. -
The Restic container: This handles our backups. This image starts with the Restic container, and adds some packages & scripts to handle backups of the database. It has its own repository.
-
Docker Compose: Docker Compose is used to bring up the Bookstack environment, and to connect containers to their ports and filesystems.
Why use Docker Compose instead of Kubernetes? Docker compose is a lot simpler to configure,
-
TCP Ports 80 & 443: The Bookstack container uses both ports 80 and 443. Port 80 is specifically required by the ACME HTTP-01 challenge, used by Let's Encrypt.
-
A backend network: Docker Compose creates a backend network, used solely to connect the Bookstack container to port 3306 on the MariaDB container. The MariaDB container cannot be access from the outside world.
-
A DB volume: Named
bookstack-db
, this stores the database's data files. This must only be accessed by MariaDB. If you want to take backups, use appropriate backup tools! -
A data volume: Named
bookstack-data
, this stores everything else related to Bookstack, except for code. The specific paths to things are explained in a following section. -
Vault: Stanford Vault is used to store all of the secrets needed by the container. That being said, most secrets are only pulled from Vault at install-time. A Vault AppRole is used to provide access to the secrets.
-
A Google Cloud project and bucket: This stores the backups created by Restic.
The following paths exist in the VM's data volume:
-
BOOKSTACK_APP_KEY.txt
: The API key to get admin access to Bookstack, through it's API. -
cert
: This has a self-signed TLS cert and key. They are not used, and should be left alone. -
log/letsencrypt
: Certbot logs are placed here. -
log/nginx
: Nginx web server access and error logs are placed here. -
log/php/error.log
: PHP-level logs, including logs from FPM (the PHP FastCGI implementation), are here. -
www/laravel.log
: This has the logs from the PHP Laravel application framework. This is the primary application log. -
nginx
: All of Nginx's configuration is here. It should be left alone. -
php
: PHP's configuration is here. It should be left alone. -
www
: TODO
The following variables are needed to create the Bookstack VM:
-
BOOKSTACK_NAME
: This is the hostname of the LXC VM. For example,rc-bookstack-srcf
. This will form the hostname part of the app's URL, under the stanford.edu domain. -
LETS_ENCRYPT_EMAIL
: This is the email that Let's Encrypt will use for notifications. For example,[email protected]
. -
VAULT_APPID
: The Vault AppRole ID. -
VAULT_ADDR
: The address (URL) of the Vault server. -
VAULT_MOUNT
: The mountpoint of the Key-Value Secrets Engine. -
VAULT_BASE
: The base path for our secrets. -
GOOGLE_PROJECT_ID
: The ID of the Google Cloud project which stores Restic backups. -
GOOGLE_RESTIC_BUCKET
: The name of the Google Cloud Storage bucket which stores Restic backups.
The following environment variables are used by the Bookstack containers:
-
BOOKSTACK_URL
: The URL to the Bookstack site. It's formed by takingBOOKSTACK_NAME
, appending domainstanford.edu
, ashttps://${BOOKSTACK_NAME}.stanford.edu
. -
LETS_ENCRYPT_CONTACT
: As above, fromLETS_ENCRYPT_EMAIL
. -
LETS_ENCRYPT_TOS_AGREE
: Hard-coded toyes
. When using the LXC deployment script—described later on this page—you are prompted to read & agree to the Let's Encrypt Terms of Service. -
LETS_ENCRYPT_STAGING
: This is optional. If it's set, Certbot will use the Let's Encrypt staging environment. -
BOOKSTACK_TZ
: The Olsen time zone ID for the Bookstack application. It influences how times are displayed. Hard-coded toUS/Pacific
. -
BOOKSTACK_AUTH_METHOD
: The authentication method to use. This is eitherstandard
(username/password) orsaml2
. -
BOOKSTACK_SAML_IDP_NAME
: The name of the SAML-based login method that Bookstack will present to users. Hard-coded to "Stanford Login". -
BOOKSTACK_SAML_IDP_ENTITYID
: The URL to the SAML IdP's metadata. Hard-coded to the Stanford Login IdP metadata. -
BOOKSTACK_SECRET_SAML_CERT
: This is the path to the file containing the Bookstack SAML SP certificate. -
BOOKSTACK_SECRET_SAML_KEY
: This is the path to the file containing the Bookstack SAML SP private key. -
BOOKSTACK_SAML_DUMP_USER_DETAILS
: This variable is normally not set. If it is set, andBOOKSTACK_AUTH_METHOD
is set tosaml2
: then the normal Bookstack app will be disabled. Instead, when you log in, you will be presented with a JSON-format dump of all the attributes received from the SAML IdP, along with the values of what Bookstack will be using for its attributes (exernal_id
,name
,email
, etc.). -
BOOKSTACK_DEBUG
: This variable is normally not set. If it is set, logs of additional detail—including secrets—are output to logs. -
BOOKSTACK_SECRET_DB_BOOKSTACK_PASSWORD
: In MariaDB, a userbookstack
is given full access to databasebookstack
. This is the path to the file containing the password. -
BOOKSTACK_SECRET_DB_ROOT_PASSWORD
: This is the path to the file containing the MariaDB root password. -
BOOKSTACK_SECRET_RESTIC_PASSWORD
: This is the path to the file containing the password of the Restic repository. The Restic repository contains all of the backup data, and is encrypted using this password. If you lose or change this password, all backups will be lost! -
BOOKSTACK_SECRET_RESTIC_GOOGLE_CREDENTIALS
: This is the path to the JSON file containing credentials for a Google Cloud service account. This, combined with the Project ID and bucket name, will be used to store backups via Restic. -
GOOGLE_PROJECT_ID
: As above, fromGOOGLE_PROJECT_ID
. -
GOOGLE_RESTIC_BUCKET
: As above, fromGOOGLE_RESTIC_BUCKET
. -
MAIL_HOST
: The SMTP server that receives email. It must support TLS. -
MAIL_PORT
: The port to use for SMTP. -
MAIL_FROM
: The email address to use for sending mail. -
MAIL_FROM_NAME
: The name to use with the sending email address.
The following paths are pulled from Vault, off of some base path (represented
here as BASE
:
-
BASE/saml
has two keys, which must be updated in sync:-
cert
contains the SAML SP certificate -
key
contains the SAML SP key
-
-
BASE/db
has two passwords, used by the Database server:-
bookstack
contains the password for thebookstack
database user -
root
contains the MariaDB root password
-
-
BASE/restic
has a password and a key, used by Restic:-
password
contains the password used to encrypt Restic data in the cloud. -
gcp
contains the JSON Google Cloud Service Account credential that Restic will use.
-
See the docker-bookstack-certbot repo for information on how the container image is built.
For MariaDB, we don't modify the container, we just rely on the MariaDB containers provided by MariaDB in Docker Hub.
See the booksack-restic repo for information on how the Restic container image is built.
This content has its own page!
There are three containers:
-
The
bookstack-db
container, providing thedb
service, runs MariaDB. -
The
bookstack-app
container, providing theapp
service, runs Bookstack. -
The
bookstack-restic
container, providing therestic
service, runs Restic.
When they're running, you can access the container environments with docker exec -it CONTAINER_NAME bash
. You can also read logs from the container using
docker logs CONTAINER_NAME
.
There are two data volumes:
-
The
bookstack-data
volume has the files from thebookstack-app
container. -
The
bookstack-db
volume has the MariaDB database files from thebookstack-db
container.
The command docker volume inspect VOLUME_NAME
command gives you, among other
things, the path to volume files. Don't mess with these files while its
container is running!
This content has its own page.
Remember, before upgrading anything, be sure to have a backup of everything!
Upgrading the MariaDB container involves shutting down the entire Docker
Compose stack, updating the docker-compose.yaml
file, and bringing up just
the database. You then run the upgrade command, and bring up the Bookstack
application.
The entire stack must be shut down, because everything in the stack relies on the database.
To stop the stack, run docker-compose down
, which will shut down everything,
but leave the volumes intact. It's then safe to update to the newer
docker-compose.yaml
file.
Next, run docker-compose up --no-start
. That creates everything, but does
not start any services. This will download the new MariaDB container image,
but not start anything.
Next, run docker-compose start -d db
. That will start just the MariaDB
server. Run docker logs -f bookstack-db
to see logs from DB server start.
Once you see the message "mariadbd: ready for connection.", it is safe to
continue.
The last part of the upgrade is to run mariadb-upgrade
within the container.
To do so, run docker-compose exec db mariadb-upgrade --password=$(cat /run/bookstack/db-root)
. The tool will either finish the upgrade, or will
report no upgrade is needed. In particular, minor upgrades might not need any
table upgrades.
Finally, run docker-compose up -d
to bring up the rest of the stack (the
Bookstack server)!
Upgrading the Bookstack container involves updating the Dockerfile of docker-bookstack-certbot, waiting for it to rebuild, restarting the stack, and running post-upgrade commands.
First, update the Dockerfile
in the
docker-bookstack-certbot
repository, and wait for the container image to build.
Next, run docker-compose pull app
to have Docker pull down the updated
container image. If no new image is found, wait a while and try again. At
this point, the stack is still running on the older image.
Run docker-compose up -d
, which should trigger a recreation and restart of the
Bookstack container. Run docker logs -f bookstack-app
to see logs from DB
server start. The updated Bookstack service should see that this is an
upgrade, and run a schema migration (that covers the php artisan migrate
command from the upstream Bookstack upgrade
guide).
The last part of the upgrade is to clear system caches, which can be done with three commands:
-
docker-compose exec app php /app/www/artisan cache:clear
-
docker-compose exec app php /app/www/artisan config:clear
-
docker-compose exec app php /app/www/artisan view:clear
Finally, depending on the upgrade, you might also need to run additional steps in the upstream Bookstack upgrade guide).
Upgrading the Restic container involves upgrading the Dockerfile of bookstack-restic, waiting for it to rebuild, restarting the stack, and running post-upgrade commands.
First, update the Dockerfile
in the
bookstack-restic
repository, and wait for the container image to build.
Next, run docker-compose pull restic
to have Docker pull down the updated
container image. If no new image is found, wait a while and try again. At
this point, the stack is still running on the older image.
Run docker-compose up -d
, which should trigger a recreation and restart of the
Restic container. There is no specific command needed to run an upgrade.
The last part of the upgrade is to confirm connectivity is still good, and that the repository is good. This can be done with two commands:
-
docker-compose exec restic restic snapshots --latest 1
will connect to the repository and return the information from the latest snapshot. -
docker-compose exec restic restic check
does a consistency check of Restic's data, without going as far as downloading all of the pack files.