Skip to content

Instantly share code, notes, and snippets.

@sbassett29
Last active August 22, 2025 19:49
Show Gist options
  • Save sbassett29/d12e3e09f0b6e81c985efe35b389ac46 to your computer and use it in GitHub Desktop.
Save sbassett29/d12e3e09f0b6e81c985efe35b389ac46 to your computer and use it in GitHub Desktop.
#/usr/bin/env bash
################################################################################
# Author: [email protected]
# License: MIT <https://opensource.org/license/mit>
# Usage:
# A basic mediawiki installation manager
# (with set -u, script will exit if the above are not defined)
# Based upon: https://gist.github.com/m-radzikowski/53e0b39e9a59a1518990e76c2bff8038
################################################################################
set -Eeuo pipefail
trap cleanup SIGINT SIGTERM ERR EXIT
# variables
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd -P)
dir_uniqid='x'
git_branch='master'
git_clone_depth=0
mw_core_repo_url='https://gerrit.wikimedia.org/r/mediawiki/core'
mw_core_dir=.
mw_skins_dir="skins"
mw_exts_dir="extensions"
mw_server_url="https://localhost:8443"
mw_docker_port="8081"
# functions
usage() {
cat <<EOF
Usage: $(basename "${BASH_SOURCE[0]}") [-hbus] {args}
A bash script to help quickly set up local copies of
MediaWiki and various Wikimedia-deployed extensions, skins.
Available options:
-h, --help Print this help and exit
-b, --branch {arg} The branch to clone
(default: master)
-u, --uniqid {arg} A phab task id or gerrit id, for dir naming
(default: 8-digit unique id)
-d, --deep Perform deep git clones
(default: shallow clones)
-k, --kill {arg} Kill running, related docker containers
and delete dir if {arg} is provided
EOF
exit
}
cleanup() {
trap - SIGINT SIGTERM ERR EXIT
}
msg() {
echo >&2 -e "${1-}"
}
die() {
local msg=$1
local code=${2-1} # default exit status 1
msg "$msg"
exit "$code"
}
parse_params() {
while [[ "$#" -gt 0 ]]; do
case "$1" in
-h | --help) usage ;;
-b | --branch)
if [[ -z "${2-}" ]]; then die "Error: No value supplied with ${1} option!"; fi;
git_branch="${2-}"
shift
;;
-u | --uniqid)
if [ -z "${2-}" ]; then die "Error: No value supplied with ${1} option!"; fi;
dir_uniqid="${2-}"
shift
;;
-d | --deep)
git_clone_depth=1
shift
;;
-k | --kill)
kill_docker_and_dir "${2-}"
die ""
;;
-?*) die "Unknown option: $1" ;;
*) break ;;
esac
shift
done
return 0
}
check_deps() {
bins=('git' 'uuidgen' 'sha256sum' 'basename' 'docker')
for bin in "${bins[@]}"; do
if [[ -z $(which $bin) ]]; then
die "dependency '$bin' does not appear to be installed - exiting.\n"
fi
done
}
create_uniqid() {
printf $(uuidgen | sha256sum | head -c 8)
}
process_global_options() {
mw_core_dir="$(basename $mw_core_repo_url)-${git_branch}-${dir_uniqid}"
if [[ $dir_uniqid == "x" ]]; then
dir_uniqid=$(create_uniqid)
fi
if [[ $git_clone_depth -eq 0 ]]; then
git_clone_depth='--depth=1'
fi
}
kill_docker_and_dir() {
docker ps -a
if [[ ! -z "${1-}" ]]; then
mw_core_dir="${1-}"
if [[ -d $mw_core_dir ]]; then
cd "$mw_core_dir"
docker compose down
cd ..
rm -rf "$mw_core_dir"
fi
else
# just try to stop/rm all running dockers
docker container stop $(docker container ls -aq) && docker container rm $(docker container ls -aq)
fi
# for good measure
docker system prune --force
docker ps -a
}
clone_mw_core() {
# assumes process_global_options has run
if [[ -d $mw_core_dir ]]; then
msg "Warning: ${mw_core_dir} exists already! Using a new, random uniqid for the core dir name..."
dir_uniqid=$(create_uniqid)
process_global_options
fi
cmd="git clone -b ${git_branch} --single-branch ${git_clone_depth} ${mw_core_repo_url} ${mw_core_dir}"
msg "Running: ${cmd}"
$cmd
}
config_mw_core() {
# assumes process_global_options has run
cd "$mw_core_dir"
# set .env
echo "MW_SCRIPT_PATH=/w
MW_SERVER=${mw_server_url}
MW_DOCKER_PORT=${mw_docker_port}
MEDIAWIKI_USER=Admin
MEDIAWIKI_PASSWORD=dockerpass
XDEBUG_CONFIG=
XDEBUG_ENABLE=true
XHPROF_ENABLE=true" >> .env
echo "MW_DOCKER_UID=$(id -u)
MW_DOCKER_GID=$(id -g)" >> .env
# run docker commands
docker compose up -d
docker compose exec mediawiki composer update
docker compose exec mediawiki /bin/bash /docker/install.sh
docker compose exec mediawiki git config --add safe.directory '*'
}
clone_mw_vector_skin() {
# assumes we're still in mw install dir
cd "$mw_skins_dir"
git clone -b ${git_branch} --single-branch ${git_clone_depth} "https://gerrit.wikimedia.org/r/mediawiki/skins/Vector"
docker compose exec -i mediawiki bash -c "cd ${mw_skins_dir}/Vector && composer update --no-dev"
cd ..
echo "wfLoadSkin( 'Vector' );" >> LocalSettings.php
}
clone_mw_extensions() {
# oath
cd "$mw_exts_dir"
git clone -b ${git_branch} --single-branch ${git_clone_depth} "https://gerrit.wikimedia.org/r/mediawiki/extensions/OATHAuth"
docker compose exec -i mediawiki bash -c "cd ${mw_exts_dir}/OATHAuth && composer update --no-dev"
cd ..
echo "wfLoadExtension( 'OATHAuth' );" >> LocalSettings.php
echo "\$wgGroupPermissions['user']['oathauth-enable'] = true;" >> LocalSettings.php
docker compose exec mediawiki php maintenance/run.php update.php
# webauthn
cd "$mw_exts_dir"
git clone -b ${git_branch} --single-branch ${git_clone_depth} "https://gerrit.wikimedia.org/r/mediawiki/extensions/WebAuthn"
docker compose exec -i mediawiki bash -c "cd ${mw_exts_dir}/WebAuthn && composer update --no-dev"
cd ..
echo "wfLoadExtension( 'WebAuthn' );" >> LocalSettings.php
echo "\$wgWebAuthnLimitPasskeysToRoaming = true;" >> LocalSettings.php
docker compose exec mediawiki php maintenance/run.php update.php
}
setup_mw_envoy_tls() {
# assumes process_global_options has run
cd "$mw_core_dir"
# amend docker compose to support envoy tls
# https://w.wiki/F6Sj
if [[ -f 'docker-compose.yml' ]]; then
echo '
envoy:
image: envoyproxy/envoy:v1.25-latest
ports:
- "8443:443"
- "9901:9901"
volumes:
- ./envoy.yaml:/etc/envoy/envoy.yaml
- ./certs:/etc/envoy/certs
depends_on:
- mediawiki-web' >> docker-compose.yml
fi
# create dev certs
mkdir certs
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout certs/server.key -out certs/server.crt \
-subj "/CN=localhost"
# write basic envoy.yaml config
echo 'static_resources:
listeners:
- name: https_listener
address:
socket_address:
address: 0.0.0.0
port_value: 443
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
codec_type: AUTO
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: mediawiki_cluster
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
common_tls_context:
tls_certificates:
- certificate_chain:
filename: "/etc/envoy/certs/server.crt"
private_key:
filename: "/etc/envoy/certs/server.key"
clusters:
- name: mediawiki_cluster
connect_timeout: 1s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: mediawiki_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: mediawiki-web
port_value: 8080
admin:
address:
socket_address:
address: 0.0.0.0
port_value: 9901' >> envoy.yaml
cd ..
}
# main
parse_params "$@"
check_deps
process_global_options
clone_mw_core
setup_mw_envoy_tls
config_mw_core
clone_mw_vector_skin
clone_mw_extensions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment