diff --git a/.dockerignore b/.dockerignore index 4f8233a..1269488 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1 @@ data -db-data -.env diff --git a/.env.mariadb b/.env.mariadb deleted file mode 100644 index 21e328e..0000000 --- a/.env.mariadb +++ /dev/null @@ -1,38 +0,0 @@ - -# General Configuration -WRITEFREELY_BIND_PORT=8080 -WRITEFREELY_BIND_HOST=0.0.0.0 -WRITEFREELY_SITE_NAME="My Blog" -WRITEFREELY_SITE_DESCRIPTION="My fancy blog" - -# Database Configuration -MARIADB_USER=writefreely -MARIADB_PASSWORD=changeme -MARIADB_DATABASE=writefreely -MARIADB_ROOT_PASSWORD=changeme - -WRITEFREELY_DATABASE_DATABASE=mysql -WRITEFREELY_DATABASE_USERNAME=${MARIADB_USER} -WRITEFREELY_DATABASE_PASSWORD=${MARIADB_PASSWORD} -WRITEFREELY_DATABASE_NAME=${MARIADB_DATABASE} -WRITEFREELY_DATABASE_HOST=writefreely-db -WRITEFREELY_DATABASE_PORT=3306 - - -# Application Settings -WRITEFREELY_HOST= -WRITEFREELY_SINGLE_USER=true -WRITEFREELY_OPEN_REGISTRATION=false -WRITEFREELY_MIN_USERNAME_LEN=4 -WRITEFREELY_MAX_BLOG=4 -WRITEFREELY_FEDERATION=true -WRITEFREELY_PUBLIC_STATS=true -WRITEFREELY_PRIVATE=false -WRITEFREELY_LOCAL_TIMELINE=true -WRITEFREELY_USER_INVITES= - -# Writefreely Users -WRITEFREELY_ADMIN_USER=admin -WRITEFREELY_ADMIN_PASSWORD=changeme -WRITEFREELY_WRITER_USER= -WRITEFREELY_WRITER_PASSWORD= diff --git a/.env.sqlite b/.env.sqlite deleted file mode 100644 index 5e8292a..0000000 --- a/.env.sqlite +++ /dev/null @@ -1,31 +0,0 @@ - -# General Configuration -WRITEFREELY_BIND_PORT=8080 -WRITEFREELY_BIND_HOST=0.0.0.0 -WRITEFREELY_SITE_NAME="My Blog" -WRITEFREELY_SITE_DESCRIPTION="My fancy blog" - -# Database Configuration -WRITEFREELY_DATABASE_DATABASE=sqlite3 -WRITEFREELY_SQLITE_FILENAME=./writefreely.db -WRITEFREELY_DATABASE_USERNAME=writefreely -WRITEFREELY_DATABASE_PASSWORD=changeme -WRITEFREELY_DATABASE_NAME=writefreely - -# Application Settings -WRITEFREELY_HOST= -WRITEFREELY_SINGLE_USER=true -WRITEFREELY_OPEN_REGISTRATION=false -WRITEFREELY_MIN_USERNAME_LEN=4 -WRITEFREELY_MAX_BLOG=4 -WRITEFREELY_FEDERATION=true -WRITEFREELY_PUBLIC_STATS=true -WRITEFREELY_PRIVATE=false -WRITEFREELY_LOCAL_TIMELINE=true -WRITEFREELY_USER_INVITES= - -# Writefreely Users -WRITEFREELY_ADMIN_USER=admin -WRITEFREELY_ADMIN_PASSWORD=changeme -WRITEFREELY_WRITER_USER= -WRITEFREELY_WRITER_PASSWORD= diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index b4b6758..bdd8e12 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -1,63 +1,88 @@ name: Docker +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + on: - workflow_dispatch: - push: - branches: [ main ] - tags: [ 'v*.*.*' ] schedule: - cron: '37 1 * * *' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + push: + branches: [ main ] + # Publish semver tags as releases. + tags: [ 'v*.*.*' ] + pull_request: + branches: [ main ] env: - WRITEFREELY_VERSION: v0.15.1 - DOCKERHUB_REPO: jrasanen/writefreely + # Use docker.io for Docker Hub if empty + REGISTRY: ghcr.io + # github.repository as / + IMAGE_NAME: ${{ github.repository }} + jobs: build: + runs-on: ubuntu-latest permissions: contents: read packages: write + # This is used to complete the identity challenge + # with sigstore/fulcio when running outside of PRs. id-token: write steps: - - name: Checkout code - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v3 + # Workaround: https://github.com/docker/build-push-action/issues/461 + - name: Setup Docker buildx + uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf + + # Login against a Docker registry except on PR + # https://github.com/docker/login-action + - name: Log into registry ${{ env.REGISTRY }} + if: github.event_name != 'pull_request' + uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GH_TOKEN }} + - name: Log in to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - uses: actions/cache@v4 + # Extract metadata (tags, labels) for Docker + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- + images: | + jrasanen/writefreely + ghcr.io/${{ github.repository }} - - name: Build and Push - uses: docker/build-push-action@v5 + + # Build and push Docker image with Buildx (don't push on PR) + # https://github.com/docker/build-push-action + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@ac9327eae2b366085ac7f6a2d02df8aa8ead720a with: context: . - file: ./Dockerfile - push: true + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + - name: Build and push Docker images + uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - build-args: | - WRITEFREELY_VERSION=${{ env.WRITEFREELY_VERSION }} - tags: | - ${{ env.DOCKERHUB_REPO }}:${{ env.WRITEFREELY_VERSION }} - ${{ env.DOCKERHUB_REPO }}:latest - platforms: linux/amd64,linux/arm64 - cache-from: type=gha - cache-to: type=gha,mode=max diff --git a/.gitignore b/.gitignore index b1ee384..82f0c3a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1 @@ /data/ -/db-data/ -/.env diff --git a/Dockerfile b/Dockerfile index 9572f3f..88eb37e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,30 +1,38 @@ -ARG GOLANG_VERSION=1.22 +## Writefreely Docker image +## Copyright (C) 2019, 2020 Gergely Nagy +## +## This program is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program. If not, see . + +ARG GOLANG_VERSION=1.21 # Build image FROM golang:${GOLANG_VERSION}-alpine as build -LABEL org.opencontainers.image.source="https://github.com/writefreely/writefreely" -LABEL org.opencontainers.image.description="WriteFreely is a clean, minimalist publishing platform made for writers. Start a blog, share knowledge within your organization, or build a community around the shared act of writing." +ARG WRITEFREELY_VERSION=v0.14.0 +ARG WRITEFREELY_FORK=writeas/writefreely -ARG WRITEFREELY_VERSION=v0.15.1 -ARG WRITEFREELY_FORK=writefreely/writefreely - -RUN apk -U upgrade \ - && apk add --no-cache nodejs npm make g++ git sqlite-dev \ - && npm install -g less less-plugin-clean-css \ - && mkdir -p /go/src/github.com/writefreely/writefreely +RUN apk add --update nodejs npm make g++ git sqlite-dev RUN npm install -g less less-plugin-clean-css +RUN go get -u github.com/jteeuwen/go-bindata/... RUN mkdir -p /go/src/github.com/${WRITEFREELY_FORK} RUN git clone https://github.com/${WRITEFREELY_FORK}.git /go/src/github.com/${WRITEFREELY_FORK} -b ${WRITEFREELY_VERSION} WORKDIR /go/src/github.com/${WRITEFREELY_FORK} ENV GO111MODULE=on -ENV NODE_OPTIONS=--openssl-legacy-provider - RUN make build \ && make ui - RUN mkdir /stage && \ cp -R /go/bin \ /go/src/github.com/${WRITEFREELY_FORK}/templates \ @@ -36,15 +44,9 @@ RUN mkdir /stage && \ mv /stage/cmd/writefreely/writefreely /stage # Final image -FROM alpine:3.19 - -ARG WRITEFREELY_UID=1000 -ARG WRITEFREELY_GID=1000 - -RUN apk -U upgrade && apk add --no-cache openssl ca-certificates - -RUN addgroup -g ${WRITEFREELY_GID} -S writefreely && adduser -u ${WRITEFREELY_UID} -S -G writefreely writefreely +FROM alpine:3.18 +RUN apk add --no-cache openssl ca-certificates COPY --from=build --chown=daemon:daemon /stage /writefreely COPY bin/writefreely-docker.sh /writefreely/ @@ -52,9 +54,4 @@ WORKDIR /writefreely VOLUME /data EXPOSE 8080 -RUN chown -R writefreely:writefreely /writefreely - -USER writefreely - ENTRYPOINT ["/writefreely/writefreely-docker.sh"] - diff --git a/README.md b/README.md index 2311d4a..6ebc4a5 100644 --- a/README.md +++ b/README.md @@ -1,143 +1,58 @@ -# WriteFreely Docker Build +# writefreely-docker -This project builds a Docker image for [WriteFreely](https://github.com/writefreely/writefreely), a minimalist, privacy-focused, and federated blogging platform. The image is uses on Alpine Linux. +[![Build Status](https://ci.madhouse-project.org/api/badges/algernon/writefreely-docker/status.svg?branch=master)](https://ci.madhouse-project.org/algernon/writefreely-docker) +[![Docker Image](https://img.shields.io/badge/docker-latest-blue?style=flat-square)](https://hub.docker.com/r/algernon/writefreely) +[![Source](https://img.shields.io/badge/source-git-brightgreen?style=flat-square)](https://git.madhouse-project.org/algernon/writefreely-docker) + +This is a [Docker][docker] image for [WriteFreely][writefreely], set up in a way +that makes it easier to deploy it in production, including the initial setup step. + + [docker]: https://www.docker.com/ + [writefreely]: https://github.com/writeas/writefreely + +## Overview + +The image is set up to use SQLite for the database, it does not support MySQL +out of the box - but you can always provide your own `config.ini`. The config +file, the database, and the generated keys are all stored on the single volume +the image uses, mounted on `/data`. + +The primary purpose of the image is to provide a single-step setup and upgrade +experience, where the initial setup and any upgrades are handled by the image +itself. As such, the image will create a default `config.ini` unless one already +exists, with reasonable defaults. It will also run database migrations, and save +a backup before doing so (which it will delete, if no migrations were +necessary). ## Getting started To get started, the easiest way to test it out is running the following command: -```bash +```shell docker run -p 8080:8080 -it --rm -v /some/path/to/data:/data \ - jrasanen/writefreely + algernon/writefreely ``` -Then point your browser to http://localhost:8080, and you should see WriteFreely up and running. +Then point your browser to `http://localhost:8080`, and you should see +WriteFreely up and running. ## Setup -The image will perform an initial setup, unless the supplied volume already contains a config.ini. Settings can be tweaked via environment variables, of which you can find a list below. Do note that these environment variables are only used for the initial setup as of this writing! If a configuration file already exists, the environment variables will be blissfully ignored. +The image will perform an initial setup, unless the supplied volume already +contains a `config.ini`. Settings can be tweaked via environment variables, of +which you can find a list below. Do note that these environment variables are +*only* used for the initial setup as of this writing! If a configuration file +already exists, the environment variables will be blissfully ignored. ### Environment variables -The following variables will be used to construct the `config.ini` on first start. After it has been configured, you can edit it on the volume. +- `WRITEFREELY_BIND_HOST` and `WRITEFREELY_BIND_PORT` determine the host and port WriteFreely will bind to. Defaults to `0.0.0.0` and `8080`, respectively. +- `WRITEFREELY_SITE_NAME` is the site title one wants. Defaults to "A Writefreely blog". +- `WRITEFREELY_SINGLE_USER`, `WRITEFREELY_OPEN_REGISTRATION`, + `WRITEFREELY_MIN_USERNAME_LEN`, `WRITEFREELY_MAX_BLOG`, + `WRITEFREELY_FEDERATION`, `WRITEFREELY_PUBLIC_STATS`, `WRITEFREELY_PRIVATE`, + `WRITEFREELY_LOCAL_TIMELINE`, and `WRITEFREELY_USER_INVITES` all correspond to + the similarly named `config.ini` settings. See the [WriteFreely docs][wf:docs] + for more information about them. -## General Configuration - -- **`WRITEFREELY_BIND_PORT`**: Specifies the port on which the WriteFreely server will listen. Defaults to `8080`. -- **`WRITEFREELY_BIND_HOST`**: Defines the host IP to bind to. Defaults to `0.0.0.0`. -- **`WRITEFREELY_SITE_NAME`**: Sets the name of your WriteFreely site. Used to identify the site in federation. -- **`WRITEFREELY_SITE_DESCRIPTION`**: Provides a short description of your site. This description may be used in federated networks. - -## Database Configuration - -- **`WRITEFREELY_DATABASE_DATABASE`**: Specifies the type of database used, such as `mysql` or `sqlite3`. -- **`WRITEFREELY_SQLITE_FILENAME`**: (Optional) DB filename if `sqlite3` detabase is selected. Defaults to `/data/writefreely.db`. -- **`WRITEFREELY_DATABASE_USERNAME`**: The username for the database. -- **`WRITEFREELY_DATABASE_PASSWORD`**: The password for the database. -- **`WRITEFREELY_DATABASE_NAME`**: The name of the database to connect to. -- **`WRITEFREELY_DATABASE_HOST`**: The hostname or IP address of the database server. -- **`WRITEFREELY_DATABASE_PORT`**: The port number on which the database server is running. - -## Application Settings - -- **`WRITEFREELY_HOST`**: The full URL where the site will be accessible. -- **`WRITEFREELY_SINGLE_USER`**: Set to `true` to run the instance as a single-user blog, otherwise `false`. -- **`WRITEFREELY_OPEN_REGISTRATION`**: Whether or not anyone can register via the landing page -- **`WRITEFREELY_MIN_USERNAME_LEN`**: The minimum length for usernames. -- **`WRITEFREELY_MAX_BLOG`**: Maximum number of blogs a single user can create under one account -- **`WRITEFREELY_FEDERATION`**: Whether or not federation via ActivityPub is enabled -- **`WRITEFREELY_PUBLIC_STATS`**: DWhether or not usage stats are made public via NodeInfo -- **`WRITEFREELY_PRIVATE`**: Set to `true` to make the site private. -- **`WRITEFREELY_LOCAL_TIMELINE`**: Whether or not the instance reader (and the Public option on blogs) is enabled -- **`WRITEFREELY_USER_INVITES`**: Who is allowed to send user invites, if anyone. A blank value disables invites for all users. Valid choices: empty, user, or admin - -## Writefreely Users - -- **`WRITEFREELY_ADMIN_USER`**: Administrator user name. In single user instances is editor too. -- **`WRITEFREELY_ADMIN_PASSWORD`**: Administrator password - -### Volumes - -* `/data`: Directory where WriteFreely stores its data, including database files and configuration. - -### Using Docker Compose - -You can use Docker Compose to set up WriteFreely with different database configurations. The configuration files are already included in this repository. Follow the steps below to start the services. - -#### Clone the Repository - -First, clone this repository: - -```bash -git clone https://github.com/yourusername/writefreely-docker.git -cd writefreely-docker -``` - -#### Prepare the Data Directory - -Create the data directory and assign the appropriate permissions: - -```bash -mkdir data -sudo chown 1000:1000 data -``` - -#### Configure the Environment - -Before starting the services, you need to copy the appropriate .env file and edit it to configure the environment variables, especially the passwords. - -##### For MariaDB - -Copy the .env.mariadb file to .env: - -```bash -cp .env.mariadb .env -``` - -##### For SQLite - -Copy the .env.sqlite file to .env: - -```bash -cp .env.sqlite .env -``` - -Then, edit the .env file to set the appropriate values for your environment: - -```bash -nano .env -``` - -Ensure to set secure passwords and other necessary configuration options. - -#### Start the Services - -##### MariaDB - -To use the **MariaDB** configuration, run: - -```bash -docker-compose -f docker-compose.mariadb.yaml up -``` - -##### SQLite - -To use the **SQLite** configuration, run: - -```bash -docker-compose -f docker-compose.sqlite3.yaml up -``` - -### Building the Image - -If you want to build the image yourself, clone this repository and run the following command inside the repository's directory: - -```bash -docker build -t yourusername/writefreely . -``` - -Replace `yourusername` with your Docker Hub username or a suitable image name. - -### Contributing - -Contributions are welcome! Please fork this repository and submit pull requests for any enhancements or bug fixes. + [wf:docs]: https://writefreely.org/docs/latest/admin/config diff --git a/bin/writefreely-docker.sh b/bin/writefreely-docker.sh index da78a9f..21079ac 100755 --- a/bin/writefreely-docker.sh +++ b/bin/writefreely-docker.sh @@ -20,204 +20,61 @@ set -e cd /data WRITEFREELY=/writefreely/writefreely -attempts=0 -max_attempts=5 - -log() { - echo "$(date '+%Y/%m/%d %H:%M:%S') $1" -} - -validate_url() { - URL="$1" - if echo "$URL" | grep -Eq "^https?://[a-zA-Z0-9._-]+"; then - return 0 # Success - else - return 1 # Failure - fi -} - -retry_command() { - local cmd=$1 - attempts=0 - until $cmd; do - attempts=$((attempts+1)) - if [ $attempts -ge $max_attempts ]; then - log "Failed to execute '$cmd' after $attempts attempts." - return 1 - fi - log "Retrying '$cmd' ($attempts/$max_attempts)..." - sleep 5 - done - return 0 -} - -initialize_database() { - log "Initializing database..." - if ! retry_command "${WRITEFREELY} --init-db"; then - log "Initialization of database failed. Removing config.ini." - rm ./config.ini - exit 1 - fi -} - -generate_keys() { - log "Generating keys..." - ${WRITEFREELY} --gen-keys -} - -create_admin_user() { - if [ -n "$WRITEFREELY_ADMIN_USER" ]; then - ${WRITEFREELY} user create --admin ${WRITEFREELY_ADMIN_USER}:${WRITEFREELY_ADMIN_PASSWORD} - log "Created admin user ${WRITEFREELY_ADMIN_USER}" - else - log "Admin user not defined" - exit 1 - fi -} - -create_writer_user() { - if [ -n "$WRITEFREELY_WRITER_USER" ]; then - ${WRITEFREELY} user create ${WRITEFREELY_WRITER_USER}:${WRITEFREELY_WRITER_PASSWORD} - log "Created writer user ${WRITEFREELY_WRITER_USER}" - fi -} - -validate_url "$WRITEFREELY_HOST" || { - log "Error: $WRITEFREELY_HOST is not a valid URL. It must start with http:// or https:// and be followed by a valid hostname." - exit 1 -} if [ -e ./config.ini ] && [ -e ./keys/email.aes256 ]; then - log "Migration required. Running migration..." - ${WRITEFREELY} -migrate - exec ${WRITEFREELY} + ${WRITEFREELY} -migrate + exec ${WRITEFREELY} fi if [ -e ./config.ini ]; then - initialize_database - generate_keys - create_admin_user - create_writer_user - exec ${WRITEFREELY} + ${WRITEFREELY} -init-db + ${WRITEFREELY} -gen-keys + exec ${WRITEFREELY} fi WRITEFREELY_BIND_PORT="${WRITEFREELY_BIND_PORT:-8080}" WRITEFREELY_BIND_HOST="${WRITEFREELY_BIND_HOST:-0.0.0.0}" WRITEFREELY_SITE_NAME="${WRITEFREELY_SITE_NAME:-A Writefreely blog}" -WRITEFREELY_SITE_DESCRIPTION="${WRITEFREELY_SITE_DESCRIPTION:-My Writefreely blog}" cat >./config.ini <