├── .editorconfig ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── ansible.cfg ├── files ├── coredns │ ├── Corefile.j2 │ └── zones │ │ └── db.ulmlernt.org ├── grafana │ └── dashboards │ │ ├── bigbluebutton-all-servers.json │ │ ├── bigbluebutton-server-instance.json │ │ ├── cockpit.json │ │ └── node-exporter-full.json └── logo │ ├── ulm_lernt_fahne_transparent.png │ ├── ulm_lernt_fahne_transparent.svg │ ├── ulm_lernt_fahne_weiss.png │ ├── ulm_lernt_fahne_weiss.svg │ ├── ulm_lernt_logo_transparent.png │ ├── ulm_lernt_logo_transparent.svg │ ├── ulm_lernt_logo_weiss.png │ └── ulm_lernt_logo_weiss.svg ├── helper ├── disable_bbb_hosts.yml ├── enable_bbb_hosts.yml ├── fix_ufw.yml ├── reboot.yml ├── reset_apt_package_lists.yml ├── store_known_hosts.yml └── upgradereboot.yml ├── inventory ├── main.yml ├── requirements.yml ├── roles ├── base-user │ └── tasks │ │ └── main.yml ├── base │ ├── handlers │ │ └── main.yml │ └── tasks │ │ ├── firewall.yml │ │ └── main.yml ├── bbb-collect │ └── tasks │ │ └── main.yml ├── bbb-easy-join │ ├── tasks │ │ └── main.yml │ └── templates │ │ ├── bbb-easy-join.service.j2 │ │ ├── env.j2 │ │ └── nginx-bbb-easy-join.conf ├── bbb-exporter │ ├── tasks │ │ └── main.yml │ └── templates │ │ ├── bbb-exporter.service │ │ └── env.j2 ├── bbb-ulmlernt │ ├── files │ │ ├── conf-muted-ulmlernt.wav │ │ ├── conf-unmuted-ulmlernt.wav │ │ ├── default.odp │ │ └── default.pdf │ ├── handlers │ │ └── main.yml │ └── tasks │ │ └── main.yml ├── coredns-ufw │ └── tasks │ │ └── main.yml ├── dirty-docker │ └── tasks │ │ └── main.yml ├── docker │ └── tasks │ │ └── main.yml ├── dokuwiki │ ├── tasks │ │ └── main.yml │ └── templates │ │ ├── dokuwiki-backup.sh │ │ └── dokuwiki.nginx ├── greenlight │ ├── handlers │ │ └── main.yml │ ├── tasks │ │ └── main.yml │ └── templates │ │ ├── env.j2 │ │ ├── greenlight.service │ │ └── nginx-greenlight.conf.j2 ├── nginx-tls-add │ ├── handlers │ │ └── main.yml │ ├── tasks │ │ ├── config.yml │ │ ├── http.yml │ │ ├── letsencrypt.yml │ │ └── main.yml │ └── templates │ │ ├── a13.conf.j2 │ │ └── http.conf.j2 ├── nginx-tls-monitoring │ ├── handlers │ │ └── main.yml │ ├── tasks │ │ ├── config.yml │ │ ├── http.yml │ │ ├── letsencrypt.yml │ │ └── main.yml │ └── templates │ │ ├── http-mon.conf.j2 │ │ └── mon.conf.j2 ├── nginx-tls-redirect │ ├── handlers │ │ └── main.yml │ ├── tasks │ │ ├── config.yml │ │ ├── http.yml │ │ ├── letsencrypt.yml │ │ └── main.yml │ └── templates │ │ ├── a13.conf.j2 │ │ └── http.conf.j2 ├── nginx-tls │ ├── files │ │ ├── hackhack.gif │ │ └── index.html │ ├── handlers │ │ └── main.yml │ ├── tasks │ │ ├── config.yml │ │ ├── http.yml │ │ ├── letsencrypt.yml │ │ └── main.yml │ └── templates │ │ ├── a13.conf.j2 │ │ └── http.conf.j2 ├── nginx │ ├── handlers │ │ └── main.yml │ └── tasks │ │ └── main.yml ├── no-docker │ └── tasks │ │ └── main.yml ├── no-rocketchat │ ├── handlers │ │ └── main.yml │ ├── tasks │ │ └── main.yml │ └── templates │ │ └── no-rocketchat.nginx ├── no-turn │ └── tasks │ │ └── main.yml ├── node-exporter-ufw │ └── tasks │ │ └── main.yml ├── prometheus-ufw │ └── tasks │ │ └── main.yml ├── restic-client │ ├── tasks │ │ └── main.yml │ └── templates │ │ ├── backup_directory.sh │ │ ├── backup_postgres.sh │ │ └── init_repo.sh ├── restic-server │ ├── files │ │ └── rest-server-0.9.7-linux-amd64 │ ├── handlers │ │ └── main.yml │ ├── tasks │ │ ├── letsencrypt.yml │ │ └── main.yml │ └── templates │ │ └── rest-server.service ├── scalelite-config │ └── tasks │ │ └── main.yml ├── scalelite │ ├── handlers │ │ └── main.yml │ ├── tasks │ │ └── main.yml │ └── templates │ │ ├── env.j2 │ │ ├── nginx-scalelite.conf │ │ ├── scalelite-api.service │ │ ├── scalelite-poller.service │ │ └── scalelite.target └── turn-standalone │ ├── handlers │ └── main.yml │ ├── tasks │ ├── certbot.yml │ ├── coturn.yml │ ├── firewall.yml │ └── main.yml │ └── templates │ ├── certbot-zerossl.sh │ ├── coturn.logrotate │ ├── coturn.service │ └── turnserver.conf.j2 ├── tmp └── .gitkeep └── vars.yml /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.or 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.{py,rst,ini}] 11 | indent_style = space 12 | indent_size = 4 13 | 14 | [*.yml] 15 | indent_style = space 16 | indent_size = 2 17 | 18 | [*.md] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.retry 2 | tmp/ 3 | !tmp/.gitkeep 4 | vault_password 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stadtulm/a13-ansible/3b0c44d9712af5c5d3bd745c0a987cc9acf92102/.gitmodules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Stadt Ulm 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # a13-ansible 2 | 3 | This repository is no longer maintained. 4 | Currently (12/2021) maintained ansible roles for BBB can be found here: 5 | - https://gitlab.com/infra.run 6 | - https://github.com/ebbba-org/ansible-role-bigbluebutton 7 | - https://codeberg.org/DigitalSouveraeneSchule/bbb.git 8 | 9 | ### Prepare 10 | 11 | #### Install modules 12 | ``` 13 | ansible-galaxy install -r requirements.yml 14 | ``` 15 | 16 | Note: `cloudalchemy.node-exporter` requires the gnu variant of `tar` on macOS. (`brew install gnu-tar`) 17 | Note: `cloudalchemy.prometheus` requires the `jmespath` python module on your (deployer) machine 18 | 19 | #### Passwords 20 | 21 | You need to create the file `vault_password` and put the ansible vault password in there. 22 | 23 | #### SSH Host Keys 24 | 25 | Get all SSH host keys and store in local .ssh/known\_hosts file by executing following skript 26 | 27 | ``` 28 | ansible-playbook helper/store_known_hosts.yml 29 | ``` 30 | 31 | ### Run 32 | ``` 33 | ansible-playbook main.yml 34 | ``` 35 | 36 | ## HowTo 37 | ### Add new machine 38 | * Update DNS zonefile in `files/coredns/zones/` 39 | * add A and AAAA records 40 | * update serial (`yyyymmddvv` with `vv` being the version increment. E.g., 2020040101) 41 | * Update DNS by `ansible-playbook main.yml --tags dns` 42 | * Enter Hostname twice in `inventory`, below `[all]` and below the other role the machine should have, eg. `[bbb]` 43 | * Confirm that you can ssh into the machine by its newly aquired dns name (this also adds the host key to your `~/.ssh/known_hosts`) 44 | * run `ansible-playbook main.yml -l your.fresh.hostname.example` (you may need `--user root` if you don't have an user yet, the base role creates one for you) 45 | * ...? 46 | * register your new bbb instance: 47 | * at the monitoring by running `ansible-playbook main.yml --tags monitoring` 48 | * at the loadbalancer by running `ansible-playbook main.yml --tags config` 49 | * enable it manually in the loadbalancer 50 | 51 | ## Things to tell your Network Admin 52 | * Proxy exeptions for IPv4 adress of turnserver 53 | 54 | -------------------------------------------------------------------------------- /ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | inventory = ./inventory 3 | interpreter_python = /usr/bin/python3 4 | fact_caching = yaml 5 | fact_caching_connection = tmp 6 | vault_password_file = ./vault_password 7 | 8 | [ssh_connection] 9 | pipelining = True 10 | -------------------------------------------------------------------------------- /files/coredns/Corefile.j2: -------------------------------------------------------------------------------- 1 | ulmlernt.org { 2 | bind {{ ansible_default_ipv4.address }} 3 | bind {{ ansible_default_ipv6.address }} 4 | prometheus {{ ansible_default_ipv4.address }}:9153 5 | 6 | root /etc/coredns/zones 7 | file db.ulmlernt.org { 8 | # hetzner secondary ns 9 | transfer to 78.46.255.56 10 | transfer to 213.239.242.238 11 | transfer to 213.133.105.6 12 | transfer to 193.47.99.3 13 | } 14 | } -------------------------------------------------------------------------------- /files/coredns/zones/db.ulmlernt.org: -------------------------------------------------------------------------------- 1 | $ORIGIN ulmlernt.org. 2 | @ 3600 IN SOA ns.ulmlernt.org. noc.ulmlernt.de. ( 3 | 2021070801 ; serial ;;; PLEASE UPDATE THIS AFTER EACH EDIT! 4 | 1800 ; refresh (30 min) 5 | 1800 ; retry (30 min) 6 | 86400 ; expire (1 day) 7 | 3600 ; minimum (1 hour) 8 | ) 9 | 10 | @ 3600 IN NS hydrogen.ns.hetzner.com. 11 | @ 3600 IN NS oxygen.ns.hetzner.com. 12 | @ 3600 IN NS helium.ns.hetzner.de. 13 | @ 3600 IN NS ns.ulmlernt.org. 14 | 15 | ns IN A 195.201.237.77 16 | ns IN AAAA 2a01:4f8:c17:af72::1 17 | 18 | fsn01 3600 IN A 195.201.237.77 19 | fsn01 3600 IN AAAA 2a01:4f8:c17:af72::1 20 | 21 | fsn02 3600 IN A 144.76.96.80 22 | fsn02 3600 IN AAAA 2a01:4f8:192:314f::2 23 | 24 | fsn03 3600 IN A 78.47.162.149 25 | fsn03 3600 IN AAAA 2a01:4f8:c17:9865::1 26 | 27 | fsn04 3600 IN A 136.243.8.92 28 | fsn04 3600 IN AAAA 2a01:4f8:211:21db::2 29 | 30 | fsn05 3600 IN A 136.243.18.16 31 | fsn05 3600 IN AAAA 2a01:4f8:211:2b8f::2 32 | 33 | fsn06 3600 IN A 148.251.6.47 34 | fsn06 3600 IN AAAA 2a01:4f8:201:602e::2 35 | 36 | fsn07 3600 IN A 148.251.47.101 37 | fsn07 3600 IN AAAA 2a01:4f8:202:264::2 38 | 39 | fsn29 3600 IN A 188.34.190.64 40 | 41 | fsn34 3600 IN A 138.201.118.3 42 | fsn34 3600 IN AAAA 2a01:4f8:c17:f692::2 43 | 44 | bck 3600 IN A 49.12.102.120 45 | bck 3600 IN AAAA 2a01:4f8:c17:badd::1 46 | 47 | 48 | lb 3600 IN CNAME fsn01.ulmlernt.org. 49 | mon 3600 IN CNAME fsn01.ulmlernt.org. 50 | gl 3600 IN CNAME fsn03.ulmlernt.org. 51 | chat 3600 IN CNAME fsn03.ulmlernt.org. 52 | doku 3600 IN CNAME fsn03.ulmlernt.org. 53 | turn01 3600 IN CNAME fsn34.ulmlernt.org. 54 | turnipv4only01 3600 IN CNAME fsn29.ulmlernt.org. -------------------------------------------------------------------------------- /files/grafana/dashboards/bigbluebutton-all-servers.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [ 3 | { 4 | "name": "DS_PROMETHEUS", 5 | "label": "prometheus", 6 | "description": "", 7 | "type": "datasource", 8 | "pluginId": "prometheus", 9 | "pluginName": "Prometheus" 10 | } 11 | ], 12 | "__requires": [ 13 | { 14 | "type": "grafana", 15 | "id": "grafana", 16 | "name": "Grafana", 17 | "version": "6.7.3" 18 | }, 19 | { 20 | "type": "panel", 21 | "id": "graph", 22 | "name": "Graph", 23 | "version": "" 24 | }, 25 | { 26 | "type": "datasource", 27 | "id": "prometheus", 28 | "name": "Prometheus", 29 | "version": "1.0.0" 30 | }, 31 | { 32 | "type": "panel", 33 | "id": "stat", 34 | "name": "Stat", 35 | "version": "" 36 | } 37 | ], 38 | "annotations": { 39 | "list": [ 40 | { 41 | "builtIn": 1, 42 | "datasource": "-- Grafana --", 43 | "enable": true, 44 | "hide": true, 45 | "iconColor": "rgba(0, 211, 255, 1)", 46 | "name": "Annotations & Alerts", 47 | "type": "dashboard" 48 | } 49 | ] 50 | }, 51 | "editable": true, 52 | "gnetId": null, 53 | "graphTooltip": 0, 54 | "id": null, 55 | "iteration": 1587902354603, 56 | "links": [], 57 | "panels": [ 58 | { 59 | "collapsed": false, 60 | "datasource": "${DS_PROMETHEUS}", 61 | "gridPos": { 62 | "h": 1, 63 | "w": 24, 64 | "x": 0, 65 | "y": 0 66 | }, 67 | "id": 28, 68 | "panels": [], 69 | "title": "Main", 70 | "type": "row" 71 | }, 72 | { 73 | "cacheTimeout": null, 74 | "datasource": "${DS_PROMETHEUS}", 75 | "gridPos": { 76 | "h": 26, 77 | "w": 4, 78 | "x": 0, 79 | "y": 1 80 | }, 81 | "id": 12, 82 | "links": [], 83 | "maxPerRow": 2, 84 | "options": { 85 | "colorMode": "background", 86 | "fieldOptions": { 87 | "calcs": [ 88 | "last" 89 | ], 90 | "defaults": { 91 | "mappings": [ 92 | { 93 | "from": "", 94 | "id": 1, 95 | "operator": "", 96 | "text": "Online", 97 | "to": "", 98 | "type": 1, 99 | "value": "1" 100 | }, 101 | { 102 | "from": "", 103 | "id": 2, 104 | "operator": "", 105 | "text": "Offline", 106 | "to": "", 107 | "type": 1, 108 | "value": "0" 109 | } 110 | ], 111 | "thresholds": { 112 | "mode": "absolute", 113 | "steps": [ 114 | { 115 | "color": "red", 116 | "value": null 117 | }, 118 | { 119 | "color": "green", 120 | "value": 1 121 | } 122 | ] 123 | }, 124 | "title": "" 125 | }, 126 | "overrides": [], 127 | "values": false 128 | }, 129 | "graphMode": "none", 130 | "justifyMode": "auto", 131 | "orientation": "horizontal" 132 | }, 133 | "pluginVersion": "6.7.3", 134 | "repeat": null, 135 | "repeatDirection": "h", 136 | "targets": [ 137 | { 138 | "aggregation": "Last", 139 | "alias": "A", 140 | "decimals": 2, 141 | "displayAliasType": "Always", 142 | "displayType": "Regular", 143 | "displayValueWithAlias": "Never", 144 | "expr": "bbb_api_up", 145 | "instant": false, 146 | "interval": "", 147 | "legendFormat": "{{instance}}", 148 | "refId": "A", 149 | "units": "none", 150 | "valueHandler": "Number Threshold" 151 | } 152 | ], 153 | "timeFrom": null, 154 | "timeShift": null, 155 | "title": "BBB API", 156 | "type": "stat" 157 | }, 158 | { 159 | "aliasColors": {}, 160 | "bars": false, 161 | "dashLength": 10, 162 | "dashes": false, 163 | "datasource": "${DS_PROMETHEUS}", 164 | "decimals": 0, 165 | "fill": 1, 166 | "fillGradient": 0, 167 | "gridPos": { 168 | "h": 9, 169 | "w": 20, 170 | "x": 4, 171 | "y": 1 172 | }, 173 | "hiddenSeries": false, 174 | "id": 2, 175 | "legend": { 176 | "alignAsTable": true, 177 | "avg": true, 178 | "current": true, 179 | "max": true, 180 | "min": true, 181 | "rightSide": true, 182 | "show": true, 183 | "total": false, 184 | "values": true 185 | }, 186 | "lines": true, 187 | "linewidth": 1, 188 | "nullPointMode": "null", 189 | "options": { 190 | "dataLinks": [] 191 | }, 192 | "percentage": false, 193 | "pointradius": 2, 194 | "points": false, 195 | "renderer": "flot", 196 | "seriesOverrides": [], 197 | "spaceLength": 10, 198 | "stack": false, 199 | "steppedLine": false, 200 | "targets": [ 201 | { 202 | "expr": "bbb_meetings_participants", 203 | "interval": "", 204 | "legendFormat": "{{instance}}", 205 | "refId": "A" 206 | } 207 | ], 208 | "thresholds": [], 209 | "timeFrom": null, 210 | "timeRegions": [], 211 | "timeShift": null, 212 | "title": "Participants", 213 | "tooltip": { 214 | "shared": true, 215 | "sort": 0, 216 | "value_type": "individual" 217 | }, 218 | "type": "graph", 219 | "xaxis": { 220 | "buckets": null, 221 | "mode": "time", 222 | "name": null, 223 | "show": true, 224 | "values": [] 225 | }, 226 | "yaxes": [ 227 | { 228 | "decimals": 0, 229 | "format": "short", 230 | "label": "Participants", 231 | "logBase": 1, 232 | "max": null, 233 | "min": "0", 234 | "show": true 235 | }, 236 | { 237 | "format": "short", 238 | "label": null, 239 | "logBase": 1, 240 | "max": null, 241 | "min": null, 242 | "show": false 243 | } 244 | ], 245 | "yaxis": { 246 | "align": false, 247 | "alignLevel": null 248 | } 249 | }, 250 | { 251 | "aliasColors": {}, 252 | "bars": false, 253 | "dashLength": 10, 254 | "dashes": false, 255 | "datasource": "${DS_PROMETHEUS}", 256 | "decimals": 0, 257 | "fill": 1, 258 | "fillGradient": 0, 259 | "gridPos": { 260 | "h": 8, 261 | "w": 20, 262 | "x": 4, 263 | "y": 10 264 | }, 265 | "hiddenSeries": false, 266 | "id": 6, 267 | "legend": { 268 | "alignAsTable": true, 269 | "avg": true, 270 | "current": true, 271 | "max": true, 272 | "min": true, 273 | "rightSide": true, 274 | "show": true, 275 | "total": false, 276 | "values": true 277 | }, 278 | "lines": true, 279 | "linewidth": 1, 280 | "nullPointMode": "null", 281 | "options": { 282 | "dataLinks": [] 283 | }, 284 | "percentage": false, 285 | "pointradius": 2, 286 | "points": false, 287 | "renderer": "flot", 288 | "seriesOverrides": [], 289 | "spaceLength": 10, 290 | "stack": false, 291 | "steppedLine": false, 292 | "targets": [ 293 | { 294 | "expr": "bbb_meetings_voice_participants", 295 | "interval": "", 296 | "legendFormat": "{{instance}}", 297 | "refId": "A" 298 | } 299 | ], 300 | "thresholds": [], 301 | "timeFrom": null, 302 | "timeRegions": [], 303 | "timeShift": null, 304 | "title": "Voice Participants", 305 | "tooltip": { 306 | "shared": true, 307 | "sort": 0, 308 | "value_type": "individual" 309 | }, 310 | "type": "graph", 311 | "xaxis": { 312 | "buckets": null, 313 | "mode": "time", 314 | "name": null, 315 | "show": true, 316 | "values": [] 317 | }, 318 | "yaxes": [ 319 | { 320 | "decimals": 0, 321 | "format": "short", 322 | "label": "Participants", 323 | "logBase": 1, 324 | "max": null, 325 | "min": "0", 326 | "show": true 327 | }, 328 | { 329 | "format": "short", 330 | "label": null, 331 | "logBase": 1, 332 | "max": null, 333 | "min": null, 334 | "show": false 335 | } 336 | ], 337 | "yaxis": { 338 | "align": false, 339 | "alignLevel": null 340 | } 341 | }, 342 | { 343 | "aliasColors": {}, 344 | "bars": false, 345 | "dashLength": 10, 346 | "dashes": false, 347 | "datasource": "${DS_PROMETHEUS}", 348 | "decimals": 0, 349 | "fill": 1, 350 | "fillGradient": 0, 351 | "gridPos": { 352 | "h": 9, 353 | "w": 20, 354 | "x": 4, 355 | "y": 18 356 | }, 357 | "hiddenSeries": false, 358 | "id": 8, 359 | "legend": { 360 | "alignAsTable": true, 361 | "avg": true, 362 | "current": true, 363 | "max": true, 364 | "min": true, 365 | "rightSide": true, 366 | "show": true, 367 | "total": false, 368 | "values": true 369 | }, 370 | "lines": true, 371 | "linewidth": 1, 372 | "nullPointMode": "null", 373 | "options": { 374 | "dataLinks": [] 375 | }, 376 | "percentage": false, 377 | "pointradius": 2, 378 | "points": false, 379 | "renderer": "flot", 380 | "seriesOverrides": [], 381 | "spaceLength": 10, 382 | "stack": false, 383 | "steppedLine": false, 384 | "targets": [ 385 | { 386 | "expr": "bbb_meetings_video_participants", 387 | "interval": "", 388 | "legendFormat": "{{instance}}", 389 | "refId": "A" 390 | } 391 | ], 392 | "thresholds": [], 393 | "timeFrom": null, 394 | "timeRegions": [], 395 | "timeShift": null, 396 | "title": "Video Participants", 397 | "tooltip": { 398 | "shared": true, 399 | "sort": 0, 400 | "value_type": "individual" 401 | }, 402 | "type": "graph", 403 | "xaxis": { 404 | "buckets": null, 405 | "mode": "time", 406 | "name": null, 407 | "show": true, 408 | "values": [] 409 | }, 410 | "yaxes": [ 411 | { 412 | "decimals": 0, 413 | "format": "short", 414 | "label": "Participants", 415 | "logBase": 1, 416 | "max": null, 417 | "min": "0", 418 | "show": true 419 | }, 420 | { 421 | "decimals": 0, 422 | "format": "short", 423 | "label": null, 424 | "logBase": 1, 425 | "max": null, 426 | "min": null, 427 | "show": false 428 | } 429 | ], 430 | "yaxis": { 431 | "align": false, 432 | "alignLevel": null 433 | } 434 | }, 435 | { 436 | "collapsed": false, 437 | "datasource": "${DS_PROMETHEUS}", 438 | "gridPos": { 439 | "h": 1, 440 | "w": 24, 441 | "x": 0, 442 | "y": 27 443 | }, 444 | "id": 34, 445 | "panels": [], 446 | "title": "Max Participants", 447 | "type": "row" 448 | }, 449 | { 450 | "datasource": "${DS_PROMETHEUS}", 451 | "gridPos": { 452 | "h": 2, 453 | "w": 24, 454 | "x": 0, 455 | "y": 28 456 | }, 457 | "id": 24, 458 | "maxPerRow": 24, 459 | "options": { 460 | "colorMode": "value", 461 | "fieldOptions": { 462 | "calcs": [ 463 | "max" 464 | ], 465 | "defaults": { 466 | "mappings": [], 467 | "thresholds": { 468 | "mode": "absolute", 469 | "steps": [ 470 | { 471 | "color": "green", 472 | "value": null 473 | } 474 | ] 475 | }, 476 | "title": "" 477 | }, 478 | "overrides": [], 479 | "values": false 480 | }, 481 | "graphMode": "none", 482 | "justifyMode": "auto", 483 | "orientation": "auto" 484 | }, 485 | "pluginVersion": "6.7.3", 486 | "repeat": null, 487 | "repeatDirection": "h", 488 | "targets": [ 489 | { 490 | "expr": "bbb_meetings_participants", 491 | "format": "time_series", 492 | "hide": false, 493 | "interval": "", 494 | "legendFormat": "{{instance}}", 495 | "refId": "A" 496 | } 497 | ], 498 | "timeFrom": null, 499 | "timeShift": null, 500 | "title": "", 501 | "transparent": true, 502 | "type": "stat" 503 | }, 504 | { 505 | "collapsed": false, 506 | "datasource": "${DS_PROMETHEUS}", 507 | "gridPos": { 508 | "h": 1, 509 | "w": 24, 510 | "x": 0, 511 | "y": 30 512 | }, 513 | "id": 75, 514 | "panels": [], 515 | "title": "Max Video Participants", 516 | "type": "row" 517 | }, 518 | { 519 | "datasource": "${DS_PROMETHEUS}", 520 | "gridPos": { 521 | "h": 2, 522 | "w": 24, 523 | "x": 0, 524 | "y": 31 525 | }, 526 | "id": 4, 527 | "maxPerRow": 24, 528 | "options": { 529 | "colorMode": "value", 530 | "fieldOptions": { 531 | "calcs": [ 532 | "max" 533 | ], 534 | "defaults": { 535 | "mappings": [], 536 | "thresholds": { 537 | "mode": "absolute", 538 | "steps": [ 539 | { 540 | "color": "green", 541 | "value": null 542 | } 543 | ] 544 | }, 545 | "title": "" 546 | }, 547 | "overrides": [], 548 | "values": false 549 | }, 550 | "graphMode": "none", 551 | "justifyMode": "auto", 552 | "orientation": "vertical" 553 | }, 554 | "pluginVersion": "6.7.3", 555 | "repeat": null, 556 | "repeatDirection": "h", 557 | "targets": [ 558 | { 559 | "expr": "bbb_meetings_video_participants", 560 | "interval": "", 561 | "legendFormat": "{{instance}}", 562 | "refId": "A" 563 | } 564 | ], 565 | "timeFrom": null, 566 | "timeShift": null, 567 | "title": "", 568 | "transparent": true, 569 | "type": "stat" 570 | }, 571 | { 572 | "collapsed": false, 573 | "datasource": "${DS_PROMETHEUS}", 574 | "gridPos": { 575 | "h": 1, 576 | "w": 24, 577 | "x": 0, 578 | "y": 33 579 | }, 580 | "id": 115, 581 | "panels": [], 582 | "title": "Current Meetings", 583 | "type": "row" 584 | }, 585 | { 586 | "datasource": "${DS_PROMETHEUS}", 587 | "gridPos": { 588 | "h": 2, 589 | "w": 24, 590 | "x": 0, 591 | "y": 34 592 | }, 593 | "id": 135, 594 | "maxPerRow": 24, 595 | "options": { 596 | "colorMode": "value", 597 | "fieldOptions": { 598 | "calcs": [ 599 | "last" 600 | ], 601 | "defaults": { 602 | "mappings": [], 603 | "thresholds": { 604 | "mode": "absolute", 605 | "steps": [ 606 | { 607 | "color": "green", 608 | "value": null 609 | } 610 | ] 611 | }, 612 | "title": "" 613 | }, 614 | "overrides": [], 615 | "values": false 616 | }, 617 | "graphMode": "none", 618 | "justifyMode": "auto", 619 | "orientation": "vertical" 620 | }, 621 | "pluginVersion": "6.7.3", 622 | "repeat": null, 623 | "repeatDirection": "h", 624 | "targets": [ 625 | { 626 | "expr": "bbb_meetings", 627 | "instant": true, 628 | "interval": "", 629 | "legendFormat": "{{instance}}", 630 | "refId": "A" 631 | } 632 | ], 633 | "timeFrom": null, 634 | "timeShift": null, 635 | "title": "", 636 | "transparent": true, 637 | "type": "stat" 638 | }, 639 | { 640 | "collapsed": true, 641 | "datasource": "${DS_PROMETHEUS}", 642 | "gridPos": { 643 | "h": 1, 644 | "w": 24, 645 | "x": 0, 646 | "y": 36 647 | }, 648 | "id": 32, 649 | "panels": [ 650 | { 651 | "aliasColors": {}, 652 | "bars": false, 653 | "cacheTimeout": null, 654 | "dashLength": 10, 655 | "dashes": false, 656 | "datasource": "${DS_PROMETHEUS}", 657 | "fill": 1, 658 | "fillGradient": 0, 659 | "gridPos": { 660 | "h": 11, 661 | "w": 24, 662 | "x": 0, 663 | "y": 37 664 | }, 665 | "hiddenSeries": false, 666 | "id": 22, 667 | "legend": { 668 | "alignAsTable": true, 669 | "avg": true, 670 | "current": true, 671 | "max": true, 672 | "min": true, 673 | "rightSide": true, 674 | "show": true, 675 | "total": false, 676 | "values": true 677 | }, 678 | "lines": true, 679 | "linewidth": 1, 680 | "links": [], 681 | "nullPointMode": "null", 682 | "options": { 683 | "dataLinks": [] 684 | }, 685 | "percentage": false, 686 | "pluginVersion": "6.7.0", 687 | "pointradius": 2, 688 | "points": false, 689 | "renderer": "flot", 690 | "seriesOverrides": [], 691 | "spaceLength": 10, 692 | "stack": false, 693 | "steppedLine": false, 694 | "targets": [ 695 | { 696 | "expr": "histogram_quantile(0.95, sum(rate(bbb_api_latency_bucket{endpoint=\"getMeetings\"}[5m])) by (le, instance))", 697 | "instant": false, 698 | "interval": "", 699 | "intervalFactor": 1, 700 | "legendFormat": "{{instance}} - getMeetings", 701 | "refId": "A" 702 | } 703 | ], 704 | "thresholds": [], 705 | "timeFrom": null, 706 | "timeRegions": [], 707 | "timeShift": null, 708 | "title": "API 95-th Percentile Latency", 709 | "tooltip": { 710 | "shared": true, 711 | "sort": 0, 712 | "value_type": "individual" 713 | }, 714 | "type": "graph", 715 | "xaxis": { 716 | "buckets": null, 717 | "mode": "time", 718 | "name": null, 719 | "show": true, 720 | "values": [] 721 | }, 722 | "yaxes": [ 723 | { 724 | "format": "short", 725 | "label": "Latency [s]", 726 | "logBase": 1, 727 | "max": null, 728 | "min": null, 729 | "show": true 730 | }, 731 | { 732 | "format": "short", 733 | "label": null, 734 | "logBase": 1, 735 | "max": null, 736 | "min": null, 737 | "show": false 738 | } 739 | ], 740 | "yaxis": { 741 | "align": false, 742 | "alignLevel": null 743 | } 744 | } 745 | ], 746 | "title": "API", 747 | "type": "row" 748 | } 749 | ], 750 | "refresh": "1m", 751 | "schemaVersion": 22, 752 | "style": "dark", 753 | "tags": [], 754 | "templating": { 755 | "list": [ 756 | { 757 | "current": { 758 | "selected": false, 759 | "text": "prometheus", 760 | "value": "prometheus" 761 | }, 762 | "hide": 2, 763 | "label": "datasource", 764 | "name": "DS_PROMETHEUS", 765 | "options": [], 766 | "query": "prometheus", 767 | "refresh": 1, 768 | "regex": "", 769 | "type": "datasource" 770 | }, 771 | { 772 | "allValue": null, 773 | "current": {}, 774 | "datasource": "${DS_PROMETHEUS}", 775 | "definition": "label_values(bbb_api_up, instance)", 776 | "hide": 0, 777 | "includeAll": true, 778 | "index": -1, 779 | "label": "Instance", 780 | "multi": true, 781 | "name": "instance", 782 | "options": [], 783 | "query": "label_values(bbb_api_up, instance)", 784 | "refresh": 1, 785 | "regex": "", 786 | "skipUrlSync": false, 787 | "sort": 3, 788 | "tagValuesQuery": "", 789 | "tags": [], 790 | "tagsQuery": "", 791 | "type": "query", 792 | "useTags": false 793 | } 794 | ] 795 | }, 796 | "time": { 797 | "from": "now-12h", 798 | "to": "now" 799 | }, 800 | "timepicker": { 801 | "refresh_intervals": [ 802 | "15s", 803 | "30s", 804 | "1m", 805 | "5m", 806 | "15m", 807 | "30m", 808 | "1h", 809 | "2h", 810 | "1d" 811 | ] 812 | }, 813 | "timezone": "", 814 | "title": "BigBlueButton All Servers", 815 | "uid": "HIbd_CXZz", 816 | "variables": { 817 | "list": [] 818 | }, 819 | "version": 9 820 | } 821 | -------------------------------------------------------------------------------- /files/grafana/dashboards/bigbluebutton-server-instance.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [ 3 | { 4 | "name": "DS_PROMETHEUS", 5 | "label": "Prometheus", 6 | "description": "", 7 | "type": "datasource", 8 | "pluginId": "prometheus", 9 | "pluginName": "Prometheus" 10 | } 11 | ], 12 | "__requires": [ 13 | { 14 | "type": "grafana", 15 | "id": "grafana", 16 | "name": "Grafana", 17 | "version": "6.7.1" 18 | }, 19 | { 20 | "type": "panel", 21 | "id": "graph", 22 | "name": "Graph", 23 | "version": "" 24 | }, 25 | { 26 | "type": "datasource", 27 | "id": "prometheus", 28 | "name": "Prometheus", 29 | "version": "1.0.0" 30 | }, 31 | { 32 | "type": "panel", 33 | "id": "stat", 34 | "name": "Stat", 35 | "version": "" 36 | } 37 | ], 38 | "annotations": { 39 | "list": [ 40 | { 41 | "builtIn": 1, 42 | "datasource": "-- Grafana --", 43 | "enable": true, 44 | "hide": true, 45 | "iconColor": "rgba(0, 211, 255, 1)", 46 | "name": "Annotations & Alerts", 47 | "type": "dashboard" 48 | } 49 | ] 50 | }, 51 | "editable": true, 52 | "gnetId": null, 53 | "graphTooltip": 0, 54 | "id": null, 55 | "iteration": 1586958609639, 56 | "links": [], 57 | "panels": [ 58 | { 59 | "collapsed": false, 60 | "datasource": "${DS_PROMETHEUS}", 61 | "gridPos": { 62 | "h": 1, 63 | "w": 24, 64 | "x": 0, 65 | "y": 0 66 | }, 67 | "id": 36, 68 | "panels": [], 69 | "title": "Main", 70 | "type": "row" 71 | }, 72 | { 73 | "cacheTimeout": null, 74 | "datasource": "${DS_PROMETHEUS}", 75 | "gridPos": { 76 | "h": 2, 77 | "w": 4, 78 | "x": 0, 79 | "y": 1 80 | }, 81 | "id": 12, 82 | "links": [], 83 | "options": { 84 | "colorMode": "value", 85 | "fieldOptions": { 86 | "calcs": [ 87 | "last" 88 | ], 89 | "defaults": { 90 | "mappings": [ 91 | { 92 | "from": "", 93 | "id": 1, 94 | "operator": "", 95 | "text": "Online", 96 | "to": "", 97 | "type": 1, 98 | "value": "1" 99 | }, 100 | { 101 | "from": "", 102 | "id": 2, 103 | "operator": "", 104 | "text": "Offline", 105 | "to": "", 106 | "type": 1, 107 | "value": "0" 108 | } 109 | ], 110 | "thresholds": { 111 | "mode": "absolute", 112 | "steps": [ 113 | { 114 | "color": "red", 115 | "value": null 116 | }, 117 | { 118 | "color": "green", 119 | "value": 1 120 | } 121 | ] 122 | } 123 | }, 124 | "overrides": [], 125 | "values": false 126 | }, 127 | "graphMode": "none", 128 | "justifyMode": "auto", 129 | "orientation": "horizontal" 130 | }, 131 | "pluginVersion": "6.7.1", 132 | "targets": [ 133 | { 134 | "expr": "bbb_api_up{instance=\"$instance\"}", 135 | "interval": "", 136 | "legendFormat": "", 137 | "refId": "A" 138 | } 139 | ], 140 | "timeFrom": null, 141 | "timeShift": null, 142 | "title": "BBB API", 143 | "type": "stat" 144 | }, 145 | { 146 | "aliasColors": { 147 | "Participants": "blue" 148 | }, 149 | "bars": false, 150 | "dashLength": 10, 151 | "dashes": false, 152 | "datasource": "${DS_PROMETHEUS}", 153 | "fill": 1, 154 | "fillGradient": 0, 155 | "gridPos": { 156 | "h": 8, 157 | "w": 20, 158 | "x": 4, 159 | "y": 1 160 | }, 161 | "hiddenSeries": false, 162 | "id": 2, 163 | "legend": { 164 | "alignAsTable": true, 165 | "avg": true, 166 | "current": true, 167 | "max": true, 168 | "min": true, 169 | "show": true, 170 | "total": false, 171 | "values": true 172 | }, 173 | "lines": true, 174 | "linewidth": 1, 175 | "nullPointMode": "null", 176 | "options": { 177 | "dataLinks": [] 178 | }, 179 | "percentage": false, 180 | "pointradius": 2, 181 | "points": false, 182 | "renderer": "flot", 183 | "seriesOverrides": [], 184 | "spaceLength": 10, 185 | "stack": false, 186 | "steppedLine": false, 187 | "targets": [ 188 | { 189 | "expr": "bbb_meetings_participants{instance=\"$instance\"}", 190 | "interval": "", 191 | "legendFormat": "Participants", 192 | "refId": "A" 193 | } 194 | ], 195 | "thresholds": [], 196 | "timeFrom": null, 197 | "timeRegions": [], 198 | "timeShift": null, 199 | "title": "Participants", 200 | "tooltip": { 201 | "shared": true, 202 | "sort": 0, 203 | "value_type": "individual" 204 | }, 205 | "type": "graph", 206 | "xaxis": { 207 | "buckets": null, 208 | "mode": "time", 209 | "name": null, 210 | "show": true, 211 | "values": [] 212 | }, 213 | "yaxes": [ 214 | { 215 | "decimals": 0, 216 | "format": "short", 217 | "label": "Participants", 218 | "logBase": 1, 219 | "max": null, 220 | "min": "0", 221 | "show": true 222 | }, 223 | { 224 | "format": "short", 225 | "label": null, 226 | "logBase": 1, 227 | "max": null, 228 | "min": null, 229 | "show": false 230 | } 231 | ], 232 | "yaxis": { 233 | "align": false, 234 | "alignLevel": null 235 | } 236 | }, 237 | { 238 | "datasource": "${DS_PROMETHEUS}", 239 | "gridPos": { 240 | "h": 4, 241 | "w": 4, 242 | "x": 0, 243 | "y": 3 244 | }, 245 | "id": 40, 246 | "options": { 247 | "colorMode": "value", 248 | "fieldOptions": { 249 | "calcs": [ 250 | "last" 251 | ], 252 | "defaults": { 253 | "mappings": [], 254 | "thresholds": { 255 | "mode": "absolute", 256 | "steps": [ 257 | { 258 | "color": "green", 259 | "value": null 260 | } 261 | ] 262 | } 263 | }, 264 | "overrides": [], 265 | "values": false 266 | }, 267 | "graphMode": "area", 268 | "justifyMode": "auto", 269 | "orientation": "horizontal" 270 | }, 271 | "pluginVersion": "6.7.1", 272 | "targets": [ 273 | { 274 | "expr": "bbb_meetings{instance=\"$instance\"}", 275 | "interval": "", 276 | "legendFormat": "", 277 | "refId": "A" 278 | } 279 | ], 280 | "timeFrom": null, 281 | "timeShift": null, 282 | "title": "Active Meetings", 283 | "type": "stat" 284 | }, 285 | { 286 | "datasource": "${DS_PROMETHEUS}", 287 | "gridPos": { 288 | "h": 4, 289 | "w": 4, 290 | "x": 0, 291 | "y": 7 292 | }, 293 | "id": 30, 294 | "options": { 295 | "colorMode": "value", 296 | "fieldOptions": { 297 | "calcs": [ 298 | "max" 299 | ], 300 | "defaults": { 301 | "mappings": [], 302 | "thresholds": { 303 | "mode": "absolute", 304 | "steps": [ 305 | { 306 | "color": "green", 307 | "value": null 308 | } 309 | ] 310 | } 311 | }, 312 | "overrides": [], 313 | "values": false 314 | }, 315 | "graphMode": "none", 316 | "justifyMode": "auto", 317 | "orientation": "horizontal" 318 | }, 319 | "pluginVersion": "6.7.1", 320 | "targets": [ 321 | { 322 | "expr": "bbb_meetings_participants{instance=\"$instance\"}", 323 | "interval": "", 324 | "legendFormat": "Selected timeframe", 325 | "refId": "A" 326 | }, 327 | { 328 | "expr": "max_over_time(bbb_meetings_participants{instance=\"$instance\"}[100y])", 329 | "interval": "", 330 | "legendFormat": "All time", 331 | "refId": "B" 332 | } 333 | ], 334 | "timeFrom": null, 335 | "timeShift": null, 336 | "title": "Max Participants", 337 | "type": "stat" 338 | }, 339 | { 340 | "aliasColors": { 341 | "Participants": "blue" 342 | }, 343 | "bars": false, 344 | "dashLength": 10, 345 | "dashes": false, 346 | "datasource": "${DS_PROMETHEUS}", 347 | "fill": 1, 348 | "fillGradient": 0, 349 | "gridPos": { 350 | "h": 8, 351 | "w": 14, 352 | "x": 4, 353 | "y": 9 354 | }, 355 | "hiddenSeries": false, 356 | "id": 34, 357 | "legend": { 358 | "alignAsTable": true, 359 | "avg": true, 360 | "current": true, 361 | "max": true, 362 | "min": true, 363 | "show": true, 364 | "total": false, 365 | "values": true 366 | }, 367 | "lines": true, 368 | "linewidth": 1, 369 | "nullPointMode": "null", 370 | "options": { 371 | "dataLinks": [] 372 | }, 373 | "percentage": false, 374 | "pointradius": 2, 375 | "points": false, 376 | "renderer": "flot", 377 | "seriesOverrides": [ 378 | { 379 | "alias": "Participants", 380 | "yaxis": 2 381 | } 382 | ], 383 | "spaceLength": 10, 384 | "stack": false, 385 | "steppedLine": false, 386 | "targets": [ 387 | { 388 | "expr": "100 - (avg by (instance) (irate(node_cpu_seconds_total{job=\"node\",mode=\"idle\",instance=\"$instance\"}[5m])) * 100)", 389 | "interval": "", 390 | "legendFormat": "CPU Utilization", 391 | "refId": "A" 392 | }, 393 | { 394 | "expr": "bbb_meetings_participants{instance=\"$instance\"}", 395 | "interval": "", 396 | "legendFormat": "Participants", 397 | "refId": "B" 398 | } 399 | ], 400 | "thresholds": [], 401 | "timeFrom": null, 402 | "timeRegions": [], 403 | "timeShift": null, 404 | "title": "CPU Utilization vs. Participants", 405 | "tooltip": { 406 | "shared": true, 407 | "sort": 0, 408 | "value_type": "individual" 409 | }, 410 | "type": "graph", 411 | "xaxis": { 412 | "buckets": null, 413 | "mode": "time", 414 | "name": null, 415 | "show": true, 416 | "values": [] 417 | }, 418 | "yaxes": [ 419 | { 420 | "format": "percent", 421 | "label": "CPU Utlization [%]", 422 | "logBase": 1, 423 | "max": "100", 424 | "min": null, 425 | "show": true 426 | }, 427 | { 428 | "decimals": 0, 429 | "format": "short", 430 | "label": "Participants", 431 | "logBase": 1, 432 | "max": null, 433 | "min": "0", 434 | "show": true 435 | } 436 | ], 437 | "yaxis": { 438 | "align": false, 439 | "alignLevel": null 440 | } 441 | }, 442 | { 443 | "aliasColors": { 444 | "Voice participants": "orange" 445 | }, 446 | "bars": false, 447 | "dashLength": 10, 448 | "dashes": false, 449 | "datasource": "${DS_PROMETHEUS}", 450 | "fill": 1, 451 | "fillGradient": 0, 452 | "gridPos": { 453 | "h": 8, 454 | "w": 6, 455 | "x": 18, 456 | "y": 9 457 | }, 458 | "hiddenSeries": false, 459 | "id": 6, 460 | "legend": { 461 | "alignAsTable": true, 462 | "avg": true, 463 | "current": true, 464 | "max": true, 465 | "min": true, 466 | "rightSide": false, 467 | "show": true, 468 | "total": false, 469 | "values": true 470 | }, 471 | "lines": true, 472 | "linewidth": 1, 473 | "nullPointMode": "null", 474 | "options": { 475 | "dataLinks": [] 476 | }, 477 | "percentage": false, 478 | "pointradius": 2, 479 | "points": false, 480 | "renderer": "flot", 481 | "seriesOverrides": [], 482 | "spaceLength": 10, 483 | "stack": false, 484 | "steppedLine": false, 485 | "targets": [ 486 | { 487 | "expr": "bbb_meetings_voice_participants{instance=\"$instance\"}", 488 | "interval": "", 489 | "legendFormat": "Voice participants", 490 | "refId": "A" 491 | } 492 | ], 493 | "thresholds": [], 494 | "timeFrom": null, 495 | "timeRegions": [], 496 | "timeShift": null, 497 | "title": "Voice Participants", 498 | "tooltip": { 499 | "shared": true, 500 | "sort": 0, 501 | "value_type": "individual" 502 | }, 503 | "type": "graph", 504 | "xaxis": { 505 | "buckets": null, 506 | "mode": "time", 507 | "name": null, 508 | "show": true, 509 | "values": [] 510 | }, 511 | "yaxes": [ 512 | { 513 | "decimals": 0, 514 | "format": "short", 515 | "label": "Participants", 516 | "logBase": 1, 517 | "max": null, 518 | "min": "0", 519 | "show": true 520 | }, 521 | { 522 | "format": "short", 523 | "label": null, 524 | "logBase": 1, 525 | "max": null, 526 | "min": null, 527 | "show": false 528 | } 529 | ], 530 | "yaxis": { 531 | "align": false, 532 | "alignLevel": null 533 | } 534 | }, 535 | { 536 | "datasource": "${DS_PROMETHEUS}", 537 | "gridPos": { 538 | "h": 7, 539 | "w": 4, 540 | "x": 0, 541 | "y": 11 542 | }, 543 | "id": 26, 544 | "options": { 545 | "colorMode": "value", 546 | "fieldOptions": { 547 | "calcs": [ 548 | "last" 549 | ], 550 | "defaults": { 551 | "mappings": [], 552 | "thresholds": { 553 | "mode": "absolute", 554 | "steps": [ 555 | { 556 | "color": "green", 557 | "value": null 558 | } 559 | ] 560 | } 561 | }, 562 | "overrides": [], 563 | "values": false 564 | }, 565 | "graphMode": "none", 566 | "justifyMode": "auto", 567 | "orientation": "horizontal" 568 | }, 569 | "pluginVersion": "6.7.1", 570 | "targets": [ 571 | { 572 | "expr": "bbb_meetings_participants{instance=\"$instance\"}", 573 | "interval": "", 574 | "legendFormat": "Participants", 575 | "refId": "A" 576 | }, 577 | { 578 | "expr": "bbb_meetings_listeners{instance=\"$instance\"}", 579 | "interval": "", 580 | "legendFormat": "Listeners", 581 | "refId": "B" 582 | }, 583 | { 584 | "expr": "bbb_meetings_voice_participants{instance=\"$instance\"}", 585 | "interval": "", 586 | "legendFormat": "Voice", 587 | "refId": "C" 588 | }, 589 | { 590 | "expr": "bbb_meetings_video_participants{instance=\"$instance\"}", 591 | "interval": "", 592 | "legendFormat": "Video", 593 | "refId": "D" 594 | } 595 | ], 596 | "timeFrom": null, 597 | "timeShift": null, 598 | "title": "Type of Users Count", 599 | "type": "stat" 600 | }, 601 | { 602 | "aliasColors": { 603 | "Participants": "blue" 604 | }, 605 | "bars": false, 606 | "dashLength": 10, 607 | "dashes": false, 608 | "datasource": "${DS_PROMETHEUS}", 609 | "fill": 1, 610 | "fillGradient": 0, 611 | "gridPos": { 612 | "h": 9, 613 | "w": 14, 614 | "x": 4, 615 | "y": 17 616 | }, 617 | "hiddenSeries": false, 618 | "id": 32, 619 | "legend": { 620 | "alignAsTable": true, 621 | "avg": true, 622 | "current": true, 623 | "max": true, 624 | "min": true, 625 | "show": true, 626 | "total": false, 627 | "values": true 628 | }, 629 | "lines": true, 630 | "linewidth": 1, 631 | "nullPointMode": "null", 632 | "options": { 633 | "dataLinks": [] 634 | }, 635 | "percentage": false, 636 | "pointradius": 2, 637 | "points": false, 638 | "renderer": "flot", 639 | "seriesOverrides": [ 640 | { 641 | "alias": "Participants", 642 | "yaxis": 2 643 | }, 644 | { 645 | "alias": "CPU total utilization", 646 | "yaxis": 2 647 | } 648 | ], 649 | "spaceLength": 10, 650 | "stack": false, 651 | "steppedLine": false, 652 | "targets": [ 653 | { 654 | "expr": "irate(node_network_transmit_bytes_total{instance=\"$instance\",device!~'tap.*|veth.*|br.*|docker.*|virbr*|lo*'}[5m])*8", 655 | "interval": "", 656 | "intervalFactor": 1, 657 | "legendFormat": "Bandwidth out", 658 | "refId": "A" 659 | }, 660 | { 661 | "expr": "irate(node_network_receive_bytes_total{instance=\"$instance\",device!~'tap.*|veth.*|br.*|docker.*|virbr*|lo*'}[5m])*8", 662 | "interval": "", 663 | "intervalFactor": 1, 664 | "legendFormat": "Bandwidth in", 665 | "refId": "C" 666 | }, 667 | { 668 | "expr": "bbb_meetings_participants{instance=\"$instance\"}", 669 | "format": "time_series", 670 | "instant": false, 671 | "interval": "", 672 | "intervalFactor": 1, 673 | "legendFormat": "Participants", 674 | "refId": "B" 675 | } 676 | ], 677 | "thresholds": [], 678 | "timeFrom": null, 679 | "timeRegions": [], 680 | "timeShift": null, 681 | "title": "Bandwidth vs. Participants", 682 | "tooltip": { 683 | "shared": true, 684 | "sort": 0, 685 | "value_type": "individual" 686 | }, 687 | "type": "graph", 688 | "xaxis": { 689 | "buckets": null, 690 | "mode": "time", 691 | "name": null, 692 | "show": true, 693 | "values": [] 694 | }, 695 | "yaxes": [ 696 | { 697 | "format": "bps", 698 | "label": "Bandwidth", 699 | "logBase": 1, 700 | "max": null, 701 | "min": "0", 702 | "show": true 703 | }, 704 | { 705 | "decimals": 0, 706 | "format": "short", 707 | "label": "Participants", 708 | "logBase": 1, 709 | "max": null, 710 | "min": "0", 711 | "show": true 712 | } 713 | ], 714 | "yaxis": { 715 | "align": false, 716 | "alignLevel": null 717 | } 718 | }, 719 | { 720 | "aliasColors": { 721 | "Voice participants": "orange" 722 | }, 723 | "bars": false, 724 | "dashLength": 10, 725 | "dashes": false, 726 | "datasource": "${DS_PROMETHEUS}", 727 | "fill": 1, 728 | "fillGradient": 0, 729 | "gridPos": { 730 | "h": 9, 731 | "w": 6, 732 | "x": 18, 733 | "y": 17 734 | }, 735 | "hiddenSeries": false, 736 | "id": 8, 737 | "legend": { 738 | "alignAsTable": true, 739 | "avg": true, 740 | "current": true, 741 | "max": true, 742 | "min": true, 743 | "show": true, 744 | "total": false, 745 | "values": true 746 | }, 747 | "lines": true, 748 | "linewidth": 1, 749 | "nullPointMode": "null", 750 | "options": { 751 | "dataLinks": [] 752 | }, 753 | "percentage": false, 754 | "pointradius": 2, 755 | "points": false, 756 | "renderer": "flot", 757 | "seriesOverrides": [], 758 | "spaceLength": 10, 759 | "stack": false, 760 | "steppedLine": false, 761 | "targets": [ 762 | { 763 | "expr": "bbb_meetings_video_participants{instance=\"$instance\"}", 764 | "interval": "", 765 | "legendFormat": "Voice participants", 766 | "refId": "A" 767 | } 768 | ], 769 | "thresholds": [], 770 | "timeFrom": null, 771 | "timeRegions": [], 772 | "timeShift": null, 773 | "title": "Video Participants", 774 | "tooltip": { 775 | "shared": true, 776 | "sort": 0, 777 | "value_type": "individual" 778 | }, 779 | "type": "graph", 780 | "xaxis": { 781 | "buckets": null, 782 | "mode": "time", 783 | "name": null, 784 | "show": true, 785 | "values": [] 786 | }, 787 | "yaxes": [ 788 | { 789 | "decimals": 0, 790 | "format": "short", 791 | "label": "Participants", 792 | "logBase": 1, 793 | "max": null, 794 | "min": "0", 795 | "show": true 796 | }, 797 | { 798 | "format": "short", 799 | "label": null, 800 | "logBase": 1, 801 | "max": null, 802 | "min": null, 803 | "show": false 804 | } 805 | ], 806 | "yaxis": { 807 | "align": false, 808 | "alignLevel": null 809 | } 810 | }, 811 | { 812 | "datasource": "${DS_PROMETHEUS}", 813 | "gridPos": { 814 | "h": 2, 815 | "w": 4, 816 | "x": 0, 817 | "y": 18 818 | }, 819 | "id": 24, 820 | "options": { 821 | "colorMode": "value", 822 | "fieldOptions": { 823 | "calcs": [ 824 | "last" 825 | ], 826 | "defaults": { 827 | "mappings": [], 828 | "thresholds": { 829 | "mode": "absolute", 830 | "steps": [ 831 | { 832 | "color": "green", 833 | "value": null 834 | } 835 | ] 836 | } 837 | }, 838 | "overrides": [], 839 | "values": false 840 | }, 841 | "graphMode": "area", 842 | "justifyMode": "auto", 843 | "orientation": "horizontal" 844 | }, 845 | "pluginVersion": "6.7.1", 846 | "targets": [ 847 | { 848 | "expr": "bbb_meetings_participants - bbb_meetings_listeners{instance=\"$instance\"}", 849 | "interval": "", 850 | "legendFormat": "{{instance}}", 851 | "refId": "A" 852 | } 853 | ], 854 | "timeFrom": null, 855 | "timeShift": null, 856 | "title": "(Participants - Listeners) Delta", 857 | "type": "stat" 858 | }, 859 | { 860 | "datasource": "${DS_PROMETHEUS}", 861 | "gridPos": { 862 | "h": 6, 863 | "w": 4, 864 | "x": 0, 865 | "y": 20 866 | }, 867 | "id": 16, 868 | "options": { 869 | "colorMode": "value", 870 | "fieldOptions": { 871 | "calcs": [ 872 | "last" 873 | ], 874 | "defaults": { 875 | "mappings": [], 876 | "thresholds": { 877 | "mode": "absolute", 878 | "steps": [ 879 | { 880 | "color": "green", 881 | "value": null 882 | } 883 | ] 884 | } 885 | }, 886 | "overrides": [], 887 | "values": false 888 | }, 889 | "graphMode": "none", 890 | "justifyMode": "auto", 891 | "orientation": "auto" 892 | }, 893 | "pluginVersion": "6.7.1", 894 | "targets": [ 895 | { 896 | "expr": "bbb_recordings_processing{instance=\"$instance\"}", 897 | "interval": "", 898 | "legendFormat": "Processing", 899 | "refId": "B" 900 | }, 901 | { 902 | "expr": "bbb_recordings_processed{instance=\"$instance\"}", 903 | "interval": "", 904 | "legendFormat": "Processed", 905 | "refId": "A" 906 | }, 907 | { 908 | "expr": "bbb_recordings_published{instance=\"$instance\"}", 909 | "interval": "", 910 | "legendFormat": "Published", 911 | "refId": "E" 912 | }, 913 | { 914 | "expr": "bbb_recordings_unpublished{instance=\"$instance\"}", 915 | "interval": "", 916 | "legendFormat": "Unpublished", 917 | "refId": "C" 918 | }, 919 | { 920 | "expr": "bbb_recordings_deleted{instance=\"$instance\"}", 921 | "interval": "", 922 | "legendFormat": "Deleted", 923 | "refId": "D" 924 | } 925 | ], 926 | "timeFrom": null, 927 | "timeShift": null, 928 | "title": "Recordings", 929 | "type": "stat" 930 | }, 931 | { 932 | "collapsed": true, 933 | "datasource": "${DS_PROMETHEUS}", 934 | "gridPos": { 935 | "h": 1, 936 | "w": 24, 937 | "x": 0, 938 | "y": 26 939 | }, 940 | "id": 38, 941 | "panels": [ 942 | { 943 | "aliasColors": { 944 | "getRecordings deleted": "purple" 945 | }, 946 | "bars": false, 947 | "cacheTimeout": null, 948 | "dashLength": 10, 949 | "dashes": false, 950 | "datasource": "${DS_PROMETHEUS}", 951 | "fill": 1, 952 | "fillGradient": 0, 953 | "gridPos": { 954 | "h": 7, 955 | "w": 14, 956 | "x": 0, 957 | "y": 27 958 | }, 959 | "hiddenSeries": false, 960 | "id": 22, 961 | "legend": { 962 | "alignAsTable": true, 963 | "avg": true, 964 | "current": true, 965 | "max": true, 966 | "min": true, 967 | "rightSide": true, 968 | "show": true, 969 | "total": false, 970 | "values": true 971 | }, 972 | "lines": true, 973 | "linewidth": 1, 974 | "links": [], 975 | "nullPointMode": "null", 976 | "options": { 977 | "dataLinks": [] 978 | }, 979 | "percentage": false, 980 | "pluginVersion": "6.7.0", 981 | "pointradius": 2, 982 | "points": false, 983 | "renderer": "flot", 984 | "seriesOverrides": [], 985 | "spaceLength": 10, 986 | "stack": false, 987 | "steppedLine": false, 988 | "targets": [ 989 | { 990 | "expr": "histogram_quantile(0.95, sum(rate(bbb_api_latency_bucket{endpoint=\"getMeetings\", instance=\"$instance\"}[5m])) by (le, instance))", 991 | "instant": false, 992 | "interval": "", 993 | "intervalFactor": 1, 994 | "legendFormat": "getMeetings", 995 | "refId": "A" 996 | }, 997 | { 998 | "expr": "histogram_quantile(0.95, sum(rate(bbb_api_latency_bucket{endpoint=\"getRecordings\", parameters=\"state=processing\", instance=\"$instance\"}[5m])) by (le, instance))", 999 | "hide": false, 1000 | "interval": "", 1001 | "legendFormat": "getRecordings processing", 1002 | "refId": "B" 1003 | }, 1004 | { 1005 | "expr": "histogram_quantile(0.95, sum(rate(bbb_api_latency_bucket{endpoint=\"getRecordings\", parameters=\"state=processed\", instance=\"$instance\"}[5m])) by (le, instance))", 1006 | "interval": "", 1007 | "legendFormat": "getRecordings processed", 1008 | "refId": "C" 1009 | }, 1010 | { 1011 | "expr": "histogram_quantile(0.95, sum(rate(bbb_api_latency_bucket{endpoint=\"getRecordings\", parameters=\"state=published\", instance=\"$instance\"}[5m])) by (le, instance))", 1012 | "interval": "", 1013 | "legendFormat": "getRecordings published", 1014 | "refId": "D" 1015 | }, 1016 | { 1017 | "expr": "histogram_quantile(0.95, sum(rate(bbb_api_latency_bucket{endpoint=\"getRecordings\", parameters=\"state=unpublished\", instance=\"$instance\"}[5m])) by (le, instance))", 1018 | "interval": "", 1019 | "legendFormat": "getRecordings unpublished", 1020 | "refId": "E" 1021 | }, 1022 | { 1023 | "expr": "histogram_quantile(0.95, sum(rate(bbb_api_latency_bucket{endpoint=\"getRecordings\", parameters=\"state=deleted\", instance=\"$instance\"}[5m])) by (le, instance))", 1024 | "interval": "", 1025 | "legendFormat": "getRecordings deleted", 1026 | "refId": "F" 1027 | } 1028 | ], 1029 | "thresholds": [], 1030 | "timeFrom": null, 1031 | "timeRegions": [], 1032 | "timeShift": null, 1033 | "title": "API 95-th Percentile Latency", 1034 | "tooltip": { 1035 | "shared": true, 1036 | "sort": 0, 1037 | "value_type": "individual" 1038 | }, 1039 | "type": "graph", 1040 | "xaxis": { 1041 | "buckets": null, 1042 | "mode": "time", 1043 | "name": null, 1044 | "show": true, 1045 | "values": [] 1046 | }, 1047 | "yaxes": [ 1048 | { 1049 | "decimals": null, 1050 | "format": "short", 1051 | "label": "Latency [s]", 1052 | "logBase": 1, 1053 | "max": null, 1054 | "min": null, 1055 | "show": true 1056 | }, 1057 | { 1058 | "format": "short", 1059 | "label": "", 1060 | "logBase": 1, 1061 | "max": null, 1062 | "min": null, 1063 | "show": false 1064 | } 1065 | ], 1066 | "yaxis": { 1067 | "align": false, 1068 | "alignLevel": null 1069 | } 1070 | } 1071 | ], 1072 | "title": "API", 1073 | "type": "row" 1074 | } 1075 | ], 1076 | "refresh": "30s", 1077 | "schemaVersion": 22, 1078 | "style": "dark", 1079 | "tags": [], 1080 | "templating": { 1081 | "list": [ 1082 | { 1083 | "current": { 1084 | "selected": false, 1085 | "text": "prometheus", 1086 | "value": "prometheus" 1087 | }, 1088 | "hide": 2, 1089 | "label": "datasource", 1090 | "name": "DS_PROMETHEUS", 1091 | "options": [], 1092 | "query": "prometheus", 1093 | "refresh": 1, 1094 | "regex": "", 1095 | "type": "datasource" 1096 | }, 1097 | { 1098 | "allValue": null, 1099 | "current": {}, 1100 | "datasource": "${DS_PROMETHEUS}", 1101 | "definition": "label_values(bbb_api_up, instance)", 1102 | "hide": 0, 1103 | "includeAll": false, 1104 | "index": -1, 1105 | "label": "Instance", 1106 | "multi": false, 1107 | "name": "instance", 1108 | "options": [], 1109 | "query": "label_values(bbb_api_up, instance)", 1110 | "refresh": 1, 1111 | "regex": "", 1112 | "skipUrlSync": false, 1113 | "sort": 0, 1114 | "tagValuesQuery": "", 1115 | "tags": [], 1116 | "tagsQuery": "", 1117 | "type": "query", 1118 | "useTags": false 1119 | } 1120 | ] 1121 | }, 1122 | "time": { 1123 | "from": "now-6h", 1124 | "to": "now" 1125 | }, 1126 | "timepicker": { 1127 | "refresh_intervals": [ 1128 | "15s", 1129 | "30s", 1130 | "1m", 1131 | "5m", 1132 | "15m", 1133 | "30m", 1134 | "1h", 1135 | "2h", 1136 | "1d" 1137 | ] 1138 | }, 1139 | "timezone": "", 1140 | "title": "BigBlueButton Server Instance", 1141 | "uid": "HIbd_CXZz2", 1142 | "variables": { 1143 | "list": [] 1144 | }, 1145 | "version": 14 1146 | } 1147 | -------------------------------------------------------------------------------- /files/grafana/dashboards/cockpit.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": "-- Grafana --", 7 | "enable": true, 8 | "hide": true, 9 | "iconColor": "rgba(0, 211, 255, 1)", 10 | "name": "Annotations & Alerts", 11 | "type": "dashboard" 12 | } 13 | ] 14 | }, 15 | "description": "Fuer Leute mit wenig Beinfreiheit", 16 | "editable": true, 17 | "gnetId": null, 18 | "graphTooltip": 0, 19 | "iteration": 1619357145525, 20 | "links": [], 21 | "panels": [ 22 | { 23 | "aliasColors": {}, 24 | "bars": false, 25 | "dashLength": 10, 26 | "dashes": false, 27 | "datasource": "${DS_PROMETHEUS}", 28 | "description": "", 29 | "fieldConfig": { 30 | "defaults": { 31 | "links": [] 32 | }, 33 | "overrides": [] 34 | }, 35 | "fill": 1, 36 | "fillGradient": 3, 37 | "gridPos": { 38 | "h": 4, 39 | "w": 11, 40 | "x": 0, 41 | "y": 0 42 | }, 43 | "hiddenSeries": false, 44 | "id": 8, 45 | "legend": { 46 | "avg": false, 47 | "current": false, 48 | "max": false, 49 | "min": false, 50 | "show": true, 51 | "total": false, 52 | "values": false 53 | }, 54 | "lines": true, 55 | "linewidth": 1, 56 | "nullPointMode": "null", 57 | "options": { 58 | "alertThreshold": true 59 | }, 60 | "percentage": false, 61 | "pluginVersion": "7.5.4", 62 | "pointradius": 2, 63 | "points": false, 64 | "renderer": "flot", 65 | "seriesOverrides": [], 66 | "spaceLength": 10, 67 | "stack": false, 68 | "steppedLine": false, 69 | "targets": [ 70 | { 71 | "expr": "sum(bbb_meetings)", 72 | "interval": "", 73 | "legendFormat": "Meetings", 74 | "refId": "A" 75 | }, 76 | { 77 | "expr": "sum(bbb_meetings_participants)", 78 | "interval": "", 79 | "legendFormat": "Teilnehmende", 80 | "refId": "B" 81 | }, 82 | { 83 | "expr": "sum(bbb_meetings_video_participants)", 84 | "interval": "", 85 | "legendFormat": "Videos", 86 | "refId": "C" 87 | } 88 | ], 89 | "thresholds": [], 90 | "timeFrom": null, 91 | "timeRegions": [], 92 | "timeShift": null, 93 | "title": "Zusammenfassung", 94 | "tooltip": { 95 | "shared": true, 96 | "sort": 0, 97 | "value_type": "individual" 98 | }, 99 | "type": "graph", 100 | "xaxis": { 101 | "buckets": null, 102 | "mode": "time", 103 | "name": null, 104 | "show": true, 105 | "values": [] 106 | }, 107 | "yaxes": [ 108 | { 109 | "$$hashKey": "object:278", 110 | "format": "short", 111 | "label": null, 112 | "logBase": 1, 113 | "max": null, 114 | "min": null, 115 | "show": true 116 | }, 117 | { 118 | "$$hashKey": "object:279", 119 | "format": "short", 120 | "label": null, 121 | "logBase": 1, 122 | "max": null, 123 | "min": null, 124 | "show": true 125 | } 126 | ], 127 | "yaxis": { 128 | "align": false, 129 | "alignLevel": null 130 | } 131 | }, 132 | { 133 | "datasource": "${DS_PROMETHEUS}", 134 | "fieldConfig": { 135 | "defaults": { 136 | "color": { 137 | "mode": "thresholds" 138 | }, 139 | "mappings": [], 140 | "thresholds": { 141 | "mode": "absolute", 142 | "steps": [ 143 | { 144 | "color": "dark-blue", 145 | "value": null 146 | } 147 | ] 148 | } 149 | }, 150 | "overrides": [] 151 | }, 152 | "gridPos": { 153 | "h": 4, 154 | "w": 5, 155 | "x": 11, 156 | "y": 0 157 | }, 158 | "id": 6, 159 | "options": { 160 | "colorMode": "value", 161 | "graphMode": "area", 162 | "justifyMode": "auto", 163 | "orientation": "auto", 164 | "reduceOptions": { 165 | "calcs": [ 166 | "last" 167 | ], 168 | "fields": "", 169 | "values": false 170 | }, 171 | "text": {}, 172 | "textMode": "auto" 173 | }, 174 | "pluginVersion": "7.5.4", 175 | "targets": [ 176 | { 177 | "expr": "sum(bbb_meetings_participants)", 178 | "interval": "", 179 | "legendFormat": "Teilnehmende", 180 | "refId": "B" 181 | }, 182 | { 183 | "expr": "sum(bbb_meetings)", 184 | "interval": "", 185 | "legendFormat": "Meetings", 186 | "refId": "A" 187 | }, 188 | { 189 | "expr": "sum(bbb_meetings_video_participants)", 190 | "interval": "", 191 | "legendFormat": "Videoteilnehmende", 192 | "refId": "C" 193 | } 194 | ], 195 | "timeFrom": null, 196 | "timeShift": null, 197 | "title": "Aktueller Status", 198 | "type": "stat" 199 | }, 200 | { 201 | "datasource": "${DS_PROMETHEUS}", 202 | "fieldConfig": { 203 | "defaults": { 204 | "color": { 205 | "mode": "thresholds" 206 | }, 207 | "mappings": [], 208 | "thresholds": { 209 | "mode": "absolute", 210 | "steps": [ 211 | { 212 | "color": "green", 213 | "value": null 214 | } 215 | ] 216 | } 217 | }, 218 | "overrides": [] 219 | }, 220 | "gridPos": { 221 | "h": 4, 222 | "w": 4, 223 | "x": 16, 224 | "y": 0 225 | }, 226 | "id": 10, 227 | "options": { 228 | "colorMode": "value", 229 | "graphMode": "none", 230 | "justifyMode": "auto", 231 | "orientation": "auto", 232 | "reduceOptions": { 233 | "calcs": [ 234 | "max" 235 | ], 236 | "fields": "", 237 | "values": false 238 | }, 239 | "text": {}, 240 | "textMode": "auto" 241 | }, 242 | "pluginVersion": "7.5.4", 243 | "targets": [ 244 | { 245 | "expr": "sum(bbb_meetings_participants)", 246 | "interval": "", 247 | "legendFormat": "Teilnehmende", 248 | "refId": "B" 249 | }, 250 | { 251 | "expr": "sum(bbb_meetings)", 252 | "interval": "", 253 | "legendFormat": "Meetings", 254 | "refId": "A" 255 | }, 256 | { 257 | "expr": "sum(bbb_meetings_video_participants)", 258 | "interval": "", 259 | "legendFormat": "Videoteilnehmende", 260 | "refId": "C" 261 | } 262 | ], 263 | "timeFrom": null, 264 | "timeShift": null, 265 | "title": "Maximum bisher", 266 | "type": "stat" 267 | }, 268 | { 269 | "datasource": null, 270 | "fieldConfig": { 271 | "defaults": { 272 | "color": { 273 | "mode": "thresholds" 274 | }, 275 | "mappings": [], 276 | "max": 1, 277 | "min": 0, 278 | "thresholds": { 279 | "mode": "absolute", 280 | "steps": [ 281 | { 282 | "color": "green", 283 | "value": null 284 | }, 285 | { 286 | "color": "#EAB839", 287 | "value": 0.8 288 | }, 289 | { 290 | "color": "red", 291 | "value": 1 292 | } 293 | ] 294 | } 295 | }, 296 | "overrides": [] 297 | }, 298 | "gridPos": { 299 | "h": 4, 300 | "w": 3, 301 | "x": 20, 302 | "y": 0 303 | }, 304 | "id": 12, 305 | "options": { 306 | "colorMode": "value", 307 | "graphMode": "area", 308 | "justifyMode": "auto", 309 | "orientation": "horizontal", 310 | "reduceOptions": { 311 | "calcs": [ 312 | "last" 313 | ], 314 | "fields": "", 315 | "values": false 316 | }, 317 | "text": {}, 318 | "textMode": "auto" 319 | }, 320 | "pluginVersion": "7.5.4", 321 | "targets": [ 322 | { 323 | "exemplar": true, 324 | "expr": "node_load1", 325 | "instant": false, 326 | "interval": "", 327 | "legendFormat": "{{instance}}", 328 | "refId": "A" 329 | } 330 | ], 331 | "timeFrom": null, 332 | "timeShift": null, 333 | "title": "UlmLernt.de Load", 334 | "transformations": [ 335 | { 336 | "id": "filterFieldsByName", 337 | "options": { 338 | "include": { 339 | "names": [ 340 | "Time", 341 | "fsn03.ulmlernt.org" 342 | ] 343 | } 344 | } 345 | } 346 | ], 347 | "transparent": true, 348 | "type": "stat" 349 | }, 350 | { 351 | "aliasColors": {}, 352 | "bars": false, 353 | "dashLength": 10, 354 | "dashes": false, 355 | "datasource": null, 356 | "fieldConfig": { 357 | "defaults": {}, 358 | "overrides": [] 359 | }, 360 | "fill": 1, 361 | "fillGradient": 0, 362 | "gridPos": { 363 | "h": 12, 364 | "w": 12, 365 | "x": 0, 366 | "y": 4 367 | }, 368 | "hiddenSeries": false, 369 | "id": 16, 370 | "legend": { 371 | "avg": false, 372 | "current": false, 373 | "max": false, 374 | "min": false, 375 | "show": true, 376 | "total": false, 377 | "values": false 378 | }, 379 | "lines": true, 380 | "linewidth": 1, 381 | "nullPointMode": "null", 382 | "options": { 383 | "alertThreshold": true 384 | }, 385 | "percentage": false, 386 | "pluginVersion": "7.5.4", 387 | "pointradius": 2, 388 | "points": false, 389 | "renderer": "flot", 390 | "seriesOverrides": [], 391 | "spaceLength": 10, 392 | "stack": false, 393 | "steppedLine": false, 394 | "targets": [ 395 | { 396 | "exemplar": true, 397 | "expr": "histogram_quantile(0.95, sum(rate(bbb_api_latency_bucket{endpoint=\"getMeetings\"}[5m])) by (le, instance)) ", 398 | "interval": "", 399 | "legendFormat": "{{instance}}", 400 | "refId": "A" 401 | } 402 | ], 403 | "thresholds": [], 404 | "timeRegions": [], 405 | "title": "95 Quantil Latency BBB Server", 406 | "tooltip": { 407 | "shared": true, 408 | "sort": 0, 409 | "value_type": "individual" 410 | }, 411 | "type": "graph", 412 | "xaxis": { 413 | "buckets": null, 414 | "mode": "time", 415 | "name": null, 416 | "show": true, 417 | "values": [] 418 | }, 419 | "yaxes": [ 420 | { 421 | "format": "short", 422 | "label": null, 423 | "logBase": 1, 424 | "max": null, 425 | "min": null, 426 | "show": true 427 | }, 428 | { 429 | "format": "short", 430 | "label": null, 431 | "logBase": 1, 432 | "max": null, 433 | "min": null, 434 | "show": true 435 | } 436 | ], 437 | "yaxis": { 438 | "align": false, 439 | "alignLevel": null 440 | } 441 | }, 442 | { 443 | "datasource": "${DS_PROMETHEUS}", 444 | "fieldConfig": { 445 | "defaults": { 446 | "color": { 447 | "mode": "thresholds" 448 | }, 449 | "mappings": [], 450 | "max": 60, 451 | "min": 0, 452 | "thresholds": { 453 | "mode": "absolute", 454 | "steps": [ 455 | { 456 | "color": "green", 457 | "value": null 458 | }, 459 | { 460 | "color": "dark-yellow", 461 | "value": 20 462 | }, 463 | { 464 | "color": "dark-red", 465 | "value": 40 466 | } 467 | ] 468 | } 469 | }, 470 | "overrides": [] 471 | }, 472 | "gridPos": { 473 | "h": 12, 474 | "w": 4, 475 | "x": 12, 476 | "y": 4 477 | }, 478 | "id": 4, 479 | "options": { 480 | "displayMode": "lcd", 481 | "orientation": "horizontal", 482 | "reduceOptions": { 483 | "calcs": [ 484 | "last" 485 | ], 486 | "fields": "", 487 | "values": false 488 | }, 489 | "showUnfilled": true, 490 | "text": {} 491 | }, 492 | "pluginVersion": "7.5.4", 493 | "targets": [ 494 | { 495 | "expr": "bbb_meetings_video_participants", 496 | "interval": "", 497 | "legendFormat": "{{instance}}", 498 | "refId": "A" 499 | } 500 | ], 501 | "timeFrom": null, 502 | "timeShift": null, 503 | "title": "Video Participants", 504 | "transparent": true, 505 | "type": "bargauge" 506 | }, 507 | { 508 | "cacheTimeout": null, 509 | "datasource": "${DS_PROMETHEUS}", 510 | "description": "", 511 | "fieldConfig": { 512 | "defaults": { 513 | "color": { 514 | "mode": "thresholds" 515 | }, 516 | "mappings": [], 517 | "max": 350, 518 | "min": 0, 519 | "thresholds": { 520 | "mode": "absolute", 521 | "steps": [ 522 | { 523 | "color": "green", 524 | "value": null 525 | }, 526 | { 527 | "color": "#EAB839", 528 | "value": 150 529 | }, 530 | { 531 | "color": "red", 532 | "value": 250 533 | } 534 | ] 535 | }, 536 | "unit": "none" 537 | }, 538 | "overrides": [] 539 | }, 540 | "gridPos": { 541 | "h": 12, 542 | "w": 4, 543 | "x": 16, 544 | "y": 4 545 | }, 546 | "id": 2, 547 | "links": [], 548 | "options": { 549 | "displayMode": "lcd", 550 | "orientation": "horizontal", 551 | "reduceOptions": { 552 | "calcs": [ 553 | "lastNotNull" 554 | ], 555 | "fields": "", 556 | "values": false 557 | }, 558 | "showUnfilled": true, 559 | "text": {} 560 | }, 561 | "pluginVersion": "7.5.4", 562 | "targets": [ 563 | { 564 | "expr": "bbb_meetings_participants", 565 | "format": "time_series", 566 | "interval": "", 567 | "legendFormat": "{{instance}}", 568 | "refId": "A" 569 | } 570 | ], 571 | "timeFrom": null, 572 | "timeShift": null, 573 | "title": "Teilnehmer", 574 | "transparent": true, 575 | "type": "bargauge" 576 | }, 577 | { 578 | "datasource": null, 579 | "fieldConfig": { 580 | "defaults": { 581 | "color": { 582 | "mode": "thresholds" 583 | }, 584 | "mappings": [], 585 | "thresholds": { 586 | "mode": "absolute", 587 | "steps": [ 588 | { 589 | "color": "red", 590 | "value": null 591 | }, 592 | { 593 | "color": "green", 594 | "value": 1 595 | } 596 | ] 597 | }, 598 | "unit": "short" 599 | }, 600 | "overrides": [] 601 | }, 602 | "gridPos": { 603 | "h": 12, 604 | "w": 3, 605 | "x": 20, 606 | "y": 4 607 | }, 608 | "id": 14, 609 | "options": { 610 | "displayMode": "basic", 611 | "orientation": "horizontal", 612 | "reduceOptions": { 613 | "calcs": [ 614 | "lastNotNull" 615 | ], 616 | "fields": "", 617 | "values": false 618 | }, 619 | "showUnfilled": true, 620 | "text": {} 621 | }, 622 | "pluginVersion": "7.5.4", 623 | "targets": [ 624 | { 625 | "exemplar": true, 626 | "expr": "up", 627 | "interval": "", 628 | "legendFormat": "{{instance}}", 629 | "refId": "A" 630 | } 631 | ], 632 | "timeFrom": null, 633 | "timeShift": null, 634 | "title": "Turn Server", 635 | "transformations": [ 636 | { 637 | "id": "filterFieldsByName", 638 | "options": { 639 | "include": { 640 | "names": [ 641 | "Time", 642 | "fsn17.ulmlernt.org", 643 | "fsn18.ulmlernt.org", 644 | "fsn19.ulmlernt.org", 645 | "fsn21.ulmlernt.org", 646 | "fsn28.ulmlernt.org", 647 | "fsn29.ulmlernt.org", 648 | "fsn30.ulmlernt.org", 649 | "fsn31.ulmlernt.org", 650 | "fsn32.ulmlernt.org", 651 | "fsn33.ulmlernt.org", 652 | "fsn34.ulmlernt.org", 653 | "fsn35.ulmlernt.org" 654 | ] 655 | } 656 | } 657 | } 658 | ], 659 | "type": "bargauge" 660 | } 661 | ], 662 | "refresh": "10s", 663 | "schemaVersion": 27, 664 | "style": "dark", 665 | "tags": [], 666 | "templating": { 667 | "list": [ 668 | { 669 | "current": { 670 | "selected": false, 671 | "text": "prometheus", 672 | "value": "prometheus" 673 | }, 674 | "description": null, 675 | "error": null, 676 | "hide": 2, 677 | "includeAll": false, 678 | "label": "datasource", 679 | "multi": false, 680 | "name": "DS_PROMETHEUS", 681 | "options": [], 682 | "query": "prometheus", 683 | "refresh": 1, 684 | "regex": "", 685 | "skipUrlSync": false, 686 | "type": "datasource" 687 | } 688 | ] 689 | }, 690 | "time": { 691 | "from": "now-6h", 692 | "to": "now" 693 | }, 694 | "timepicker": { 695 | "refresh_intervals": [ 696 | "5s", 697 | "10s", 698 | "30s", 699 | "1m", 700 | "5m", 701 | "15m", 702 | "30m", 703 | "1h", 704 | "2h", 705 | "1d" 706 | ] 707 | }, 708 | "timezone": "", 709 | "title": "Cockpit", 710 | "uid": "ZxiNVbeWk", 711 | "version": 10 712 | } -------------------------------------------------------------------------------- /files/logo/ulm_lernt_fahne_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stadtulm/a13-ansible/3b0c44d9712af5c5d3bd745c0a987cc9acf92102/files/logo/ulm_lernt_fahne_transparent.png -------------------------------------------------------------------------------- /files/logo/ulm_lernt_fahne_transparent.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /files/logo/ulm_lernt_fahne_weiss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stadtulm/a13-ansible/3b0c44d9712af5c5d3bd745c0a987cc9acf92102/files/logo/ulm_lernt_fahne_weiss.png -------------------------------------------------------------------------------- /files/logo/ulm_lernt_fahne_weiss.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /files/logo/ulm_lernt_logo_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stadtulm/a13-ansible/3b0c44d9712af5c5d3bd745c0a987cc9acf92102/files/logo/ulm_lernt_logo_transparent.png -------------------------------------------------------------------------------- /files/logo/ulm_lernt_logo_transparent.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /files/logo/ulm_lernt_logo_weiss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stadtulm/a13-ansible/3b0c44d9712af5c5d3bd745c0a987cc9acf92102/files/logo/ulm_lernt_logo_weiss.png -------------------------------------------------------------------------------- /files/logo/ulm_lernt_logo_weiss.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /helper/disable_bbb_hosts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Deactivate hosts in scalelite 3 | hosts: loadbalancer 4 | become: yes 5 | become_user: scalelite 6 | vars: 7 | bbb_disabled: bbb_tok 8 | tasks: 9 | - name: remove hosts from scalelite 10 | shell: | 11 | cd /var/www/scalelite/ 12 | set -o allexport; source .env; set +o allexport 13 | SERVER={{ item }} 14 | ID=$(bundle exec ./bin/rake servers | sed -E "s/$/;/" | sed -E "s/\s//g" | tr --delete '\n' | sed -E "s/online;/online;\n/g" | grep $SERVER | cut -d";" -f1 | cut -d":" -f2 ) 15 | ./bin/rake servers:disable[$ID] 16 | args: 17 | executable: /bin/bash 18 | with_inventory_hostnames: "{{ bbb_disabled }}" 19 | - name: display scalelite server status 20 | shell: | 21 | cd /var/www/scalelite/ 22 | set -o allexport; source .env; set +o allexport 23 | ./bin/rake servers status 24 | args: 25 | executable: /bin/bash 26 | register: shell_result 27 | - debug: 28 | var: shell_result.stdout_lines -------------------------------------------------------------------------------- /helper/enable_bbb_hosts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Deactivate hosts in scalelite 3 | hosts: loadbalancer 4 | become: yes 5 | become_user: scalelite 6 | vars: 7 | bbb_enabled: bbb_tik 8 | tasks: 9 | - name: add hosts to scalelite 10 | shell: | 11 | cd /var/www/scalelite/ 12 | set -o allexport; source .env; set +o allexport 13 | SERVER={{ item }} 14 | ID=$(bundle exec ./bin/rake servers | sed -E "s/$/;/" | sed -E "s/\s//g" | tr --delete '\n' | sed -E "s/online;/online;\n/g" | grep $SERVER | cut -d";" -f1 | cut -d":" -f2 ) 15 | ./bin/rake servers:enable[$ID] 16 | args: 17 | executable: /bin/bash 18 | with_inventory_hostnames: "{{ bbb_enabled }}" 19 | - name: display scalelite server status 20 | shell: | 21 | cd /var/www/scalelite/ 22 | set -o allexport; source .env; set +o allexport 23 | ./bin/rake servers status 24 | args: 25 | executable: /bin/bash 26 | register: shell_result 27 | - debug: 28 | var: shell_result.stdout_lines -------------------------------------------------------------------------------- /helper/fix_ufw.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restore ufw with iptables / chains errors 3 | hosts: all 4 | become: yes 5 | tasks: 6 | - ufw: 7 | state: disabled 8 | - shell: "iptables -F; iptables -X; ip6tables -F; ip6tables -X" 9 | - ufw: 10 | state: enabled 11 | -------------------------------------------------------------------------------- /helper/reboot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: reboot server(s) 3 | hosts: all 4 | become: yes 5 | tags: 6 | - reboot 7 | tasks: 8 | - name: reboot Server 9 | reboot: 10 | 11 | -------------------------------------------------------------------------------- /helper/reset_apt_package_lists.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: remove all package lists from /etc/apt/sources.list.d 3 | hosts: all 4 | become: yes 5 | tasks: 6 | - shell: /bin/rm /etc/apt/sources.list.d/* 7 | 8 | 9 | -------------------------------------------------------------------------------- /helper/store_known_hosts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Store known hosts of 'all' the hosts in the inventory file 3 | # Source: https://www.ipspace.net/kb/Ansible/Collect_SSH_Keys_Ansible.html 4 | # execute: ANSIBLE_HOST_KEY_CHECKING=false ansible-playbook helper/store_known_hosts.yml 5 | hosts: all 6 | gather_facts: no 7 | connection: local 8 | vars: 9 | - known_hosts: "~/.ssh/known_hosts" 10 | tasks: 11 | - name: scan and register 12 | command: "ssh-keyscan {{ansible_host|default(inventory_hostname)}}" 13 | register: "host_keys" 14 | changed_when: false 15 | 16 | - file: path={{known_hosts}} state=touch 17 | run_once: true 18 | 19 | - blockinfile: 20 | dest: "{{known_hosts}}" 21 | marker: "# {mark} This part managed by Ansible" 22 | block: | 23 | {% for h in groups['all'] if hostvars[h].host_keys is defined %} 24 | {{ hostvars[h].host_keys.stdout }} 25 | {% endfor %} 26 | run_once: true 27 | -------------------------------------------------------------------------------- /helper/upgradereboot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: upgrade and reboot server(s) 3 | hosts: all 4 | become: yes 5 | tags: 6 | - upgradeandreboot 7 | tasks: 8 | - name: upgrade server 9 | apt: 10 | update_cache: yes 11 | cache_valid_time: 3600 12 | upgrade: safe 13 | - name: reboot server 14 | reboot: 15 | -------------------------------------------------------------------------------- /inventory: -------------------------------------------------------------------------------- 1 | [all] 2 | fsn01.ulmlernt.org ansible_host=195.201.237.77 3 | fsn02.ulmlernt.org ansible_host=144.76.96.80 4 | fsn03.ulmlernt.org ansible_host=78.47.162.149 5 | fsn04.ulmlernt.org ansible_host=136.243.8.92 6 | fsn05.ulmlernt.org ansible_host=136.243.18.16 7 | fsn06.ulmlernt.org ansible_host=148.251.6.47 8 | fsn29.ulmlernt.org ansible_host=188.34.190.64 9 | fsn34.ulmlernt.org ansible_host=138.201.118.3 10 | bck.ulmlernt.org ansible_host=49.12.102.120 11 | 12 | [dns] 13 | fsn01.ulmlernt.org 14 | 15 | [bbb] 16 | fsn02.ulmlernt.org bbb_additional_turn_server="turn01.ulmlernt.org" bbb_turn_ipv4_only="turnipv4only01.ulmlernt.org" 17 | fsn04.ulmlernt.org bbb_additional_turn_server="turn01.ulmlernt.org" bbb_turn_ipv4_only="turnipv4only01.ulmlernt.org" 18 | fsn05.ulmlernt.org bbb_additional_turn_server="turn01.ulmlernt.org" bbb_turn_ipv4_only="turnipv4only01.ulmlernt.org" 19 | fsn06.ulmlernt.org bbb_additional_turn_server="turn01.ulmlernt.org" bbb_turn_ipv4_only="turnipv4only01.ulmlernt.org" 20 | 21 | [loadbalancer] 22 | fsn01.ulmlernt.org nginx_domain_name="lb.ulmlernt.org" 23 | 24 | [monitoring] 25 | fsn01.ulmlernt.org 26 | 27 | [frontend] 28 | fsn03.ulmlernt.org 29 | 30 | [backupstorage] 31 | bck.ulmlernt.org 32 | 33 | [turn] 34 | fsn34.ulmlernt.org turn_hostname="turn01.ulmlernt.org" 35 | 36 | [turnipv4only] 37 | fsn29.ulmlernt.org turn_hostname="turnipv4only01.ulmlernt.org" 38 | -------------------------------------------------------------------------------- /main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Basic Setup 3 | hosts: all 4 | become: yes 5 | tags: 6 | - base 7 | vars_files: 8 | - vars.yml 9 | roles: 10 | - base 11 | 12 | - name: Base User setup/update 13 | hosts: all 14 | become: yes 15 | tags: 16 | - base 17 | - user 18 | vars_files: 19 | - vars.yml 20 | roles: 21 | - base-user 22 | 23 | - name: Monitored host 24 | hosts: all 25 | become: yes 26 | vars: 27 | _node_exporter_binary_install_dir: '/opt/node_exporter' 28 | _node_exporter_system_group: 'prometheus' 29 | _node_exporter_system_user: 'prometheus' 30 | roles: 31 | - cloudalchemy.node-exporter 32 | - node-exporter-ufw 33 | 34 | - name: DNS Server 35 | hosts: dns 36 | become: yes 37 | tags: 38 | - dns 39 | vars: 40 | coredns_config_file: files/coredns/Corefile.j2 41 | roles: 42 | - cloudalchemy.coredns 43 | - coredns-ufw 44 | 45 | - name: TURN Server 46 | hosts: turn, turn_test 47 | become: yes 48 | tags: 49 | - turn 50 | - test 51 | vars_files: 52 | - vars.yml 53 | vars: 54 | turn_ipv4_only: no 55 | certbot_source: letsencrypt 56 | roles: 57 | - turn-standalone 58 | 59 | - name: TURN IPv4 only Server 60 | hosts: turnipv4only, turnipv4only_test 61 | become: yes 62 | tags: 63 | - turn 64 | - test 65 | vars_files: 66 | - vars.yml 67 | vars: 68 | turn_ipv4_only: yes 69 | certbot_source: letsencrypt 70 | roles: 71 | - turn-standalone 72 | 73 | - name: BigBlueButton 74 | hosts: bbb, bbb_test 75 | become: yes 76 | tags: 77 | - bbb 78 | vars_files: 79 | - vars.yml 80 | vars: 81 | bbb_letsencrypt_enable: yes 82 | bbb_letsencrypt_email: "{{ letsencrypt_email }}" 83 | bbb_coturn_enable: no 84 | bbb_turn_enable: yes 85 | bbb_stun_servers: 86 | - server: "{{ bbb_additional_turn_server }}" 87 | bbb_turn_servers: 88 | - server: "{{ bbb_turn_ipv4_only }}" 89 | port: 443 90 | secret: "{{ turn_secret }}" 91 | tls: true 92 | - server: "{{ bbb_additional_turn_server }}" 93 | port: 443 94 | secret: "{{ turn_secret }}" 95 | tls: true 96 | - server: "{{ bbb_additional_turn_server }}" 97 | port: 3478 98 | secret: "{{ turn_secret }}" 99 | tls: false 100 | bbb_default_welcome_message: Wilkommen bei %%CONFNAME%%!

Hilfe und Anleitungen finden Sie in unserer ulmlernt Dokumentation.

Sollten Probleme auftauchen bitten wir Sie darum unsere allgemeinen Hinweise zur Nutzung von ulmlernt zu beachten. Dort finden Sie auch eine Anleitung zur Störungsmeldung. 101 | bbb_default_welcome_message_footer: ulmlernt wird von der Stadt Ulm durch die Abteilung Bildung und Sport in Kooperation mit der Digitalen Agenda bereitgestellt und verwendet BigBlueButton. 102 | bbb_greenlight_enable: no 103 | bbb_api_demos_enable: no 104 | bbb_ntp_cron: yes 105 | roles: 106 | - nginx 107 | - dirty-docker # dirty hack because docker role in n0emis BBB will fail if docker group not present 108 | - n0emis.bigbluebutton 109 | - bbb-ulmlernt 110 | # TODO remove Docker from install process 111 | # removal of docker group makes problems if docker is reinstalled by gerlingguyscript in n0emis 112 | - no-docker 113 | - no-turn 114 | 115 | - name: Collect BBB secrets 116 | hosts: bbb, bbb_test 117 | become: yes 118 | tags: 119 | - bbb 120 | - config 121 | - test 122 | roles: 123 | - bbb-collect 124 | 125 | - name: BigBlueButton Test 126 | hosts: bbb_test 127 | become: yes 128 | tags: 129 | - bbb 130 | - test 131 | vars_files: 132 | - vars.yml 133 | roles: 134 | - bbb-easy-join 135 | 136 | 137 | - name: BBB Exporter 138 | hosts: bbb 139 | become: yes 140 | tags: 141 | - bbb 142 | roles: 143 | - bbb-exporter 144 | 145 | - name: Loadbalancer 146 | hosts: loadbalancer 147 | become: yes 148 | tags: 149 | - loadbalancer 150 | vars_files: 151 | - vars.yml 152 | vars: 153 | redis_port: 6379 154 | redis_bind_interface: 127.0.0.1 155 | ruby_install_from_source: yes 156 | ruby_version: 2.6.6 157 | ruby_download_url: https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.6.tar.gz 158 | ruby_install_bundler: yes 159 | postgresql_hba_entries: 160 | - {type: local, database: all, user: postgres, auth_method: peer} 161 | - {type: local, database: all, user: all, auth_method: peer} 162 | - {type: host, database: all, user: all, address: '127.0.0.1/32', auth_method: trust} 163 | - {type: host, database: all, user: all, address: '::1/128', auth_method: trust} 164 | postgresql_users: 165 | - name: scalelite 166 | state: present 167 | - name: grafana 168 | state: present 169 | postgresql_databases: 170 | - name: scalelite 171 | owner: scalelite 172 | state: present 173 | - name: grafana 174 | owner: grafana 175 | state: present 176 | roles: 177 | - nginx 178 | - nginx-tls 179 | - geerlingguy.redis 180 | - role: geerlingguy.postgresql 181 | become: yes 182 | - geerlingguy.ruby 183 | - scalelite 184 | 185 | - name: Register BBBs at Loadbalancer 186 | hosts: loadbalancer 187 | become: yes 188 | tags: 189 | - loadbalancer 190 | - config 191 | roles: 192 | - scalelite-config 193 | 194 | # TODO Remove Easyjoin from LB 195 | # - name: Easy Join 196 | # hosts: loadbalancer 197 | # become: yes 198 | # vars_files: 199 | # - vars.yml 200 | # vars: 201 | # nodejs_version: "12.x" 202 | # bbb_secret: "{{ scalelite_loadbalancer_secret }}" 203 | # tags: 204 | # - loadbalancer 205 | # roles: 206 | # - nginx 207 | # - nginx-tls 208 | # - geerlingguy.nodejs 209 | # - bbb-easy-join 210 | 211 | - name: Frontend - Greenlight 212 | hosts: frontend 213 | become: yes 214 | vars_files: 215 | - vars.yml 216 | tags: 217 | - frontend 218 | - greenlight 219 | vars: 220 | nginx_domain_name: "ulmlernt.de" 221 | nginx_redirect_alias: "www.ulmlernt.de" 222 | lb_url: "{{ scalelite_loadbalancer_url }}" 223 | lb_secret: "{{ scalelite_loadbalancer_secret }}" 224 | redis_port: 6379 225 | redis_bind_interface: 127.0.0.1 226 | ruby_install_from_source: yes 227 | ruby_version: 2.6.6 228 | ruby_download_url: https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.6.tar.gz 229 | ruby_install_bundler: yes 230 | nodejs_version: "12.x" 231 | postgresql_hba_entries: 232 | - {type: local, database: all, user: postgres, auth_method: peer} 233 | - {type: local, database: all, user: all, auth_method: peer} 234 | - {type: host, database: all, user: all, address: '127.0.0.1/32', auth_method: trust} 235 | - {type: host, database: all, user: all, address: '::1/128', auth_method: trust} 236 | postgresql_users: 237 | - name: greenlight 238 | state: present 239 | role_attr_flags: CREATEDB # rake db:migrate needs priv 240 | postgresql_databases: 241 | - name: greenlight_production 242 | owner: greenlight 243 | state: present 244 | roles: 245 | - nginx 246 | - nginx-tls 247 | - nginx-tls-redirect 248 | - geerlingguy.redis 249 | - role: geerlingguy.postgresql 250 | become: yes 251 | - geerlingguy.ruby 252 | - geerlingguy.nodejs 253 | #- docker 254 | - greenlight 255 | #- greenlight-docker 256 | 257 | - name: Frontend - no rocketchat 258 | hosts: frontend 259 | become: yes 260 | vars_files: 261 | - vars.yml 262 | vars: 263 | nginx_domain_name: "ulmlernt.de" 264 | nginx_redirect_alias: "chat.ulmlernt.de" 265 | tags: 266 | - no-rocketchat 267 | roles: 268 | - nginx 269 | - nginx-tls-redirect 270 | 271 | 272 | - name: Frontend - Dokuwiki 273 | hosts: frontend 274 | become: yes 275 | vars_files: 276 | - vars.yml 277 | tags: 278 | - frontend 279 | - dokuwiki 280 | vars: 281 | nginx_domain_name: "doku.ulmlernt.de" 282 | nginx_root: "/var/www/dokuwiki" 283 | roles: 284 | - nginx 285 | - nginx-tls-add 286 | - dokuwiki 287 | 288 | - name: Monitoring 289 | hosts: loadbalancer 290 | become: yes 291 | tags: 292 | - monitoring 293 | vars_files: 294 | - vars.yml 295 | vars: 296 | nginx_domain_name: mon.ulmlernt.org 297 | prometheus_targets: 298 | node: 299 | - targets: 300 | "{{ groups['all'] | map('extract', hostvars, ['ansible_fqdn']) | map('regex_replace', '$', ':9100') | list }}" 301 | bbb: 302 | - targets: 303 | "{{ groups['bbb'] | map('extract', hostvars, ['ansible_fqdn']) | map('regex_replace', '$', ':9688') | list }}" 304 | prometheus_scrape_configs: 305 | - job_name: "prometheus" 306 | metrics_path: "/metrics" 307 | static_configs: 308 | - targets: 309 | - "127.0.0.1:9090" 310 | - job_name: "coredns" 311 | metrics_path: "/metrics" 312 | static_configs: 313 | - targets: 314 | - "{{ ansible_default_ipv4.address }}:9153" 315 | - job_name: "node" 316 | file_sd_configs: 317 | - files: 318 | - "/etc/prometheus/file_sd/node.yml" 319 | relabel_configs: 320 | - source_labels: ['__address__'] 321 | separator: ':' 322 | regex: '(.*):.*' 323 | target_label: 'instance' 324 | replacement: '$1' 325 | - job_name: "bbb" 326 | file_sd_configs: 327 | - files: 328 | - "/etc/prometheus/file_sd/bbb.yml" 329 | relabel_configs: 330 | - source_labels: ['__address__'] 331 | separator: ':' 332 | regex: '(.*):.*' 333 | target_label: 'instance' 334 | replacement: '$1' 335 | grafana_address: 127.0.0.1 336 | grafana_port: 3001 337 | grafana_url: "https://mon.ulmlernt.org" 338 | grafana_domain: "mon.ulmlernt.org" 339 | grafana_database: 340 | type: postgres 341 | host: 127.0.0.1:5432 342 | name: grafana 343 | user: grafana 344 | password: trust 345 | grafana_datasources: 346 | - name: prometheus 347 | type: prometheus 348 | access: proxy 349 | url: 'http://localhost:9090' 350 | basicAuth: false 351 | isDefault: true 352 | grafana_dashboards_dir: files/grafana/dashboards 353 | grafana_security: 354 | admin_user: admin 355 | admin_password: "{{ monitoring_grafana_admin_password }}" 356 | grafana_users: 357 | allow_sign_up: false 358 | default_theme: dark 359 | auto_assign_org: true 360 | editors_can_admin: true 361 | auto_assign_org_role: Editor 362 | grafana_auth: 363 | disable_login_form: true 364 | anonymous: 365 | enabled: true 366 | org_name: ulmlernt 367 | org_role: Viewer 368 | github: 369 | enabled: true 370 | allow_sign_up: true 371 | client_id: "{{ monitoring_github_client_id }}" 372 | client_secret: "{{ monitoring_github_client_secret }}" 373 | scopes: user:email,read:org 374 | auth_url: https://github.com/login/oauth/authorize 375 | token_url: https://github.com/login/oauth/access_token 376 | api_url: https://api.github.com/user 377 | team_ids: "" 378 | allowed_organizations: stadtulm 379 | roles: 380 | - cloudalchemy.prometheus 381 | - prometheus-ufw 382 | - cloudalchemy.grafana 383 | - nginx-tls-monitoring 384 | 385 | - name: Backup Server 386 | hosts: backupstorage 387 | tags: 388 | - backup 389 | become: yes 390 | vars: 391 | rest_backup_storage_dir: "/mnt/backup/restic" 392 | rest_backup_server: "bck.ulmlernt.org" 393 | rest_backup_tasks: 394 | - { name: "dokuwiki", conn_pw: "{{ restic_backup_dokuwiki_conn_pw }}", backup_pw: "{{ restic_backup_dokuwiki_backup_pw }}" } 395 | - { name: "greenlight" , conn_pw: "{{ restic_backup_greenlight_conn_pw }}", backup_pw: "{{ restic_backup_greenlight_backup_pw }}" } 396 | - { name: "zammad" , conn_pw: "{{ restic_backup_zammad_conn_pw }}", backup_pw: "{{ restic_backup_zammad_backup_pw }}" } 397 | vars_files: 398 | - vars.yml 399 | roles: 400 | - restic-server 401 | 402 | - name: Backup Client - Dokuwiki 403 | hosts: frontend 404 | become: yes 405 | tags: 406 | - backup 407 | vars_files: 408 | - vars.yml 409 | vars: 410 | backup_name: "dokuwiki" 411 | backup_server: "{{ restic_backup_server }}" 412 | backup_conn_pw: "{{ restic_backup_dokuwiki_conn_pw }}" 413 | backup_pw: "{{ restic_backup_dokuwiki_backup_pw }}" 414 | backup_script: "backup_directory.sh" 415 | backup_dir: "/var/www/dokuwiki" 416 | roles: 417 | - restic-client 418 | 419 | - name: Backup Client - Greenlight 420 | hosts: frontend 421 | become: yes 422 | tags: 423 | - backup 424 | vars_files: 425 | - vars.yml 426 | vars: 427 | backup_name: "greenlight" 428 | backup_server: "{{ restic_backup_server }}" 429 | backup_conn_pw: "{{ restic_backup_greenlight_conn_pw }}" 430 | backup_pw: "{{ restic_backup_greenlight_backup_pw }}" 431 | backup_script: "backup_postgres.sh" 432 | backup_pg_db: "greenlight_production" 433 | roles: 434 | - restic-client 435 | 436 | -------------------------------------------------------------------------------- /requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | roles: 3 | - name: cloudalchemy.node-exporter 4 | version: 0.19.0 5 | - name: cloudalchemy.coredns 6 | version: 0.3.2 7 | - name: n0emis.bigbluebutton 8 | - name: geerlingguy.nodejs 9 | version: 5.1.1 10 | - name: geerlingguy.docker 11 | version: 2.7.0 12 | - name: geerlingguy.redis 13 | version: 1.6.0 14 | - name: geerlingguy.postgresql 15 | version: 2.2.0 16 | - name: geerlingguy.ruby 17 | version: 2.6.0 18 | - name: cloudalchemy.prometheus 19 | version: 2.15.0 20 | - name: cloudalchemy.grafana 21 | version: 0.17.0 22 | - name: geerlingguy.elasticsearch 23 | version: 4.2.0 24 | - name: geerlingguy.java 25 | version: 1.10.0 26 | - name: geerlingguy.mailhog 27 | version: 2.2.0 -------------------------------------------------------------------------------- /roles/base-user/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure 'sudo' group exists 3 | group: 4 | name: sudo 5 | state: present 6 | 7 | - name: Create users 8 | user: 9 | name: '{{ item.name }}' 10 | shell: /bin/bash 11 | password: '!' 12 | update_password: on_create 13 | groups: 14 | - sudo 15 | state: present 16 | with_items: '{{ users }}' 17 | 18 | - name: Add SSH keys 19 | authorized_key: 20 | user: '{{ item.name }}' 21 | state: present 22 | key: '{{ item.sshkeys }}' 23 | with_items: '{{ users }}' 24 | 25 | - name: Ensure sudo group is passwordless 26 | lineinfile: 27 | path: /etc/sudoers.d/sudo-nopasswd 28 | line: '%sudo ALL=(ALL) NOPASSWD: ALL' 29 | state: present 30 | mode: 0440 31 | create: yes 32 | validate: '/usr/sbin/visudo -cf %s' 33 | 34 | - name: Ensure sudoers uses sudoers.d 35 | lineinfile: 36 | path: /etc/sudoers 37 | line: '#includedir /etc/sudoers.d' 38 | state: present 39 | validate: '/usr/sbin/visudo -cf %s' 40 | -------------------------------------------------------------------------------- /roles/base/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart sshd 3 | service: 4 | name: sshd 5 | state: restarted 6 | 7 | - name: restart ufw 8 | service: 9 | name: ufw 10 | state: restarted 11 | 12 | - name: restart fail2ban 13 | service: 14 | name: fail2ban 15 | state: restarted 16 | -------------------------------------------------------------------------------- /roles/base/tasks/firewall.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure ufw and fail2ban packages 3 | apt: 4 | name: 5 | - ufw 6 | - fail2ban 7 | state: present 8 | 9 | - name: Allow ssh in ufw 10 | ufw: 11 | rule: allow 12 | name: OpenSSH 13 | 14 | # https://tomschlick.com/2017/05/24/dealing-with-ufw-chain-already-exists/ 15 | - name: Pause for ufw ... to mitigate hickups 16 | pause: 17 | seconds: 1 18 | 19 | - name: Allow everything out 20 | ufw: 21 | direction: outgoing 22 | policy: allow 23 | 24 | # https://tomschlick.com/2017/05/24/dealing-with-ufw-chain-already-exists/ 25 | - name: Pause for ufw ... to mitigate hickups 26 | pause: 27 | seconds: 1 28 | 29 | - name: Allow default nothing in and enable UFW 30 | ufw: 31 | direction: incoming 32 | policy: deny 33 | state: enabled 34 | 35 | # https://tomschlick.com/2017/05/24/dealing-with-ufw-chain-already-exists/ 36 | - name: Pause for ufw ... to mitigate hickups 37 | pause: 38 | seconds: 1 39 | 40 | - name: ensure fail2ban uses ufw 41 | copy: 42 | content: | 43 | [DEFAULT] 44 | banaction = ufw 45 | mode: 0644 46 | dest: /etc/fail2ban/jail.local 47 | notify: restart fail2ban 48 | -------------------------------------------------------------------------------- /roles/base/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure wrong hostname is not in hosts file 3 | lineinfile: 4 | dest: /etc/hosts 5 | regexp: '{{ ansible_hostname }}$' 6 | state: absent 7 | backup: yes 8 | when: ansible_hostname != inventory_hostname_short 9 | 10 | - name: Ensure Hostname 11 | hostname: 12 | name: '{{ inventory_hostname_short }}' 13 | when: ansible_hostname != inventory_hostname_short 14 | 15 | - name: Ensure Hostname in hosts file 16 | lineinfile: 17 | dest: /etc/hosts 18 | regexp: '{{ inventory_hostname_short }}$' 19 | line: '{{ ansible_default_ipv4.address }} {{ inventory_hostname }} {{ inventory_hostname_short }}' 20 | state: present 21 | when: ansible_default_ipv4.address is defined 22 | 23 | - name: Update apt 24 | apt: 25 | update_cache: yes 26 | cache_valid_time: 3600 27 | # upgrade disabled for stability reasons 28 | # upgrade: safe 29 | 30 | - name: Ensure basic packages 31 | apt: 32 | name: 33 | - byobu 34 | - zsh 35 | - curl 36 | - wget 37 | - dnsutils 38 | - git 39 | - sudo 40 | - htop 41 | - vim 42 | state: present 43 | 44 | - name: Ensure clean motd 45 | file: 46 | path: '/etc/update-motd.d/{{item}}' 47 | # only removing executable rights would be cleaner, but they are gone now 48 | state: absent 49 | with_items: 50 | - 10-help-text 51 | - 50-motd-news 52 | - 80-esm 53 | - 80-livepatch 54 | 55 | # TODO: why is this failing on fsn10 ? 56 | # - name: Ensure clean motd, news updater doesn't run 57 | # systemd: 58 | # name: motd-news 59 | # enabled: no 60 | # masked: yes 61 | 62 | - name: Ensure notice about configuration managment in motd 63 | copy: 64 | content: | 65 | #!/bin/sh 66 | printf "\n" 67 | printf "This system is managed with ansible, see following repository for details\n" 68 | printf "https://github.com/stadtulm/a13-ansible\n" 69 | printf "\n" 70 | mode: 0755 71 | dest: /etc/update-motd.d/11-help-text 72 | 73 | - name: Ensure that LANG from ssh clients is not used 74 | lineinfile: 75 | path: /etc/ssh/sshd_config 76 | line: 'AcceptEnv LANG LC_*' 77 | state: absent 78 | notify: restart sshd 79 | 80 | - import_tasks: firewall.yml 81 | -------------------------------------------------------------------------------- /roles/bbb-collect/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Register bbb conf 3 | command: bbb-conf --secret 4 | register: bbb_conf_result 5 | check_mode: no 6 | 7 | - name: Extract bbb url 8 | set_fact: 9 | bbb_url: "{{ bbb_conf_result.stdout | regex_search('URL: (.+)', multiline=True) | regex_replace('URL: ') }}" 10 | cacheable: yes 11 | 12 | - name: Extract bbb secret 13 | set_fact: 14 | ## regex_replace with multiline only works when following PR is released: 15 | ## https://github.com/ansible/ansible/pull/65051 16 | # bbb_secret: "{{ bbb_conf_result.stdout | regex_replace('Secret: ([a-zA-Z0-9]+)', '\\1', multiline=True) }}" 17 | bbb_secret: "{{ bbb_conf_result.stdout | regex_search('Secret: ([a-zA-Z0-9]*)', multiline=True) | regex_replace('Secret: ') }}" 18 | cacheable: yes 19 | -------------------------------------------------------------------------------- /roles/bbb-easy-join/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure git is available 3 | apt: 4 | name: 5 | - git 6 | state: present 7 | 8 | - name: Create user 9 | user: 10 | name: bbb-easy-join 11 | shell: /bin/bash 12 | home: /var/lib/bbb-easy-join 13 | create_home: yes 14 | password: '!' 15 | update_password: on_create 16 | groups: nogroup 17 | system: yes 18 | state: present 19 | 20 | - name: Checkout bbb-easy-join 21 | git: 22 | repo: https://github.com/stadtulm/bbb-easy-join.git 23 | dest: /var/www/bbb-easy-join 24 | version: 4c0ac089753e331610ec7b1c0b43ef3cd55be430 25 | force: yes 26 | 27 | - name: Ensure bbb-easy-join is owner of /var/www/bbb-easy-join 28 | file: 29 | path: /var/www/bbb-easy-join 30 | recurse: yes 31 | owner: bbb-easy-join 32 | 33 | - name: Ensure required npm packages are installed 34 | npm: 35 | path: /var/www/bbb-easy-join 36 | state: present 37 | become: yes 38 | become_user: bbb-easy-join 39 | 40 | - name: Ensure environment for bbb-easy-join 41 | template: 42 | src: env.j2 43 | dest: /var/www/bbb-easy-join/.env 44 | owner: bbb-easy-join 45 | 46 | - name: Ensure systemd unit for bbb-easy-join 47 | template: 48 | src: bbb-easy-join.service.j2 49 | dest: /etc/systemd/system/bbb-easy-join.service 50 | 51 | - name: Enable bbb-easy-join 52 | systemd: 53 | daemon_reload: yes 54 | name: bbb-easy-join 55 | enabled: yes 56 | state: started 57 | 58 | - name: Ensure bbb-easy-join nginx config exists 59 | template: 60 | src: nginx-bbb-easy-join.conf 61 | dest: /etc/bigbluebutton/nginx/bbb-easy-join.nginx 62 | notify: reload nginx 63 | -------------------------------------------------------------------------------- /roles/bbb-easy-join/templates/bbb-easy-join.service.j2: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=BigBlueButton Easy Join 3 | Requires=network.target 4 | 5 | [Service] 6 | Type=simple 7 | ExecStart=/usr/bin/node app.js 8 | WorkingDirectory=/var/www/bbb-easy-join 9 | Restart=always 10 | RestartSec=60 11 | User=bbb-easy-join 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /roles/bbb-easy-join/templates/env.j2: -------------------------------------------------------------------------------- 1 | HOST=127.0.0.1 2 | PORT=5000 3 | # you can call `sudo bbb-conf --secret` for the following two values 4 | BBB_API_URL=https://{{ ansible_fqdn }}/bigbluebutton 5 | BBB_API_SECRET={{ bbb_secret }} 6 | WELCOME_MESSAGE="Welcome to %%CONFNAME%%!

Invite others with following URL: %%JOINURL%%

Use a headset to avoid causing background noise for others." -------------------------------------------------------------------------------- /roles/bbb-easy-join/templates/nginx-bbb-easy-join.conf: -------------------------------------------------------------------------------- 1 | location /b { 2 | proxy_pass http://127.0.0.1:5000; 3 | 4 | proxy_set_header Host $host; 5 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 6 | proxy_set_header X-Forwarded-Proto $scheme; 7 | proxy_http_version 1.1; 8 | } 9 | -------------------------------------------------------------------------------- /roles/bbb-exporter/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure git and pip are available 3 | apt: 4 | name: 5 | - git 6 | - python3-pip 7 | state: present 8 | 9 | - name: Create system user for bbb-exporter 10 | user: 11 | name: bbb-exporter 12 | shell: /bin/bash 13 | home: /var/lib/bbb-exporter 14 | create_home: yes 15 | password: '!' 16 | update_password: on_create 17 | system: yes 18 | state: present 19 | 20 | - name: Checkout bbb-exporter 21 | git: 22 | repo: https://github.com/greenstatic/bigbluebutton-exporter.git 23 | dest: /var/www/bbb-exporter 24 | version: ca57db839188726e2c1b29387cd4747a23a0043e 25 | 26 | - name: Ensure bbb-exporter is owner of /var/www/bbb-exporter 27 | file: 28 | path: /var/www/bbb-exporter 29 | recurse: yes 30 | owner: bbb-exporter 31 | group: bbb-exporter 32 | 33 | - name: Ensure required pip packages are installed 34 | pip: 35 | requirements: /var/www/bbb-exporter/requirements.txt 36 | state: present 37 | extra_args: --user 38 | become: yes 39 | become_user: bbb-exporter 40 | 41 | - name: Ensure environment for bbb-exporter 42 | template: 43 | src: env.j2 44 | dest: /var/www/bbb-exporter/.env 45 | owner: bbb-exporter 46 | 47 | - name: Ensure systemd unit for bbb-exporter 48 | template: 49 | src: bbb-exporter.service 50 | dest: /etc/systemd/system/bbb-exporter.service 51 | 52 | - name: Enable bbb-exporter 53 | systemd: 54 | daemon_reload: yes 55 | name: bbb-exporter 56 | enabled: yes 57 | state: started 58 | 59 | - name: Ensure exporter config for ufw 60 | copy: 61 | content: | 62 | [bbb-exporter] 63 | title=bbb-exporter 64 | description=prometheus bbb exporter 65 | ports=9688/tcp 66 | mode: 0644 67 | dest: /etc/ufw/applications.d/bbb-exporter 68 | 69 | - name: allow bbb-exporter in ufw 70 | ufw: 71 | rule: allow 72 | name: bbb-exporter 73 | state: enabled 74 | -------------------------------------------------------------------------------- /roles/bbb-exporter/templates/bbb-exporter.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=BBB Exporter 3 | Requires=network.target 4 | 5 | [Service] 6 | Type=simple 7 | EnvironmentFile=/var/www/bbb-exporter/.env 8 | WorkingDirectory=/var/www/bbb-exporter/bbb-exporter 9 | Restart=always 10 | RestartSec=60 11 | ExecStart=/usr/bin/python3 /var/www/bbb-exporter/bbb-exporter/server.py 12 | User=bbb-exporter 13 | 14 | [Install] 15 | WantedBy=multi-user.target 16 | -------------------------------------------------------------------------------- /roles/bbb-exporter/templates/env.j2: -------------------------------------------------------------------------------- 1 | API_BASE_URL={{ bbb_url }}api/ 2 | API_SECRET={{ bbb_secret }} 3 | -------------------------------------------------------------------------------- /roles/bbb-ulmlernt/files/conf-muted-ulmlernt.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stadtulm/a13-ansible/3b0c44d9712af5c5d3bd745c0a987cc9acf92102/roles/bbb-ulmlernt/files/conf-muted-ulmlernt.wav -------------------------------------------------------------------------------- /roles/bbb-ulmlernt/files/conf-unmuted-ulmlernt.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stadtulm/a13-ansible/3b0c44d9712af5c5d3bd745c0a987cc9acf92102/roles/bbb-ulmlernt/files/conf-unmuted-ulmlernt.wav -------------------------------------------------------------------------------- /roles/bbb-ulmlernt/files/default.odp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stadtulm/a13-ansible/3b0c44d9712af5c5d3bd745c0a987cc9acf92102/roles/bbb-ulmlernt/files/default.odp -------------------------------------------------------------------------------- /roles/bbb-ulmlernt/files/default.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stadtulm/a13-ansible/3b0c44d9712af5c5d3bd745c0a987cc9acf92102/roles/bbb-ulmlernt/files/default.pdf -------------------------------------------------------------------------------- /roles/bbb-ulmlernt/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: reload nginx 3 | service: 4 | name: nginx 5 | state: reloaded 6 | 7 | - name: restart bigbluebutton 8 | command: bbb-conf --restart 9 | become: yes 10 | -------------------------------------------------------------------------------- /roles/bbb-ulmlernt/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: ensure default recording is disabled 3 | lineinfile: 4 | path: /usr/share/bbb-web/WEB-INF/classes/bigbluebutton.properties 5 | regexp: '^disableRecordingDefault' 6 | line: 'disableRecordingDefault=true' 7 | notify: restart bigbluebutton 8 | 9 | - name: ensure recording auto start is disabled 10 | lineinfile: 11 | path: /usr/share/bbb-web/WEB-INF/classes/bigbluebutton.properties 12 | regexp: '^autoStartRecording' 13 | line: 'autoStartRecording=false' 14 | notify: restart bigbluebutton 15 | 16 | - name: ensure users are not allowed to start recording 17 | lineinfile: 18 | path: /usr/share/bbb-web/WEB-INF/classes/bigbluebutton.properties 19 | regexp: '^allowStartStopRecording' 20 | line: 'allowStartStopRecording=false' 21 | notify: restart bigbluebutton 22 | 23 | - name: ensure http call to root is redirected 24 | copy: 25 | content: | 26 | location = / { 27 | return 301 https://ulmlernt.de; 28 | } 29 | mode: 0755 30 | dest: /etc/bigbluebutton/nginx/redirectroot.nginx 31 | notify: reload nginx 32 | 33 | - name: ensure our own default slides are used 34 | copy: 35 | src: default.pdf 36 | dest: /var/www/bigbluebutton-default/default.pdf 37 | 38 | - name: copy our own mute/unmute sounds 39 | copy: 40 | src: '{{ item }}' 41 | dest: '/opt/freeswitch/share/freeswitch/sounds/en/us/callie/conference/48000/{{item}}' 42 | with_items: 43 | - conf-muted-ulmlernt.wav 44 | - conf-unmuted-ulmlernt.wav 45 | 46 | - name: ensure our own mute sound is used 47 | xml: 48 | path: /opt/freeswitch/etc/freeswitch/autoload_configs/conference.conf.xml 49 | xpath: '/configuration/profiles/profile[@name="cdquality"]/param[@name="muted-sound"]' 50 | attribute: value 51 | value: conference/conf-muted-ulmlernt.wav 52 | notify: restart bigbluebutton 53 | 54 | - name: ensure our own unmute sound is used 55 | xml: 56 | path: /opt/freeswitch/etc/freeswitch/autoload_configs/conference.conf.xml 57 | xpath: '/configuration/profiles/profile[@name="cdquality"]/param[@name="unmuted-sound"]' 58 | attribute: value 59 | value: conference/conf-unmuted-ulmlernt.wav 60 | notify: restart bigbluebutton 61 | 62 | - name: adapt freeswitch ports in ufw 63 | copy: 64 | content: | 65 | [freeswitch] 66 | title=freeswitch 67 | description=freeswitch telecom stack 68 | ports=16384:32768/udp 69 | mode: 0644 70 | dest: /etc/ufw/applications.d/freeswitch 71 | 72 | - name: allow freeswitch in ufw 73 | ufw: 74 | rule: allow 75 | name: freeswitch 76 | state: enabled 77 | 78 | - name: Restart BBB every night @4am 79 | cron: 80 | state: present 81 | name: "restart BigBlueButton" 82 | minute: "0" 83 | hour: "4" 84 | user: root 85 | job: "bbb-conf --restart" 86 | 87 | - name: run bbb-conf --setip to ensure no echo test hanging after upgrade 88 | become: yes 89 | shell: 90 | cmd: "bbb-conf --setip {{ inventory_hostname }}" 91 | 92 | -------------------------------------------------------------------------------- /roles/coredns-ufw/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure coredns config for ufw 3 | copy: 4 | content: | 5 | [coredns] 6 | title=coredns 7 | description=coredns dns server 8 | ports=53,9153/tcp|53/udp 9 | mode: 0644 10 | dest: /etc/ufw/applications.d/coredns 11 | 12 | - name: allow coredns in ufw 13 | ufw: 14 | rule: allow 15 | name: coredns 16 | state: enabled 17 | -------------------------------------------------------------------------------- /roles/dirty-docker/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure Docker Group exists because otherwise n0emis BBB docker dependency will fail 3 | group: 4 | name: docker 5 | state: present -------------------------------------------------------------------------------- /roles/docker/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # source: https://docs.docker.com/engine/install/ubuntu/ 3 | - name: Ensure Prequisitions for Docker are met 4 | apt: 5 | name: 6 | - apt-transport-https 7 | - ca-certificates 8 | - curl 9 | - gnupg-agent 10 | - software-properties-common 11 | state: present 12 | 13 | - name: Ensure Docker Key is present 14 | apt_key: 15 | url: https://download.docker.com/linux/ubuntu/gpg 16 | state: present 17 | 18 | - name: Ensure Docker Repository is present 19 | apt_repository: 20 | repo: deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable 21 | state: present 22 | 23 | - name: Ensure Docker is installed 24 | apt: 25 | name: 26 | - docker-ce 27 | - docker-ce-cli 28 | - containerd.io 29 | - docker-compose 30 | state: present 31 | 32 | - name: Ensure Users are in Docker group 33 | user: 34 | name: '{{ item.name }}' 35 | groups: docker 36 | append: yes 37 | state: present 38 | with_items: '{{ users }}' 39 | -------------------------------------------------------------------------------- /roles/dokuwiki/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure PHP nginx installed 3 | apt: 4 | name: 5 | - nginx 6 | - php7.2-fpm 7 | - php7.2 8 | 9 | - name: Ensure current Dokuwiki 10 | unarchive: 11 | src: https://download.dokuwiki.org/src/dokuwiki/dokuwiki-stable.tgz 12 | dest: /tmp 13 | remote_src: yes 14 | 15 | - name: Ensure Dokuwiki directory exists 16 | file: 17 | path: /var/www/dokuwiki 18 | state: directory 19 | owner: www-data 20 | group: www-data 21 | 22 | - name: Ensure dokuwiki install.php not present 23 | file: 24 | path: /var/www/dokuwiki/install.php 25 | state: absent 26 | 27 | - name: Ensure Nginx Dokuwiki conf 28 | template: 29 | src: dokuwiki.nginx 30 | dest: "/etc/nginx/{{ nginx_domain_name }}.d/dokuwiki.conf" 31 | 32 | 33 | -------------------------------------------------------------------------------- /roles/dokuwiki/templates/dokuwiki-backup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BACKUPFILE=dokuwiki_`date +%d-%m-%Y"_"%H_%M_%S`.tgz 4 | LOGFILE=/var/backups/dokuwiki/dokuwiki.log 5 | 6 | { 7 | tar -zcvf /var/backups/dokuwiki/$BACKUPFILE {{ nginx_root }} 8 | rsync -av --delete /var/backups/dokuwiki srf@lb.ulmlernt.org:/var/backups/dokuwiki 9 | } 2>&1 | tee -a $LOGFILE 10 | 11 | -------------------------------------------------------------------------------- /roles/dokuwiki/templates/dokuwiki.nginx: -------------------------------------------------------------------------------- 1 | client_max_body_size 200M; 2 | 3 | location / { 4 | index doku.php; 5 | try_files $uri $uri/ @dokuwiki; 6 | } 7 | 8 | location ~ ^/lib.*\.(gif|png|ico|jpg)$ { 9 | expires 30d; 10 | } 11 | 12 | location ^~ /conf/ { return 403; } 13 | location ^~ /data/ { return 403; } 14 | 15 | location @dokuwiki { 16 | rewrite ^/_media/(.*) /lib/exe/fetch.php?media=$1 last; 17 | rewrite ^/_detail/(.*) /lib/exe/detail.php?media=$1 last; 18 | rewrite ^/_export/([^/]+)/(.*) /doku.php?do=export_$1&id=$2 last; 19 | rewrite ^/(.*) /doku.php?id=$1 last; 20 | } 21 | 22 | location ~ \.php$ { 23 | include fastcgi_params; 24 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 25 | fastcgi_pass unix:/run/php/php7.2-fpm.sock; 26 | } 27 | -------------------------------------------------------------------------------- /roles/greenlight/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart greenlight 3 | service: 4 | name: greenlight 5 | state: restarted 6 | -------------------------------------------------------------------------------- /roles/greenlight/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure group "greenlight" exists 3 | group: 4 | name: greenlight 5 | state: present 6 | 7 | - name: Ensure user greenlight exists 8 | user: 9 | name: greenlight 10 | shell: /bin/bash 11 | password: '!' 12 | update_password: on_create 13 | groups: greenlight 14 | state: present 15 | 16 | - name: Checkout greenlight repository 17 | git: 18 | repo: https://github.com/stadtulm/greenlight.git 19 | dest: /var/www/greenlight 20 | version: ulmlernt 21 | force: yes 22 | notify: 23 | - restart greenlight 24 | - reload nginx 25 | 26 | - name: Ensure greenlight is owner of /var/www/greenlight 27 | file: 28 | path: /var/www/greenlight 29 | recurse: yes 30 | owner: greenlight 31 | group: greenlight 32 | 33 | - name: Ensure Bundler 2 is installed 34 | gem: 35 | name: bundler 36 | state: latest 37 | user_install: no 38 | 39 | - name: Ensure sqlite is installed 40 | apt: 41 | name: 42 | - sqlite3 43 | - libsqlite3-dev 44 | state: present 45 | 46 | # bundler: deployment mode by cli argument is deprecated, so we're going to run this: 47 | - name: Ensure bundle in is deployment mode 48 | command: 49 | cmd: bundle config set deployment 'true' 50 | chdir: /var/www/greenlight 51 | creates: /var/www/greenlight/.bundle/config 52 | become: yes 53 | become_user: greenlight 54 | 55 | - name: Ensure gems for greenlight are installed 56 | bundler: 57 | state: present 58 | exclude_groups: 59 | - development 60 | - test 61 | chdir: /var/www/greenlight 62 | become: yes 63 | become_user: greenlight 64 | 65 | - name: find out what the current greenlight env is 66 | slurp: 67 | src: /var/www/greenlight/.env 68 | register: current_greenlight_env 69 | ignore_errors: yes 70 | when: not greenlight_key_base is defined 71 | 72 | - name: register current turn secret 73 | set_fact: 74 | greenlight_key_base: "{{ current_greenlight_env.content | b64decode | regex_findall('SECRET_KEY_BASE=(.+)') | first }}" 75 | when: not greenlight_key_base is defined and not current_greenlight_env.failed 76 | 77 | - name: Generate greenlight secret key base 78 | command: openssl rand -hex 64 79 | register: openssl_rand64 80 | check_mode: no 81 | when: not greenlight_key_base is defined 82 | 83 | - name: Register secret key base 84 | set_fact: 85 | greenlight_key_base: "{{ openssl_rand64.stdout }}" 86 | when: not greenlight_key_base is defined 87 | 88 | - name: Ensure environment file for greenlight 89 | template: 90 | src: env.j2 91 | dest: /var/www/greenlight/.env 92 | become: yes 93 | become_user: greenlight 94 | 95 | - name: Ensure systemd unit for greenlight 96 | template: 97 | src: greenlight.service 98 | dest: /etc/systemd/system/greenlight.service 99 | 100 | - name: Enable greenlight units 101 | systemd: 102 | daemon_reload: yes 103 | name: greenlight 104 | enabled: yes 105 | 106 | - name: Ensure greenlight is started 107 | systemd: 108 | name: greenlight 109 | state: started 110 | 111 | - name: Ensure greenlight nginx config exists 112 | template: 113 | src: nginx-greenlight.conf.j2 114 | dest: /etc/nginx/a13.d/greenlight.conf 115 | notify: reload nginx 116 | 117 | - name: Restart Greenlight every night @4am 118 | cron: 119 | state: present 120 | name: "restart Greenlight" 121 | minute: "0" 122 | hour: "4" 123 | user: root 124 | job: "systemctl restart greenlight.service" 125 | -------------------------------------------------------------------------------- /roles/greenlight/templates/env.j2: -------------------------------------------------------------------------------- 1 | PORT=5000 2 | RAILS_ENV=production 3 | 4 | REDIS_URL=redis://127.0.0.1:6379 5 | 6 | # Create a Secret Key for Rails 7 | # 8 | # You can generate a secure one through the Greenlight docker image 9 | # with the command. 10 | # 11 | # docker run --rm bigbluebutton/greenlight:v2 bundle exec rake secret 12 | # 13 | SECRET_KEY_BASE={{ greenlight_key_base }} 14 | 15 | # The endpoint and secret for your BigBlueButton server. 16 | # Set these if you are running GreenLight on a single BigBlueButton server. 17 | # You can retrive these by running the following command on your BigBlueButton server: 18 | # 19 | # bbb-conf --secret 20 | # 21 | BIGBLUEBUTTON_ENDPOINT={{ lb_url }} 22 | BIGBLUEBUTTON_SECRET={{ lb_secret }} 23 | 24 | 25 | # Set this to true if you want GreenLight to support user signup and login without 26 | # Omniauth. For more information, see: 27 | # 28 | # https://docs.bigbluebutton.org/greenlight/gl-overview.html#accounts-and-profile 29 | # 30 | ALLOW_GREENLIGHT_ACCOUNTS=true 31 | 32 | # Set this to true if you want GreenLight to send verification emails upon 33 | # the creation of a new account 34 | # 35 | ALLOW_MAIL_NOTIFICATIONS=true 36 | 37 | # The notifications are sent using sendmail, unless the SMTP_SERVER variable is set. 38 | # In that case, make sure the rest of the variables are properly set. 39 | # 40 | # SMTP_SERVER=smtp.gmail.com 41 | # SMTP_PORT=587 42 | # SMTP_DOMAIN=gmail.com 43 | # SMTP_USERNAME= 44 | # SMTP_PASSWORD= 45 | # SMTP_AUTH=plain 46 | # SMTP_STARTTLS_AUTO=true 47 | # 48 | SMTP_SERVER={{ mail_admin.server }} 49 | SMTP_PORT={{ mail_admin.port }} 50 | SMTP_DOMAIN={{ mail_admin.domain }} 51 | SMTP_USERNAME={{ mail_admin.user }} 52 | SMTP_PASSWORD={{ mail_admin_password }} 53 | SMTP_AUTH={{ mail_admin.auth }} 54 | SMTP_STARTTLS_AUTO={{ mail_admin.starttls }} 55 | 56 | # Specify the email address that all mail is sent from 57 | SMTP_SENDER={{ mail_admin.sender }} 58 | 59 | # Prefix for the applications root URL. 60 | # Useful for deploying the application to a subdirectory, which is highly recommended 61 | # if deploying on a BigBlueButton server. Keep in mind that if you change this, you'll 62 | # have to update your authentication callback URL's to reflect this change. 63 | # 64 | # The recommended prefix is "/b". 65 | # 66 | RELATIVE_URL_ROOT=/ 67 | 68 | # Specify which settings you would like the users to configure on room creation 69 | # or edit after the room has been created 70 | # By default, all settings are turned OFF. 71 | # 72 | # Current settings available: 73 | # mute-on-join: Automatically mute users by default when they join a room 74 | # require-moderator-approval: Require moderators to approve new users before they can join the room 75 | # anyone-can-start: Allows anyone with the join url to start the room in BigBlueButton 76 | # all-join-moderator: All users join as moderators in BigBlueButton 77 | ROOM_FEATURES=mute-on-join,require-moderator-approval,anyone-can-start,all-join-moderator 78 | 79 | # Specify the maximum number of records to be sent to the BigBlueButton API in one call 80 | # Default is set to 25 records 81 | PAGINATION_NUMBER=25 82 | 83 | # Specify the maximum number of rows that should be displayed per page for a paginated table 84 | # Default is set to 25 rows 85 | NUMBER_OF_ROWS=25 86 | 87 | # Specify if you want to display the Google Calendar button 88 | # ENABLE_GOOGLE_CALENDAR_BUTTON=true|false 89 | ENABLE_GOOGLE_CALENDAR_BUTTON=false 90 | 91 | # Set the application into Maintenance Mode 92 | # 93 | # Current options supported: 94 | # true: Renders an error page that does not allow users to access any of the features in the application 95 | # false: Application runs normally 96 | MAINTENANCE_MODE=false 97 | 98 | # Displays a flash that appears to inform the user of a scheduled maintenance window 99 | # This variable should contain ONLY the date and time of the scheduled maintenance 100 | # 101 | # Ex: MAINTENANCE_WINDOW=Friday August 18 6pm-10pm EST 102 | MAINTENANCE_WINDOW= 103 | 104 | # The link to the Report an Issue button that appears on the 500 page and in the Account Dropdown 105 | # 106 | # Defaults to the Github Issues Page for Greenlight 107 | # Button can be disabled by setting the value to blank 108 | REPORT_ISSUE_URL=https://github.com/bigbluebutton/greenlight/issues/new 109 | 110 | # Comment this out to send logs to STDOUT in production instead of log/production.log . 111 | # 112 | RAILS_LOG_TO_STDOUT=true 113 | 114 | # 115 | # Force SSL 116 | # 117 | ENABLE_SSL=true 118 | 119 | # Database settings 120 | # 121 | # Greenlight may work out of the box with sqlite3, but for production it is recommended to use postgresql. 122 | # In such case, these variables must be included. 123 | # 124 | DB_ADAPTER=postgresql 125 | DB_HOST=127.0.0.1 126 | DB_NAME=greenlight_production 127 | DB_USERNAME=greenlight 128 | DB_PASSWORD=trust 129 | 130 | # Specify the default registration to be used by Greenlight until an administrator sets the 131 | # registration method 132 | # Allowed values are: 133 | # open - For open registration 134 | # invite - For invite only registration 135 | # approval - For approve/decline registration 136 | DEFAULT_REGISTRATION=approval 137 | -------------------------------------------------------------------------------- /roles/greenlight/templates/greenlight.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Greenlight 3 | After=network-online.target 4 | Wants=network-online.target postgresql.service 5 | 6 | [Service] 7 | Type=simple 8 | EnvironmentFile=/var/www/greenlight/.env 9 | WorkingDirectory=/var/www/greenlight 10 | Restart=always 11 | RestartSec=60 12 | ExecStart=/var/www/greenlight/bin/start 13 | User=greenlight 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /roles/greenlight/templates/nginx-greenlight.conf.j2: -------------------------------------------------------------------------------- 1 | access_log /var/log/nginx/access-greenlight.log cri; 2 | 3 | location / { 4 | proxy_pass http://127.0.0.1:5000; 5 | proxy_set_header Host $host; 6 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 7 | proxy_set_header X-Forwarded-Proto $scheme; 8 | proxy_set_header X-Request-Id $request_id; 9 | proxy_http_version 1.1; 10 | } 11 | 12 | location /cable { 13 | proxy_pass http://127.0.0.1:5000; 14 | proxy_set_header Host $host; 15 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 16 | proxy_set_header X-Forwarded-Proto $scheme; 17 | proxy_set_header X-Request-Id $request_id; 18 | proxy_set_header Upgrade $http_upgrade; 19 | proxy_set_header Connection "Upgrade"; 20 | proxy_http_version 1.1; 21 | proxy_read_timeout 6h; 22 | proxy_send_timeout 6h; 23 | client_body_timeout 6h; 24 | send_timeout 6h; 25 | } 26 | 27 | # Allow larger body size for uploading presentations 28 | location ~ /preupload_presentation$ { 29 | client_max_body_size 30m; 30 | 31 | proxy_pass http://127.0.0.1:5000; 32 | proxy_set_header Host $host; 33 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 34 | proxy_set_header X-Forwarded-Proto $scheme; 35 | proxy_set_header X-Request-Id $request_id; 36 | proxy_http_version 1.1; 37 | } 38 | -------------------------------------------------------------------------------- /roles/nginx-tls-add/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: reload nginx 3 | service: 4 | name: nginx 5 | state: reloaded 6 | -------------------------------------------------------------------------------- /roles/nginx-tls-add/tasks/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure nginx subconfig folder exists 3 | file: 4 | path: "/etc/nginx/{{ nginx_domain_name }}.d" 5 | state: directory 6 | 7 | - name: Register certbot certificate file 8 | stat: 9 | path: "/etc/letsencrypt/live/{{ nginx_domain_name }}/fullchain.pem" 10 | register: certbot_certificate_file_path 11 | 12 | - name: Ensure availability of generic nginx config 13 | template: 14 | src: a13.conf.j2 15 | dest: "/etc/nginx/sites-available/{{ nginx_domain_name }}.https.conf" 16 | 17 | - name: Generate dhparams 18 | shell: openssl dhparam -out /etc/nginx/dhparams.pem 2048 19 | args: 20 | creates: /etc/nginx/dhparams.pem 21 | 22 | - name: Ensure generic nginx config is applied 23 | file: 24 | state: link 25 | src: "/etc/nginx/sites-available/{{ nginx_domain_name }}.https.conf" 26 | dest: "/etc/nginx/sites-enabled/{{ nginx_domain_name }}.https.conf" 27 | when: certbot_certificate_file_path.stat.exists 28 | notify: reload nginx 29 | -------------------------------------------------------------------------------- /roles/nginx-tls-add/tasks/http.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Remove default nginx config 3 | file: 4 | name: /etc/nginx/sites-enabled/default 5 | state: absent 6 | 7 | - name: Ensure folder for letsencrypt exists 8 | file: 9 | name: /var/www/letsencrypt 10 | state: directory 11 | 12 | - name: Ensure nginx site for letsencrypt requests 13 | template: 14 | src: http.conf.j2 15 | dest: "/etc/nginx/sites-available/{{ nginx_domain_name}}.http.conf" 16 | 17 | - name: Ensure generic nginx config is applied 18 | file: 19 | state: link 20 | src: "/etc/nginx/sites-available/{{ nginx_domain_name}}.http.conf" 21 | dest: "/etc/nginx/sites-enabled/{{ nginx_domain_name}}.http.conf" 22 | notify: reload nginx 23 | -------------------------------------------------------------------------------- /roles/nginx-tls-add/tasks/letsencrypt.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add certbot apt repo 3 | apt_repository: 4 | repo: ppa:certbot/certbot 5 | state: present 6 | 7 | - name: Install certbot 8 | apt: 9 | pkg: 10 | - certbot 11 | - python3-certbot-nginx 12 | update_cache: true 13 | state: present 14 | 15 | - name: Ensure Let's Encrypt uses API v2 16 | lineinfile: 17 | path: /etc/letsencrypt/cli.ini 18 | regexp: '^server' 19 | line: 'server = https://acme-v02.api.letsencrypt.org/directory' 20 | 21 | - name: Register certbot certificate file 22 | stat: 23 | path: "/etc/letsencrypt/live/{{ nginx_domain_name }}/fullchain.pem" 24 | register: certbot_certificate_file_path 25 | 26 | - name: Generate Certificates 27 | command: certbot certonly -d {{ nginx_domain_name }} --agree-tos --email {{ letsencrypt_email }} -n --nginx 28 | notify: reload nginx 29 | when: not certbot_certificate_file_path.stat.exists 30 | 31 | # FIXME: webroot 32 | -------------------------------------------------------------------------------- /roles/nginx-tls-add/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - import_tasks: http.yml 3 | 4 | - import_tasks: letsencrypt.yml 5 | 6 | - import_tasks: config.yml 7 | -------------------------------------------------------------------------------- /roles/nginx-tls-add/templates/a13.conf.j2: -------------------------------------------------------------------------------- 1 | server { 2 | listen 443 ssl http2; 3 | listen [::]:443 ssl http2; 4 | server_name {{ nginx_domain_name }}; 5 | 6 | ssl_certificate /etc/letsencrypt/live/{{ nginx_domain_name }}/fullchain.pem; 7 | ssl_certificate_key /etc/letsencrypt/live/{{ nginx_domain_name }}/privkey.pem; 8 | ssl_trusted_certificate /etc/letsencrypt/live/{{ nginx_domain_name }}/fullchain.pem; 9 | 10 | ssl_session_cache shared:SSL:50m; 11 | ssl_session_timeout 5m; 12 | ssl_stapling on; 13 | ssl_stapling_verify on; 14 | 15 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 16 | ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; 17 | 18 | ssl_dhparam /etc/nginx/dhparams.pem; 19 | ssl_prefer_server_ciphers on; 20 | 21 | {% if nginx_root is defined %} 22 | root {{ nginx_root }}; 23 | {% else %} 24 | root /var/www/html; 25 | {% endif %} 26 | include /etc/nginx/{{ nginx_domain_name }}.d/*.conf; 27 | } 28 | -------------------------------------------------------------------------------- /roles/nginx-tls-add/templates/http.conf.j2: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | listen [::]:80; 4 | server_name {{ nginx_domain_name }}; 5 | 6 | location /.well-known/acme-challenge { 7 | root /var/www/letsencrypt; 8 | try_files $uri $uri/ =404; 9 | } 10 | 11 | location / { 12 | rewrite ^ https://{{ nginx_domain_name }}$request_uri? permanent; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /roles/nginx-tls-monitoring/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: reload nginx 3 | service: 4 | name: nginx 5 | state: reloaded 6 | -------------------------------------------------------------------------------- /roles/nginx-tls-monitoring/tasks/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Register certbot certificate file 3 | stat: 4 | path: "/etc/letsencrypt/live/{{ nginx_domain_name }}/fullchain.pem" 5 | register: certbot_certificate_file_path 6 | 7 | - name: Ensure availability of monitoring nginx config 8 | template: 9 | src: mon.conf.j2 10 | dest: /etc/nginx/sites-available/mon.conf 11 | 12 | - name: Generate dhparams 13 | shell: openssl dhparam -out /etc/nginx/dhparams.pem 2048 14 | args: 15 | creates: /etc/nginx/dhparams.pem 16 | 17 | - name: Ensure generic nginx config is applied 18 | file: 19 | state: link 20 | src: /etc/nginx/sites-available/mon.conf 21 | dest: /etc/nginx/sites-enabled/mon.conf 22 | when: certbot_certificate_file_path.stat.exists 23 | notify: reload nginx 24 | -------------------------------------------------------------------------------- /roles/nginx-tls-monitoring/tasks/http.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Remove default nginx config 3 | file: 4 | name: /etc/nginx/sites-enabled/default 5 | state: absent 6 | 7 | - name: Ensure folder for letsencrypt exists 8 | file: 9 | name: /var/www/letsencrypt 10 | state: directory 11 | 12 | - name: Ensure nginx site for letsencrypt requests 13 | template: 14 | src: http-mon.conf.j2 15 | dest: /etc/nginx/sites-available/http-mon.conf 16 | 17 | - name: Ensure generic nginx config is applied 18 | file: 19 | state: link 20 | src: /etc/nginx/sites-available/http-mon.conf 21 | dest: /etc/nginx/sites-enabled/http-mon.conf 22 | notify: reload nginx 23 | -------------------------------------------------------------------------------- /roles/nginx-tls-monitoring/tasks/letsencrypt.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add certbot apt repo 3 | apt_repository: 4 | repo: ppa:certbot/certbot 5 | state: present 6 | 7 | - name: Install certbot 8 | apt: 9 | pkg: 10 | - certbot 11 | - python3-certbot-nginx 12 | update_cache: true 13 | state: present 14 | 15 | - name: Ensure Let's Encrypt uses API v2 16 | lineinfile: 17 | path: /etc/letsencrypt/cli.ini 18 | regexp: '^server' 19 | line: 'server = https://acme-v02.api.letsencrypt.org/directory' 20 | 21 | - name: Register certbot certificate file 22 | stat: 23 | path: "/etc/letsencrypt/live/{{ nginx_domain_name }}/fullchain.pem" 24 | register: certbot_certificate_file_path 25 | 26 | - name: Generate Certificates 27 | command: certbot certonly -d {{ nginx_domain_name }} --agree-tos --email {{ letsencrypt_email }} -n --nginx 28 | notify: reload nginx 29 | when: not certbot_certificate_file_path.stat.exists 30 | -------------------------------------------------------------------------------- /roles/nginx-tls-monitoring/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - import_tasks: http.yml 3 | 4 | - import_tasks: letsencrypt.yml 5 | 6 | - import_tasks: config.yml 7 | -------------------------------------------------------------------------------- /roles/nginx-tls-monitoring/templates/http-mon.conf.j2: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | listen [::]:80; 4 | server_name {{ nginx_domain_name }}; 5 | 6 | location /.well-known/acme-challenge { 7 | root /var/www/letsencrypt; 8 | try_files $uri $uri/ =404; 9 | } 10 | 11 | location / { 12 | rewrite ^ https://{{ nginx_domain_name }}$request_uri? permanent; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /roles/nginx-tls-monitoring/templates/mon.conf.j2: -------------------------------------------------------------------------------- 1 | server { 2 | listen 443 ssl http2; 3 | listen [::]:443 ssl http2; 4 | server_name {{ nginx_domain_name }}; 5 | 6 | ssl_certificate /etc/letsencrypt/live/{{ nginx_domain_name }}/fullchain.pem; 7 | ssl_certificate_key /etc/letsencrypt/live/{{ nginx_domain_name }}/privkey.pem; 8 | ssl_trusted_certificate /etc/letsencrypt/live/{{ nginx_domain_name }}/fullchain.pem; 9 | 10 | ssl_session_cache shared:SSL:50m; 11 | ssl_session_timeout 5m; 12 | ssl_stapling on; 13 | ssl_stapling_verify on; 14 | 15 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 16 | ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; 17 | 18 | ssl_dhparam /etc/nginx/dhparams.pem; 19 | ssl_prefer_server_ciphers on; 20 | 21 | root /var/www/html; 22 | 23 | location / { 24 | proxy_pass http://127.0.0.1:3001; 25 | 26 | proxy_read_timeout 60s; 27 | proxy_redirect off; 28 | 29 | proxy_set_header Host $http_host; 30 | 31 | proxy_set_header X-Real-IP $remote_addr; 32 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 33 | proxy_set_header X-Forwarded-Proto $scheme; 34 | 35 | proxy_http_version 1.1; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /roles/nginx-tls-redirect/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: reload nginx 3 | service: 4 | name: nginx 5 | state: reloaded 6 | -------------------------------------------------------------------------------- /roles/nginx-tls-redirect/tasks/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure availability of generic nginx config 3 | template: 4 | src: a13.conf.j2 5 | dest: "/etc/nginx/sites-available/{{ nginx_redirect_alias }}.https.conf" 6 | 7 | - name: Ensure generic nginx config is applied 8 | file: 9 | state: link 10 | src: "/etc/nginx/sites-available/{{ nginx_redirect_alias }}.https.conf" 11 | dest: "/etc/nginx/sites-enabled/{{ nginx_redirect_alias }}.https.conf" 12 | when: certbot_certificate_file_path.stat.exists 13 | notify: reload nginx 14 | -------------------------------------------------------------------------------- /roles/nginx-tls-redirect/tasks/http.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure nginx site for letsencrypt requests 3 | template: 4 | src: http.conf.j2 5 | dest: "/etc/nginx/sites-available/{{ nginx_redirect_alias }}.http.conf" 6 | 7 | - name: Ensure generic nginx config is applied 8 | file: 9 | state: link 10 | src: "/etc/nginx/sites-available/{{ nginx_redirect_alias }}.http.conf" 11 | dest: "/etc/nginx/sites-enabled/{{ nginx_redirect_alias }}.http.conf" 12 | notify: reload nginx 13 | -------------------------------------------------------------------------------- /roles/nginx-tls-redirect/tasks/letsencrypt.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add certbot apt repo 3 | apt_repository: 4 | repo: ppa:certbot/certbot 5 | state: present 6 | 7 | - name: Install certbot 8 | apt: 9 | pkg: 10 | - certbot 11 | - python3-certbot-nginx 12 | update_cache: true 13 | state: present 14 | 15 | - name: Ensure Let's Encrypt uses API v2 16 | lineinfile: 17 | path: /etc/letsencrypt/cli.ini 18 | regexp: '^server' 19 | line: 'server = https://acme-v02.api.letsencrypt.org/directory' 20 | 21 | 22 | - name: Register certbot certificate file 23 | stat: 24 | path: "/etc/letsencrypt/live/{{ nginx_redirect_alias }}/fullchain.pem" 25 | register: certbot_certificate_file_path 26 | 27 | - name: Generate Certificates 28 | command: certbot certonly -d {{ nginx_redirect_alias }} --agree-tos --email {{ letsencrypt_email }} -n --nginx 29 | notify: reload nginx 30 | when: not certbot_certificate_file_path.stat.exists 31 | 32 | # FIXME: webroot 33 | -------------------------------------------------------------------------------- /roles/nginx-tls-redirect/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - import_tasks: http.yml 3 | 4 | - import_tasks: letsencrypt.yml 5 | 6 | - import_tasks: config.yml 7 | -------------------------------------------------------------------------------- /roles/nginx-tls-redirect/templates/a13.conf.j2: -------------------------------------------------------------------------------- 1 | server { 2 | listen 443 ssl http2; 3 | listen [::]:443 ssl http2; 4 | server_name {{ nginx_redirect_alias }}; 5 | 6 | ssl_certificate /etc/letsencrypt/live/{{ nginx_redirect_alias }}/fullchain.pem; 7 | ssl_certificate_key /etc/letsencrypt/live/{{ nginx_redirect_alias }}/privkey.pem; 8 | ssl_trusted_certificate /etc/letsencrypt/live/{{ nginx_redirect_alias }}/fullchain.pem; 9 | 10 | ssl_session_cache shared:SSL:50m; 11 | ssl_session_timeout 5m; 12 | ssl_stapling on; 13 | ssl_stapling_verify on; 14 | 15 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 16 | ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; 17 | 18 | ssl_dhparam /etc/nginx/dhparams.pem; 19 | ssl_prefer_server_ciphers on; 20 | 21 | root /var/www/html; 22 | 23 | location / { 24 | rewrite ^ https://{{ nginx_domain_name }}$request_uri? permanent; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /roles/nginx-tls-redirect/templates/http.conf.j2: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | listen [::]:80; 4 | server_name {{ nginx_redirect_alias }}; 5 | 6 | location /.well-known/acme-challenge { 7 | root /var/www/letsencrypt; 8 | try_files $uri $uri/ =404; 9 | } 10 | 11 | location / { 12 | rewrite ^ https://{{ nginx_domain_name }}$request_uri? permanent; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /roles/nginx-tls/files/hackhack.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stadtulm/a13-ansible/3b0c44d9712af5c5d3bd745c0a987cc9acf92102/roles/nginx-tls/files/hackhack.gif -------------------------------------------------------------------------------- /roles/nginx-tls/files/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ulmlernt 6 | 27 | 28 | 29 |

Wartungsarbeiten

30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /roles/nginx-tls/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: reload nginx 3 | service: 4 | name: nginx 5 | state: reloaded 6 | -------------------------------------------------------------------------------- /roles/nginx-tls/tasks/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure nginx subconfig folder exists 3 | file: 4 | path: /etc/nginx/a13.d 5 | state: directory 6 | 7 | - name: Register certbot certificate file 8 | stat: 9 | path: "/etc/letsencrypt/live/{{ nginx_domain_name }}/fullchain.pem" 10 | register: certbot_certificate_file_path 11 | 12 | - name: Ensure availability of generic nginx config 13 | template: 14 | src: a13.conf.j2 15 | dest: /etc/nginx/sites-available/a13.conf 16 | 17 | - name: Ensure existence of maintenance folder 18 | file: 19 | path: /var/www/html/maintenance 20 | state: directory 21 | 22 | - name: Copy maintenance files 23 | copy: 24 | src: "{{item}}" 25 | dest: "/var/www/html/maintenance/{{item}}" 26 | with_items: 27 | - index.html 28 | - hackhack.gif 29 | 30 | - name: Generate dhparams 31 | shell: openssl dhparam -out /etc/nginx/dhparams.pem 2048 32 | args: 33 | creates: /etc/nginx/dhparams.pem 34 | 35 | - name: Ensure generic nginx config is applied 36 | file: 37 | state: link 38 | src: /etc/nginx/sites-available/a13.conf 39 | dest: /etc/nginx/sites-enabled/a13.conf 40 | when: certbot_certificate_file_path.stat.exists 41 | notify: reload nginx 42 | -------------------------------------------------------------------------------- /roles/nginx-tls/tasks/http.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Remove default nginx config 3 | file: 4 | name: /etc/nginx/sites-enabled/default 5 | state: absent 6 | 7 | - name: Ensure folder for letsencrypt exists 8 | file: 9 | name: /var/www/letsencrypt 10 | state: directory 11 | 12 | - name: Ensure nginx site for letsencrypt requests 13 | template: 14 | src: http.conf.j2 15 | dest: /etc/nginx/sites-available/http.conf 16 | 17 | - name: Ensure generic nginx config is applied 18 | file: 19 | state: link 20 | src: /etc/nginx/sites-available/http.conf 21 | dest: /etc/nginx/sites-enabled/http.conf 22 | notify: reload nginx 23 | -------------------------------------------------------------------------------- /roles/nginx-tls/tasks/letsencrypt.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add certbot apt repo 3 | apt_repository: 4 | repo: ppa:certbot/certbot 5 | state: present 6 | 7 | - name: Install certbot 8 | apt: 9 | pkg: 10 | - certbot 11 | - python3-certbot-nginx 12 | update_cache: true 13 | state: present 14 | 15 | - name: Ensure Let's Encrypt uses API v2 16 | lineinfile: 17 | path: /etc/letsencrypt/cli.ini 18 | regexp: '^server' 19 | line: 'server = https://acme-v02.api.letsencrypt.org/directory' 20 | 21 | - name: Register certbot certificate file 22 | stat: 23 | path: "/etc/letsencrypt/live/{{ nginx_domain_name }}/fullchain.pem" 24 | register: certbot_certificate_file_path 25 | 26 | - name: Generate Certificates 27 | command: certbot certonly -d {{ nginx_domain_name }} --agree-tos --email {{ letsencrypt_email }} -n --nginx 28 | notify: reload nginx 29 | when: not certbot_certificate_file_path.stat.exists 30 | 31 | # FIXME: webroot 32 | -------------------------------------------------------------------------------- /roles/nginx-tls/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - import_tasks: http.yml 3 | 4 | - import_tasks: letsencrypt.yml 5 | 6 | - import_tasks: config.yml 7 | -------------------------------------------------------------------------------- /roles/nginx-tls/templates/a13.conf.j2: -------------------------------------------------------------------------------- 1 | server { 2 | listen 443 ssl http2; 3 | listen [::]:443 ssl http2; 4 | server_name {{ nginx_domain_name }}; 5 | 6 | ssl_certificate /etc/letsencrypt/live/{{ nginx_domain_name }}/fullchain.pem; 7 | ssl_certificate_key /etc/letsencrypt/live/{{ nginx_domain_name }}/privkey.pem; 8 | ssl_trusted_certificate /etc/letsencrypt/live/{{ nginx_domain_name }}/fullchain.pem; 9 | 10 | ssl_session_cache shared:SSL:50m; 11 | ssl_session_timeout 5m; 12 | ssl_stapling on; 13 | ssl_stapling_verify on; 14 | 15 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 16 | ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; 17 | 18 | ssl_dhparam /etc/nginx/dhparams.pem; 19 | ssl_prefer_server_ciphers on; 20 | 21 | root /var/www/html; 22 | 23 | location /maintenance { 24 | index index.html; 25 | } 26 | 27 | # uncomment this for maintenance 28 | #if ($remote_addr != 127.0.0.1) { 29 | # return 503; 30 | #} 31 | 32 | error_page 503 @maintenance; 33 | location @maintenance { 34 | rewrite ^(.*)hackhack.gif$ /maintenance/hackhack.gif break; 35 | rewrite ^(.*)$ /maintenance/index.html break; 36 | } 37 | 38 | include /etc/nginx/a13.d/*.conf; 39 | } 40 | -------------------------------------------------------------------------------- /roles/nginx-tls/templates/http.conf.j2: -------------------------------------------------------------------------------- 1 | server_tokens off; 2 | 3 | server { 4 | listen 80 default_server; 5 | listen [::]:80 default_server; 6 | server_name {{ nginx_domain_name }}; 7 | 8 | location /.well-known/acme-challenge { 9 | root /var/www/letsencrypt; 10 | try_files $uri $uri/ =404; 11 | } 12 | 13 | location / { 14 | rewrite ^ https://{{ nginx_domain_name }}$request_uri? permanent; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /roles/nginx/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: reload nginx 3 | service: 4 | name: nginx 5 | state: reloaded 6 | -------------------------------------------------------------------------------- /roles/nginx/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure nginx ppa is used for newer nginx versions 3 | apt_repository: 4 | repo: ppa:nginx/stable 5 | register: nginx_repo 6 | 7 | - name: Ensure nginx from ppa is pinned 8 | copy: 9 | content: | 10 | Package: * 11 | Pin: release n=xenial,o=LP-PPA-nginx-stable 12 | Pin-Priority: 1000 13 | dest: /etc/apt/preferences.d/ppa-nginx 14 | mode: 0755 15 | register: nginx_ppa_pin 16 | 17 | - name: Ensure apt cache gets updated 18 | apt: 19 | update_cache: yes 20 | when: nginx_repo.changed or nginx_ppa_pin.changed 21 | 22 | - name: Ensure nginx installed 23 | apt: 24 | name: nginx 25 | state: present 26 | 27 | - name: Ensure nginx from ppa is really installed 28 | apt: 29 | name: nginx 30 | state: latest 31 | when: nginx_repo.changed or nginx_ppa_pin.changed 32 | 33 | - name: Ensure nginx ufw configuration is available 34 | copy: 35 | # this is the standard config from the nginx package(?) 36 | # space character in the rule name is not that nice, but whatever 37 | content: | 38 | [Nginx HTTP] 39 | title=Web Server (Nginx, HTTP) 40 | description=Small, but very powerful and efficient web server 41 | ports=80/tcp 42 | 43 | [Nginx HTTPS] 44 | title=Web Server (Nginx, HTTPS) 45 | description=Small, but very powerful and efficient web server 46 | ports=443/tcp 47 | 48 | [Nginx Full] 49 | title=Web Server (Nginx, HTTP + HTTPS) 50 | description=Small, but very powerful and efficient web server 51 | ports=80,443/tcp 52 | mode: 0644 53 | dest: /etc/ufw/applications.d/nginx 54 | 55 | - name: Allow nginx in ufw 56 | ufw: 57 | rule: allow 58 | name: Nginx Full 59 | state: enabled 60 | 61 | - name: Ensure nginx configuration contains log format with request id 62 | lineinfile: 63 | line: "log_format cri '$remote_addr - $remote_user [$time_local] \"$request\" $status $body_bytes_sent \"$http_referer\" \"$http_user_agent\" request_id=$request_id';" 64 | insertbefore: '^\s*access_log' 65 | path: /etc/nginx/nginx.conf 66 | # validate: '/usr/sbin/nginx -t -c %s' 67 | notify: reload nginx 68 | -------------------------------------------------------------------------------- /roles/no-docker/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure Docker Key is removed 3 | apt_key: 4 | id: 9DC858229FC7DD38854AE2D88D81803C0EBFCD88 5 | state: absent 6 | 7 | - name: Ensure Docker Repository is absent 8 | apt_repository: 9 | repo: "{{ item }}" 10 | state: absent 11 | with_items: 12 | - "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable" 13 | - "deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable" 14 | 15 | - name: Ensure Docker is not installed 16 | apt: 17 | name: 18 | - docker-ce 19 | - docker-ce-cli 20 | - containerd.io 21 | - docker-compose 22 | state: absent 23 | 24 | - name: Ensure Docker group is deleted 25 | group: 26 | name: docker 27 | state: absent 28 | 29 | - name: check if docker network interface exists 30 | shell: ip a | grep docker 31 | register: docker_network_interface 32 | 33 | - name: Ensure Docker network interface is deleted 34 | command: ip link delete docker0 35 | when: docker_network_interface.rc == 0 36 | -------------------------------------------------------------------------------- /roles/no-rocketchat/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: reload nginx 3 | service: 4 | name: nginx 5 | state: reloaded 6 | -------------------------------------------------------------------------------- /roles/no-rocketchat/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: ensure rocketchat user does not exist 3 | user: 4 | name: rocketchat 5 | state: absent 6 | 7 | - name: Ensure rocketchat files / dir do not exist 8 | file: 9 | path: "{{ item }}" 10 | state: absent 11 | loop: 12 | - "/srv/rocketchat" 13 | - "/etc/nginx/chat.ulmlernt.org.d" 14 | - "/etc/nginx/sites-available/chat.ulmlernt.org.http.conf" 15 | - "/etc/nginx/sites-available/chat.ulmlernt.org.https.conf" 16 | - "/etc/nginx/sites-enabled/chat.ulmlernt.org.http.conf" 17 | - "/etc/nginx/sites-enabled/chat.ulmlernt.org.https.conf" 18 | notify: reload nginx 19 | 20 | - name: Ensure Docker is removed 21 | apt: 22 | name: docker-ce 23 | state: absent 24 | -------------------------------------------------------------------------------- /roles/no-rocketchat/templates/no-rocketchat.nginx: -------------------------------------------------------------------------------- 1 | client_max_body_size 200M; 2 | 3 | location / { 4 | index doku.php; 5 | try_files $uri $uri/ @dokuwiki; 6 | } 7 | 8 | location ~ ^/lib.*\.(gif|png|ico|jpg)$ { 9 | expires 30d; 10 | } 11 | 12 | location ^~ /conf/ { return 403; } 13 | location ^~ /data/ { return 403; } 14 | 15 | location @dokuwiki { 16 | rewrite ^/_media/(.*) /lib/exe/fetch.php?media=$1 last; 17 | rewrite ^/_detail/(.*) /lib/exe/detail.php?media=$1 last; 18 | rewrite ^/_export/([^/]+)/(.*) /doku.php?do=export_$1&id=$2 last; 19 | rewrite ^/(.*) /doku.php?id=$1 last; 20 | } 21 | 22 | location ~ \.php$ { 23 | include fastcgi_params; 24 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 25 | fastcgi_pass unix:/run/php/php7.2-fpm.sock; 26 | } 27 | -------------------------------------------------------------------------------- /roles/no-turn/tasks/main.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stadtulm/a13-ansible/3b0c44d9712af5c5d3bd745c0a987cc9acf92102/roles/no-turn/tasks/main.yml -------------------------------------------------------------------------------- /roles/node-exporter-ufw/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure node-exporter config for ufw 3 | copy: 4 | content: | 5 | [node-exporter] 6 | title=node-exporter 7 | description=prometheus node exporter 8 | ports=9100/tcp 9 | mode: 0644 10 | dest: /etc/ufw/applications.d/node-exporter 11 | 12 | - name: allow node-exporter in ufw 13 | ufw: 14 | rule: allow 15 | name: node-exporter 16 | state: enabled 17 | -------------------------------------------------------------------------------- /roles/prometheus-ufw/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure prometheus config for ufw 3 | copy: 4 | content: | 5 | [prometheus] 6 | title=prometheus 7 | description=prometheus monitoring 8 | ports=9090/tcp 9 | mode: 0644 10 | dest: /etc/ufw/applications.d/prometheus 11 | 12 | - name: allow prometheus in ufw 13 | ufw: 14 | rule: allow 15 | name: prometheus 16 | state: enabled 17 | -------------------------------------------------------------------------------- /roles/restic-client/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Ensure restic binary" 3 | apt: 4 | name: restic 5 | state: present 6 | 7 | - name: "Ensure restic task base directory" 8 | file: 9 | path: "/srv/restic/" 10 | owner: root 11 | mode: '0700' 12 | state: directory 13 | 14 | - name: "Ensure restic task base for {{ backup_name }}" 15 | file: 16 | path: "/srv/restic/{{ backup_name }}/" 17 | owner: root 18 | mode: '0700' 19 | state: directory 20 | 21 | - name: "Ensure init repo script for {{ backup_name }}" 22 | template: 23 | src: "init_repo.sh" 24 | dest: "/srv/restic/{{ backup_name }}/init_repo.sh" 25 | owner: root 26 | mode: '0700' 27 | 28 | - name: "Ensure backup repository for {{ backup_name }} exists" 29 | command: "/srv/restic/{{ backup_name }}/init_repo.sh" 30 | become: yes 31 | become_user: root 32 | run_once: true 33 | 34 | - name: "Ensure Backup Script for {{ backup_name }}" 35 | template: 36 | src: "{{ backup_script }}" 37 | dest: "/srv/restic/{{ backup_name }}/{{ backup_script }}" 38 | owner: root 39 | mode: '0700' 40 | 41 | - name: "Ensure Cron Job for backup {{ backup_name }} exists" 42 | cron: 43 | name: "Backup {{ backup_name }}" 44 | minute: "0" 45 | hour: "8-22" 46 | user: root 47 | job: "/srv/restic/{{ backup_name }}/{{ backup_script }}" 48 | 49 | 50 | 51 | 52 | #- name: "check if rest server connection password file exists" 53 | # stat: 54 | # path: "/srv/restic/rest_conn_password.txt" 55 | # register: rest_conn_pw_file 56 | # 57 | #- name: "if password file exists read pw file" 58 | # command: "cat /srv/restic/rest_conn_password.txt" 59 | # register: rest_conn_pw 60 | # when: rest_conn_pw_file.stat.exists 61 | # 62 | #- name: "generate pw if not exist" 63 | # command: "openssl rand -base64 64" 64 | # register: rest_conn_pw 65 | # when: rest_conn_pw_file.stat.exists == false 66 | # 67 | #- name: "Create PW file if not exist" 68 | # copy: 69 | # content: "{{ rest_conn_pw.stdout }}" 70 | # dest: "/srv/restic/rest_conn_password.txt" 71 | # when: rest_conn_pw_file.stat.exists == false 72 | # 73 | #- name: "debug" 74 | # debug: 75 | # var: rest_conn_pw 76 | # 77 | #- name: "Ensure Password for rest-server" 78 | # set_fact: 79 | # rest_conn_password: "{{ rest_conn_pw.stdout }}" 80 | # cacheable: yes 81 | # 82 | -------------------------------------------------------------------------------- /roles/restic-client/templates/backup_directory.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export RESTIC_REPOSITORY="rest:https://{{ backup_name }}:{{ backup_conn_pw }}@{{ backup_server }}:8000/{{ backup_name }}" 4 | export RESTIC_PASSWORD="{{ backup_pw }}" 5 | 6 | LOGFILE="/srv/restic/{{ backup_name }}/restic.log" 7 | BACKUPDIR="{{ backup_dir }}" 8 | 9 | { 10 | restic backup $BACKUPDIR 11 | } 2>&1 | tee -a $LOGFILE 12 | 13 | -------------------------------------------------------------------------------- /roles/restic-client/templates/backup_postgres.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export RESTIC_REPOSITORY="rest:https://{{ backup_name }}:{{ backup_conn_pw }}@{{ backup_server }}:8000/{{ backup_name }}" 4 | export RESTIC_PASSWORD="{{ backup_pw }}" 5 | 6 | LOGFILE="/srv/restic/{{ backup_name }}/restic.log" 7 | 8 | { 9 | cd /tmp/ # "errorhandling" for sudo postgres 10 | sudo -u postgres pg_dump -F p {{ backup_pg_db }} | restic backup --stdin # 11 | } 2>&1 | tee -a $LOGFILE 12 | 13 | -------------------------------------------------------------------------------- /roles/restic-client/templates/init_repo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export RESTIC_REPOSITORY="rest:https://{{ backup_name }}:{{ backup_conn_pw }}@{{ backup_server }}:8000/{{ backup_name }}" 4 | export RESTIC_PASSWORD="{{ backup_pw }}" 5 | 6 | LOGFILE="/srv/restic/{{ backup_name }}/restic.log" 7 | 8 | { 9 | restic snapshots > /dev/null || restic init # initialize repository if not already exits 10 | } 2>&1 | tee -a $LOGFILE 11 | 12 | -------------------------------------------------------------------------------- /roles/restic-server/files/rest-server-0.9.7-linux-amd64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stadtulm/a13-ansible/3b0c44d9712af5c5d3bd745c0a987cc9acf92102/roles/restic-server/files/rest-server-0.9.7-linux-amd64 -------------------------------------------------------------------------------- /roles/restic-server/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart rest-server 3 | service: 4 | name: rest-server 5 | state: restarted 6 | 7 | - name: reload ufw 8 | service: 9 | name: ufw 10 | state: reloaded 11 | -------------------------------------------------------------------------------- /roles/restic-server/tasks/letsencrypt.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add certbot apt repo 3 | apt_repository: 4 | repo: ppa:certbot/certbot 5 | state: present 6 | 7 | - name: Install certbot 8 | apt: 9 | pkg: 10 | - certbot 11 | update_cache: true 12 | state: present 13 | 14 | - name: Ensure Let's Encrypt uses API v2 15 | lineinfile: 16 | path: /etc/letsencrypt/cli.ini 17 | regexp: '^server' 18 | line: 'server = https://acme-v02.api.letsencrypt.org/directory' 19 | 20 | - name: Register certbot certificate file 21 | stat: 22 | path: "/etc/letsencrypt/live/{{ rest_backup_server }}/fullchain.pem" 23 | register: certbot_certificate_file_path 24 | 25 | - name: Generate Certificates 26 | command: certbot certonly -d {{ rest_backup_server }} --agree-tos --email {{ letsencrypt_email }} --standalone 27 | when: not certbot_certificate_file_path.stat.exists 28 | 29 | - name: enable (limited) http in ufw for certbot renewal 30 | ufw: 31 | rule: limit 32 | port: http 33 | 34 | - name: ensure certbot renew in cron 35 | cron: 36 | name: "certbot renew weekly" 37 | special_time: weekly 38 | job: "certbot renew" 39 | user: root -------------------------------------------------------------------------------- /roles/restic-server/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Ensure certs group exists" 3 | group: 4 | name: certs 5 | state: present 6 | 7 | - name: "Ensure restic-server user" 8 | user: 9 | name: rest-server 10 | shell: /bin/bash 11 | password: '!' 12 | update_password: on_create 13 | groups: certs 14 | state: present 15 | 16 | - name: "Ensure restic-server binary" 17 | copy: 18 | src: ../files/rest-server-0.9.7-linux-amd64 19 | dest: /usr/local/bin/rest-server 20 | owner: rest-server 21 | mode: '0700' 22 | 23 | - name: "Ensure restic-server backup root directory" 24 | file: 25 | path: "{{ rest_backup_storage_dir }}" 26 | state: directory 27 | owner: rest-server 28 | mode: '0700' 29 | 30 | 31 | - name: "ensure python lib for htpasswd actions" 32 | apt: 33 | name: 34 | - python3-passlib 35 | - python3-bcrypt 36 | state: present 37 | 38 | # for certbot standalone mode 39 | - name: "ensure no Webserver installed" 40 | apt: 41 | name: 42 | - nginx 43 | - apache2 44 | state: absent 45 | 46 | - import_tasks: letsencrypt.yml 47 | 48 | - name: "Ensure /etc/rest-server" 49 | file: 50 | path: "/etc/rest-server" 51 | state: directory 52 | owner: rest-server 53 | mode: '0700' 54 | 55 | - name: "Ensure read on certpaths for certificates" 56 | file: 57 | path: /etc/letsencrypt 58 | group: certs 59 | mode: g+rx 60 | recurse: yes 61 | 62 | - name: "Ensure restic-server systemd unit" 63 | template: 64 | src: rest-server.service 65 | dest: /etc/systemd/system/rest-server.service 66 | 67 | - name: "enable rest-server systemd" 68 | systemd: 69 | name: rest-server.service 70 | state: started 71 | enabled: yes 72 | 73 | - name: "Ensure Backup User can connect" 74 | htpasswd: 75 | path: "{{ rest_backup_storage_dir }}/.htpasswd" 76 | name: "{{ item.name }}" 77 | password: "{{ item.conn_pw }}" 78 | owner: rest-server 79 | mode: "0600" 80 | crypt_scheme: bcrypt 81 | with_items: "{{ rest_backup_tasks }}" 82 | 83 | 84 | - name: adapt rest-server ports in ufw 85 | copy: 86 | content: | 87 | [rest-server] 88 | title=Restic REST Server 89 | description=REST Server for restic backup 90 | ports=8000/tcp 91 | mode: 0644 92 | dest: /etc/ufw/applications.d/rest-server 93 | 94 | - name: allow rest-server in ufw 95 | ufw: 96 | rule: allow 97 | name: rest-server 98 | state: enabled -------------------------------------------------------------------------------- /roles/restic-server/templates/rest-server.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Rest Server 3 | After=syslog.target 4 | After=network.target 5 | 6 | [Service] 7 | Type=simple 8 | User=rest-server 9 | Group=rest-server 10 | ExecStart=/usr/local/bin/rest-server --path {{ rest_backup_storage_dir }} --private-repos --tls --tls-cert /etc/letsencrypt/live/{{ rest_backup_server }}/fullchain.pem --tls-key /etc/letsencrypt/live/{{ rest_backup_server }}/privkey.pem 11 | Restart=always 12 | RestartSec=5 13 | 14 | [Install] 15 | WantedBy=multi-user.target 16 | -------------------------------------------------------------------------------- /roles/scalelite-config/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | #- name: Build a list of bbb instances with their secrets 3 | # debug: 4 | # msg: "{{ hostvars[item]['bbb_url'] }} : {{ hostvars[item]['bbb_secret'] }}" 5 | # with_items: "{{ groups['bbb'] }}" 6 | 7 | - name: Panic and remove all BBB Servers from loadbalancer before adding 8 | shell: 9 | chdir: /var/www/scalelite 10 | executable: /bin/bash 11 | cmd: "set -o allexport; source .env; set +o allexport; bundle exec ./bin/rake servers | grep id | cut -d ' ' -f2 | xargs -I % bundle exec ./bin/rake servers:{{ item }}[%]" 12 | with_items: 13 | - panic 14 | - remove 15 | become: yes 16 | become_user: scalelite 17 | 18 | - name: Ensure all servers are registered at the loadbalancer 19 | shell: 20 | chdir: /var/www/scalelite 21 | executable: /bin/bash 22 | cmd: "set -o allexport; source .env; set +o allexport; bundle exec ./bin/rake servers:add[{{ (hostvars[item]['bbb_url'] + 'api') | quote }},{{ hostvars[item]['bbb_secret'] | quote }}]" 23 | with_items: "{{ groups['bbb'] }}" 24 | when: hostvars[item]['bbb_url'] is defined 25 | become: yes 26 | become_user: scalelite 27 | 28 | - name: Enable all servers at loadbalancer 29 | shell: 30 | chdir: /var/www/scalelite 31 | executable: /bin/bash 32 | cmd: "set -o allexport; source .env; set +o allexport; bundle exec ./bin/rake servers | grep id | cut -d ' ' -f2 | xargs -I % bundle exec ./bin/rake servers:enable[%]" 33 | become: yes 34 | become_user: scalelite 35 | 36 | - name: Ensure new servers are instantly polled 37 | shell: 38 | chdir: /var/www/scalelite 39 | executable: /bin/bash 40 | cmd: "set -o allexport; source .env; set +o allexport; bundle exec ./bin/rake poll:all" 41 | become: yes 42 | become_user: scalelite 43 | 44 | # FIXME: aquire scalelite secret 45 | -------------------------------------------------------------------------------- /roles/scalelite/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart scalelite 3 | service: 4 | name: scalelite.target 5 | state: restarted 6 | -------------------------------------------------------------------------------- /roles/scalelite/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure group "scalelite" exists 3 | group: 4 | name: scalelite 5 | state: present 6 | 7 | - name: Ensure user scalelite exists 8 | user: 9 | name: scalelite 10 | shell: /bin/bash 11 | password: '!' 12 | update_password: on_create 13 | groups: scalelite 14 | state: present 15 | 16 | - name: Checkout scalelite repository 17 | git: 18 | repo: https://github.com/blindsidenetworks/scalelite.git 19 | dest: /var/www/scalelite 20 | version: b49c803e6db62cabed3fa49796f6e8917d222bfb 21 | notify: restart scalelite 22 | 23 | 24 | - name: Ensure scalelite is owner of /var/www/scalelite 25 | file: 26 | path: /var/www/scalelite 27 | recurse: yes 28 | owner: scalelite 29 | group: scalelite 30 | 31 | - name: Ensure Bundler 2 is installed 32 | gem: 33 | name: bundler 34 | state: latest 35 | user_install: no 36 | 37 | - name: Ensure sqlite is installed 38 | apt: 39 | name: 40 | - sqlite3 41 | - libsqlite3-dev 42 | state: present 43 | 44 | # bundler: deployment mode by cli argument is deprecated, so we're going to run this: 45 | - name: Ensure bundle in is deployment mode 46 | command: 47 | cmd: bundle config set deployment 'true' 48 | chdir: /var/www/scalelite 49 | creates: /var/www/scalelite/.bundle/config 50 | become: yes 51 | become_user: scalelite 52 | 53 | - name: Ensure gems for scalelite are installed 54 | bundler: 55 | state: present 56 | exclude_groups: 57 | - development 58 | - test 59 | chdir: /var/www/scalelite 60 | become: yes 61 | become_user: scalelite 62 | 63 | - name: Generate scalelite secret key base 64 | command: openssl rand -hex 64 65 | register: openssl_rand64 66 | check_mode: no 67 | when: not scalelite_secret_key_base is defined 68 | 69 | - name: Register secret key base 70 | set_fact: 71 | scalelite_secret_key_base: "{{ openssl_rand64.stdout }}" 72 | when: not scalelite_secret_key_base is defined 73 | 74 | - name: Generate scalelite loadbalancer secret 75 | command: openssl rand -hex 32 76 | register: openssl_rand32 77 | check_mode: no 78 | when: not scalelite_loadbalancer_secret is defined 79 | 80 | - name: Register loadbalancer secret 81 | set_fact: 82 | scalelite_loadbalancer_secret: "{{ openssl_rand32.stdout }}" 83 | when: not scalelite_loadbalancer_secret is defined 84 | 85 | - name: Ensure environment file for scalelite 86 | template: 87 | src: env.j2 88 | dest: /var/www/scalelite/.env 89 | become: yes 90 | become_user: scalelite 91 | 92 | - name: Ensure systemd unit for scalelite api 93 | template: 94 | src: scalelite-api.service 95 | dest: /etc/systemd/system/scalelite-api.service 96 | 97 | - name: Ensure systemd unit for scalelite poller 98 | template: 99 | src: scalelite-poller.service 100 | dest: /etc/systemd/system/scalelite-poller.service 101 | 102 | - name: Ensure systemd target for scalelite 103 | template: 104 | src: scalelite.target 105 | dest: /etc/systemd/system/scalelite.target 106 | 107 | - name: Enable scalelite units 108 | systemd: 109 | daemon_reload: yes 110 | name: "{{ item }}" 111 | enabled: yes 112 | with_items: 113 | - scalelite-api 114 | - scalelite-poller 115 | 116 | - name: Count scalelite database tables 117 | shell: 118 | cmd: "psql -h 127.0.0.1 -U scalelite scalelite -qAt -c 'select count(*) from pg_stat_user_tables;'" 119 | check_mode: no 120 | register: scalelite_database_tablecount 121 | 122 | - name: Ensure database schema is loaded 123 | shell: 124 | chdir: /var/www/scalelite 125 | executable: /bin/bash 126 | cmd: "set -o allexport; source .env; set +o allexport; bundle exec rails db:schema:load" 127 | become: yes 128 | become_user: scalelite 129 | when: scalelite_database_tablecount.stdout|int <= 1 130 | 131 | - name: Ensure scalelite-api is started 132 | systemd: 133 | name: scalelite-api 134 | state: started 135 | 136 | - name: Ensure scalelite-poller is started 137 | systemd: 138 | name: scalelite-poller 139 | state: started 140 | 141 | - name: Ensure scalelite nginx config exists 142 | template: 143 | src: nginx-scalelite.conf 144 | dest: /etc/nginx/a13.d/scalelite.conf 145 | notify: reload nginx 146 | 147 | - name: Restart Scalelite every night @4am 148 | cron: 149 | state: present 150 | name: "restart Scalelite" 151 | minute: "0" 152 | hour: "4" 153 | user: root 154 | job: "systemctl restart scalelite.target" -------------------------------------------------------------------------------- /roles/scalelite/templates/env.j2: -------------------------------------------------------------------------------- 1 | RAILS_ENV=production 2 | RAILS_LOG_TO_STDOUT=1 3 | DISABLE_SPRING=1 4 | BIND=tcp://127.0.0.1:3000 5 | DATABASE_URL=postgresql://scalelite:trust@127.0.0.1:5432/scalelite 6 | REDIS_URL=redis://127.0.0.1:6379 7 | SECRET_KEY_BASE={{ scalelite_secret_key_base }} 8 | LOADBALANCER_SECRET={{ scalelite_loadbalancer_secret }} 9 | # BUILD_NUMBER=${CI_COMMIT_TAG:-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHORT_SHA}} 10 | -------------------------------------------------------------------------------- /roles/scalelite/templates/nginx-scalelite.conf: -------------------------------------------------------------------------------- 1 | location / { 2 | proxy_pass http://127.0.0.1:3000; 3 | 4 | proxy_read_timeout 60s; 5 | proxy_redirect off; 6 | 7 | proxy_set_header Host $http_host; 8 | 9 | proxy_set_header X-Real-IP $remote_addr; 10 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 11 | proxy_set_header X-Forwarded-Proto $scheme; 12 | proxy_set_header X-Request-Id $request_id; 13 | 14 | proxy_http_version 1.1; 15 | } 16 | -------------------------------------------------------------------------------- /roles/scalelite/templates/scalelite-api.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Scalelite API 3 | After=network-online.target 4 | Wants=network-online.target 5 | Before=scalelite.target 6 | PartOf=scalelite.target 7 | 8 | [Service] 9 | Type=simple 10 | EnvironmentFile=/var/www/scalelite/.env 11 | WorkingDirectory=/var/www/scalelite 12 | Restart=always 13 | RestartSec=60 14 | ExecStart=/usr/local/bin/bundle exec puma -C config/puma.rb 15 | User=scalelite 16 | 17 | [Install] 18 | WantedBy=scalelite.target 19 | -------------------------------------------------------------------------------- /roles/scalelite/templates/scalelite-poller.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Scalelite Poller 3 | After=network-online.target 4 | Wants=network-online.target 5 | Before=scalelite.target 6 | PartOf=scalelite.target 7 | 8 | [Service] 9 | Type=simple 10 | EnvironmentFile=/var/www/scalelite/.env 11 | WorkingDirectory=/var/www/scalelite 12 | Restart=always 13 | RestartSec=60 14 | ExecStart=/usr/local/bin/bundle exec rake poll 15 | User=scalelite 16 | 17 | [Install] 18 | WantedBy=scalelite.target 19 | -------------------------------------------------------------------------------- /roles/scalelite/templates/scalelite.target: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Scalelite 3 | 4 | [Install] 5 | WantedBy=multi-user.target 6 | -------------------------------------------------------------------------------- /roles/turn-standalone/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart coturn 3 | service: 4 | name: coturn 5 | state: restarted 6 | -------------------------------------------------------------------------------- /roles/turn-standalone/tasks/certbot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure 'ssl-cert' group exists 3 | group: 4 | name: ssl-cert 5 | state: present 6 | 7 | - name: Copy certbot-zerossl Wrapper for certbot 8 | template: 9 | src: certbot-zerossl.sh 10 | dest: /usr/local/bin/certbot-zerossl 11 | owner: root 12 | group: root 13 | mode: u=rwx,g=rx,o=rx 14 | when: certbot_source == "zerossl" 15 | 16 | - name: Install certbot 17 | apt: 18 | name: certbot 19 | update_cache: true 20 | state: present 21 | 22 | - name: Install required packages for zerossl 23 | apt: 24 | name: certbot 25 | update_cache: true 26 | state: present 27 | when: certbot_source == "zerossl" 28 | 29 | - name: Ensure ZeroSSL/LE Directories are owned by ssl-cert group 30 | file: 31 | path: "{{ item }}" 32 | owner: root 33 | group: ssl-cert 34 | mode: g+rx 35 | state: directory 36 | with_items: 37 | - /etc/letsencrypt/live 38 | - /etc/letsencrypt/archive 39 | - /etc/letsencrypt/keys 40 | 41 | - name: Register certbot certificate file 42 | stat: 43 | path: "/etc/letsencrypt/live/{{ turn_hostname }}/fullchain.pem" 44 | register: certbot_certificate_file_path 45 | 46 | - name: Generate ZeroSSL Certificates 47 | command: certbot-zerossl certonly -d {{ turn_hostname }} -d {{ ansible_fqdn }} --agree-tos --email {{ letsencrypt_email }} --standalone 48 | when: not certbot_certificate_file_path.stat.exists and certbot_source == "zerossl" 49 | 50 | - name: Generate Lets Encrypt Certificates 51 | command: certbot certonly -d {{ turn_hostname }} -d {{ ansible_fqdn }} --agree-tos --email {{ letsencrypt_email }} --standalone 52 | when: not certbot_certificate_file_path.stat.exists and certbot_source == "letsencrypt" 53 | 54 | - name: Ensure ZeroSSL/LE Certificate and Keys are owned by ssl-cert group and readable 55 | file: 56 | path: "{{ item }}" 57 | mode: 'u=rw,g=r' 58 | group: ssl-cert 59 | with_items: 60 | - /etc/letsencrypt/live/{{ turn_hostname }}/fullchain.pem 61 | - /etc/letsencrypt/live/{{ turn_hostname }}/privkey.pem 62 | -------------------------------------------------------------------------------- /roles/turn-standalone/tasks/coturn.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install coturn 3 | apt: 4 | name: coturn 5 | update_cache: true 6 | state: present 7 | 8 | - name: Copy systemd coturn unit 9 | template: 10 | src: "coturn.service" 11 | dest: /etc/systemd/system/coturn.service 12 | 13 | - name: Ensure coturn is in ssl-cert group 14 | user: 15 | name: turnserver 16 | groups: ssl-cert 17 | append: yes 18 | state: present 19 | 20 | - name: Enable coturn 21 | systemd: 22 | daemon_reload: yes 23 | name: coturn 24 | enabled: yes 25 | 26 | - name: Copy coturn config-file 27 | template: 28 | src: "turnserver.conf.j2" 29 | dest: /etc/turnserver.conf 30 | notify: restart coturn 31 | 32 | - name: Enable coturn server in defaults 33 | lineinfile: 34 | path: /etc/default/coturn 35 | regexp: 'TURNSERVER_ENABLED' 36 | line: 'TURNSERVER_ENABLED=1' 37 | notify: restart coturn 38 | 39 | - name: Disable IPv6 if coturn is set to IPv4 only 40 | sysctl: 41 | name: net.ipv6.conf.all.disable_ipv6 42 | value: '1' 43 | sysctl_set: yes 44 | state: present 45 | reload: yes 46 | when: turn_ipv4_only 47 | 48 | - name: Ensure Coturn Logfile 49 | file: 50 | path: /var/log/coturn.log 51 | owner: turnserver 52 | mode: '0600' 53 | state: touch 54 | 55 | - name: Ensure Log rotation 56 | template: 57 | src: coturn.logrotate 58 | dest: /etc/logrotate.d/coturn 59 | 60 | - name: Ensure coturn is started 61 | systemd: 62 | name: coturn 63 | state: started 64 | 65 | - name: Ensure coturn is restarted everyday @ 4 am 66 | cron: 67 | name: "restart coturn" 68 | minute: "0" 69 | hour: "4" 70 | user: root 71 | job: "systemctl restart coturn.service" -------------------------------------------------------------------------------- /roles/turn-standalone/tasks/firewall.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: adapt letsencrypt/zerossl standalone ports in ufw 3 | copy: 4 | content: | 5 | [letsencrypt] 6 | title=letsencrypt standalone on 80,443 7 | description=Standalone certbot certificate aquiring 8 | ports=80,443/tcp 9 | mode: 0644 10 | dest: /etc/ufw/applications.d/letsencrypt 11 | 12 | - name: allow letsencrypt/zerossl in ufw 13 | ufw: 14 | rule: allow 15 | name: letsencrypt 16 | state: enabled 17 | 18 | - name: adapt coturn ports in ufw 19 | copy: 20 | content: | 21 | [turnserver] 22 | title=Coturn turnserver 23 | description=Free open source implementation of TURN and STUN Server 24 | ports=443,444,3478,3479,32768:65535/tcp|443,444,3478,3479,32768:65535/udp 25 | mode: 0644 26 | dest: /etc/ufw/applications.d/turnserver 27 | 28 | - name: allow turnserver in ufw 29 | ufw: 30 | rule: allow 31 | name: turnserver 32 | state: enabled 33 | -------------------------------------------------------------------------------- /roles/turn-standalone/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - import_tasks: firewall.yml 3 | 4 | - import_tasks: certbot.yml 5 | 6 | - import_tasks: coturn.yml 7 | -------------------------------------------------------------------------------- /roles/turn-standalone/templates/certbot-zerossl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CERTBOT_ARGS=() 4 | 5 | function parse_eab_credentials() 6 | { 7 | PYTHONIOENCODING=utf8 8 | ZEROSSL_EAB_KID=$(echo $1 | python3 -c "import sys, json; print(json.load(sys.stdin)['eab_kid'])") 9 | ZEROSSL_EAB_HMAC_KEY=$(echo $1 | python3 -c "import sys, json; print(json.load(sys.stdin)['eab_hmac_key'])") 10 | CERTBOT_ARGS+=(--eab-kid "$ZEROSSL_EAB_KID" --eab-hmac-key "$ZEROSSL_EAB_HMAC_KEY" --server "https://acme.zerossl.com/v2/DV90") 11 | } 12 | 13 | while [[ "$#" -gt 0 ]]; do 14 | case $1 in 15 | --zerossl-api-key=*) 16 | ZEROSSL_API_KEY="${1:18}" 17 | ;; 18 | --zerossl-api-key|-z) 19 | ZEROSSL_API_KEY="${2}" 20 | shift 21 | ;; 22 | --zerossl-email=*) 23 | ZEROSSL_EMAIL="${1:16}" 24 | ;; 25 | --email|--zerossl-email|-m) 26 | ZEROSSL_EMAIL="${2}" 27 | CERTBOT_ARGS+=(-m "${2}") 28 | shift 29 | ;; 30 | *) CERTBOT_ARGS+=($1) ;; 31 | esac 32 | shift 33 | done 34 | 35 | if [[ -n $ZEROSSL_API_KEY ]]; then 36 | parse_eab_credentials $(curl -s -X POST "https://api.zerossl.com/acme/eab-credentials?access_key=$ZEROSSL_API_KEY") 37 | elif [[ -n $ZEROSSL_EMAIL ]]; then 38 | parse_eab_credentials $(curl -s https://api.zerossl.com/acme/eab-credentials-email --data "email=$ZEROSSL_EMAIL") 39 | fi 40 | 41 | certbot ${CERTBOT_ARGS[@]} 42 | -------------------------------------------------------------------------------- /roles/turn-standalone/templates/coturn.logrotate: -------------------------------------------------------------------------------- 1 | /var/log/coturn.log 2 | { 3 | rotate 30 4 | daily 5 | missingok 6 | notifempty 7 | delaycompress 8 | compress 9 | postrotate 10 | systemctl kill -sHUP coturn.service 11 | endscript 12 | } -------------------------------------------------------------------------------- /roles/turn-standalone/templates/coturn.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Coturn STUN/TURN Server 3 | Documentation=man:coturn(1) man:turnadmin(1) man:turnserver(1) 4 | After=network.target 5 | After=network-online.target 6 | After=remote-fs.target 7 | Wants=network-online.target 8 | 9 | [Service] 10 | User=turnserver 11 | Group=turnserver 12 | Type=forking 13 | RuntimeDirectory=turnserver 14 | PIDFile=/run/turnserver/turnserver.pid 15 | ExecStart=/usr/bin/turnserver --daemon -c /etc/turnserver.conf --pidfile /run/turnserver/turnserver.pid 16 | #FixMe: turnserver exit faster than it is finshing the setup and ready for handling the connection. 17 | ExecStartPost=/bin/sleep 2 18 | Restart=on-failure 19 | InaccessibleDirectories=/home 20 | PrivateTmp=yes 21 | AmbientCapabilities=CAP_NET_BIND_SERVICE 22 | LimitNOFILE=1048576 23 | 24 | [Install] 25 | WantedBy=multi-user.target 26 | Alias=turnserver.service 27 | -------------------------------------------------------------------------------- /roles/turn-standalone/templates/turnserver.conf.j2: -------------------------------------------------------------------------------- 1 | listening-port=3478 2 | tls-listening-port=443 3 | 4 | listening-ip={{ ansible_default_ipv4.address }} 5 | relay-ip={{ ansible_default_ipv4.address }} 6 | 7 | {% if turn_ipv4_only is sameas false %} 8 | relay-ip={{ ansible_default_ipv6.address }} 9 | listening-ip={{ ansible_default_ipv6.address }} 10 | {% endif %} 11 | 12 | min-port=32768 13 | max-port=65535 14 | 15 | fingerprint 16 | lt-cred-mech 17 | use-auth-secret 18 | static-auth-secret={{ turn_secret }} 19 | realm={{ turn_hostname }} 20 | 21 | cert=/etc/letsencrypt/live/{{ turn_hostname }}/fullchain.pem 22 | pkey=/etc/letsencrypt/live/{{ turn_hostname }}/privkey.pem 23 | 24 | # Limit the allowed ciphers to improve security 25 | # Based on https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ 26 | cipher-list="ECDH+AESGCM:ECDH+CHACHA20:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS" 27 | 28 | # Enable longer DH TLS key to improve security 29 | dh2066 30 | 31 | keep-address-family 32 | no-cli 33 | no-tlsv1 34 | no-tlsv1_1 35 | 36 | # Log to a single filename (rather than new log files each startup). You'll 37 | # want to install a logrotate configuration (see below) 38 | log-file=/var/log/coturn.log 39 | 40 | # To enable single filename logs you need to enable the simple-log flag 41 | simple-log 42 | 43 | # Block connections to IP ranges which shouldn't be reachable 44 | no-loopback-peers 45 | no-multicast-peers 46 | # CVE-2020-26262 47 | # If running coturn version older than 4.5.2, uncomment these rules and ensure 48 | # that you have listening-ip set to ipv4 addresses only. 49 | #denied-peer-ip=0.0.0.0-0.255.255.255 50 | #denied-peer-ip=127.0.0.0-127.255.255.255 51 | #denied-peer-ip=::1 52 | # Private (LAN) addresses 53 | # If you are running BigBlueButton within a LAN, you might need to add an "allow" rule for your address range. 54 | # IPv4 Private-Use 55 | denied-peer-ip=10.0.0.0-10.255.255.255 56 | denied-peer-ip=172.16.0.0-172.31.255.255 57 | denied-peer-ip=192.168.0.0-192.168.255.255 58 | # Other IPv4 Special-Purpose addresses 59 | denied-peer-ip=100.64.0.0-100.127.255.255 60 | denied-peer-ip=169.254.0.0-169.254.255.255 61 | denied-peer-ip=192.0.0.0-192.0.0.255 62 | denied-peer-ip=192.0.2.0-192.0.2.255 63 | denied-peer-ip=198.18.0.0-198.19.255.255 64 | denied-peer-ip=198.51.100.0-198.51.100.255 65 | denied-peer-ip=203.0.113.0-203.0.113.255 66 | # IPv6 Unique-Local 67 | denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 68 | # IPv6 Link-Local Unicast 69 | denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff 70 | # Other IPv6 Special-Purpose assignments 71 | denied-peer-ip=::ffff:0:0-::ffff:ffff:ffff 72 | denied-peer-ip=64:ff9b::-64:ff9b::ffff:ffff 73 | denied-peer-ip=64:ff9b:1::-64:ff9b:1:ffff:ffff:ffff:ffff:ffff 74 | denied-peer-ip=2001::-2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff 75 | denied-peer-ip=2001:db8::-2001:db8:ffff:ffff:ffff:ffff:ffff:ffff 76 | denied-peer-ip=2002::-2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff 77 | -------------------------------------------------------------------------------- /tmp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stadtulm/a13-ansible/3b0c44d9712af5c5d3bd745c0a987cc9acf92102/tmp/.gitkeep -------------------------------------------------------------------------------- /vars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | users: 3 | - { name: 'robbi5', sshkeys: 'https://github.com/robbi5.keys' } 4 | - { name: 'stk', sshkeys: 'https://github.com/stkdiretto.keys' } 5 | - { name: 'ubahnverleih', sshkeys: 'https://github.com/ubahnverleih.keys' } 6 | - { name: 'srf', sshkeys: 'https://github.com/sebastianfischer.keys' } 7 | 8 | letsencrypt_email: admin@ulmlernt.de 9 | 10 | mail_admin: 11 | { server: 'smtp.ionos.de', port: '587', domain: 'ulmlernt.de', 12 | user: 'admin@ulmlernt.de', auth: 'plain', starttls: 'true', sender: 'admin@ulmlernt.de' } 13 | 14 | mail_admin_password: !vault | 15 | $ANSIBLE_VAULT;1.1;AES256 16 | 33613237653334643333306539356430623662306537656235386539343261323435396637346233 17 | 6662366662653264653339333135383934616637316661640a363934663864663066633134363935 18 | 64376439373363363834643532313062633134393933636239346430353561623931396365306465 19 | 3365616636656461610a373262633630393030366630626239656266313066343034376565363738 20 | 34313163633866343739653832373534663436653237373130333364626263303730343965356635 21 | 6630323530356661323535613365326662366466333764633164 22 | 23 | greenlight_key_base: !vault | 24 | $ANSIBLE_VAULT;1.1;AES256 25 | 39303265396332343262343933373433393763363836363331366139646264336634363562386661 26 | 3735333036336539316338373365643961393961356631330a373065343434373933353538393236 27 | 38623064343866376134373132313262373039393464653239623532653233663139323331643065 28 | 3630663839333830650a393166386333666238633265313462653636303638383761643736663232 29 | 66386237386339646466666238333836386237396136373432646632646330303337336562633630 30 | 38653234356639666564316432303537643162303534623230653065333934663533393038653162 31 | 39336461383630383633343535343334353334383938663134393466663732356332653939316431 32 | 36666461323636366633306239623933626635323836613536333532366364323532333130323762 33 | 62363161613937356230633666383138663037326435663139353261373263613738336131333765 34 | 64326531653833386538316631363534353439386664366663633436393263366162613964366533 35 | 353665356234396461663137333162663134 36 | 37 | greenlight_postgres_password: !vault | 38 | $ANSIBLE_VAULT;1.1;AES256 39 | 39353834613161356466346161613062303332616632343535333435303831373465376338326138 40 | 3766353639306133376138663363306233373131663162320a316532643565323664396264666335 41 | 39623161336138303938313436336331333535613539656236326335353538383065373630316536 42 | 3463343736643265610a396164626464626365333366663531343761386538356232323461633133 43 | 37666639306436626436386136646436343533363436373536623865326264306237 44 | 45 | scalelite_loadbalancer_url: "https://lb.ulmlernt.org/bigbluebutton/" 46 | scalelite_loadbalancer_secret: !vault | 47 | $ANSIBLE_VAULT;1.1;AES256 48 | 33316130636133336266613962643637613662616530646564356235333533306331323635393830 49 | 3538653839326331373066653337353339636164333531380a653563306430373637646532656166 50 | 62326638363562346565626138326437626339636464613834396137386266326663623261366665 51 | 3636353638623166300a616431383664343538636537666534323564373763633336386562383534 52 | 36373534373530313861373134393634333437346530366232623035386162383463343064623862 53 | 33303635653534643737656166363038616564656530366437393436343537343933636261343936 54 | 61373039663131663132366133643263666436346362323530386234663037373235366333643139 55 | 64396661373561376164 56 | 57 | monitoring_grafana_admin_password: !vault | 58 | $ANSIBLE_VAULT;1.1;AES256 59 | 30366237316237353732313038643866373533313837336136333464636233663738663433643661 60 | 3039386337643663316330393464656261316332343263300a343861383534356236373166656235 61 | 35353337303435383261323238663665373131663433326530633933346130656465376137376234 62 | 6535613334373838380a346165653830373066316232343139323231613537303939653631393735 63 | 62333430383961333063316663613436353137616539383135643537333761356635 64 | 65 | monitoring_github_client_id: !vault | 66 | $ANSIBLE_VAULT;1.1;AES256 67 | 61613032633166643564343530616562306635366537333761393638626237613137363532383361 68 | 3362333364313562613636656261666437633065653338640a393237333437376362643431366562 69 | 39393362636130313161653462383230633230386636323963306135383232616261653061356236 70 | 6537616263636664340a316666363363613066386337333731363338653936323435323332313665 71 | 64613235303533303061386135343831323333663561316466633166616538636131 72 | 73 | monitoring_github_client_secret: !vault | 74 | $ANSIBLE_VAULT;1.1;AES256 75 | 64313333653634353637373539353362343331626330353833316262343234336663343064653537 76 | 3933343131303436653336653936623863613538643634640a623539313239323535323534366335 77 | 36633539323734353131616637346664363162383131633764653338663534663262303636653062 78 | 3939623836666463370a636362623464356137353231616436303433383535623135636331303465 79 | 39653739326537353536353433646638376139333738333432646561373836373230313833643665 80 | 6633323363353132363532656330373130383435323137663934 81 | 82 | turn_secret: !vault | 83 | $ANSIBLE_VAULT;1.1;AES256 84 | 65636566386434313831343137383835636636363933376239373264643734383233343262323036 85 | 3131366433346139626630386233623037323235646233630a633131356536313739626335343837 86 | 32306238393432613366633132646365326333383864343563633137343234663336636637636663 87 | 3732663330356365640a656635393466303363303362353033353239326438623232656432333933 88 | 34643265656665643465636665656337616264636465613933653335353664343139393363326532 89 | 3061333734626431326334663962356230376163636334336535 90 | 91 | restic_backup_server: "bck.ulmlernt.org" 92 | 93 | restic_backup_dokuwiki_conn_pw: !vault | 94 | $ANSIBLE_VAULT;1.1;AES256 95 | 64346536323264313162633037623163306330393533623563373437303132666232303765376363 96 | 3130363734313166633738663332396332353236616533620a393937366466366235353364353963 97 | 61363431383134383661653961333336373365663365373037376266383537323461643034666334 98 | 6330386430656235370a373239343964616638386261623039643665643731653665643938306566 99 | 30366266643135366462396334623631626665336330313536373331643062613532353339316437 100 | 64323035643639396139353361346632306365636339623365313936316231303663643666376436 101 | 36623638393538663263616235643863383737643438356237623339343535363464353864386265 102 | 62356637636430363761386661313162393136353564643737343738356332343264346231323730 103 | 3635 104 | restic_backup_dokuwiki_backup_pw: !vault | 105 | $ANSIBLE_VAULT;1.1;AES256 106 | 65316563343230616634636237313534306339663336663030343965393666356639643166366136 107 | 3630633261343565336162613534653034666232316562320a653863623430616434646638373639 108 | 30663533646638316131333965643137636532303337386631343062303737623537303664386635 109 | 6437366138386331350a656666633437303363643163656330653766636439366338353365656136 110 | 62653730646431313336333463376466313434633738353339306334323233303561653130363838 111 | 38333435363763393631376535316266373839346331653931333934663961326664303138323932 112 | 30323263666565666537393264396137623334303464336166356162363936323535346566353431 113 | 35393962366234616534346136613738303735326533386637663763313135636330343935323866 114 | 3239 115 | 116 | restic_backup_greenlight_conn_pw: !vault | 117 | $ANSIBLE_VAULT;1.1;AES256 118 | 62383866393731663735303166363936303635393761373534643137336462383636646235303461 119 | 3865353238373832653665623763656565633161636535340a363138636333393037353532626538 120 | 37373538323936316664383433323864383366623139373665316432656133323032623939643866 121 | 6364656437613539320a313638653062626432386239616136633435376239643331306563383464 122 | 36313233663137363735393735383931646634333436366535353531633133333137393135383232 123 | 32396461333331623161393665653438316233303261616336333533313265623861303636663863 124 | 38303535313164316537626664623838326537613934623435373731303362363736636638316263 125 | 35646561666534643134 126 | 127 | restic_backup_greenlight_backup_pw: !vault | 128 | $ANSIBLE_VAULT;1.1;AES256 129 | 39633430306338633963343665343036366438386266336533306265373335396538333134343962 130 | 3463323232656136616339346334646566663037393439360a306433623563663534326537333931 131 | 61383866373531613765623232653133653664666663346262666337626365316263363638393836 132 | 3666316537366565610a363034383738323337386336346436336337663539653463353038636561 133 | 38633565363865656538323365363665366233613338636631666663623033306566623863616636 134 | 34373965303432326432303539353931333062313330613534643837666339613963623537663433 135 | 37613264383066393437356138313837366531666638306366633863343561333736393536386262 136 | 65633439316537373734 137 | 138 | restic_backup_zammad_conn_pw: !vault | 139 | $ANSIBLE_VAULT;1.1;AES256 140 | 62373462366366333136363735643339356434666236343136313132396339623939343564353738 141 | 3662646462313839333836313833636438353465323662300a623638383730633535626430336537 142 | 65383035313835303034376435333562633061663361303033373765356362313164616538336537 143 | 6161323032646330340a383031643664613564323366393735326665303232326262623237376636 144 | 39376639366430616662353336396131663237633330323965353135383834333938616230333137 145 | 36326262363366313838323234393765613965383361353038316534343430396462346234623734 146 | 33623164646434396634646362306138303866343164366632383434613833336433613830323863 147 | 35366633326637626436633732393334666334326663366336323431666234666336343766373836 148 | 64393864313934373835616634353935353630386361663932633331373638303563393666643832 149 | 32316237646265393131653732613831373237353064393162306239633264663064396231653064 150 | 663763613932393230386262326465616538 151 | 152 | restic_backup_zammad_backup_pw: !vault | 153 | $ANSIBLE_VAULT;1.1;AES256 154 | 33353266343131366663656466663030303634356666333239646364656130646331613432316539 155 | 3334663335366535393431326435373262393534346364630a303437363838333637303434616234 156 | 65383331353238653439633234393333643062366334663562303533323965313766623936383733 157 | 3131306230386566380a373363353766376232356336623864346166373035643335363563653661 158 | 62343039633337346161626466336665363737386230353965656361613731313437396566613066 159 | 35653964306235653162323366366330323661363761663238386636363162373865343563633030 160 | 33313862316463363233386162613236353063346138386534666535623932363030366661343331 161 | 65366532346530643338666539366435363064313932353865353064366336613733313964653634 162 | 63613638373137626363393166633935363637303562363131656563333564373161356235666433 163 | 37656130623861353564363561376562613362386366313430616132336166613831393739663834 164 | 346163353230343661646337313763333636 --------------------------------------------------------------------------------