├── .gitignore ├── LICENSE ├── bootstrap.yml ├── castle ├── docker-compose.yml ├── letterbook.Dockerfile ├── letterbook.compose.yml ├── mastodon-streaming.Dockerfile ├── mastodon.Dockerfile ├── mastodon.compose.yml ├── network-tools.Dockerfile ├── pasture.Dockerfile ├── pasture.castle.yml ├── readme.md ├── sharkey.compose.yml~ ├── templates ├── Dockerfile └── compose.yml ├── traefik.Dockerfile ├── units ├── redir443.service └── redir80.service └── volumes ├── ca.json ├── hosts ├── mastodon └── init.sh ├── pasture └── .gitignore ├── postgresql.conf ├── proxy ├── acme.json ├── traefik.yml └── traefik_dynamic.yml ├── root-ca ├── .gitignore └── readme.md └── sharkey ├── default.yml └── docker.env /.gitignore: -------------------------------------------------------------------------------- 1 | *.backup.* 2 | .idea/ 3 | .env 4 | *local* 5 | volumes/containers.conf -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /bootstrap.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | name: sandcastles 3 | 4 | services: 5 | root-ca: 6 | image: smallstep/step-ca:0.27.1 7 | environment: 8 | - DOCKER_STEPCA_INIT_NAME=Letterbook Sandcastles 9 | - DOCKER_STEPCA_INIT_DNS_NAMES=root-ca.castle,root-ca,localhost 10 | - DOCKER_STEPCA_INIT_REMOTE_MANAGEMENT=true 11 | - DOCKER_STEPCA_INIT_ACME=true 12 | - DOCKER_STEPCA_INIT_PASSWORD=capassword 13 | ports: 14 | - "9000:9000" 15 | networks: 16 | default: 17 | restart: unless-stopped -------------------------------------------------------------------------------- /castle: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | # shellcheck disable=SC3024 4 | # shellcheck disable=SC3030 5 | # shellcheck disable=SC1078 6 | DIR=$(dirname "$(readlink -f -- "$0")") 7 | C_ERROR="\033[0;31m" 8 | C_WARN="\033[0;33m" 9 | C_VERBOSE="\033[0;32m" 10 | C_NOTICE="\033[0;34m" 11 | C_NONE="\033[0m" 12 | set_usage() { 13 | USAGE="""Usage: ${CMD:=${0##*/}} ${1:-"[bootstrap|up|down|build|help]"} [APPS...|(-a|--all)] [(-v|--verbose)] [(-d|--dry-run)] [(-h|--help)] 14 | APPS: 15 | The apps to run this command on. 16 | 17 | Options: 18 | -a|--all - apply to all apps 19 | -v|--verbose - output additional info to stderr; pass multiple times for greater verbosity 20 | -d|--dry-run - preview the commends to be run, but do not execute them 21 | -h|--help - display help text 22 | ${2} 23 | 24 | Examples: 25 | ${C_VERBOSE}Bootstrap the project${C_NONE} 26 | ./castle bootstrap 27 | 28 | ${C_VERBOSE}Build baseline required images, plus mastodon and sharkey${C_NONE} 29 | ./castle build mastodon sharkey --required 30 | 31 | ${C_VERBOSE}Run mastodon and letterbook${C_NONE} 32 | ./castle up mastodon letterbook""" 33 | }; set_usage; 34 | set_help() { 35 | HELP="""Sandcastles - ${C_VERBOSE}${1:-"federation sandbox"}${C_NONE} 36 | ${2:-"""Easily and quickly set up a federation sandbox, for testing against 37 | production-like configurations of multiple fediverse backends."""} 38 | """ 39 | }; set_help; 40 | exit2 () { printf >&2 "%s: %s: '%s'\n%b\n" "$CMD" "$1" "$2" "$USAGE"; exit 2; } 41 | exit_runtime () { 42 | printf >&2 "Sandcastles require a container runtime\nYou must install either podman (and podman-compose) or docker"; 43 | exit 3; 44 | } 45 | exit_err() { 46 | printf >&2 "${C_ERROR}%s Error!${C_NONE}\n%b\n" "$1" "⤷ $2"; exit 4 47 | } 48 | exit_usage() { printf "%b\n" "$USAGE"; exit 0; } 49 | exit_help() { printf "%b\n%b\n" "$HELP" "$USAGE"; exit 0; } 50 | check () { { [ "$1" != "$EOL" ] && [ "$1" != '--' ]; } || exit2 "missing argument" "$2"; } # avoid infinite loop 51 | verbose () { 52 | if [ "$opt_verbose" -gt 0 ]; then 53 | debug=$1; shift 54 | printf "${C_VERBOSE}%s${C_NONE}\n%s\n" "$debug" "⤷ $(printf "%s " "$@")" >&2 55 | fi 56 | } 57 | verbose2 () { 58 | if [ $opt_verbose -gt 1 ]; then 59 | verbose "$@" 60 | fi 61 | } 62 | 63 | # run commands with support for verbose output and dry-runs 64 | run() { 65 | debug=$1; shift 66 | cmd_args=("$@") 67 | if [ "$opt_dryrun" = "true" ]; then 68 | printf "${C_NOTICE}(dry-run) ${C_VERBOSE}$debug${C_NONE}\n%s\n" "⤷ $(printf "%s %s" "${cmd_args[*]}" "${arg_pass[*]}")" >&2 69 | return 70 | fi 71 | if [ "$opt_verbose" -gt 0 ]; then 72 | verbose "$debug" "${cmd_args[*]}" 73 | fi 74 | if [ ${#arg_pass[@]} -gt 0 ]; then 75 | verbose "passthrough" "${#arg_pass[@]}" 76 | "${cmd_args[@]}" "${arg_pass[*]}" 77 | else 78 | "${cmd_args[@]}" 79 | fi 80 | code=$? 81 | if [ $code = 0 ]; then 82 | return 83 | fi 84 | exit $code 85 | } 86 | 87 | # parse action 88 | case "$1" in 89 | # handle help cases 90 | -h | --help | help ) exit_help;; 91 | '' ) exit_usage;; 92 | bootstrap ) opt_action=$1; shift 93 | set_help "$opt_action" "perform first time setup" 94 | set_usage "$opt_action" 95 | ;; 96 | up ) opt_action=$1; shift 97 | set_help "$opt_action" "run the selected apps" 98 | set_usage "$opt_action" 99 | ;; 100 | down ) opt_action=$1; shift 101 | set_help "$opt_action" "shut down the selected apps" 102 | set_usage "$opt_action" 103 | ;; 104 | build ) opt_action=$1; shift 105 | set_help "$opt_action" "Build container images with trust for your Sandcastle private CA" 106 | set_usage "$opt_action" "-r|--required - build required container images not associated with an individual app" 107 | ;; 108 | new ) opt_action=$1; shift 109 | set_help "$opt_action" "Scaffold up required files to add new apps to the Sandcastle. New app names should follow the command" 110 | ;; 111 | * ) exit2 "invalid command" "$opt_action";; 112 | esac 113 | 114 | # parse remaining command-line options 115 | set -- "$@" "${EOL:=$(printf '\1\3\3\7')}" # end-of-list marker 116 | opt_apps=() 117 | opt_verbose=0 118 | arg_pass=() 119 | while [ "$1" != "$EOL" ]; do 120 | opt="$1"; shift 121 | case "$opt" in 122 | 123 | # defined options 124 | -a | --all ) opt_all=true;; 125 | -h | --help ) exit_help;; 126 | -r | --required ) opt_required=true;; 127 | -v | --verbose ) opt_verbose=$((opt_verbose + 1));; 128 | -d | --dry-run ) opt_dryrun=true;; 129 | 130 | # process special cases 131 | --) while [ "$1" != "$EOL" ]; do set -- "$@" "$1"; arg_pass+=("$1"); shift; done;; # parse remaining as passthrough 132 | --[!=]*=*) set -- "${opt%%=*}" "${opt#*=}" "$@";; # "--opt=arg" -> "--opt" "arg" 133 | -[A-Za-z0-9] | -*[!A-Za-z0-9]*) exit2 "invalid option" "$opt";; # anything invalid like '-*' 134 | -?*) other="${opt#-?}"; set -- "${opt%$other}" "-${other}" "$@";; # "-abc" -> "-a" "-bc" 135 | *) opt_apps+=("$opt");; # positional, rotate to the end 136 | esac 137 | done; shift 138 | 139 | if (command -v podman >/dev/null 2>&1) && (command -v podman-compose >/dev/null 2>&1); then 140 | env_runtime="podman" 141 | elif command -v docker >/dev/null 2>&1; then 142 | env_runtime="docker" 143 | else 144 | exit_runtime 145 | fi 146 | 147 | verbose2 "parsed options" """DIR = '$DIR' 148 | action = '$opt_action' 149 | arg_pass = ${arg_pass[*]} 150 | apps = ${opt_apps[*]} 151 | --all = $opt_all 152 | --required = $opt_required 153 | --verbose = $opt_verbose 154 | --dry-run = $opt_dryrun 155 | runtime = '$env_runtime'""" 156 | 157 | # Zhu Li, do the thing! 158 | case $opt_action in 159 | up ) 160 | compose_files=("-f" "$DIR/docker-compose.yml") 161 | if [ "$opt_all" = "true" ]; then 162 | all_files=$(find "$DIR" -type f -name "*.compose.yml") 163 | for n in ${all_files[*]}; do compose_files+=("-f" "$n"); done 164 | else 165 | for n in ${opt_apps[*]}; do compose_files+=("-f" "$n.compose.yml"); done 166 | fi 167 | cmd_args=("$env_runtime" "compose" "${compose_files[@]}" "up" "-d") 168 | if [ "$env_runtime" = "podman" ]; then 169 | CONTAINERS_CONF_OVERRIDE="$DIR/volumes/containers.conf" 170 | export CONTAINERS_CONF_OVERRIDE 171 | fi 172 | run "compose up" "${cmd_args[@]}" 173 | ;; 174 | 175 | build ) 176 | docker_files=() 177 | if [ "$opt_all" = "true" ]; then 178 | verbose "find all Dockerfiles" 179 | docker_files+=$(find "$DIR" -type f -name "*.Dockerfile") 180 | elif [ "$opt_required" = "true" ]; then 181 | verbose "find Dockerfiles" "--required" 182 | docker_files+=("$DIR/traefik.Dockerfile" "$DIR/network-tools.Dockerfile") 183 | fi 184 | if [ "$opt_all" != "true" ]; then 185 | for n in ${opt_apps[*]}; do 186 | verbose "find Dockerfiles" "$n" 187 | found=$(find "$DIR" -type f -name "$n*.Dockerfile") 188 | docker_files+=("$found") 189 | done 190 | fi 191 | verbose "found" ${docker_files[*]} 192 | 193 | # tags() is an array of "tuples" which don't exist in sh scripts as far as I know. 194 | # so, it's actually a regular array, but with paired elements. 195 | # Like ("x.Dockerfile" "tag/x:latest" "y.Dockerfile" "tag/y:latest")... 196 | tags=() 197 | for n in ${docker_files[*]}; do 198 | tags+=("$n" "localhost/sandcastles/$(basename ${n//.Dockerfile})"); 199 | done 200 | 201 | i=0 202 | while [ "$i" -lt ${#tags[@]} ]; do 203 | cmd_args=("$env_runtime" "build" "$DIR" "-f" "${tags[i]}" "-t" "${tags[i+1]}:latest") 204 | if [ $env_runtime = "podman" ]; then 205 | cmd_args+=("--format" "docker") 206 | fi 207 | run "build ${tags[i+1]}" "${cmd_args[@]}" 208 | i=$((i+2)) 209 | done 210 | ;; 211 | 212 | down ) 213 | compose_files=() 214 | if [ "$opt_all" = "true" ]; then 215 | compose_files+=("-f" "$DIR/docker-compose.yml") 216 | all_files=$(find "$DIR" -type f -name "*.compose.yml") 217 | for n in ${all_files[*]}; do compose_files+=("-f" "$n"); done 218 | else 219 | for n in ${opt_apps[*]}; do compose_files+=("-f" "$n.compose.yml"); done 220 | fi 221 | cmd_args=("$env_runtime" "compose" "${compose_files[@]}" "down") 222 | run "compose down" "${cmd_args[@]}" 223 | ;; 224 | 225 | bootstrap ) 226 | if [ -d "$DIR/volumes/root-ca/secrets" ]; then 227 | exit_err "$opt_action" """Refusing to overwrite data in ${C_WARN}$DIR/volumes/root-ca/${C_NONE} 228 | This project has already been bootstrapped. You must remove the secrets stored in 229 | ${C_WARN}$DIR/volumes/root-ca/${C_NONE} before you can bootstrap again. 230 | Those secrets cannot be recovered later! Make a copy of them, or revoke trust in them before they are deleted.""" 231 | fi 232 | 233 | run "create sandcastles internal CA" "$env_runtime" "compose" "-f" "$DIR/bootstrap.yml" "up" "-d" 234 | container_id=$(run "get container id" "$env_runtime" "compose" "-f" "bootstrap.yml" "ps" "-q") 235 | container_id=${container_id:-"dry-run_container"} 236 | run "wait for container" sleep 1 237 | run "extract private key for sandcastles internal CA" \ 238 | "$env_runtime" "cp" "$container_id:/home/step/templates" "$DIR/volumes/root-ca/" 239 | run "..." "$env_runtime" "cp" "$container_id:/home/step/secrets" "$DIR/volumes/root-ca/" 240 | run "..." "$env_runtime" "cp" "$container_id:/home/step/db" "$DIR/volumes/root-ca/" 241 | run "..." "$env_runtime" "cp" "$container_id:/home/step/certs" "$DIR/volumes/root-ca/" 242 | run "..." "$env_runtime" "compose" "-f" "bootstrap.yml" "down" 243 | 244 | run "configure access to sandcastles internal CA" mkdir "-p" "$DIR/volumes/root-ca/config" 245 | run "..." "cp" "$DIR/volumes/ca.json" "$DIR/volumes/root-ca/config/ca.json" 246 | run "..." "find" "$DIR/volumes/root-ca" "-type" "d" "-exec" "chmod" "755" "{}" "+" 247 | run "..." "find" "$DIR/volumes/root-ca" "-type" "f" "-exec" "chmod" "644" "{}" "+" 248 | 249 | run "collect info about the runtime environment" \ 250 | printf "%s\n" "DOCKER_PATH=${DOCKER_HOST//"unix://"}" > "$DIR/.env" 251 | run "..." printf "%s\n" """[containers] 252 | base_hosts_file = \"$DIR/volumes/hosts\" """ > "$DIR/volumes/containers.conf" 253 | ;; 254 | 255 | new ) 256 | compose_template=$(cat "$DIR/templates/compose.yml") 257 | docker_template=$(cat "$DIR/templates/Dockerfile") 258 | for n in ${opt_apps[*]}; do 259 | compose_file=${compose_template//"{{app_name}}"/$n} 260 | docker_file=${docker_template//"{{app_name}}"/$n} 261 | run "generate compose template for $n" printf "%s\n" "$compose_file" > "$DIR/$n.compose.yml" 262 | run "generate Dockerfile template for $n" printf "%s\n" "$docker_file" > "$DIR/$n.Dockerfile" 263 | done 264 | ;; 265 | esac 266 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.10' 2 | name: sandcastles 3 | 4 | services: 5 | network-tools: 6 | networks: 7 | default: 8 | image: localhost/sandcastles/network-tools:latest 9 | healthcheck: 10 | test: curl -sS --fail-with-body https://proxy.castle > /dev/null 11 | command: curl -Ivv https://proxy.castle 12 | 13 | proxy: 14 | image: localhost/sandcastles/traefik:latest 15 | build: 16 | dockerfile: proxy.Dockerfile 17 | tags: 18 | - localhost/traefik-sandcastle:latest 19 | security_opt: 20 | - label=type:container_runtime_t 21 | volumes: 22 | - '${DOCKER_PATH}:/var/run/docker.sock:z' 23 | - './volumes/proxy:/etc/traefik/:z' 24 | networks: 25 | default: 26 | aliases: 27 | - 'proxy.castle' 28 | ports: 29 | - '8080:80' 30 | - '8443:443' 31 | restart: unless-stopped 32 | 33 | root-ca: 34 | image: docker.io/smallstep/step-ca:0.27.1 35 | volumes: 36 | - './volumes/root-ca:/home/step:z' 37 | environment: 38 | - DOCKER_STEPCA_INIT_NAME=Letterbook Sandcastles 39 | - DOCKER_STEPCA_INIT_DNS_NAMES=root-ca.castle,root-ca,localhost 40 | - DOCKER_STEPCA_INIT_REMOTE_MANAGEMENT=true 41 | - DOCKER_STEPCA_INIT_ACME=false 42 | - DOCKER_STEPCA_INIT_PASSWORD=capassword 43 | ports: 44 | - "9000:9000" 45 | networks: 46 | default: 47 | aliases: 48 | - root-ca.castle 49 | restart: unless-stopped 50 | 51 | httpbin: 52 | scale: 0 53 | labels: 54 | - traefik.http.routers.httpbin.rule=Host(`httpbin.castle`) 55 | - traefik.http.routers.httpbin.tls=true 56 | - traefik.http.routers.httpbin.tls.certresolver=smallstep 57 | - traefik.port=80 58 | - traefik.enable=true 59 | image: docker.io/kennethreitz/httpbin 60 | networks: 61 | default: 62 | restart: unless-stopped 63 | 64 | networks: 65 | default: 66 | -------------------------------------------------------------------------------- /letterbook.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/smallstep/step-cli:0.25.0 AS step-cli 2 | 3 | FROM mcr.microsoft.com/dotnet/sdk:8.0 4 | 5 | USER root 6 | 7 | COPY --from=step-cli /usr/local/bin/step /usr/local/bin/step 8 | COPY volumes/root-ca/certs/root_ca.crt /usr/local/share/ca-certificates/root_ca.crt 9 | RUN /usr/local/bin/step certificate install /usr/local/share/ca-certificates/root_ca.crt 10 | 11 | WORKDIR /app 12 | -------------------------------------------------------------------------------- /letterbook.compose.yml: -------------------------------------------------------------------------------- 1 | # must be used as an overlay for the main docker-compose file. Ex 2 | # docker-compose -f docker-compose.yml -f letterbook.compose.yml up 3 | 4 | ### NOTICE ## 5 | # 6 | # Letterbook is not a typical sandcastle component, because it doesn't have any stable releases, yet. It's included here 7 | # as a development convenience, to make it easier to build and run the app from source, in the sandcastle environment. 8 | # 9 | # To do so, you must have a local clone of the source code, you must add it to a local `.env` file 10 | # as the LETTERBOOK_REPO var. Ex: 11 | # LETTERBOOK_REPO=/path/to/letterbook 12 | 13 | # defaults 14 | # handle: admin 15 | # login: admin@letterbook.example 16 | # password: Password1! 17 | 18 | services: 19 | letterbook: 20 | labels: 21 | - traefik.http.routers.letterbook.rule=Host(`letterbook.castle`) 22 | - traefik.http.routers.letterbook.tls=true 23 | - traefik.http.routers.letterbook.tls.certresolver=smallstep 24 | - traefik.http.services.letterbook-sandcastles.loadBalancer.healthCheck.path=/healthz 25 | - traefik.port=5127 26 | - traefik.enable=true 27 | - traefik.docker.network=sandcastles_letterbook 28 | image: localhost/sandcastles/letterbook:latest 29 | build: 30 | dockerfile: ./letterbook.Dockerfile 31 | tags: 32 | - localhost/sandcastles/letterbook:latest 33 | command: dotnet run --project Source/Letterbook/Letterbook.csproj -c Debug --launch-profile sandcastle 34 | environment: 35 | ASPNETCORE_ENVIRONMENT: Sandcastle 36 | OTEL_EXPORTER_OTLP_ENDPOINT: 'http://tempo:4317' 37 | volumes: 38 | - 'letterbook_nuget_cache:/root/.nuget/packages/' 39 | - 'letterbook_build_cache:/app/artifacts/' 40 | - '${LETTERBOOK_REPO}Source:/app/Source:z' 41 | - '${LETTERBOOK_REPO}Letterbook.sln:/app/Letterbook.sln:z' 42 | - '${LETTERBOOK_REPO}Directory.Build.props:/app/Directory.Build.props:z' 43 | - '${LETTERBOOK_REPO}Directory.Packages.props:/app/Directory.Packages.props:z' 44 | ports: 45 | - '2982:5127' 46 | - '5127:5127' 47 | networks: 48 | - default 49 | - letterbook 50 | healthcheck: 51 | test: curl -sS --fail-with-body localhost:5127/healthz 52 | interval: 2s 53 | timeout: 1s 54 | retries: 10 55 | depends_on: 56 | letterbook_db: 57 | condition: service_healthy 58 | 59 | letterbook_db: 60 | image: timescale/timescaledb:2.17.2-pg15-oss 61 | environment: 62 | - POSTGRES_USER=letterbook 63 | - POSTGRES_PASSWORD=letterbookpw 64 | - POSTGRES_DB=letterbook 65 | volumes: 66 | - letterbook_db_data:/var/lib/postgresql/data 67 | networks: 68 | - letterbook 69 | healthcheck: 70 | test: pg_isready -d letterbook -U postgres 71 | interval: 2s 72 | timeout: 1s 73 | retries: 10 74 | restart: always 75 | 76 | 77 | 78 | proxy: 79 | networks: 80 | default: 81 | aliases: 82 | - letterbook.castle 83 | letterbook: {} 84 | 85 | networks: 86 | letterbook: 87 | default: 88 | 89 | volumes: 90 | letterbook_db_data: 91 | letterbook_nuget_cache: 92 | letterbook_build_cache: 93 | -------------------------------------------------------------------------------- /mastodon-streaming.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/smallstep/step-cli:0.25.0 AS step-cli 2 | 3 | FROM ghcr.io/mastodon/mastodon-streaming:v4.3.4 4 | 5 | USER root 6 | 7 | COPY --from=step-cli /usr/local/bin/step /usr/local/bin/step 8 | COPY volumes/root-ca/certs/root_ca.crt /usr/local/share/ca-certificates/root_ca.crt 9 | RUN /usr/local/bin/step certificate install /usr/local/share/ca-certificates/root_ca.crt 10 | 11 | USER mastodon 12 | -------------------------------------------------------------------------------- /mastodon.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/smallstep/step-cli:0.25.0 AS step-cli 2 | 3 | FROM ghcr.io/mastodon/mastodon:v4.3.4 4 | 5 | USER root 6 | 7 | COPY --from=step-cli /usr/local/bin/step /usr/local/bin/step 8 | COPY volumes/root-ca/certs/root_ca.crt /usr/local/share/ca-certificates/root_ca.crt 9 | RUN /usr/local/bin/step certificate install /usr/local/share/ca-certificates/root_ca.crt 10 | 11 | USER mastodon 12 | -------------------------------------------------------------------------------- /mastodon.compose.yml: -------------------------------------------------------------------------------- 1 | # must be used as an overlay for the main docker-compose file. Ex 2 | # docker-compose -f docker-compose.yml -f mastodon.castle.yml up 3 | 4 | # defaults 5 | # handle: castle_mastodon 6 | # admin: castle@mastodon.castle 7 | # password: password 8 | 9 | services: 10 | mastodon: 11 | labels: 12 | - traefik.http.routers.mastodon.rule=Host(`mastodon.castle`) 13 | - traefik.http.routers.mastodon.tls=true 14 | - traefik.http.routers.mastodon.tls.certresolver=smallstep 15 | - traefik.http.services.mastodon-sandcastles.loadBalancer.healthCheck.path=/health 16 | - traefik.port=2970 17 | - traefik.enable=true 18 | - traefik.docker.network=sandcastles_mastodon 19 | depends_on: 20 | mastodon_db: 21 | condition: service_started 22 | mastodon_redis: 23 | condition: service_started 24 | mastodon-init: 25 | condition: service_completed_successfully 26 | image: localhost/sandcastles/mastodon:latest 27 | build: 28 | dockerfile: ./mastodon.Dockerfile 29 | target: mastodon 30 | tags: 31 | - localhost/sandcastles/mastodon:latest 32 | command: bundle exec rails server -p 2970 -b 0.0.0.0 33 | # command: bundle exec puma -C config/puma.rb 34 | # command: tail -f /dev/null 35 | volumes: &mastodon_data_web_volume 36 | - 'mastodon_data_web:/mastodon/public/system:z' 37 | ports: 38 | - '2970:2970' 39 | networks: 40 | default: 41 | mastodon: 42 | environment: &mastodon_env 43 | LOCAL_DOMAIN: mastodon.castle 44 | ALTERNATE_DOMAINS: 127.0.0.1:2970,localhost:2970 45 | DB_HOST: mastodon_db 46 | DB_USER: mastodon 47 | DB_NAME: mastodon 48 | DB_PASS: password 49 | DB_PORT: 5432 50 | REDIS_HOST: mastodon_redis 51 | SECRET_KEY_BASE: 6f8fbd95f1e6b3d15121c6d0c54cae5efebeec70cf4d5c9ee09158a8241ffcb01a9d13b66e95ef8bc5adfc50c20ce9ffb3659b7c09e94449a45dd28582f5a578 52 | OTP_SECRET: 6163a17e06524facdb86fccb0293b6f6070cc6a493cba9d663451a477503896d3ac3dd300193c4773b5eefe97da22564e6b2a5555b43ced647e35e930d3687ed 53 | S3_ENABLED: 'false' 54 | ES_ENABLED: 'false' 55 | HTTPS_ENABLED: false 56 | LOCAL_HTTPS: false 57 | ALLOWED_PRIVATE_ADDRESSES: 10.0.0.0/8,127.0.0.1,172.0.0.0/8,192.0.0.0/8 58 | RAILS_ENV: production 59 | RAILS_LOG_LEVEL: info 60 | ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY: fkSxKD2bF396kdQbrP1EJ7WbU7ZgNokR 61 | ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT: r0hvVmzBVsjxC7AMlwhOzmtc36ZCOS1E 62 | ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY: PhdFyyfy5xJ7WVd2lWBpcPScRQHzRTNr 63 | # restart: on-failure 64 | healthcheck: 65 | test: ['CMD-SHELL',"curl -s --noproxy localhost localhost:2970/health | grep -q 'OK' || exit 1"] 66 | interval: 5s 67 | timeout: 1s 68 | retries: 10 69 | 70 | mastodon-init: 71 | image: ghcr.io/mastodon/mastodon:v4.3.4 72 | command: /opt/mastodon/init.sh 73 | volumes: 74 | - './volumes/mastodon/init.sh:/opt/mastodon/init.sh:ro' 75 | - 'mastodon_data_web:/mastodon/public/system:z' 76 | networks: 77 | - mastodon 78 | environment: 79 | <<: *mastodon_env 80 | RAILS_ENVIRONMENT: production 81 | FORCE_INIT: 'false' 82 | 83 | mastodon-streaming: 84 | scale: 0 85 | depends_on: 86 | mastodon: 87 | condition: service_healthy 88 | build: 89 | dockerfile: mastodon.Dockerfile 90 | target: mastodon-streaming 91 | tags: 92 | - localhost/sandcastles/mastodon:latest 93 | image: localhost/sandcastles/mastodon-streaming:latest 94 | command: node ./streaming/index.js 95 | networks: 96 | - mastodon 97 | environment: 98 | <<: *mastodon_env 99 | MASTODON_MODE: streaming 100 | restart: on-failure 101 | 102 | mastodon-sidekiq: 103 | depends_on: 104 | mastodon: 105 | condition: service_healthy 106 | image: localhost/sandcastles/mastodon:latest 107 | build: 108 | dockerfile: mastodon.Dockerfile 109 | target: mastodon 110 | tags: 111 | - localhost/sandcastles/mastodon:latest 112 | command: bundle exec sidekiq 113 | volumes: *mastodon_data_web_volume 114 | networks: 115 | - default 116 | - mastodon 117 | environment: 118 | <<: *mastodon_env 119 | MASTODON_MODE: sidekiq 120 | restart: on-failure 121 | 122 | mastodon_db: 123 | image: docker.io/postgres:16-alpine 124 | command: 125 | - postgres 126 | - -c 127 | - config_file=/etc/postgresql/postgresql.conf 128 | environment: 129 | POSTGRES_DB: mastodon 130 | POSTGRES_PASSWORD: password 131 | POSTGRES_USER: mastodon 132 | POSTGRES_HOST_AUTH_METHOD: scram-sha-256 133 | networks: 134 | - mastodon 135 | volumes: 136 | - mastodon_data_db:/var/lib/postgresql/data 137 | - ./volumes/postgresql.conf:/etc/postgresql/postgresql.conf:z 138 | 139 | mastodon_redis: 140 | image: docker.io/redis:7.4-alpine 141 | environment: 142 | - ALLOW_EMPTY_PASSWORD=yes 143 | volumes: 144 | - 'mastodon_data_redis:/data' 145 | networks: 146 | - mastodon 147 | restart: 'no' 148 | 149 | proxy: 150 | networks: 151 | default: 152 | aliases: 153 | - mastodon.castle 154 | mastodon: {} 155 | 156 | volumes: 157 | mastodon_data_db: 158 | driver: local 159 | mastodon_data_redis: 160 | driver: local 161 | mastodon_data_web: 162 | driver: local 163 | 164 | networks: 165 | mastodon: 166 | default: 167 | -------------------------------------------------------------------------------- /network-tools.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/smallstep/step-cli:0.25.0 AS step-cli 2 | 3 | FROM docker.io/jonlabelle/network-tools:latest 4 | 5 | USER root 6 | 7 | COPY --from=step-cli /usr/local/bin/step /usr/local/bin/step 8 | COPY volumes/root-ca/certs/root_ca.crt /usr/local/share/ca-certificates/root_ca.crt 9 | RUN /usr/local/bin/step certificate install /usr/local/share/ca-certificates/root_ca.crt 10 | -------------------------------------------------------------------------------- /pasture.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-alpine 2 | 3 | RUN pip install ipython 4 | RUN pip install bovine 5 | 6 | RUN pip install fediverse_pasture 7 | 8 | ADD https://codeberg.org/helge/funfedidev/archive/cd14bd038b3733f8efa343c2157dfd8768e641f9.zip /var/source.zip 9 | 10 | RUN unzip /var/source.zip -d /var/source/ 11 | RUN mkdir /work 12 | RUN cp /var/source/funfedidev/fediverse-pasture/work / -r 13 | WORKDIR /opt 14 | 15 | COPY volumes/root-ca/certs/root_ca.crt /usr/local/share/ca-certificates/root_ca.crt 16 | 17 | RUN cat /usr/local/share/ca-certificates/root_ca.crt >> /etc/ssl/certs/ca-certificates.crt -------------------------------------------------------------------------------- /pasture.castle.yml: -------------------------------------------------------------------------------- 1 | 2 | services: 3 | pasture_one_actor: 4 | image: pasture-sandcastles 5 | build: 6 | dockerfile: ./pasture.Dockerfile 7 | volumes: 8 | - ./volumes/pasture:/opt 9 | command: python -m fediverse_pasture.one_actor --port 80 --assets assets 10 | networks: 11 | pasture: 12 | pasture_runner: 13 | image: pasture-sandcastles 14 | build: 15 | dockerfile: ./pasture.Dockerfile 16 | volumes: 17 | - ./volumes/pasture:/opt 18 | working_dir: /work 19 | depends_on: 20 | - pasture_one_actor 21 | command: /bin/sh 22 | stdin_open: true 23 | tty: true 24 | networks: 25 | pasture: 26 | pasture_http_signature: 27 | image: pasture-sandcastles 28 | build: 29 | dockerfile: ./pasture.Dockerfile 30 | volumes: 31 | - ./volumes/pasture:/opt 32 | command: python -m fediverse_pasture.http_signature --port 80 33 | networks: 34 | pasture: 35 | pasture_verify_actor: 36 | container_name: actor.pasture.castle 37 | labels: 38 | - traefik.http.routers.pasture.rule=Host(`actor.pasture.castle`) 39 | - traefik.http.routers.pasture.tls=true 40 | - traefik.http.routers.pasture.tls.certresolver=smallstep 41 | - traefik.port=80 42 | - traefik.enable=true 43 | image: pasture-sandcastles 44 | build: 45 | dockerfile: ./pasture.Dockerfile 46 | volumes: 47 | - ./volumes/pasture:/opt 48 | command: python -m fediverse_pasture.verify_actor --port 80 --domain pasture_verify_actor 49 | expose: 50 | - 80 51 | networks: 52 | pasture: 53 | fediverse: 54 | 55 | proxy: 56 | networks: 57 | default: 58 | aliases: 59 | - actor.pasture.castle 60 | fediverse: 61 | aliases: 62 | - actor.pasture.castle 63 | 64 | 65 | networks: 66 | pasture: 67 | internal: true -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Sandcastles 2 | 3 | > Heavenly, she's so heavenly 4 | > When she smiles at you and she helps you build 5 | > Castles in the sand 6 | 7 | The Letterbook Sandcastles project offers an integration and federation test sandbox for developers of fediverse software. The goal is to make it easy to set up local instances of most fediverse servers, which can all federate with each other, with minimal necessary configuration. The whole environment is meant to be entirely local and self-contained. It doesn't require you to buy a domain name, or host any services exposed to the internet. You can also run your own under-development software without much difficulty, as long as you can run it in a docker container. 8 | 9 | # How it Works 10 | This is accomplished by running them all in a docker compose project, along with some supporting infrastructure to provision and use SSL certificates. 11 | 12 | ## Smallstep Certificate Authority 13 | This provides a root certificate authority which can issue SSL certificates to all of the other servers managed by the project. These servers are preconfigured to trust this CA, and the certificates will be provisioned as needed. 14 | 15 | ## Traefik Reverse Proxy 16 | Traefik serves as a reverse proxy, handling *all* of the federated traffick between services. It does this so that it can also manage their SSL certificates and connections. Traefik will automatically request or renew the certificates from Smallstep. 17 | 18 | # Available Services 19 | 20 | - [x] Mastodon (v4.3.4) 21 | - [x] Letterbook (development source build) 22 | - [ ] GoToSocial (#9) 23 | - [ ] Sharkey (#8) 24 | - [ ] Akkoma 25 | - [ ] Iceshrimp.NET 26 | - [ ] NodeBB 27 | 28 | # Getting Started 29 | 30 | ## Prerequisites 31 | 32 | You will need a docker run time and a docker client that supports docker compose. The easiest way to do that is to just install docker desktop. It's also confirmed to work with (rootless) podman compose. If you want to use a rootless container runtime, podman seems to work better than docker. 33 | 34 | You may also want to [install the `step cli`](https://smallstep.com/docs/step-cli/installation/). This isn't strictly necessary, but it will make it a lot easier to manage your certificates, and to add your new internal root CA as a trusted CA on your local computer. 35 | 36 | ## Quickstart 37 | 38 | If you just want to get up and running as quickly as possible, this is the process: 39 | 40 | ```shell 41 | git clone https://github.com/Letterbook/Sandcastles.git 42 | cd Sandcastles 43 | ./castle bootstrap 44 | ./castle build --all 45 | ./castle up --all 46 | # At this point, all of the available services should be running. Or, at 47 | # least initializing, and they'll be up soon. You can interact with them 48 | # and exchange federated messages, check their logs, and do any other 49 | # testing you want. 50 | ``` 51 | 52 | And when you're done, shut it all down: 53 | ```shell 54 | ./castle down --all 55 | ``` 56 | 57 | ## Steps 58 | 59 | Read on for a more detailed discussion of using sandcastles, what the scripts are doing, and how it works. 60 | 61 | ### 1. Clone this repo 62 | ```shell 63 | git clone https://github.com/Letterbook/Sandcastles.git 64 | cd Sandcastles 65 | ``` 66 | 67 | ### 2. Bootstrap and run the project 68 | 69 | You can use the provided `castle` script to perform most actions with the project. It _should_ work with most common shells, but was made in `bash`. It also _should_ work in linux, mac, and windows (under WSL). But, it was made in a linux environment. If you want to inspect what `castle` will do before you execute it, there is a `--dry-run` flag you can use for that. 70 | 71 | One of the core functions of the Sandcastles project is to automatically provision trusted TLS certificates and https endpoints for all the fediverse services the project manages. To accomplish this offline, we need to run our own internal certificate authority, so that we can issue certificates to our services for their internal hostnames. Bootstrapping initializes that internal CA, and makes it's root certificate available so other services can be set up to trust it. The `bootstrap` command normally only needs to be run once, unless you need to regenerate your private keys for the internal sandcastles CA. 72 | ```shell 73 | ./castle bootstrap 74 | ``` 75 | 76 | > [!Tip] 77 | > For all commands other than bootstrap, you can specify individual services to manage, or use the `--all` flag to cover all of them. 78 | 79 | We have to build our own container images so that we can add our own trusted certificate authority. In most cases, that's the only modification we need to make to the container images that projects provide. You should build the container images before first use. This is necessary if you're running on `podman`. If you're using `docker`, it's still a good idea. You may need to rebuild your container images periodically, to receive updates or test your changes. 80 | ```shell 81 | ./castle build --all 82 | ``` 83 | 84 | Then you can run and interact with the apps. 85 | ```shell 86 | ./castle up mastodon 87 | # do stuff 88 | ./castle down --all 89 | ``` 90 | 91 | Check the help for more details. 92 | 93 | ```shell 94 | ./castle help 95 | ./castle up --help 96 | # etc 97 | ``` 98 | 99 | ### Alternative, bootstrap and run manually 100 |
101 | 102 | Steps 103 | 104 | 105 | If for some reason you can't use the `castle` CLI, you can run all of the necessary steps yourself. 106 | 107 | ### 2b. Initialize the internal root CA 108 | ```shell 109 | docker compose -f bootstrap.yml up root-ca -d 110 | export ROOT_CA_CASTLE=$(docker compose -f bootstrap.yml ps -q) 111 | sleep 1 112 | docker cp $ROOT_CA_CASTLE:/home/step/templates volumes/root-ca/ 113 | docker cp $ROOT_CA_CASTLE:/home/step/secrets volumes/root-ca/ 114 | docker cp $ROOT_CA_CASTLE:/home/step/db volumes/root-ca/ 115 | docker cp $ROOT_CA_CASTLE:/home/step/config volumes/root-ca/ 116 | docker cp $ROOT_CA_CASTLE:/home/step/certs volumes/root-ca/ 117 | docker compose -f bootstrap.yml down 118 | cp volumes/ca.json volumes/root-ca/config/ca.json -f 119 | ``` 120 | 121 | And on *nix, set the file permissions so containers can access them: 122 | ```shell 123 | find volumes/root-ca -type d -exec chmod 755 {} + 124 | find volumes/root-ca -type f -exec chmod 644 {} + 125 | ``` 126 | 127 | This will configure the internal Smallstep CA, and will generate a number of secrets that you should maintain. If you need to regenerate any of these secrets, you can delete everything in the `./volumes/root-ca/` except the `.gitignore` file. 128 | 129 | ### 3. Prepare your host system 130 | 131 | #### Provide docker compose env vars 132 | 133 | Create a local env file for docker compose. This allows the traefik proxy to read labels on the containers, and route to them accordingly. 134 | 135 | ```shell 136 | DOCKER_PATH=$(sed -e 's|^.*://||' <<< $DOCKER_HOST) 137 | echo "DOCKER_PATH=${DOCKER_PATH}" > .env 138 | ``` 139 | 140 | ### 4. Run everything 141 | 142 | #### Using Podman 143 | 144 | This step will build new images that are configured to trust the root certificate authority you just created. Podman can build and run these images just fine, but podman compose doesn't set the right options to build the images. So, to use podman, you should build the images yourself, instead of relying on podman compose to do it. That can be done with the following commands. 145 | 146 | ```shell 147 | podman build . -f proxy.Dockerfile -t localhost/traefik-sandcastle:latest 148 | podman build . -f mastodon.Dockerfile -t localhost/mastodon-sandcastle:latest --target mastodon 149 | podman build . -f mastodon.Dockerfile -t localhost/mastodon-sandcastle:latest --target mastodon-streaming 150 | ``` 151 | 152 | #### Compose 153 | 154 | This will re-build the service images with built-in trust for your new internal root CA. This allows all of the services to federate with each other with no additional modifications. The re-build is only necessary once, or whenever a service is updated. You can run only the services you want by specifying their overlay files as extra `-f` args to `docker compose up` 155 | ```shell 156 | # add other *.castle.yml as needed 157 | docker compose -f docker-compose.yml -f mastodon.castle.yml -f sharkey.castle.yml \ 158 | up -d 159 | ``` 160 | 161 | If you need to rebuild these images because you regenerated the root CA secrets, you can do so by adding the `--build` and `--force-recreate` flags to the compose command. 162 | ```shell 163 | # add other *.castle.yml as needed 164 | docker compose -f docker-compose.yml -f mastodon.castle.yml -f sharkey.castle.yml \ 165 | up --build --force-recreate -d 166 | ``` 167 | 168 | At this point, you have a functioning sandbox full of fedi services that can all federate with each other. To make this maximally useful to you for local development of your own fedi service, continue on to the following optional steps. 169 |
170 | 171 | ### Add .castle domains to your local hosts file (Optional) 172 | Each of the components provided by this project is configured to serve from it's own .castle domain (ie. mastodon.castle, letterbook.castle, etc). To interact with them from your host (outside of any docker container) you should add these to your system's hosts file. 173 | ```ini 174 | # C:\Windows\System32\drivers\etc\hosts 175 | # OR 176 | # /etc/hosts 177 | 127.0.0.1 proxy.castle 178 | 127.0.0.1 mastodon.castle 179 | 127.0.0.1 letterbook.castle 180 | #etc 181 | ``` 182 | 183 | They'll all be available on port `8443` 184 | 185 | ### Add your internal CA as a trusted CA on your host (Optional) 186 | 187 | > [!Warning] 188 | > This is a security risk, if your CA's private key is ever compromised 189 | 190 | This requires having the [`step` cli](https://smallstep.com/docs/step-cli/reference/certificate/) installed on your host machine. After this step, your computer will trust TLS certificates issued by your internal sandcastles CA, just like it was a well known certificate authority like Verisign or Let's Encrypt. In the bootstrapping step, you generated a private key to be used by this CA to sign those TLS certificates. Anyone with access to that key can issue certificates that your computer will trust, even if they're fraudulent. This means another server with access to that key could impersonate other services, like gmail or banks. Keep that key safe. 191 | ```shell 192 | step certificate install --all volumes/root-ca/certs/root_ca.crt 193 | ``` 194 | 195 | #### Alternatively 196 | You might not have to configure system-wide trust for the sandcastle internal CA. Many stacks have a way to provide a custom CA bundle that can be used to validate certificates on HTTP requests. For example: 197 | 198 | - Nodejs 199 | `export NODE_EXTRA_CA_CERTS=/path/to/volumes/root-ca/certs/root_ca.crt` 200 | 201 | 202 | ### Revoke the internal CA (Optional) 203 | If you need to revoke trust in the Sandcastles CA, you can use [`step` cli](https://smallstep.com/docs/step-cli/reference/certificate/uninstall/) again. 204 | 205 | ```shell 206 | step certificate uninstall --all volumes/root-ca/certs/root_ca.crt 207 | ``` 208 | 209 | # Contributing 210 | 211 | Please contribute! There's so much fedi software out there! If you build or host some sort of fedi server, it would be so helpful for you to share some configurations that make it easy to spin up a test instance of that software in the sandbox. In the absence of a reference implementation or a test suite, this kind of integration sandbox might be our best resource for building new apps and improving cross-app federation support. 212 | 213 | I no longer have ready access to a Windows machine. I'll do what I can to maintain Windows compatibility, but help is appreciated. 214 | 215 | ## Adding New Backends 216 | 217 | There's usually two components to adding a new backend component to the sandcastle environment: a dockerfile, and a docker compose file. They must be named with the same prefix, because the `castle` CLI will naively search for and use them by file name. If a project requires multiple images to send messages to peer services, they will each need a dockerfile, so that they can be configured to trust the internal CA. In that case, you can append a suffix to the names of other necessary dockerfiles, like `my_service.Dockerfile` and `my_service-worker.Dockerfile`. 218 | 219 | The docker compose file must provide additional configuration to the central Traefik proxy, in order to make the server accessible to peer services. This comes in the form of container labels that will configure routing, and a network alias that will configure internal DNS. 220 | 221 | The `castle` CLI includes a `new` command that will scaffold these necessary files. For example: 222 | 223 | ```shell 224 | ./castle new my_serice 225 | ``` 226 | 227 | From there, you can customize the generated Dockerfile and compose.yml files. If you think any new components you add would be generally useful to other developers, please consider submitting them in a PR! 228 | 229 | ### Contributing New Components 230 | 231 | One of the goals for this project to have as close to zero required configuration as possible. The intent is to let developers interact with federated peer software. We don't want to force them to become experts in each of those project's operational quirks. So, new backend components should self-initialize as much as possible. If the app requires any migrations, secrets, or configuration, that should be included, with useful defaults. If at all possible, you should also provide a default user and password, so that developers can quickly log in and start generating test federated messages to examine. 232 | -------------------------------------------------------------------------------- /sharkey.compose.yml~: -------------------------------------------------------------------------------- 1 | # Copyright © 2014-2024 syuilo and contributors 2 | # Configuration adapted from 3 | # https://activitypub.software/TransFem-org/Sharkey/-/blob/7dfe9087b2580e97ea0eb990c94e98624b76c9de/docker-compose_example.yml 4 | 5 | # must be used as an overlay for the main docker-compose file. Ex 6 | # docker-compose -f docker-compose.yml -f sharkey.castle.yml up 7 | 8 | services: 9 | sharkey: 10 | container_name: sharkey.castle 11 | labels: 12 | - traefik.http.routers.sharkey.rule=Host(`sharkey.castle`) 13 | - traefik.http.routers.sharkey.tls=true 14 | - traefik.http.routers.sharkey.tls.certresolver=smallstep 15 | - traefik.http.services.sharkey-sandcastles.loadBalancer.healthCheck.path=/health 16 | - traefik.port=3000 17 | - traefik.enable=true 18 | - traefik.docker.network=fediverse 19 | image: registry.activitypub.software/transfem-org/sharkey:2024.5.1 20 | environment: 21 | - NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/root_ca.crt 22 | restart: always 23 | links: 24 | - sharkey_db 25 | - sharkey_redis 26 | depends_on: 27 | sharkey_db: 28 | condition: service_healthy 29 | sharkey_redis: 30 | condition: service_healthy 31 | ports: 32 | - "3000" 33 | networks: 34 | - shonk 35 | - fediverse 36 | - default 37 | volumes: 38 | - sharkey_data:/sharkey/files 39 | - ./volumes/sharkey/:/sharkey/.config:ro 40 | - ./volumes/root-ca/certs/root_ca.crt:/usr/local/share/ca-certificates/root_ca.crt 41 | 42 | sharkey_redis: 43 | restart: always 44 | image: redis:7-alpine 45 | networks: 46 | - shonk 47 | volumes: 48 | - sharkey_redis:/data 49 | healthcheck: 50 | test: "redis-cli ping" 51 | interval: 5s 52 | retries: 20 53 | 54 | sharkey_db: 55 | restart: always 56 | image: postgres:15-alpine 57 | networks: 58 | - shonk 59 | env_file: 60 | - ./volumes/sharkey/docker.env 61 | volumes: 62 | - sharkey_db:/var/lib/postgresql/data 63 | healthcheck: 64 | test: "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB" 65 | interval: 5s 66 | retries: 20 67 | 68 | # mcaptcha: 69 | # restart: always 70 | # image: mcaptcha/mcaptcha:latest 71 | # networks: 72 | # internal_network: 73 | # external_network: 74 | # aliases: 75 | # - localhost 76 | # ports: 77 | # - 7493:7493 78 | # env_file: 79 | # - .config/docker.env 80 | # environment: 81 | # PORT: 7493 82 | # MCAPTCHA_redis_URL: "redis://mcaptcha_redis/" 83 | # depends_on: 84 | # db: 85 | # condition: service_healthy 86 | # mcaptcha_redis: 87 | # condition: service_healthy 88 | # 89 | # mcaptcha_redis: 90 | # image: mcaptcha/cache:latest 91 | # networks: 92 | # - internal_network 93 | # healthcheck: 94 | # test: "redis-cli ping" 95 | # interval: 5s 96 | # retries: 20 97 | 98 | # meilisearch: 99 | # restart: always 100 | # image: getmeili/meilisearch:v1.3.4 101 | # environment: 102 | # - MEILI_NO_ANALYTICS=true 103 | # - MEILI_ENV=production 104 | # - MEILI_MASTER_KEY=ChangeThis 105 | # networks: 106 | # - shonk 107 | # volumes: 108 | # - ./meili_data:/meili_data 109 | 110 | volumes: 111 | sharkey_data: 112 | driver: local 113 | sharkey_db: 114 | driver: local 115 | sharkey_redis: 116 | driver: local 117 | 118 | networks: 119 | shonk: 120 | -------------------------------------------------------------------------------- /templates/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/smallstep/step-cli:0.25.0 AS step-cli 2 | 3 | FROM IMAGE FOR {{app_name}} as {{app_name}} 4 | 5 | USER root 6 | 7 | COPY --from=step-cli /usr/local/bin/step /usr/local/bin/step 8 | COPY volumes/root-ca/certs/root_ca.crt /usr/local/share/ca-certificates/root_ca.crt 9 | RUN /usr/local/bin/step certificate install /usr/local/share/ca-certificates/root_ca.crt 10 | 11 | # perform any other build steps you might need 12 | -------------------------------------------------------------------------------- /templates/compose.yml: -------------------------------------------------------------------------------- 1 | # must be used as an overlay for the main docker-compose file. Ex 2 | # docker-compose -f docker-compose.yml -f {{app_name}}.castle.yml up 3 | 4 | # defaults 5 | # handle: 6 | # admin: 7 | # password: 8 | 9 | services: 10 | {{app_name}}: 11 | labels: 12 | - traefik.http.routers.{{app_name}}.rule=Host(`{{app_name}}.castle`) 13 | - traefik.http.routers.{{app_name}}.tls=true 14 | - traefik.http.routers.{{app_name}}.tls.certresolver=smallstep 15 | - traefik.http.services.{{app_name}}-sandcastles.loadBalancer.healthCheck.path= 16 | - traefik.port= 17 | - traefik.enable=true 18 | - traefik.docker.network=sandcastles_{{app_name}} 19 | image: localhost/sandcastles/{{app_name}}:latest 20 | build: 21 | dockerfile: ./{{app_name}}.Dockerfile 22 | target: {{app_name}} 23 | tags: 24 | - localhost/sandcastles/{{app_name}}:latest 25 | command: 26 | ports: 27 | - '2999:' 28 | 29 | proxy: 30 | networks: 31 | default: 32 | aliases: 33 | - {{app_name}}.castle 34 | {{app_name}}: {} 35 | 36 | networks: 37 | {{app_name}}: 38 | default: 39 | -------------------------------------------------------------------------------- /traefik.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/traefik:v3.3 2 | 3 | COPY volumes/root-ca/certs/root_ca.crt /usr/local/share/ca-certificates/root_ca.crt 4 | 5 | RUN cat /usr/local/share/ca-certificates/root_ca.crt >> /etc/ssl/certs/ca-certificates.crt 6 | -------------------------------------------------------------------------------- /units/redir443.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Redirect tcp port 443 to 8443 with redir 3 | 4 | [Service] 5 | ExecStart=/bin/redir -sn -I https 127.0.0.1:443 127.0.0.1:8443 6 | 7 | [Install] 8 | WantedBy=multi-user.target 9 | -------------------------------------------------------------------------------- /units/redir80.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Redirect tcp port 80 to 8080 with redir 3 | 4 | [Service] 5 | ExecStart=/bin/redir -sn -I http 127.0.0.1:80 127.0.0.1:8080 6 | 7 | [Install] 8 | WantedBy=multi-user.target 9 | -------------------------------------------------------------------------------- /volumes/ca.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "/home/step/certs/root_ca.crt", 3 | "federatedRoots": null, 4 | "crt": "/home/step/certs/intermediate_ca.crt", 5 | "key": "/home/step/secrets/intermediate_ca_key", 6 | "address": ":9000", 7 | "insecureAddress": "", 8 | "dnsNames": [ 9 | "root-ca.castle", 10 | "root-ca", 11 | "localhost" 12 | ], 13 | "logger": { 14 | "format": "text" 15 | }, 16 | "db": { 17 | "type": "badgerv2", 18 | "dataSource": "/home/step/db", 19 | "badgerFileLoadingMode": "" 20 | }, 21 | "authority": { 22 | "enableAdmin": true, 23 | "policy": { 24 | "x509": { 25 | "allow": { 26 | "dns": ["*.castle"] 27 | } 28 | } 29 | } 30 | }, 31 | "tls": { 32 | "cipherSuites": [ 33 | "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", 34 | "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" 35 | ], 36 | "minVersion": 1.2, 37 | "maxVersion": 1.3, 38 | "renegotiation": false 39 | } 40 | } -------------------------------------------------------------------------------- /volumes/hosts: -------------------------------------------------------------------------------- 1 | # Default minimal hosts file to be provided to containers 2 | # Podman uses the local machine's hosts file by default, which can cause problems if you have conflicting entries with 3 | # a sandcastle network alias 4 | # Loopback entries; do not change. 5 | # For historical reasons, localhost precedes localhost.localdomain: 6 | 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 7 | ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 8 | -------------------------------------------------------------------------------- /volumes/mastodon/init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | 5 | INIT_FILE=/mastodon/public/system/.init_done 6 | 7 | if [ -e $INIT_FILE ] && [ "${FORCE_INIT:-false}" != "true" ]; then 8 | exit 0 9 | fi 10 | 11 | # migrate the db 12 | bundle exec rake db:migrate 13 | sleep 1 14 | 15 | # create the initial user account 16 | tootctl accounts create "${MASTODON_ADMIN_ACCOUNT:=castle_mastodon}" \ 17 | --email "${MASTODON_ADMIN_EMAIL:-castle@mastodon.castle}" \ 18 | --confirmed \ 19 | --role=Owner 20 | 21 | tootctl accounts approve "${MASTODON_ADMIN_ACCOUNT}" 22 | 23 | # set the user password to a known value 24 | echo "user = User.find(1) 25 | user.reset_password! 26 | user.reset_password('${MASTODON_ADMIN_PASSWORD:-password}', '${MASTODON_ADMIN_PASSWORD:-password}')" | rails c 27 | 28 | # mark initialization as complete, so it doesn't have to be repeated on the next startup 29 | touch $INIT_FILE 30 | -------------------------------------------------------------------------------- /volumes/pasture/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /volumes/postgresql.conf: -------------------------------------------------------------------------------- 1 | # ----------------------------- 2 | # PostgreSQL configuration file 3 | # ----------------------------- 4 | # 5 | # This file consists of lines of the form: 6 | # 7 | # name = value 8 | # 9 | # (The "=" is optional.) Whitespace may be used. Comments are introduced with 10 | # "#" anywhere on a line. The complete list of parameter names and allowed 11 | # values can be found in the PostgreSQL documentation. 12 | # 13 | # The commented-out settings shown in this file represent the default values. 14 | # Re-commenting a setting is NOT sufficient to revert it to the default value; 15 | # you need to reload the server. 16 | # 17 | # This file is read on server startup and when the server receives a SIGHUP 18 | # signal. If you edit the file on a running system, you have to SIGHUP the 19 | # server for the changes to take effect, run "pg_ctl reload", or execute 20 | # "SELECT pg_reload_conf()". Some parameters, which are marked below, 21 | # require a server shutdown and restart to take effect. 22 | # 23 | # Any parameter can also be given as a command-line option to the server, e.g., 24 | # "postgres -c log_connections=on". Some parameters can be changed at run time 25 | # with the "SET" SQL command. 26 | # 27 | # Memory units: B = bytes Time units: us = microseconds 28 | # kB = kilobytes ms = milliseconds 29 | # MB = megabytes s = seconds 30 | # GB = gigabytes min = minutes 31 | # TB = terabytes h = hours 32 | # d = days 33 | 34 | 35 | #------------------------------------------------------------------------------ 36 | # FILE LOCATIONS 37 | #------------------------------------------------------------------------------ 38 | 39 | # The default values of these variables are driven from the -D command-line 40 | # option or PGDATA environment variable, represented here as ConfigDir. 41 | 42 | #data_directory = 'ConfigDir' # use data in another directory 43 | # (change requires restart) 44 | # hba_file = '/etc/postgresql/pg_hba.conf' # host-based authentication file 45 | # (change requires restart) 46 | #ident_file = 'ConfigDir/pg_ident.conf' # ident configuration file 47 | # (change requires restart) 48 | 49 | # If external_pid_file is not explicitly set, no extra PID file is written. 50 | #external_pid_file = '' # write an extra PID file 51 | # (change requires restart) 52 | 53 | 54 | #------------------------------------------------------------------------------ 55 | # CONNECTIONS AND AUTHENTICATION 56 | #------------------------------------------------------------------------------ 57 | 58 | # - Connection Settings - 59 | 60 | listen_addresses = '*' 61 | # comma-separated list of addresses; 62 | # defaults to 'localhost'; use '*' for all 63 | # (change requires restart) 64 | #port = 5432 # (change requires restart) 65 | max_connections = 100 # (change requires restart) 66 | #superuser_reserved_connections = 3 # (change requires restart) 67 | #unix_socket_directories = '/var/run/postgresql' # comma-separated list of directories 68 | # (change requires restart) 69 | #unix_socket_group = '' # (change requires restart) 70 | #unix_socket_permissions = 0777 # begin with 0 to use octal notation 71 | # (change requires restart) 72 | #bonjour = off # advertise server via Bonjour 73 | # (change requires restart) 74 | #bonjour_name = '' # defaults to the computer name 75 | # (change requires restart) 76 | 77 | # - TCP settings - 78 | # see "man tcp" for details 79 | 80 | #tcp_keepalives_idle = 0 # TCP_KEEPIDLE, in seconds; 81 | # 0 selects the system default 82 | #tcp_keepalives_interval = 0 # TCP_KEEPINTVL, in seconds; 83 | # 0 selects the system default 84 | #tcp_keepalives_count = 0 # TCP_KEEPCNT; 85 | # 0 selects the system default 86 | #tcp_user_timeout = 0 # TCP_USER_TIMEOUT, in milliseconds; 87 | # 0 selects the system default 88 | 89 | #client_connection_check_interval = 0 # time between checks for client 90 | # disconnection while running queries; 91 | # 0 for never 92 | 93 | # - Authentication - 94 | 95 | #authentication_timeout = 1min # 1s-600s 96 | #password_encryption = scram-sha-256 # scram-sha-256 or md5 97 | #db_user_namespace = off 98 | 99 | # GSSAPI using Kerberos 100 | #krb_server_keyfile = 'FILE:${sysconfdir}/krb5.keytab' 101 | #krb_caseins_users = off 102 | 103 | # - SSL - 104 | 105 | #ssl = off 106 | #ssl_ca_file = '' 107 | #ssl_cert_file = 'server.crt' 108 | #ssl_crl_file = '' 109 | #ssl_crl_dir = '' 110 | #ssl_key_file = 'server.key' 111 | #ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers 112 | #ssl_prefer_server_ciphers = on 113 | #ssl_ecdh_curve = 'prime256v1' 114 | #ssl_min_protocol_version = 'TLSv1.2' 115 | #ssl_max_protocol_version = '' 116 | #ssl_dh_params_file = '' 117 | #ssl_passphrase_command = '' 118 | #ssl_passphrase_command_supports_reload = off 119 | 120 | 121 | #------------------------------------------------------------------------------ 122 | # RESOURCE USAGE (except WAL) 123 | #------------------------------------------------------------------------------ 124 | 125 | # - Memory - 126 | 127 | shared_buffers = 128MB # min 128kB 128 | # (change requires restart) 129 | #huge_pages = try # on, off, or try 130 | # (change requires restart) 131 | #huge_page_size = 0 # zero for system default 132 | # (change requires restart) 133 | #temp_buffers = 8MB # min 800kB 134 | #max_prepared_transactions = 0 # zero disables the feature 135 | # (change requires restart) 136 | # Caution: it is not advisable to set max_prepared_transactions nonzero unless 137 | # you actively intend to use prepared transactions. 138 | #work_mem = 4MB # min 64kB 139 | #hash_mem_multiplier = 2.0 # 1-1000.0 multiplier on hash table work_mem 140 | #maintenance_work_mem = 64MB # min 1MB 141 | #autovacuum_work_mem = -1 # min 1MB, or -1 to use maintenance_work_mem 142 | #logical_decoding_work_mem = 64MB # min 64kB 143 | #max_stack_depth = 2MB # min 100kB 144 | #shared_memory_type = mmap # the default is the first option 145 | # supported by the operating system: 146 | # mmap 147 | # sysv 148 | # windows 149 | # (change requires restart) 150 | dynamic_shared_memory_type = posix # the default is usually the first option 151 | # supported by the operating system: 152 | # posix 153 | # sysv 154 | # windows 155 | # mmap 156 | # (change requires restart) 157 | #min_dynamic_shared_memory = 0MB # (change requires restart) 158 | 159 | # - Disk - 160 | 161 | #temp_file_limit = -1 # limits per-process temp file space 162 | # in kilobytes, or -1 for no limit 163 | 164 | # - Kernel Resources - 165 | 166 | #max_files_per_process = 1000 # min 64 167 | # (change requires restart) 168 | 169 | # - Cost-Based Vacuum Delay - 170 | 171 | #vacuum_cost_delay = 0 # 0-100 milliseconds (0 disables) 172 | #vacuum_cost_page_hit = 1 # 0-10000 credits 173 | #vacuum_cost_page_miss = 2 # 0-10000 credits 174 | #vacuum_cost_page_dirty = 20 # 0-10000 credits 175 | #vacuum_cost_limit = 200 # 1-10000 credits 176 | 177 | # - Background Writer - 178 | 179 | #bgwriter_delay = 200ms # 10-10000ms between rounds 180 | #bgwriter_lru_maxpages = 100 # max buffers written/round, 0 disables 181 | #bgwriter_lru_multiplier = 2.0 # 0-10.0 multiplier on buffers scanned/round 182 | #bgwriter_flush_after = 512kB # measured in pages, 0 disables 183 | 184 | # - Asynchronous Behavior - 185 | 186 | #backend_flush_after = 0 # measured in pages, 0 disables 187 | #effective_io_concurrency = 1 # 1-1000; 0 disables prefetching 188 | #maintenance_io_concurrency = 10 # 1-1000; 0 disables prefetching 189 | #max_worker_processes = 8 # (change requires restart) 190 | #max_parallel_workers_per_gather = 2 # taken from max_parallel_workers 191 | #max_parallel_maintenance_workers = 2 # taken from max_parallel_workers 192 | #max_parallel_workers = 8 # maximum number of max_worker_processes that 193 | # can be used in parallel operations 194 | #parallel_leader_participation = on 195 | #old_snapshot_threshold = -1 # 1min-60d; -1 disables; 0 is immediate 196 | # (change requires restart) 197 | 198 | 199 | #------------------------------------------------------------------------------ 200 | # WRITE-AHEAD LOG 201 | #------------------------------------------------------------------------------ 202 | 203 | # - Settings - 204 | 205 | #wal_level = replica # minimal, replica, or logical 206 | # (change requires restart) 207 | #fsync = on # flush data to disk for crash safety 208 | # (turning this off can cause 209 | # unrecoverable data corruption) 210 | #synchronous_commit = on # synchronization level; 211 | # off, local, remote_write, remote_apply, or on 212 | #wal_sync_method = fsync # the default is the first option 213 | # supported by the operating system: 214 | # open_datasync 215 | # fdatasync (default on Linux and FreeBSD) 216 | # fsync 217 | # fsync_writethrough 218 | # open_sync 219 | #full_page_writes = on # recover from partial page writes 220 | #wal_log_hints = off # also do full page writes of non-critical updates 221 | # (change requires restart) 222 | #wal_compression = off # enables compression of full-page writes; 223 | # off, pglz, lz4, zstd, or on 224 | #wal_init_zero = on # zero-fill new WAL files 225 | #wal_recycle = on # recycle WAL files 226 | #wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers 227 | # (change requires restart) 228 | #wal_writer_delay = 200ms # 1-10000 milliseconds 229 | #wal_writer_flush_after = 1MB # measured in pages, 0 disables 230 | #wal_skip_threshold = 2MB 231 | 232 | #commit_delay = 0 # range 0-100000, in microseconds 233 | #commit_siblings = 5 # range 1-1000 234 | 235 | # - Checkpoints - 236 | 237 | #checkpoint_timeout = 5min # range 30s-1d 238 | #checkpoint_completion_target = 0.9 # checkpoint target duration, 0.0 - 1.0 239 | #checkpoint_flush_after = 256kB # measured in pages, 0 disables 240 | #checkpoint_warning = 30s # 0 disables 241 | max_wal_size = 1GB 242 | min_wal_size = 80MB 243 | 244 | # - Prefetching during recovery - 245 | 246 | #recovery_prefetch = try # prefetch pages referenced in the WAL? 247 | #wal_decode_buffer_size = 512kB # lookahead window used for prefetching 248 | # (change requires restart) 249 | 250 | # - Archiving - 251 | 252 | #archive_mode = off # enables archiving; off, on, or always 253 | # (change requires restart) 254 | #archive_library = '' # library to use to archive a logfile segment 255 | # (empty string indicates archive_command should 256 | # be used) 257 | #archive_command = '' # command to use to archive a logfile segment 258 | # placeholders: %p = path of file to archive 259 | # %f = file name only 260 | # e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f' 261 | #archive_timeout = 0 # force a logfile segment switch after this 262 | # number of seconds; 0 disables 263 | 264 | # - Archive Recovery - 265 | 266 | # These are only used in recovery mode. 267 | 268 | #restore_command = '' # command to use to restore an archived logfile segment 269 | # placeholders: %p = path of file to restore 270 | # %f = file name only 271 | # e.g. 'cp /mnt/server/archivedir/%f %p' 272 | #archive_cleanup_command = '' # command to execute at every restartpoint 273 | #recovery_end_command = '' # command to execute at completion of recovery 274 | 275 | # - Recovery Target - 276 | 277 | # Set these only when performing a targeted recovery. 278 | 279 | #recovery_target = '' # 'immediate' to end recovery as soon as a 280 | # consistent state is reached 281 | # (change requires restart) 282 | #recovery_target_name = '' # the named restore point to which recovery will proceed 283 | # (change requires restart) 284 | #recovery_target_time = '' # the time stamp up to which recovery will proceed 285 | # (change requires restart) 286 | #recovery_target_xid = '' # the transaction ID up to which recovery will proceed 287 | # (change requires restart) 288 | #recovery_target_lsn = '' # the WAL LSN up to which recovery will proceed 289 | # (change requires restart) 290 | #recovery_target_inclusive = on # Specifies whether to stop: 291 | # just after the specified recovery target (on) 292 | # just before the recovery target (off) 293 | # (change requires restart) 294 | #recovery_target_timeline = 'latest' # 'current', 'latest', or timeline ID 295 | # (change requires restart) 296 | #recovery_target_action = 'pause' # 'pause', 'promote', 'shutdown' 297 | # (change requires restart) 298 | 299 | 300 | #------------------------------------------------------------------------------ 301 | # REPLICATION 302 | #------------------------------------------------------------------------------ 303 | 304 | # - Sending Servers - 305 | 306 | # Set these on the primary and on any standby that will send replication data. 307 | 308 | #max_wal_senders = 10 # max number of walsender processes 309 | # (change requires restart) 310 | #max_replication_slots = 10 # max number of replication slots 311 | # (change requires restart) 312 | #wal_keep_size = 0 # in megabytes; 0 disables 313 | #max_slot_wal_keep_size = -1 # in megabytes; -1 disables 314 | #wal_sender_timeout = 60s # in milliseconds; 0 disables 315 | #track_commit_timestamp = off # collect timestamp of transaction commit 316 | # (change requires restart) 317 | 318 | # - Primary Server - 319 | 320 | # These settings are ignored on a standby server. 321 | 322 | #synchronous_standby_names = '' # standby servers that provide sync rep 323 | # method to choose sync standbys, number of sync standbys, 324 | # and comma-separated list of application_name 325 | # from standby(s); '*' = all 326 | #vacuum_defer_cleanup_age = 0 # number of xacts by which cleanup is delayed 327 | 328 | # - Standby Servers - 329 | 330 | # These settings are ignored on a primary server. 331 | 332 | #primary_conninfo = '' # connection string to sending server 333 | #primary_slot_name = '' # replication slot on sending server 334 | #promote_trigger_file = '' # file name whose presence ends recovery 335 | #hot_standby = on # "off" disallows queries during recovery 336 | # (change requires restart) 337 | #max_standby_archive_delay = 30s # max delay before canceling queries 338 | # when reading WAL from archive; 339 | # -1 allows indefinite delay 340 | #max_standby_streaming_delay = 30s # max delay before canceling queries 341 | # when reading streaming WAL; 342 | # -1 allows indefinite delay 343 | #wal_receiver_create_temp_slot = off # create temp slot if primary_slot_name 344 | # is not set 345 | #wal_receiver_status_interval = 10s # send replies at least this often 346 | # 0 disables 347 | #hot_standby_feedback = off # send info from standby to prevent 348 | # query conflicts 349 | #wal_receiver_timeout = 60s # time that receiver waits for 350 | # communication from primary 351 | # in milliseconds; 0 disables 352 | #wal_retrieve_retry_interval = 5s # time to wait before retrying to 353 | # retrieve WAL after a failed attempt 354 | #recovery_min_apply_delay = 0 # minimum delay for applying changes during recovery 355 | 356 | # - Subscribers - 357 | 358 | # These settings are ignored on a publisher. 359 | 360 | #max_logical_replication_workers = 4 # taken from max_worker_processes 361 | # (change requires restart) 362 | #max_sync_workers_per_subscription = 2 # taken from max_logical_replication_workers 363 | 364 | 365 | #------------------------------------------------------------------------------ 366 | # QUERY TUNING 367 | #------------------------------------------------------------------------------ 368 | 369 | # - Planner Method Configuration - 370 | 371 | #enable_async_append = on 372 | #enable_bitmapscan = on 373 | #enable_gathermerge = on 374 | #enable_hashagg = on 375 | #enable_hashjoin = on 376 | #enable_incremental_sort = on 377 | #enable_indexscan = on 378 | #enable_indexonlyscan = on 379 | #enable_material = on 380 | #enable_memoize = on 381 | #enable_mergejoin = on 382 | #enable_nestloop = on 383 | #enable_parallel_append = on 384 | #enable_parallel_hash = on 385 | #enable_partition_pruning = on 386 | #enable_partitionwise_join = off 387 | #enable_partitionwise_aggregate = off 388 | #enable_seqscan = on 389 | #enable_sort = on 390 | #enable_tidscan = on 391 | 392 | # - Planner Cost Constants - 393 | 394 | #seq_page_cost = 1.0 # measured on an arbitrary scale 395 | #random_page_cost = 4.0 # same scale as above 396 | #cpu_tuple_cost = 0.01 # same scale as above 397 | #cpu_index_tuple_cost = 0.005 # same scale as above 398 | #cpu_operator_cost = 0.0025 # same scale as above 399 | #parallel_setup_cost = 1000.0 # same scale as above 400 | #parallel_tuple_cost = 0.1 # same scale as above 401 | #min_parallel_table_scan_size = 8MB 402 | #min_parallel_index_scan_size = 512kB 403 | #effective_cache_size = 4GB 404 | 405 | #jit_above_cost = 100000 # perform JIT compilation if available 406 | # and query more expensive than this; 407 | # -1 disables 408 | #jit_inline_above_cost = 500000 # inline small functions if query is 409 | # more expensive than this; -1 disables 410 | #jit_optimize_above_cost = 500000 # use expensive JIT optimizations if 411 | # query is more expensive than this; 412 | # -1 disables 413 | 414 | # - Genetic Query Optimizer - 415 | 416 | #geqo = on 417 | #geqo_threshold = 12 418 | #geqo_effort = 5 # range 1-10 419 | #geqo_pool_size = 0 # selects default based on effort 420 | #geqo_generations = 0 # selects default based on effort 421 | #geqo_selection_bias = 2.0 # range 1.5-2.0 422 | #geqo_seed = 0.0 # range 0.0-1.0 423 | 424 | # - Other Planner Options - 425 | 426 | #default_statistics_target = 100 # range 1-10000 427 | #constraint_exclusion = partition # on, off, or partition 428 | #cursor_tuple_fraction = 0.1 # range 0.0-1.0 429 | #from_collapse_limit = 8 430 | #jit = on # allow JIT compilation 431 | #join_collapse_limit = 8 # 1 disables collapsing of explicit 432 | # JOIN clauses 433 | #plan_cache_mode = auto # auto, force_generic_plan or 434 | # force_custom_plan 435 | #recursive_worktable_factor = 10.0 # range 0.001-1000000 436 | 437 | 438 | #------------------------------------------------------------------------------ 439 | # REPORTING AND LOGGING 440 | #------------------------------------------------------------------------------ 441 | 442 | # - Where to Log - 443 | 444 | #log_destination = 'stderr' # Valid values are combinations of 445 | # stderr, csvlog, jsonlog, syslog, and 446 | # eventlog, depending on platform. 447 | # csvlog and jsonlog require 448 | # logging_collector to be on. 449 | 450 | # This is used when logging to stderr: 451 | #logging_collector = off # Enable capturing of stderr, jsonlog, 452 | # and csvlog into log files. Required 453 | # to be on for csvlogs and jsonlogs. 454 | # (change requires restart) 455 | 456 | # These are only used if logging_collector is on: 457 | #log_directory = 'log' # directory where log files are written, 458 | # can be absolute or relative to PGDATA 459 | #log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' # log file name pattern, 460 | # can include strftime() escapes 461 | #log_file_mode = 0600 # creation mode for log files, 462 | # begin with 0 to use octal notation 463 | #log_rotation_age = 1d # Automatic rotation of logfiles will 464 | # happen after that time. 0 disables. 465 | #log_rotation_size = 10MB # Automatic rotation of logfiles will 466 | # happen after that much log output. 467 | # 0 disables. 468 | #log_truncate_on_rotation = off # If on, an existing log file with the 469 | # same name as the new log file will be 470 | # truncated rather than appended to. 471 | # But such truncation only occurs on 472 | # time-driven rotation, not on restarts 473 | # or size-driven rotation. Default is 474 | # off, meaning append to existing files 475 | # in all cases. 476 | 477 | # These are relevant when logging to syslog: 478 | #syslog_facility = 'LOCAL0' 479 | #syslog_ident = 'postgres' 480 | #syslog_sequence_numbers = on 481 | #syslog_split_messages = on 482 | 483 | # This is only relevant when logging to eventlog (Windows): 484 | # (change requires restart) 485 | #event_source = 'PostgreSQL' 486 | 487 | # - When to Log - 488 | 489 | log_min_messages = warning # values in order of decreasing detail: 490 | # debug5 491 | # debug4 492 | # debug3 493 | # debug2 494 | # debug1 495 | # info 496 | # notice 497 | # warning 498 | # error 499 | # log 500 | # fatal 501 | # panic 502 | 503 | log_min_error_statement = warning # values in order of decreasing detail: 504 | # debug5 505 | # debug4 506 | # debug3 507 | # debug2 508 | # debug1 509 | # info 510 | # notice 511 | # warning 512 | # error 513 | # log 514 | # fatal 515 | # panic (effectively off) 516 | 517 | #log_min_duration_statement = -1 # -1 is disabled, 0 logs all statements 518 | # and their durations, > 0 logs only 519 | # statements running at least this number 520 | # of milliseconds 521 | 522 | #log_min_duration_sample = -1 # -1 is disabled, 0 logs a sample of statements 523 | # and their durations, > 0 logs only a sample of 524 | # statements running at least this number 525 | # of milliseconds; 526 | # sample fraction is determined by log_statement_sample_rate 527 | 528 | #log_statement_sample_rate = 1.0 # fraction of logged statements exceeding 529 | # log_min_duration_sample to be logged; 530 | # 1.0 logs all such statements, 0.0 never logs 531 | 532 | 533 | #log_transaction_sample_rate = 0.0 # fraction of transactions whose statements 534 | # are logged regardless of their duration; 1.0 logs all 535 | # statements from all transactions, 0.0 never logs 536 | 537 | #log_startup_progress_interval = 10s # Time between progress updates for 538 | # long-running startup operations. 539 | # 0 disables the feature, > 0 indicates 540 | # the interval in milliseconds. 541 | 542 | # - What to Log - 543 | 544 | #debug_print_parse = off 545 | #debug_print_rewritten = off 546 | #debug_print_plan = off 547 | #debug_pretty_print = on 548 | #log_autovacuum_min_duration = 10min # log autovacuum activity; 549 | # -1 disables, 0 logs all actions and 550 | # their durations, > 0 logs only 551 | # actions running at least this number 552 | # of milliseconds. 553 | #log_checkpoints = on 554 | log_connections = on 555 | log_disconnections = on 556 | #log_duration = off 557 | #log_error_verbosity = verbose # terse, default, or verbose messages 558 | #log_hostname = off 559 | #log_line_prefix = '%m [%p] ' # special values: 560 | # %a = application name 561 | # %u = user name 562 | # %d = database name 563 | # %r = remote host and port 564 | # %h = remote host 565 | # %b = backend type 566 | # %p = process ID 567 | # %P = process ID of parallel group leader 568 | # %t = timestamp without milliseconds 569 | # %m = timestamp with milliseconds 570 | # %n = timestamp with milliseconds (as a Unix epoch) 571 | # %Q = query ID (0 if none or not computed) 572 | # %i = command tag 573 | # %e = SQL state 574 | # %c = session ID 575 | # %l = session line number 576 | # %s = session start timestamp 577 | # %v = virtual transaction ID 578 | # %x = transaction ID (0 if none) 579 | # %q = stop here in non-session 580 | # processes 581 | # %% = '%' 582 | # e.g. '<%u%%%d> ' 583 | #log_lock_waits = off # log lock waits >= deadlock_timeout 584 | #log_recovery_conflict_waits = off # log standby recovery conflict waits 585 | # >= deadlock_timeout 586 | #log_parameter_max_length = -1 # when logging statements, limit logged 587 | # bind-parameter values to N bytes; 588 | # -1 means print in full, 0 disables 589 | #log_parameter_max_length_on_error = 0 # when logging an error, limit logged 590 | # bind-parameter values to N bytes; 591 | # -1 means print in full, 0 disables 592 | log_statement = 'ddl' # none, ddl, mod, all 593 | #log_replication_commands = off 594 | #log_temp_files = -1 # log temporary files equal or larger 595 | # than the specified size in kilobytes; 596 | # -1 disables, 0 logs all temp files 597 | log_timezone = 'UTC' 598 | 599 | 600 | #------------------------------------------------------------------------------ 601 | # PROCESS TITLE 602 | #------------------------------------------------------------------------------ 603 | 604 | #cluster_name = '' # added to process titles if nonempty 605 | # (change requires restart) 606 | #update_process_title = on 607 | 608 | 609 | #------------------------------------------------------------------------------ 610 | # STATISTICS 611 | #------------------------------------------------------------------------------ 612 | 613 | # - Cumulative Query and Index Statistics - 614 | 615 | #track_activities = on 616 | #track_activity_query_size = 1024 # (change requires restart) 617 | #track_counts = on 618 | #track_io_timing = off 619 | #track_wal_io_timing = off 620 | #track_functions = none # none, pl, all 621 | #stats_fetch_consistency = cache 622 | 623 | 624 | # - Monitoring - 625 | 626 | #compute_query_id = auto 627 | #log_statement_stats = off 628 | #log_parser_stats = off 629 | #log_planner_stats = off 630 | #log_executor_stats = off 631 | 632 | 633 | #------------------------------------------------------------------------------ 634 | # AUTOVACUUM 635 | #------------------------------------------------------------------------------ 636 | 637 | #autovacuum = on # Enable autovacuum subprocess? 'on' 638 | # requires track_counts to also be on. 639 | #autovacuum_max_workers = 3 # max number of autovacuum subprocesses 640 | # (change requires restart) 641 | #autovacuum_naptime = 1min # time between autovacuum runs 642 | #autovacuum_vacuum_threshold = 50 # min number of row updates before 643 | # vacuum 644 | #autovacuum_vacuum_insert_threshold = 1000 # min number of row inserts 645 | # before vacuum; -1 disables insert 646 | # vacuums 647 | #autovacuum_analyze_threshold = 50 # min number of row updates before 648 | # analyze 649 | #autovacuum_vacuum_scale_factor = 0.2 # fraction of table size before vacuum 650 | #autovacuum_vacuum_insert_scale_factor = 0.2 # fraction of inserts over table 651 | # size before insert vacuum 652 | #autovacuum_analyze_scale_factor = 0.1 # fraction of table size before analyze 653 | #autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum 654 | # (change requires restart) 655 | #autovacuum_multixact_freeze_max_age = 400000000 # maximum multixact age 656 | # before forced vacuum 657 | # (change requires restart) 658 | #autovacuum_vacuum_cost_delay = 2ms # default vacuum cost delay for 659 | # autovacuum, in milliseconds; 660 | # -1 means use vacuum_cost_delay 661 | #autovacuum_vacuum_cost_limit = -1 # default vacuum cost limit for 662 | # autovacuum, -1 means use 663 | # vacuum_cost_limit 664 | 665 | 666 | #------------------------------------------------------------------------------ 667 | # CLIENT CONNECTION DEFAULTS 668 | #------------------------------------------------------------------------------ 669 | 670 | # - Statement Behavior - 671 | 672 | #client_min_messages = notice # values in order of decreasing detail: 673 | # debug5 674 | # debug4 675 | # debug3 676 | # debug2 677 | # debug1 678 | # log 679 | # notice 680 | # warning 681 | # error 682 | #search_path = '"$user", public' # schema names 683 | #row_security = on 684 | #default_table_access_method = 'heap' 685 | #default_tablespace = '' # a tablespace name, '' uses the default 686 | #default_toast_compression = 'pglz' # 'pglz' or 'lz4' 687 | #temp_tablespaces = '' # a list of tablespace names, '' uses 688 | # only default tablespace 689 | #check_function_bodies = on 690 | #default_transaction_isolation = 'read committed' 691 | #default_transaction_read_only = off 692 | #default_transaction_deferrable = off 693 | #session_replication_role = 'origin' 694 | #statement_timeout = 0 # in milliseconds, 0 is disabled 695 | #lock_timeout = 0 # in milliseconds, 0 is disabled 696 | #idle_in_transaction_session_timeout = 0 # in milliseconds, 0 is disabled 697 | #idle_session_timeout = 0 # in milliseconds, 0 is disabled 698 | #vacuum_freeze_table_age = 150000000 699 | #vacuum_freeze_min_age = 50000000 700 | #vacuum_failsafe_age = 1600000000 701 | #vacuum_multixact_freeze_table_age = 150000000 702 | #vacuum_multixact_freeze_min_age = 5000000 703 | #vacuum_multixact_failsafe_age = 1600000000 704 | #bytea_output = 'hex' # hex, escape 705 | #xmlbinary = 'base64' 706 | #xmloption = 'content' 707 | #gin_pending_list_limit = 4MB 708 | 709 | # - Locale and Formatting - 710 | 711 | datestyle = 'iso, mdy' 712 | #intervalstyle = 'postgres' 713 | timezone = 'UTC' 714 | #timezone_abbreviations = 'Default' # Select the set of available time zone 715 | # abbreviations. Currently, there are 716 | # Default 717 | # Australia (historical usage) 718 | # India 719 | # You can create your own file in 720 | # share/timezonesets/. 721 | #extra_float_digits = 1 # min -15, max 3; any value >0 actually 722 | # selects precise output mode 723 | #client_encoding = sql_ascii # actually, defaults to database 724 | # encoding 725 | 726 | # These settings are initialized by initdb, but they can be changed. 727 | lc_messages = 'en_US.utf8' # locale for system error message 728 | # strings 729 | lc_monetary = 'en_US.utf8' # locale for monetary formatting 730 | lc_numeric = 'en_US.utf8' # locale for number formatting 731 | lc_time = 'en_US.utf8' # locale for time formatting 732 | 733 | # default configuration for text search 734 | default_text_search_config = 'pg_catalog.english' 735 | 736 | # - Shared Library Preloading - 737 | 738 | #local_preload_libraries = '' 739 | #session_preload_libraries = '' 740 | #shared_preload_libraries = '' # (change requires restart) 741 | #jit_provider = 'llvmjit' # JIT library to use 742 | 743 | # - Other Defaults - 744 | 745 | #dynamic_library_path = '$libdir' 746 | #gin_fuzzy_search_limit = 0 747 | 748 | 749 | #------------------------------------------------------------------------------ 750 | # LOCK MANAGEMENT 751 | #------------------------------------------------------------------------------ 752 | 753 | #deadlock_timeout = 1s 754 | #max_locks_per_transaction = 64 # min 10 755 | # (change requires restart) 756 | #max_pred_locks_per_transaction = 64 # min 10 757 | # (change requires restart) 758 | #max_pred_locks_per_relation = -2 # negative values mean 759 | # (max_pred_locks_per_transaction 760 | # / -max_pred_locks_per_relation) - 1 761 | #max_pred_locks_per_page = 2 # min 0 762 | 763 | 764 | #------------------------------------------------------------------------------ 765 | # VERSION AND PLATFORM COMPATIBILITY 766 | #------------------------------------------------------------------------------ 767 | 768 | # - Previous PostgreSQL Versions - 769 | 770 | #array_nulls = on 771 | #backslash_quote = safe_encoding # on, off, or safe_encoding 772 | #escape_string_warning = on 773 | #lo_compat_privileges = off 774 | #quote_all_identifiers = off 775 | #standard_conforming_strings = on 776 | #synchronize_seqscans = on 777 | 778 | # - Other Platforms and Clients - 779 | 780 | #transform_null_equals = off 781 | 782 | 783 | #------------------------------------------------------------------------------ 784 | # ERROR HANDLING 785 | #------------------------------------------------------------------------------ 786 | 787 | #exit_on_error = off # terminate session on any error? 788 | #restart_after_crash = on # reinitialize after backend crash? 789 | #data_sync_retry = off # retry or panic on failure to fsync 790 | # data? 791 | # (change requires restart) 792 | #recovery_init_sync_method = fsync # fsync, syncfs (Linux 5.8+) 793 | 794 | 795 | #--------------------------------------------------------------NewFile1.txt---------------- 796 | # CONFIG FILE INCLUDES 797 | #------------------------------------------------------------------------------ 798 | 799 | # These options allow settings to be loaded from files other than the 800 | # default postgresql.conf. Note that these are directives, not variable 801 | # assignments, so they can usefully be given more than once. 802 | 803 | #include_dir = '...' # include files ending in '.conf' from 804 | # a directory, e.g., 'conf.d' 805 | #include_if_exists = '...' # include file only if it exists 806 | #include = '...' # include file 807 | 808 | 809 | #------------------------------------------------------------------------------ 810 | # CUSTOMIZED OPTIONS 811 | #------------------------------------------------------------------------------ 812 | 813 | # Add settings for extensions here 814 | -------------------------------------------------------------------------------- /volumes/proxy/acme.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Letterbook/Sandcastles/a1157c28a9e9c6483353f389dc16cbba3f81cec2/volumes/proxy/acme.json -------------------------------------------------------------------------------- /volumes/proxy/traefik.yml: -------------------------------------------------------------------------------- 1 | entryPoints: 2 | web: 3 | address: ':80' 4 | http: 5 | redirections: 6 | entryPoint: 7 | to: websecure 8 | scheme: https 9 | websecure: 10 | address: ':443' 11 | hostwebsecure: 12 | address: 'localhost:8443' 13 | 14 | log: 15 | level: INFO 16 | 17 | accessLog: {} 18 | 19 | api: 20 | dashboard: true 21 | debug: true 22 | insecure: true 23 | 24 | providers: 25 | docker: 26 | watch: true 27 | network: sandcastles_default 28 | endpoint: unix:///var/run/docker.sock 29 | exposedByDefault: false 30 | file: 31 | filename: '/etc/traefik/traefik_dynamic.yml' 32 | 33 | certificatesResolvers: 34 | smallstep: 35 | acme: 36 | caServer: 'https://root-ca.castle:9000/acme/acme/directory' 37 | storage: 'acme.json' 38 | httpChallenge: 39 | entryPoint: web -------------------------------------------------------------------------------- /volumes/proxy/traefik_dynamic.yml: -------------------------------------------------------------------------------- 1 | http: 2 | middlewares: 3 | simpleAuth: 4 | basicAuth: 5 | users: 6 | # username: sandcastles 7 | # password: admin 8 | - "sandcastles:$apr1$Xe1bQOFU$OQ.6qf4QCcRk5E8mQ.yt4." 9 | routers: 10 | api: 11 | rule: "Host(`proxy.castle`) && PathPrefix(`/dashboard`)" 12 | entrypoints: 13 | - websecure 14 | middlewares: 15 | - simpleAuth 16 | service: 'api@internal' 17 | tls: 18 | certResolver: 'smallstep' 19 | dashboard: 20 | rule: "Host(`localhost`) && PathPrefix(`/dashboard`)" 21 | entrypoints: 22 | - websecure 23 | - web 24 | - hostwebsecure 25 | middlewares: 26 | - simpleAuth 27 | service: 'api@internal' -------------------------------------------------------------------------------- /volumes/root-ca/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this dir, except this file 2 | # This dir will contain secrets that you will need to trust on your 3 | * 4 | !.gitignore 5 | !readme.md -------------------------------------------------------------------------------- /volumes/root-ca/readme.md: -------------------------------------------------------------------------------- 1 | # Warning 2 | 3 | > [!WARNING] 4 | > After setup, this directory will contain secrets that should never be shared with anyone. Doing so could put your computer at risk. 5 | 6 | In order to permit most fedi software to talk to each other with minimal modifications, they need to do it over HTTPS. Which means they all need to have SSL certificates, which all need to be issued by a certificate authority, which they need to trust. That likely includes Letterbook, or any other fediverse application that you're developing on your host machine. Which means you need to trust it as well. 7 | 8 | When you use this project, you're going to create a root certificate authority, so that you can issue those certificates. Most of that happens automatically, and you don't need to be *too* concerned with how it works. But, in order to make full use of it, you will probably have to add that CA as a trusted root certificate authority on your own machine. 9 | 10 | After you've set up the project, this directory will contain the private signing key for that CA, along with a variety of other secrets and configuration data. **Never reveal this data to anyone!** If the signing key of the local root CA you're about to create is ever compromised, it could be used to perform invisible man-in-the-middle attacks against any computer that trusts it. Which likely includes the computer you're using to read this right now. -------------------------------------------------------------------------------- /volumes/sharkey/default.yml: -------------------------------------------------------------------------------- 1 | #━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2 | # Misskey configuration 3 | #━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4 | 5 | # ┌──────────────────────────────┐ 6 | #───┘ a boring but important thing └──────────────────────────── 7 | 8 | # 9 | # First of all, let me tell you a story that may possibly be 10 | # boring to you and possibly important to you. 11 | # 12 | # Misskey is licensed under the AGPLv3 license. This license is 13 | # known to be often misunderstood. Please read the following 14 | # instructions carefully and select the appropriate option so 15 | # that you do not negligently cause a license violation. 16 | # 17 | 18 | # -------- 19 | # Option 1: If you host Misskey AS-IS (without any changes to 20 | # the source code. forks are not included). 21 | # 22 | # Step 1: Congratulations! You don't need to do anything. 23 | 24 | # -------- 25 | # Option 2: If you have made changes to the source code (forks 26 | # are included) and publish a Git repository of source 27 | # code. There should be no access restrictions on 28 | # this repository. Strictly speaking, it doesn't have 29 | # to be a Git repository, but you'll probably use Git! 30 | # 31 | # Step 1: Build and run the Misskey server first. 32 | # Step 2: Open in 33 | # your browser with the administrator account. 34 | # Step 3: Enter the URL of your Git repository in the 35 | # "Repository URL" field. 36 | 37 | # -------- 38 | # Option 3: If neither of the above applies to you. 39 | # (In this case, the source code should be published 40 | # on the Misskey interface. IT IS NOT ENOUGH TO 41 | # DISCLOSE THE SOURCE CODE WEHN A USER REQUESTS IT BY 42 | # E-MAIL OR OTHER MEANS. If you are not satisfied 43 | # with this, it is recommended that you read the 44 | # license again carefully. Anyway, enabling this 45 | # option will automatically generate and publish a 46 | # tarball at build time, protecting you from 47 | # inadvertent license violations. (There is no legal 48 | # guarantee, of course.) The tarball will generated 49 | # from the root directory of your codebase. So it is 50 | # also recommended to check directory 51 | # once after building and before activating the server 52 | # to avoid ACCIDENTAL LEAKING OF SENSITIVE INFORMATION. 53 | # To prevent certain files from being included in the 54 | # tarball, add a glob pattern after line 15 in 55 | # . DO NOT FORGET TO BUILD AFTER 56 | # ENABLING THIS OPTION!) 57 | # 58 | # Step 1: Uncomment the following line. 59 | # 60 | # publishTarballInsteadOfProvideRepositoryUrl: true 61 | 62 | # ┌─────┐ 63 | #───┘ URL └───────────────────────────────────────────────────── 64 | 65 | # Final accessible URL seen by a user. 66 | url: https://sharkey.castle/ 67 | 68 | # ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE 69 | # URL SETTINGS AFTER THAT! 70 | 71 | # ┌───────────────────────┐ 72 | #───┘ Port and TLS settings └─────────────────────────────────── 73 | 74 | # 75 | # Misskey requires a reverse proxy to support HTTPS connections. 76 | # 77 | # +----- https://example.tld/ ------------+ 78 | # +------+ |+-------------+ +----------------+| 79 | # | User | ---> || Proxy (443) | ---> | Misskey (3000) || 80 | # +------+ |+-------------+ +----------------+| 81 | # +---------------------------------------+ 82 | # 83 | # You need to set up a reverse proxy. (e.g. nginx) 84 | # An encrypted connection with HTTPS is highly recommended 85 | # because tokens may be transferred in GET requests. 86 | 87 | # The port that your Misskey server should listen on. 88 | port: 3000 89 | 90 | # ┌──────────────────────────┐ 91 | #───┘ PostgreSQL configuration └──────────────────────────────── 92 | 93 | db: 94 | host: sharkey_db 95 | port: 5432 96 | 97 | # Database name 98 | db: misskey 99 | 100 | # Auth 101 | user: example-misskey-user 102 | pass: example-misskey-pass 103 | 104 | # Whether disable Caching queries 105 | #disableCache: true 106 | 107 | # Extra Connection options 108 | #extra: 109 | # ssl: true 110 | 111 | dbReplications: false 112 | 113 | # You can configure any number of replicas here 114 | #dbSlaves: 115 | # - 116 | # host: 117 | # port: 118 | # db: 119 | # user: 120 | # pass: 121 | # - 122 | # host: 123 | # port: 124 | # db: 125 | # user: 126 | # pass: 127 | 128 | # ┌─────────────────────┐ 129 | #───┘ Redis configuration └───────────────────────────────────── 130 | 131 | redis: 132 | host: sharkey_redis 133 | port: 6379 134 | #family: 0 # 0=Both, 4=IPv4, 6=IPv6 135 | #pass: example-pass 136 | #prefix: example-prefix 137 | #db: 1 138 | 139 | #redisForPubsub: 140 | # host: redis 141 | # port: 6379 142 | # #family: 0 # 0=Both, 4=IPv4, 6=IPv6 143 | # #pass: example-pass 144 | # #prefix: example-prefix 145 | # #db: 1 146 | 147 | #redisForJobQueue: 148 | # host: redis 149 | # port: 6379 150 | # #family: 0 # 0=Both, 4=IPv4, 6=IPv6 151 | # #pass: example-pass 152 | # #prefix: example-prefix 153 | # #db: 1 154 | 155 | #redisForTimelines: 156 | # host: redis 157 | # port: 6379 158 | # #family: 0 # 0=Both, 4=IPv4, 6=IPv6 159 | # #pass: example-pass 160 | # #prefix: example-prefix 161 | # #db: 1 162 | 163 | # ┌───────────────────────────┐ 164 | #───┘ MeiliSearch configuration └───────────────────────────── 165 | 166 | # You can set scope to local (default value) or global 167 | # (include notes from remote). 168 | 169 | #meilisearch: 170 | # host: meilisearch 171 | # port: 7700 172 | # apiKey: '' 173 | # ssl: true 174 | # index: '' 175 | # scope: global 176 | 177 | # ┌───────────────┐ 178 | #───┘ ID generation └─────────────────────────────────────────── 179 | 180 | # You can select the ID generation method. 181 | # You don't usually need to change this setting, but you can 182 | # change it according to your preferences. 183 | 184 | # Available methods: 185 | # aid ... Short, Millisecond accuracy 186 | # aidx ... Millisecond accuracy 187 | # meid ... Similar to ObjectID, Millisecond accuracy 188 | # ulid ... Millisecond accuracy 189 | # objectid ... This is left for backward compatibility 190 | 191 | # ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE 192 | # ID SETTINGS AFTER THAT! 193 | 194 | id: 'aidx' 195 | 196 | # ┌────────────────┐ 197 | #───┘ Error tracking └────────────────────────────────────────── 198 | 199 | # Sentry is available for error tracking. 200 | # See the Sentry documentation for more details on options. 201 | 202 | #sentryForBackend: 203 | # enableNodeProfiling: true 204 | # options: 205 | # dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' 206 | 207 | #sentryForFrontend: 208 | # options: 209 | # dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' 210 | 211 | # ┌─────────────────────┐ 212 | #───┘ Other configuration └───────────────────────────────────── 213 | 214 | # Whether disable HSTS 215 | #disableHsts: true 216 | 217 | # Number of worker processes 218 | #clusterLimit: 1 219 | 220 | # Job concurrency per worker 221 | # deliverJobConcurrency: 128 222 | # inboxJobConcurrency: 16 223 | # relationshipJobConcurrency: 16 224 | # What's relationshipJob?: 225 | # Follow, unfollow, block and unblock(ings) while following-imports, etc. or account migrations. 226 | 227 | # Job rate limiter 228 | # deliverJobPerSec: 128 229 | # inboxJobPerSec: 32 230 | # relationshipJobPerSec: 64 231 | 232 | # Job attempts 233 | # deliverJobMaxAttempts: 12 234 | # inboxJobMaxAttempts: 8 235 | 236 | # Local address used for outgoing requests 237 | #outgoingAddress: 127.0.0.1 238 | 239 | # IP address family used for outgoing request (ipv4, ipv6 or dual) 240 | #outgoingAddressFamily: ipv4 241 | 242 | # Amount of characters that can be used when writing notes (maximum: 8192, minimum: 1) 243 | maxNoteLength: 3000 244 | 245 | # Proxy for HTTP/HTTPS 246 | #proxy: http://127.0.0.1:3128 247 | 248 | proxyBypassHosts: 249 | - api.deepl.com 250 | - api-free.deepl.com 251 | - www.recaptcha.net 252 | - hcaptcha.com 253 | - challenges.cloudflare.com 254 | 255 | # Proxy for SMTP/SMTPS 256 | #proxySmtp: http://127.0.0.1:3128 # use HTTP/1.1 CONNECT 257 | #proxySmtp: socks4://127.0.0.1:1080 # use SOCKS4 258 | #proxySmtp: socks5://127.0.0.1:1080 # use SOCKS5 259 | 260 | # Media Proxy 261 | #mediaProxy: https://example.com/proxy 262 | 263 | # Proxy remote files (default: true) 264 | # Proxy remote files by this instance or mediaProxy to prevent remote files from running in remote domains. 265 | proxyRemoteFiles: true 266 | 267 | # Movie Thumbnail Generation URL 268 | # There is no reference implementation. 269 | # For example, Misskey will point to the following URL: 270 | # https://example.com/thumbnail.webp?thumbnail=1&url=https%3A%2F%2Fstorage.example.com%2Fpath%2Fto%2Fvideo.mp4 271 | #videoThumbnailGenerator: https://example.com 272 | 273 | # Sign to ActivityPub GET request (default: true) 274 | signToActivityPubGet: true 275 | # check that inbound ActivityPub GET requests are signed ("authorized fetch") 276 | checkActivityPubGetSignature: true 277 | 278 | # For security reasons, uploading attachments from the intranet is prohibited, 279 | # but exceptions can be made from the following settings. Default value is "undefined". 280 | # Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)). 281 | #allowedPrivateNetworks: [ 282 | # '127.0.0.1/32' 283 | #] 284 | 285 | #customMOTD: ['Hello World', 'The sharks rule all', 'Shonks'] 286 | 287 | # Upload or download file size limits (bytes) 288 | #maxFileSize: 262144000 289 | -------------------------------------------------------------------------------- /volumes/sharkey/docker.env: -------------------------------------------------------------------------------- 1 | # db settings 2 | POSTGRES_PASSWORD=example-misskey-pass 3 | POSTGRES_USER=example-misskey-user 4 | POSTGRES_DB=misskey 5 | DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@sharkey_db:5432/${POSTGRES_DB}" 6 | --------------------------------------------------------------------------------