Code Snippets

Web

Web

BookStack

Get / Post / Delete options

works roughly like this:

# Import all markdown files into a book
find . -name '*.md' -print0 | while IFS= read -r -d '' file; do
    python bookstack-api.py markdown_inport "$file"
done

# Trash all pages containing a search query:
python bookstack-api.py search_delete "encryption_cipher_text"

Note on import: It does not handle any images, tags. The page title is the filename

#!/usr/bin/env python3

import os
import sys
import requests

# This is where BookStack API details can be hard-coded if you prefer
# to write them in this script instead of using environment variables.
default_bookstack_options = {
    "url": 'https://brain.vandragt.com',
    "token_id": 'n0N3L...rDvED',
    "token_secret": 'jshjIdU...TeWqCtZX',
    "book_id": 17,
}


# Gather the BookStack API options either from the hard-coded details above otherwise
# it defaults back to environment variables.
def gather_api_options() -> dict:
    return {
        "url": default_bookstack_options["url"] or os.getenv("BS_URL"),
        "token_id": default_bookstack_options["token_id"] or os.getenv("BS_TOKEN_ID"),
        "token_secret": default_bookstack_options["token_secret"] or os.getenv("BS_TOKEN_SECRET"),
        "book_id": default_bookstack_options["book_id"] or os.getenv("BS_BOOK_ID"),
    }


# Send a multipart post request to BookStack, at the given endpoint with the given data.
def bookstack_delete(endpoint: str) -> dict:
    # Fetch the API-specific options
    bs_api_opts = gather_api_options()

    # Format the request URL and the authorization header, so we can access the API
    request_url = bs_api_opts["url"].rstrip("/") + "/api/" + endpoint.lstrip("/")
    request_headers = {
        "Authorization": "Token {}:{}".format(bs_api_opts["token_id"], bs_api_opts["token_secret"])
    }

    # Make the request to bookstack with the gathered details
    response = requests.delete(request_url, headers=request_headers)

    # Throw an error if the request was not successful
    response.raise_for_status()

    # Return the response data decoded from it's JSON format
    return 

# Send a get request to BookStack, at the given endpoint with the given data.
def bookstack_get(endpoint: str, data: dict) -> dict:
    # Fetch the API-specific options
    bs_api_opts = gather_api_options()

    # Format the request URL and the authorization header, so we can access the API
    request_url = bs_api_opts["url"].rstrip("/") + "/api/" + endpoint.lstrip("/")
    request_headers = {
        "Authorization": "Token {}:{}".format(bs_api_opts["token_id"], bs_api_opts["token_secret"])
    }

    # Make the request to bookstack with the gathered details
    response = requests.get(request_url, headers=request_headers, params=data)

    # Throw an error if the request was not successful
    response.raise_for_status()

    # Return the response data decoded from it's JSON format
    return response.json()

# Send a post request to BookStack, at the given endpoint with the given data.
def bookstack_post(endpoint: str, data: dict) -> dict:
    # Fetch the API-specific options
    bs_api_opts = gather_api_options()

    # Format the request URL and the authorization header, so we can access the API
    request_url = bs_api_opts["url"].rstrip("/") + "/api/" + endpoint.lstrip("/")
    request_headers = {
        "Authorization": "Token {}:{}".format(bs_api_opts["token_id"], bs_api_opts["token_secret"])
    }

    # Make the request to bookstack with the gathered details
    response = requests.post(request_url, headers=request_headers, data=data)

    # Throw an error if the request was not successful
    response.raise_for_status()

    # Return the response data decoded from it's JSON format
    return response.json()


# Error out and exit the app
def error_out(message: str):
    print(message)
    exit(1)


def search_delete():
    if len(sys.argv) < 3:
        error_out("search_delete <query> arguments need to be provided")
    
    get_data = {
        "query": sys.argv[2],
        "count": 100
    }

    # Send the upload request and get back the attachment data
    try:
        json = bookstack_get("/search", get_data)
    except requests.HTTPError as e:
        error_out("Upload failed with status {} and data: {}".format(e.response.status_code, e.response.text))

    for page in json['data']:
        try:
            bookstack_delete("/pages/{}".format(page['id']))
        except requests.HTTPError as e:
            error_out("Upload failed with status {} and data: {}".format(e.response.status_code, e.response.text))

        print("deleted: {}".format(page['name']))


def import_markdown():
    if len(sys.argv) < 3:
        error_out("import_markdown <file_path> arguments need to be provided")

    # Gather details from the command line arguments and create a file name
    # from the file path
    file_path = sys.argv[2]
    file_name = os.path.basename(file_path)
    book_id = bs_api_opts["book_id"]

    # Ensure the file exists
    if not os.path.isfile(file_path):
        error_out("Could not find provided file: {}".format(file_path))

    with open(file_path, 'r') as file:
        markdown_content = file.read()

    # Gather the data we'll be sending to BookStack.
    # The format matches that what the "requests" library expects
    # to be provided for its "files" parameter.
    post_data = {
        "name": file_name,
        "markdown": markdown_content,
        "book_id": book_id
    }

    # Send the upload request and get back the attachment data
    try:
        page = bookstack_post("/pages", post_data)
    except requests.HTTPError as e:
        error_out("Upload failed with status {} and data: {}".format(e.response.status_code, e.response.text))

    # Output the results
    print("File successfully uploaded to book {}.".format(book_id))
    print(" - page ID: {}".format(page['id']))
    print(" - page Name: {}".format(page['name']))


# Run this when called on command line
if __name__ == '__main__':
    # Check arguments provided
    if len(sys.argv) < 2:
        error_out("<callback> arguments need to be provided")

    bs_api_opts = gather_api_options()
    callback = sys.argv[1]

    # callback
    possibles = globals().copy()
    possibles.update(locals())
    method = possibles.get(callback)
    if not method:
         raise NotImplementedError("Method %s not implemented" % method_name)
    method()

Web

Docker

Mount a volume as the host user

This allows writing to the volume.

Dockerfile:

COPY .docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh", "docker-php-entrypoint", "php-fpm"]

entrypoint.sh:


#!/usr/bin/env bash

echo "Checking USER ID"
www_uid=`stat -c "%u" /srv/app/src`
www_gid=`stat -c "%g" /srv/app/src`

echo "Host user is $www_uid:$www_gid"

if [ ! $www_uid -eq 0 ]; then
    echo "Updating www-data user and group to match host IDs"
    usermod -u $www_uid www-data
    groupmod -g $www_gid www-data
fi

mkdir -p /srv/app/data
chown www-data:www-data /srv/app/data

exec "$@"

Shell

Shell

Flatpak

Flatpak Runner

Example: fr handbrake

#!/usr/bin/env bash
# Quickly run flatpak installed cli apps by going a grep insensitive search.
# Example: `fr handbrake --help` would run `flatpak run fr.handbrake.HandBrakeCLI --help`
FLATPAK_ID=$(flatpak list --app | grep -i "$1"| head -n1 | cut -f2 )
shift
echo "Running: $FLATPAK_ID $*"
flatpak run "$FLATPAK_ID" "$@"