bup remote server docker-compose config with CIFS-mounted backup store

In our previous post How to setup a “bup remote” server in 5 minutes using docker-compose we outlined how to setup your own bup remote server using docker-composeRead that post before this one!

This post provides an alternate docker-compose.yml config file that mounts a remote CIFS directory as /bup backup directory instead of using a local directory. This is most useful when using a NAS and a separate bup server.

For this example, we’ll mount the CIFS share //10.1.2.3/bup-backups with user cifsuser and password pheT8Eigho.

Note: For performance reasons, the CIFS server (NAS) and the bup server should be locally connected, not via the internet.

# Mount the backup volume using CIFS
# NOTE: We recommend to not use a storage mounted over the internet
# for performance reasons. Instead, deploy a bup remote server locally.
volumes:
  bup-backups:
    driver_opts:
      type: cifs
      o: "username=cifsuser,password=pheT8Eigho,uid=1111,gid=1111"
      device: "//10.1.2.3/bup-backups"

version: "3.8"
services:
  bup-server:
    image: ulikoehler/bup-server:latest
    environment:
      - SSH_PORT=2022
    volumes:
      - ./dotssh:/home/bup/.ssh
      - ./dropbear:/etc/dropbear
      # BUP backup storage: CIFS mounted
      - bup-backups:/bup
    ports:
      - 2022:2022
    restart: unless-stopped

 

Posted by Uli Köhler in bup, Docker, Networking

How to setup a “bup remote” server in 5 minutes using docker-compose

The bup backup system implements remote backup on a server by connecting via SSH to said server, starting a bup process there and then communicating via the SSH tunnel.

In this post, we’ll setup a server for bup remote backup based on our ulikoehler/bup-server image (which contains both bup and dropbear as an SSH server).

1. Initialize the directory structure & create SSH keyset to access the server

I recommend doing this in /opt/bup, but in principle, any directory will do.

mkdir -p dotssh bup
# Generate new elliptic curve public key
ssh-keygen -t ed25519 -f id_bup -N ""
# Add SSH key to list of authorized keys
cat id_bup.pub | sudo tee -a dotssh/authorized_keys
# Fix permissions so that dropbear does not complain
sudo chown -R 1111:1111 bup
sudo chmod 0600 dotssh/authorized_keys
sudo chmod 0700 dotssh

1111 is the user ID of the bup user in the VM.

2. Create docker-compose.yml

Note: This docker-compose.yml uses a local backup directory – you can also mount a CIFS directory from e.g. a NAS device. See bup remote server docker-compose config with CIFS-mounted backup store for more details.

version: "3.8"
services:
  bup-server:
    image: ulikoehler/bup-server:latest
    environment:
      - SSH_PORT=2022
    volumes:
      - ./dotssh:/home/bup/.ssh
      - ./dropbear:/etc/dropbear
      # BUP backup storage:
      - ./bup:/bup
    ports:
      - 2022:2022
    restart: unless-stopped

3. Startup the container

At this point, you can use docker-compose up to startup the service. However, it’s typically easier to just use TechOverflow’s script to generate a systemd script to autostart the service on boot (and start it right now):

curl -fsSL https://techoverflow.net/scripts/create-docker-compose-service.sh | sudo bash /dev/stdin

When you run docker-compose logs -f, you should see a greeting message from dropbear such as

bupremotedocker-bup-remote-1  | [1] Dec 25 14:58:20 Not backgrounding

4. Create a .ssh/config entry on the client

You need to do this for each client.

Copy id_bup (which we generated earlier) to each client into a folder such as ~/.ssh. Where you copy it does not matter, but the user who will be running the backups later will need access to this file. Also, for that user you need to create a .ssh/config entry telling SSH how to access the bup server:

Host BupServer
    HostName 10.1.2.3
    User bup
    Port 2022
    IdentityFile ~/.ssh/id_bup

Set HostName to the IP or domain name of the host running the docker container.
Set User to bup. This is hard-coded in the container.
Set Port to whatever port you mapped out in docker-compose.yml. If the ports: line in docker-compose.ymlis - 1234:2022, the correct value for Port in .ssh/config is 1234.
Set IdentityFile to whereever id_bup is located (see above).

Now you need to connect to the bup server container once for each client. This is both to spot issues with your SSH configuration (such as wrong permissions on the id_bup file) and to save the SSH host key of the container as known key:

ssh BupServer

If this prompts you for a password, something is wrong in your configuration – possibly, your are connecting to the wrong SSH host since the bup server container has password authentication disabled.

5. Connect using bup

Every client will need bup to be installed. See How to install bup on Ubuntu 22.04 and similar posts.

You have to understand that bup will need both a local directory (called the index) and a directory on the bup server called destination directory.  You have to use one index directory and one destination directory per backup project. What you define as backup project is up to you, but I strongly recommend to use one backup project per application you backup, in order to have data locality: Backups from one application belong together.

By convention, the /bup directory on the server (i.e. container) is dedicated for this purpose (and mapped to a directory or volume outside of the container).

On the local host, I recommend using either /var/lib/bup/project.index.bup or ~/bup/project.index.bup and let bup auto-create project-specific directories from there. If you use a special user on the client to do backups, you can also place the indexes. If the index is lost, this is not an issue as long as the backup works (it just will take a few minutes to check all files again). You should not backup the index directory.

There is no requirement for the .bup or .index.bup suffix but if you use it, it will allow you to quickly discern what a directory is and whether it is important or nor.

In order to use bup, you first need to initialize the directories. You can do this multiple times without any issue, so I do it at the start of each of my backup scripts.

bup -d ~/buptest.index.bup init -r BupServer:/bup/buptest.bup

After that, you can start backing up. Generally this is done by first running bup index (this operation is local-only) and then running bup save (which saves the backup on the bup remote server).

bup -d ~/buptest.index.bup index . && bup save -r BupServer:/bup/buptest.bup -9 --strip-path $(pwd) -n mybackup .

Some parameters demand further explanation:

  • -9: Maximum compression. bup is so fast that it hardly makes a difference but it saves a ton of disk space especially for text-like data.
  • --strip-path $(pwd) If you backup a directory /home/uli/Documents/ with a file /home/uli/Documents/Letters/Myletter.txt this makes bup save the backup of said file under the name Letters/Myletter.txt instead of  /home/uli/Documents/Letters/Myletter.txt.
  • -n mybackup. The name of this backup. This allows you to separate different backups in a single repository.

6. Let’s restore!

You might want to say hopefully I’ll never need to restore. WRONG. You need to restore right now, and you need to restore regularly, as a test that if you actually need to recover data by restoring, it will actually work.

In order to do this, we’ll first need to get access to the folder where. This is typically stored on some kind of Linux server anyway, so just install bup there. In our example above, the directory we’ll work with is called buptest.bup.

There are two conventient ways to view bup backups:

  1. Use bup web and open your browser at http://localhost:8080 to view the backup data (including history):
    bup -d buptest.bup web
  2. Use bup fuse to mount the entire tree including history to a directory such as /tmp/buptest:
    mkdir -p  /tmp/buptest && bup -d buptest.bup fuse /tmp/buptest

     

Posted by Uli Köhler in bup, Container, Docker

How to install dropbearconvert on Ubuntu

In order to install the Dropbear SSH key converter dropbearconvert on recent Ubuntu versions, run

sudo apt -y install dropbear-bin

 

Posted by Uli Köhler in Linux

How to run ssh-keygen non-interactively to generate a SSH key file

Problem:

If you want to generate a key file using ssh-keygen, it will prompt you for a key password:

$ ssh-keygen -t ed25519 -f id_ed25519´
Generating public/private ed25519 key pair.
Enter passphrase (empty for no passphrase): 

For scripts and automated deployments, however, you want to avoid the prompt and disable the password encryption of the key.

Solution:

Use -N "" to set an empty password:

ssh-keygen -t ed25519 -f id_ed25519 -N ""

For example:

$ ssh-keygen -t ed25519 -f id_ed25519 -N ""
Generating public/private ed25519 key pair.
Your identification has been saved in id_ed25519
Your public key has been saved in id_ed25519.pub
The key fingerprint is:
SHA256:o/PVrBXpccLOjrVi6vfGWcmNtoubpINfrMxbz4EXtrU uli@uli-desktop
The key's randomart image is:
+--[ED25519 256]--+
|                 |
|                 |
|                 |
|           . .   |
|        S   *.++.|
|       . . B B*+o|
|      o  ...#++E |
|       o.+=%+B.. |
|       .+=X*B.+. |
+----[SHA256]-----+

 

Posted by Uli Köhler in Linux

How to list installed files for package on Alpine Linux

In order to list the installed files for an Alpine package such as dropbear, use

apk info -qL NAME_OF_PACKAGE

For example:

# apk info -qL dropbear
usr/bin/dropbearkey
usr/sbin/dropbear

Note: This might list the installed files for the most recent version of the package, not the installed package.

Posted by Uli Köhler in Alpine Linux

Minimal SSH server on Docker container using dropbear

This example Dockerfile runs a dropbear SSH daemon on Alpine Linux. It creates a system user called myuser and only allows login for that specific user.

FROM alpine:3.17
WORKDIR /app
ENV SSHUSER=myuser
# The SSH user to create
RUN apk --no-cache add dropbear &&\
    mkdir -p /home/$SSHUSER/.ssh &&\
    adduser -s /bin/sh -D $SSHUSER --home /home/$SSHUSER &&\
    chown -R $SSHUSER:$SSHUSER /home/$SSHUSER

CMD ["/bin/sh", "-c", "/usr/sbin/dropbear -RFEwgsjk -G ${SSHUSER} -p 22"]

Change the username to your liking.

Build like this:

docker build -t sshtest .

Starting the container

You can run it like this – remember to mount /etc/dropbear to a volume or local directory both for persisting host key files and for storing authorized key files:

docker run -v $(pwd)/dropbear:/etc/dropbear -v $(pwd)/dotssh:/home/myuser/.ssh -it sshtest

Dropbear options

The dropbear options -RFEwgsjk are:

  • -R: Create hostkeys as required
  • -F: Don’t fork into background
  • -E: Log to stderr rather than syslog
  • -w: Disallow root logins
  • -g: Disable password logins for root
  • -s: Disable password logins
  • -j: Disable local port forwarding
  • -k: Disable remote port forwarding

Setting up public key authentication

First, generate a key pair using

ssh-keygen -t ed25519 -f id_dropbear -N ""

We assume that you have mounted the user’s home .ssh directory in ./dotssh (as in our example, see Starting the container above). You can then copy the pubkey that is generated by ssh-keygen – which is saved in id_dropbear.pub – to the authorized_keys file in the Dropbear SSH directory:

cat id_dropbear.pub | sudo tee -a ./dotssh/authorized_keys

The sudo (in sudo tee) is only required because the dotssh directory is owned by another user.

Connecting to the container

First, you need to find the container’s IP address using the method outline in How to list just container names & IP address(es) of all Docker conatiners. In our example, this IP address is 10.254.1.4. You can then connect to the container using the public key:

ssh -i id_dropbear [email protected]

 

Posted by Uli Köhler in Docker

How to list just container names & IP address(es) of all Docker conatiners

Also see:

Use

docker ps -q | xargs -n1 docker inspect --format '{{.Name}} {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'

Example output:

$ docker ps -q | xargs -n1 docker inspect --format '{{.Name}} {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
/flamboyant_cohen 10.254.1.6
/flamboyant_cohen 10.254.1.6
/lucid_shtern 10.254.1.4

This solution was inspired by GitHub user zeroows on GitHub Gists

 

Posted by Uli Köhler in Docker

How to list just container names of all Docker containers

Also see:

Use

docker ps --format "{{.Names}}"

Example output:

$ docker ps --format "{{.Names}}"
flamboyant_cohen
lucid_shtern
gitlab-techoverflow_gitlab_1

 

Posted by Uli Köhler in Docker

How to list just container ID of all Docker containers

Also see:

Use

docker ps -q

Example output:

$ docker ps -q
29f721fa3124
39a240769ae8
cae90fe55b9a
90580dc4a6d2
348c24768864
5e64779be4f0
78874ae92a8e
92650c527106
948a8718050f
7aad5a210e3c

 

Posted by Uli Köhler in Docker

How to correctly use apk in Dockerfile

In Dockerfiles you should always use apk with --no-cache to prevent extra files being deposited on the containers, for example:

FROM alpine:3.17
RUN apk add --no-cache python3-dev

 

Posted by Uli Köhler in Alpine Linux, Docker

WordPress backup script using bup (bup remote)

This script backups a WordPress installation (including data,base files & directories, excluding cache) to a bup remote server running on 10.1.2.3. You need to ensure passwordless access to that server.

It is based on automated extraction of database host, username & password, see How to grep for WordPress DB_NAME, DB_USER, DB_PASSWORD and DB_HOST in wp-config.php for more details.

#!/bin/bash
export NAME=$(basename $(pwd))
export BUP_DIR=/var/bup/$NAME.bup
export REMOTE_BUP_DIR=/bup-backups/$NAME.bup
export REMOTE_SERVER=10.1.2.3
export BUP_REMOTE=$REMOTE_SERVER:$REMOTE_BUP_DIR

# Init
bup -d $BUP_DIR init -r $BUP_REMOTE
# Save MariaDB dump (extract MariaDB config from wp-config.php)
DB_NAME=$(grep -oP "define\(['\"]DB_NAME['\"],\s*['\"]\K[^'\"]+(?=[\'\"]\s*\)\s*;)" wp-config.php)
DB_USER=$(grep -oP "define\(['\"]DB_USER['\"],\s*['\"]\K[^'\"]+(?=[\'\"]\s*\)\s*;)" wp-config.php)
DB_PASSWORD=$(grep -oP "define\(['\"]DB_PASSWORD['\"],\s*['\"]\K[^'\"]+(?=[\'\"]\s*\)\s*;)" wp-config.php)
DB_HOST=$(grep -oP "define\(['\"]DB_HOST['\"],\s*['\"]\K[^'\"]+(?=[\'\"]\s*\)\s*;)" wp-config.php)
mysqldump -h$DB_HOST -u$DB_USER -p$DB_PASSWORD $DB_NAME | bup -d $BUP_DIR split -n $NAME-$DB_NAME.sql

# Save wordpress directory
bup -d $BUP_DIR index --exclude wp-content/cache --exclude wp-content/uploads/cache . && bup save -r $BUP_REMOTE -9 --strip-path $(pwd) -n $NAME .

# OPTIONAL: Add par2 information
#   This is only recommended for backup on unreliable storage or for extremely critical backups
#   If you already have bitrot protection (like BTRFS with regular scrubbing), this might be overkill.
# Uncomment this line to enable:
# bup on $REMOTE_SERVER -d $REMOTE_BUP_DIR fsck -g

# OPTIONAL: Cleanup old backups
bup on $REMOTE_SERVER -d $REMOTE_BUP_DIR prune-older --keep-all-for 1m --keep-dailies-for 6m --keep-monthlies-for forever -9 --unsafe

 

Posted by Uli Köhler in bup, Wordpress

How to install bup on Alpine Linux

In order to install the bup backup software on Alpine Linux, you currently have to compile it yourself.

First, install the prerequisites:

apk add bash make g++ python3-dev git automake autoconf par2cmdline py3-pip && pip3 install wheel && pip3 install pyxattr

Now we can clone bup:

git clone -b 0.33 --depth 1 https://github.com/bup/bup

and build:

cd bup && ./configure && make -j4 install PREFIX=/usr

After this, bup should be installed in /usr/bin/bup. The bup clone directory we created in the first step is not needed any more.

Posted by Uli Köhler in Alpine Linux, bup

How to get all WordPress posts as JSON using Python & the WordPress REST API

In our previous post How to get WordPress posts as JSON using Python & the WordPress REST API we showed how to fetch a single page of 10 posts using the WordPress REST API in Python.

In this post, we’ll use the pagination in order to fetch a list of all the posts.

Firstly, we observe that once we query an invalid page such as ?page=1000000, the returned JSON will be

{'code': 'rest_post_invalid_page_number',
 'message': 'The page number requested is larger than the number of pages available.',
 'data': {'status': 400}}

instead of the JSON array representing the list of posts.

Using this information, we can write a fetcher that fetches pages of 100 posts each until this error message is encountered:

from tqdm import tqdm
import requests

def page_numbers():
    """Infinite generate of page numbers"""
    num = 1
    while True:
        yield num
        num += 1

posts = []
for page in tqdm(page_numbers()):
    # Fetch the next [pagesize=10] posts
    posts_page = requests.get("https://mydomain.com/wp-json/wp/v2/posts", params={"page": page, "per_page": 100}).json()
    # Check for "last page" error code
    if isinstance(posts_page, dict) and posts_page["code"] == "rest_post_invalid_page_number": # Found last page
        break
    # No error code -> add posts
    posts += posts_page

 

 

Posted by Uli Köhler in Python, Wordpress

How to install “autom4te” on Alpine Linux

Problem:

When trying to build applications on Alpine Linux, you sometimes see messages like

sh: autom4te: not found
aclocal: error: autom4te failed with exit status: 127

but when you try to apk install autom4te you see that the package can not be found:

fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/community/x86_64/APKINDEX.tar.gz
ERROR: unable to select packages:
  autom4te (no such package):
    required by: world[autom4te]

Solution:

autom4te is available from the autoconf package. Therefore, in order to fix the issue, you can run

apk install autoconf

 

Posted by Uli Köhler in Alpine Linux

How to install “aclocal” on Alpine Linux

Problem:

When trying to build applications on Alpine Linux, you sometimes see messages like

./automake.sh: line 5: aclocal: not found

but when you try to apk install aclocal you see that the package can not be found:

fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/community/x86_64/APKINDEX.tar.gz
ERROR: unable to select packages:
  aclocal (no such package):
    required by: world[aclocal]

Solution:

aclocal is available from the automake package. Therefore, in order to fix the issue, you can run

apk install automake

 

Posted by Uli Köhler in Alpine Linux

How to install “cpp” on Alpine Linux

Problem:

When trying to build applications on Alpine Linux, you often see messages like

Looking for cpp (not found)

but when you try to apk install cpp you see an error message like

fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/community/x86_64/APKINDEX.tar.gz
ERROR: unable to select packages:
  cpp (no such package):
    required by: world[cpp]

Solution:

cpp is just an alias for any C++ compiler. In practice, that means either installing clang++ or g++. I typically recommend g++, even though both will work for most applications.

Therefore, in order to fix the issue, you can run

apk install g++

 

Posted by Uli Köhler in Alpine Linux

How to fix bup make AssertionError: assert not date.startswith(b’$Format’)

Problem:

While trying to build bup using make you see the following error message:

set -e; bup_ver=$(./bup version); \
echo "s,%BUP_VERSION%,$bup_ver,g" > Documentation/substvars.tmp; \
echo "s,%BUP_DATE%,$bup_ver,g" >> Documentation/substvars.tmp
Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/uli/dev/deb-buildscripts/bup/lib/bup/main.py", line 181, in <module>
    cmd_module = import_module('bup.cmd.'
  File "/usr/lib/python3.10/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 883, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/home/uli/dev/deb-buildscripts/bup/lib/bup/cmd/version.py", line 5, in <module>
    from bup import options, version
  File "/home/uli/dev/deb-buildscripts/bup/lib/bup/version.py", line 20, in <module>
    assert not date.startswith(b'$Format')
AssertionError
make: *** [GNUmakefile:252: Documentation/substvars] Fehler 1

Solution:

This error occurs because bup can’t determine the correct version & date for bup. This happens because you deleted the .git directory which bup needs in order to determine its version.

You can create a clone of bup with the .git directory intact by just cloning, for example, the specific version you want to build:

git clone -b 0.33 --depth 1 https://github.com/bup/bup

and then run

./configure
make -j4

as usual

Posted by Uli Köhler in bup

How to get WordPress posts as JSON using Python & the WordPress REST API

You can use the requests library to fetch the posts as JSON from /wp-json/wp/v2/posts

On the wordpress site, you typically don’t need to configure this – the JSON API is enabled by default and accessing publically available info can be done without authentication.

import requests

# posts is a list of JSON objects, each representing a post
posts = requests.get("https://mydomain.com/wp-json/wp/v2/posts").json()

This will, by default, fetch the most recent 10 posts. See TODO for more info on how to fetch all posts using pagination

Posted by Uli Köhler in Python, Wordpress

Windows Auto-Login registry .reg file generator

This generator allows you to generate a .reg registry file to autologin any user (even admin users), allowing for basically a one-click installation. Tested with Windows10.

Your inputs are not sent to our servers but only processed in your browser! Hence, your username & password are safe.

Continue reading →

Posted by Uli Köhler in Generators, Windows

How to escape double quotes (“) in stirng

Note: This solution does not deal with double quotes which are escaped already correctly, plus it does not correctly escape backslashes. However it can still serve as a starting point for doing customized escaping.

'foo"bar"gas'.replace(/"/g, "\\\"") # Results in foo\"bar\"gas

 

Posted by Uli Köhler in Javascript
This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Cookie settingsACCEPTPrivacy &amp; Cookies Policy